apptify 0.6.1 → 0.7.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 (130) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_LICENSES.txt +23 -0
  3. package/dist/apptify.css +1 -1
  4. package/dist/apptify.js +2395 -2451
  5. package/dist/components/AppleCard.vue.d.ts +1 -1
  6. package/dist/components/content.d.ts +81 -20
  7. package/dist/components/forms.d.ts +2 -2
  8. package/dist/components/foundation.d.ts +133 -11
  9. package/dist/components/mobile-image-viewer.d.ts +35 -12
  10. package/dist/components/navibar.d.ts +2 -2
  11. package/dist/components/overlays.d.ts +10 -59
  12. package/dist/components/scrollbar.d.ts +88 -0
  13. package/dist/components/speed-dial.d.ts +157 -0
  14. package/dist/core/context.d.ts +11 -0
  15. package/dist/core/device.d.ts +2 -0
  16. package/dist/core/image-gallery-motion.d.ts +2 -1
  17. package/dist/core/image-motion.d.ts +1 -1
  18. package/dist/core/layers.d.ts +1 -0
  19. package/dist/core/motion-driver.d.ts +40 -0
  20. package/dist/core/motion.d.ts +1 -1
  21. package/dist/core/ripple.d.ts +16 -0
  22. package/dist/core/scroll-motion.d.ts +6 -0
  23. package/dist/core/scrollbar.d.ts +14 -0
  24. package/dist/core/speed-dial.d.ts +21 -0
  25. package/dist/index.d.ts +237 -26
  26. package/docs/COMPONENTS.md +10 -6
  27. package/package.json +5 -7
  28. package/templates/playground/README.md +1 -1
  29. package/templates/playground/package.json +1 -2
  30. package/templates/playground/source-snapshot.json +299 -66
  31. package/templates/playground/src/App.vue +1 -0
  32. package/templates/playground/src/ComponentDemo.vue +30 -21
  33. package/templates/playground/src/catalog.ts +70 -65
  34. package/templates/playground/src/components/ComponentIndex.vue +10 -14
  35. package/templates/playground/src/components/MasonryFeed.vue +52 -0
  36. package/templates/playground/src/components/UserMenu.vue +1 -1
  37. package/templates/playground/src/components/forbidden-paper-scene.vue +367 -0
  38. package/templates/playground/src/components/forbidden-paper-story.ts +233 -0
  39. package/templates/playground/src/editor/ComponentPlayground.vue +89 -0
  40. package/templates/playground/src/editor/ConfigurableEditor.vue +1 -1
  41. package/templates/playground/src/editor/apple-input-source.ts +1 -1
  42. package/templates/playground/src/editor/component-metadata.ts +72 -0
  43. package/templates/playground/src/editor/component-playground.ts +162 -0
  44. package/templates/playground/src/editor/contract.ts +1 -1
  45. package/templates/playground/src/editor/documents.ts +11 -2
  46. package/templates/playground/src/editor/handbook.ts +22 -0
  47. package/templates/playground/src/editor/handbooks/apple-accordion.ts +66 -0
  48. package/templates/playground/src/editor/handbooks/apple-action-sheet.ts +72 -0
  49. package/templates/playground/src/editor/handbooks/apple-alert.ts +67 -0
  50. package/templates/playground/src/editor/handbooks/apple-aside.ts +32 -0
  51. package/templates/playground/src/editor/handbooks/apple-auto-size.ts +32 -0
  52. package/templates/playground/src/editor/handbooks/apple-autocomplete.ts +114 -0
  53. package/templates/playground/src/editor/handbooks/apple-avatar-group.ts +50 -0
  54. package/templates/playground/src/editor/handbooks/apple-avatar.ts +56 -0
  55. package/templates/playground/src/editor/handbooks/apple-back-top.ts +62 -0
  56. package/templates/playground/src/editor/handbooks/apple-badge.ts +62 -0
  57. package/templates/playground/src/editor/handbooks/apple-breadcrumbs.ts +38 -0
  58. package/templates/playground/src/editor/handbooks/apple-button.ts +92 -0
  59. package/templates/playground/src/editor/handbooks/apple-card.ts +112 -0
  60. package/templates/playground/src/editor/handbooks/apple-cascader.ts +102 -0
  61. package/templates/playground/src/editor/handbooks/apple-checkbox.ts +85 -0
  62. package/templates/playground/src/editor/handbooks/apple-color-picker.ts +79 -0
  63. package/templates/playground/src/editor/handbooks/apple-container.ts +26 -0
  64. package/templates/playground/src/editor/handbooks/apple-date-picker.ts +104 -0
  65. package/templates/playground/src/editor/handbooks/apple-dialog.ts +151 -0
  66. package/templates/playground/src/editor/handbooks/apple-divider.ts +38 -0
  67. package/templates/playground/src/editor/handbooks/apple-drawer.ts +151 -0
  68. package/templates/playground/src/editor/handbooks/apple-empty.ts +43 -0
  69. package/templates/playground/src/editor/handbooks/apple-floating-group.ts +51 -0
  70. package/templates/playground/src/editor/handbooks/apple-form-field.ts +69 -0
  71. package/templates/playground/src/editor/handbooks/apple-form.ts +76 -0
  72. package/templates/playground/src/editor/handbooks/apple-grid.ts +32 -0
  73. package/templates/playground/src/editor/handbooks/apple-image.ts +122 -0
  74. package/templates/playground/src/editor/handbooks/apple-infinite-scroll.ts +73 -0
  75. package/templates/playground/src/editor/handbooks/apple-link.ts +68 -0
  76. package/templates/playground/src/editor/handbooks/apple-list.ts +67 -0
  77. package/templates/playground/src/editor/handbooks/apple-menu.ts +72 -0
  78. package/templates/playground/src/editor/handbooks/apple-navibar.ts +100 -0
  79. package/templates/playground/src/editor/handbooks/apple-otp-input.ts +108 -0
  80. package/templates/playground/src/editor/handbooks/apple-overlay-host.ts +13 -0
  81. package/templates/playground/src/editor/handbooks/apple-pagination.ts +61 -0
  82. package/templates/playground/src/editor/handbooks/apple-popover.ts +112 -0
  83. package/templates/playground/src/editor/handbooks/apple-progress.ts +56 -0
  84. package/templates/playground/src/editor/handbooks/apple-provider.ts +32 -0
  85. package/templates/playground/src/editor/handbooks/apple-pull-refresh.ts +61 -0
  86. package/templates/playground/src/editor/handbooks/apple-radio-group.ts +91 -0
  87. package/templates/playground/src/editor/handbooks/apple-rate.ts +97 -0
  88. package/templates/playground/src/editor/handbooks/apple-scroll-bar.ts +15 -0
  89. package/templates/playground/src/editor/handbooks/apple-search.ts +49 -0
  90. package/templates/playground/src/editor/handbooks/apple-segmented-control.ts +85 -0
  91. package/templates/playground/src/editor/handbooks/apple-select.ts +91 -0
  92. package/templates/playground/src/editor/handbooks/apple-sheet.ts +151 -0
  93. package/templates/playground/src/editor/handbooks/apple-skeleton.ts +74 -0
  94. package/templates/playground/src/editor/handbooks/apple-slider.ts +103 -0
  95. package/templates/playground/src/editor/handbooks/apple-snackbar.ts +89 -0
  96. package/templates/playground/src/editor/handbooks/apple-speed-dial-item.ts +16 -0
  97. package/templates/playground/src/editor/handbooks/apple-speed-dial.ts +13 -0
  98. package/templates/playground/src/editor/handbooks/apple-spinner.ts +38 -0
  99. package/templates/playground/src/editor/handbooks/apple-stack.ts +44 -0
  100. package/templates/playground/src/editor/handbooks/apple-statistic.ts +69 -0
  101. package/templates/playground/src/editor/handbooks/apple-stepper.ts +91 -0
  102. package/templates/playground/src/editor/handbooks/apple-steps.ts +61 -0
  103. package/templates/playground/src/editor/handbooks/apple-switch.ts +79 -0
  104. package/templates/playground/src/editor/handbooks/apple-tab-bar.ts +66 -0
  105. package/templates/playground/src/editor/handbooks/apple-table.ts +176 -0
  106. package/templates/playground/src/editor/handbooks/apple-tabs.ts +72 -0
  107. package/templates/playground/src/editor/handbooks/apple-tag.ts +56 -0
  108. package/templates/playground/src/editor/handbooks/apple-textarea.ts +109 -0
  109. package/templates/playground/src/editor/handbooks/apple-timeline.ts +44 -0
  110. package/templates/playground/src/editor/handbooks/apple-tooltip.ts +45 -0
  111. package/templates/playground/src/editor/handbooks/apple-transition.ts +44 -0
  112. package/templates/playground/src/editor/handbooks/apple-tree.ts +84 -0
  113. package/templates/playground/src/editor/handbooks/apple-upload.ts +126 -0
  114. package/templates/playground/src/editor/playground-examples.ts +105 -0
  115. package/templates/playground/src/images.ts +3 -0
  116. package/templates/playground/src/router/index.ts +19 -2
  117. package/templates/playground/src/style.css +4 -4
  118. package/templates/playground/src/views/403.vue +65 -0
  119. package/templates/playground/src/views/component-docs/[component].vue +11 -14
  120. package/templates/playground/src/views/components.vue +11 -41
  121. package/templates/playground/src/views/index.vue +3 -1
  122. package/templates/playground/src/views/settings.vue +5 -2
  123. package/tooling/create-playground.mjs +2 -2
  124. package/tooling/prepare-playground.mjs +3 -3
  125. package/tooling/test-create-apptify.mjs +3 -1
  126. package/tooling/test-playground-package.mjs +10 -0
  127. package/dist/components/gallery-scrollbar.d.ts +0 -46
  128. package/dist/core/desktop-image-enter.d.ts +0 -6
  129. package/dist/core/desktop-image-return.d.ts +0 -1
  130. package/templates/playground/src/components/DirectoryDialog.vue +0 -20
@@ -28,6 +28,7 @@
28
28
  </apple-stack>
29
29
  </apple-provider>
30
30
  </apple-stack>
31
+ <apple-button v-else-if="name === 'AppleOverlayHost'" @click="notify('由 Provider 内的 OverlayHost 显示')">显示通知</apple-button>
31
32
  <apple-stack v-else-if="name === 'AppleContainer'">
32
33
  <apple-slider v-model="containerWidth" label="内容宽度" :min="240" :max="640" :step="40" />
33
34
  <apple-container :width="containerWidth">
@@ -47,7 +48,7 @@
47
48
  </apple-grid>
48
49
  </apple-stack>
49
50
  <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
+ <apple-image :gallery="{ src: imageUrl('airpods-max-orange.jpg'), alt: '橙色 AirPods Max 耳机' }" aspect-ratio="1" fit="contain" />
51
52
  <template #actions><apple-button @click="notify('已加入购物袋')">加入购物袋</apple-button><apple-link as="button" :icon="icons.Heart" icon-only label="收藏" @click="notify('已收藏')" /></template>
52
53
  </apple-card>
53
54
  <apple-stack v-else-if="name === 'AppleImage'" class="demo-image">
@@ -61,6 +62,7 @@
61
62
  <apple-stack v-else-if="name === 'AppleBadge'" direction="row" align="center"><apple-badge :value="unread"><apple-link variant="secondary" :icon="icons.Bell" @click="unread++">新消息</apple-link></apple-badge><apple-link as="button" :disabled="!unread" @click="unread = 0">全部已读</apple-link></apple-stack>
62
63
  <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-link as="button" v-else :icon="icons.Plus" @click="tag = true">添加标签</apple-link></apple-stack>
63
64
  <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>
65
+ <div v-else-if="name === 'AppleScrollBar'" class="demo-scroll-bar" tabindex="0" aria-label="滚动示例"><div class="demo-scroll-bar__content"><apple-card v-for="n in 12" :key="n" class="demo-layout-tile"><strong>0{{ n }}</strong></apple-card></div></div>
64
66
  <apple-input v-else-if="name === 'AppleInput'" v-model="text" label="姓名" placeholder="怎么称呼你" clearable />
65
67
  <apple-textarea v-else-if="name === 'AppleTextarea'" v-model="bio" label="个人简介" placeholder="写下你的想法" :maxlength="160" counter />
66
68
  <apple-select v-else-if="name === 'AppleSelect'" v-model="city" label="所在城市" :items="cities" />
@@ -94,7 +96,7 @@
94
96
  <apple-steps v-else-if="name === 'AppleSteps'" v-model="step" :items="steps" clickable />
95
97
  <apple-accordion v-else-if="name === 'AppleAccordion'" :items="questions" />
96
98
  <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" />
99
+ <apple-tree v-else-if="name === 'AppleTree'" v-model="tree" :items="folders" :mobile-directory="false" />
98
100
  <apple-list v-else-if="name === 'AppleList'" v-model="setting" :items="settings" selectable @select="notify('已选择设置项')" />
99
101
  <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-link variant="secondary" @click="updateDelivery">{{ delivered ? '查看配送过程' : '确认签收' }}</apple-link></apple-stack>
100
102
  <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>
@@ -125,6 +127,8 @@
125
127
  <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
128
  <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>
127
129
  <template v-else-if="name === 'AppleFloatingGroup'"><apple-button variant="secondary" @click="notify('快捷操作已就绪')">快捷操作</apple-button><apple-card class="demo-floating-preview"><apple-provider><apple-floating-group :threshold="0" /></apple-provider></apple-card></template>
130
+ <apple-card v-else-if="name === 'AppleSpeedDial'" class="demo-floating-preview"><apple-provider><apple-floating-group :back-top="false" label="全局快捷操作预览"><apple-back-top :threshold="0" :fixed="false" /><apple-button :icon="icons.Plus" icon-only label="新增" @click="notify('已新增')" /><apple-button :icon="icons.Menu" icon-only label="目录" :disabled="!$apple.speedDial.mobile.value" @click="$apple.speedDial.open.value = true" /></apple-floating-group></apple-provider></apple-card>
131
+ <apple-button v-else-if="name === 'AppleSpeedDialItem'" :icon="icons.Plus" icon-only label="新增" @click="notify('已新增')" />
128
132
  <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>
129
133
  <apple-stack v-else-if="name === 'AppleTransition'">
130
134
  <apple-segmented-control v-model="period" :items="periods" label="统计周期" />
@@ -139,33 +143,34 @@
139
143
  </template>
140
144
 
141
145
  <script lang="ts">
146
+ import { imageUrl } from './images'
142
147
  import { defineComponent, h, markRaw } from 'vue'
143
- import { Plus, Heart, Bell, ArrowUpRight, ArrowUp, MoreHorizontal, ZoomIn, Share2, Trash2 } from 'lucide-vue-next'
144
- import { AppleButton } from 'apptify'
148
+ import { Plus, Heart, Bell, ArrowUpRight, ArrowUp, MoreHorizontal, ZoomIn, Share2, Trash2, Menu } from 'lucide-vue-next'
149
+ import { AppleButton, resolveMotion, scrollToWithMotion, type MotionScroll } from 'apptify'
145
150
  export default defineComponent({
146
151
  name: 'ComponentDemo', props: { name: { type: String, required: true } }, emits: ['navigate'],
147
152
  data() { return {
148
- 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,
153
+ icons: markRaw({ Plus, Heart, Bell, ArrowUpRight, ArrowUp, MoreHorizontal, ZoomIn, Menu }), 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,
149
154
  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,
150
155
  virtualTable: false, transitionBackward: false,
151
156
  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'}],
152
157
  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'}],
153
158
  previewThemes: [{label:'浅色',value:'light'},{label:'深色',value:'dark'},{label:'石墨',value:'graphite'},{label:'玫瑰',value:'rose'}], layoutItems: ['概览', '设计', '支持'],
154
159
  galleryImages: [
155
- { src: '/images/lake.jpg', alt: '山间湖泊与小屋' },
156
- { src: '/images/airpods-max-orange.jpg', alt: '橙色 AirPods Max 耳机' },
157
- { src: '/images/city.jpg', alt: '城市天际线' },
158
- { src: '/images/mountains.jpg', alt: '群山与山峰' },
159
- { src: '/images/forest.jpg', alt: '阳光下的森林' },
160
- { src: '/images/architecture.jpg', alt: '建筑细节' },
161
- { src: '/images/coast.jpg', alt: '海岸风景' },
162
- { src: '/images/food.jpg', alt: '餐桌上的美食' },
163
- { src: '/images/desert.jpg', alt: '沙漠沙丘' },
164
- { src: '/images/waterfall.jpg', alt: '山间瀑布' },
165
- { src: '/images/sunset.jpg', alt: '夕阳风景' },
166
- { src: '/images/snow.jpg', alt: '雪地风景' },
160
+ { src: imageUrl('lake.jpg'), alt: '山间湖泊与小屋' },
161
+ { src: imageUrl('airpods-max-orange.jpg'), alt: '橙色 AirPods Max 耳机' },
162
+ { src: imageUrl('city.jpg'), alt: '城市天际线' },
163
+ { src: imageUrl('mountains.jpg'), alt: '群山与山峰' },
164
+ { src: imageUrl('forest.jpg'), alt: '阳光下的森林' },
165
+ { src: imageUrl('architecture.jpg'), alt: '建筑细节' },
166
+ { src: imageUrl('coast.jpg'), alt: '海岸风景' },
167
+ { src: imageUrl('food.jpg'), alt: '餐桌上的美食' },
168
+ { src: imageUrl('desert.jpg'), alt: '沙漠沙丘' },
169
+ { src: imageUrl('waterfall.jpg'), alt: '山间瀑布' },
170
+ { src: imageUrl('sunset.jpg'), alt: '夕阳风景' },
171
+ { src: imageUrl('snow.jpg'), alt: '雪地风景' },
167
172
  ],
168
- images: ['/images/lake.jpg', '/images/airpods-max-orange.jpg'], people: [{name:'林初'},{name:'Alex'},{name:'Taylor'},{name:'Quinn'},{name:'Sam'}],
173
+ images: [imageUrl('lake.jpg'), imageUrl('airpods-max-orange.jpg')], people: [{name:'林初'},{name:'Alex'},{name:'Taylor'},{name:'Quinn'},{name:'Sam'}],
169
174
  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'}],
170
175
  deliveryItems: [{label:'快递送达',value:'express'},{label:'到店取货',value:'pickup'}], periods: [{label:'日',value:'day'},{label:'周',value:'week'},{label:'月',value:'month'}],
171
176
  regions: [{label:'北京市',value:'beijing',children:[{label:'朝阳区',value:'chaoyang'},{label:'海淀区',value:'haidian'}]},{label:'浙江省',value:'zhejiang',children:[{label:'杭州市',value:'hangzhou',children:[{label:'西湖区',value:'xihu'}]}]}],
@@ -175,8 +180,9 @@ export default defineComponent({
175
180
  folders: [{label:'设计资源',value:'design',children:[{label:'组件',value:'components',children:[{label:'按钮',value:'buttons'},{label:'输入框',value:'inputs'}]},{label:'图标',value:'icons'}]},{label:'项目文件',value:'project'}],
176
181
  settings: [{label:'个人资料',value:'profile',description:'姓名、头像和联系方式'},{label:'通知',value:'notifications',description:'推送和邮件偏好'},{label:'隐私与安全',value:'security',description:'管理账户的安全设置'}],
177
182
  events: [{label:'订单已确认',value:'confirmed',description:'我们正在准备你的订单',time:'09:41'},{label:'正在配送',value:'shipping',description:'你的包裹已出发',time:'12:00'},{label:'即将送达',value:'arrival',description:'留意配送通知',time:'预计明天'}],
178
- 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'}],
183
+ actions: [{label:'收藏',value:'favorite'},{label:'分享',value:'share'},{label:'删除',value:'delete',danger:true}], slides: [{label:'山间清晨',value:0,src:imageUrl('lake.jpg')},{label:'一场聆听',value:1,src:imageUrl('airpods-max-orange.jpg')}],
179
184
  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}],
185
+ topScroll: undefined as MotionScroll | undefined,
180
186
  timers: [] as ReturnType<typeof setTimeout>[],
181
187
  } },
182
188
  computed: {
@@ -193,8 +199,9 @@ export default defineComponent({
193
199
  this.transitionBackward = this.periods.findIndex(item => item.value === next) < this.periods.findIndex(item => item.value === previous)
194
200
  },
195
201
  },
196
- beforeUnmount() { this.timers.forEach(clearTimeout) },
202
+ beforeUnmount() { this.topScroll?.cancel(); this.timers.forEach(clearTimeout) },
197
203
  methods: {
204
+ imageUrl,
198
205
  notify(message: string) { this.$apple.notify(message, {tone:'success'}) },
199
206
  validate(data: FormData) { return String(data.get('name') ?? '').trim().length > 0 || '请输入姓名' },
200
207
  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: '预计明天' } },
@@ -206,7 +213,7 @@ export default defineComponent({
206
213
  },
207
214
  refresh(done: () => void) { this.timers.push(setTimeout(() => { this.refreshText = `更新于 ${new Date().toLocaleTimeString('zh-CN')}`; done() }, 700)) },
208
215
  loadMore(done: () => void) { this.timers.push(setTimeout(() => { this.itemsCount = Math.min(12, this.itemsCount + 3); done() }, 500)) },
209
- toTop() { window.scrollTo({top:0,behavior:this.$apple.motion.value.mode === 'none' || this.$apple.motion.value.reduced ? 'instant':'smooth'}) },
216
+ toTop() { this.topScroll = scrollToWithMotion(window, 0, resolveMotion('inherit', this.$apple.motion.value.mode, this.$apple.motion.value.reduced) === 'full') },
210
217
  },
211
218
  })
212
219
  </script>
@@ -220,6 +227,8 @@ export default defineComponent({
220
227
  .component-demo > .demo-tag-row > :deep(.apple-button) { align-self: center; max-width: 100%; }
221
228
 
222
229
  .demo-transition-stage { display: grid; min-width: 0; overflow: hidden; --demo-slide-distance: 24px; }
230
+ .demo-scroll-bar { height: 240px; overflow: auto; border-radius: 12px; }
231
+ .demo-scroll-bar__content { width: 720px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; padding: 16px; }
223
232
  .demo-transition-stage.is-backward { --demo-slide-distance: -24px; }
224
233
  .demo-transition-stage :deep(.apple-statistic) { grid-area: 1 / 1; min-width: 0; }
225
234
  .demo-transition-stage :deep(.apple-slide-x-enter-active),
@@ -1,70 +1,75 @@
1
+ import { componentMetadata } from './editor/component-metadata'
1
2
  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
+ const item = (name: string, api: string, code: string): CatalogItem => ({ name, ...componentMetadata[name], api, code })
3
4
  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">\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('AppleBackTop', '回到顶部', '移动交互', '回到开始,毫不费力。', 'threshold · label', '<apple-back-top :threshold="400" />'),
64
- item('AppleFloatingGroup', '悬浮操作组', '移动交互', '常用操作,触手可及。', 'backTop · threshold · target · label · motion | default', '<apple-floating-group :threshold="300" />'),
65
- item('AppleAutoSize', '自适应尺寸', '基础', '内容变化,布局随行。', 'axis(height|both) · motion | default', '<apple-auto-size><div v-if="expanded">详情内容</div></apple-auto-size>'),
66
- item('AppleTransition', '动效容器', '基础', '为页面保留连续感。', 'name(page|slide-x|slide-y|fade) · mode · appear · motion | default', '<apple-transition name="page"><router-view /></apple-transition>'),
67
- 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>'),
5
+ item('AppleButton', 'variant · size · icon · iconOnly · label · loading · disabled · ripple · motion', '<apple-button variant="primary" :loading="saving" @click="save">继续</apple-button>'),
6
+ item('AppleLink', 'href · external · disabled | slot: default', '<apple-link href="https://www.apple.com.cn/" external>Apple 官网</apple-link>'),
7
+ item('AppleSearch', 'v-model(string) · label · placeholder · disabled | search(query)', '<apple-search v-model="query" label="搜索设置" placeholder="搜索设置" @search="search" />'),
8
+ item('AppleProvider', 'theme · motion(auto|full|reduced|none) | slot: default', '<apple-provider theme="dark" motion="auto">\n <apple-button>继续</apple-button>\n</apple-provider>'),
9
+ item('AppleOverlayHost', '由 AppleProvider 自动包含;无公开 props、事件或插槽', '<apple-overlay-host />'),
10
+ item('AppleContainer', 'width(number|string) | slot: default', '<apple-container :width="960">\n <apple-stack>内容</apple-stack>\n</apple-container>'),
11
+ 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>'),
12
+ 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>'),
13
+ item('AppleCard', 'title · subtitle · text · image · zoom · shadow · motion | slots: media, title, icon, actions', '<apple-card title="AirPods Max" subtitle="声音,出类拔萃。" motion="auto">\n <template #actions><apple-button>进一步了解</apple-button></template>\n</apple-card>'),
14
+ item('AppleImage', 'gallery(object|array) · index · preview · carousel · autoplay · interval(5000ms) · 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" />'),
15
+ item('AppleAvatar', 'src · name · size', '<apple-avatar name="林初" :size="48" />'),
16
+ item('AppleAvatarGroup', 'items[{name,src,value}] · max · size · label | slot: default', '<apple-avatar-group :items="members" :max="4" :size="40" label="项目成员" />'),
17
+ item('AppleBadge', 'value · max · dot · tone', '<apple-badge :value="8"><apple-button>消息</apple-button></apple-badge>'),
18
+ item('AppleTag', 'tone · closable | close', '<apple-tag tone="success" closable @close="remove">已完成</apple-tag>'),
19
+ item('AppleDivider', 'label · vertical', '<apple-divider label="更多选择" />'),
20
+ item('AppleInput', 'v-model · label · placeholder · type · error · hint · clearable · disabled', '<apple-input v-model="name" label="姓名" placeholder="怎么称呼你" clearable />'),
21
+ item('AppleTextarea', 'v-model · label · rows · maxlength · error · hint', '<apple-textarea v-model="bio" label="个人简介" :maxlength="160" />'),
22
+ item('AppleSelect', 'v-model · label · items[{label,value,disabled}] · placeholder · disabled', '<apple-select v-model="city" label="所在城市" :items="cities" />'),
23
+ item('AppleAutocomplete', 'v-model · label · items · placeholder · disabled', '<apple-autocomplete v-model="device" label="搜索设备" :items="devices" />'),
24
+ item('AppleCheckbox', 'v-model · label · disabled · indeterminate', '<apple-checkbox v-model="agreed" label="我已阅读并同意条款" />'),
25
+ item('AppleRadioGroup', 'v-model · label · items · disabled', '<apple-radio-group v-model="delivery" label="配送方式" :items="methods" />'),
26
+ item('AppleSwitch', 'v-model · label · disabled', '<apple-switch v-model="notifications" label="接收通知" />'),
27
+ item('AppleSlider', 'v-model · label · min · max · step · disabled', '<apple-slider v-model="volume" label="音量" :min="0" :max="100" />'),
28
+ item('AppleStepper', 'v-model · label · min · max · step', '<apple-stepper v-model="quantity" label="数量" :min="1" :max="10" />'),
29
+ item('AppleSegmentedControl', 'v-model · items · label · disabled', '<apple-segmented-control v-model="period" :items="periods" label="统计周期" />'),
30
+ 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" />'),
31
+ item('AppleColorPicker', 'v-model(#hex) · label · disabled', '<apple-color-picker v-model="color" label="强调色" />'),
32
+ 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" />'),
33
+ item('AppleOtpInput', 'v-model · length · label | complete', '<apple-otp-input v-model="code" :length="6" label="验证码" />'),
34
+ item('AppleCascader', 'v-model(value[]) · items[{label,value,children}] · label', '<apple-cascader v-model="region" label="所在地区" :items="regions" />'),
35
+ item('AppleRate', 'v-model · max · label · readonly · disabled', '<apple-rate v-model="rating" label="体验评分" />'),
36
+ 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>'),
37
+ 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>'),
38
+ 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" />'),
39
+ item('AppleAside', 'hideOnPreview=true · motion | slot: default | 布局由调用方提供', '<apple-aside :hide-on-preview="false">\n <apple-list :items="items" />\n</apple-aside>'),
40
+ 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>'),
41
+ 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>'),
42
+ item('AppleBreadcrumbs', 'items[{label,value,href,disabled}] · label', '<apple-breadcrumbs :items="[{label:\'首页\',value:\'home\',href:\'/\'},{label:\'设置\',value:\'settings\'}]" />'),
43
+ item('ApplePagination', 'v-model(page) · total · pageSize', '<apple-pagination v-model="page" :total="120" :page-size="10" />'),
44
+ item('AppleSteps', 'v-model(index) · items[{label,value,description,disabled}] · clickable · disabled', '<apple-steps v-model="step" :items="steps" clickable />'),
45
+ item('AppleAccordion', 'v-model · items[{label,value,content}] · multiple', '<apple-accordion :items="questions" />'),
46
+ 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" />'),
47
+ item('AppleTree', 'v-model · items[{label,value,children}] | select', '<apple-tree v-model="selected" :items="folders" />'),
48
+ item('AppleList', 'v-model · items[{label,value,description,href,disabled}] · selectable | select(item)', '<apple-list v-model="setting" :items="settings" selectable @select="openSetting" />'),
49
+ item('AppleTimeline', 'items[{label,value,description,time,tone}] · orientation(horizontal|vertical) · label', '<apple-timeline :items="events" orientation="horizontal" />'),
50
+ item('AppleStatistic', 'label · value · prefix · suffix · precision', '<apple-statistic label="本月访问" :value="12840" suffix="次" />'),
51
+ item('AppleDialog', 'v-model · title · persistent · confirmText · cancelText | confirm, cancel', '<apple-dialog v-model="open" title="保存更改?" @confirm="save">\n 你的更改会同步到所有设备。\n</apple-dialog>'),
52
+ item('AppleDrawer', 'v-model · title · placement(left|right|bottom) · width · persistent · loading', '<apple-drawer v-model="open" title="偏好设置" placement="right">内容</apple-drawer>'),
53
+ item('AppleSheet', 'v-model · title · persistent', '<apple-sheet v-model="open" title="分享">内容</apple-sheet>'),
54
+ item('AppleSnackbar', 'v-model · message · title · tone · duration(ms) · action · closable | close, action', "const notice = apple.notify('已保存', { tone: 'success', duration: 4000 })"),
55
+ item('AppleAlert', 'v-model · title · message · tone · closable | close', '<apple-alert v-model="visible" title="一切已就绪" tone="success" closable>内容已同步。</apple-alert>'),
56
+ item('AppleProgress', 'modelValue · max · label · indeterminate · showValue · tone', '<apple-progress :model-value="68" label="正在上传" show-value />'),
57
+ item('AppleSpinner', 'size · label', '<apple-spinner label="正在加载" />'),
58
+ 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 />'),
59
+ item('AppleEmpty', 'title · description | slot: default', '<apple-empty title="还没有收藏" description="喜欢的内容,会出现在这里。" />'),
60
+ 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>'),
61
+ item('AppleTooltip', 'text | slot: default', '<apple-tooltip text="添加到收藏"><apple-button>收藏</apple-button></apple-tooltip>'),
62
+ item('AppleMenu', 'v-model · items[{label,value,disabled,danger,icon,description}] · label · selected | select(value,item)', '<apple-menu :items="actions" label="更多操作" @select="onAction" />'),
63
+ 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>'),
64
+ item('ApplePullRefresh', 'v-model(refreshing) · threshold · label · disabled | refresh(done)', '<apple-pull-refresh @refresh="async done => { await reload(); done() }">内容</apple-pull-refresh>'),
65
+ item('AppleBackTop', 'threshold · label', '<apple-back-top :threshold="400" />'),
66
+ item('AppleFloatingGroup', 'backTop · threshold · target · label · motion | default', '<apple-floating-group :threshold="300" />'),
67
+ item('AppleSpeedDial', 'backTop · threshold · target · label', '<!-- App.vue -->\n<apple-speed-dial />'),
68
+ item('AppleSpeedDialItem', 'label · icon · visible · disabled · variant | click', '<apple-speed-dial-item label="新增" :icon="Plus" @click="create" />'),
69
+ item('AppleAutoSize', 'axis(height|both) · motion | default', '<apple-auto-size><div v-if="expanded">详情内容</div></apple-auto-size>'),
70
+ item('AppleTransition', 'name(page|slide-x|slide-y|fade) · mode · appear · motion | default', '<apple-transition name="page"><router-view /></apple-transition>'),
71
+ 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>'),
72
+ item('AppleScrollBar', 'target · axis(both|horizontal|vertical) · label | interaction · refresh()', '<apple-scroll-bar target="#scroll-area" />'),
68
73
  ]
69
74
  export const groups = ['全部组件', '基础', '表单', '导航', '数据展示', '反馈', '移动交互']
70
75
 
@@ -1,43 +1,39 @@
1
1
  <template>
2
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
- <p v-if="!treeItems.length" class="index-empty" role="status">未找到组件,请尝试其他关键词。</p>
3
+ <AppleTree :items="treeItems" :model-value="activeId || activeGroup" :expanded="expanded" label="组件目录树" @update:expanded="expanded = $event" @select="navigate" />
5
4
  </nav>
6
5
  </template>
7
6
 
8
7
  <script setup lang="ts">
9
- import { computed } from 'vue'
10
8
  import { useRoute, useRouter } from 'vue-router'
9
+ import { scrollToWithMotion } from 'apptify'
11
10
  import { AppleTree } from 'apptify'
12
11
  import type { AppleTreeItem, AppleValue } from 'apptify'
13
12
  import { sections, componentId } from '../catalog'
14
13
 
15
- const props = defineProps<{ activeGroup: string; activeId: string; search?: string }>()
14
+ defineProps<{ activeGroup: string; activeId: string }>()
16
15
  const emit = defineEmits<{ navigate: [] }>()
17
16
  const router = useRouter()
18
17
  const route = useRoute()
19
18
  const expanded = defineModel<AppleValue[]>('expanded', { default: () => sections.map(section => section.id) })
20
- const query = computed(() => props.search?.trim().toLocaleLowerCase() ?? '')
21
- const treeItems = computed<AppleTreeItem[]>(() => sections.flatMap(section => {
22
- const groupMatches = section.label.toLocaleLowerCase().includes(query.value)
23
- const items = section.items.filter(item => groupMatches || `${item.label} ${item.name} ${componentId(item.name)} ${item.description}`.toLocaleLowerCase().includes(query.value))
24
- return items.length ? [{ label: section.label, value: section.id, children: items.map(item => ({ label: item.label, value: componentId(item.name) })) }] : []
25
- }))
26
- const expandedGroups = computed(() => query.value ? treeItems.value.map(item => item.value) : expanded.value)
19
+ const treeItems: AppleTreeItem[] = sections.map(section => ({ label: section.label, value: section.id, children: section.items.map(item => ({ label: item.label, value: componentId(item.name), description: `${item.name} ${item.description}` })) }))
27
20
  function navigate(item: AppleTreeItem) {
28
21
  if (item.children?.length) return
29
22
  const hash = `#${item.value}`
30
23
  if (route.hash === hash) {
31
24
  const target = document.getElementById(String(item.value))
32
- if (target) window.scrollTo({ top: target.getBoundingClientRect().top + window.scrollY - 96, behavior: 'instant' })
25
+ if (target) {
26
+ const motion = document.querySelector('#app > .apple-provider')?.getAttribute('data-apple-motion')
27
+ const top = () => Math.min(target.getBoundingClientRect().top + window.scrollY - 96, Math.max(0, document.documentElement.scrollHeight - window.innerHeight))
28
+ scrollToWithMotion(window, top, motion === 'full')
29
+ }
33
30
  } else void router.push(`/components${hash}`)
34
31
  emit('navigate')
35
32
  }
36
33
  </script>
37
34
 
38
35
  <style scoped>
39
- .component-index { min-width: 0; }
36
+ .component-index { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
40
37
  .component-index :deep(.apple-tree__row) { min-height: 34px; }
41
38
  .component-index :deep(.apple-tree__row > span:last-child) { min-width: 0; overflow-wrap: anywhere; }
42
- .index-empty { margin: 12px 8px; font-size: 12px; line-height: 1.7; color: var(--apple-secondary); }
43
39
  </style>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <div ref="root" class="masonry-feed"><slot /></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { onBeforeUnmount, onMounted, ref } from 'vue'
7
+
8
+ const emit = defineEmits<{ layout: [] }>()
9
+ const root = ref<HTMLElement>()
10
+ let observer: ResizeObserver | undefined
11
+ let frame = 0
12
+ const gap = 20
13
+
14
+ function layout() {
15
+ frame = 0
16
+ const container = root.value
17
+ if (!container || !container.clientWidth) return
18
+ const cards = Array.from(container.children) as HTMLElement[]
19
+ const columns = Math.max(1, Math.floor((container.clientWidth + gap) / (400 + gap)))
20
+ const width = (container.clientWidth - gap * (columns - 1)) / columns
21
+ const heights = Array<number>(columns).fill(0)
22
+ for (const card of cards) card.style.width = `${width}px`
23
+ for (const card of cards) {
24
+ const column = heights.indexOf(Math.min(...heights))
25
+ card.style.position = 'absolute'
26
+ card.style.left = `${column * (width + gap)}px`
27
+ card.style.top = `${heights[column]}px`
28
+ heights[column] += card.offsetHeight + gap
29
+ }
30
+ container.style.height = `${Math.max(...heights)}px`
31
+ emit('layout')
32
+ }
33
+
34
+ function scheduleLayout() {
35
+ if (!frame) frame = requestAnimationFrame(layout)
36
+ }
37
+
38
+ onMounted(() => {
39
+ observer = new ResizeObserver(scheduleLayout)
40
+ observer.observe(root.value!)
41
+ for (const card of root.value!.children) observer.observe(card)
42
+ layout()
43
+ })
44
+ onBeforeUnmount(() => {
45
+ observer?.disconnect()
46
+ cancelAnimationFrame(frame)
47
+ })
48
+ </script>
49
+
50
+ <style scoped>
51
+ .masonry-feed { position: relative; }
52
+ </style>
@@ -101,7 +101,7 @@ function showProfile() {
101
101
  .user-popup.apple-popover { border-radius: 24px; background: var(--apple-surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
102
102
  .user-popup .user-menu-list > .apple-link > .apple-link__content { width: 100%; justify-content: flex-start; gap: 11px; }
103
103
  .user-popup .user-menu-list > .apple-link { min-height: 44px; font-weight: 400; line-height: inherit; }
104
- .user-popup .user-menu-list > .apple-link:hover:where(:not([data-apple-touch] *)):not(:disabled) { background: var(--apple-hover); }
104
+ .user-popup .user-menu-list > .apple-link:hover:not(:disabled) { background: var(--apple-hover); }
105
105
  .user-popup .user-menu-list > .apple-link:active:not(:disabled) { background: var(--apple-pressed); }
106
106
  .user-trigger.user-account-trigger { width: 44px; height: 44px; border: 0; background: transparent; }
107
107
  .user-trigger.user-account-trigger:focus-visible { outline: 3px solid var(--apple-accent); outline-offset: 2px; }