srcdev-nuxt-components 9.1.41 → 9.1.43

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.
@@ -26,7 +26,9 @@
26
26
  "Bash(node -e ':*)",
27
27
  "Bash(git ls-tree *)",
28
28
  "Bash(node -p \"require\\('./package.json'\\).version\")",
29
- "Bash(git status *)"
29
+ "Bash(git status *)",
30
+ "Bash(cp /Users/simoncornforth/websites/nuxt-components/.claude/skills/components/data-grid.md /Users/simoncornforth/websites/nuxt-components/.claude/skills/components/auto-grid.md)",
31
+ "Bash(rm /Users/simoncornforth/websites/nuxt-components/.claude/skills/components/data-grid.md)"
30
32
  ],
31
33
  "additionalDirectories": []
32
34
  }
@@ -1,8 +1,8 @@
1
- # DataGrid Component
1
+ # AutoGrid Component
2
2
 
3
3
  ## Overview
4
4
 
5
- `DataGrid` is a responsive auto-fit CSS grid wrapper. It renders whatever named slots the consumer provides, auto-fitting columns to a minimum of `250px` each. Column count and gap are controlled via CSS custom properties, making layout adjustments a single-line style override rather than a prop change.
5
+ `AutoGrid` is a responsive auto-fit CSS grid wrapper. It renders whatever named slots the consumer provides, auto-fitting columns to a minimum of `250px` each. Column count and gap are controlled via CSS custom properties, making layout adjustments a single-line style override rather than a prop change.
6
6
 
7
7
  ---
8
8
 
@@ -11,21 +11,21 @@
11
11
  Pass any number of named slots — the component renders each one in document order inside the grid.
12
12
 
13
13
  ```vue
14
- <DataGrid>
14
+ <AutoGrid>
15
15
  <template #item-1><StatCard label="Revenue" value="£24,500" /></template>
16
16
  <template #item-2><StatCard label="Clients" value="142" /></template>
17
17
  <template #item-3><StatCard label="Bookings" value="38" /></template>
18
- </DataGrid>
18
+ </AutoGrid>
19
19
  ```
20
20
 
21
21
  When filling from a data array, use a dynamic slot name in a `v-for`:
22
22
 
23
23
  ```vue
24
- <DataGrid>
24
+ <AutoGrid>
25
25
  <template v-for="(item, i) in stats" #[`item-${i}`] :key="i">
26
26
  <StatCard :label="item.label" :value="item.value" />
27
27
  </template>
28
- </DataGrid>
28
+ </AutoGrid>
29
29
  ```
30
30
 
31
31
  ---
@@ -34,10 +34,10 @@ When filling from a data array, use a dynamic slot name in a `v-for`:
34
34
 
35
35
  > **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:style-class-passthrough`.
36
36
 
37
- | Prop (template form) | Type | Default | Notes |
38
- |---|---|---|---|
39
- | `tag` | `"div" \| "section" \| "article" \| "main"` | `"div"` | Use a semantic tag for page landmark regions. |
40
- | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root element. |
37
+ | Prop (template form) | Type | Default | Notes |
38
+ | -------------------------- | ------------------------------------------- | ------- | --------------------------------------------- |
39
+ | `tag` | `"div" \| "section" \| "article" \| "main"` | `"div"` | Use a semantic tag for page landmark regions. |
40
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes on the root element. |
41
41
 
42
42
  ---
43
43
 
@@ -45,25 +45,27 @@ When filling from a data array, use a dynamic slot name in a `v-for`:
45
45
 
46
46
  Override these via `style` attribute or a `styleClassPassthrough` class in a consuming `<style>` block.
47
47
 
48
- | Property | Default | Notes |
49
- |---|---|---|
50
- | `--data-grid-columns` | `repeat(auto-fit, minmax(250px, 1fr))` | Full `grid-template-columns` value. Override to fix column count or change min width. |
51
- | `--data-grid-gap` | `1rem` | Grid gap between items. |
48
+ | Property | Default | Notes |
49
+ | ------------------------- | -------- | ---------------------------------------------------------------------------------------- |
50
+ | `--auto-grid-min-col-size` | `250px` | Minimum column width; browser auto-fits as many columns as will fit. |
51
+ | `--auto-grid-gap` | `1rem` | Grid gap between items. |
52
52
 
53
53
  ### Fixed column count
54
54
 
55
+ Override `grid-template-columns` directly — there is no single token for this:
56
+
55
57
  ```vue
56
- <DataGrid style="--data-grid-columns: repeat(3, 1fr); --data-grid-gap: 2.4rem;">
58
+ <AutoGrid style="grid-template-columns: repeat(3, 1fr); --auto-grid-gap: 2.4rem;">
57
59
  ...
58
- </DataGrid>
60
+ </AutoGrid>
59
61
  ```
60
62
 
61
63
  ### Narrower minimum item width
62
64
 
63
65
  ```vue
64
- <DataGrid style="--data-grid-columns: repeat(auto-fit, minmax(180px, 1fr));">
66
+ <AutoGrid style="--auto-grid-min-col-size: 180px;">
65
67
  ...
66
- </DataGrid>
68
+ </AutoGrid>
67
69
  ```
68
70
 
69
71
  ---
@@ -73,7 +75,7 @@ Override these via `style` attribute or a `styleClassPassthrough` class in a con
73
75
  ### Stat cards (default auto-fit)
74
76
 
75
77
  ```vue
76
- <DataGrid>
78
+ <AutoGrid>
77
79
  <template #revenue>
78
80
  <div class="stat-card">
79
81
  <span class="stat-card-label">Revenue</span>
@@ -86,17 +88,17 @@ Override these via `style` attribute or a `styleClassPassthrough` class in a con
86
88
  <span class="stat-card-value">142</span>
87
89
  </div>
88
90
  </template>
89
- </DataGrid>
91
+ </AutoGrid>
90
92
  ```
91
93
 
92
94
  ### Semantic section with auto aria-labelledby
93
95
 
94
96
  ```vue
95
- <DataGrid tag="section">
97
+ <AutoGrid tag="section">
96
98
  <!-- aria-labelledby is wired automatically via useAriaLabelledById -->
97
99
  <template #item-1><div>Item 1</div></template>
98
100
  <template #item-2><div>Item 2</div></template>
99
- </DataGrid>
101
+ </AutoGrid>
100
102
  ```
101
103
 
102
104
  ### Data-driven grid
@@ -105,20 +107,20 @@ Override these via `style` attribute or a `styleClassPassthrough` class in a con
105
107
  <script setup lang="ts">
106
108
  const stats = [
107
109
  { id: "revenue", label: "Revenue", value: "£24,500" },
108
- { id: "clients", label: "Clients", value: "142" },
110
+ { id: "clients", label: "Clients", value: "142" },
109
111
  { id: "bookings", label: "Bookings", value: "38" },
110
112
  ];
111
113
  </script>
112
114
 
113
115
  <template>
114
- <DataGrid>
116
+ <AutoGrid>
115
117
  <template v-for="stat in stats" #[stat.id] :key="stat.id">
116
118
  <div class="stat-card">
117
119
  <span class="stat-card-label">{{ stat.label }}</span>
118
120
  <span class="stat-card-value">{{ stat.value }}</span>
119
121
  </div>
120
122
  </template>
121
- </DataGrid>
123
+ </AutoGrid>
122
124
  </template>
123
125
  ```
124
126
 
@@ -137,19 +139,19 @@ See [component-aria-landmark.md](../component-aria-landmark.md) for the full lan
137
139
  ## Local style override scaffold
138
140
 
139
141
  ```vue
140
- <DataGrid :style-class-passthrough="['my-data-grid']">
142
+ <AutoGrid :style-class-passthrough="['my-auto-grid']">
141
143
  ...
142
- </DataGrid>
144
+ </AutoGrid>
143
145
 
144
146
  <style>
145
- /* ─── DataGrid local overrides ──────────────────────────────────────
147
+ /* ─── AutoGrid local overrides ──────────────────────────────────────
146
148
  Use CSS custom properties for layout, not utility classes.
147
149
  Delete this block if no overrides are needed.
148
150
  ─────────────────────────────────────────────────────────────────── */
149
- .data-grid {
150
- &.my-data-grid {
151
- --data-grid-columns: repeat(auto-fit, minmax(200px, 1fr));
152
- --data-grid-gap: 2rem;
151
+ .auto-grid {
152
+ &.my-auto-grid {
153
+ --auto-grid-min-col-size: 200px;
154
+ --auto-grid-gap: 2rem;
153
155
  }
154
156
  }
155
157
  </style>
@@ -163,5 +165,5 @@ See [component-local-style-override.md](../component-local-style-override.md) fo
163
165
 
164
166
  - Auto-imported in Nuxt — no manual import needed.
165
167
  - Slot names can be anything — semantic (`#revenue`) or indexed (`#item-0`). Document order determines render order.
166
- - `--data-grid-columns` accepts any valid `grid-template-columns` value, including named tracks and `subgrid`.
167
- - The component does not impose a column count `auto-fit` with `minmax` means the browser decides. Use `repeat(N, 1fr)` in `--data-grid-columns` to fix the count.
168
+ - `--auto-grid-min-col-size` controls the minimum column width; `auto-fit` fills as many columns as will fit.
169
+ - To fix the column count, override `grid-template-columns` directly (e.g. `style="grid-template-columns: repeat(3, 1fr)"`) there is no single token for this.
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: SamaritanPromptMixed
3
+ description: SamaritanPromptMixed animated text prompt — props, MessageConfig API, typewriter/word-pulse effects, accessibility (aria-live), CSS tokens, consumer styling
4
+ type: reference
5
+ ---
6
+
7
+ # SamaritanPromptMixed
8
+
9
+ ## Overview
10
+
11
+ `SamaritanPromptMixed` cycles through a list of messages using animated text effects. Each message can independently use either a `typewriter` effect (characters typed and deleted one-by-one) or a `word-pulse` effect (full text fades in, holds, then fades out). Effects and timing can be set globally via props and overridden per-message via `MessageConfig`.
12
+
13
+ The component uses `useCancellableTimer` for all async timing — the loop exits cleanly on `onUnmounted` without dangling promises.
14
+
15
+ ### Accessibility
16
+
17
+ - The visual animated content (`.samaritan-prompt__content`) carries `aria-hidden="true"` — screen readers never hear incremental characters.
18
+ - A visually hidden `aria-live="polite"` `aria-atomic="true"` span announces the complete message at the moment it is fully displayed:
19
+ - **Typewriter**: announced after all characters are typed (before the hold/delete phase).
20
+ - **Word-pulse**: announced when `textOpacity` reaches `1` (fade-in complete).
21
+ - `announcedText` is cleared to `""` before each deletion/fade-out, so the same message announced again in the next cycle fires a new announcement (value changes `"" → text`).
22
+ - `prefers-reduced-motion: reduce` disables the content fade transition and the cursor pulse animation via a CSS media query.
23
+
24
+ ## Props
25
+
26
+ | Prop | Type | Default | Description |
27
+ |------|------|---------|-------------|
28
+ | `messageConfigs` | `MessageConfig[]` | — | Array of message objects. **Required.** |
29
+ | `effect` | `"typewriter" \| "word-pulse"` | `"typewriter"` | Default effect for all messages. |
30
+ | `typeSpeed` | `number` | `80` | ms per character typed. |
31
+ | `deleteSpeed` | `number` | `40` | ms per character deleted. |
32
+ | `holdDuration` | `number` | `7000` | ms to hold the fully typed text before deleting. |
33
+ | `pauseDuration` | `number` | `1000` | ms pause after a message completes, before the next begins. |
34
+ | `wordDuration` | `number` | `1200` | ms the word-pulse text stays fully visible. |
35
+ | `fadeDuration` | `number` | `400` | ms for the word-pulse fade-in / fade-out CSS transition. |
36
+ | `introDelay` | `number` | `2000` | ms delay before the first message starts on mount. Set to `0` to start immediately. |
37
+ | `hideCursorInCycle` | `boolean` | `true` | Hide the cursor during text animation; show it during pause/hold. Per-message override available. |
38
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
39
+
40
+ ## MessageConfig
41
+
42
+ Each entry in `messageConfigs` accepts:
43
+
44
+ ```typescript
45
+ interface MessageConfig {
46
+ text: string;
47
+ effect?: "typewriter" | "word-pulse"; // overrides global prop
48
+ typeSpeed?: number;
49
+ deleteSpeed?: number;
50
+ holdDuration?: number;
51
+ pauseDuration?: number;
52
+ wordDuration?: number;
53
+ fadeDuration?: number;
54
+ hideCursorInCycle?: boolean;
55
+ }
56
+ ```
57
+
58
+ Any field omitted falls back to the corresponding component prop.
59
+
60
+ ## Basic usage
61
+
62
+ ```vue
63
+ <SamaritanPromptMixed
64
+ :message-configs="[
65
+ { text: 'INITIALISING SYSTEM' },
66
+ { text: 'LOADING PROFILE' },
67
+ { text: 'ACCESS GRANTED' },
68
+ ]"
69
+ />
70
+ ```
71
+
72
+ ## Mixed effects
73
+
74
+ ```vue
75
+ <SamaritanPromptMixed
76
+ :message-configs="[
77
+ { text: 'INITIALISING SYSTEM', effect: 'typewriter', hold-duration: 3000 },
78
+ { text: 'STAND BY', effect: 'word-pulse', word-duration: 2000 },
79
+ { text: 'CONNECTED', effect: 'typewriter' },
80
+ ]"
81
+ />
82
+ ```
83
+
84
+ ## Fast intro, no delay
85
+
86
+ ```vue
87
+ <SamaritanPromptMixed
88
+ :intro-delay="0"
89
+ :type-speed="50"
90
+ :message-configs="[{ text: 'READY' }]"
91
+ />
92
+ ```
93
+
94
+ ## CSS custom properties
95
+
96
+ | Token | Default | Description |
97
+ |-------|---------|-------------|
98
+ | `--samaritan-font-size` | `2rem` | Text size. |
99
+ | `--samaritan-color-text` | `#ffffff` | Text colour. |
100
+ | `--samaritan-color-underline` | `#ffffff` | Underline bar colour. |
101
+ | `--samaritan-color-cursor` | `#cc0000` | Cursor colour (peak of pulse). |
102
+ | `--samaritan-color-cursor-off` | `transparent` | Cursor colour (trough of pulse). |
103
+ | `--samaritan-font-family` | `"Mono MMM 5", "Nova Mono", "Courier New", monospace` | Font stack. |
104
+ | `--samaritan-letter-spacing` | `0.08em` | Letter spacing. |
105
+
106
+ The custom font `Mono MMM 5` is loaded via `@font-face` inside the component's unscoped `<style>` block from `/fonts/monoMMM_5.ttf`.
107
+
108
+ ## Consumer styling
109
+
110
+ Use an unscoped style block scoped by a page or section wrapper. No `:deep()` needed.
111
+
112
+ ```vue
113
+ <style>
114
+ .my-page .samaritan-prompt {
115
+ --samaritan-font-size: 3.2rem;
116
+ --samaritan-color-text: #00ff88;
117
+ --samaritan-color-cursor: #ff4400;
118
+ }
119
+ </style>
120
+ ```
121
+
122
+ ## CSS classes
123
+
124
+ | Class | Notes |
125
+ |-------|-------|
126
+ | `.samaritan-prompt` | Root element. |
127
+ | `.samaritan-prompt__content` | Wraps text + underline. `aria-hidden="true"`. Opacity transitions on word-pulse effect. |
128
+ | `.samaritan-prompt__stage` | Centers the text span; min-height prevents layout shift. |
129
+ | `.samaritan-prompt__text` | The visible animated text. `text-transform: uppercase`. |
130
+ | `.samaritan-prompt__underline` | Horizontal rule below text. |
131
+ | `.samaritan-prompt__cursor` | `▲` glyph; pulses via `samaritan-pulse` keyframe. `aria-hidden="true"`. |
132
+ | `.samaritan-prompt__sr-text` | Visually hidden `aria-live` region for screen reader announcements. |
133
+
134
+ ## Notes
135
+
136
+ - The `introDelay` only applies at the start of the loop, not between messages. Between messages, `pauseDuration` applies.
137
+ - The cursor pulse animation uses the `samaritan-pulse` keyframe (defined in the component's global style). The cursor hides/shows during cycles via `cursorVisible` + opacity transition, not `display`.
138
+ - `prefers-reduced-motion: reduce` suppresses the opacity transition on `.samaritan-prompt__content` and the `animation` on `.samaritan-prompt__cursor`. The `aria-live` announcements continue to fire regardless.
139
+ - The `MessageConfig` type is exported from the component: `import type { MessageConfig } from 'srcdev-nuxt-components/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue'`.
@@ -0,0 +1,87 @@
1
+ # CSS Animation Utilities
2
+
3
+ ## Overview
4
+
5
+ Scroll-driven animation utility classes bundled with the layer. Apply a class to any element to get a CSS-only, scroll-linked animation — no JavaScript required. All utilities use the [CSS Scroll-Driven Animations](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline) spec (`animation-timeline: view()`).
6
+
7
+ **Browser support**: Chrome 115+, Firefox 110+, Safari 18+. No polyfill exists — use `@supports` for graceful degradation where needed.
8
+
9
+ All utilities are wrapped in `@media (prefers-reduced-motion: no-preference)` — animations are automatically disabled for users who prefer reduced motion.
10
+
11
+ ## Available classes
12
+
13
+ ### `.animation-scroller-x`
14
+
15
+ Scales and fades items relative to their position in a **horizontal scroll container** (carousel, horizontal list). Items at the edges are small and faint; items at the centre are full size and opaque.
16
+
17
+ ```css
18
+ animation-timeline: view(x);
19
+ ```
20
+
21
+ | Position | `opacity` | `scale` |
22
+ |---|---|---|
23
+ | Edges (0 %, 100 %) | 0.25 | 0.5 |
24
+ | Centre (35 %–65 %) | 1 | 1 |
25
+
26
+ #### Usage
27
+
28
+ ```html
29
+ <!-- Scrollable container — overflow-x must be auto or scroll -->
30
+ <div class="carousel-track" style="overflow-x: auto; display: flex;">
31
+ <div class="animation-scroller-x">Item 1</div>
32
+ <div class="animation-scroller-x">Item 2</div>
33
+ <div class="animation-scroller-x">Item 3</div>
34
+ </div>
35
+ ```
36
+
37
+ The animation is driven by the element's position inside its nearest scrollport on the x-axis. Add `.animation-scroller-x` to each **child** — not the container.
38
+
39
+ ---
40
+
41
+ ### `.entry-zoom-reveal`
42
+
43
+ Fades and zooms an element in as it scrolls into the vertical viewport (bottom 30 % inset to top 5 %).
44
+
45
+ ```html
46
+ <div class="entry-zoom-reveal">Content revealed on scroll</div>
47
+ ```
48
+
49
+ - `fill: both` — element stays hidden before entry, visible after exit
50
+
51
+ ---
52
+
53
+ ### `.entry-slide-in`
54
+
55
+ Slides an element up from 200 px below as it scrolls into view.
56
+
57
+ ```html
58
+ <div class="entry-slide-in">Slides up on scroll</div>
59
+ ```
60
+
61
+ ---
62
+
63
+ ### `.entry-exit-blur`
64
+
65
+ Blurs an element as it enters and exits the vertical viewport; sharp in the centre (45 %–55 %).
66
+
67
+ ```html
68
+ <div class="entry-exit-blur">Sharp in view, blurred at edges</div>
69
+ ```
70
+
71
+ ---
72
+
73
+ ### `.auto-rotate`
74
+
75
+ Rotates an element 0 → 360 ° as it scrolls through the vertical viewport.
76
+
77
+ ```html
78
+ <div class="auto-rotate">
79
+ <img src="/logo.svg" alt="" />
80
+ </div>
81
+ ```
82
+
83
+ ## Notes
84
+
85
+ - All classes use `animation-timeline: view()` (vertical) **except** `.animation-scroller-x` which uses `view(x)`.
86
+ - The `scroller` keyframe name used by `.animation-scroller-x` is global — avoid re-declaring `@keyframes scroller` in your own CSS.
87
+ - Utility classes are included automatically when you extend the layer — no explicit import needed in your app.
@@ -34,6 +34,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
34
34
  ├── component-local-style-override.md — styleClassPassthrough + scoped style block for per-usage visual customisation
35
35
  ├── component-prop-driven-container-layout.md — vary CSS grid layout inside @container queries using data-* attribute selectors
36
36
  ├── css-grid-max-width-gutters.md — cap a centre grid column width by growing gutters, with start/center alignment variants
37
+ ├── css-animation-utilities.md — scroll-driven animation utility classes: scroller-x (carousel), entry-zoom-reveal, entry-slide-in, entry-exit-blur, auto-rotate
37
38
  ├── component-aria-landmark.md — useAriaLabelledById composable: aria-labelledby for section/main/article/aside tags
38
39
  ├── component-export-types.md — move inline component types to app/types/components/ barrel for consumer imports
39
40
  ├── component-inline-action-button.md — InputButtonCore variant="inline" pattern for buttons embedded in custom input wrappers
@@ -76,8 +77,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
76
77
  ├── display-qr-code.md — DisplayQrCode: QR code SVG from a string value, colour/size/variant/radius props, currentColor default
77
78
  ├── capture-qr-code.md — CaptureQrCode: live camera scanner, error state, visibility/route/KeepAlive lifecycle, media stream cleanup
78
79
  ├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
79
- ├── data-grid.md — DataGrid: auto-fit responsive grid, $slots iteration, --data-grid-columns/gap tokens, semantic tag + aria
80
- └── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
80
+ ├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-min-col-size/gap tokens, semantic tag + aria
81
+ ├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
82
+ └── samaritan-prompt-mixed.md — SamaritanPromptMixed: animated text prompt, typewriter/word-pulse effects, MessageConfig API, aria-live accessibility, CSS tokens
81
83
  ```
82
84
 
83
85
  ## Skill file template
@@ -0,0 +1,21 @@
1
+ @keyframes scroller {
2
+ 0%,
3
+ 100% {
4
+ opacity: 0.25;
5
+ scale: 0.5;
6
+ }
7
+ 35%,
8
+ 65% {
9
+ opacity: 1;
10
+ scale: 1;
11
+ }
12
+ }
13
+
14
+ /* Used to scroll scale elements when scrolling x-axis like a carousel. */
15
+
16
+ @media (prefers-reduced-motion: no-preference) {
17
+ .animation-scroller-x {
18
+ animation: scroller linear both;
19
+ animation-timeline: view(x);
20
+ }
21
+ }
@@ -7,7 +7,9 @@
7
7
  }
8
8
  }
9
9
 
10
- .auto-rotate {
11
- animation: autoRotateAnimation;
12
- animation-timeline: view();
10
+ @media (prefers-reduced-motion: no-preference) {
11
+ .auto-rotate {
12
+ animation: autoRotateAnimation;
13
+ animation-timeline: view();
14
+ }
13
15
  }
@@ -1,6 +1,8 @@
1
- .entry-exit-blur {
2
- animation: entryExitBlurAnimation linear both;
3
- animation-timeline: view();
1
+ @media (prefers-reduced-motion: no-preference) {
2
+ .entry-exit-blur {
3
+ animation: entryExitBlurAnimation linear both;
4
+ animation-timeline: view();
5
+ }
4
6
  }
5
7
  @keyframes entryExitBlurAnimation {
6
8
  0% {
@@ -9,7 +9,9 @@
9
9
  }
10
10
  }
11
11
 
12
- .entry-zoom-reveal {
13
- animation: entryZoomRevealAnimation both;
14
- animation-timeline: view(70% 5%);
12
+ @media (prefers-reduced-motion: no-preference) {
13
+ .entry-zoom-reveal {
14
+ animation: entryZoomRevealAnimation both;
15
+ animation-timeline: view(70% 5%);
16
+ }
15
17
  }
@@ -1,4 +1,5 @@
1
- @import './_entry-zoom-reveal';
2
- @import './_entry-slide-in';
3
- @import './_entry-exit-blur';
4
- @import './_auto-rotate';
1
+ @import "./_entry-zoom-reveal";
2
+ @import "./_entry-slide-in";
3
+ @import "./_entry-exit-blur";
4
+ @import "./_auto-rotate";
5
+ @import "./_animation-scroller-x.css";
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <component :is="tag" class="auto-grid" :class="[elementClasses, { 'is-responsive': isResponsive }]" :aria-labelledby="ariaLabelledby">
3
+ <slot v-for="(_, name) in $slots" :key="name" :name="name"></slot>
4
+ </component>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ interface Props {
9
+ tag?: "div" | "section" | "article" | "main";
10
+ isResponsive?: boolean;
11
+ styleClassPassthrough?: string | string[];
12
+ }
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ tag: "div",
16
+ isResponsive: false,
17
+ styleClassPassthrough: () => [],
18
+ });
19
+
20
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
21
+ const { ariaLabelledby } = useAriaLabelledById(props.tag);
22
+
23
+ watch(
24
+ () => props.styleClassPassthrough,
25
+ () => resetElementClasses(props.styleClassPassthrough)
26
+ );
27
+ </script>
28
+
29
+ <style lang="css">
30
+ @layer components {
31
+ .auto-grid {
32
+ --auto-grid-min-col-size-small: 250px;
33
+ --auto-grid-min-col-size-default: 300px;
34
+ --auto-grid-min-col-size-large: 350px;
35
+ --auto-grid-gap: 1rem;
36
+
37
+ display: grid;
38
+ gap: var(--auto-grid-gap);
39
+
40
+ &:not(.is-responsive) {
41
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--auto-grid-min-col-size-default), 100%), 1fr));
42
+ }
43
+
44
+ &.is-responsive {
45
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--auto-grid-min-col-size-small), 100%), 1fr));
46
+
47
+ @container (width >= 768px) {
48
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--auto-grid-min-col-size-default), 100%), 1fr));
49
+ }
50
+
51
+ @container (width >= 1024px) {
52
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--auto-grid-min-col-size-large), 100%), 1fr));
53
+ }
54
+ }
55
+ }
56
+ }
57
+ </style>