Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fe19cdf
feat(extension-manager): add Card/CardGrid primitives and component t…
gene9831 Aug 18, 2026
2333dc5
feat(extension-manager): add manager composition and public API
gene9831 Aug 18, 2026
918a48b
feat: add playwright cache-development-mode to .gitignore and create …
gene9831 Aug 18, 2026
4e87d6a
Merge branch 'codex/pr1-extension-card-grid' of github.com:gene9831/t…
gene9831 Aug 18, 2026
780d521
feat: update ExtensionCard and ExtensionCardActions to use role="swit…
gene9831 Aug 18, 2026
1f3e755
Merge branch 'codex/pr1-extension-card-grid' of github.com:gene9831/t…
gene9831 Aug 18, 2026
56cbea2
feat: update ExtensionManager test to use click on switch track for e…
gene9831 Aug 18, 2026
cf5a529
feat: implement useStableId composable and update ExtensionCardPopove…
gene9831 Aug 18, 2026
64a54e6
Merge branch 'codex/pr1-extension-card-grid' of github.com:gene9831/t…
gene9831 Aug 18, 2026
451dfc6
feat: replace useId with useStableId for consistent ID generation in …
gene9831 Aug 18, 2026
d697e55
fix: adjust min-width for button styles and correct event listener usage
gene9831 Aug 18, 2026
ad12a0b
Merge branch 'codex/pr1-extension-card-grid' of github.com:gene9831/t…
gene9831 Aug 18, 2026
322251a
style: update padding and border-radius for ExtensionCardMoreMenu and…
gene9831 Aug 19, 2026
a27cc08
Merge branch 'codex/pr1-extension-card-grid' of github.com:gene9831/t…
gene9831 Aug 19, 2026
ad7e8e0
style: refactor CSS for ExtensionCard components to use nested select…
gene9831 Aug 19, 2026
623cf92
Merge branch 'codex/pr1-extension-card-grid' into codex/pr2-extension…
gene9831 Aug 20, 2026
db3ac40
style: enhance input and select components with improved padding, min…
gene9831 Aug 20, 2026
68b11c7
Merge branch 'develop' of github.com:opentiny/tiny-robot into codex/p…
gene9831 Aug 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<script setup lang="ts">
import { IconArrowDown, IconClose } from '@opentiny/tiny-robot-svgs'
import type { ExtensionManagerTagOption } from '../index.type'

defineOptions({ name: 'ExtensionFilterControls' })

const props = defineProps<{
tags: readonly ExtensionManagerTagOption[]
}>()

const selectedTag = defineModel<string>('selectedTag', { default: '' })
const searchValue = defineModel<string>('searchValue', { default: '' })
</script>

<template>
<div class="extension-filter-controls" data-testid="extension-filter-controls">
<div data-testid="filter-root" class="extension-filter-controls__fields">
<div class="extension-filter-controls__select-wrapper">
<select
data-testid="filter-tag"
class="extension-filter-controls__select"
aria-label="标签"
:disabled="props.tags.length === 0"
v-model="selectedTag"
>
<option value="">全部标签</option>
<option v-for="tag in props.tags" :key="tag.value" :value="tag.value">{{ tag.label }}</option>
</select>
<IconArrowDown class="extension-filter-controls__select-arrow" aria-hidden="true" />
</div>
<div class="extension-filter-controls__input-wrapper">
<input
data-testid="filter-search"
class="extension-filter-controls__input"
type="search"
aria-label="搜索扩展"
placeholder="请输入关键字搜索"
v-model="searchValue"
/>
<button
v-if="searchValue"
data-testid="filter-search-clear"
class="extension-filter-controls__clear"
type="button"
aria-label="清空搜索"
@click="searchValue = ''"
>
<IconClose class="extension-filter-controls__clear-icon" aria-hidden="true" />
</button>
</div>
</div>
</div>
</template>

<style lang="less" scoped>
.extension-filter-controls {
container: extension-filter-controls / inline-size;
padding: 16px 0;
}

.extension-filter-controls__select,
.extension-filter-controls__input {
box-sizing: border-box;
width: 100%;
min-width: 0;
height: 32px;
padding: 0 12px;
border: 1px solid var(--tr-mcp-server-picker-field-border-color);
border-radius: 8px;
outline: none;
background: var(--tr-container-bg-default);
color: var(--tr-text-primary);
font-size: 13px;

&:focus {
border-color: var(--tr-mcp-server-picker-tabs-border-color-active);
}
}

.extension-filter-controls__fields {
display: grid;
grid-template-columns: minmax(200px, 1fr) minmax(0, 2fr);
gap: 8px;
}

.extension-filter-controls__select-wrapper,
.extension-filter-controls__input-wrapper {
position: relative;
min-width: 0;
}

.extension-filter-controls__select {
appearance: none;
padding-right: 32px;

&:disabled + .extension-filter-controls__select-arrow {
opacity: 0.5;
}
}

.extension-filter-controls__select-arrow {
position: absolute;
top: 50%;
right: 10px;
width: 16px;
height: 16px;
transform: translateY(-50%);
color: var(--tr-text-secondary);
pointer-events: none;
}

.extension-filter-controls__input {
appearance: none;
padding-right: 40px;

&::-webkit-search-cancel-button {
appearance: none;
}
}

.extension-filter-controls__clear {
position: absolute;
top: 50%;
right: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
padding: 0;
transform: translateY(-50%);
border: 0;
border-radius: 6px;
outline: none;
background: transparent;
color: var(--tr-text-secondary);
cursor: pointer;

&:hover {
color: var(--tr-text-primary);
}

&:focus-visible {
outline: 2px solid var(--tr-mcp-server-picker-tabs-border-color-active);
outline-offset: -2px;
}
}

.extension-filter-controls__clear-icon {
width: 14px;
height: 14px;
}

@container extension-filter-controls (max-width: 480px) {
.extension-filter-controls__fields {
grid-template-columns: 1fr;
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ExtensionCardGridItem } from '../index.type'
import type { ExtensionManagerSectionKey } from '../index.type'

export interface ExtensionManagerSectionState {
key: ExtensionManagerSectionKey
title: string
items: ExtensionCardGridItem[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script setup lang="ts">
import { IconArrowDown } from '@opentiny/tiny-robot-svgs'
import { type VNode } from 'vue'
import ExtensionCardGrid from './ExtensionCardGrid.vue'
import type { ExtensionCardGridActionEvent, ExtensionCardGridNameClickEvent } from '../index.type'
import type { ExtensionManagerSectionState } from './ExtensionManagerSection.types'

defineOptions({ name: 'ExtensionManagerSection' })

const props = defineProps<{
tabId: string
section: ExtensionManagerSectionState
expanded: boolean
}>()

const slots = defineSlots<{
item?: (props: { item: ExtensionManagerSectionState['items'][number]; index: number }) => VNode[]
empty?: () => VNode[]
}>()

const emit = defineEmits<{
(e: 'section-toggle', expanded: boolean): void
(e: 'action', event: ExtensionCardGridActionEvent): void
(e: 'name-click', event: ExtensionCardGridNameClickEvent): void
}>()

const toggle = () => emit('section-toggle', !props.expanded)

const handleAction = (event: ExtensionCardGridActionEvent) => emit('action', event)

const handleNameClick = (event: ExtensionCardGridNameClickEvent) => emit('name-click', event)
</script>

<template>
<section class="extension-manager-section" :data-tab-id="props.tabId" :data-section-key="props.section.key">
<div class="extension-manager-section__header">
<button class="extension-manager-section__title" type="button" :aria-expanded="props.expanded" @click="toggle">
<IconArrowDown class="extension-manager-section__arrow" :class="{ 'is-expanded': props.expanded }" />
<span>{{ props.section.title }}</span>
</button>
</div>

<div v-if="props.expanded" class="extension-manager-section__body">
<ExtensionCardGrid
v-if="props.section.items.length > 0"
:items="props.section.items"
:primary-actions-limit="1"
:name-clickable="true"
@action="handleAction"
@name-click="handleNameClick"
>
<template v-if="slots.item" #item="{ item, index }">
<slot name="item" :item="item" :index="index" />
</template>
</ExtensionCardGrid>

<div v-else class="extension-manager-section__state">
<slot name="empty">暂无内容</slot>
</div>
</div>
</section>
</template>

<style lang="less" scoped>
.extension-manager-section {
min-width: 0;
}

.extension-manager-section__header {
min-height: 24px;
}

.extension-manager-section__title {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0;
border: 0;
background: transparent;
color: var(--tr-text-primary);
font-size: 14px;
line-height: 22px;
text-align: left;
cursor: pointer;
}

.extension-manager-section__arrow {
display: inline-block;
color: var(--tr-text-tertiary);
font-size: 16px;
transform: rotate(-90deg);
transition: transform 0.2s ease;

&.is-expanded {
transform: rotate(0);
}
}

.extension-manager-section__body {
min-width: 0;
}

.extension-manager-section__state {
padding: 28px 0;
color: var(--tr-text-secondary);
font-size: 13px;
text-align: center;
}
</style>
Loading
Loading