srcdev-nuxt-components 9.4.7 → 9.4.8

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 (49) hide show
  1. package/.claude/component-ledger/audit.json +1 -1
  2. package/.claude/component-ledger/output.html +1 -1
  3. package/.claude/skills/component-dynamic-slots.md +1 -1
  4. package/.claude/skills/components/header-block.md +92 -0
  5. package/.claude/skills/components/rotating-carousel-image.md +101 -0
  6. package/.claude/skills/components/skip-links.md +82 -0
  7. package/.claude/skills/components/tabs-core.md +187 -0
  8. package/.claude/skills/index.md +4 -0
  9. package/.vscode/srcdev-component-header-block.code-snippets +41 -0
  10. package/.vscode/srcdev-component-rotating-carousel-image.code-snippets +74 -0
  11. package/.vscode/srcdev-component-skip-links.code-snippets +55 -0
  12. package/.vscode/srcdev-component-tabs-core.code-snippets +78 -0
  13. package/app/assets/styles/setup/05.typography/02.utility-classes/_font-classes-page-heading.css +2 -1
  14. package/app/components/01.atoms/animations/rotating-carousel-image/CONSUMER-STYLING.md +54 -0
  15. package/app/components/01.atoms/animations/rotating-carousel-image/RotatingCarouselImage.vue +315 -0
  16. package/app/components/01.atoms/animations/rotating-carousel-image/stories/RotatingCarouselImage.stories.ts +156 -0
  17. package/app/components/01.atoms/animations/rotating-carousel-image/tests/RotatingCarouselImage.spec.ts +230 -0
  18. package/app/components/01.atoms/animations/rotating-carousel-image/tests/__snapshots__/RotatingCarouselImage.spec.ts.snap +19 -0
  19. package/app/components/01.atoms/navigation/skip-links/CONSUMER-STYLING.md +31 -0
  20. package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +92 -0
  21. package/app/components/01.atoms/navigation/skip-links/stories/SkipLinks.stories.ts +113 -0
  22. package/app/components/01.atoms/navigation/skip-links/tests/SkipLinks.spec.ts +71 -0
  23. package/app/components/01.atoms/navigation/skip-links/tests/__snapshots__/SkipLinks.spec.ts.snap +15 -0
  24. package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +99 -0
  25. package/app/components/01.atoms/navigation/tabs/TabsCore.vue +272 -0
  26. package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +199 -0
  27. package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +274 -0
  28. package/app/components/01.atoms/text-blocks/header-block/CONSUMER-STYLING.md +43 -0
  29. package/app/components/01.atoms/text-blocks/header-block/HeaderBlock.vue +50 -0
  30. package/app/components/01.atoms/text-blocks/header-block/stories/HeaderBlock.stories.ts +111 -0
  31. package/app/components/01.atoms/text-blocks/header-block/tests/HeaderBlock.spec.ts +119 -0
  32. package/app/components/01.atoms/text-blocks/header-block/tests/__snapshots__/HeaderBlock.spec.ts.snap +5 -0
  33. package/app/components/03.organisms/site-header/SiteHeader.vue +1 -1
  34. package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +1 -1
  35. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +3 -3
  36. package/app/composables/useTabs.ts +225 -207
  37. package/app/types/components/index.ts +2 -0
  38. package/app/types/components/rotating-carousel-image.d.ts +4 -0
  39. package/app/types/components/skip-links.d.ts +4 -0
  40. package/package.json +1 -1
  41. package/app/components/01.atoms/grids/data-grid/tests/__snapshots__/DataGrid.spec.ts.snap +0 -11
  42. package/app/components/rotating-carousel/RotatingCarouselImage.vue +0 -216
  43. package/app/components/skip-links/SkipLinks.vue +0 -60
  44. package/app/components/tabs/TabsCore.vue +0 -306
  45. package/app/components/typography/HeaderBlock.vue +0 -35
  46. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/AutoGrid.vue +0 -0
  47. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/stories/AutoGrid.stories.ts +0 -0
  48. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/tests/AutoGrid.spec.ts +0 -0
  49. /package/app/components/01.atoms/grids/{data-grid → auto-grid}/tests/__snapshots__/AutoGrid.spec.ts.snap +0 -0
@@ -1,216 +0,0 @@
1
- <template>
2
- <component
3
- :is="tag"
4
- class="rotating-carousel"
5
- :class="[elementClasses]"
6
- :style="`--_rotate-x: ${rotateXProp}deg; --_perspective: ${perspectiveProp}; --_translateZ: ${translateZProp}; --_animation-play-state: ${
7
- pauseOnHover ? 'paused' : 'running'
8
- }`"
9
- ref="carouselRef"
10
- >
11
- <div class="slider" :style="`--quantity: ${Object.keys(data).length}`">
12
- <div v-for="(item, key) in data" :key="key" class="item" :style="`--_position: ${key}`">
13
- <NuxtImg :src="item.src" :alt="item.alt" />
14
- </div>
15
- </div>
16
- </component>
17
- </template>
18
-
19
- <script setup lang="ts">
20
- interface CarouselImageData {
21
- src: string
22
- alt: string
23
- }
24
-
25
- const props = defineProps({
26
- data: {
27
- type: Array as PropType<CarouselImageData[]>,
28
- default: () => [],
29
- },
30
- tag: {
31
- type: String,
32
- default: "div",
33
- validator(value: string) {
34
- return [
35
- "div",
36
- "p",
37
- "span",
38
- "section",
39
- "article",
40
- "aside",
41
- "header",
42
- "footer",
43
- "main",
44
- "nav",
45
- "ul",
46
- "ol",
47
- ].includes(value)
48
- },
49
- },
50
- rotateX: {
51
- type: Number,
52
- default: 0,
53
- },
54
- perspective: {
55
- type: Number,
56
- default: 1000,
57
- },
58
- translateZ: {
59
- type: Number,
60
- default: 1000,
61
- },
62
- pauseOnHover: {
63
- type: Boolean,
64
- default: false,
65
- },
66
- useParallaxEffect: {
67
- type: Boolean,
68
- default: true,
69
- },
70
- styleClassPassthrough: {
71
- type: [String, Array] as PropType<string | string[]>,
72
- default: () => [],
73
- },
74
- })
75
-
76
- const perspectiveProp = computed(() => `${props.perspective.toString()}px`)
77
- const translateZProp = computed(() => `${props.translateZ.toString()}px`)
78
-
79
- const carouselRef = ref<HTMLElement | null>(null)
80
- const rotateXProp = ref(props.rotateX)
81
- const minRotateX = -32
82
- const maxRotateX = 32
83
-
84
- const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
85
-
86
- watch(
87
- () => props.styleClassPassthrough,
88
- () => {
89
- resetElementClasses(props.styleClassPassthrough)
90
- }
91
- )
92
-
93
- watch(
94
- () => props.rotateX,
95
- () => {
96
- if (!props.useParallaxEffect) {
97
- console.log("rotateXProp changed: ", rotateXProp.value)
98
-
99
- rotateXProp.value = props.rotateX
100
- }
101
- }
102
- )
103
-
104
- watch(
105
- () => props.useParallaxEffect,
106
- (currentValue) => {
107
- if (currentValue) {
108
- handleScroll()
109
- window.addEventListener("scroll", handleScroll)
110
- } else {
111
- window.removeEventListener("scroll", handleScroll)
112
- }
113
- }
114
- )
115
-
116
- const handleScroll = () => {
117
- if (!carouselRef.value) return
118
- if ("IntersectionObserver" in window) {
119
- const rect = carouselRef.value.getBoundingClientRect()
120
- const viewportHeight = window.innerHeight
121
-
122
- const elementCenter = rect.top + rect.height / 2
123
- const viewportCenter = viewportHeight / 2
124
- const distanceFromCenter = viewportCenter - elementCenter
125
- const maxDistance = viewportHeight / 2 + rect.height / 2
126
-
127
- const progress = (distanceFromCenter + maxDistance) / (maxDistance * 2)
128
- const clampedProgress = Math.max(0, Math.min(1, progress))
129
-
130
- rotateXProp.value = minRotateX + (maxRotateX - minRotateX) * clampedProgress
131
- }
132
- }
133
-
134
- onMounted(async () => {
135
- if (props.useParallaxEffect) {
136
- handleScroll()
137
- await nextTick()
138
- window.addEventListener("scroll", handleScroll)
139
- }
140
- })
141
-
142
- onUnmounted(() => {
143
- window.removeEventListener("scroll", handleScroll)
144
- })
145
- </script>
146
-
147
- <style lang="css">
148
- @layer components {
149
- /* @property --_rotate-x {
150
- syntax: '<angle>';
151
- inherits: false;
152
- initial-value: 0deg;
153
- }
154
-
155
- @keyframes autoRotateAnimation {
156
- from {
157
- --_rotate-x: -16deg;
158
- }
159
- to {
160
- --_rotate-x: 16deg;
161
- }
162
- } */
163
-
164
- @keyframes autoRun {
165
- from {
166
- transform: perspective(var(--_perspective)) rotateX(var(--_rotate-x)) rotateY(0deg);
167
- }
168
- to {
169
- transform: perspective(var(--_perspective)) rotateX(var(--_rotate-x)) rotateY(360deg);
170
- }
171
- }
172
-
173
- .rotating-carousel {
174
- width: 100%;
175
- height: 70svh;
176
- text-align: center;
177
- overflow: hidden;
178
- position: relative;
179
-
180
- /* padding-block: 800px; */
181
-
182
- /* &.scroll-effect {
183
- animation: autoRotateAnimation;
184
- animation-timeline: view();
185
- } */
186
-
187
- .slider {
188
- position: absolute;
189
- width: 200px;
190
- height: 250px;
191
- bottom: 35%;
192
- left: calc(50% - 100px);
193
- transform-style: preserve-3d;
194
- transform: perspective(var(--_perspective));
195
- animation: autoRun 30s linear infinite;
196
- z-index: 2;
197
-
198
- &:has(.item img:hover) {
199
- animation-play-state: var(--_animation-play-state);
200
- }
201
-
202
- .item {
203
- position: absolute;
204
- inset: 0 0 0 0;
205
- transform: rotateY(calc((var(--_position) - 1) * (360 / var(--quantity)) * 1deg)) translateZ(var(--_translateZ));
206
-
207
- img {
208
- width: 100%;
209
- height: 100%;
210
- object-fit: cover;
211
- }
212
- }
213
- }
214
- }
215
- }
216
- </style>
@@ -1,60 +0,0 @@
1
- <template>
2
- <div class="home-link-navigation">
3
- <div v-if="slots.homeLink" class="home-link">
4
- <slot name="homeLink"></slot>
5
- </div>
6
- <nav class="skip-links-nav" aria-label="Skip navigation">
7
- <a ref="skipLink" href="#main-content" class="skip-link">Skip to main content</a>
8
- <a href="#footer-content" class="skip-link">Skip to footer</a>
9
- </nav>
10
- </div>
11
- </template>
12
-
13
- <script lang="ts" setup>
14
- const slots = useSlots();
15
- </script>
16
-
17
- <style lang="css">
18
- @layer components {
19
- .home-link-navigation {
20
- position: relative;
21
-
22
- .skip-links-nav {
23
- position: absolute;
24
- top: 100%;
25
- left: 0;
26
- z-index: 1000;
27
-
28
- display: flex;
29
- flex-direction: column;
30
- gap: 0.2rem;
31
- background-color: black;
32
- border: 1px solid white;
33
- padding: 0.2rem;
34
- margin: 0;
35
-
36
- /* Hidden off-screen via transform — does NOT break keyboard focusability */
37
- opacity: 0;
38
- transform: translateY(-100%);
39
- transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
40
-
41
- &:focus-within {
42
- opacity: 1;
43
- transform: translateY(0);
44
- }
45
-
46
- .skip-link {
47
- display: block;
48
- color: white;
49
- text-decoration: none;
50
- padding: 0.8rem 1.2rem;
51
- text-wrap: nowrap;
52
-
53
- &:focus-visible {
54
- outline: 2px solid white;
55
- }
56
- }
57
- }
58
- }
59
- }
60
- </style>
@@ -1,306 +0,0 @@
1
- <template>
2
- <div class="tabs-core" :class="`axis-${axis}`">
3
- <ul
4
- ref="tabsNavRef"
5
- role="tablist"
6
- aria-labelledby="channel-name"
7
- class="tabs-list"
8
- :class="[elementClasses]"
9
- @mouseleave="resetHoverToActivePosition()"
10
- >
11
- <li v-for="(index, key) in itemCount" :key="key">
12
- <button
13
- :id="`tab-${key}-trigger`"
14
- :data-tab-index="key"
15
- data-nav-item
16
- role="tab"
17
- aria-selected="false"
18
- class="tabs-list-item"
19
- @click.prevent="navItemClicked($event)"
20
- @mouseover="navItemHovered($event)"
21
- >
22
- <slot :name="`tab-${key}-trigger`"></slot>
23
- </button>
24
- </li>
25
- </ul>
26
- <div class="tab-content-wrapper">
27
- <div
28
- v-for="(item, key) in itemCount"
29
- :id="`tab-${key}-content`"
30
- :key="key"
31
- ref="tabsContentRefs"
32
- class="tab-content"
33
- :aria-labelledby="`tab-${key}-trigger`"
34
- role="region"
35
- aria-hidden="true"
36
- >
37
- <slot :name="`tab-${key}-content`"></slot>
38
- </div>
39
- </div>
40
- </div>
41
- </template>
42
-
43
- <script setup lang="ts">
44
- const props = defineProps({
45
- tag: {
46
- type: String as PropType<string>,
47
- default: "button",
48
- },
49
- axis: {
50
- type: String as PropType<"x" | "y">,
51
- default: "x",
52
- },
53
- transitionDuration: {
54
- type: Number as PropType<number>,
55
- default: 200,
56
- },
57
- itemCount: {
58
- type: Number as PropType<number>,
59
- default: 0,
60
- },
61
- trackHover: {
62
- type: Boolean as PropType<boolean>,
63
- default: true,
64
- },
65
- trackActive: {
66
- type: Boolean as PropType<boolean>,
67
- default: true,
68
- },
69
- trackIndicator: {
70
- type: Boolean as PropType<boolean>,
71
- default: true,
72
- },
73
- styleClassPassthrough: {
74
- type: [String, Array] as PropType<string | string[]>,
75
- default: () => [],
76
- },
77
- });
78
-
79
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
80
-
81
- const tabsNavRef = ref<HTMLElement | null>(null);
82
- const tabsContentRefs = ref<HTMLElement[] | null>(null);
83
-
84
- const { initNavDecorators, navItemClicked, navItemHovered, resetHoverToActivePosition } = useTabs(
85
- props.axis,
86
- tabsNavRef,
87
- tabsContentRefs,
88
- props.transitionDuration
89
- );
90
-
91
- onMounted(() => {
92
- initNavDecorators();
93
- });
94
- </script>
95
-
96
- <style lang="css">
97
- @layer components {
98
- .tabs-core {
99
- .tabs-list {
100
- position: relative;
101
- display: flex;
102
- width: fit-content;
103
- z-index: 1;
104
-
105
- list-style-type: none;
106
- margin: 0;
107
- padding: 0;
108
-
109
- .nav__hovered {
110
- content: "";
111
- position: absolute;
112
- left: 0;
113
- right: 0;
114
- bottom: 0;
115
- top: 0;
116
- scale: var(--_width-hovered, 0.125) 1;
117
- translate: var(--_x-hovered, 0) 0;
118
- transform-origin: left;
119
- transition:
120
- scale var(--_transition-duration),
121
- translate var(--_transition-duration);
122
- z-index: 1;
123
- }
124
-
125
- .nav__active {
126
- content: "";
127
- position: absolute;
128
- left: 0;
129
- right: 0;
130
- bottom: 0;
131
- top: 0;
132
- scale: var(--_width-active, 0.125) 1;
133
- translate: var(--_x-active, 0) 0;
134
- transform-origin: left;
135
- transition:
136
- scale var(--_transition-duration),
137
- translate var(--_transition-duration);
138
- z-index: 2;
139
- }
140
-
141
- .nav__active-indicator {
142
- content: "";
143
- position: absolute;
144
- left: 0;
145
- right: 0;
146
- bottom: 0;
147
- scale: var(--_width-active, 0.125) 1;
148
- translate: var(--_x-active, 0) 0;
149
- transform-origin: left;
150
- transition:
151
- scale var(--_transition-duration),
152
- translate var(--_transition-duration);
153
- z-index: 3;
154
- }
155
-
156
- .tabs-list-item {
157
- opacity: 0.7;
158
- position: relative;
159
- transition: color 100ms;
160
- z-index: 4;
161
-
162
- &:hover {
163
- opacity: 1;
164
- }
165
-
166
- &[aria-selected="true"] {
167
- opacity: 1;
168
- }
169
- }
170
-
171
- border-bottom: var(--_tabs-border-bottom);
172
-
173
- .nav__hovered {
174
- background: light-dark(var(--slate-07), var(--slate-03));
175
- color: light-dark(var(--slate-00), var(--slate-10));
176
- }
177
-
178
- .nav__active {
179
- background: light-dark(var(--slate-10), var(--slate-00));
180
- color: light-dark(var(--slate-00), var(--slate-10));
181
- }
182
-
183
- .nav__active-indicator {
184
- background: light-dark(var(--slate-10), var(--slate-00));
185
- height: 0.4rem;
186
- }
187
-
188
- .tabs-list-item {
189
- background: transparent;
190
- border: 0;
191
- color: light-dark(var(--slate-10), var(--slate-00));
192
- cursor: pointer;
193
- font: inherit;
194
- text-transform: uppercase;
195
- font-weight: 500;
196
- margin: 0;
197
- padding: 1em 2em;
198
-
199
- &:hover {
200
- color: light-dark(var(--slate-00), var(--slate-10));
201
- }
202
-
203
- &[aria-selected="true"] {
204
- color: light-dark(var(--slate-00), var(--slate-10));
205
- }
206
-
207
- &.transitioning {
208
- color: light-dark(var(--slate-00), var(--slate-10));
209
- }
210
- }
211
- }
212
-
213
- .tab-content-wrapper {
214
- display: grid;
215
- grid-template-areas: "element-stack";
216
-
217
- background-color: light-dark(var(--slate-09), var(--slate-10));
218
- border: 0.1rem solid var(--slate-06);
219
- border-start-start-radius: 0;
220
- border-start-end-radius: 0;
221
- border-end-start-radius: 0;
222
- border-end-end-radius: 0;
223
- outline: 0.1rem solid var(--slate-06);
224
-
225
- .tab-content {
226
- grid-area: element-stack;
227
- display: none;
228
- }
229
- }
230
- }
231
-
232
- /*
233
- * Deal with axis-y
234
- **/
235
-
236
- .tabs-core {
237
- &.axis-y {
238
- display: flex;
239
- flex-direction: row;
240
- gap: 2em;
241
-
242
- .tabs-list {
243
- flex-direction: column;
244
-
245
- border-bottom: initial;
246
- border-left: var(--_tabs-border-bottom);
247
- position: relative;
248
-
249
- .tabs-list-item {
250
- text-align: left;
251
- width: 100%;
252
-
253
- &:hover {
254
- /* color: light-dark(var(--slate-00), var(--slate-10)); */
255
- }
256
-
257
- &[aria-selected="true"] {
258
- /* color: var(--_tabs-active-text); */
259
- }
260
-
261
- &.transitioning {
262
- /* color: light-dark(var(--slate-00), var(--slate-10)); */
263
- }
264
- }
265
-
266
- .nav__hovered {
267
- left: 0;
268
- right: initial;
269
- bottom: initial;
270
- top: 0;
271
- height: var(--_y-height);
272
- translate: 0 var(--_y-hovered, 0);
273
- transform-origin: top;
274
- width: var(--_y-width);
275
- }
276
-
277
- .nav__active {
278
- left: 0;
279
- right: initial;
280
- bottom: initial;
281
- top: 0;
282
- height: var(--_y-height);
283
- translate: 0 var(--_y-active, 0);
284
- transform-origin: top;
285
- width: var(--_y-width);
286
- }
287
-
288
- .nav__active-indicator {
289
- left: 0;
290
- right: initial;
291
- bottom: initial;
292
- top: 0;
293
- height: var(--_y-height);
294
- scale: var(--_width-active, 0.125) 1;
295
- translate: 0 var(--_y-active, 0);
296
- transform-origin: top;
297
- width: 0.4em;
298
- }
299
- }
300
- .tab-content-wrapper {
301
- flex-grow: 1;
302
- }
303
- }
304
- }
305
- }
306
- </style>
@@ -1,35 +0,0 @@
1
- <template>
2
- <component :is="tag" :class="[classLevelClass, elementClasses]">
3
- <slot></slot>
4
- </component>
5
- </template>
6
-
7
- <script setup lang="ts">
8
- const props = defineProps<{
9
- tagLevel?: number | string
10
- classLevel?: number | string
11
- styleClassPassthrough?: string | string[]
12
- }>()
13
-
14
- const tagLevel = computed(() => {
15
- const n = Number(props.tagLevel ?? 1)
16
- return [1, 2, 3, 4, 5, 6].includes(n) ? n : 1
17
- })
18
-
19
- const classLevel = computed(() => {
20
- const n = Number(props.classLevel ?? 1)
21
- return [1, 2, 3, 4, 5, 6].includes(n) ? n : 1
22
- })
23
-
24
- const tag = computed(() => `h${tagLevel.value}`)
25
- const classLevelClass = computed(() => `page-heading-${classLevel.value}`)
26
-
27
- const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough ?? [])
28
-
29
- watch(
30
- () => props.styleClassPassthrough,
31
- (newVal) => {
32
- resetElementClasses(newVal ?? [])
33
- }
34
- )
35
- </script>