clarity-theme 0.1.0 → 0.1.1

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 (119) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +18 -4
  3. package/README.zh-CN.md +18 -4
  4. package/app/app.config.ts +110 -110
  5. package/app/app.vue +5 -5
  6. package/app/components/blog/BlogAside.vue +25 -25
  7. package/app/components/blog/BlogFooter.vue +25 -25
  8. package/app/components/blog/BlogHeader.global.vue +48 -48
  9. package/app/components/blog/BlogPanel.vue +43 -43
  10. package/app/components/blog/BlogSidebar.vue +53 -53
  11. package/app/components/blog/BlogWidget.vue +42 -42
  12. package/app/components/blog/Mask.vue +25 -25
  13. package/app/components/blog/SkipToContent.vue +9 -9
  14. package/app/components/blog/ThemeToggle.vue +22 -22
  15. package/app/components/content/Badge.vue +45 -45
  16. package/app/components/content/Blur.vue +14 -14
  17. package/app/components/content/CardList.vue +8 -8
  18. package/app/components/content/Chat.vue +41 -41
  19. package/app/components/content/Copy.vue +71 -71
  20. package/app/components/content/EmojiClock.vue +49 -49
  21. package/app/components/content/FeedCard.vue +96 -96
  22. package/app/components/content/FeedGroup.vue +61 -61
  23. package/app/components/content/Folding.vue +19 -19
  24. package/app/components/content/Key.vue +143 -143
  25. package/app/components/content/LinkBanner.vue +27 -27
  26. package/app/components/content/LinkCard.vue +28 -28
  27. package/app/components/content/MdTitle.vue +8 -8
  28. package/app/components/content/Mermaid.vue +99 -99
  29. package/app/components/content/MusicScore.vue +63 -63
  30. package/app/components/content/Pic.vue +49 -49
  31. package/app/components/content/Poetry.vue +27 -27
  32. package/app/components/content/ProseA.vue +24 -24
  33. package/app/components/content/ProseCode.vue +16 -16
  34. package/app/components/content/ProsePre.vue +117 -117
  35. package/app/components/content/ProseTable.vue +23 -23
  36. package/app/components/content/Quote.vue +21 -21
  37. package/app/components/content/Tab.vue +32 -32
  38. package/app/components/content/Timeline.vue +33 -33
  39. package/app/components/content/Tip.vue +40 -40
  40. package/app/components/content/VideoEmbed.vue +112 -112
  41. package/app/components/partial/Button.vue +36 -36
  42. package/app/components/partial/DlGroup.vue +31 -31
  43. package/app/components/partial/Dropdown.vue +21 -21
  44. package/app/components/partial/Error.vue +39 -39
  45. package/app/components/partial/Expand.vue +26 -26
  46. package/app/components/partial/IconNavList.vue +24 -24
  47. package/app/components/partial/Pagination.vue +62 -62
  48. package/app/components/partial/RadioGroup.vue +30 -30
  49. package/app/components/partial/Secret.vue +11 -11
  50. package/app/components/partial/Slider.vue +48 -48
  51. package/app/components/partial/Toggle.vue +17 -17
  52. package/app/components/popover/Lightbox.vue +44 -44
  53. package/app/components/popover/Search.vue +143 -143
  54. package/app/components/popover/SearchItem.vue +28 -28
  55. package/app/components/post/Archive.vue +40 -40
  56. package/app/components/post/Article.vue +49 -49
  57. package/app/components/post/Comment.vue +119 -119
  58. package/app/components/post/Excerpt.vue +40 -40
  59. package/app/components/post/OrderToggle.vue +70 -70
  60. package/app/components/post/PostFooter.vue +52 -52
  61. package/app/components/post/PostHeader.vue +66 -66
  62. package/app/components/post/PostSurround.vue +51 -51
  63. package/app/components/post/Slide.vue +85 -85
  64. package/app/components/util/Date.vue +62 -62
  65. package/app/components/util/HydrateSafe.vue +10 -10
  66. package/app/components/util/Img.vue +38 -38
  67. package/app/components/util/Link.vue +11 -11
  68. package/app/components/widget/BlogLog.vue +23 -23
  69. package/app/components/widget/BlogStats.vue +38 -38
  70. package/app/components/widget/BlogTech.vue +57 -57
  71. package/app/components/widget/Empty.vue +3 -3
  72. package/app/components/widget/Toc.vue +67 -67
  73. package/app/composables/useArticle.ts +113 -113
  74. package/app/composables/useAvoid.ts +87 -87
  75. package/app/composables/useCopy.ts +21 -21
  76. package/app/composables/usePagination.ts +70 -70
  77. package/app/composables/useShiki.ts +129 -129
  78. package/app/composables/useToc.ts +57 -57
  79. package/app/composables/useWidgets.ts +56 -56
  80. package/app/error.vue +31 -31
  81. package/app/layouts/default.vue +25 -25
  82. package/app/pages/[...slug].vue +59 -59
  83. package/app/pages/archive.vue +129 -129
  84. package/app/pages/index.vue +76 -76
  85. package/app/pages/link.vue +62 -62
  86. package/app/pages/preview.vue +46 -46
  87. package/app/plugins/easter-egg.ts +23 -23
  88. package/app/plugins/init.ts +6 -6
  89. package/app/plugins/tippy.ts +22 -22
  90. package/app/shiki.config.ts +8 -8
  91. package/app/stores/layout.ts +34 -34
  92. package/app/stores/search.ts +37 -37
  93. package/app/types/article.ts +16 -16
  94. package/app/types/index.ts +14 -14
  95. package/app/types/nav.ts +11 -11
  96. package/app/utils/anim.ts +37 -37
  97. package/app/utils/img.ts +83 -83
  98. package/config/app.ts +61 -61
  99. package/config/content.ts +75 -75
  100. package/config/define.ts +3 -3
  101. package/config/feeds.empty.ts +6 -4
  102. package/config/public.ts +1 -1
  103. package/config/schema.ts +89 -89
  104. package/config/server.ts +1 -1
  105. package/modules/clarity-config/index.ts +162 -159
  106. package/nuxt.config.ts +212 -212
  107. package/package.json +25 -21
  108. package/public/assets/atom.css +274 -274
  109. package/public/assets/atom.xsl +109 -109
  110. package/remark-plugins/rehype-meta-slots.ts +58 -58
  111. package/remark-plugins/remark-code-component.ts +41 -41
  112. package/server/api/stats.get.ts +84 -83
  113. package/server/routes/atom.xml.get.ts +85 -85
  114. package/server/routes/subscriptions.opml.get.ts +64 -64
  115. package/shared/utils/clarity.ts +24 -24
  116. package/shared/utils/icon.ts +218 -218
  117. package/shared/utils/link.ts +52 -52
  118. package/shared/utils/str.ts +88 -88
  119. package/shared/utils/time.ts +115 -115
@@ -1,114 +1,114 @@
1
- <script setup lang="ts">
2
- const props = withDefaults(defineProps<{
3
- /** 视频类型,数字过长时需通过双引号传入字符串 */
4
- type?: 'raw' | 'bilibili' | 'bilibili-nano' | 'youtube' | 'douyin' | 'douyin-wide' | 'tiktok'
5
- id: string
6
- autoplay?: boolean
7
- /** 视频宽高比,如 `16 / 9` `1.6` */
8
- ratio?: string | number
9
- /** 仅 type 为 `raw` 时可用 */
10
- poster?: string
11
- /** 视频宽度 */
12
- width?: string
13
- /** 视频高度 */
14
- height?: string
15
- /** CSS Zoom 比例 */
16
- zoom?: number
17
- }>(), {
18
- type: 'raw',
19
- height: '80vh',
20
- })
21
-
22
- const ratio = computed(() => {
23
- if (props.ratio) {
24
- return props.ratio
25
- }
26
- switch (props.type) {
27
- case 'raw':
28
- return undefined
29
- case 'douyin':
30
- return '27 / 56'
31
- case 'douyin-wide':
32
- return '1198 / 731'
33
- default:
34
- return '16 / 9'
35
- }
36
- })
37
-
38
- const src = computed(() => {
39
- switch (props.type) {
40
- case 'bilibili':
41
- return `https://player.bilibili.com/player.html?bvid=${props.id}&autoplay=${props.autoplay}`
42
- case 'bilibili-nano':
43
- return `https://www.bilibili.com/blackboard/newplayer.html?bvid=${props.id}&autoplay=${props.autoplay}`
44
- case 'youtube':
45
- return `https://www.youtube.com/embed/${props.id}?rel=0&disablekb=1&playsinline=1&autoplay=${props.autoplay}`
46
- case 'douyin':
47
- return `https://open.douyin.com/player/video?vid=${props.id}`
48
- case 'douyin-wide':
49
- return `https://open.douyin.com/player/video?vid=${props.id}`
50
- case 'tiktok':
51
- return `https://www.tiktok.com/embed/v3/${props.id}`
52
- default:
53
- return props.id
54
- }
55
- })
56
-
57
- const videoPlayer = useTemplateRef('video-player')
58
-
59
- // 抖音会预检测宽度是否小于 730px,再返回竖屏或横屏模式
60
- // douyin-wide 需要阻塞加载,等进入视口后通过 zoom: 0.1 触发横屏模式
61
- // 已优化为 loading="lazy"
62
- const zoom = ref<number>()
63
- const zoomFactorForFixed = {
64
- 'douyin': 0.0031,
65
- 'douyin-wide': 0.00134,
66
- }
67
-
68
- function useZoomHelper(resizeType: keyof typeof zoomFactorForFixed) {
69
- useResizeObserver(videoPlayer, ([entry]) => {
70
- if (!entry)
71
- return
72
- const { width } = entry.contentRect
73
- const zoomFactor = zoomFactorForFixed[resizeType]
74
- zoom.value = width * zoomFactor
75
- })
76
- }
77
-
78
- onMounted(() => {
79
- if (props.type in zoomFactorForFixed)
80
- useZoomHelper(props.type as keyof typeof zoomFactorForFixed)
81
- })
82
- </script>
83
-
84
- <template>
85
- <div
86
- ref="video-player"
87
- class="video"
88
- :style="{
89
- aspectRatio: ratio,
90
- maxWidth: width,
91
- maxHeight: height,
92
- }"
93
- >
94
- <video
95
- v-if="type === 'raw'"
96
- :poster
97
- :src
98
- controls
99
- />
100
- <iframe
101
- v-else
102
- :src
103
- :style="{ zoom }"
104
- scrolling="no"
105
- loading="lazy"
106
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture"
107
- />
108
- </div>
109
- </template>
110
-
111
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const props = withDefaults(defineProps<{
3
+ /** 视频类型,数字过长时需通过双引号传入字符串 */
4
+ type?: 'raw' | 'bilibili' | 'bilibili-nano' | 'youtube' | 'douyin' | 'douyin-wide' | 'tiktok'
5
+ id: string
6
+ autoplay?: boolean
7
+ /** 视频宽高比,如 `16 / 9` `1.6` */
8
+ ratio?: string | number
9
+ /** 仅 type 为 `raw` 时可用 */
10
+ poster?: string
11
+ /** 视频宽度 */
12
+ width?: string
13
+ /** 视频高度 */
14
+ height?: string
15
+ /** CSS Zoom 比例 */
16
+ zoom?: number
17
+ }>(), {
18
+ type: 'raw',
19
+ height: '80vh',
20
+ })
21
+
22
+ const ratio = computed(() => {
23
+ if (props.ratio) {
24
+ return props.ratio
25
+ }
26
+ switch (props.type) {
27
+ case 'raw':
28
+ return undefined
29
+ case 'douyin':
30
+ return '27 / 56'
31
+ case 'douyin-wide':
32
+ return '1198 / 731'
33
+ default:
34
+ return '16 / 9'
35
+ }
36
+ })
37
+
38
+ const src = computed(() => {
39
+ switch (props.type) {
40
+ case 'bilibili':
41
+ return `https://player.bilibili.com/player.html?bvid=${props.id}&autoplay=${props.autoplay}`
42
+ case 'bilibili-nano':
43
+ return `https://www.bilibili.com/blackboard/newplayer.html?bvid=${props.id}&autoplay=${props.autoplay}`
44
+ case 'youtube':
45
+ return `https://www.youtube.com/embed/${props.id}?rel=0&disablekb=1&playsinline=1&autoplay=${props.autoplay}`
46
+ case 'douyin':
47
+ return `https://open.douyin.com/player/video?vid=${props.id}`
48
+ case 'douyin-wide':
49
+ return `https://open.douyin.com/player/video?vid=${props.id}`
50
+ case 'tiktok':
51
+ return `https://www.tiktok.com/embed/v3/${props.id}`
52
+ default:
53
+ return props.id
54
+ }
55
+ })
56
+
57
+ const videoPlayer = useTemplateRef('video-player')
58
+
59
+ // 抖音会预检测宽度是否小于 730px,再返回竖屏或横屏模式
60
+ // douyin-wide 需要阻塞加载,等进入视口后通过 zoom: 0.1 触发横屏模式
61
+ // 已优化为 loading="lazy"
62
+ const zoom = ref<number>()
63
+ const zoomFactorForFixed = {
64
+ 'douyin': 0.0031,
65
+ 'douyin-wide': 0.00134,
66
+ }
67
+
68
+ function useZoomHelper(resizeType: keyof typeof zoomFactorForFixed) {
69
+ useResizeObserver(videoPlayer, ([entry]) => {
70
+ if (!entry)
71
+ return
72
+ const { width } = entry.contentRect
73
+ const zoomFactor = zoomFactorForFixed[resizeType]
74
+ zoom.value = width * zoomFactor
75
+ })
76
+ }
77
+
78
+ onMounted(() => {
79
+ if (props.type in zoomFactorForFixed)
80
+ useZoomHelper(props.type as keyof typeof zoomFactorForFixed)
81
+ })
82
+ </script>
83
+
84
+ <template>
85
+ <div
86
+ ref="video-player"
87
+ class="video"
88
+ :style="{
89
+ aspectRatio: ratio,
90
+ maxWidth: width,
91
+ maxHeight: height,
92
+ }"
93
+ >
94
+ <video
95
+ v-if="type === 'raw'"
96
+ :poster
97
+ :src
98
+ controls
99
+ />
100
+ <iframe
101
+ v-else
102
+ :src
103
+ :style="{ zoom }"
104
+ scrolling="no"
105
+ loading="lazy"
106
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture"
107
+ />
108
+ </div>
109
+ </template>
110
+
111
+ <style lang="scss" scoped>
112
112
  .video {
113
113
  contain: paint;
114
114
  border-radius: 0.8rem;
@@ -124,4 +124,4 @@ iframe, video {
124
124
  width: 100%;
125
125
  height: 100%;
126
126
  }
127
- </style>
127
+ </style>
@@ -1,38 +1,38 @@
1
- <script setup lang="ts">
2
- import { UtilLink } from '#components'
3
-
4
- export interface ButtonProps {
5
- variant?: 'button' | 'text'
6
- icon?: string
7
- text?: string
8
- to?: string
9
- desc?: string
10
- primary?: boolean
11
- }
12
-
13
- withDefaults(defineProps<ButtonProps>(), {
14
- variant: 'button',
15
- })
16
- </script>
17
-
18
- <template>
19
- <component
20
- :is="to ? UtilLink : 'button'"
21
- :to
22
- class="z-button"
23
- :class="[variant, { primary }]"
24
- >
25
- <div class="button-main">
26
- <Icon v-if="icon" :name="icon" />
27
- <slot>{{ text }}</slot>
28
- </div>
29
- <div v-if="desc" class="button-desc">
30
- {{ desc }}
31
- </div>
32
- </component>
33
- </template>
34
-
35
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import { UtilLink } from '#components'
3
+
4
+ export interface ButtonProps {
5
+ variant?: 'button' | 'text'
6
+ icon?: string
7
+ text?: string
8
+ to?: string
9
+ desc?: string
10
+ primary?: boolean
11
+ }
12
+
13
+ withDefaults(defineProps<ButtonProps>(), {
14
+ variant: 'button',
15
+ })
16
+ </script>
17
+
18
+ <template>
19
+ <component
20
+ :is="to ? UtilLink : 'button'"
21
+ :to
22
+ class="z-button"
23
+ :class="[variant, { primary }]"
24
+ >
25
+ <div class="button-main">
26
+ <Icon v-if="icon" :name="icon" />
27
+ <slot>{{ text }}</slot>
28
+ </div>
29
+ <div v-if="desc" class="button-desc">
30
+ {{ desc }}
31
+ </div>
32
+ </component>
33
+ </template>
34
+
35
+ <style lang="scss" scoped>
36
36
  .z-button {
37
37
  display: inline-block;
38
38
  transition: color 0.1s, background-color 0.2s;
@@ -95,4 +95,4 @@ withDefaults(defineProps<ButtonProps>(), {
95
95
  text-align: center;
96
96
  color: var(--c-text-2);
97
97
  }
98
- </style>
98
+ </style>
@@ -1,33 +1,33 @@
1
- <script setup lang="ts">
2
- import type { VNodeChild } from 'vue'
3
-
4
- interface DlItem {
5
- label: string
6
- value: MaybeRefOrGetter<VNodeChild>
7
- tip?: MaybeRefOrGetter<string>
8
- }
9
-
10
- withDefaults(defineProps<{
11
- items: DlItem[]
12
- size?: 'small' | 'medium' | 'large'
13
- }>(), {
14
- size: 'medium',
15
- })
16
- </script>
17
-
18
- <template>
19
- <dl class="dl-group" :class="size">
20
- <div v-for="{ label, value, tip } in items" :key="label">
21
- <dt>{{ label }}</dt>
22
- <dd :title="toValue(tip)">
23
- <!-- 支持 string, Ref<String>, VNode, () => VNode -->
24
- <component :is="() => toValue(value)" />
25
- </dd>
26
- </div>
27
- </dl>
28
- </template>
29
-
30
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import type { VNodeChild } from 'vue'
3
+
4
+ interface DlItem {
5
+ label: string
6
+ value: MaybeRefOrGetter<VNodeChild>
7
+ tip?: MaybeRefOrGetter<string>
8
+ }
9
+
10
+ withDefaults(defineProps<{
11
+ items: DlItem[]
12
+ size?: 'small' | 'medium' | 'large'
13
+ }>(), {
14
+ size: 'medium',
15
+ })
16
+ </script>
17
+
18
+ <template>
19
+ <dl class="dl-group" :class="size">
20
+ <div v-for="{ label, value, tip } in items" :key="label">
21
+ <dt>{{ label }}</dt>
22
+ <dd :title="toValue(tip)">
23
+ <!-- 支持 string, Ref<String>, VNode, () => VNode -->
24
+ <component :is="() => toValue(value)" />
25
+ </dd>
26
+ </div>
27
+ </dl>
28
+ </template>
29
+
30
+ <style lang="scss" scoped>
31
31
  .dl-group {
32
32
  > div {
33
33
  padding: 0.2em 0;
@@ -66,4 +66,4 @@ withDefaults(defineProps<{
66
66
  }
67
67
  }
68
68
  }
69
- </style>
69
+ </style>
@@ -1,23 +1,23 @@
1
- <!-- https://vue-tippy.netlify.app/props/ -->
2
- <!-- 如果要点击触发,请使用 trigger="focusin" 并添加 tabindex="0" -->
3
-
4
- <template>
5
- <Tooltip
6
- class="dropdown"
7
- interactive
8
- placement="bottom"
9
- :arrow="false"
10
- :hide-on-click="false"
11
- :offset="[0, 0]"
12
- >
13
- <slot />
14
- <template #content="{ hide }">
15
- <slot name="content" :hide />
16
- </template>
17
- </Tooltip>
18
- </template>
19
-
20
- <style lang="scss" scoped>
1
+ <!-- https://vue-tippy.netlify.app/props/ -->
2
+ <!-- 如果要点击触发,请使用 trigger="focusin" 并添加 tabindex="0" -->
3
+
4
+ <template>
5
+ <Tooltip
6
+ class="dropdown"
7
+ interactive
8
+ placement="bottom"
9
+ :arrow="false"
10
+ :hide-on-click="false"
11
+ :offset="[0, 0]"
12
+ >
13
+ <slot />
14
+ <template #content="{ hide }">
15
+ <slot name="content" :hide />
16
+ </template>
17
+ </Tooltip>
18
+ </template>
19
+
20
+ <style lang="scss" scoped>
21
21
  // https://vue-tippy.netlify.app/props#appendto
22
22
  // Tooltip 位于组件根部时,interactive tippy 会插入到父组件
23
23
  :deep() ~ [data-tippy-root] > .tippy-box {
@@ -51,4 +51,4 @@
51
51
  }
52
52
  }
53
53
  }
54
- </style>
54
+ </style>
@@ -1,41 +1,41 @@
1
- <script setup lang="ts">
2
- import type { BundledLanguage } from 'shiki'
3
-
4
- withDefaults(defineProps<{
5
- icon?: string
6
- title?: string
7
- message?: string
8
- code?: string
9
- language?: BundledLanguage
10
- }>(), {
11
- icon: 'line-md:cloud-alt-print-twotone-loop',
12
- language: 'log',
13
- })
14
- </script>
15
-
16
- <template>
17
- <div class="error proper-height">
18
- <div />
19
- <Icon class="error-icon" :name="icon" />
20
- <div class="error-title" v-html="title" />
21
-
22
- <div class="operation">
23
- <slot name="operation" />
24
- </div>
25
-
26
- <ProsePre
27
- v-if="code"
28
- :filename="message"
29
- :language
30
- :code
31
- meta="wrap"
32
- />
33
-
34
- <slot />
35
- </div>
36
- </template>
37
-
38
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import type { BundledLanguage } from 'shiki'
3
+
4
+ withDefaults(defineProps<{
5
+ icon?: string
6
+ title?: string
7
+ message?: string
8
+ code?: string
9
+ language?: BundledLanguage
10
+ }>(), {
11
+ icon: 'line-md:cloud-alt-print-twotone-loop',
12
+ language: 'log',
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <div class="error proper-height">
18
+ <div />
19
+ <Icon class="error-icon" :name="icon" />
20
+ <div class="error-title" v-html="title" />
21
+
22
+ <div class="operation">
23
+ <slot name="operation" />
24
+ </div>
25
+
26
+ <ProsePre
27
+ v-if="code"
28
+ :filename="message"
29
+ :language
30
+ :code
31
+ meta="wrap"
32
+ />
33
+
34
+ <slot />
35
+ </div>
36
+ </template>
37
+
38
+ <style lang="scss" scoped>
39
39
  .error {
40
40
  display: flex;
41
41
  flex-direction: column;
@@ -67,4 +67,4 @@ withDefaults(defineProps<{
67
67
  }
68
68
  }
69
69
  }
70
- </style>
70
+ </style>
@@ -1,28 +1,28 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- name?: string
4
- inPlace?: boolean
5
- }>()
6
-
7
- const expand = defineModel<boolean>()
8
- </script>
9
-
10
- <template>
11
- <div class="z-expand">
12
- <Transition name="collapse">
13
- <div v-show="expand" class="expand-content">
14
- <slot />
15
- </div>
16
- </Transition>
17
-
18
- <button class="toggle-btn" :class="{ 'in-place': inPlace }" @click="expand = !expand">
19
- <Icon class="toggle-icon" :class="{ expand }" name="tabler:chevrons-down" />
20
- <span>{{ `${expand ? '收起' : '展开'}${name}` }}</span>
21
- </button>
22
- </div>
23
- </template>
24
-
25
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ name?: string
4
+ inPlace?: boolean
5
+ }>()
6
+
7
+ const expand = defineModel<boolean>()
8
+ </script>
9
+
10
+ <template>
11
+ <div class="z-expand">
12
+ <Transition name="collapse">
13
+ <div v-show="expand" class="expand-content">
14
+ <slot />
15
+ </div>
16
+ </Transition>
17
+
18
+ <button class="toggle-btn" :class="{ 'in-place': inPlace }" @click="expand = !expand">
19
+ <Icon class="toggle-icon" :class="{ expand }" name="tabler:chevrons-down" />
20
+ <span>{{ `${expand ? '收起' : '展开'}${name}` }}</span>
21
+ </button>
22
+ </div>
23
+ </template>
24
+
25
+ <style lang="scss" scoped>
26
26
  .z-expand {
27
27
  display: flex;
28
28
  flex-direction: column;
@@ -48,4 +48,4 @@ const expand = defineModel<boolean>()
48
48
  transform: scaleY(-1);
49
49
  }
50
50
  }
51
- </style>
51
+ </style>
@@ -1,26 +1,26 @@
1
- <script setup lang="ts">
2
- import type { NavItem } from '../../types/nav'
3
-
4
- defineProps<{
5
- list: NavItem[]
6
- }>()
7
- </script>
8
-
9
- <template>
10
- <menu>
11
- <UtilLink
12
- v-for="{ text, icon, url } in list"
13
- :key="text"
14
- v-tip="text"
15
- :to="url"
16
- :aria-label="text"
17
- >
18
- <Icon :name="icon" />
19
- </UtilLink>
20
- </menu>
21
- </template>
22
-
23
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import type { NavItem } from '../../types/nav'
3
+
4
+ defineProps<{
5
+ list: NavItem[]
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <menu>
11
+ <UtilLink
12
+ v-for="{ text, icon, url } in list"
13
+ :key="text"
14
+ v-tip="text"
15
+ :to="url"
16
+ :aria-label="text"
17
+ >
18
+ <Icon :name="icon" />
19
+ </UtilLink>
20
+ </menu>
21
+ </template>
22
+
23
+ <style lang="scss" scoped>
24
24
  menu {
25
25
  display: flex;
26
26
  justify-content: center;
@@ -39,4 +39,4 @@ menu {
39
39
  }
40
40
  }
41
41
  }
42
- </style>
42
+ </style>