srcdev-nuxt-components 9.6.19 → 9.6.20
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.
- package/.claude/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/skills/component-compliance-checklist.md +5 -0
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/index.md +12 -12
- package/.claude/skills/storybook-add-story.md +25 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
package/.claude/skills/index.md
CHANGED
|
@@ -86,7 +86,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
86
86
|
├── eyebrow-text.md — EyebrowText props, usage patterns, styling
|
|
87
87
|
├── hero-text.md — HeroText props, usage patterns, styling
|
|
88
88
|
├── layout-grid-by-cols.md — LayoutGridByCols dynamic slots (item-{n}), column/gap/collapse props that write public --layout-grid-by-cols-* tokens (CSS-settable), section label
|
|
89
|
-
├── page-row.md — PageRow layout primitive: CSS grid named lines, nesting pattern, align prop, aria-labelledby, CSS token API
|
|
89
|
+
├── page-row.md — PageRow layout primitive: CSS grid named lines, nesting pattern, align prop, data-variant hook, aria-labelledby, CSS token API
|
|
90
90
|
├── link-text.md — LinkText props, slots, usage patterns, styling
|
|
91
91
|
├── header-block.md — HeaderBlock: tagLevel/classLevel semantic-vs-visual heading decoupling, page-heading-N utility classes
|
|
92
92
|
├── text-block.md — TextBlock: vertical-rhythm text wrapper, --text-block-padding-block-start/-end tokens, heading-id slot prop for section/article aria-labelledby
|
|
@@ -120,9 +120,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
120
120
|
├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
|
|
121
121
|
├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names (dark ones vanish on dark backgrounds; mono alternatives), showLabel + scoped icon/label slots, per-item brandColour (set brand-colour tokens on .social-icons-list-item), tile/circle/label tokens, four recipes (mono tiles, dark dock, pills, overlapping stack), unsafe hrefs skipped
|
|
122
122
|
├── display-qr-code.md — DisplayQrCode: QR code SVG from a string value, colour/size/variant/radius props, currentColor default
|
|
123
|
-
├── capture-qr-code.md — CaptureQrCode: live camera scanner, error
|
|
124
|
-
├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
|
|
125
|
-
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-gap/min-col-size-{small,default,large} tokens, is-responsive, semantic tag + aria
|
|
123
|
+
├── capture-qr-code.md — CaptureQrCode: live camera scanner, localisable errorMessages, error slot, restarts when the tab is shown again, own-root media stream cleanup, wrapping results
|
|
124
|
+
├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, no-result/read-error messages, data-dropping state hook, CSS override points
|
|
125
|
+
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-gap/min-col-size-{small,default,large} tokens, is-responsive (data-responsive hook), semantic tag + aria
|
|
126
126
|
├── display-avatar.md — DisplayAvatar (02.molecules): circular avatar with image/initials fallback (alt announced), broken-image fallback, data-size scale tokens, image-size, --display-avatar-* colours, chip badge, icon slot
|
|
127
127
|
├── profile-section.md — ProfileSection (02.molecules): picture + numbered profile-info-N slots, eyebrow/hero header (above, or over the picture top/bottom on a scrim via header-placement), profileLinks, container-query two-column layout, .location/.services highlight tokens
|
|
128
128
|
├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping SegmentedControl, wired to useSettingsStore, labels/icons props, small sizing variant
|
|
@@ -132,18 +132,18 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
132
132
|
├── display-dialog.md — DisplayDialog: native <dialog> overlay, 5 variants (dialog/modal/confirm/alert/fullscreen), useDialogControls integration, InputButton close button, viewport-capped panel with wrapping title/content/footer, aria-label fallthrough when untitled, CSS token API
|
|
133
133
|
├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
|
|
134
134
|
├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, --display-pill-* tokens (variants fall back to base colour/border/ring tokens); data-size/data-variant/data-reversed hooks (2026-10-08, were bare classes), label line-clamp token, -02 ring and outlined-monochrome recipes, StressTest story
|
|
135
|
-
├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
|
|
136
|
-
├── samaritan-prompt.md — SamaritanPrompt + SamaritanPromptMixed: animated text prompts, typewriter/word-pulse effects, MessageConfig API, pause on hover, aria-live, CSS tokens
|
|
135
|
+
├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay/track-edges), button align/offset/edge-inset tokens, InputButton prev/next, prefixed classes + data-* state hooks, empty/single-item handling, CSS tokens
|
|
136
|
+
├── samaritan-prompt.md — SamaritanPrompt + SamaritanPromptMixed: animated text prompts, typewriter/word-pulse effects, MessageConfig API, grapheme-safe typing, instant typing under reduced motion, pause on hover, aria-live, theme-default colours, wrapping + line-clamp, CSS tokens
|
|
137
137
|
├── display-toast.md — DisplayToast (standalone v-model) + DisplayToastProvider + useToastQueue (app-wide queue): stacking, FLIP dismiss, maxVisible, SemanticTheme × 4, masked SVG glass variant
|
|
138
138
|
├── display-prompt.md — DisplayPrompt: inline notification banner, SemanticTheme × 4, local vs parent-controlled dismiss, outlined modifier, CSS token override
|
|
139
139
|
├── expanding-panel-classic.md — ExpandingPanelClassic: grid-template-rows animation (no Baseline-2025 dependency), same API as ExpandingPanel, cross-browser animation parity trade-off
|
|
140
|
-
├── site-header.md — SiteHeader: PageRow + SkipLinks + ResponsiveHeader composition, #branding/#secondaryNavigation slots, dual styleClassPassthrough hooks
|
|
140
|
+
├── site-header.md — SiteHeader: PageRow + SkipLinks + ResponsiveHeader composition, #branding/#secondaryNavigation slots, dual styleClassPassthrough hooks, sticky on the outer row, capped branding column
|
|
141
141
|
├── responsive-header.md — ResponsiveHeader: overflow-collapsing adaptive nav, measurement-pipeline gotchas (unsized icons, vw font-size drift), full CSS token API
|
|
142
142
|
├── navigation-items.md — NavigationItems: internal overflow-panel renderer for ResponsiveHeader, complement-visibility logic, not used standalone
|
|
143
143
|
├── cookie-consent-banner.md — CookieConsentBanner: fixed non-modal Accept/Reject banner built on AlertContent, driven by useCookieConsent; title/message slots, acceptLabel/rejectLabel/icon/showIcon/ariaLabel props, InputButton (inline) actions, position/button tokens (panel via --alert-content-*), teleported so tokens go on html/passthrough
|
|
144
144
|
├── display-banner.md — DisplayBanner: canvas/content stacked overlay banner, conditional slot wrappers, CSS token API
|
|
145
|
-
├── deep-expanding-menu.md — DeepExpandingMenu: anchor-positioned popover nav panels, browser support caveat, CSS token API
|
|
146
|
-
├── deep-expanding-menu-classic.md — DeepExpandingMenuClassic: <details>-based fallback nav, click-
|
|
145
|
+
├── deep-expanding-menu.md — DeepExpandingMenu: anchor-positioned popover nav panels, browser support caveat, ariaLabel, prefixed classes, wrapping/flip/scroll stress handling, CSS token API
|
|
146
|
+
├── deep-expanding-menu-classic.md — DeepExpandingMenuClassic: <details>-based fallback nav, per-instance exclusive groups, outside-click/Escape/link-follow close, ariaLabel, prefixed classes, container-width panels, CSS token API
|
|
147
147
|
├── display-tooltip.md — DisplayTooltip: anchor-positioned popover trigger, browser support caveat, triggerIcon prop, type="button" trigger, hideTrigger removes it from tab order (data-hidden), CSS token API
|
|
148
148
|
├── display-tooltip-rich.md — DisplayTooltipRich (Material 3 rich tooltip, renamed from DisplayTooltipDefined 2026-10-18): structured title/body/action content with InputButton close button, per-field line-clamp tokens, composes DisplayTooltip
|
|
149
149
|
├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
|
|
@@ -169,13 +169,13 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
169
169
|
├── sliding-toggle.md — SlidingToggle: single-choice segmented radiogroup with text labels of any width, measured sliding + hover indicators (useSlidingIndicator), roving tabindex/arrow keys, corner-shape token, CSS token API
|
|
170
170
|
├── entry-animation.md — EntryAnimation: scroll-driven entry animation wrapper (slide-in/zoom-reveal/exit-blur utility classes), skipAnimation for above-the-fold loop items, reduced-motion handled at the CSS layer
|
|
171
171
|
├── column-flow-grid.md — ColumnFlowGrid (renamed from MasonryGrid): CSS multi-column text-flow layout, named dynamic slots (no count/data prop), itemMinWidth/gap/unit sizing, CSS token API; not a true masonry, see "which one do I want?"
|
|
172
|
-
├── masonry-grid.md — MasonryGrid: real measured-height masonry (greedy shortest-column packing, animated resize) absorbed from the now-retired MasonryGridOrdered; named dynamic slots, fixedWidth/justify, CSS token API
|
|
172
|
+
├── masonry-grid.md — MasonryGrid: real measured-height masonry (greedy shortest-column packing, animated resize, re-packs on any tile resize) absorbed from the now-retired MasonryGridOrdered; named dynamic slots, fixedWidth/justify, CSS token API
|
|
173
173
|
├── glowing-border.md — GlowingBorder: animated conic-gradient glow border, 5 colour variants, tag prop, full CSS token API for width/radius/surface/duration/per-variant colour stops, reduced-motion guard
|
|
174
174
|
├── sparkle-effect.md — SparkleEffect: slot wrapper sending sparkles along its rounded border (offset-path) plus a glow on hover/focus-visible, count prop (1 to 12, preset table), continuous prop (loop while hovered/focused), radius prop/token, blur token, disabled suppression, reduced-motion and old-Safari glow-only fallback, CSS token API
|
|
175
175
|
├── section-parallax.md — SectionParallax: CSS fixed-background parallax section, iOS Safari limitation, when to use vs ScrollRevealImage, CSS token API, reduced-motion guard
|
|
176
176
|
├── container-glow.md — ContainerGlow (renamed from ContainerGlowCore): pointer-proximity glow-border cards, named dynamic slots (one card per slot), config-prop-driven layout/interaction, full CSS token API for static visuals, reduced-motion guard
|
|
177
|
-
├── dashboard-quad-grid.md — DashboardQuadGrid (renamed from LayoutGridA): fixed 4-slot dashboard grid, container-query breakpoints, CSS token API
|
|
178
|
-
└── dashboard-stats-grid.md — DashboardStatsGrid (renamed from LayoutGridB): top-row panel cluster + bottom-row panel strip dashboard grid, topRowSlot1ItemCount/bottomRowItemCount, CSS token API
|
|
177
|
+
├── dashboard-quad-grid.md — DashboardQuadGrid (renamed from LayoutGridA): fixed 4-slot dashboard grid, prefixed cell classes, container-query breakpoints, CSS token API
|
|
178
|
+
└── dashboard-stats-grid.md — DashboardStatsGrid (renamed from LayoutGridB): top-row panel cluster + bottom-row panel strip dashboard grid, topRowSlot1ItemCount/bottomRowItemCount (floored, clamped at 0), prefixed classes, CSS token API
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
## Skill file template
|
|
@@ -313,6 +313,31 @@ prop and try to pass it. Make it unmistakable:
|
|
|
313
313
|
|
|
314
314
|
`PriceList.stories.ts` and `GoogleReviews.stories.ts` (line-clamp controls) follow this.
|
|
315
315
|
|
|
316
|
+
## Where shared story helpers can live
|
|
317
|
+
|
|
318
|
+
Nowhere shared, so far. Stories ship in the package (`app/` is in `package.json` `files`) but
|
|
319
|
+
`.storybook/` doesn't, so a story importing from `.storybook/` breaks in a consumer's type-check;
|
|
320
|
+
and any `.ts` under `app/components/` is registered as a component (`pathPrefix: false` scan).
|
|
321
|
+
Repeat small fixtures per story instead (the grid `StressTest` stories, 2026-10-21, each carry their
|
|
322
|
+
own oversized-content block).
|
|
323
|
+
|
|
324
|
+
## Simulating hardware or library events in a stress story
|
|
325
|
+
|
|
326
|
+
When a component's data only arrives from hardware or a third-party child (a camera, a file
|
|
327
|
+
decoder), a story can't feed it worst-case data through props. Add story-only buttons that call the
|
|
328
|
+
component's own handlers through its internal instance, and say so in the story text:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const cmpRef = ref<ComponentPublicInstance | null>(null);
|
|
332
|
+
const internals = () =>
|
|
333
|
+
(cmpRef.value?.$ as unknown as { setupState: { onDetect: (codes: { rawValue: string }[]) => void } } | undefined)
|
|
334
|
+
?.setupState;
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
`setupState` is internal (not on the public type, hence the cast) but present in every build.
|
|
338
|
+
Don't `defineExpose` handlers just for a story. `CaptureQrCode` and `DecodeQrCode` (2026-10-21)
|
|
339
|
+
follow this.
|
|
340
|
+
|
|
316
341
|
## Dynamic slot names in template strings
|
|
317
342
|
|
|
318
343
|
A dynamic slot argument can't contain spaces: `` #[`item-${i + 6}-summary`] `` is cut at the first space
|
|
@@ -346,6 +346,32 @@ be resolved from the ends and never reaches it. Mount with two items sharing the
|
|
|
346
346
|
then `setProps` with the first and last items swapped, and assert the rendered labels in order
|
|
347
347
|
(`SkipLinks.spec.ts`). With an `href`-only key that test rendered five links instead of four.
|
|
348
348
|
|
|
349
|
+
## Stub third-party children that hold shared state
|
|
350
|
+
|
|
351
|
+
When a component renders a library child whose behaviour the spec doesn't need (the spec drives
|
|
352
|
+
the parent's own handlers), stub the child in the mount helper. Unmounting doesn't isolate a
|
|
353
|
+
library that keeps module-level state: `QrcodeStream` queues every camera start/stop on one shared
|
|
354
|
+
promise chain, so a start left from an earlier test failed mid-test and emitted `error` into the
|
|
355
|
+
next `CaptureQrCode`, but only under full-suite load and only with `SRCDEV_STANDALONE=true`
|
|
356
|
+
(2026-10-21).
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
const qrcodeStreamStub = { name: "QrcodeStream", emits: ["detect", "error"], template: `<div class="qrcode-stream-stub"></div>` };
|
|
360
|
+
|
|
361
|
+
const mountCapture = async (options: CaptureMountOptions = {}) => {
|
|
362
|
+
const wrapper = await mountSuspended(CaptureQrCode, {
|
|
363
|
+
...options,
|
|
364
|
+
global: { ...options?.global, stubs: { QrcodeStream: qrcodeStreamStub, ...options?.global?.stubs } },
|
|
365
|
+
});
|
|
366
|
+
mounted.push(wrapper);
|
|
367
|
+
return wrapper;
|
|
368
|
+
};
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Assert against the stub's class, not the component's tag name (see
|
|
372
|
+
`.claude/rules/test-no-vacuous-assertions.md`). Run the spec with `npm run test:run` so
|
|
373
|
+
standalone-only modules load.
|
|
374
|
+
|
|
349
375
|
## Mocking browser APIs
|
|
350
376
|
|
|
351
377
|
### Global constructors (ResizeObserver, IntersectionObserver, etc.)
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
"description": "CaptureQrCode live camera scanner with localisable copy",
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
|
-
"<CaptureQrCode
|
|
6
|
+
"<CaptureQrCode",
|
|
7
|
+
" camera-stopped-label=\"${1:Camera stopped}\"",
|
|
8
|
+
" reset-camera-label=\"${2:Reset camera}\"",
|
|
9
|
+
" :error-messages=\"{ NotAllowedError: '${3:Camera access was blocked.}', default: '${4:The camera could not be started.}' }\"",
|
|
10
|
+
"/>"
|
|
7
11
|
]
|
|
8
12
|
},
|
|
9
13
|
"SRCDEV CaptureQrCode Custom Error": {
|
|
@@ -22,7 +26,12 @@
|
|
|
22
26
|
"description": "DecodeQrCode image upload / drop decoder",
|
|
23
27
|
"scope": "vue,html",
|
|
24
28
|
"body": [
|
|
25
|
-
"<DecodeQrCode
|
|
29
|
+
"<DecodeQrCode",
|
|
30
|
+
" upload-label=\"${1:Upload a QR code image}\"",
|
|
31
|
+
" drop-label=\"${2:Or drop a QR code image here}\"",
|
|
32
|
+
" no-result-label=\"${3:No QR code was found in that image.}\"",
|
|
33
|
+
" read-error-label=\"${4:That file couldn't be read as an image.}\"",
|
|
34
|
+
"/>"
|
|
26
35
|
]
|
|
27
36
|
}
|
|
28
37
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "CarouselFlip with looped item slots",
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
|
-
"<CarouselFlip :carousel-data-ids=\"$1items.map((item) => item.id)\">",
|
|
6
|
+
"<CarouselFlip :carousel-data-ids=\"$1items.map((item) => item.id)\" aria-label=\"${4:Image carousel}\">",
|
|
7
7
|
" <template v-for=\"$2item in $1items\" :key=\"$2item.id\" #[$2item.id]>",
|
|
8
8
|
" $3",
|
|
9
9
|
" </template>",
|
|
@@ -25,15 +25,33 @@
|
|
|
25
25
|
"</CarouselFlip>"
|
|
26
26
|
]
|
|
27
27
|
},
|
|
28
|
+
"SRCDEV CarouselFlip Track Edges": {
|
|
29
|
+
"description": "CarouselFlip with prev/next overlaid on the ends of the visible track",
|
|
30
|
+
"scope": "vue,html",
|
|
31
|
+
"body": [
|
|
32
|
+
"<CarouselFlip",
|
|
33
|
+
" :carousel-data-ids=\"$1items.map((item) => item.id)\"",
|
|
34
|
+
" button-layout=\"track-edges\"",
|
|
35
|
+
" :allow-carousel-overflow=\"true\"",
|
|
36
|
+
">",
|
|
37
|
+
" <template v-for=\"$2item in $1items\" :key=\"$2item.id\" #[$2item.id]>",
|
|
38
|
+
" $3",
|
|
39
|
+
" </template>",
|
|
40
|
+
"</CarouselFlip>"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
28
43
|
"SRCDEV CarouselFlip CSS Override": {
|
|
29
44
|
"description": "CSS override scaffold for CarouselFlip tokens",
|
|
30
45
|
"scope": "css",
|
|
31
46
|
"body": [
|
|
32
47
|
".$1my-page {",
|
|
33
48
|
" .carousel-flip {",
|
|
34
|
-
" --carousel-flip-gap: $2;",
|
|
35
|
-
" --carousel-flip-marker-active-colour: $3;",
|
|
36
|
-
" --carousel-flip-button-background-colour: $4;",
|
|
49
|
+
" --carousel-flip-gap: ${2:1rem};",
|
|
50
|
+
" --carousel-flip-marker-active-colour: ${3:var(--theme-surface)};",
|
|
51
|
+
" --carousel-flip-button-background-colour: ${4:var(--theme-surface-subtle)};",
|
|
52
|
+
" --carousel-flip-button-icon-colour: ${5:var(--theme-text)};",
|
|
53
|
+
" --carousel-flip-button-align: ${6:center};",
|
|
54
|
+
" --carousel-flip-button-inline-offset: ${7:0px};",
|
|
37
55
|
" }",
|
|
38
56
|
"}"
|
|
39
57
|
]
|
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"SRCDEV DeepExpandingMenuClassic Basic": {
|
|
3
|
-
"description": "DeepExpandingMenuClassic — <details>-based
|
|
3
|
+
"description": "DeepExpandingMenuClassic — <details>-based nav",
|
|
4
4
|
"scope": "vue,html",
|
|
5
|
+
"prefix": "srcdev-deep-expanding-menu-classic",
|
|
5
6
|
"body": [
|
|
6
|
-
"<DeepExpandingMenuClassic :nav-links=\"$
|
|
7
|
+
"<DeepExpandingMenuClassic :nav-links=\"${1:navLinks}\" aria-label=\"${2:Main}\" />"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"SRCDEV DeepExpandingMenuClassic Nav Links": {
|
|
11
|
+
"description": "DeepExpandingMenuClassic — navLinks data (ResponsiveHeaderNavItem[])",
|
|
12
|
+
"scope": "typescript,vue",
|
|
13
|
+
"prefix": "srcdev-deep-expanding-menu-classic-links",
|
|
14
|
+
"body": [
|
|
15
|
+
"const ${1:navLinks}: ResponsiveHeaderNavItem[] = [",
|
|
16
|
+
" { name: \"${2:Home}\", path: \"${3:/}\" },",
|
|
17
|
+
" {",
|
|
18
|
+
" name: \"${4:Services}\",",
|
|
19
|
+
" childLinksTitle: \"${5:Our services}\",",
|
|
20
|
+
" childLinks: [",
|
|
21
|
+
" { name: \"${6:Haircuts}\", path: \"${7:/services/haircuts}\" },",
|
|
22
|
+
" ],",
|
|
23
|
+
" },",
|
|
24
|
+
"];"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"SRCDEV DeepExpandingMenuClassic Style Override": {
|
|
28
|
+
"description": "DeepExpandingMenuClassic — unlayered token override scaffold",
|
|
29
|
+
"scope": "css,vue",
|
|
30
|
+
"prefix": "srcdev-deep-expanding-menu-classic-css",
|
|
31
|
+
"body": [
|
|
32
|
+
".${1:site-nav} {",
|
|
33
|
+
" --deep-expanding-menu-classic-item-gap: ${2:2.4rem};",
|
|
34
|
+
" --deep-expanding-menu-classic-panel-background-colour: ${3:var(--theme-surface-subtle)};",
|
|
35
|
+
" --deep-expanding-menu-classic-panel-heading-line-clamp: ${4:none};",
|
|
36
|
+
"}"
|
|
7
37
|
]
|
|
8
38
|
}
|
|
9
39
|
}
|
|
@@ -2,8 +2,38 @@
|
|
|
2
2
|
"SRCDEV DeepExpandingMenu Basic": {
|
|
3
3
|
"description": "DeepExpandingMenu — anchor-positioned popover nav panels",
|
|
4
4
|
"scope": "vue,html",
|
|
5
|
+
"prefix": "srcdev-deep-expanding-menu",
|
|
5
6
|
"body": [
|
|
6
|
-
"<DeepExpandingMenu :nav-links=\"$
|
|
7
|
+
"<DeepExpandingMenu :nav-links=\"${1:navLinks}\" aria-label=\"${2:Main}\" />"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"SRCDEV DeepExpandingMenu Nav Links": {
|
|
11
|
+
"description": "DeepExpandingMenu — navLinks data (ResponsiveHeaderNavItem[])",
|
|
12
|
+
"scope": "typescript,vue",
|
|
13
|
+
"prefix": "srcdev-deep-expanding-menu-links",
|
|
14
|
+
"body": [
|
|
15
|
+
"const ${1:navLinks}: ResponsiveHeaderNavItem[] = [",
|
|
16
|
+
" { name: \"${2:Home}\", path: \"${3:/}\" },",
|
|
17
|
+
" {",
|
|
18
|
+
" name: \"${4:Services}\",",
|
|
19
|
+
" childLinksTitle: \"${5:Our services}\",",
|
|
20
|
+
" childLinks: [",
|
|
21
|
+
" { name: \"${6:Haircuts}\", path: \"${7:/services/haircuts}\" },",
|
|
22
|
+
" ],",
|
|
23
|
+
" },",
|
|
24
|
+
"];"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"SRCDEV DeepExpandingMenu Style Override": {
|
|
28
|
+
"description": "DeepExpandingMenu — unlayered token override scaffold",
|
|
29
|
+
"scope": "css,vue",
|
|
30
|
+
"prefix": "srcdev-deep-expanding-menu-css",
|
|
31
|
+
"body": [
|
|
32
|
+
".${1:site-nav} {",
|
|
33
|
+
" --deep-expanding-menu-gap: ${2:2.4rem};",
|
|
34
|
+
" --deep-expanding-menu-panel-background-colour: ${3:var(--theme-surface-subtle)};",
|
|
35
|
+
" --deep-expanding-menu-panel-heading-line-clamp: ${4:none};",
|
|
36
|
+
"}"
|
|
7
37
|
]
|
|
8
38
|
}
|
|
9
39
|
}
|
|
@@ -48,13 +48,14 @@
|
|
|
48
48
|
]
|
|
49
49
|
},
|
|
50
50
|
"SRCDEV SamaritanPrompt Tokens": {
|
|
51
|
-
"description": "SamaritanPrompt / SamaritanPromptMixed CSS token overrides",
|
|
51
|
+
"description": "SamaritanPrompt / SamaritanPromptMixed CSS token overrides (classic white-on-black look)",
|
|
52
52
|
"scope": "css,vue",
|
|
53
53
|
"body": [
|
|
54
54
|
"--samaritan-prompt-font-size: ${1:2rem};",
|
|
55
|
-
"--samaritan-prompt-text-colour: ${2
|
|
56
|
-
"--samaritan-prompt-underline-colour: ${3
|
|
57
|
-
"--samaritan-prompt-cursor-colour: ${4:#cc0000};"
|
|
55
|
+
"--samaritan-prompt-text-colour: ${2:#fff};",
|
|
56
|
+
"--samaritan-prompt-underline-colour: ${3:#fff};",
|
|
57
|
+
"--samaritan-prompt-cursor-colour: ${4:#cc0000};",
|
|
58
|
+
"--samaritan-prompt-text-line-clamp: ${5:none};"
|
|
58
59
|
]
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -14,6 +14,18 @@
|
|
|
14
14
|
"</SiteHeader>"
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
|
+
"SRCDEV SiteHeader Sticky Tokens": {
|
|
18
|
+
"description": "SiteHeader sticky + layout token overrides (set on the styleClassPassthrough class)",
|
|
19
|
+
"scope": "css",
|
|
20
|
+
"body": [
|
|
21
|
+
".${1:header} {",
|
|
22
|
+
" --site-header-position: ${2:sticky};",
|
|
23
|
+
" --site-header-sticky-offset: ${3:0px};",
|
|
24
|
+
" --site-header-bg: ${4:var(--theme-surface-subtle)};",
|
|
25
|
+
" --site-header-branding-max-width: ${5:50%};",
|
|
26
|
+
"}"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
17
29
|
"SRCDEV SiteHeader With Secondary Navigation": {
|
|
18
30
|
"description": "SiteHeader with the secondaryNavigation slot (e.g. a settings icon link)",
|
|
19
31
|
"scope": "vue,html",
|
|
@@ -36,7 +48,7 @@
|
|
|
36
48
|
"body": [
|
|
37
49
|
"<SiteHeader",
|
|
38
50
|
" :responsive-nav-links=\"$1responsiveNavLinks\"",
|
|
39
|
-
" home-nav-aria-label=\"${2:Home
|
|
51
|
+
" home-nav-aria-label=\"${2:Home navigation}\"",
|
|
40
52
|
" main-nav-aria-label=\"${3:Main navigation}\"",
|
|
41
53
|
" secondary-nav-aria-label=\"${4:Secondary navigation}\"",
|
|
42
54
|
" overflow-menu-aria-label=\"${5:Overflow navigation menu}\"",
|
package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
| `--dashboard-quad-grid-gap` | `2rem` | Gap between the four slot panels |
|
|
8
8
|
| `--dashboard-quad-grid-padding` | `2rem` | Padding inside each slot panel |
|
|
9
9
|
| `--dashboard-quad-grid-outline-width` | `0.1rem` | Slot panel outline width |
|
|
10
|
-
| `--dashboard-quad-grid-outline-colour` | `
|
|
10
|
+
| `--dashboard-quad-grid-outline-colour` | `var(--theme-border)` | Slot panel outline colour |
|
|
11
11
|
| `--dashboard-quad-grid-outline-offset` | `0` | Slot panel outline offset |
|
|
12
12
|
| `--dashboard-quad-grid-border-width` | `0` | Slot panel border width (off by default — the panel edge is drawn by the outline) |
|
|
13
|
-
| `--dashboard-quad-grid-border-colour` | `
|
|
13
|
+
| `--dashboard-quad-grid-border-colour` | `var(--theme-border)` | Slot panel border colour, only visible once `--dashboard-quad-grid-border-width` is set |
|
|
14
14
|
| `--dashboard-quad-grid-border-radius` | `0.5rem` | Slot panel corner radius |
|
|
15
15
|
| `--dashboard-quad-grid-rule-width` | `0.1rem` | Width of the grid gap rule line (CSS Gap Decorations — see Notes) |
|
|
16
16
|
| `--dashboard-quad-grid-rule-colour` | `transparent` | Colour of the grid gap rule line, invisible by default |
|
|
@@ -31,6 +31,16 @@ Or scope to a single instance via `styleClassPassthrough`:
|
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
+
## State hooks
|
|
35
|
+
|
|
36
|
+
No state attributes. Element classes: `.dashboard-quad-grid-wrapper` (root, the container),
|
|
37
|
+
`.dashboard-quad-grid` (the grid), `.dashboard-quad-grid-cell` (each of the four cells) with
|
|
38
|
+
`.dashboard-quad-grid-slot-1` to `-slot-4`. Cells wrap their content and the columns are
|
|
39
|
+
`minmax(0, auto)`, so an unbroken string can't widen the grid.
|
|
40
|
+
|
|
41
|
+
> Changed 2026-10-21: the cells were `.slot1` to `.slot4` (bare classes a consumer's own CSS could
|
|
42
|
+
> match), styled through `& > div`; the outline and border colours defaulted to `black`.
|
|
43
|
+
|
|
34
44
|
## Local overrides
|
|
35
45
|
|
|
36
46
|
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="dashboard-quad-grid-wrapper" :class="[elementClasses]">
|
|
3
3
|
<div class="dashboard-quad-grid">
|
|
4
|
-
<div class="
|
|
4
|
+
<div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-1">
|
|
5
5
|
<slot name="slot1"></slot>
|
|
6
6
|
</div>
|
|
7
|
-
<div class="
|
|
7
|
+
<div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-2">
|
|
8
8
|
<slot name="slot2"></slot>
|
|
9
9
|
</div>
|
|
10
|
-
<div class="
|
|
10
|
+
<div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-3">
|
|
11
11
|
<slot name="slot3"></slot>
|
|
12
12
|
</div>
|
|
13
|
-
<div class="
|
|
13
|
+
<div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-4">
|
|
14
14
|
<slot name="slot4"></slot>
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
@@ -50,61 +50,63 @@ watch(
|
|
|
50
50
|
rule-break: intersection;
|
|
51
51
|
rule-inset: var(--dashboard-quad-grid-rule-inset, 0);
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
.dashboard-quad-grid-cell {
|
|
54
|
+
min-inline-size: 0;
|
|
55
|
+
overflow-wrap: anywhere;
|
|
54
56
|
padding: var(--dashboard-quad-grid-padding, 2rem);
|
|
55
57
|
outline: var(--dashboard-quad-grid-outline-width, 0.1rem) solid
|
|
56
|
-
var(--dashboard-quad-grid-outline-colour,
|
|
58
|
+
var(--dashboard-quad-grid-outline-colour, var(--theme-border));
|
|
57
59
|
outline-offset: var(--dashboard-quad-grid-outline-offset, 0);
|
|
58
60
|
border: var(--dashboard-quad-grid-border-width, 0) solid
|
|
59
|
-
var(--dashboard-quad-grid-border-colour,
|
|
61
|
+
var(--dashboard-quad-grid-border-colour, var(--theme-border));
|
|
60
62
|
border-radius: var(--dashboard-quad-grid-border-radius, 0.5rem);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
@container (min-width: 768px) {
|
|
64
|
-
grid-template-columns: repeat(2, auto);
|
|
66
|
+
grid-template-columns: repeat(2, minmax(0, auto));
|
|
65
67
|
grid-template-rows: repeat(3, auto);
|
|
66
68
|
|
|
67
|
-
.
|
|
69
|
+
.dashboard-quad-grid-slot-1 {
|
|
68
70
|
grid-column: 1 / span 2;
|
|
69
71
|
grid-row: 1;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
.
|
|
74
|
+
.dashboard-quad-grid-slot-2 {
|
|
73
75
|
grid-column: 1 / span 2;
|
|
74
76
|
grid-row: 2;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
.
|
|
79
|
+
.dashboard-quad-grid-slot-3 {
|
|
78
80
|
grid-column: 1;
|
|
79
81
|
grid-row: 3;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
.
|
|
84
|
+
.dashboard-quad-grid-slot-4 {
|
|
83
85
|
grid-column: 2;
|
|
84
86
|
grid-row: 3;
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
@container (min-width: 1060px) {
|
|
89
|
-
grid-template-columns: repeat(3, auto);
|
|
91
|
+
grid-template-columns: repeat(3, minmax(0, auto));
|
|
90
92
|
grid-template-rows: repeat(2, auto);
|
|
91
93
|
|
|
92
|
-
.
|
|
94
|
+
.dashboard-quad-grid-slot-1 {
|
|
93
95
|
grid-column: 1;
|
|
94
96
|
grid-row: 1 / span 2;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
.
|
|
99
|
+
.dashboard-quad-grid-slot-2 {
|
|
98
100
|
grid-column: 2 / span 2;
|
|
99
101
|
grid-row: 1;
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
.
|
|
104
|
+
.dashboard-quad-grid-slot-3 {
|
|
103
105
|
grid-column: 2;
|
|
104
106
|
grid-row: 2;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
.
|
|
109
|
+
.dashboard-quad-grid-slot-4 {
|
|
108
110
|
grid-column: 3;
|
|
109
111
|
grid-row: 2;
|
|
110
112
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
1
2
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
3
|
+
import CanvasSwitcher from "../../../../canvas-switcher/CanvasSwitcher.vue";
|
|
2
4
|
import DashboardQuadGridComponent from "../DashboardQuadGrid.vue";
|
|
3
5
|
import type { MediaCanvas } from "~/types/components";
|
|
4
6
|
|
|
@@ -530,3 +532,43 @@ ResponsiveComparison.parameters = {
|
|
|
530
532
|
},
|
|
531
533
|
},
|
|
532
534
|
};
|
|
535
|
+
|
|
536
|
+
export const StressTest: StoryFn = () => ({
|
|
537
|
+
components: { DashboardQuadGridComponent },
|
|
538
|
+
template: `
|
|
539
|
+
<DashboardQuadGridComponent>
|
|
540
|
+
<template #slot1><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><h3 style="margin: 0;">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</h3><p style="margin: 0;">https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own</p></div></template>
|
|
541
|
+
<template #slot2><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">Ein sehr langer Beschreibungstext aus Northtown, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Raster zu sprengen oder die Nachbarspalten zusammenzudrücken.</p></div></template>
|
|
542
|
+
<template #slot3><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><img src="/images/banners/banner-1.webp" width="1600" height="400" alt="" style="display: block; max-inline-size: 100%; block-size: auto;"></div></template>
|
|
543
|
+
<template #slot4><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">🙂 👍🏽 👨👩👧 <b>Not bold</b></p></div></template>
|
|
544
|
+
</DashboardQuadGridComponent>
|
|
545
|
+
`,
|
|
546
|
+
});
|
|
547
|
+
StressTest.storyName = "Stress Test (Worst-Case Data)";
|
|
548
|
+
StressTest.decorators = [
|
|
549
|
+
(story, context) => ({
|
|
550
|
+
components: { story, CanvasSwitcher },
|
|
551
|
+
setup() {
|
|
552
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
|
|
553
|
+
return { canvasName };
|
|
554
|
+
},
|
|
555
|
+
template: `
|
|
556
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
557
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
558
|
+
</div>
|
|
559
|
+
<div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
|
|
560
|
+
<story />
|
|
561
|
+
</div>
|
|
562
|
+
`,
|
|
563
|
+
}),
|
|
564
|
+
];
|
|
565
|
+
StressTest.parameters = {
|
|
566
|
+
layout: "fullscreen",
|
|
567
|
+
docs: {
|
|
568
|
+
story: { inline: false, height: "720px" },
|
|
569
|
+
description: {
|
|
570
|
+
story:
|
|
571
|
+
"Oversized and hostile slot content at every `CanvasSwitcher` width. Check that: the unbroken heading and URL wrap inside their cell instead of widening it; the German paragraph wraps; the 1600px image scales down to its cell; emoji, `<b>` and right-to-left text render as text; the empty and one-character cells keep their place; no neighbouring cell is squeezed to nothing and nothing scrolls sideways. At tablet and wider, check the two- and three-column arrangements too.",
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
};
|