aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/pages/ComponentsPage.tsx
blob: 8191acb98428c7ae7e168f1e033ed5f6d964d7e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
import { Fragment, useMemo, useState, type ReactNode } from 'react'
import { Card } from '@astryxdesign/core/Card'
import { Center } from '@astryxdesign/core/Center'
import { Collapsible, CollapsibleGroup } from '@astryxdesign/core/Collapsible'
import { Divider } from '@astryxdesign/core/Divider'
import { Grid } from '@astryxdesign/core/Grid'
import { Heading } from '@astryxdesign/core/Heading'
import { HStack } from '@astryxdesign/core/HStack'
import { Icon } from '@astryxdesign/core/Icon'
import { Section } from '@astryxdesign/core/Section'
import { Text } from '@astryxdesign/core/Text'
import { TextInput } from '@astryxdesign/core/TextInput'
import { ToggleButton, ToggleButtonGroup } from '@astryxdesign/core/ToggleButton'
import { VStack } from '@astryxdesign/core/VStack'
import {
  Blocks,
  Layers,
  Search,
  SlidersHorizontal,
} from 'lucide-react'
import { Page, PageHeader } from '../components/Page'
import { ASTRYX_COMPONENTS, type AstryxComponentGroup } from '../data/astryxComponents'
import { ActionsDemo } from '../demos/ActionsDemo'
import { ChatDemo } from '../demos/ChatDemo'
import { CommandPaletteDemo } from '../demos/CommandPaletteDemo'
import { DataDemo } from '../demos/DataDemo'
import { DialogsDemo } from '../demos/DialogsDemo'
import { FeedbackDemo } from '../demos/FeedbackDemo'
import { InputsDemo } from '../demos/InputsDemo'
import { LayoutDemo } from '../demos/LayoutDemo'
import { MenusDemo } from '../demos/MenusDemo'
import { NavigationDemo } from '../demos/NavigationDemo'
import { ProvidersDemo } from '../demos/ProvidersDemo'
import { SelectionDemo } from '../demos/SelectionDemo'
import { SelectorsDemo } from '../demos/SelectorsDemo'
import { TypographyDemo } from '../demos/TypographyDemo'

interface DemoDefinition {
  id: string
  group: AstryxComponentGroup
  title: string
  description: string
  preview: ReactNode
  components: string[]
}

const DEMOS: DemoDefinition[] = [
  {
    id: 'actions',
    group: 'base',
    title: '操作与链接',
    description: '按钮、图标按钮、工具栏、切换按钮和链接共享同一套交互状态。',
    preview: <ActionsDemo />,
    components: [
      'Button', 'ButtonGroup', 'IconButton', 'ToggleButton', 'ToggleButtonGroup',
      'SegmentedControl', 'SegmentedControlItem', 'Link', 'Kbd', 'Toolbar', 'Icon',
    ],
  },
  {
    id: 'typography',
    group: 'base',
    title: '文字与内容',
    description: '标题、正文、引用、代码块和分隔线共同建立清晰的内容层级。',
    preview: <TypographyDemo />,
    components: [
      'Text', 'Heading', 'Blockquote', 'Code', 'CodeBlock', 'Markdown', 'Citation',
      'Token', 'Divider', 'VisuallyHidden', 'Timer', 'Timestamp',
    ],
  },
  {
    id: 'layout',
    group: 'base',
    title: '布局与卡片',
    description: '响应式网格、可选卡片和折叠面板组成的真实布局结构。',
    preview: <LayoutDemo />,
    components: [
      'Stack', 'HStack', 'VStack', 'Grid', 'GridSpan', 'Center', 'Section', 'Card',
      'ClickableCard', 'SelectableCard', 'Collapsible', 'CollapsibleGroup',
      'ScrollableArea', 'Layout', 'LayoutHeader', 'LayoutContent', 'LayoutFooter',
      'LayoutPanel', 'Outline', 'OverflowList', 'AspectRatio', 'Thumbnail',
      'Carousel', 'MediaTheme',
    ],
  },
  {
    id: 'data',
    group: 'base',
    title: '列表、表格与头像',
    description: '列表、表格、头像和计数徽标用于呈现结构化数据。',
    preview: <DataDemo />,
    components: [
      'List', 'ListItem', 'Item', 'MetadataList', 'MetadataListItem', 'Table',
      'TableBody', 'TableCell', 'TableFooter', 'TableHeader', 'TableHeaderCell',
      'TableRow', 'TableSelectionToolbar', 'TreeList', 'EmptyState', 'Skeleton',
      'Spinner', 'Avatar', 'AvatarGroup', 'AvatarGroupOverflow', 'AvatarStatusDot',
      'Badge', 'StatusDot', 'NavIcon',
    ],
  },
  {
    id: 'providers',
    group: 'base',
    title: '主题与全局能力',
    description: '主题、链接和国际化 Provider 已经在页面中生效。',
    preview: <ProvidersDemo />,
    components: ['Theme', 'MediaTheme', 'LinkProvider', 'InternationalizationProvider'],
  },
  {
    id: 'feedback',
    group: 'base',
    title: '反馈与状态',
    description: '用横幅、进度、加载指示和骨架屏表达系统状态。',
    preview: <FeedbackDemo />,
    components: ['Banner', 'ProgressBar', 'Spinner', 'Skeleton', 'EmptyState'],
  },
  {
    id: 'navigation',
    group: 'base',
    title: '导航与步骤',
    description: '面包屑、标签页和步骤条帮助用户在层级和流程中定位。',
    preview: <NavigationDemo />,
    components: [
      'AppShell', 'TopNav', 'TopNavHeading', 'TopNavItem', 'TopNavMenu',
      'TopNavMegaMenu', 'TopNavMegaMenuFeaturedCard', 'TopNavMegaMenuItem',
      'SideNav', 'SideNavCollapseButton', 'SideNavHeading', 'SideNavItem',
      'SideNavSection', 'MobileNav', 'Breadcrumbs', 'BreadcrumbItem', 'Pagination',
      'TabList', 'Tab', 'TabMenu', 'Stepper', 'Step',
    ],
  },
  {
    id: 'chat',
    group: 'base',
    title: '聊天消息',
    description: '消息列表、气泡、元数据和时间戳组成的对话界面。',
    preview: <ChatDemo />,
    components: [
      'ChatComposer', 'ChatComposerDrawer', 'ChatComposerInput', 'ChatDictationButton',
      'ChatLayout', 'ChatLayoutScrollButton', 'ChatMessage', 'ChatMessageBubble',
      'ChatMessageList', 'ChatMessageMetadata', 'ChatSendButton', 'ChatSystemMessage',
      'ChatTokenizedText', 'ChatToolCalls',
    ],
  },
  {
    id: 'inputs',
    group: 'form',
    title: '文本输入与校验',
    description: '文本、长文本、开关和保存反馈组成的表单输入场景。',
    preview: <InputsDemo />,
    components: [
      'TextInput', 'TextArea', 'NumberInput', 'InputGroup', 'InputGroupText',
      'Field', 'FieldLabel', 'FieldStatus', 'FormLayout', 'FileInput', 'PowerSearch',
    ],
  },
  {
    id: 'selection',
    group: 'form',
    title: '选择控件',
    description: '复选框、单选框和滑杆展示不同粒度的选择操作。',
    preview: <SelectionDemo />,
    components: [
      'CheckboxInput', 'CheckboxList', 'CheckboxListItem', 'RadioList',
      'RadioListItem', 'Switch', 'Slider',
    ],
  },
  {
    id: 'selectors',
    group: 'form',
    title: '选择器与日期',
    description: '下拉选择、日期输入和更多选择模式集中在同一场景。',
    preview: <SelectorsDemo />,
    components: [
      'Selector', 'SelectorOption', 'MultiSelector', 'ComplexSelector', 'Typeahead',
      'BaseTypeahead', 'TypeaheadItem', 'Tokenizer', 'Calendar', 'DateInput',
      'DateRangeInput', 'DateTimeInput', 'TimeInput',
    ],
  },
  {
    id: 'dialogs',
    group: 'overlay',
    title: '对话框、抽屉与底部面板',
    description: '点击按钮打开真实弹层,体验遮罩、焦点管理和关闭流程。',
    preview: <DialogsDemo />,
    components: [
      'Dialog', 'AlertDialog', 'DialogHeader', 'Drawer', 'DrawerHeader',
      'BottomSheet', 'BottomSheetSwitcher', 'Overlay',
    ],
  },
  {
    id: 'menus',
    group: 'overlay',
    title: '菜单与浮层',
    description: '下拉菜单、上下文菜单、弹出框、提示和悬停卡片。',
    preview: <MenusDemo />,
    components: [
      'DropdownMenu', 'DropdownMenuCheckboxItem', 'DropdownMenuDivider',
      'DropdownMenuGroup', 'DropdownMenuItem', 'DropdownMenuRadioGroup',
      'DropdownMenuRadioItem', 'DropdownMenuSubMenu', 'ContextMenu', 'MoreMenu',
      'Popover', 'Tooltip', 'HoverCard',
    ],
  },
  {
    id: 'command',
    group: 'overlay',
    title: '命令面板与通知',
    description: '命令面板把搜索、分组结果和键盘操作集中在一个浮层里。',
    preview: <CommandPaletteDemo />,
    components: [
      'CommandPalette', 'CommandPaletteEmpty', 'CommandPaletteFooter',
      'CommandPaletteGroup', 'CommandPaletteInput', 'CommandPaletteItem',
      'CommandPaletteList', 'Toast', 'Lightbox',
    ],
  },
]

const GROUPS = [
  {
    id: 'base',
    label: '基础组件',
    icon: Blocks,
    description: '布局、排版、内容、操作、反馈、导航和数据展示等通用构建块。',
  },
  {
    id: 'form',
    label: '表单组件',
    icon: SlidersHorizontal,
    description: '输入、选择、日期、校验和表单布局控件。',
  },
  {
    id: 'overlay',
    label: '弹层组件',
    icon: Layers,
    description: '对话框、抽屉、菜单、浮层、提示和命令面板。',
  },
] as const

type Filter = 'all' | AstryxComponentGroup

const GROUP_COUNTS = GROUPS.reduce(
  (counts, group) => {
    counts[group.id] = ASTRYX_COMPONENTS.filter(
      (component) => component.group === group.id,
    ).length
    return counts
  },
  { base: 0, form: 0, overlay: 0 } as Record<AstryxComponentGroup, number>,
)

function DemoCard({ demo }: { demo: DemoDefinition }) {
  return (
    <Card padding={0}>
      <Section variant="muted" padding={5}>
        {demo.preview}
      </Section>
      <Section variant="transparent" padding={5}>
        <VStack gap={2}>
          <Heading level={3}>{demo.title}</Heading>
          <Text type="body" color="secondary" as="p">
            {demo.description}
          </Text>
          <Text type="supporting" size="sm" maxLines={2}>
            覆盖组件:{demo.components.join('、')}
          </Text>
        </VStack>
      </Section>
    </Card>
  )
}

export function ComponentsPage() {
  const [filter, setFilter] = useState<Filter>('all')
  const [query, setQuery] = useState('')

  const results = useMemo(() => {
    const normalized = query.trim().toLowerCase()

    return DEMOS.filter((demo) => {
      const matchesGroup = filter === 'all' || demo.group === filter
      const matchesQuery =
        normalized.length === 0 ||
        demo.title.toLowerCase().includes(normalized) ||
        demo.description.toLowerCase().includes(normalized) ||
        demo.components.some((name) => name.toLowerCase().includes(normalized))

      return matchesGroup && matchesQuery
    })
  }, [filter, query])

  const sections = GROUPS.map((group) => ({
    ...group,
    demos: results.filter((demo) => demo.group === group.id),
  })).filter((group) => group.demos.length > 0)

  const coveredComponents = new Set(results.flatMap((demo) => demo.components)).size
  const isFiltering = filter !== 'all' || query.trim().length > 0

  return (
    <Page>
      <VStack gap={8}>
        <PageHeader
          eyebrow="实时预览"
          title="组件"
          description={`通过可交互的组合场景展示 Astryx 组件的使用效果。全部 ${ASTRYX_COMPONENTS.length} 个组件按基础、表单和弹层分类整理。`}
        />

        <VStack gap={2}>
          <Text type="supporting" size="sm">
            组件名称与 API 保留 Astryx 原始命名;示例中的操作和内容均可直接体验。
          </Text>
          <HStack gap={3} wrap="wrap">
            <Text type="supporting" size="sm">
              基础组件 {GROUP_COUNTS.base}
            </Text>
            <Text type="supporting" size="sm">
              表单组件 {GROUP_COUNTS.form}
            </Text>
            <Text type="supporting" size="sm">
              弹层组件 {GROUP_COUNTS.overlay}
            </Text>
          </HStack>
        </VStack>

        <Divider />

        <VStack gap={4}>
          <HStack gap={4} wrap="wrap" vAlign="end">
            <TextInput
              label="搜索组件"
              isLabelHidden
              placeholder="搜索组件名或场景"
              value={query}
              onChange={setQuery}
              startIcon={Search}
              size="lg"
              width={360}
            />
            <ToggleButtonGroup
              label="按组件分类筛选"
              value={filter}
              onChange={(value) => setFilter((value as Filter | null) ?? 'all')}
              size="sm">
              <ToggleButton label="全部" value="all" />
              {GROUPS.map((group) => (
                <ToggleButton
                  key={group.id}
                  label={group.label}
                  value={group.id}
                />
              ))}
            </ToggleButtonGroup>
          </HStack>
          <Text type="supporting" size="sm">
            {isFiltering
              ? `${results.length} 个场景,覆盖 ${coveredComponents} 个组件`
              : `${DEMOS.length} 个场景,覆盖 ${coveredComponents} 个组件`}
          </Text>
        </VStack>

        {results.length === 0 ? (
          <Center minHeight={220}>
            <VStack gap={2} hAlign="center">
              <Text type="label">没有找到匹配的场景。</Text>
              <Text type="supporting" color="secondary">
                试试更宽泛的关键词,或切换回「全部」。
              </Text>
            </VStack>
          </Center>
        ) : (
          sections.map((group, index) => (
            <Fragment key={group.id}>
              {index > 0 ? <Divider /> : null}
              <VStack gap={4}>
                <VStack gap={1}>
                  <HStack gap={2} vAlign="center" wrap="wrap">
                    <Icon icon={group.icon} size="md" color="accent" />
                    <Heading level={2}>{group.label}</Heading>
                    <Text type="supporting" size="sm">
                      {group.demos.length} 个场景
                    </Text>
                  </HStack>
                  <Text type="body" color="secondary" as="p">
                    {group.description}
                  </Text>
                </VStack>
                <Grid columns={{ minWidth: 460, max: 2 }} gap={4} align="start">
                  {group.demos.map((demo) => (
                    <DemoCard key={demo.id} demo={demo} />
                  ))}
                </Grid>
              </VStack>
            </Fragment>
          ))
        )}

        <Divider />

        <CollapsibleGroup type="single">
          <Collapsible
            value="all-components"
            trigger={<Text type="label">查看完整组件清单</Text>}>
            <VStack gap={3}>
              {GROUPS.map((group) => (
                <VStack key={group.id} gap={1}>
                  <Text type="label">{group.label}</Text>
                  <Text type="supporting" size="sm">
                    {ASTRYX_COMPONENTS.filter(
                      (component) => component.group === group.id,
                    )
                      .map((component) => component.name)
                      .join('、')}
                  </Text>
                </VStack>
              ))}
            </VStack>
          </Collapsible>
        </CollapsibleGroup>

        <Text type="supporting" size="sm">
          共收录 {ASTRYX_COMPONENTS.length} 个组件,数据来自
          @astryxdesign/core 0.7.2。
        </Text>
      </VStack>
    </Page>
  )
}