apptify 0.8.0 → 0.8.5

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 (41) hide show
  1. package/dist/apptify.css +1 -1
  2. package/dist/apptify.js +1503 -1721
  3. package/dist/components/button.d.ts +1 -1
  4. package/dist/components/foundation.d.ts +2 -4
  5. package/dist/components/link.d.ts +1 -1
  6. package/dist/core/image-gallery-motion.d.ts +1 -1
  7. package/dist/core/layers.d.ts +0 -1
  8. package/dist/index.d.ts +2 -46
  9. package/docs/COMPONENTS.md +7 -4
  10. package/package.json +1 -1
  11. package/templates/playground/README.md +1 -1
  12. package/templates/playground/package.json +1 -1
  13. package/templates/playground/source-snapshot.json +80 -40
  14. package/templates/playground/src/ComponentDemo.vue +2 -5
  15. package/templates/playground/src/catalog.ts +0 -1
  16. package/templates/playground/src/components/ErrorPagePreviews.vue +86 -0
  17. package/templates/playground/src/components/bad-request-paper-scene.vue +280 -0
  18. package/templates/playground/src/components/bad-request-paper-story.ts +169 -0
  19. package/templates/playground/src/components/gateway-paper-scene.vue +336 -0
  20. package/templates/playground/src/components/gateway-paper-story.ts +197 -0
  21. package/templates/playground/src/components/hamster-429-scene.vue +1045 -0
  22. package/templates/playground/src/components/server-fire-paper-scene.vue +249 -0
  23. package/templates/playground/src/components/server-fire-paper-story.ts +272 -0
  24. package/templates/playground/src/components/unavailable-paper-scene.vue +488 -0
  25. package/templates/playground/src/editor/component-metadata.ts +0 -1
  26. package/templates/playground/src/editor/handbooks/apple-dialog.ts +1 -1
  27. package/templates/playground/src/editor/playground-examples.ts +0 -1
  28. package/templates/playground/src/error-pages.ts +9 -0
  29. package/templates/playground/src/style.css +0 -2
  30. package/templates/playground/src/views/400.vue +74 -0
  31. package/templates/playground/src/views/403.vue +5 -2
  32. package/templates/playground/src/views/429.vue +74 -0
  33. package/templates/playground/src/views/500.vue +67 -0
  34. package/templates/playground/src/views/502.vue +68 -0
  35. package/templates/playground/src/views/503.vue +79 -0
  36. package/templates/playground/src/views/[...all].vue +5 -2
  37. package/templates/playground/src/views/components.vue +8 -8
  38. package/tooling/prepare-playground.mjs +2 -2
  39. package/dist/components/scrollbar.d.ts +0 -88
  40. package/dist/core/scrollbar.d.ts +0 -14
  41. package/templates/playground/src/editor/handbooks/apple-scroll-bar.ts +0 -15
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <main class="main-content paper-400-page">
3
+ <apple-card class="paper-400-card" shadow="none" zoom="none">
4
+ <template #title>
5
+ <div class="paper-400-heading">
6
+ <p class="paper-400-number" aria-hidden="true">400</p>
7
+ <h1><span class="paper-400-sr-only">400 · </span>{{ page.title }}</h1>
8
+ </div>
9
+ </template>
10
+ <p class="paper-400-copy">{{ page.copy[0] }}<br />{{ page.copy[1] }}</p>
11
+ <div class="paper-400-actions">
12
+ <router-link v-slot="{ href, navigate }" to="/" custom>
13
+ <apple-button :href="href" @click="navigate">返回首页</apple-button>
14
+ </router-link>
15
+ <apple-link as="button" @click="goBack"><ArrowLeft :size="16" aria-hidden="true" /> 返回上一页</apple-link>
16
+ </div>
17
+ </apple-card>
18
+ <BadRequestPaperScene class="paper-400-illustration" :theme="scheme" :reduced-motion="reducedMotion" />
19
+ </main>
20
+ </template>
21
+
22
+ <route lang="json">
23
+ { "alias": "/error/400", "meta": { "title": "请求有误" } }
24
+ </route>
25
+
26
+ <script setup lang="ts">
27
+ import { computed } from 'vue'
28
+ import { ArrowLeft } from 'lucide-vue-next'
29
+ import { useRouter } from 'vue-router'
30
+ import { useApple } from 'apptify'
31
+ import { resolveMotion } from 'apptify'
32
+ import BadRequestPaperScene from '../components/bad-request-paper-scene.vue'
33
+
34
+ import { errorPages } from '../error-pages'
35
+
36
+ const page = errorPages[400]
37
+ const router = useRouter()
38
+ const apple = useApple()
39
+ const scheme = computed(() => apple.theme.value.current.scheme)
40
+ const reducedMotion = computed(() => resolveMotion('inherit', apple.motion.value.mode, apple.motion.value.reduced) !== 'full')
41
+ function goBack() {
42
+ if (typeof router.options.history.state.back === 'string') router.back()
43
+ else void router.push('/')
44
+ }
45
+ </script>
46
+
47
+ <style scoped>
48
+ .paper-400-page { max-width:1360px; display:grid; grid-template-columns:minmax(260px,.78fr) minmax(0,1.65fr); align-content:center; align-items:center; column-gap:16px; padding-top:38px; padding-bottom:calc(38px + 64px); }
49
+ .paper-400-card { background:transparent; border:0; overflow:visible; }
50
+ .paper-400-card :deep(.apple-card__body) { padding:0 0 0 8px; }
51
+ .paper-400-card :deep(.apple-card__heading) { margin:0; }
52
+ .paper-400-heading { width:100%; }
53
+ .paper-400-number { color:var(--apple-text); font-family:Georgia,'Times New Roman',serif; font-size:clamp(78px,8.2vw,118px); line-height:.9; letter-spacing:-.075em; margin:0 0 28px; font-weight:400; }
54
+ .paper-400-heading h1 { font-size:clamp(26px,2.7vw,35px); line-height:1.35; letter-spacing:-.035em; font-weight:600; margin:0 0 17px; }
55
+ .paper-400-copy { font-size:13px; line-height:1.95; color:var(--apple-secondary); margin:0; text-wrap:pretty; }
56
+ .paper-400-actions { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-top:27px; }
57
+ .paper-400-actions .apple-link { font-size:12px; padding-inline:8px; color:var(--apple-secondary); }
58
+ .paper-400-illustration { min-width:0; }
59
+ .paper-400-sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
60
+ @media(max-width:880px) {
61
+ .paper-400-page { grid-template-columns:minmax(0,1fr); max-width:680px; padding-top:18px; }
62
+ .paper-400-illustration { grid-row:1; }
63
+ .paper-400-card { margin-top:36px; }
64
+ .paper-400-card :deep(.apple-card__body) { padding:0; text-align:center; }
65
+ .paper-400-number { font-size:70px; margin-bottom:18px; }
66
+ .paper-400-heading h1 { font-size:26px; margin-bottom:12px; }
67
+ .paper-400-actions { justify-content:center; margin-top:18px; }
68
+ }
69
+ @media(max-width:420px) {
70
+ .paper-400-page { padding-inline:18px; padding-top:12px; }
71
+ .paper-400-copy { font-size:12px; }
72
+ .paper-400-card { margin-top:30px; }
73
+ }
74
+ </style>
@@ -4,10 +4,10 @@
4
4
  <template #title>
5
5
  <div class="paper-403-heading">
6
6
  <p class="paper-403-number" aria-hidden="true">403</p>
7
- <h1>无权访问</h1>
7
+ <h1>{{ page.title }}</h1>
8
8
  </div>
9
9
  </template>
10
- <p class="paper-403-copy">这只鸡腿,可不能随便拿。<br />你暂时没有访问此页面的权限。</p>
10
+ <p class="paper-403-copy">{{ page.copy[0] }}<br />{{ page.copy[1] }}</p>
11
11
  <div class="paper-403-actions">
12
12
  <router-link v-slot="{ href, navigate }" :to="homePath" custom>
13
13
  <apple-button :href="href" @click="navigate">返回首页</apple-button>
@@ -28,6 +28,9 @@ import { ArrowLeft } from 'lucide-vue-next'
28
28
  import { useRouter } from 'vue-router'
29
29
  import ForbiddenPaperScene from '../components/forbidden-paper-scene.vue'
30
30
 
31
+ import { errorPages } from '../error-pages'
32
+
33
+ const page = errorPages[403]
31
34
  const router = useRouter()
32
35
  const homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'
33
36
  function goBack() {
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <main class="main-content paper-429-page">
3
+ <apple-card class="paper-429-card" shadow="none" zoom="none">
4
+ <template #title>
5
+ <div class="paper-429-heading">
6
+ <p class="paper-429-number" aria-hidden="true">429</p>
7
+ <h1><span class="paper-429-sr-only">429 · </span>{{ page.title }}</h1>
8
+ </div>
9
+ </template>
10
+ <p class="paper-429-copy">{{ page.copy[0] }}<br />{{ page.copy[1] }}</p>
11
+ <div class="paper-429-actions">
12
+ <router-link v-slot="{ href, navigate }" to="/" custom>
13
+ <apple-button :href="href" @click="navigate">返回首页</apple-button>
14
+ </router-link>
15
+ <apple-link as="button" @click="goBack"><ArrowLeft :size="16" aria-hidden="true" /> 返回上一页</apple-link>
16
+ </div>
17
+ </apple-card>
18
+ <Hamster429Scene class="paper-429-illustration" :theme="scheme" :reduced-motion="reducedMotion" />
19
+ </main>
20
+ </template>
21
+
22
+ <route lang="json">
23
+ { "alias": "/error/429", "meta": { "title": "请求过于频繁" } }
24
+ </route>
25
+
26
+ <script setup lang="ts">
27
+ import { computed } from 'vue'
28
+ import { ArrowLeft } from 'lucide-vue-next'
29
+ import { useRouter } from 'vue-router'
30
+ import { useApple } from 'apptify'
31
+ import { resolveMotion } from 'apptify'
32
+ import Hamster429Scene from '../components/hamster-429-scene.vue'
33
+
34
+ import { errorPages } from '../error-pages'
35
+
36
+ const page = errorPages[429]
37
+ const router = useRouter()
38
+ const apple = useApple()
39
+ const scheme = computed(() => apple.theme.value.current.scheme)
40
+ const reducedMotion = computed(() => resolveMotion('inherit', apple.motion.value.mode, apple.motion.value.reduced) !== 'full')
41
+ function goBack() {
42
+ if (typeof router.options.history.state.back === 'string') router.back()
43
+ else void router.push('/')
44
+ }
45
+ </script>
46
+
47
+ <style scoped>
48
+ .paper-429-page { max-width:1360px; display:grid; grid-template-columns:minmax(260px,.78fr) minmax(0,1.65fr); align-content:center; align-items:center; column-gap:16px; padding-top:38px; padding-bottom:calc(38px + 64px); }
49
+ .paper-429-card { background:transparent; border:0; overflow:visible; }
50
+ .paper-429-card :deep(.apple-card__body) { padding:0 0 0 8px; }
51
+ .paper-429-card :deep(.apple-card__heading) { margin:0; }
52
+ .paper-429-heading { width:100%; }
53
+ .paper-429-number { color:var(--apple-text); font-family:Georgia,'Times New Roman',serif; font-size:clamp(78px,8.2vw,118px); line-height:.9; letter-spacing:-.075em; margin:0 0 28px; font-weight:429; }
54
+ .paper-429-heading h1 { font-size:clamp(26px,2.7vw,35px); line-height:1.35; letter-spacing:-.035em; font-weight:600; margin:0 0 17px; }
55
+ .paper-429-copy { font-size:13px; line-height:1.95; color:var(--apple-secondary); margin:0; text-wrap:pretty; }
56
+ .paper-429-actions { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-top:27px; }
57
+ .paper-429-actions .apple-link { font-size:12px; padding-inline:8px; color:var(--apple-secondary); }
58
+ .paper-429-illustration { min-width:0; }
59
+ .paper-429-sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
60
+ @media(max-width:880px) {
61
+ .paper-429-page { grid-template-columns:minmax(0,1fr); max-width:680px; padding-top:18px; }
62
+ .paper-429-illustration { grid-row:1; }
63
+ .paper-429-card { margin-top:36px; }
64
+ .paper-429-card :deep(.apple-card__body) { padding:0; text-align:center; }
65
+ .paper-429-number { font-size:70px; margin-bottom:18px; }
66
+ .paper-429-heading h1 { font-size:26px; margin-bottom:12px; }
67
+ .paper-429-actions { justify-content:center; margin-top:18px; }
68
+ }
69
+ @media(max-width:420px) {
70
+ .paper-429-page { padding-inline:18px; padding-top:12px; }
71
+ .paper-429-copy { font-size:12px; }
72
+ .paper-429-card { margin-top:30px; }
73
+ }
74
+ </style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <main class="main-content paper-500-page">
3
+ <apple-card class="paper-500-card" shadow="none" zoom="none">
4
+ <template #title>
5
+ <div class="paper-500-heading">
6
+ <p class="paper-500-number" aria-hidden="true">500</p>
7
+ <h1>{{ page.title }}</h1>
8
+ </div>
9
+ </template>
10
+ <p class="paper-500-copy">{{ page.copy[0] }}<br />{{ page.copy[1] }}</p>
11
+ <div class="paper-500-actions">
12
+ <router-link v-slot="{ href, navigate }" :to="homePath" custom>
13
+ <apple-button :href="href" @click="navigate">返回首页</apple-button>
14
+ </router-link>
15
+ <apple-link as="button" @click="goBack"><ArrowLeft :size="16" aria-hidden="true" /> 返回上一页</apple-link>
16
+ </div>
17
+ </apple-card>
18
+ <ServerFirePaperScene class="paper-500-illustration" />
19
+ </main>
20
+ </template>
21
+
22
+ <route lang="json">
23
+ { "alias": "/error/500", "meta": { "title": "服务器内部错误" } }
24
+ </route>
25
+
26
+ <script setup lang="ts">
27
+ import { ArrowLeft } from 'lucide-vue-next'
28
+ import { useRouter } from 'vue-router'
29
+ import { errorPages } from '../error-pages'
30
+ import ServerFirePaperScene from '../components/server-fire-paper-scene.vue'
31
+
32
+ const page = errorPages[500]
33
+ const router = useRouter()
34
+ const homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'
35
+ function goBack() {
36
+ if (typeof router.options.history.state.back === 'string') router.back()
37
+ else void router.push(homePath)
38
+ }
39
+ </script>
40
+
41
+ <style scoped>
42
+ .paper-500-page { max-width: 1360px; display: grid; grid-template-columns: minmax(260px, .78fr) minmax(0, 1.65fr); align-content: center; align-items: center; column-gap: 16px; padding-top: 38px; padding-bottom: calc(38px + 64px); }
43
+ .paper-500-card { background: transparent; border: 0; overflow: visible; }
44
+ .paper-500-card :deep(.apple-card__body) { padding: 0 0 0 8px; }
45
+ .paper-500-card :deep(.apple-card__heading) { margin: 0; }
46
+ .paper-500-heading { width: 100%; }
47
+ .paper-500-number { color: var(--apple-text); font-family: Georgia, 'Times New Roman', serif; font-size: clamp(78px, 8.2vw, 118px); line-height: .9; letter-spacing: -.075em; margin: 0 0 28px; font-weight: 400; }
48
+ .paper-500-heading h1 { font-size: clamp(26px, 2.7vw, 35px); line-height: 1.35; letter-spacing: -.035em; font-weight: 600; margin: 0 0 17px; }
49
+ .paper-500-copy { font-size: 13px; line-height: 1.95; color: var(--apple-secondary); margin: 0; text-wrap: pretty; }
50
+ .paper-500-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 27px; }
51
+ .paper-500-actions .apple-link { font-size: 12px; padding-inline: 8px; color: var(--apple-secondary); }
52
+ .paper-500-illustration { min-width: 0; }
53
+ @media (max-width: 880px) {
54
+ .paper-500-page { grid-template-columns: minmax(0, 1fr); max-width: 680px; padding-top: 18px; }
55
+ .paper-500-illustration { grid-row: 1; }
56
+ .paper-500-card { margin-top: 26px; }
57
+ .paper-500-card :deep(.apple-card__body) { padding: 0; text-align: center; }
58
+ .paper-500-number { font-size: 70px; margin-bottom: 18px; }
59
+ .paper-500-heading h1 { font-size: 26px; margin-bottom: 12px; }
60
+ .paper-500-actions { justify-content: center; margin-top: 18px; }
61
+ }
62
+ @media (max-width: 420px) {
63
+ .paper-500-page { padding-inline: 18px; padding-top: 12px; }
64
+ .paper-500-copy { font-size: 12px; }
65
+ .paper-500-card { margin-top: 22px; }
66
+ }
67
+ </style>
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <main class="main-content paper-502-page">
3
+ <apple-card class="paper-502-card" shadow="none" zoom="none">
4
+ <template #title>
5
+ <div class="paper-502-heading">
6
+ <p class="paper-502-number" aria-hidden="true">502</p>
7
+ <h1>{{ page.title }}</h1>
8
+ </div>
9
+ </template>
10
+ <p class="paper-502-copy">{{ page.copy[0] }}<br />{{ page.copy[1] }}</p>
11
+ <div class="paper-502-actions">
12
+ <router-link v-slot="{ href, navigate }" :to="homePath" custom>
13
+ <apple-button :href="href" @click="navigate">返回首页</apple-button>
14
+ </router-link>
15
+ <apple-link as="button" @click="goBack"><ArrowLeft :size="16" aria-hidden="true" /> 返回上一页</apple-link>
16
+ </div>
17
+ </apple-card>
18
+ <GatewayPaperScene class="paper-502-illustration" />
19
+ </main>
20
+ </template>
21
+
22
+ <route lang="json">
23
+ { "alias": "/error/502", "meta": { "title": "网关错误" } }
24
+ </route>
25
+
26
+ <script setup lang="ts">
27
+ import { ArrowLeft } from 'lucide-vue-next'
28
+ import { useRouter } from 'vue-router'
29
+ import GatewayPaperScene from '../components/gateway-paper-scene.vue'
30
+
31
+ import { errorPages } from '../error-pages'
32
+
33
+ const page = errorPages[502]
34
+ const router = useRouter()
35
+ const homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'
36
+ function goBack() {
37
+ if (typeof router.options.history.state.back === 'string') router.back()
38
+ else void router.push(homePath)
39
+ }
40
+ </script>
41
+
42
+ <style scoped>
43
+ .paper-502-page { max-width: 1360px; display: grid; grid-template-columns: minmax(260px, .78fr) minmax(0, 1.65fr); align-content: center; align-items: center; column-gap: 16px; padding-top: 38px; padding-bottom: calc(38px + 64px); }
44
+ .paper-502-card { background: transparent; border: 0; overflow: visible; }
45
+ .paper-502-card :deep(.apple-card__body) { padding: 0 0 0 8px; }
46
+ .paper-502-card :deep(.apple-card__heading) { margin: 0; }
47
+ .paper-502-heading { width: 100%; }
48
+ .paper-502-number { color: var(--apple-text); font-family: Georgia, 'Times New Roman', serif; font-size: clamp(78px, 8.2vw, 118px); line-height: .9; letter-spacing: -.075em; margin: 0 0 28px; font-weight: 400; }
49
+ .paper-502-heading h1 { font-size: clamp(26px, 2.7vw, 35px); line-height: 1.35; letter-spacing: -.035em; font-weight: 600; margin: 0 0 17px; }
50
+ .paper-502-copy { font-size: 13px; line-height: 1.95; color: var(--apple-secondary); margin: 0; text-wrap: pretty; }
51
+ .paper-502-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 27px; }
52
+ .paper-502-actions .apple-link { font-size: 12px; padding-inline: 8px; color: var(--apple-secondary); }
53
+ .paper-502-illustration { min-width: 0; }
54
+ @media (max-width: 880px) {
55
+ .paper-502-page { grid-template-columns: minmax(0, 1fr); max-width: 680px; padding-top: 18px; }
56
+ .paper-502-illustration { grid-row: 1; }
57
+ .paper-502-card { margin-top: 26px; }
58
+ .paper-502-card :deep(.apple-card__body) { padding: 0; text-align: center; }
59
+ .paper-502-number { font-size: 70px; margin-bottom: 18px; }
60
+ .paper-502-heading h1 { font-size: 26px; margin-bottom: 12px; }
61
+ .paper-502-actions { justify-content: center; margin-top: 18px; }
62
+ }
63
+ @media (max-width: 420px) {
64
+ .paper-502-page { padding-inline: 18px; padding-top: 12px; }
65
+ .paper-502-copy { font-size: 12px; }
66
+ .paper-502-card { margin-top: 22px; }
67
+ }
68
+ </style>
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <main class="main-content paper-503-page">
3
+ <apple-card class="paper-503-card" shadow="none" zoom="none">
4
+ <template #title>
5
+ <div class="paper-503-heading">
6
+ <p class="paper-503-number" aria-hidden="true">503</p>
7
+ <h1><span class="paper-503-sr-only">503 · </span>{{ page.title }}</h1>
8
+ <p class="paper-503-english" lang="en">Service temporarily unavailable</p>
9
+ </div>
10
+ </template>
11
+ <p class="paper-503-copy">{{ page.copy[0] }}<br />{{ page.copy[1] }}</p>
12
+ <div class="paper-503-actions">
13
+ <router-link v-slot="{ href, navigate }" :to="homePath" custom>
14
+ <apple-button :href="href" @click="navigate">返回首页</apple-button>
15
+ </router-link>
16
+ <apple-link as="button" @click="goBack"><ArrowLeft :size="16" aria-hidden="true" /> 返回上一页</apple-link>
17
+ </div>
18
+ </apple-card>
19
+ <UnavailablePaperScene class="paper-503-illustration" :theme="scheme" :reduced-motion="reducedMotion" />
20
+ </main>
21
+ </template>
22
+
23
+ <route lang="json">
24
+ { "alias": "/error/503", "meta": { "title": "服务暂时不可用" } }
25
+ </route>
26
+
27
+ <script setup lang="ts">
28
+ import { computed } from 'vue'
29
+ import { ArrowLeft } from 'lucide-vue-next'
30
+ import { useRouter } from 'vue-router'
31
+ import { useApple } from 'apptify'
32
+ import { resolveMotion } from 'apptify'
33
+ import UnavailablePaperScene from '../components/unavailable-paper-scene.vue'
34
+
35
+ import { errorPages } from '../error-pages'
36
+
37
+ const page = errorPages[503]
38
+ const router = useRouter()
39
+ const homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'
40
+ const apple = useApple()
41
+ const scheme = computed(() => apple.theme.value.current.scheme)
42
+ const reducedMotion = computed(() => resolveMotion('inherit', apple.motion.value.mode, apple.motion.value.reduced) !== 'full')
43
+
44
+ function goBack() {
45
+ if (typeof router.options.history.state.back === 'string') router.back()
46
+ else void router.push(homePath)
47
+ }
48
+ </script>
49
+
50
+ <style scoped>
51
+ .paper-503-page { max-width: 1360px; display: grid; grid-template-columns: minmax(260px, .78fr) minmax(0, 1.65fr); align-content: center; align-items: center; column-gap: 16px; padding-top: 38px; padding-bottom: calc(38px + 64px); }
52
+ .paper-503-card { background: transparent; border: 0; overflow: visible; }
53
+ .paper-503-card :deep(.apple-card__body) { padding: 0 0 0 8px; }
54
+ .paper-503-card :deep(.apple-card__heading) { margin: 0; }
55
+ .paper-503-heading { width: 100%; }
56
+ .paper-503-number { color: var(--apple-text); font-family: Georgia, 'Times New Roman', serif; font-size: clamp(78px, 8.2vw, 118px); line-height: .9; letter-spacing: -.075em; margin: 0 0 28px; font-weight: 400; }
57
+ .paper-503-heading h1 { font-size: clamp(26px, 2.7vw, 35px); line-height: 1.35; letter-spacing: -.035em; font-weight: 600; margin: 0 0 9px; }
58
+ .paper-503-english { font-size: 11px; line-height: 1.6; letter-spacing: .015em; color: var(--apple-secondary); margin: 0 0 20px; font-weight: 400; }
59
+ .paper-503-copy { font-size: 13px; line-height: 1.95; color: var(--apple-secondary); margin: 0; text-wrap: pretty; }
60
+ .paper-503-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 27px; }
61
+ .paper-503-actions .apple-link { font-size: 12px; padding-inline: 8px; color: var(--apple-secondary); }
62
+ .paper-503-illustration { min-width: 0; }
63
+ .paper-503-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
64
+ @media (max-width: 880px) {
65
+ .paper-503-page { grid-template-columns: minmax(0, 1fr); max-width: 680px; padding-top: 18px; }
66
+ .paper-503-illustration { grid-row: 1; }
67
+ .paper-503-card { margin-top: 26px; }
68
+ .paper-503-card :deep(.apple-card__body) { padding: 0; text-align: center; }
69
+ .paper-503-number { font-size: 70px; margin-bottom: 18px; }
70
+ .paper-503-heading h1 { font-size: 26px; margin-bottom: 8px; }
71
+ .paper-503-english { margin-bottom: 15px; }
72
+ .paper-503-actions { justify-content: center; margin-top: 18px; }
73
+ }
74
+ @media (max-width: 420px) {
75
+ .paper-503-page { padding-inline: 18px; padding-top: 12px; }
76
+ .paper-503-copy { font-size: 12px; }
77
+ .paper-503-card { margin-top: 22px; }
78
+ }
79
+ </style>
@@ -4,10 +4,10 @@
4
4
  <template #title>
5
5
  <div class="paper-404-heading">
6
6
  <p class="paper-404-number" aria-hidden="true">404</p>
7
- <h1>页面不存在</h1>
7
+ <h1>{{ page.title }}</h1>
8
8
  </div>
9
9
  </template>
10
- <p class="paper-404-copy">有些东西,悄悄溜走了。<br />就像松鼠的橡果,这一页也不知漂去了哪里。</p>
10
+ <p class="paper-404-copy">{{ page.copy[0] }}<br />{{ page.copy[1] }}</p>
11
11
  <div class="paper-404-actions">
12
12
  <router-link v-slot="{ href, navigate }" :to="homePath" custom>
13
13
  <apple-button :href="href" @click="navigate">返回首页</apple-button>
@@ -24,6 +24,9 @@ import { ArrowLeft } from 'lucide-vue-next'
24
24
  import { useRouter } from 'vue-router'
25
25
  import NotFoundPaperScene from '../components/not-found-paper-scene.vue'
26
26
 
27
+ import { errorPages } from '../error-pages'
28
+
29
+ const page = errorPages[404]
27
30
  const router = useRouter()
28
31
  const homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'
29
32
  function goBack() {
@@ -12,10 +12,9 @@
12
12
  <div class="components-content">
13
13
  <div class="feed-toolbar">
14
14
  <span class="feed-count">全部组件 <small>{{ catalog.length }}</small></span>
15
- <div class="preview-controls" role="group" aria-label="预览尺寸">
16
- <apple-link as="button" icon-only :icon="Monitor" :class="{ active: previewMode === 'desktop' }" :aria-pressed="previewMode === 'desktop'" label="桌面预览" @click="previewMode = 'desktop'" />
17
- <apple-link icon-only :icon="Smartphone" :class="{ active: previewMode === 'mobile' }" :aria-pressed="previewMode === 'mobile'" label="手机预览" @click="previewMode = 'mobile'" />
18
- </div>
15
+ <router-link v-slot="{ href, navigate }" to="#error-pages" custom>
16
+ <apple-link :href="href" @click="navigate">错误页面 <ArrowDown :size="14" aria-hidden="true" /></apple-link>
17
+ </router-link>
19
18
  </div>
20
19
  <div ref="feed" class="component-feed">
21
20
  <section v-for="(section, index) in sections" :id="section.id" :key="section.id" class="feed-group feed-anchor" :data-group="section.id" :aria-labelledby="`${section.id}-heading`">
@@ -23,12 +22,13 @@
23
22
  <MasonryFeed class="feed-cards" @layout="schedulePosition">
24
23
  <apple-card v-for="item in section.items" :id="componentId(item.name)" :key="item.name" class="component-card feed-anchor" :data-group="section.id" :aria-labelledby="`${componentId(item.name)}-heading`">
25
24
  <header class="component-card-heading"><div><h3 :id="`${componentId(item.name)}-heading`"><router-link :to="`#${componentId(item.name)}`">{{ item.label }}<Hash :size="14" /></router-link></h3><p>{{ item.description }}</p></div><code>{{ componentId(item.name) }}</code></header>
26
- <section class="detail-preview" :class="{ 'mobile-preview': previewMode === 'mobile' }" :aria-label="`${item.label}预览`"><ComponentDemo :name="item.name" @navigate="navigateGroup" /></section>
25
+ <section class="detail-preview" :aria-label="`${item.label}预览`"><ComponentDemo :name="item.name" @navigate="navigateGroup" /></section>
27
26
  <ComponentDocumentLink :href="documentPaths[item.name]" label="代码与 API" />
28
27
  </apple-card>
29
28
  </MasonryFeed>
30
29
  </section>
31
30
  </div>
31
+ <ErrorPagePreviews id="error-pages" class="feed-anchor" />
32
32
  </div>
33
33
  </div>
34
34
  </apple-container>
@@ -38,10 +38,11 @@
38
38
  <script setup lang="ts">
39
39
  import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
40
40
  import { useRoute, useRouter } from 'vue-router'
41
- import { Hash, Monitor, Smartphone } from 'lucide-vue-next'
41
+ import { ArrowDown, Hash } from 'lucide-vue-next'
42
42
  import { catalog, sections, componentId } from '../catalog'
43
43
  import ComponentDemo from '../ComponentDemo.vue'
44
44
  import ComponentIndex from '../components/ComponentIndex.vue'
45
+ import ErrorPagePreviews from '../components/ErrorPagePreviews.vue'
45
46
  import MasonryFeed from '../components/MasonryFeed.vue'
46
47
  import ComponentDocumentLink from '../editor/ComponentDocumentLink.vue'
47
48
  import { componentDocuments, componentDocumentPath } from '../editor/documents'
@@ -52,7 +53,6 @@ const router = useRouter()
52
53
  const feed = ref<HTMLElement>()
53
54
  const activeGroup = ref(sections[0].id)
54
55
  const activeId = ref('')
55
- const previewMode = ref('desktop')
56
56
  const expandedGroups = ref(sections.map(section => section.id))
57
57
  let anchors: HTMLElement[] = []
58
58
  let frame = 0
@@ -107,7 +107,7 @@ onBeforeUnmount(() => {
107
107
  .directory-index { display: flex; flex-direction: column; overflow: hidden; }
108
108
  .directory-header { flex-shrink: 0; }
109
109
  .directory-header > span { color: var(--apple-secondary); font-size: 12px; display: block; margin: 0 0 12px 8px; }
110
- .directory-tree { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
110
+ .directory-tree { display: flex; flex-direction: column; min-height: 0; overflow: hidden; padding-inline: var(--apple-focus-clearance, 6px); padding-block-start: var(--apple-focus-clearance, 6px); }
111
111
  @media (max-width: 900px) { .directory-index { display: none; } }
112
112
  .components-page > * { max-width: none; }
113
113
  .feed-anchor { scroll-margin-top: 96px; }
@@ -50,7 +50,7 @@ async function adapt(directory) {
50
50
  source = source.replace('to="/" aria-label="Apptify 首页"', ':to="homePath" aria-label="Apptify 首页"')
51
51
  source = source.replace(navigationEnd, "].filter(item => router.getRoutes().some(page => page.path === item.href))\nconst homePath = navigation[0]?.href ?? '/'\n\nfunction navigate")
52
52
  }
53
- if (['[...all].vue', '403.vue'].some(view => path === join(output, 'src/views', view))) {
53
+ if (['[...all].vue', '403.vue', '500.vue', '502.vue', '503.vue'].some(view => path === join(output, 'src/views', view))) {
54
54
  source = source.replace('to="/" custom', ':to="homePath" custom')
55
55
  source = source.replace('const router = useRouter()', "const router = useRouter()\nconst homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'")
56
56
  source = source.replace("else void router.push('/')", 'else void router.push(homePath)')
@@ -92,7 +92,7 @@ await writeFile(join(output, 'gitignore'), 'node_modules/\ndist/\n.env.local\n')
92
92
  await writeFile(join(output, 'README.md'), [
93
93
  "# Apptify 应用模板",
94
94
  "",
95
- "可编辑的 Vue 应用,包含导航、账号表单、外观设置、403、404 与组件示例。建议 Node.js 22 或 24 LTS。",
95
+ "可编辑的 Vue 应用,包含导航、账号表单、外观设置、403、404、502、503 与组件示例。建议 Node.js 22 或 24 LTS。",
96
96
  "",
97
97
  "```sh",
98
98
  "npm install",
@@ -1,88 +0,0 @@
1
- import { type PropType } from 'vue';
2
- import { type ScrollBarAxis } from '../core/scrollbar';
3
- export declare const AppleScrollBar: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
- target: {
5
- type: PropType<string | HTMLElement | (() => HTMLElement | undefined)>;
6
- default: string;
7
- };
8
- axis: {
9
- type: PropType<ScrollBarAxis>;
10
- default: string;
11
- };
12
- label: {
13
- type: StringConstructor;
14
- default: string;
15
- };
16
- controls: {
17
- type: StringConstructor;
18
- default: string;
19
- };
20
- }>, () => null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "interaction"[], "interaction", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
21
- target: {
22
- type: PropType<string | HTMLElement | (() => HTMLElement | undefined)>;
23
- default: string;
24
- };
25
- axis: {
26
- type: PropType<ScrollBarAxis>;
27
- default: string;
28
- };
29
- label: {
30
- type: StringConstructor;
31
- default: string;
32
- };
33
- controls: {
34
- type: StringConstructor;
35
- default: string;
36
- };
37
- }>> & Readonly<{
38
- onInteraction?: ((...args: any[]) => any) | undefined;
39
- }>, {
40
- label: string;
41
- target: string | HTMLElement | (() => HTMLElement | undefined);
42
- axis: ScrollBarAxis;
43
- controls: string;
44
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
45
- export declare const scrollBarComponents: {
46
- AppleScrollBar: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
47
- target: {
48
- type: PropType<string | HTMLElement | (() => HTMLElement | undefined)>;
49
- default: string;
50
- };
51
- axis: {
52
- type: PropType<ScrollBarAxis>;
53
- default: string;
54
- };
55
- label: {
56
- type: StringConstructor;
57
- default: string;
58
- };
59
- controls: {
60
- type: StringConstructor;
61
- default: string;
62
- };
63
- }>, () => null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "interaction"[], "interaction", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
64
- target: {
65
- type: PropType<string | HTMLElement | (() => HTMLElement | undefined)>;
66
- default: string;
67
- };
68
- axis: {
69
- type: PropType<ScrollBarAxis>;
70
- default: string;
71
- };
72
- label: {
73
- type: StringConstructor;
74
- default: string;
75
- };
76
- controls: {
77
- type: StringConstructor;
78
- default: string;
79
- };
80
- }>> & Readonly<{
81
- onInteraction?: ((...args: any[]) => any) | undefined;
82
- }>, {
83
- label: string;
84
- target: string | HTMLElement | (() => HTMLElement | undefined);
85
- axis: ScrollBarAxis;
86
- controls: string;
87
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
88
- };
@@ -1,14 +0,0 @@
1
- export type ScrollBarAxis = 'both' | 'horizontal' | 'vertical';
2
- interface ScrollBarOptions {
3
- axis?: ScrollBarAxis;
4
- label?: string;
5
- controls?: string;
6
- host?: HTMLElement;
7
- interaction?: () => void;
8
- }
9
- export declare function attachScrollBar(target: HTMLElement, options?: ScrollBarOptions): {
10
- refresh: () => void;
11
- dispose(): void;
12
- };
13
- export declare function installScrollBars(host: HTMLElement): () => void;
14
- export {};
@@ -1,15 +0,0 @@
1
- import { defineHandbook } from '../handbook'
2
-
3
- export default defineHandbook({
4
- name: 'AppleScrollBar', source: 'src/components/scrollbar.ts',
5
- props: [
6
- { name: 'target', type: 'string | HTMLElement | (() => HTMLElement | undefined)', default: '""', description: 'CSS 选择器、元素或返回元素的函数;空值自动接管当前文档的滚动容器。' },
7
- { name: 'axis', type: "'both' | 'horizontal' | 'vertical'", default: '"both"', description: '指定目标时显示的滚动方向。' },
8
- { name: 'label', type: 'string', default: '""', description: '滚动条的可访问名称,默认从目标名称生成。' },
9
- { name: 'controls', type: 'string', default: '""', description: '目标元素 ID;未提供时使用或生成目标 ID。' },
10
- ],
11
- events: [{ name: 'interaction', type: '() => void', description: '拖动、点击轨道、键盘或滚轮操作时触发。' }],
12
- slots: [],
13
- methods: [{ name: 'refresh()', type: '() => void', description: '重新测量目标及滚动位置。尺寸、内容和滚动变化默认自动同步。' }],
14
- notes: ['AppleProvider 默认启用全局接管,无需逐个包裹内容。滚动条使用 fixed,不占布局空间;z-index 跟随目标及祖先层级,默认为 0,弹窗内留在所属弹层上下文。', '原本主动隐藏滚动条的轮播、标签栏与日期滚轮保持原有交互;data-apple-scrollbar-ignore 可排除指定容器。'],
15
- })