@indielayer/ui 1.17.0 → 1.18.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 (47) hide show
  1. package/README.md +2 -2
  2. package/docs/assets/css/tailwind.css +6 -0
  3. package/docs/components/common/CodePreview.vue +14 -9
  4. package/docs/components/common/DocsFeatures.vue +41 -0
  5. package/docs/components/common/DocsHero.vue +216 -0
  6. package/docs/components/common/DocumentPage.vue +99 -112
  7. package/docs/components/common/ExampleBlocks.vue +157 -0
  8. package/docs/components/toolbar/Toolbar.vue +11 -2
  9. package/docs/components/toolbar/ToolbarColorToggle.vue +4 -4
  10. package/docs/components/toolbar/ToolbarSearch.vue +59 -62
  11. package/docs/composables/useDocMeta.ts +47 -0
  12. package/docs/icons.ts +28 -0
  13. package/docs/layouts/default.vue +1 -3
  14. package/docs/layouts/simple.vue +3 -1
  15. package/docs/main.ts +5 -0
  16. package/docs/pages/colors.vue +56 -47
  17. package/docs/pages/component/select/size.vue +1 -1
  18. package/docs/pages/component/select/usage.vue +14 -7
  19. package/docs/pages/error.vue +5 -3
  20. package/docs/pages/icons.vue +64 -54
  21. package/docs/pages/index.vue +93 -82
  22. package/docs/pages/typography.vue +38 -28
  23. package/docs/router/index.ts +31 -3
  24. package/docs/search/components.json +1 -1
  25. package/docs/search/index.json +1 -0
  26. package/lib/components/container/theme/Container.base.theme.js +1 -1
  27. package/lib/components/divider/theme/Divider.base.theme.js +1 -1
  28. package/lib/components/input/Input.vue.js +23 -24
  29. package/lib/components/select/Select.vue.d.ts +16 -27
  30. package/lib/components/select/Select.vue.js +451 -344
  31. package/lib/index.js +1 -1
  32. package/lib/index.umd.js +4 -4
  33. package/lib/version.d.ts +1 -1
  34. package/lib/version.js +1 -1
  35. package/lib/virtual/components/virtualList/VirtualList.vue.js +33 -31
  36. package/lib/virtual/components/virtualList/useDynamicRowHeight.js +18 -19
  37. package/package.json +8 -3
  38. package/src/components/container/theme/Container.base.theme.ts +1 -1
  39. package/src/components/divider/theme/Divider.base.theme.ts +1 -1
  40. package/src/components/input/Input.vue +1 -2
  41. package/src/components/select/Select.vue +94 -18
  42. package/src/version.ts +1 -1
  43. package/src/virtual/components/virtualList/VirtualList.test.ts +143 -26
  44. package/src/virtual/components/virtualList/VirtualList.vue +12 -18
  45. package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +22 -8
  46. package/src/virtual/components/virtualList/useDynamicRowHeight.ts +4 -2
  47. package/src/virtual/utils/parseNumericStyleValue.ts +2 -0
package/README.md CHANGED
@@ -12,11 +12,11 @@
12
12
  Vue 3 & Nuxt 3 UI Components built with Tailwind 3. **Build and prototype fast web applications.** 🚀
13
13
 
14
14
  ## 📖 Documentation
15
- Read the <a href="https://indielayer.com/ui/docs">Online Documentation</a>.
15
+ Read the <a href="https://indielayer.com">Online Documentation</a>.
16
16
 
17
17
  ## Quickstart
18
18
  ```bash
19
- npm init @indielayer/ui@latest
19
+ pnpm create @indielayer/ui@latest
20
20
  ```
21
21
 
22
22
  ---
@@ -38,6 +38,12 @@ body {
38
38
  }
39
39
  }
40
40
 
41
+ @layer components {
42
+ .docs-container {
43
+ @apply max-w-screen-2xl mx-auto w-full px-4 md:px-8 mt-8 md:mt-16;
44
+ }
45
+ }
46
+
41
47
  .inset-center {
42
48
  position: absolute;
43
49
  top: 50%;
@@ -24,17 +24,22 @@ function toggleCode() {
24
24
  expandedSync.value = !expandedSync.value
25
25
  }
26
26
 
27
- function copy(text?: string) {
27
+ async function copy(text?: string) {
28
28
  if (!text) return
29
29
 
30
- const el = document.createElement('textarea')
30
+ try {
31
+ await navigator.clipboard.writeText(text)
32
+ notifications?.success('Copied to clipboard!')
33
+ } catch {
34
+ const el = document.createElement('textarea')
31
35
 
32
- el.value = text
33
- document.body.appendChild(el)
34
- el.select()
35
- document.execCommand('copy')
36
- document.body.removeChild(el)
37
- notifications?.success('Copied to clipboard!')
36
+ el.value = text
37
+ document.body.appendChild(el)
38
+ el.select()
39
+ document.execCommand('copy')
40
+ document.body.removeChild(el)
41
+ notifications?.success('Copied to clipboard!')
42
+ }
38
43
  }
39
44
  </script>
40
45
 
@@ -50,7 +55,7 @@ function copy(text?: string) {
50
55
  ghost
51
56
  size="sm"
52
57
  :href="`${github}/${title?.toLowerCase()}.vue`"
53
- target="blank"
58
+ target="_blank"
54
59
  />
55
60
  <template #tooltip>
56
61
  Edit on <span class="text-gray-300">GitHub</span>
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ const features = [
3
+ {
4
+ icon: 'check-circle',
5
+ title: 'Accessible by default',
6
+ description: 'Keyboard-friendly components and sensible ARIA patterns out of the box.',
7
+ },
8
+ {
9
+ icon: 'palette',
10
+ title: 'Tailwind themes',
11
+ description: 'Base and Carbon theme variants with a shared preset for your design tokens.',
12
+ },
13
+ {
14
+ icon: 'cube',
15
+ title: 'Tree-shakeable',
16
+ description: 'Import only the components you need, or register the full library in one line.',
17
+ },
18
+ {
19
+ icon: 'code',
20
+ title: 'Vue 3 and Nuxt 3',
21
+ description: 'First-class Vue plugin plus a Nuxt module for SSR-ready setups.',
22
+ },
23
+ ]
24
+ </script>
25
+
26
+ <template>
27
+ <section class="docs-features -mx-4 lg:-mx-8 px-4 lg:px-8 mb-10">
28
+ <div class="docs-container">
29
+ <div class="text-overline text-primary-600 dark:text-primary-400 mb-3">Why Indielayer UI</div>
30
+ <h2 class="text-h3 mb-8 text-gray-900 dark:text-white">Everything you need to ship faster</h2>
31
+
32
+ <div class="grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
33
+ <div v-for="feature in features" :key="feature.title" class="min-w-0">
34
+ <x-icon :icon="feature.icon" size="lg" class="text-primary-500 mb-3"/>
35
+ <h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ feature.title }}</h3>
36
+ <p class="text-sm text-gray-600 dark:text-gray-400 m-0 leading-relaxed">{{ feature.description }}</p>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </section>
41
+ </template>
@@ -0,0 +1,216 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, onUnmounted, ref } from 'vue'
3
+ import { version } from '@indielayer/ui'
4
+ import ComponentsIndex from '../../search/components.json'
5
+ import ExampleBlocks from './ExampleBlocks.vue'
6
+
7
+ const componentCount = ComponentsIndex.length
8
+
9
+ const panelRef = ref<HTMLElement | null>(null)
10
+ const isHovering = ref(false)
11
+ const tiltX = ref(5)
12
+ const tiltY = ref(-8)
13
+ const prefersReducedMotion = ref(false)
14
+
15
+ const REST_TILT_X = 5
16
+ const REST_TILT_Y = -8
17
+ const MAX_TILT_X = 10
18
+ const MAX_TILT_Y = 14
19
+
20
+ const panelTransform = computed(() => {
21
+ if (prefersReducedMotion.value) return undefined
22
+
23
+ return `perspective(1200px) rotateX(${tiltX.value}deg) rotateY(${tiltY.value}deg)`
24
+ })
25
+
26
+ let motionQuery: MediaQueryList | null = null
27
+
28
+ function onMotionPreferenceChange(event: MediaQueryListEvent) {
29
+ prefersReducedMotion.value = event.matches
30
+ }
31
+
32
+ function updateTiltFromPointer(event: PointerEvent) {
33
+ if (!panelRef.value || prefersReducedMotion.value) return
34
+
35
+ const rect = panelRef.value.getBoundingClientRect()
36
+ const x = (event.clientX - rect.left) / rect.width - 0.5
37
+ const y = (event.clientY - rect.top) / rect.height - 0.5
38
+
39
+ tiltY.value = x * MAX_TILT_Y
40
+ tiltX.value = -y * MAX_TILT_X
41
+ }
42
+
43
+ function onPanelEnter() {
44
+ if (prefersReducedMotion.value) return
45
+
46
+ isHovering.value = true
47
+ tiltX.value = 0
48
+ tiltY.value = 0
49
+ }
50
+
51
+ function onPanelMove(event: PointerEvent) {
52
+ if (!isHovering.value) return
53
+
54
+ updateTiltFromPointer(event)
55
+ }
56
+
57
+ function onPanelLeave() {
58
+ isHovering.value = false
59
+ tiltX.value = REST_TILT_X
60
+ tiltY.value = REST_TILT_Y
61
+ }
62
+
63
+ onMounted(() => {
64
+ motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
65
+ prefersReducedMotion.value = motionQuery.matches
66
+ motionQuery.addEventListener('change', onMotionPreferenceChange)
67
+ })
68
+
69
+ onUnmounted(() => {
70
+ motionQuery?.removeEventListener('change', onMotionPreferenceChange)
71
+ })
72
+
73
+ function scrollToInstallation() {
74
+ const el = document.getElementById('installation')
75
+ const main = document.getElementById('main')
76
+
77
+ if (el && main) {
78
+ main.scrollTo({ top: el.offsetTop - 24, behavior: 'smooth' })
79
+ }
80
+ }
81
+ </script>
82
+
83
+ <template>
84
+ <section class="docs-hero relative mb-6 -mt-2 -mx-4 lg:-mx-8 px-4 lg:px-8">
85
+ <div class="docs-container grid lg:grid-cols-2 gap-10 lg:gap-8 items-center pb-10 lg:pb-14">
86
+ <div class="min-w-0">
87
+ <div class="flex items-center gap-4 mb-8">
88
+ <img
89
+ src="@/assets/images/logo_mini.svg"
90
+ width="52"
91
+ height="49"
92
+ alt=""
93
+ class="shrink-0 drop-shadow-sm"
94
+ />
95
+ <div>
96
+ <div class="flex items-center gap-2 flex-wrap">
97
+ <span class="text-xl font-bold tracking-tight text-gray-900 dark:text-white">Indielayer UI</span>
98
+ <x-tag size="xs" color="primary" filled rounded>v{{ version }}</x-tag>
99
+ </div>
100
+ <p class="text-sm text-gray-500 dark:text-gray-400 m-0 mt-0.5">Vue 3 component library</p>
101
+ </div>
102
+ </div>
103
+
104
+ <h1 class="text-4xl sm:text-5xl font-bold tracking-tight text-gray-900 dark:text-white leading-[1.1] mb-5 max-w-xl">
105
+ Composable UI blocks for modern Vue apps
106
+ </h1>
107
+
108
+ <p class="text-lg text-gray-600 dark:text-gray-300 max-w-lg mb-8 leading-relaxed m-0">
109
+ Accessible, themeable building blocks for Vue 3. Prototype production-quality UIs without rebuilding the basics.
110
+ </p>
111
+
112
+ <div class="flex flex-wrap items-center gap-6 mb-8 text-base">
113
+ <div class="flex items-center">
114
+ <x-icon icon="vue" size="xl"/>
115
+ <div class="ml-2">
116
+ <div class="font-semibold text-gray-900 dark:text-white">Vue.js 3</div>
117
+ <div class="text-sm text-gray-500 dark:text-gray-400">Performant UI</div>
118
+ </div>
119
+ </div>
120
+ <x-divider vertical class="!h-5 hidden sm:block"/>
121
+ <div class="flex items-center">
122
+ <x-icon icon="nuxt" size="xl"/>
123
+ <div class="ml-2">
124
+ <div class="font-semibold text-gray-900 dark:text-white">Nuxt.js 3</div>
125
+ <div class="text-sm text-gray-500 dark:text-gray-400">SSR Ready</div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <div class="flex flex-wrap gap-3 mb-8">
131
+ <x-button color="primary" to="/component/button" icon-right="arrow-right">
132
+ Browse components
133
+ </x-button>
134
+ <x-button outlined icon="book" @click="scrollToInstallation">
135
+ Installation
136
+ </x-button>
137
+ <x-button ghost icon="github" href="https://github.com/indielayer/ui" target="_blank">GitHub</x-button>
138
+ </div>
139
+
140
+ <div class="flex flex-wrap gap-2">
141
+ <x-tag size="sm" outlined rounded>{{ componentCount }}+ components</x-tag>
142
+ <x-tag size="sm" outlined rounded>Vue 3</x-tag>
143
+ <x-tag size="sm" outlined rounded>Nuxt 3</x-tag>
144
+ <x-tag size="sm" outlined rounded>Tailwind CSS 3</x-tag>
145
+ </div>
146
+ </div>
147
+
148
+ <div
149
+ class="docs-hero-panel hidden lg:block w-full min-w-0"
150
+ :class="{ 'docs-hero-panel-hover': isHovering }"
151
+ >
152
+ <div
153
+ ref="panelRef"
154
+ class="docs-hero-panel-tilt"
155
+ :style="{ transform: panelTransform }"
156
+ @pointerenter="onPanelEnter"
157
+ @pointermove="onPanelMove"
158
+ @pointerleave="onPanelLeave"
159
+ >
160
+ <div class="docs-hero-panel-surface rounded-xl border border-gray-200 bg-white shadow-lg shadow-gray-200/60 dark:border-gray-700 dark:bg-gray-950 dark:shadow-2xl dark:shadow-primary-500/10 max-h-[640px] overflow-y-auto p-6">
161
+ <example-blocks />
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </section>
167
+ </template>
168
+
169
+ <style lang="postcss" scoped>
170
+ .docs-hero {
171
+ background: radial-gradient(50% 100% at 25% 100%, #f8fafc 0%, rgb(248 250 252 / 0%) 100%);
172
+ }
173
+
174
+ html.dark .docs-hero {
175
+ background: radial-gradient(50% 100% at 25% 100%, rgb(16 185 129 / 16%) 0%, rgb(248 250 252 / 0%) 100%);
176
+ }
177
+
178
+ .docs-hero-panel:not(.docs-hero-panel-hover) {
179
+ animation: docs-hero-float 6s ease-in-out infinite;
180
+ }
181
+
182
+ .docs-hero-panel-hover {
183
+ animation: none;
184
+ }
185
+
186
+ .docs-hero-panel-tilt {
187
+ transform-style: preserve-3d;
188
+ transition: transform 0.2s ease-out;
189
+ will-change: transform;
190
+ }
191
+
192
+ .docs-hero-panel-surface {
193
+ transform: translateZ(0);
194
+ }
195
+
196
+ @keyframes docs-hero-float {
197
+ 0%,
198
+ 100% {
199
+ transform: translateY(0);
200
+ }
201
+
202
+ 50% {
203
+ transform: translateY(-4px);
204
+ }
205
+ }
206
+
207
+ @media (prefers-reduced-motion: reduce) {
208
+ .docs-hero-panel {
209
+ animation: none;
210
+ }
211
+
212
+ .docs-hero-panel-tilt {
213
+ transition: none;
214
+ }
215
+ }
216
+ </style>
@@ -1,15 +1,13 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, onUnmounted, ref } from 'vue'
2
+ import { computed } from 'vue'
3
+ import { useRoute } from 'vue-router'
4
+ import { useDocMeta } from '../../composables/useDocMeta'
3
5
 
4
6
  const props = defineProps({
5
7
  components: {
6
8
  type: [Array, String],
7
9
  default: () => [],
8
10
  },
9
- headings: {
10
- type: Array,
11
- default: () => [],
12
- },
13
11
  title: String,
14
12
  description: String,
15
13
  back: String,
@@ -21,6 +19,14 @@ const props = defineProps({
21
19
  },
22
20
  })
23
21
 
22
+ const route = useRoute()
23
+
24
+ useDocMeta({
25
+ title: props.title || 'Component',
26
+ description: props.description,
27
+ path: route.path,
28
+ })
29
+
24
30
  const headers = [
25
31
  { text: 'Name', value: 'name' },
26
32
  { text: 'Type', value: 'type' },
@@ -94,7 +100,6 @@ const componentsProperties = computed(() => {
94
100
  if (comp[property]) properties[componentName][property] = Object.keys(comp[property]).map((k) => ({ name: k }))
95
101
  })
96
102
 
97
- // vue 3 events
98
103
  if (comp['emits']) properties[componentName]['emits'] = Array.isArray(comp['emits']) ? comp['emits'].map((k) => ({ name: k })) : Object.keys(comp['emits']).map((k) => ({ name: k }))
99
104
  if (comp['expose']) properties[componentName]['methods'] = comp['expose'].map((k) => ({ name: k }))
100
105
  })
@@ -102,124 +107,106 @@ const componentsProperties = computed(() => {
102
107
  return properties
103
108
  })
104
109
 
105
- const anchors = ref([])
106
- const currentAnchor = ref(null)
110
+ const backTo = computed(() => (props.back ? `/component/${props.back}` : null))
111
+ const nextTo = computed(() => (props.next ? `/component/${props.next}` : null))
107
112
  </script>
108
113
 
109
114
  <template>
110
- <div class="w-full flex document-page">
111
- <div class="min-w-0 flex-auto">
112
- <div class="text-4xl font-semibold">
113
- {{ title }}
114
- <x-tooltip>
115
- <x-link :href="`${github}/index.vue`" target="blank" color="#94a3b8">
116
- <x-icon icon="edit" size="sm" />
117
- </x-link>
118
- <template #tooltip>
119
- Edit on <span class="text-gray-300">GitHub</span>
120
- </template>
121
- </x-tooltip>
115
+ <div class="document-page w-full">
116
+ <div class="text-4xl font-semibold">
117
+ {{ title }}
118
+ <x-tooltip>
119
+ <x-link :href="`${github}/index.vue`" target="_blank" color="#94a3b8">
120
+ <x-icon icon="edit" size="sm" />
121
+ </x-link>
122
+ <template #tooltip>
123
+ Edit on <span class="text-gray-300">GitHub</span>
124
+ </template>
125
+ </x-tooltip>
126
+ </div>
127
+ <div class="text-lg my-2 text-gray-500 dark:text-gray-400">{{ description }}</div>
128
+ <div class="mt-6 space-y-12">
129
+ <slot></slot>
130
+
131
+ <div v-for="demo in demos" :key="demo.name" class="demo-block">
132
+ <code-preview
133
+ :title="demo.name"
134
+ :description="demo.description"
135
+ :code="demo.code"
136
+ :github="github"
137
+ >
138
+ <component :is="demo.component" />
139
+ </code-preview>
122
140
  </div>
123
- <div class="text-lg my-2 text-gray-500">{{ description }}</div>
124
- <div class="mt-4">
125
- <slot></slot>
126
-
127
- <div v-for="demo in demos" :key="demo.name">
128
- <code-preview
129
- :title="demo.name"
130
- :description="demo.description"
131
- :code="demo.code"
132
- :github="github"
133
- >
134
- <component :is="demo.component" />
135
- </code-preview>
136
- </div>
137
141
 
138
- <div v-if="componentsProperties">
139
- <h2 id="api" class="!text-2xl !mt-20"><a class="anchor" href="#api">#</a>API</h2>
142
+ <div v-if="componentsProperties">
143
+ <h2 id="api" class="!text-2xl !mt-4"><a class="anchor" href="#api">#</a>API</h2>
140
144
 
141
- <section
142
- v-for="(component, componentName) in componentsProperties"
143
- :key="componentName"
145
+ <section
146
+ v-for="(component, componentName) in componentsProperties"
147
+ :key="componentName"
148
+ >
149
+ <h3
150
+ :id="String(componentName).toLowerCase()"
151
+ class="mt-10 dark:text-gray-300 text-gray-800 text-2xl border-b-2 dark:border-gray-600 pb-2"
152
+ >
153
+ {{ componentName }}
154
+ </h3>
155
+ <div
156
+ v-for="(properties, propertyName) in component"
157
+ :key="propertyName"
144
158
  >
145
- <h3 class="mt-10 dark:text-gray-300 text-gray-800 text-2xl border-b-2 dark:border-gray-600 pb-2">
146
- {{ componentName }}
147
- </h3>
148
- <div
149
- v-for="(properties, propertyName) in component"
150
- :key="propertyName"
151
- >
152
- <div>
153
- <h4 class="mt-6 mb-2 text-gray-800 dark:text-gray-300 text-xl capitalize">
154
- {{ propertyName }}
155
- </h4>
156
-
157
- <div class="text-sm font-light">
158
- <x-table
159
- :headers="propertyName === 'props' ? headers : headersSimple"
160
- :items="properties"
161
- >
162
- <template #item-name="{ item }">
163
- <div class="text-primary-500">{{ item.name }}</div>
164
- </template>
165
- <template #item-type="{ item }">
166
- <div v-for="t in item.type" :key="t">{{ t }}</div>
167
- </template>
168
- <template #item-required="{ item }">
169
- <div>{{ item.required ? 'true' : '' }}</div>
170
- </template>
171
- <template #item-validator="{ item }">
172
- <div class="space-x-2">
173
- <span v-for="validator in item.validator" :key="validator">{{ validator }}</span>
174
- </div>
175
- </template>
176
- </x-table>
177
- </div>
159
+ <div>
160
+ <h4
161
+ :id="`${String(componentName).toLowerCase()}-${propertyName}`"
162
+ class="mt-6 mb-2 text-gray-800 dark:text-gray-300 text-xl capitalize"
163
+ >
164
+ {{ propertyName }}
165
+ </h4>
166
+
167
+ <div class="text-sm font-light">
168
+ <x-table
169
+ :headers="propertyName === 'props' ? headers : headersSimple"
170
+ :items="properties"
171
+ >
172
+ <template #item-name="{ item }">
173
+ <div class="text-primary-500">{{ item.name }}</div>
174
+ </template>
175
+ <template #item-type="{ item }">
176
+ <div v-for="t in item.type" :key="t">{{ t }}</div>
177
+ </template>
178
+ <template #item-required="{ item }">
179
+ <div>{{ item.required ? 'true' : '' }}</div>
180
+ </template>
181
+ <template #item-validator="{ item }">
182
+ <div class="space-x-2">
183
+ <span v-for="validator in item.validator" :key="validator">{{ validator }}</span>
184
+ </div>
185
+ </template>
186
+ </x-table>
178
187
  </div>
179
188
  </div>
180
-
181
- </section>
182
- </div>
189
+ </div>
190
+ </section>
183
191
  </div>
184
-
185
- <!-- <div class="flex my-10">
186
- <x-button
187
- v-if="back"
188
- :to="back"
189
- outlined
190
- class="capitalize"
191
- icon="arrow-left"
192
- >{{ back }}</x-button>
193
- <x-spacer/>
194
- <x-button
195
- v-if="next"
196
- :to="next"
197
- outlined
198
- class="capitalize"
199
- icon-right="arrow-right"
200
- >{{ next }}</x-button>
201
- </div> -->
202
192
  </div>
203
193
 
204
- <div v-if="anchors.length > 0" class="hidden xl:text-sm xl:block flex-none w-64 pl-8 mr-8">
205
- <div class="flex flex-col justify-between overflow-y-auto sticky max-h-(screen-18) pt-10 pb-6 top-0">
206
- <div class="mb-8">
207
- <h5 class="text-gray-900 dark:text-gray-100 uppercase tracking-wide font-semibold mb-3 text-sm lg:text-xs">On this page</h5>
208
- <ul class="overflow-x-hidden text-gray-500 dark:text-gray-400 font-medium">
209
- <li v-for="(anchor, index) in anchors" :key="index">
210
- <a
211
- :href="`#${anchor.id}`"
212
- class="block transform transition-colors duration-200 py-2 hover:text-gray-900"
213
- :class="{
214
- 'text-gray-900 dark:text-gray-200': anchor.id === currentAnchor,
215
- }"
216
- >
217
- {{ anchor.name }}
218
- </a>
219
- </li>
220
- </ul>
221
- </div>
222
- </div>
194
+ <div v-if="backTo || nextTo" class="flex my-12 pt-8 border-t dark:border-gray-700">
195
+ <x-button
196
+ v-if="backTo"
197
+ :to="backTo"
198
+ outlined
199
+ class="capitalize"
200
+ icon-left="arrow-left"
201
+ >{{ back }}</x-button>
202
+ <x-spacer/>
203
+ <x-button
204
+ v-if="nextTo"
205
+ :to="nextTo"
206
+ outlined
207
+ class="capitalize"
208
+ icon-right="arrow-right"
209
+ >{{ next }}</x-button>
223
210
  </div>
224
211
  </div>
225
212
  </template>