apptify 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +95 -22
  2. package/dist/apptify.css +1 -1
  3. package/dist/apptify.js +4153 -2177
  4. package/dist/components/button.d.ts +1 -1
  5. package/dist/components/content.d.ts +73 -362
  6. package/dist/components/date-picker.d.ts +10 -0
  7. package/dist/components/forms.d.ts +77 -18
  8. package/dist/components/foundation.d.ts +2061 -111
  9. package/dist/components/gallery-scrollbar.d.ts +46 -0
  10. package/dist/components/mobile-image-viewer.d.ts +166 -0
  11. package/dist/components/navibar.d.ts +80 -0
  12. package/dist/components/overlays.d.ts +127 -158
  13. package/dist/components/tabs.d.ts +117 -0
  14. package/dist/core/context.d.ts +573 -9
  15. package/dist/core/desktop-image-enter.d.ts +6 -0
  16. package/dist/core/desktop-image-return.d.ts +1 -0
  17. package/dist/core/device.d.ts +5 -0
  18. package/dist/core/image-gallery-motion.d.ts +2 -0
  19. package/dist/core/image-geometry.d.ts +28 -0
  20. package/dist/core/image-motion.d.ts +18 -0
  21. package/dist/core/layers.d.ts +2 -0
  22. package/dist/core/motion.d.ts +2 -0
  23. package/dist/core/popup-motion.d.ts +12 -0
  24. package/dist/core/popup-placement.d.ts +31 -0
  25. package/dist/core/preview-navigation.d.ts +5 -0
  26. package/dist/index.d.ts +1214 -339
  27. package/docs/COMPONENTS.md +17 -15
  28. package/docs/PLAYGROUND-TEMPLATE.md +74 -0
  29. package/docs/VALIDATION.md +24 -0
  30. package/package.json +26 -7
  31. package/templates/playground/README.md +17 -0
  32. package/templates/playground/gitignore +3 -0
  33. package/templates/playground/index.html +26 -0
  34. package/templates/playground/package.json +26 -0
  35. package/templates/playground/playground/App.vue +73 -0
  36. package/templates/playground/playground/ComponentDemo.vue +234 -0
  37. package/templates/playground/playground/catalog.ts +80 -0
  38. package/templates/playground/playground/components/ComponentIndex.vue +62 -0
  39. package/templates/playground/playground/components/PageFooter.vue +3 -0
  40. package/templates/playground/playground/components/UserMenu.vue +132 -0
  41. package/templates/playground/playground/components/not-found-paper-scene.vue +210 -0
  42. package/templates/playground/playground/components/not-found-paper-story.ts +149 -0
  43. package/templates/playground/playground/env.d.ts +1 -0
  44. package/templates/playground/playground/main.ts +8 -0
  45. package/templates/playground/playground/router/index.ts +24 -0
  46. package/templates/playground/playground/style.css +228 -0
  47. package/templates/playground/playground/views/[...all].vue +68 -0
  48. package/templates/playground/playground/views/components.vue +126 -0
  49. package/templates/playground/playground/views/index.vue +75 -0
  50. package/templates/playground/playground/views/settings.vue +98 -0
  51. package/templates/playground/public/images/SOURCES.md +21 -0
  52. package/templates/playground/public/images/airpods-max-orange.jpg +0 -0
  53. package/templates/playground/public/images/architecture.jpg +0 -0
  54. package/templates/playground/public/images/city.jpg +0 -0
  55. package/templates/playground/public/images/coast.jpg +0 -0
  56. package/templates/playground/public/images/desert.jpg +0 -0
  57. package/templates/playground/public/images/food.jpg +0 -0
  58. package/templates/playground/public/images/forest.jpg +0 -0
  59. package/templates/playground/public/images/lake.jpg +0 -0
  60. package/templates/playground/public/images/mountains.jpg +0 -0
  61. package/templates/playground/public/images/snow.jpg +0 -0
  62. package/templates/playground/public/images/sunset.jpg +0 -0
  63. package/templates/playground/public/images/waterfall.jpg +0 -0
  64. package/templates/playground/source-snapshot.json +111 -0
  65. package/templates/playground/tsconfig.json +24 -0
  66. package/templates/playground/vite.config.ts +9 -0
  67. package/tooling/create-playground.mjs +35 -0
  68. package/tooling/prepare-package.mjs +35 -0
  69. package/tooling/prepare-playground.mjs +90 -0
  70. package/tooling/test-playground-package.mjs +188 -0
  71. package/tooling/vite.d.ts +5 -0
  72. package/tooling/vite.js +10 -0
@@ -0,0 +1,234 @@
1
+ <template>
2
+ <div class="component-demo" :class="demoClass" :data-component="name">
3
+ <apple-stack v-if="name === 'AppleButton'" direction="row" align="center">
4
+ <apple-button @click="notify('操作已完成')">继续</apple-button>
5
+ <apple-button variant="secondary" @click="notify('已选择次要操作')">稍后再说</apple-button>
6
+ <apple-button variant="outline" @click="notify('已加入收藏')">加入收藏</apple-button>
7
+ <apple-button variant="ghost" :icon="icons.ArrowUpRight" @click="notify('正在查看详情')">了解更多</apple-button>
8
+ <apple-button :icon="icons.Plus" icon-only label="添加" @click="notify('已添加')" />
9
+ <apple-button loading>处理中</apple-button><apple-button disabled>暂不可用</apple-button>
10
+ </apple-stack>
11
+ <apple-stack v-else-if="name === 'AppleLink'" direction="row" align="center">
12
+ <apple-link href="https://www.apple.com.cn/" external>Apple 官网</apple-link>
13
+ <apple-link href="https://www.apple.com.cn/accessibility/" external>辅助功能</apple-link>
14
+ <apple-link disabled external>暂不可用</apple-link>
15
+ </apple-stack>
16
+ <apple-stack v-else-if="name === 'AppleSearch'">
17
+ <apple-search v-model="searchText" label="搜索设置" placeholder="搜索设置" @search="notify(searchText ? `正在搜索:${searchText}` : '显示全部设置')" />
18
+ <apple-list v-if="searchResults.length" v-model="setting" :items="searchResults" selectable @select="notify('已选择设置项')" />
19
+ <apple-empty v-else title="没有找到相关设置" />
20
+ </apple-stack>
21
+ <apple-stack v-else-if="name === 'AppleProvider'">
22
+ <apple-segmented-control v-model="previewTheme" :items="previewThemes" label="局部外观" />
23
+ <apple-provider :theme="previewTheme" motion="auto" style="padding: 24px">
24
+ <apple-stack>
25
+ <apple-input v-model="text" label="显示名称" placeholder="你的名称" />
26
+ <apple-switch v-model="checked" label="接收通知" />
27
+ <apple-button @click="notify('偏好已保存')">保存偏好</apple-button>
28
+ </apple-stack>
29
+ </apple-provider>
30
+ </apple-stack>
31
+ <apple-stack v-else-if="name === 'AppleContainer'">
32
+ <apple-slider v-model="containerWidth" label="内容宽度" :min="240" :max="640" :step="40" />
33
+ <apple-container :width="containerWidth">
34
+ <apple-stack><apple-button v-for="section in layoutItems" :key="section" variant="secondary" @click="notify(`已选择${section}`)">{{ section }}</apple-button></apple-stack>
35
+ </apple-container>
36
+ </apple-stack>
37
+ <apple-stack v-else-if="name === 'AppleStack'">
38
+ <apple-segmented-control v-model="stackDirection" :items="[{label:'横向',value:'row'},{label:'纵向',value:'column'}]" label="排列方向" />
39
+ <apple-stack :direction="stackDirection" :gap="16" align="center">
40
+ <apple-button v-for="section in layoutItems" :key="section" variant="secondary" @click="notify(`已选择${section}`)">{{ section }}</apple-button>
41
+ </apple-stack>
42
+ </apple-stack>
43
+ <apple-stack v-else-if="name === 'AppleGrid'">
44
+ <apple-slider v-model="gridMin" label="最小列宽" :min="120" :max="320" :step="40" />
45
+ <apple-grid :min="gridMin" :gap="16">
46
+ <div v-for="(section, index) in layoutItems" :key="section" class="demo-layout-tile"><span>0{{ index + 1 }}</span><strong>{{ section }}</strong></div>
47
+ </apple-grid>
48
+ </apple-stack>
49
+ <apple-card v-else-if="name === 'AppleCard'" class="demo-product" title="AirPods Max" subtitle="每个音符,都更动听。" eyebrow="声音,出类拔萃。">
50
+ <apple-image :gallery="{ src: '/images/airpods-max-orange.jpg', alt: '橙色 AirPods Max 耳机' }" aspect-ratio="1" fit="contain" />
51
+ <template #actions><apple-button @click="notify('已加入购物袋')">加入购物袋</apple-button><apple-button variant="ghost" :icon="icons.Heart" icon-only label="收藏" @click="notify('已收藏')" /></template>
52
+ </apple-card>
53
+ <apple-stack v-else-if="name === 'AppleImage'" class="demo-image">
54
+ <apple-segmented-control v-model="imageLayout" :items="[{label:'省空间',value:'compact'},{label:'平铺',value:'tiled'},{label:'换行平铺',value:'tiled-wrap'}]" label="图片组布局" />
55
+ <apple-switch v-model="imageCarousel" label="轮播模式" />
56
+ <apple-switch v-model="imageSquare" label="强制 1:1(裁剪)" />
57
+ <apple-image :carousel="imageCarousel" :gallery="imageCarousel ? slides : galleryImages" :gallery-layout="imageLayout" :gallery-shape="imageSquare ? 'square' : 'natural'" />
58
+ </apple-stack>
59
+ <apple-stack v-else-if="name === 'AppleAvatar'"><apple-slider v-model="avatarSize" label="头像尺寸" :min="24" :max="72" :step="8" /><apple-stack direction="row" align="center"><apple-avatar name="林初" :size="avatarSize" /><apple-avatar name="Alex" :size="avatarSize" /><apple-avatar name="Taylor" :size="avatarSize" square /></apple-stack></apple-stack>
60
+ <apple-stack v-else-if="name === 'AppleAvatarGroup'"><apple-stepper v-model="avatarMax" label="显示人数" :min="1" :max="5" /><apple-avatar-group :items="people" :max="avatarMax" :size="44" label="设计团队" /></apple-stack>
61
+ <apple-stack v-else-if="name === 'AppleBadge'" direction="row" align="center"><apple-badge :value="unread"><apple-button variant="secondary" :icon="icons.Bell" @click="unread++">新消息</apple-button></apple-badge><apple-button variant="ghost" :disabled="!unread" @click="unread = 0">全部已读</apple-button></apple-stack>
62
+ <apple-stack v-else-if="name === 'AppleTag'" class="demo-tag-row" direction="row" align="center" :gap="10"><apple-tag>默认标签</apple-tag><apple-tag tone="success">已完成</apple-tag><apple-tag tone="info">进行中</apple-tag><apple-tag tone="warning">待确认</apple-tag><apple-tag v-if="tag" label="设计系统" closable @close="tag = false">设计系统</apple-tag><apple-button v-else variant="ghost" :icon="icons.Plus" @click="tag = true">添加标签</apple-button></apple-stack>
63
+ <apple-stack v-else-if="name === 'AppleDivider'"><apple-switch v-model="dividerVertical" label="纵向分隔" /><apple-stack :direction="dividerVertical ? 'row' : 'column'" :gap="0" align="center"><span>配送到家</span><apple-divider :vertical="dividerVertical" :label="dividerVertical ? undefined : '更多选择'" /><span>到店取货</span></apple-stack></apple-stack>
64
+ <apple-input v-else-if="name === 'AppleInput'" v-model="text" label="姓名" placeholder="怎么称呼你" clearable />
65
+ <apple-textarea v-else-if="name === 'AppleTextarea'" v-model="bio" label="个人简介" placeholder="写下你的想法" :maxlength="160" counter />
66
+ <apple-select v-else-if="name === 'AppleSelect'" v-model="city" label="所在城市" :items="cities" />
67
+ <apple-autocomplete v-else-if="name === 'AppleAutocomplete'" v-model="device" label="搜索设备" :items="devices" />
68
+ <apple-checkbox v-else-if="name === 'AppleCheckbox'" v-model="checked" label="我已阅读并同意条款" />
69
+ <apple-radio-group v-else-if="name === 'AppleRadioGroup'" v-model="delivery" label="配送方式" :items="deliveryItems" />
70
+ <apple-switch v-else-if="name === 'AppleSwitch'" v-model="checked" label="接收通知" />
71
+ <apple-slider v-else-if="name === 'AppleSlider'" v-model="volume" label="音量" />
72
+ <apple-stepper v-else-if="name === 'AppleStepper'" v-model="quantity" label="数量" :min="1" :max="10" />
73
+ <apple-segmented-control v-else-if="name === 'AppleSegmentedControl'" v-model="period" :items="periods" label="统计周期" />
74
+ <apple-stack v-else-if="name === 'AppleDatePicker'"><apple-select v-model="dateFormat" :items="dateFormats" label="日期精度" @update:model-value="date=''" /><apple-date-picker :key="dateFormat" v-model="date" :format="dateFormat" label="到店日期" /></apple-stack>
75
+ <apple-color-picker v-else-if="name === 'AppleColorPicker'" v-model="color" label="强调色" />
76
+ <apple-upload v-else-if="name === 'AppleUpload'" v-model="files" label="上传图片" accept="image/*" multiple :max-files="5" :max-size="5000000" @reject="notify('文件类型、大小或数量不符合要求')" />
77
+ <apple-otp-input v-else-if="name === 'AppleOtpInput'" v-model="otp" label="验证码" @complete="notify('验证码已填完')" />
78
+ <apple-cascader v-else-if="name === 'AppleCascader'" v-model="region" label="所在地区" :items="regions" />
79
+ <apple-rate v-else-if="name === 'AppleRate'" v-model="rating" label="体验评分" />
80
+ <apple-form v-else-if="name === 'AppleForm'" :validator="validate" @submit="notify('表单提交成功')">
81
+ <apple-input v-model="text" label="姓名" name="name" required placeholder="请输入姓名" />
82
+ <apple-input v-model="email" label="邮箱" name="email" type="email" required placeholder="name@example.com" />
83
+ <apple-button type="submit">提交</apple-button>
84
+ </apple-form>
85
+ <apple-form-field v-else-if="name === 'AppleFormField'" label="个人网站" :error="website && !website.startsWith('https://') ? '请输入以 https:// 开头的网址' : ''" hint="你的公开主页">
86
+ <template #default="field"><input v-bind="field" v-model="website" class="apple-control" type="url" placeholder="https://example.com" /></template>
87
+ </apple-form-field>
88
+ <apple-navibar v-else-if="name === 'AppleNavibar'" v-model="tab" brand="Apptify" :fixed="false" :items="tabs" label="示例导航"><template #actions><apple-button variant="ghost" :icon="icons.Bell" icon-only label="通知" @click="notify('暂无新通知')" /></template></apple-navibar>
89
+ <apple-aside v-else-if="name === 'AppleAside'" :hide-on-preview="false" aria-label="示例侧栏"><apple-list v-model="setting" :items="settings" selectable /></apple-aside>
90
+ <apple-tabs v-else-if="name === 'AppleTabs'" v-model="tab" :items="tabs"><template #default="{ value }"><p class="demo-copy">{{ value === 'overview' ? '这里是产品概览。' : value === 'spec' ? '这里是技术规格。' : '你的服务与支持。' }}</p></template></apple-tabs>
91
+ <apple-tab-bar v-else-if="name === 'AppleTabBar'" v-model="tab" :items="tabs"><template #default="{ value }"><p class="demo-copy">{{ value === 'overview' ? '这里是产品概览。' : value === 'spec' ? '这里是技术规格。' : '你的服务与支持。' }}</p></template></apple-tab-bar>
92
+ <apple-breadcrumbs v-else-if="name === 'AppleBreadcrumbs'" :items="[{label:'组件',value:'home'},{label:'导航',value:'nav'},{label:'面包屑',value:'current'}]" @click="item=>$emit('navigate',item.value==='home'?'全部组件':'导航')" />
93
+ <apple-pagination v-else-if="name === 'ApplePagination'" v-model="page" :total="120" :page-size="10" />
94
+ <apple-steps v-else-if="name === 'AppleSteps'" v-model="step" :items="steps" clickable />
95
+ <apple-accordion v-else-if="name === 'AppleAccordion'" :items="questions" />
96
+ <apple-stack v-else-if="name === 'AppleTable'"><apple-switch v-model="virtualTable" label="虚拟滚动 · 5000 行" /><apple-table :columns="columns" :rows="virtualTable ? virtualRows : rows" selectable :virtual="virtualTable" :height="360" :page-size="virtualTable ? 0 : 3" /></apple-stack>
97
+ <apple-tree v-else-if="name === 'AppleTree'" v-model="tree" :items="folders" />
98
+ <apple-list v-else-if="name === 'AppleList'" v-model="setting" :items="settings" selectable @select="notify('已选择设置项')" />
99
+ <apple-stack v-else-if="name === 'AppleTimeline'"><apple-segmented-control v-model="timelineDirection" :items="[{label:'纵向',value:'vertical'},{label:'横向',value:'horizontal'}]" label="时间线方向" /><apple-timeline :items="events" :orientation="timelineDirection" /><apple-button variant="secondary" @click="updateDelivery">{{ delivered ? '查看配送过程' : '确认签收' }}</apple-button></apple-stack>
100
+ <apple-stack v-else-if="name === 'AppleStatistic'"><apple-segmented-control v-model="period" :items="periods" label="统计周期" /><apple-statistic :label="period === 'day' ? '今日访问' : period === 'week' ? '本周访问' : '本月访问'" :value="period === 'day' ? 428 : period === 'week' ? 2996 : 12840" suffix="次" description="最新访问数据" /></apple-stack>
101
+ <apple-stack v-else-if="name === 'AppleDialog'" direction="row"><apple-button @click="openDialog">打开对话框</apple-button><apple-button variant="secondary" @click="openStack">多层对话框</apple-button></apple-stack>
102
+ <template v-else-if="name === 'AppleDrawer'"><apple-button @click="drawer = true">打开抽屉</apple-button><apple-drawer v-model="drawer" title="偏好设置"><apple-stack><apple-switch v-model="checked" label="接收通知" /><apple-slider v-model="volume" label="音量" /></apple-stack></apple-drawer></template>
103
+ <template v-else-if="name === 'AppleSheet'"><apple-button @click="sheet = true">打开底部面板</apple-button><apple-sheet v-model="sheet" title="分享这份灵感"><apple-stack direction="row"><apple-button @click="notify('链接已准备好'); sheet = false">生成链接</apple-button><apple-button variant="secondary" @click="sheet = false">取消</apple-button></apple-stack></apple-sheet></template>
104
+ <apple-stack v-else-if="name === 'AppleSnackbar'" direction="row"><apple-button @click="notify('你的更改已保存')">成功提示</apple-button><apple-button variant="secondary" @click="$apple.notify('网络连接暂时中断', {tone:'warning'})">提醒</apple-button><apple-button variant="outline" @click="notify('第一条消息'); notify('第二条消息')">堆叠提示</apple-button></apple-stack>
105
+ <apple-stack v-else-if="name === 'AppleAlert'"><apple-alert v-model="alertVisible" title="一切已就绪" tone="success" closable>内容已同步到你的所有设备。</apple-alert><apple-button v-if="!alertVisible" variant="secondary" @click="alertVisible = true">重新同步</apple-button></apple-stack>
106
+ <apple-stack v-else-if="name === 'AppleProgress'"><apple-progress :model-value="volume" label="正在上传" show-value /><apple-slider v-model="volume" label="进度" /></apple-stack>
107
+ <apple-stack v-else-if="name === 'AppleSpinner'"><apple-switch v-model="demoLoading" label="同步内容" /><apple-spinner v-if="demoLoading" label="正在同步">正在同步</apple-spinner><apple-tag v-else tone="success">已同步</apple-tag></apple-stack>
108
+ <apple-stack v-else-if="name === 'AppleSkeleton'"><apple-select v-model="skeletonVariant" :items="skeletonVariants" label="骨架类型" /><apple-switch v-model="demoLoading" label="加载内容" /><apple-skeleton v-if="demoLoading" :variant="skeletonVariant" :lines="3" avatar /><apple-list v-else :items="settings" /></apple-stack>
109
+ <apple-empty v-else-if="name === 'AppleEmpty'" title="还没有收藏" description="喜欢的内容,会出现在这里。"><apple-button variant="secondary" @click="notify('开始浏览')">开始探索</apple-button></apple-empty>
110
+ <apple-popover v-else-if="name === 'ApplePopover'" label="更多选项"><template #activator><apple-button variant="secondary" :icon="icons.MoreHorizontal">更多选项</apple-button></template><p>已同步到 iCloud</p><apple-switch v-model="checked" label="自动同步" /></apple-popover>
111
+ <apple-tooltip v-else-if="name === 'AppleTooltip'" text="添加到收藏"><apple-button variant="secondary" :icon="icons.Heart" icon-only label="收藏" @click="notify('已收藏')" /></apple-tooltip>
112
+ <apple-menu v-else-if="name === 'AppleMenu'" :items="actions" label="更多操作" @select="notify('操作已完成')" />
113
+ <template v-else-if="name === 'AppleActionSheet'">
114
+ <apple-button @click="actionSheet = true">选择操作</apple-button>
115
+ <apple-action-sheet v-model="actionSheet" title="山间的一刻" message="收藏这份灵感,或与朋友分享。" @select="notify('操作已完成')">
116
+ <template #default="{ select }">
117
+ <div class="apple-action-sheet-items">
118
+ <button v-for="action in photoActions" :key="action.value" v-apple-ripple type="button" class="apple-action-sheet-item" :class="{'apple-action-sheet-item--danger':action.danger}" @click="select(action)">
119
+ <component :is="action.icon" :size="21" aria-hidden="true" /><span>{{ action.label }}<small>{{ action.description }}</small></span>
120
+ </button>
121
+ </div>
122
+ </template>
123
+ </apple-action-sheet>
124
+ </template>
125
+ <apple-pull-refresh v-else-if="name === 'ApplePullRefresh'" @refresh="refresh"><apple-list :items="[{label:'最新内容',value:'latest',description:refreshText},{label:'最近更新',value:'recent',description:'所有内容已同步'}]" /></apple-pull-refresh>
126
+ <apple-infinite-scroll v-else-if="name === 'AppleInfiniteScroll'" :finished="itemsCount >= 12" @load="loadMore"><apple-list :items="Array.from({length:itemsCount}, (_, i) => ({label:`灵感收藏 ${i+1}`, value:i, description:'刚刚更新'}))" /></apple-infinite-scroll>
127
+ <template v-else-if="name === 'AppleBackTop'"><apple-button :icon="icons.ArrowUp" variant="secondary" @click="toTop">回到顶部</apple-button><apple-back-top :threshold="0" :fixed="false" /></template>
128
+ <template v-else-if="name === 'AppleFloatingGroup'"><apple-button variant="secondary" @click="notify('快捷操作已就绪')">快捷操作</apple-button><apple-provider class="demo-floating-preview"><apple-floating-group :threshold="0"><apple-button icon-only :icon="icons.Plus" label="添加收藏" @click="notify('已加入收藏')" /><apple-button variant="secondary" icon-only :icon="icons.Bell" label="查看通知" @click="notify('暂无新通知')" /></apple-floating-group></apple-provider></template>
129
+ <apple-stack v-else-if="name === 'AppleAutoSize'"><apple-switch v-model="checked" label="显示详情" /><apple-auto-size><apple-list :items="checked ? settings : settings.slice(0,1)" selectable /></apple-auto-size></apple-stack>
130
+ <apple-stack v-else-if="name === 'AppleTransition'">
131
+ <apple-segmented-control v-model="period" :items="periods" label="统计周期" />
132
+ <div class="demo-transition-stage" :class="{ 'is-backward': transitionBackward }">
133
+ <apple-transition name="slide-x" mode="default" :appear="false">
134
+ <apple-statistic :key="period" :value="period==='day'?428:period==='week'?2996:12840" label="访问次数" />
135
+ </apple-transition>
136
+ </div>
137
+ </apple-stack>
138
+ <apple-marquee v-else-if="name === 'AppleMarquee'" text="你的新设备已准备就绪,今天就开始探索更多可能。" />
139
+ </div>
140
+ </template>
141
+
142
+ <script lang="ts">
143
+ import { defineComponent, h, markRaw } from 'vue'
144
+ import { Plus, Heart, Bell, ArrowUpRight, ArrowUp, MoreHorizontal, ZoomIn, Share2, Trash2 } from 'lucide-vue-next'
145
+ import { AppleButton } from 'apptify'
146
+ export default defineComponent({
147
+ name: 'ComponentDemo', props: { name: { type: String, required: true } }, emits: ['navigate'],
148
+ data() { return {
149
+ icons: markRaw({ Plus, Heart, Bell, ArrowUpRight, ArrowUp, MoreHorizontal, ZoomIn }), text: '', bio: '', email: '', city: 'beijing', device: null, checked: true, delivery: 'express', volume: 68, quantity: 1, period: 'month', date: '', time: '10:30', color: '#0071e3', files: [], otp: '', region: [], rating: 4, tab: 'overview', page: 1, step: 1, tree: '', drawer: false, sheet: false, actionSheet: false, tag: true, refreshText: '今天 09:41', itemsCount: 3,
150
+ searchText: '', setting: 'profile', website: '', previewTheme: 'dark', stackDirection: 'row' as 'row' | 'column', containerWidth: 480, gridMin: 160, avatarSize: 48, avatarMax: 3, unread: 8, dividerVertical: false, delivered: false, alertVisible: true, demoLoading: true, imageLayout: 'compact' as 'compact' | 'tiled' | 'tiled-wrap', imageSquare: false, imageCarousel: false,
151
+ virtualTable: false, transitionBackward: false,
152
+ timelineDirection:'vertical' as 'vertical'|'horizontal', skeletonVariant:'text' as 'text'|'avatar'|'card'|'list'|'table'|'image', skeletonVariants:[{label:'文本',value:'text'},{label:'头像',value:'avatar'},{label:'卡片',value:'card'},{label:'列表',value:'list'},{label:'表格',value:'table'},{label:'图片',value:'image'}],
153
+ dateFormat:'YYYY/MM/DD HH:mm', dateFormats:[{label:'年 / 月',value:'YYYY/MM'},{label:'年 / 月 / 日',value:'YYYY/MM/DD'},{label:'日期与时分',value:'YYYY/MM/DD HH:mm'},{label:'日期与时分秒',value:'YYYY/MM/DD HH:mm:ss'},{label:'时分',value:'HH:mm'},{label:'时分秒',value:'HH:mm:ss'}],
154
+ previewThemes: [{label:'浅色',value:'light'},{label:'深色',value:'dark'},{label:'石墨',value:'graphite'},{label:'玫瑰',value:'rose'}], layoutItems: ['概览', '设计', '支持'],
155
+ galleryImages: [
156
+ { src: '/images/lake.jpg', alt: '山间湖泊与小屋' },
157
+ { src: '/images/airpods-max-orange.jpg', alt: '橙色 AirPods Max 耳机' },
158
+ { src: '/images/city.jpg', alt: '城市天际线' },
159
+ { src: '/images/mountains.jpg', alt: '群山与山峰' },
160
+ { src: '/images/forest.jpg', alt: '阳光下的森林' },
161
+ { src: '/images/architecture.jpg', alt: '建筑细节' },
162
+ { src: '/images/coast.jpg', alt: '海岸风景' },
163
+ { src: '/images/food.jpg', alt: '餐桌上的美食' },
164
+ { src: '/images/desert.jpg', alt: '沙漠沙丘' },
165
+ { src: '/images/waterfall.jpg', alt: '山间瀑布' },
166
+ { src: '/images/sunset.jpg', alt: '夕阳风景' },
167
+ { src: '/images/snow.jpg', alt: '雪地风景' },
168
+ ],
169
+ images: ['/images/lake.jpg', '/images/airpods-max-orange.jpg'], people: [{name:'林初'},{name:'Alex'},{name:'Taylor'},{name:'Quinn'},{name:'Sam'}],
170
+ cities: [{label:'北京',value:'beijing'},{label:'上海',value:'shanghai'},{label:'杭州',value:'hangzhou'}], devices: [{label:'MacBook Air',value:'mac'},{label:'iPad Pro',value:'ipad'},{label:'AirPods Max',value:'airpods'}],
171
+ deliveryItems: [{label:'快递送达',value:'express'},{label:'到店取货',value:'pickup'}], periods: [{label:'日',value:'day'},{label:'周',value:'week'},{label:'月',value:'month'}],
172
+ regions: [{label:'北京市',value:'beijing',children:[{label:'朝阳区',value:'chaoyang'},{label:'海淀区',value:'haidian'}]},{label:'浙江省',value:'zhejiang',children:[{label:'杭州市',value:'hangzhou',children:[{label:'西湖区',value:'xihu'}]}]}],
173
+ tabs: [{label:'概览',value:'overview'},{label:'技术规格',value:'spec'},{label:'支持',value:'support'}], steps: [{label:'选择产品',value:0},{label:'确认信息',value:1},{label:'完成',value:2}],
174
+ questions: [{label:'如何管理我的订阅?',value:'subscription',content:'前往账户设置,选择订阅即可查看或管理。'},{label:'可以随时取消吗?',value:'cancel',content:'可以。你可以随时在账户中取消,服务会保留至当前周期结束。'},{label:'如何获得帮助?',value:'support',content:'通过支持页面联系我们,我们很乐意提供帮助。'}],
175
+ columns: [{key:'name',label:'名称',sortable:true},{key:'category',label:'分类',sortable:true},{key:'status',label:'状态'},{key:'updated',label:'更新时间',sortable:true}], rows: [{id:1,name:'Apple Card',category:'基础',status:'已发布',updated:'09-24'},{id:2,name:'Apple Dialog',category:'反馈',status:'已发布',updated:'09-25'},{id:3,name:'Apple Input',category:'表单',status:'已发布',updated:'09-26'},{id:4,name:'Apple Image',category:'基础',status:'已发布',updated:'09-26'}],
176
+ folders: [{label:'设计资源',value:'design',children:[{label:'组件',value:'components',children:[{label:'按钮',value:'buttons'},{label:'输入框',value:'inputs'}]},{label:'图标',value:'icons'}]},{label:'项目文件',value:'project'}],
177
+ settings: [{label:'个人资料',value:'profile',description:'姓名、头像和联系方式'},{label:'通知',value:'notifications',description:'推送和邮件偏好'},{label:'隐私与安全',value:'security',description:'管理账户的安全设置'}],
178
+ events: [{label:'订单已确认',value:'confirmed',description:'我们正在准备你的订单',time:'09:41'},{label:'正在配送',value:'shipping',description:'你的包裹已出发',time:'12:00'},{label:'即将送达',value:'arrival',description:'留意配送通知',time:'预计明天'}],
179
+ actions: [{label:'收藏',value:'favorite'},{label:'分享',value:'share'},{label:'删除',value:'delete',danger:true}], slides: [{label:'山间清晨',value:0,src:'/images/lake.jpg'},{label:'一场聆听',value:1,src:'/images/airpods-max-orange.jpg'}],
180
+ photoActions: [{label:'加入收藏',value:'favorite',description:'将这张照片保存到你的灵感收藏',icon:markRaw(Heart),danger:false},{label:'分享照片',value:'share',description:'与朋友分享山间的这一刻',icon:markRaw(Share2),danger:false},{label:'删除照片',value:'delete',description:'从当前相册中移除',icon:markRaw(Trash2),danger:true}],
181
+ timers: [] as ReturnType<typeof setTimeout>[],
182
+ } },
183
+ computed: {
184
+ virtualRows() { return this.virtualTable ? Array.from({length:5000},(_,i)=>({id:i+1,name:`组件 ${String(i+1).padStart(4,'0')}`,category:i%2?'基础':'表单',status:'已发布',updated:'09-26'})) : [] },
185
+ demoClass(): string {
186
+ const compact = ['AppleSearch', 'AppleProvider', 'AppleAvatar', 'AppleAvatarGroup', 'AppleInput', 'AppleTextarea', 'AppleSelect', 'AppleAutocomplete', 'AppleCheckbox', 'AppleRadioGroup', 'AppleSwitch', 'AppleSlider', 'AppleStepper', 'AppleSegmentedControl', 'AppleDatePicker', 'AppleColorPicker', 'AppleUpload', 'AppleOtpInput', 'AppleCascader', 'AppleRate', 'AppleForm', 'AppleFormField', 'AppleProgress', 'AppleSpinner', 'AppleStatistic', 'AppleTransition']
187
+ const media = ['AppleImage']
188
+ return compact.includes(this.name) ? 'component-demo--compact' : media.includes(this.name) ? 'component-demo--media' : 'component-demo--wide'
189
+ },
190
+ searchResults() { return this.settings.filter(item => `${item.label} ${item.description}`.includes(this.searchText.trim())) },
191
+ },
192
+ watch: {
193
+ period(next: string, previous: string) {
194
+ this.transitionBackward = this.periods.findIndex(item => item.value === next) < this.periods.findIndex(item => item.value === previous)
195
+ },
196
+ },
197
+ beforeUnmount() { this.timers.forEach(clearTimeout) },
198
+ methods: {
199
+ notify(message: string) { this.$apple.notify(message, {tone:'success'}) },
200
+ validate(data: FormData) { return String(data.get('name') ?? '').trim().length > 0 || '请输入姓名' },
201
+ updateDelivery() { this.delivered = !this.delivered; this.events[2] = this.delivered ? { label: '已签收', value: 'arrival', description: '包裹已送达,祝你使用愉快', time: new Date().toLocaleTimeString('zh-CN') } : { label: '即将送达', value: 'arrival', description: '留意配送通知', time: '预计明天' } },
202
+ openDialog() { this.$apple.dialog({title:'保存更改?',message:'你的更改会同步到所有设备。',confirmText:'保存'}).result.then(result => { if (result) this.notify('你的更改已保存') }) },
203
+ openStack() {
204
+ const apple = this.$apple
205
+ const Inner = defineComponent({ props:['close','sendMessage'], render() { return h('div', {class:'demo-dialog-body'}, [h('p','第一层仍然保留在下面。'), h(AppleButton, {onClick:() => apple.dialog({title:'第二层对话框',message:'关闭这一层后,会回到第一层。'})}, () => '打开第二层')]) } })
206
+ apple.dialog({title:'第一层对话框',component:Inner})
207
+ },
208
+ refresh(done: () => void) { this.timers.push(setTimeout(() => { this.refreshText = `更新于 ${new Date().toLocaleTimeString('zh-CN')}`; done() }, 700)) },
209
+ loadMore(done: () => void) { this.timers.push(setTimeout(() => { this.itemsCount = Math.min(12, this.itemsCount + 3); done() }, 500)) },
210
+ toTop() { window.scrollTo({top:0,behavior:this.$apple.motion.value.mode === 'none' || this.$apple.motion.value.reduced ? 'instant':'smooth'}) },
211
+ },
212
+ })
213
+ </script>
214
+
215
+ <style scoped>
216
+ .demo-tag-row { width: 100%; max-width: 100%; }
217
+ .component-demo > .demo-tag-row > :deep(.apple-button) { min-height: 44px; padding: 7px 10px; font-size: 11px; font-weight: 500; line-height: 1.5; }
218
+ .demo-tag-row :deep(.apple-button__content) { gap: 4px; }
219
+ .demo-tag-row :deep(.apple-button__content > svg) { width: 13px; height: 13px; }
220
+ .component-demo > .demo-tag-row > :deep(.apple-tag),
221
+ .component-demo > .demo-tag-row > :deep(.apple-button) { align-self: center; max-width: 100%; }
222
+
223
+ .demo-transition-stage { display: grid; min-width: 0; overflow: hidden; --demo-slide-distance: 24px; }
224
+ .demo-transition-stage.is-backward { --demo-slide-distance: -24px; }
225
+ .demo-transition-stage :deep(.apple-statistic) { grid-area: 1 / 1; min-width: 0; }
226
+ .demo-transition-stage :deep(.apple-slide-x-enter-active),
227
+ .demo-transition-stage :deep(.apple-slide-x-leave-active) { transition: opacity var(--apple-duration) var(--apple-ease), transform var(--apple-duration) var(--apple-ease); }
228
+ .demo-transition-stage :deep(.apple-slide-x-enter-from) { opacity: 0; transform: translateX(var(--demo-slide-distance)); }
229
+ .demo-transition-stage :deep(.apple-slide-x-leave-to) { opacity: 0; transform: translateX(calc(-1 * var(--demo-slide-distance))); }
230
+ @media (prefers-reduced-motion: reduce) {
231
+ .demo-transition-stage :deep(.apple-slide-x-enter-from),
232
+ .demo-transition-stage :deep(.apple-slide-x-leave-to) { transform: none; }
233
+ }
234
+ </style>
@@ -0,0 +1,80 @@
1
+ export interface CatalogItem { name: string; label: string; group: string; description: string; api: string; code: string }
2
+ const item = (name: string, label: string, group: string, description: string, api: string, code: string): CatalogItem => ({ name, label, group, description, api, code })
3
+ export const catalog: CatalogItem[] = [
4
+ item('AppleButton', '按钮', '基础', '清晰的操作层级。', 'variant · size · icon · iconOnly · label · loading · disabled · ripple · motion', '<apple-button variant="primary" :loading="saving" @click="save">继续</apple-button>'),
5
+ item('AppleLink', '链接', '基础', '让下一步清楚可见。', 'href · external · disabled | slot: default', '<apple-link href="https://www.apple.com.cn/" external>Apple 官网</apple-link>'),
6
+ item('AppleSearch', '搜索', '基础', '快速找到关心的内容。', 'v-model(string) · label · placeholder · disabled | search(query)', '<apple-search v-model="query" label="搜索设置" placeholder="搜索设置" @search="search" />'),
7
+ item('AppleProvider', '主题容器', '基础', '独立区域,也有统一的设计语言。', 'theme · motion(auto|full|reduced|none) | slot: default', '<apple-provider theme="dark" motion="auto">\n <apple-button>继续</apple-button>\n</apple-provider>'),
8
+ item('AppleContainer', '内容容器', '基础', '在不同屏幕上保留合适的内容宽度。', 'width(number|string) | slot: default', '<apple-container :width="960">\n <apple-stack>内容</apple-stack>\n</apple-container>'),
9
+ item('AppleStack', '弹性布局', '基础', '有序排列,自然换行。', 'direction(row|column) · gap · align · wrap | slot: default', '<apple-stack direction="row" :gap="16" align="center">\n <apple-button>继续</apple-button>\n <apple-button variant="secondary">取消</apple-button>\n</apple-stack>'),
10
+ item('AppleGrid', '网格布局', '基础', '让列数随内容空间变化。', 'min(number) · gap(number) | slot: default', '<apple-grid :min="240" :gap="20">\n <div v-for="item in items" :key="item.id">{{ item.label }}</div>\n</apple-grid>'),
11
+ item('AppleCard', '卡片', '基础', '来自 Apple Store 的产品表达。', 'title · subtitle · text · image · zoom · shadow · motion | slots: media, title, icon, actions', '<apple-card title="AirPods Max" subtitle="声音,出类拔萃。" motion="auto" zoom="small">\n <template #actions><apple-button>进一步了解</apple-button></template>\n</apple-card>'),
12
+ item('AppleImage', '图片与预览', '基础', '让值得细看的内容更近一步。', 'gallery(object|array) · index · preview · carousel · disabled · label · galleryLayout(compact|tiled|tiled-wrap) · v-model:index | item({item,index,active}) · squared · aspectRatio · fit', '<apple-image :gallery="images" gallery-layout="compact" :index="0" />'),
13
+ item('AppleAvatar', '头像', '基础', '为每一位用户保留位置。', 'src · name · size', '<apple-avatar name="林初" :size="48" />'),
14
+ item('AppleAvatarGroup', '头像组', '基础', '一起参与的人,一眼可见。', 'items[{name,src,value}] · max · size · label | slot: default', '<apple-avatar-group :items="members" :max="4" :size="40" label="项目成员" />'),
15
+ item('AppleBadge', '徽标', '基础', '不打扰,也不遗漏。', 'value · max · dot · tone', '<apple-badge :value="8"><apple-button>消息</apple-button></apple-badge>'),
16
+ item('AppleTag', '标签', '基础', '轻量的分类与状态。', 'tone · closable | close', '<apple-tag tone="success" closable @close="remove">已完成</apple-tag>'),
17
+ item('AppleDivider', '分隔线', '基础', '恰到好处的分界。', 'label · vertical', '<apple-divider label="更多选择" />'),
18
+ item('AppleInput', '输入框', '表单', '每一次输入,都自然流畅。', 'v-model · label · placeholder · type · error · hint · clearable · disabled', '<apple-input v-model="name" label="姓名" placeholder="怎么称呼你" clearable />'),
19
+ item('AppleTextarea', '多行输入', '表单', '为更长的想法留白。', 'v-model · label · rows · maxlength · error · hint', '<apple-textarea v-model="bio" label="个人简介" :maxlength="160" />'),
20
+ item('AppleSelect', '选择器', '表单', '从容找到你的选择。', 'v-model · label · items[{label,value,disabled}] · placeholder · disabled', '<apple-select v-model="city" label="所在城市" :items="cities" />'),
21
+ item('AppleAutocomplete', '自动补全', '表单', '想到哪里,找到哪里。', 'v-model · label · items · placeholder · disabled', '<apple-autocomplete v-model="device" label="搜索设备" :items="devices" />'),
22
+ item('AppleCheckbox', '复选框', '表单', '一个,或不止一个。', 'v-model · label · disabled · indeterminate', '<apple-checkbox v-model="agreed" label="我已阅读并同意条款" />'),
23
+ item('AppleRadioGroup', '单选框', '表单', '专注于一个明确的选择。', 'v-model · label · items · disabled', '<apple-radio-group v-model="delivery" label="配送方式" :items="methods" />'),
24
+ item('AppleSwitch', '开关', '表单', '简单的一开一关。', 'v-model · label · disabled', '<apple-switch v-model="notifications" label="接收通知" />'),
25
+ item('AppleSlider', '滑块', '表单', '把控制权交给指尖。', 'v-model · label · min · max · step · disabled', '<apple-slider v-model="volume" label="音量" :min="0" :max="100" />'),
26
+ item('AppleStepper', '步进器', '表单', '精确一点,也轻松一点。', 'v-model · label · min · max · step', '<apple-stepper v-model="quantity" label="数量" :min="1" :max="10" />'),
27
+ item('AppleSegmentedControl', '分段控制', '表单', '在相邻的视图间切换。', 'v-model · items · label · disabled', '<apple-segmented-control v-model="period" :items="periods" label="统计周期" />'),
28
+ item('AppleDatePicker', '日期与时间', '表单', '为值得记住的日子。', 'v-model · format · granularity · label · min · max · required · disabled | change(value)', '<apple-date-picker v-model="date" label="到店日期" format="YYYY/MM/DD HH:mm" />'),
29
+ item('AppleColorPicker', '颜色选择', '表单', '找到属于你的颜色。', 'v-model(#hex) · label · disabled', '<apple-color-picker v-model="color" label="强调色" />'),
30
+ item('AppleUpload', '文件上传', '表单', '拖入文件,继续创作。', 'v-model(File[]) · label · accept · multiple · maxSize(bytes) · maxFiles · capture | reject([{file,reason}]), remove(file)', '<apple-upload v-model="files" label="上传图片" accept="image/*" multiple :max-files="5" />'),
31
+ item('AppleOtpInput', '验证码', '表单', '验证步骤,也可以很顺手。', 'v-model · length · label | complete', '<apple-otp-input v-model="code" :length="6" label="验证码" />'),
32
+ item('AppleCascader', '级联选择', '表单', '从整体,找到细节。', 'v-model(value[]) · items[{label,value,children}] · label', '<apple-cascader v-model="region" label="所在地区" :items="regions" />'),
33
+ item('AppleRate', '评分', '表单', '记录真实的感受。', 'v-model · max · label · readonly · disabled', '<apple-rate v-model="rating" label="体验评分" />'),
34
+ item('AppleForm', '表单验证', '表单', '把完整提交做好。', 'validator(FormData) => boolean|string|Promise · loading · disabled | submit(FormData), invalid, reset | methods: validate(), submit(), reset()', '<apple-form :validator="validate" @submit="submit">\n <apple-input v-model="name" name="name" label="姓名" required />\n <apple-button type="submit">提交</apple-button>\n</apple-form>'),
35
+ item('AppleFormField', '表单字段', '表单', '为自定义控件补齐标签与验证信息。', 'label · for · error · hint · required · disabled | default({id,disabled,required,aria-invalid,aria-describedby})', '<apple-form-field label="个人网站" :error="error">\n <template #default="field">\n <input v-bind="field" v-model="website" class="apple-control" type="url" />\n </template>\n</apple-form-field>'),
36
+ item('AppleNavibar', '顶部导航栏', '导航', '随可用宽度折叠的顶部导航。', 'v-model · items · brand · brandHref · fixed · breakpoint=640 · hideOnPreview · label · motion | change(value,item), toggle(open) | slots: brand, item, actions', '<apple-navibar v-model="page" brand="Apptify" :items="pages" />'),
37
+ item('AppleAside', '侧栏容器', '导航', '图片归位后,导航自然恢复。', 'hideOnPreview=true · motion | slot: default | 布局由调用方提供', '<apple-aside :hide-on-preview="false">\n <apple-list :items="items" />\n</apple-aside>'),
38
+ item('AppleTabs', '标签页', '导航', '内容各归其位。', 'v-model · items[{label,value,disabled}] | slot: default({value})', '<apple-tabs v-model="tab" :items="tabs">\n <template #default="{ value }">{{ value }}</template>\n</apple-tabs>'),
39
+ item('AppleTabBar', '页签栏', '导航', '相邻页面,自然衔接。', 'v-model · items · label · disabled · motion | slot: default({value})', '<apple-tab-bar v-model="tab" :items="tabs">\n <template #default="{value}">{{ value }}</template>\n</apple-tab-bar>'),
40
+ item('AppleBreadcrumbs', '面包屑', '导航', '始终知道身在何处。', 'items[{label,value,href,disabled}] · label', '<apple-breadcrumbs :items="[{label:\'首页\',value:\'home\',href:\'/\'},{label:\'设置\',value:\'settings\'}]" />'),
41
+ item('ApplePagination', '分页', '导航', '一步步,轻松浏览。', 'v-model(page) · total · pageSize', '<apple-pagination v-model="page" :total="120" :page-size="10" />'),
42
+ item('AppleSteps', '步骤条', '导航', '每一步,都有进展。', 'v-model(index) · items[{label,value,description,disabled}] · clickable · disabled', '<apple-steps v-model="step" :items="steps" clickable />'),
43
+ item('AppleAccordion', '折叠面板', '数据展示', '需要时,再展开。', 'v-model · items[{label,value,content}] · multiple', '<apple-accordion :items="questions" />'),
44
+ item('AppleTable', '数据表格', '数据展示', '复杂信息,清晰呈现。', 'columns[{key,label,sortable,width,minWidth,maxWidth}] · rows · rowKey · selectable · pageSize · virtual · height · rowHeight · overscan · resizable | update:selected, column-resize', '<apple-table :columns="columns" :rows="rows" row-key="id" virtual :height="360" :row-height="48" />'),
45
+ item('AppleTree', '树形结构', '数据展示', '有层次,也有条理。', 'v-model · items[{label,value,children}] | select', '<apple-tree v-model="selected" :items="folders" />'),
46
+ item('AppleList', '列表', '数据展示', '内容优先的日常界面。', 'v-model · items[{label,value,description,href,disabled}] · selectable | select(item)', '<apple-list v-model="setting" :items="settings" selectable @select="openSetting" />'),
47
+ item('AppleTimeline', '时间线', '数据展示', '把发生的事串联起来。', 'items[{label,value,description,time,tone}] · orientation(horizontal|vertical) · label', '<apple-timeline :items="events" orientation="horizontal" />'),
48
+ item('AppleStatistic', '统计数值', '数据展示', '重要数据,一目了然。', 'label · value · prefix · suffix · precision', '<apple-statistic label="本月访问" :value="12840" suffix="次" />'),
49
+ item('AppleDialog', '对话框', '反馈', '重要的决定,值得专注。', 'v-model · title · persistent · confirmText · cancelText | confirm, cancel', '<apple-dialog v-model="open" title="保存更改?" @confirm="save">\n 你的更改会同步到所有设备。\n</apple-dialog>'),
50
+ item('AppleDrawer', '抽屉', '反馈', '让补充信息就近展开。', 'v-model · title · placement(left|right|bottom) · width · persistent · loading', '<apple-drawer v-model="open" title="偏好设置" placement="right">内容</apple-drawer>'),
51
+ item('AppleSheet', '底部面板', '反馈', '为拇指设计的临时空间。', 'v-model · title · persistent', '<apple-sheet v-model="open" title="分享">内容</apple-sheet>'),
52
+ item('AppleSnackbar', '轻提示', '反馈', '及时回应,不必打断。', 'v-model · message · title · tone · duration(ms) · action · closable | close, action', "const notice = apple.notify('已保存', { tone: 'success', duration: 4000 })"),
53
+ item('AppleAlert', '提示信息', '反馈', '让状态清楚可见。', 'v-model · title · message · tone · closable | close', '<apple-alert v-model="visible" title="一切已就绪" tone="success" closable>内容已同步。</apple-alert>'),
54
+ item('AppleProgress', '进度条', '反馈', '等待,也能心中有数。', 'modelValue · max · label · indeterminate · showValue · tone', '<apple-progress :model-value="68" label="正在上传" show-value />'),
55
+ item('AppleSpinner', '加载中', '反馈', '给等待一个轻巧的回应。', 'size · label', '<apple-spinner label="正在加载" />'),
56
+ item('AppleSkeleton', '骨架屏', '反馈', '内容到达之前。', 'variant(text|avatar|card|list|table|image) · lines · rows · columns · avatar · width · height · animated · label · motion', '<apple-skeleton variant="list" :rows="3" avatar />'),
57
+ item('AppleEmpty', '空状态', '反馈', '没有内容,也不空洞。', 'title · description | slot: default', '<apple-empty title="还没有收藏" description="喜欢的内容,会出现在这里。" />'),
58
+ item('ApplePopover', '气泡面板', '反馈', '让更多操作触手可及。', 'v-model · label · placement · align · width · disabled | activator({props,open,close,isOpen}), default({close})', '<apple-popover label="更多选项">\n <template #activator><apple-button variant="secondary">更多</apple-button></template>\n <apple-switch v-model="sync" label="自动同步" />\n</apple-popover>'),
59
+ item('AppleTooltip', '文字提示', '反馈', '在需要的时候多说一句。', 'text | slot: default', '<apple-tooltip text="添加到收藏"><apple-button>收藏</apple-button></apple-tooltip>'),
60
+ item('AppleMenu', '下拉菜单', '反馈', '按需展开的操作列表。', 'v-model · items[{label,value,disabled,danger,icon,description}] · label · selected | select(value,item)', '<apple-menu :items="actions" label="更多操作" @select="onAction" />'),
61
+ item('AppleActionSheet', '动作面板', '移动交互', '熟悉、直接的移动操作。', 'v-model · title · message · items[{label,value,disabled,danger}] · cancelText | default slot {select(item),close(value)} | select(value,item)', '<apple-action-sheet v-model="open" title="选择操作" @select="onAction">\n <template #default="{ select }">\n <apple-button @click="select({ label: \'收藏\', value: \'favorite\' })">加入收藏</apple-button>\n </template>\n</apple-action-sheet>'),
62
+ item('ApplePullRefresh', '下拉刷新', '移动交互', '轻轻下拉,获取新内容。', 'v-model(refreshing) · threshold · label · disabled | refresh(done)', '<apple-pull-refresh @refresh="async done => { await reload(); done() }">内容</apple-pull-refresh>'),
63
+ item('AppleInfiniteScroll', '无限滚动', '移动交互', '浏览,不必被翻页打断。', 'loading · finished · error · disabled · distance | load(done), retry(done)', '<apple-infinite-scroll :finished="finished" @load="async done => { await loadMore(); done() }">内容</apple-infinite-scroll>'),
64
+ item('AppleBackTop', '回到顶部', '移动交互', '回到开始,毫不费力。', 'threshold · label', '<apple-back-top :threshold="400" />'),
65
+ item('AppleFloatingGroup', '悬浮操作组', '移动交互', '常用操作,触手可及。', 'backTop · threshold · target · label · motion | default', '<apple-floating-group :threshold="300">\n <apple-button icon-only :icon="Plus" label="添加" />\n</apple-floating-group>'),
66
+ item('AppleAutoSize', '自适应尺寸', '基础', '内容变化,布局随行。', 'axis(height|both) · motion | default', '<apple-auto-size><div v-if="expanded">详情内容</div></apple-auto-size>'),
67
+ item('AppleTransition', '动效容器', '基础', '为页面保留连续感。', 'name(page|slide-x|slide-y|fade) · mode · appear · motion | default', '<apple-transition name="page"><router-view /></apple-transition>'),
68
+ item('AppleMarquee', '通知栏', '移动交互', '简短消息,保持可见。', 'text · duration(seconds) · paused · label · motion', '<apple-marquee text="新的内容已经准备好。" :duration="24" />'),
69
+ ]
70
+ export const groups = ['全部组件', '基础', '表单', '导航', '数据展示', '反馈', '移动交互']
71
+
72
+ export const componentId = (name: string) => name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
73
+ export const sections = [
74
+ { id: 'basics', label: '基础' },
75
+ { id: 'forms', label: '表单' },
76
+ { id: 'navigation', label: '导航' },
77
+ { id: 'data', label: '数据展示' },
78
+ { id: 'feedback', label: '反馈' },
79
+ { id: 'mobile', label: '移动交互' },
80
+ ].map(section => ({ ...section, items: catalog.filter(item => item.group === section.label) }))
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <nav class="component-index" aria-label="组件定位">
3
+ <AppleTree :items="treeItems" :model-value="activeId || activeGroup" :expanded="expandedGroups" label="组件目录树" @update:expanded="expanded = $event" @select="navigate">
4
+ <template #item="{ item }">
5
+ <router-link :to="`/components#${item.value}`" :class="['index-link', { 'index-group': item.children, active: activeId === item.value || (item.children && activeGroup === item.value) }]" :aria-current="activeId === item.value ? 'location' : undefined" tabindex="-1" @click.prevent>
6
+ <component :is="icons[item.label]" v-if="item.children" :size="15" aria-hidden="true" />
7
+ <span>{{ item.label }}</span><small v-if="item.children">{{ item.children.length }}</small>
8
+ </router-link>
9
+ </template>
10
+ </AppleTree>
11
+ <p v-if="!treeItems.length" class="index-empty" role="status">未找到组件,请尝试其他关键词。</p>
12
+ </nav>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { computed, ref, watch } from 'vue'
17
+ import { useRoute, useRouter } from 'vue-router'
18
+ import { Bell, Box, MousePointer2, Smartphone, Table2, TextCursorInput } from 'lucide-vue-next'
19
+ import { AppleTree } from 'apptify'
20
+ import type { AppleTreeItem, AppleValue } from 'apptify'
21
+ import { sections, componentId } from '../catalog'
22
+
23
+ const props = defineProps<{ activeGroup: string; activeId: string; search?: string }>()
24
+ const emit = defineEmits<{ navigate: [] }>()
25
+ const router = useRouter()
26
+ const route = useRoute()
27
+ const expanded = ref<AppleValue[]>([props.activeGroup])
28
+ const icons: Record<string, typeof Box> = { '基础': Box, '表单': TextCursorInput, '导航': MousePointer2, '数据展示': Table2, '反馈': Bell, '移动交互': Smartphone }
29
+ const query = computed(() => props.search?.trim().toLocaleLowerCase() ?? '')
30
+ const treeItems = computed<AppleTreeItem[]>(() => sections.flatMap(section => {
31
+ const groupMatches = section.label.toLocaleLowerCase().includes(query.value)
32
+ const items = section.items.filter(item => groupMatches || `${item.label} ${item.name} ${componentId(item.name)} ${item.description}`.toLocaleLowerCase().includes(query.value))
33
+ return items.length ? [{ label: section.label, value: section.id, children: items.map(item => ({ label: item.label, value: componentId(item.name) })) }] : []
34
+ }))
35
+ const expandedGroups = computed(() => query.value ? treeItems.value.map(item => item.value) : expanded.value)
36
+ watch(() => props.activeGroup, group => {
37
+ if (!expanded.value.includes(group)) expanded.value = [...expanded.value, group]
38
+ })
39
+ function navigate(item: AppleTreeItem) {
40
+ const hash = `#${item.value}`
41
+ if (route.hash === hash) {
42
+ const target = document.getElementById(String(item.value))
43
+ if (target) window.scrollTo({ top: target.getBoundingClientRect().top + window.scrollY - 96, behavior: 'instant' })
44
+ } else void router.push(`/components${hash}`)
45
+ emit('navigate')
46
+ }
47
+ </script>
48
+
49
+ <style scoped>
50
+ .component-index { min-width: 0; }
51
+ .component-index :deep(.apple-tree__row) { min-height: 38px; gap: 0; font-size: 12px; border-radius: 6px; }
52
+ .component-index :deep(.apple-tree__toggle), .component-index :deep(.apple-tree__spacer) { flex-basis: 24px; width: 24px; height: 34px; }
53
+ .component-index .index-link { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; padding: 8px 4px; font-size: 12px; color: inherit; text-decoration: none; }
54
+ .component-index .index-group { border-radius: 0; }
55
+ /* The tree row owns the fade; avoid a second, smaller hover background on its link. */
56
+ .component-index .index-link:hover { background: transparent; }
57
+ .index-link > span { flex: 1; overflow-wrap: anywhere; }
58
+ .index-link > svg { flex-shrink: 0; }
59
+ .index-link.active { color: var(--apple-text); font-weight: 600; }
60
+ .index-link small { font-size: 10px; color: var(--apple-secondary); font-variant-numeric: tabular-nums; }
61
+ .index-empty { margin: 12px 8px; font-size: 12px; line-height: 1.7; color: var(--apple-secondary); }
62
+ </style>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <footer class="page-footer"><span>Apptify</span><span>Write your own footer here</span></footer>
3
+ </template>
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <apple-popover v-model="opened" label="用户菜单" align="end" :width="280" panel-class="user-popup">
3
+ <template #activator>
4
+ <apple-button class="user-trigger user-account-trigger" variant="ghost" icon-only label="打开用户菜单" title="用户菜单"><UserRound :size="20" /></apple-button>
5
+ </template>
6
+ <div class="user-summary">
7
+ <apple-avatar name="林初" :size="48" />
8
+ <div><strong>林初 <small>演示账户</small></strong><span>lin.chu@example.com</span></div>
9
+ </div>
10
+ <div class="user-menu-list">
11
+ <apple-button variant="ghost" @click="showProfile"><UserRound :size="17" /><span>个人资料</span><ArrowUpRight :size="14" aria-hidden="true" /></apple-button>
12
+ <router-link to="/settings" @click="opened = false"><Settings2 :size="17" /><span>偏好设置</span><ArrowUpRight :size="14" aria-hidden="true" /></router-link>
13
+ </div>
14
+ <div class="user-menu-list user-menu-examples">
15
+ <apple-button variant="ghost" @click="openAuth('login')"><LogIn :size="17" /><span>登录 <small>Login</small></span><em>示例</em></apple-button>
16
+ <apple-button variant="ghost" @click="openAuth('register')"><UserPlus :size="17" /><span>注册 <small>Register</small></span><em>示例</em></apple-button>
17
+ </div>
18
+ </apple-popover>
19
+ <apple-dialog v-model="authOpen" class="user-auth-dialog" :width="560" :title="authMode === 'login' ? '登录示例' : '注册示例'" :show-footer="false" @after-close="password = ''">
20
+ <template #title>
21
+ <span class="user-auth-heading">
22
+ <span class="user-auth-symbol" aria-hidden="true">a</span>
23
+ <span>{{ authMode === 'login' ? '登录或切换用户' : '创建账户' }}</span>
24
+ </span>
25
+ </template>
26
+ <apple-form :key="authMode" class="user-auth-form" @submit="submitAuth">
27
+ <apple-input v-if="authMode === 'register'" ref="nameInput" v-model="name" label="姓名" autocomplete="name" required>
28
+ <template #prefix><UserRound :size="18" aria-hidden="true" /></template>
29
+ </apple-input>
30
+ <apple-input ref="emailInput" v-model="email" label="电子邮箱" type="email" autocomplete="email" placeholder="name@example.com" required>
31
+ <template #prefix><Mail :size="18" aria-hidden="true" /></template>
32
+ </apple-input>
33
+ <div class="user-auth-password">
34
+ <apple-input v-model="password" label="密码" type="password" :autocomplete="authMode === 'login' ? 'current-password' : 'new-password'" required>
35
+ <template #prefix><LockKeyhole :size="18" aria-hidden="true" /></template>
36
+ </apple-input>
37
+ <apple-button v-if="authMode === 'login'" class="user-auth-forgot" variant="ghost" size="small" @click="showAuthEntry('forgot')">忘记密码?</apple-button>
38
+ </div>
39
+ <p class="user-auth-note">这是表单交互示例,尚未接入账户服务。填写内容不会提交到服务器。</p>
40
+ <div class="user-auth-actions">
41
+ <apple-button class="user-auth-submit" type="submit" size="large">{{ authMode === 'login' ? '体验登录' : '体验注册' }}</apple-button>
42
+ <apple-button class="user-auth-email" variant="outline" size="large" @click="showAuthEntry('email')">邮箱验证码登录</apple-button>
43
+ </div>
44
+ </apple-form>
45
+ <div class="user-auth-alternatives">
46
+ <apple-link href="#" @click.prevent="switchAuthMode">
47
+ {{ authMode === 'login' ? '注册账户' : '已有账户?登录' }}<ArrowUpRight :size="14" aria-hidden="true" />
48
+ </apple-link>
49
+ </div>
50
+ </apple-dialog>
51
+ </template>
52
+
53
+ <script setup lang="ts">
54
+ import { nextTick, ref, watch } from 'vue'
55
+ import { useRoute } from 'vue-router'
56
+ import { ArrowUpRight, LockKeyhole, LogIn, Mail, Settings2, UserPlus, UserRound } from 'lucide-vue-next'
57
+ import { useApple } from 'apptify'
58
+
59
+ const apple = useApple()
60
+ const route = useRoute()
61
+ const opened = ref(false)
62
+ const authOpen = ref(false)
63
+ const authMode = ref<'login' | 'register'>('login')
64
+ const name = ref('')
65
+ const email = ref('')
66
+ const password = ref('')
67
+ const nameInput = ref<{ focus: () => void } | null>(null)
68
+ const emailInput = ref<{ focus: () => void } | null>(null)
69
+ watch(() => route.path, () => { opened.value = false; authOpen.value = false })
70
+
71
+ function openAuth(mode: 'login' | 'register') {
72
+ opened.value = false
73
+ authMode.value = mode
74
+ name.value = email.value = password.value = ''
75
+ authOpen.value = true
76
+ }
77
+ function submitAuth() {
78
+ authOpen.value = false
79
+ password.value = ''
80
+ apple.notify(authMode.value === 'login' ? '登录示例已完成' : '注册示例已完成', { tone: 'success' })
81
+ }
82
+ async function switchAuthMode() {
83
+ authMode.value = authMode.value === 'login' ? 'register' : 'login'
84
+ password.value = ''
85
+ await nextTick()
86
+ const firstInput = authMode.value === 'register' ? nameInput.value : emailInput.value
87
+ firstInput?.focus()
88
+ }
89
+ function showAuthEntry(entry: 'forgot' | 'email') {
90
+ apple.notify(entry === 'forgot'
91
+ ? '找回密码尚未接入账户服务,此示例不会发送重置邮件。'
92
+ : '邮箱验证码登录尚未接入账户服务,此示例不会发送验证码。')
93
+ }
94
+ function showProfile() {
95
+ opened.value = false
96
+ apple.dialog({ title: '个人资料', message: '林初 · lin.chu@example.com\n这是用于展示账户菜单的演示资料。', confirmText: '知道了', cancelText: '' })
97
+ }
98
+ </script>
99
+
100
+ <style>
101
+ .user-popup.apple-popover { border-radius: 24px; background: var(--apple-surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
102
+ .user-popup .user-menu-list > .apple-button > .apple-button__content { width: 100%; justify-content: flex-start; gap: 11px; }
103
+ .user-popup .user-menu-list > .apple-button { min-height: 44px; font-weight: 400; line-height: inherit; }
104
+ .user-popup .user-menu-list > .apple-button:hover:where(:not([data-apple-touch] *)):not(:disabled) { background: var(--apple-hover); color: var(--apple-text); }
105
+ .user-popup .user-menu-list > .apple-button:active:not(:disabled) { background: var(--apple-pressed); color: var(--apple-text); }
106
+ .user-trigger.user-account-trigger { width: 44px; height: 44px; border: 0; background: transparent; }
107
+ .user-trigger.user-account-trigger:focus-visible { outline: 3px solid var(--apple-accent); outline-offset: 2px; }
108
+ .user-auth-dialog.apple-modal { border-radius: 24px; background: var(--apple-surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
109
+ .user-auth-dialog .apple-modal-header { position: relative; justify-content: center; padding: 40px 44px 32px; }
110
+ .user-auth-dialog .apple-modal-heading { width: 100%; text-align: center; }
111
+ .user-auth-dialog .apple-modal-heading h2 { font-size: 30px; line-height: 1.35; }
112
+ .user-auth-dialog .apple-modal-header > .apple-overlay-icon { position: absolute; top: 8px; right: 8px; }
113
+ .user-auth-heading { display: flex; flex-direction: column; align-items: center; gap: 22px; }
114
+ .user-auth-symbol { display: grid; place-items: center; box-sizing: border-box; width: 64px; height: 64px; padding-bottom: 8px; border-radius: 16px; background: var(--apple-text); color: var(--apple-surface); font: 600 56px/1 Georgia, serif; }
115
+ .user-auth-dialog .apple-modal-body { padding: 0 64px 36px; }
116
+ .user-auth-form .apple-form__fields { gap: 20px; }
117
+ .user-auth-password { position: relative; }
118
+ .user-auth-password .apple-field__label { padding-right: 110px; }
119
+ .user-auth-password .user-auth-forgot { position: absolute; top: -12px; right: -8px; min-height: 44px; padding: 8px; font-size: 12px; }
120
+ .user-auth-dialog .user-auth-note { margin: 8px 0 20px; font-size: 13px; line-height: 1.75; }
121
+ .user-auth-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
122
+ .user-auth-actions .apple-button { width: 100%; padding-inline: 12px; border-radius: var(--apple-radius, 8px); font-size: 14px; }
123
+ .user-auth-alternatives { display: flex; justify-content: center; margin-top: 24px; font-size: 13px; }
124
+ @media (max-width: 480px) {
125
+ .user-auth-dialog .apple-modal-header { padding: 28px 24px 22px; }
126
+ .user-auth-dialog .apple-modal-body { padding: 0 20px 22px; }
127
+ .user-auth-dialog .apple-modal-heading h2 { font-size: 22px; }
128
+ .user-auth-actions { gap: 8px; }
129
+ .user-auth-actions .apple-button { padding-inline: 8px; font-size: 12px; }
130
+ .user-auth-dialog .user-auth-note { margin-block: 0 8px; }
131
+ }
132
+ </style>