aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/AboutPage.tsx202
-rw-r--r--src/pages/ComponentsPage.tsx422
-rw-r--r--src/pages/HomePage.tsx219
-rw-r--r--src/pages/IconsPage.tsx334
-rw-r--r--src/pages/NotFoundPage.tsx27
5 files changed, 1204 insertions, 0 deletions
diff --git a/src/pages/AboutPage.tsx b/src/pages/AboutPage.tsx
new file mode 100644
index 0000000..5f2aec2
--- /dev/null
+++ b/src/pages/AboutPage.tsx
@@ -0,0 +1,202 @@
+import { CodeBlock } from '@astryxdesign/core/CodeBlock'
+import { Divider } from '@astryxdesign/core/Divider'
+import { Grid } from '@astryxdesign/core/Grid'
+import { Heading } from '@astryxdesign/core/Heading'
+import { Icon } from '@astryxdesign/core/Icon'
+import { List, ListItem } from '@astryxdesign/core/List'
+import { Text } from '@astryxdesign/core/Text'
+import { VStack } from '@astryxdesign/core/VStack'
+import {
+ ArrowRight,
+ Atom,
+ Blocks,
+ BookOpen,
+ ExternalLink,
+ Palette,
+ Route,
+ Shapes,
+ Zap,
+} from 'lucide-react'
+import { Page, PageHeader } from '../components/Page'
+
+const COMMANDS = `npm run dev # 启动开发服务器
+npm run build # 类型检查并构建生产版本
+npm run lint # 运行 oxlint
+npm run preview # 预览生产构建
+
+npm run astryx -- component Button # 查看 Astryx 组件文档`
+
+const PROJECT_TREE = `src/
+ components/
+ AppLayout.tsx 应用外壳、顶部导航、侧边导航
+ Page.tsx 共用页面框架与页头
+ pages/
+ HomePage.tsx
+ ComponentsPage.tsx
+ AboutPage.tsx
+ NotFoundPage.tsx
+ theme/
+ ThemeMode.tsx 明暗主题提供者
+ astryx-themes.ts 生成的主题注册表
+ router.tsx React Router 路由表
+ App.tsx 提供者与路由
+ main.tsx 入口文件`
+
+const STACK = [
+ {
+ name: 'React',
+ version: '19.3',
+ icon: Atom,
+ description: '界面运行时与 Hooks',
+ },
+ {
+ name: 'Vite',
+ version: '8.3',
+ icon: Zap,
+ description: '开发服务器与生产构建工具',
+ },
+ {
+ name: 'React Router',
+ version: '7.18',
+ icon: Route,
+ description: '基于数据路由的客户端路由,可在 hash 与 history 模式间切换',
+ },
+ {
+ name: 'Astryx',
+ version: '0.7.2',
+ icon: Palette,
+ description: '设计系统、组件与 neutral 主题',
+ },
+]
+
+export function AboutPage() {
+ return (
+ <Page>
+ <VStack gap={8}>
+ <PageHeader
+ eyebrow="关于"
+ title="是起点,不是框架。"
+ description="项目把每一部分都保持得小而可见:一张路由表、一套布局、一个主题提供者,以及普通的 React 组件。你可以直接替换需要改的部分,而不用和隐藏的约定较劲。"
+ />
+
+ <Divider />
+
+ <Grid columns={{ minWidth: 320, max: 2 }} gap={8}>
+ <VStack gap={4}>
+ <Heading level={2}>技术栈</Heading>
+ <List hasDividers>
+ {STACK.map(({ name, version, icon, description }) => (
+ <ListItem
+ key={name}
+ label={name}
+ description={description}
+ startContent={
+ <Icon icon={icon} size="sm" color="secondary" />
+ }
+ endContent={
+ <Text type="supporting" size="sm">
+ {version}
+ </Text>
+ }
+ />
+ ))}
+ </List>
+ </VStack>
+
+ <VStack gap={4}>
+ <Heading level={2}>常用命令</Heading>
+ <CodeBlock
+ code={COMMANDS}
+ language="bash"
+ title="终端"
+ size="sm"
+ hasLanguageLabel={false}
+ width="100%"
+ />
+ <Text type="supporting" size="sm">
+ Vite 8 需要 Node 20.19+。
+ </Text>
+ </VStack>
+ </Grid>
+
+ <Divider />
+
+ <VStack gap={4} maxWidth={920}>
+ <Heading level={2}>项目结构</Heading>
+ <Text type="body" color="secondary" as="p">
+ 在 <Text type="code">src/pages</Text> 下新建页面,然后在{' '}
+ <Text type="code">src/router.tsx</Text> 中注册路由,并在{' '}
+ <Text type="code">src/components/AppLayout.tsx</Text>{' '}
+ 中添加对应的导航项。
+ </Text>
+ <CodeBlock
+ code={PROJECT_TREE}
+ language="plaintext"
+ title="项目结构"
+ width="100%"
+ />
+ </VStack>
+
+ <Divider />
+
+ <VStack gap={4}>
+ <Heading level={2}>继续探索</Heading>
+ <List hasDividers>
+ <ListItem
+ label="组件"
+ description="浏览全部 173 个组件的实时交互示例。"
+ href="/components"
+ startContent={<Icon icon={Blocks} size="sm" color="secondary" />}
+ endContent={<Icon icon={ArrowRight} size="sm" color="secondary" />}
+ />
+ <ListItem
+ label="图标"
+ description="浏览 Astryx 语义图标和 Lucide 图标库。"
+ href="/icons"
+ startContent={<Icon icon={Shapes} size="sm" color="secondary" />}
+ endContent={<Icon icon={ArrowRight} size="sm" color="secondary" />}
+ />
+ <ListItem
+ label="Astryx 文档"
+ description="组件接口、主题、布局指南和页面模板。"
+ href="https://astryx.atmeta.com"
+ target="_blank"
+ startContent={<Icon icon={BookOpen} size="sm" color="secondary" />}
+ endContent={
+ <Icon icon={ExternalLink} size="sm" color="secondary" />
+ }
+ />
+ <ListItem
+ label="React Router 文档"
+ description="路由、数据加载、动作和导航。"
+ href="https://reactrouter.com"
+ target="_blank"
+ startContent={<Icon icon={Route} size="sm" color="secondary" />}
+ endContent={
+ <Icon icon={ExternalLink} size="sm" color="secondary" />
+ }
+ />
+ </List>
+ </VStack>
+
+ <Divider />
+
+ <VStack gap={2}>
+ <Heading level={3}>主题</Heading>
+ <Text type="body" color="secondary" as="p">
+ 自动生成的 <Text type="code">src/astryx-themes.ts</Text>{' '}
+ 文件注册了 neutral 主题。可以运行{' '}
+ <Text type="code">npm run astryx -- theme add &lt;slug&gt;</Text>{' '}
+ 添加其他主题包,然后在{' '}
+ <Text type="code">src/theme/ThemeMode.tsx</Text>{' '}
+ 中把它传给主题提供者。
+ </Text>
+ <Text type="supporting" size="sm">
+ Astryx 自带预构建 CSS,因此这个应用不需要 StyleX、PostCSS 或 Babel
+ 插件。
+ </Text>
+ </VStack>
+ </VStack>
+ </Page>
+ )
+}
diff --git a/src/pages/ComponentsPage.tsx b/src/pages/ComponentsPage.tsx
new file mode 100644
index 0000000..8191acb
--- /dev/null
+++ b/src/pages/ComponentsPage.tsx
@@ -0,0 +1,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>
+ )
+}
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
new file mode 100644
index 0000000..a1e7668
--- /dev/null
+++ b/src/pages/HomePage.tsx
@@ -0,0 +1,219 @@
+import { Button } from '@astryxdesign/core/Button'
+import { Card } from '@astryxdesign/core/Card'
+import { CodeBlock } from '@astryxdesign/core/CodeBlock'
+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 { List, ListItem } from '@astryxdesign/core/List'
+import { Text } from '@astryxdesign/core/Text'
+import { VStack } from '@astryxdesign/core/VStack'
+import {
+ ArrowRight,
+ Atom,
+ Check,
+ ExternalLink,
+ Layers,
+ Moon,
+ Palette,
+ Route,
+ Sparkles,
+ Terminal,
+ Zap,
+} from 'lucide-react'
+import { Page } from '../components/Page'
+
+const STACK = [
+ {
+ name: 'Vite',
+ version: '8.x',
+ icon: Zap,
+ description: '极速开发服务器、快速热更新,以及经过优化的生产构建。',
+ },
+ {
+ name: 'React',
+ version: '19.x',
+ icon: Atom,
+ description: '现代 Hooks 与全应用共享的组件模型。',
+ },
+ {
+ name: 'React Router',
+ version: '7.x',
+ icon: Route,
+ description: '嵌套路由、持续存在的应用外壳,以及类型安全的导航。',
+ },
+ {
+ name: 'Astryx',
+ version: '0.7',
+ icon: Palette,
+ description: '无障碍组件、设计令牌、主题与深色模式。',
+ },
+]
+
+const QUICK_START = `npm install
+npm run dev`
+
+const BUILT_IN = [
+ {
+ label: '应用外壳',
+ description:
+ '顶部导航、可折叠侧边栏和移动端抽屉都已替你处理好。',
+ icon: Layers,
+ },
+ {
+ label: '客户端路由',
+ description:
+ '所有页面共用一套布局,可在根目录配置中切换 hash 与 history 路由。',
+ icon: Route,
+ },
+ {
+ label: '明暗主题',
+ description:
+ 'Astryx neutral 主题可切换模式,并记住你的选择。',
+ icon: Moon,
+ },
+ {
+ label: '完整组件目录',
+ description:
+ '全部 173 个 Astryx 组件,按基础、表单和弹层分类。',
+ icon: Sparkles,
+ },
+]
+
+export function HomePage() {
+ return (
+ <Page>
+ <VStack gap={10}>
+ <VStack gap={5} maxWidth={860}>
+ <Text type="label" color="accent">
+ Vite + React + React Router + Astryx
+ </Text>
+ <VStack gap={3}>
+ <Heading level={1} type="display-2" textWrap="balance">
+ 为你的下一个 React 应用,准备更好的起点。
+ </Heading>
+ <Text type="large" color="secondary" as="p" textWrap="pretty">
+ Vite 让反馈循环保持飞快,React Router
+ 给应用真正的结构,Astryx 提供无障碍组件与主题。所有部分都已经连接好。
+ </Text>
+ </VStack>
+ <HStack gap={3} wrap="wrap">
+ <Button
+ label="浏览组件"
+ variant="primary"
+ href="/components"
+ endContent={<Icon icon={ArrowRight} size="sm" color="inherit" />}
+ />
+ <Button
+ label="Astryx 文档"
+ variant="secondary"
+ href="https://astryx.atmeta.com"
+ target="_blank"
+ rel="noreferrer"
+ endContent={
+ <Icon icon={ExternalLink} size="sm" color="inherit" />
+ }
+ />
+ </HStack>
+ </VStack>
+
+ <Divider />
+
+ <VStack gap={5}>
+ <VStack gap={2} maxWidth={680}>
+ <Text type="label" color="accent">
+ 技术栈
+ </Text>
+ <Heading level={2}>四件工具,已经连接好。</Heading>
+ <Text type="body" color="secondary" as="p">
+ 无需构建插件,也无需额外配置 CSS。Astryx
+ 直接提供预构建样式,项目里能看到的导入就是全部配置。
+ </Text>
+ </VStack>
+ <Grid columns={{ minWidth: 230, max: 4 }} gap={6}>
+ {STACK.map(({ name, version, icon, description }) => (
+ <HStack key={name} gap={3} vAlign="start">
+ <Icon icon={icon} size="md" color="accent" />
+ <VStack gap={1}>
+ <HStack gap={2} vAlign="center" wrap="wrap">
+ <Text type="label">{name}</Text>
+ <Text type="supporting" size="sm">
+ {version}
+ </Text>
+ </HStack>
+ <Text type="supporting" size="sm" as="p">
+ {description}
+ </Text>
+ </VStack>
+ </HStack>
+ ))}
+ </Grid>
+ </VStack>
+
+ <Divider />
+
+ <Grid columns={{ minWidth: 320, max: 2 }} gap={8}>
+ <VStack gap={4}>
+ <VStack gap={1}>
+ <Heading level={2}>已经接好的能力</Heading>
+ <Text type="body" color="secondary" as="p">
+ 一些方便替换、能随应用一起成长的小默认值。
+ </Text>
+ </VStack>
+ <List hasDividers>
+ {BUILT_IN.map(({ label, description, icon }) => (
+ <ListItem
+ key={label}
+ label={label}
+ description={description}
+ startContent={
+ <Icon icon={icon} size="sm" color="secondary" />
+ }
+ endContent={
+ <Icon icon={Check} size="sm" color="success" />
+ }
+ />
+ ))}
+ </List>
+ </VStack>
+
+ <Card padding={6} elevation="low">
+ <VStack gap={4}>
+ <VStack gap={1}>
+ <Heading level={2}>快速开始</Heading>
+ <Text type="body" color="secondary" as="p">
+ 安装依赖、启动 Vite,然后编辑{' '}
+ <Text type="code">src/</Text> 下的任意文件。
+ </Text>
+ </VStack>
+ <CodeBlock
+ code={QUICK_START}
+ language="bash"
+ title="终端"
+ hasLanguageLabel={false}
+ width="100%"
+ />
+ <HStack gap={2} vAlign="center">
+ <Icon icon={Terminal} size="sm" color="secondary" />
+ <Text type="supporting" size="sm">
+ 需要 Node 20.19+ 和 npm 10+。
+ </Text>
+ </HStack>
+ </VStack>
+ </Card>
+ </Grid>
+
+ <Divider />
+
+ <HStack gap={2} vAlign="center">
+ <Icon icon={Sparkles} size="sm" color="accent" />
+ <Text type="supporting" size="sm">
+ 基于 Astryx v0.7.2 和 neutral 主题构建。可在
+ src/astryx-themes.ts 中更改主题。
+ </Text>
+ </HStack>
+ </VStack>
+ </Page>
+ )
+}
diff --git a/src/pages/IconsPage.tsx b/src/pages/IconsPage.tsx
new file mode 100644
index 0000000..6b836ca
--- /dev/null
+++ b/src/pages/IconsPage.tsx
@@ -0,0 +1,334 @@
+import { useMemo, useState } from 'react'
+import { Button } from '@astryxdesign/core/Button'
+import { Center } from '@astryxdesign/core/Center'
+import { ClickableCard } from '@astryxdesign/core/ClickableCard'
+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 type { IconName } from '@astryxdesign/core/Icon'
+import { Text } from '@astryxdesign/core/Text'
+import { TextInput } from '@astryxdesign/core/TextInput'
+import { useToast } from '@astryxdesign/core/Toast'
+import { ToggleButton, ToggleButtonGroup } from '@astryxdesign/core/ToggleButton'
+import { VStack } from '@astryxdesign/core/VStack'
+import {
+ ChevronLeft,
+ ChevronRight,
+ icons,
+ Search,
+ type LucideIcon,
+} from 'lucide-react'
+import { Page, PageHeader } from '../components/Page'
+
+type IconSet = 'semantic' | 'lucide'
+
+interface SemanticIcon {
+ name: IconName
+ label: string
+}
+
+const SEMANTIC_ICONS: SemanticIcon[] = [
+ { name: 'close', label: '关闭' },
+ { name: 'chevronDown', label: '向下展开' },
+ { name: 'chevronLeft', label: '向左' },
+ { name: 'chevronRight', label: '向右' },
+ { name: 'chevronsLeft', label: '跳到开头' },
+ { name: 'chevronsRight', label: '跳到末尾' },
+ { name: 'check', label: '确认' },
+ { name: 'success', label: '成功' },
+ { name: 'error', label: '错误' },
+ { name: 'warning', label: '警告' },
+ { name: 'info', label: '信息' },
+ { name: 'calendar', label: '日历' },
+ { name: 'clock', label: '时间' },
+ { name: 'externalLink', label: '外部链接' },
+ { name: 'menu', label: '菜单' },
+ { name: 'moreHorizontal', label: '更多' },
+ { name: 'search', label: '搜索' },
+ { name: 'upload', label: '上传' },
+ { name: 'arrowUp', label: '向上' },
+ { name: 'arrowDown', label: '向下' },
+ { name: 'arrowsUpDown', label: '排序' },
+ { name: 'funnel', label: '筛选' },
+ { name: 'eyeSlash', label: '隐藏' },
+ { name: 'viewColumns', label: '列设置' },
+ { name: 'copy', label: '复制' },
+ { name: 'checkDouble', label: '已复制' },
+ { name: 'wrench', label: '设置' },
+ { name: 'stop', label: '停止' },
+ { name: 'microphone', label: '麦克风' },
+]
+
+interface LucideEntry {
+ name: string
+ Icon: LucideIcon
+}
+
+const LUCIDE_ICONS: LucideEntry[] = Object.entries(icons)
+ .map(([name, Icon]) => ({ name, Icon }))
+ .sort((a, b) => a.name.localeCompare(b.name))
+
+const LUCIDE_PAGE_SIZE = 120
+
+export function IconsPage() {
+ const [activeSet, setActiveSet] = useState<IconSet>('semantic')
+ const [query, setQuery] = useState('')
+ const [page, setPage] = useState(1)
+ const showToast = useToast()
+
+ const changeSet = (value: string | null) => {
+ setActiveSet(value === 'lucide' ? 'lucide' : 'semantic')
+ setPage(1)
+ }
+
+ const changeQuery = (value: string) => {
+ setQuery(value)
+ setPage(1)
+ }
+
+ const copyUsage = async (code: string, name: string) => {
+ try {
+ if (!navigator.clipboard) throw new Error('Clipboard unavailable')
+ await navigator.clipboard.writeText(code)
+ showToast({ body: `已复制 ${name} 的用法`, type: 'info' })
+ } catch {
+ showToast({ body: '复制失败,请检查浏览器权限', type: 'error' })
+ }
+ }
+
+ const normalizedQuery = query.trim().toLowerCase()
+
+ const filteredSemantic = useMemo(
+ () =>
+ SEMANTIC_ICONS.filter(
+ (icon) =>
+ normalizedQuery.length === 0 ||
+ icon.name.toLowerCase().includes(normalizedQuery) ||
+ icon.label.includes(query.trim()),
+ ),
+ [normalizedQuery, query],
+ )
+
+ const filteredLucide = useMemo(
+ () =>
+ normalizedQuery.length === 0
+ ? LUCIDE_ICONS
+ : LUCIDE_ICONS.filter((icon) =>
+ icon.name.toLowerCase().includes(normalizedQuery),
+ ),
+ [normalizedQuery],
+ )
+
+ const totalPages = Math.max(1, Math.ceil(filteredLucide.length / LUCIDE_PAGE_SIZE))
+ const currentPage = Math.min(page, totalPages)
+ const pageStart = (currentPage - 1) * LUCIDE_PAGE_SIZE
+ const pageItems = filteredLucide.slice(
+ pageStart,
+ pageStart + LUCIDE_PAGE_SIZE,
+ )
+
+ const isSemantic = activeSet === 'semantic'
+ const visibleCount = isSemantic ? filteredSemantic.length : filteredLucide.length
+
+ return (
+ <Page>
+ <VStack gap={8}>
+ <PageHeader
+ eyebrow="图标资源"
+ title="图标"
+ description={`浏览 ${SEMANTIC_ICONS.length} 个 Astryx 语义图标和 ${LUCIDE_ICONS.length} 个 Lucide 图标。点击任意图标即可复制它的用法。`}
+ />
+
+ <Divider />
+
+ <VStack gap={4}>
+ <HStack gap={4} wrap="wrap" vAlign="end">
+ <TextInput
+ label="搜索图标"
+ isLabelHidden
+ placeholder="搜索图标名称"
+ value={query}
+ onChange={changeQuery}
+ startIcon={Search}
+ size="lg"
+ width={360}
+ />
+ <ToggleButtonGroup
+ label="选择图标集"
+ value={activeSet}
+ onChange={changeSet}
+ size="sm">
+ <ToggleButton
+ label={`语义图标 ${SEMANTIC_ICONS.length}`}
+ value="semantic"
+ />
+ <ToggleButton
+ label={`Lucide ${LUCIDE_ICONS.length}`}
+ value="lucide"
+ />
+ </ToggleButtonGroup>
+ </HStack>
+ <Text type="supporting" size="sm">
+ {isSemantic
+ ? '语义图标通过名称使用,会自动适配当前主题。'
+ : 'Lucide 图标以组件形式传入 Icon,可与语义图标混用。'}
+ </Text>
+ </VStack>
+
+ {visibleCount === 0 ? (
+ <Center minHeight={220}>
+ <VStack gap={2} hAlign="center">
+ <Icon icon={Search} size="lg" color="secondary" />
+ <Text type="label">没有找到匹配的图标。</Text>
+ <Text type="supporting" color="secondary">
+ 试试更短的关键词。
+ </Text>
+ </VStack>
+ </Center>
+ ) : isSemantic ? (
+ <>
+ <Text type="supporting" size="sm">
+ 共 {filteredSemantic.length} 个语义图标
+ </Text>
+ <Grid columns={{ minWidth: 160, max: 4 }} gap={3}>
+ {filteredSemantic.map((semanticIcon) => (
+ <ClickableCard
+ key={semanticIcon.name}
+ padding={4}
+ onClick={() =>
+ void copyUsage(
+ `<Icon icon="${semanticIcon.name}" size="lg" />`,
+ semanticIcon.name,
+ )
+ }
+ label={`复制 ${semanticIcon.name} 图标用法`}>
+ <VStack gap={2} hAlign="center">
+ <Icon
+ icon={semanticIcon.name}
+ size="lg"
+ color="accent"
+ />
+ <Text
+ type="code"
+ size="sm"
+ display="block"
+ justify="center"
+ maxLines={1}>
+ {semanticIcon.name}
+ </Text>
+ <Text
+ type="supporting"
+ size="sm"
+ display="block"
+ justify="center">
+ {semanticIcon.label}
+ </Text>
+ </VStack>
+ </ClickableCard>
+ ))}
+ </Grid>
+ </>
+ ) : (
+ <>
+ <HStack gap={2} vAlign="center" hAlign="between" wrap="wrap">
+ <Text type="supporting" size="sm">
+ 显示 {pageStart + 1}–{Math.min(pageStart + LUCIDE_PAGE_SIZE, filteredLucide.length)} 个,共 {filteredLucide.length} 个图标
+ </Text>
+ <HStack gap={2} vAlign="center">
+ <Button
+ label="上一页"
+ variant="secondary"
+ size="sm"
+ isDisabled={currentPage === 1}
+ onClick={() => setPage((value) => Math.max(1, value - 1))}
+ icon={<Icon icon={ChevronLeft} size="sm" />}
+ />
+ <Text type="supporting" size="sm">
+ 第 {currentPage} / {totalPages} 页
+ </Text>
+ <Button
+ label="下一页"
+ variant="secondary"
+ size="sm"
+ isDisabled={currentPage === totalPages}
+ onClick={() =>
+ setPage((value) => Math.min(totalPages, value + 1))
+ }
+ endContent={<Icon icon={ChevronRight} size="sm" />}
+ />
+ </HStack>
+ </HStack>
+
+ <Grid columns={{ minWidth: 160, max: 5 }} gap={3}>
+ {pageItems.map(({ name, Icon: LucideComponent }) => (
+ <ClickableCard
+ key={name}
+ padding={3}
+ onClick={() =>
+ void copyUsage(
+ `import { ${name} } from 'lucide-react'\n\n<Icon icon={${name}} size="lg" />`,
+ name,
+ )
+ }
+ label={`复制 ${name} 图标用法`}>
+ <VStack gap={2} hAlign="center">
+ <Icon icon={LucideComponent} size="lg" color="primary" />
+ <Text
+ type="supporting"
+ size="xsm"
+ display="block"
+ justify="center"
+ maxLines={2}
+ wordBreak="break-all">
+ {name}
+ </Text>
+ </VStack>
+ </ClickableCard>
+ ))}
+ </Grid>
+
+ <HStack gap={2} vAlign="center" hAlign="between" wrap="wrap">
+ <Text type="supporting" size="sm">
+ 点击图标复制 import 与 Icon 用法
+ </Text>
+ <HStack gap={2} vAlign="center">
+ <Button
+ label="上一页"
+ variant="secondary"
+ size="sm"
+ isDisabled={currentPage === 1}
+ onClick={() => setPage((value) => Math.max(1, value - 1))}
+ icon={<Icon icon={ChevronLeft} size="sm" />}
+ />
+ <Text type="supporting" size="sm">
+ 第 {currentPage} / {totalPages} 页
+ </Text>
+ <Button
+ label="下一页"
+ variant="secondary"
+ size="sm"
+ isDisabled={currentPage === totalPages}
+ onClick={() =>
+ setPage((value) => Math.min(totalPages, value + 1))
+ }
+ endContent={<Icon icon={ChevronRight} size="sm" />}
+ />
+ </HStack>
+ </HStack>
+ </>
+ )}
+
+ <Divider />
+
+ <VStack gap={1}>
+ <Heading level={2}>使用方式</Heading>
+ <Text type="body" color="secondary" as="p">
+ 语义图标可以直接传入名称;Lucide 图标需要先导入组件,再交给 Icon 渲染。
+ </Text>
+ </VStack>
+ </VStack>
+ </Page>
+ )
+}
diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx
new file mode 100644
index 0000000..04ae800
--- /dev/null
+++ b/src/pages/NotFoundPage.tsx
@@ -0,0 +1,27 @@
+import { Button } from '@astryxdesign/core/Button'
+import { Center } from '@astryxdesign/core/Center'
+import { Heading } from '@astryxdesign/core/Heading'
+import { Text } from '@astryxdesign/core/Text'
+import { VStack } from '@astryxdesign/core/VStack'
+import { Page } from '../components/Page'
+
+export function NotFoundPage() {
+ return (
+ <Page>
+ <Center minHeight={520}>
+ <VStack gap={4} hAlign="center" maxWidth={440}>
+ <Text type="label" color="accent">
+ 404
+ </Text>
+ <Heading level={1} type="display-3" justify="center">
+ 页面未找到
+ </Heading>
+ <Text type="body" color="secondary" as="p" justify="center">
+ 这个模板里没有你访问的路由。回到概览页,再选择一个目的地吧。
+ </Text>
+ <Button label="返回概览" variant="primary" href="/" />
+ </VStack>
+ </Center>
+ </Page>
+ )
+}