srcdev-nuxt-components 9.1.49 → 9.1.51
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/settings.json +2 -4
- package/.claude/settings.local.json +22 -2
- package/.claude/skills/components/card-core.md +205 -0
- package/.claude/skills/components/display-dialog.md +128 -0
- package/.claude/skills/components/page-hero-highlights.md +1 -1
- package/.claude/skills/components/page-row.md +157 -0
- package/.claude/skills/components/services-card-grid.md +4 -4
- package/.claude/skills/components/services-section-grid.md +4 -4
- package/.claude/skills/components/tab-navigation.md +222 -0
- package/.claude/skills/composable-dialog-controls.md +55 -43
- package/.claude/skills/index.md +5 -2
- package/app/components/01.atoms/card/CONSUMER-STYLING.md +175 -0
- package/app/components/01.atoms/card/CardCore.vue +48 -21
- package/app/components/01.atoms/display-dialog/CONSUMER-STYLING.md +163 -0
- package/app/components/01.atoms/display-dialog/DisplayDialog.vue +277 -0
- package/app/components/01.atoms/display-dialog/stories/DisplayDialog.stories.ts +177 -0
- package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +264 -0
- package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +18 -0
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +90 -0
- package/app/components/01.atoms/page-row/PageRow.vue +115 -0
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +311 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +191 -0
- package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +145 -0
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +20 -9
- package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +12 -6
- package/app/components/{forms/ui → 05.forms/form-field}/FormField.vue +12 -19
- package/app/components/{forms/ui → 05.forms/form-wrapper}/FormWrapper.vue +6 -6
- package/app/components/{forms → 05.forms}/input-button/InputButtonCore.vue +2 -0
- package/app/components/{forms → 05.forms}/input-button/stories/InputButtonCore.stories.ts +1 -1
- package/app/components/{forms → 05.forms}/input-text/InputTextCore.vue +73 -73
- package/app/components/{forms → 05.forms}/input-text/variants/InputTextAsNumberWithLabel.vue +29 -29
- package/app/components/carousels/CarouselBasic.vue +99 -99
- package/app/components/carousels/CarouselInfinite.vue +150 -150
- package/app/components/deep-expanding-menu/DeepExpandingMenu.vue +111 -111
- package/app/components/responsive-header/ResponsiveHeader.vue +250 -282
- package/app/components/test-storybook/TestStorybook.vue +16 -16
- package/app/composables/tests/useDialogControls.spec.ts +34 -64
- package/app/composables/useDialogControls.ts +19 -33
- package/app/composables/useFormControl.ts +0 -2
- package/app/layouts/default.vue +9 -7
- package/app/pages/auto-grid.vue +37 -20
- package/app/pages/banner-video.vue +2 -2
- package/app/pages/forms/examples/buttons/index.vue +189 -215
- package/app/pages/forms/examples/material/text-fields.vue +2 -2
- package/app/pages/grid-stack.vue +10 -10
- package/app/pages/index.vue +20 -20
- package/app/pages/samaritan.vue +4 -4
- package/app/pages/typography/hero-text.vue +6 -6
- package/app/pages/typography/index.vue +4 -4
- package/app/pages/typography/page-body.vue +4 -4
- package/app/pages/typography/page-heading.vue +4 -4
- package/app/pages/typography/page-link.vue +4 -4
- package/app/pages/ui/accordian.vue +4 -4
- package/app/pages/ui/anchor-scroll-tab-navigation.vue +77 -66
- package/app/pages/ui/anchor-scroll.vue +94 -70
- package/app/pages/ui/animated-svg-text.vue +4 -4
- package/app/pages/ui/block-decorators.vue +11 -11
- package/app/pages/ui/card-core.vue +9 -9
- package/app/pages/ui/carousel-basic.vue +4 -4
- package/app/pages/ui/carousel-flip.vue +2 -2
- package/app/pages/ui/carousel-infinite.vue +4 -4
- package/app/pages/ui/clipped-panels.vue +10 -10
- package/app/pages/ui/contact-section.vue +4 -4
- package/app/pages/ui/container-glow.vue +2 -3
- package/app/pages/ui/deep-expanding-menu.vue +138 -0
- package/app/pages/ui/display-avatar.vue +2 -2
- package/app/pages/ui/display-banner.vue +2 -2
- package/app/pages/ui/display-chip.vue +32 -25
- package/app/pages/ui/display-details.vue +6 -10
- package/app/pages/ui/display-dialog.vue +96 -131
- package/app/pages/ui/display-pill.vue +27 -33
- package/app/pages/ui/display-prompt.vue +4 -4
- package/app/pages/ui/display-toast.vue +2 -2
- package/app/pages/ui/expanding-panel.vue +12 -16
- package/app/pages/ui/glowing-border.vue +12 -12
- package/app/pages/ui/layout-grid-a.vue +6 -6
- package/app/pages/ui/layout-grid-b.vue +4 -4
- package/app/pages/ui/magnetic-navigation.vue +6 -6
- package/app/pages/ui/marquee-scroller.vue +2 -2
- package/app/pages/ui/mask-element.vue +2 -2
- package/app/pages/ui/masonry-columns.vue +4 -4
- package/app/pages/ui/masonry-grid-ordered-experiment.vue +4 -4
- package/app/pages/ui/masonry-grid-ordered.vue +4 -4
- package/app/pages/ui/masonry-grid-sorted.vue +4 -4
- package/app/pages/ui/masonry-grid.vue +4 -4
- package/app/pages/ui/navigation/navigation-horizontal.vue +12 -12
- package/app/pages/ui/navigation/tab-navigation.vue +493 -0
- package/app/pages/ui/page-row.vue +127 -0
- package/app/pages/ui/price-list.vue +4 -4
- package/app/pages/ui/profile-section.vue +4 -4
- package/app/pages/ui/qr-code/[componentName].vue +6 -6
- package/app/pages/ui/rotating-carousel.vue +16 -16
- package/app/pages/ui/scroll-reveal-image.vue +20 -20
- package/app/pages/ui/section-parallax.vue +7 -7
- package/app/pages/ui/services/colour-finder.vue +4 -4
- package/app/pages/ui/services/services-cards.vue +4 -4
- package/app/pages/ui/services/services-section/[slug].vue +4 -4
- package/app/pages/ui/services/services-sections-compact.vue +6 -6
- package/app/pages/ui/settings.vue +10 -10
- package/app/pages/ui/simple-grid.vue +2 -2
- package/app/pages/ui/slider-gallery.vue +5 -5
- package/app/pages/ui/tabs-y.vue +2 -3
- package/app/pages/ui/tabs.vue +6 -9
- package/app/pages/ui/tooltips.vue +9 -9
- package/app/pages/ui/wipe-away-vertical.vue +14 -14
- package/package.json +1 -1
- package/.claude/skills/components/layout-row.md +0 -239
- package/app/components/display-dialog/DisplayDialogCore.vue +0 -276
- package/app/components/display-dialog/variants/DisplayDialogConfirm.vue +0 -47
- package/app/components/display-dialog/variants/DisplayDialogScrollableContent.vue +0 -51
- package/app/components/forms/c12/utils.ts +0 -14
- package/app/components/layout-row/LayoutRow.vue +0 -171
- package/app/components/layout-row/stories/LayoutRow.stories.ts +0 -528
- package/app/pages/ui/layout-row.vue +0 -139
- /package/app/components/{forms → 05.forms}/form-errors/InputError.vue +0 -0
- /package/app/components/{forms → 05.forms}/form-errors/tests/InputError.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/form-fieldset/FormFieldset.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-chromium-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-firefox-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-webkit-darwin.png +0 -0
- /package/app/components/{forms → 05.forms}/input-button/tests/InputButtonCore.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-button/tests/__snapshots__/InputButtonCore.spec.ts.snap +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox/MultipleCheckboxes.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox/SingleCheckbox.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox/tests/MultipleCheckboxes.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox/tests/data/tags.json +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioButton.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioWithLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-checkbox-radio/tests/InputCheckboxRadioCore.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-copy/InputCopyCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-copy/stories/InputCopyCore.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-copy/tests/InputCopyCore.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-copy/tests/__snapshots__/InputCopyCore.spec.ts.snap +0 -0
- /package/app/components/{forms → 05.forms}/input-description/InputDescription.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-description/tests/InputDescription.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-label/InputLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-label/tests/InputLabel.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-number/InputNumberCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-number/variants/InputNumberDefault.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-radio/MultipleRadiobuttons.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-radio/tests/MultipleRadioButtons.spec.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-radio/tests/data/tags.json +0 -0
- /package/app/components/{forms → 05.forms}/input-range/InputRangeCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-range/variants/InputRangeDefault.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-range-fancy/InputRangeFancyCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-range-fancy/InputRangeFancyWithLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-select/InputSelectCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-select/variants/InputSelectWithLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-text/stories/InputPasswordWithLabel.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-text/stories/InputTextCore.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-text/stories/InputTextWithLabel.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-text/variants/InputPasswordWithLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-text/variants/InputTextWithLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-textarea/InputTextareaCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/input-textarea/stories/InputTextareaCore.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/input-textarea/variants/InputTextareaWithLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/pending-effect/PendingEffect.vue +0 -0
- /package/app/components/{forms → 05.forms}/toggle-switch/ToggleSwitchCore.vue +0 -0
- /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -0
- /package/app/components/{forms → 05.forms}/toggle-switch/variants/ToggleSwitchWithLabel.vue +0 -0
- /package/app/components/{forms → 05.forms}/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +0 -0
- /package/app/components/{forms → 05.forms}/triple-toggle-switch/TripleToggleSwitchCore.vue +0 -0
- /package/app/{components/forms/c12/prop-validators/index.ts → utils/prop-validators.ts} +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# TabNavigation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A responsive horizontal navigation bar with CSS anchor-positioning indicators, smooth anchor
|
|
6
|
+
scroll, and a burger-menu collapse at narrow widths. Accepts both route links (`NuxtLink`) and
|
|
7
|
+
hash anchors (`<a>`). Items are driven by a `NavItemData` object; the component only reads the
|
|
8
|
+
`main` key.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Types
|
|
13
|
+
|
|
14
|
+
Import from the layer package root — do **not** use `~/types/...` in consuming apps:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import type { NavItemData } from "srcdev-nuxt-components"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
interface NavItem {
|
|
22
|
+
text: string;
|
|
23
|
+
href?: string;
|
|
24
|
+
isExternal?: boolean; // adds target="_blank" rel behaviour via NuxtLink
|
|
25
|
+
iconName?: string; // icon set name, rendered via <Icon>
|
|
26
|
+
cssName?: string; // extra CSS class on the <li>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface NavItemData {
|
|
30
|
+
[key: string]: NavItem[]; // component reads navItemData.main
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
| Prop (template form) | Type | Default | Notes |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `nav-item-data` | `NavItemData` | required | Navigation link data — only `main` key is read |
|
|
41
|
+
| `nav-align` | `"left" \| "center" \| "right"` | `"left"` | Positions the tab list within the nav container |
|
|
42
|
+
| `anchor-scroll-offset` | `number \| (() => number)` | — | Pixel offset subtracted from scroll target; pass a getter so it is re-evaluated at click time |
|
|
43
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the root `<nav>` element |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Basic usage — route links
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<TabNavigation :nav-item-data="navData" nav-align="center" />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import type { NavItemData } from "srcdev-nuxt-components"
|
|
55
|
+
|
|
56
|
+
const navData: NavItemData = {
|
|
57
|
+
main: [
|
|
58
|
+
{ text: "Home", href: "/" },
|
|
59
|
+
{ text: "About", href: "/about" },
|
|
60
|
+
{ text: "Services", href: "/services" },
|
|
61
|
+
{ text: "Contact", href: "/contact" },
|
|
62
|
+
],
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Items without a leading `#` render as `NuxtLink`. The active indicator tracks the current route
|
|
67
|
+
via `router-link-exact-active`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Anchor scroll usage
|
|
72
|
+
|
|
73
|
+
Hash-prefixed hrefs render as plain `<a>` elements to keep Vue Router out of the scroll path.
|
|
74
|
+
Pass a getter for the offset so the sticky bar height is read at click time (not at mount).
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<div ref="stickyRef" class="sticky-nav">
|
|
78
|
+
<TabNavigation
|
|
79
|
+
:nav-item-data="anchorData"
|
|
80
|
+
:anchor-scroll-offset="() => stickyRef?.offsetHeight ?? 0"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const stickyRef = ref<HTMLElement | null>(null)
|
|
87
|
+
|
|
88
|
+
const anchorData: NavItemData = {
|
|
89
|
+
main: [
|
|
90
|
+
{ text: "Overview", href: "#overview" },
|
|
91
|
+
{ text: "Features", href: "#features" },
|
|
92
|
+
{ text: "Pricing", href: "#pricing" },
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Clicks are handled by `useAnchorScroll`. Respects `prefers-reduced-motion` — motion-sensitive
|
|
98
|
+
users get an instant jump rather than smooth scroll. See the `composable-anchor-scroll.md` skill
|
|
99
|
+
for the full composable reference.
|
|
100
|
+
|
|
101
|
+
### Mixed navigation
|
|
102
|
+
|
|
103
|
+
Route links and hash anchors can coexist in the same `navItemData`:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const navData: NavItemData = {
|
|
107
|
+
main: [
|
|
108
|
+
{ text: "Home", href: "/" }, // NuxtLink
|
|
109
|
+
{ text: "Overview", href: "#intro" }, // <a> + smooth scroll
|
|
110
|
+
{ text: "Blog", href: "/blog" }, // NuxtLink
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Responsive collapse
|
|
118
|
+
|
|
119
|
+
`useNavCollapse` measures the nav list on every resize. When the list overflows its container it
|
|
120
|
+
is hidden and replaced with a burger button. The button opens a slide-down panel listing all items.
|
|
121
|
+
|
|
122
|
+
The nav hides itself (opacity 0) until the first measurement completes to prevent a flash of the
|
|
123
|
+
wrong state.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Alignment variants
|
|
128
|
+
|
|
129
|
+
```vue
|
|
130
|
+
<TabNavigation :nav-item-data="navData" nav-align="left" /> <!-- default -->
|
|
131
|
+
<TabNavigation :nav-item-data="navData" nav-align="center" />
|
|
132
|
+
<TabNavigation :nav-item-data="navData" nav-align="right" />
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`center` and `right` use `margin-inline: auto` / `margin-inline-start: auto` on the `ul`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## CSS token API
|
|
140
|
+
|
|
141
|
+
Set `--tab-nav-*` tokens on any ancestor element — no prop needed. See `CONSUMER-STYLING.md` in
|
|
142
|
+
the component source folder for the full token reference.
|
|
143
|
+
|
|
144
|
+
### Horizontal nav
|
|
145
|
+
|
|
146
|
+
| Token | Default | Controls |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `--tab-nav-link-color` | `var(--slate-01, currentColor)` | Link text colour |
|
|
149
|
+
| `--tab-nav-link-hover-color` | `var(--slate-04, currentColor)` | Link hover colour |
|
|
150
|
+
| `--tab-nav-link-active-color` | `var(--slate-01, currentColor)` | Active link colour |
|
|
151
|
+
| `--tab-nav-link-size` | `1.6rem` | Font size |
|
|
152
|
+
| `--tab-nav-link-weight` | `400` | Font weight |
|
|
153
|
+
| `--tab-nav-link-tracking` | `0.06em` | Letter spacing |
|
|
154
|
+
| `--tab-nav-gap` | `2.2rem` | Gap between items |
|
|
155
|
+
| `--tab-nav-transition` | `250ms ease` | Colour fade on hover/active |
|
|
156
|
+
|
|
157
|
+
### Indicator decorators
|
|
158
|
+
|
|
159
|
+
| Token | Default | Controls |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `--tab-nav-decorator-indicator-color` | `var(--slate-01, currentColor)` | Active underline bar colour |
|
|
162
|
+
| `--tab-nav-decorator-hovered-bg` | `transparent` | Hover pill background (set non-transparent to enable) |
|
|
163
|
+
|
|
164
|
+
### Mobile panel
|
|
165
|
+
|
|
166
|
+
| Token | Default | Controls |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `--tab-nav-panel-bg` | `var(--page-bg, #1a1614)` | Panel background |
|
|
169
|
+
| `--tab-nav-panel-border-color` | `color-mix(in oklch, var(--slate-01) 35%, transparent)` | Panel top border |
|
|
170
|
+
| `--tab-nav-panel-item-border` | `color-mix(in oklch, var(--slate-01) 8%, transparent)` | Item dividers |
|
|
171
|
+
| `--tab-nav-panel-link-color` | `var(--slate-01, currentColor)` | Panel link colour |
|
|
172
|
+
| `--tab-nav-panel-padding-block` | `1.4rem` | Link vertical padding |
|
|
173
|
+
| `--tab-nav-panel-padding-inline` | `1.5rem` | Link horizontal padding |
|
|
174
|
+
| `--tab-nav-panel-slide-duration` | `350ms` | Panel open/close duration |
|
|
175
|
+
| `--tab-nav-panel-slide-easing` | `cubic-bezier(0.4, 0, 0.2, 1)` | Panel easing |
|
|
176
|
+
|
|
177
|
+
### Burger button
|
|
178
|
+
|
|
179
|
+
| Token | Default | Controls |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `--tab-nav-burger-color` | `var(--slate-01, currentColor)` | Bar colour |
|
|
182
|
+
| `--tab-nav-burger-width` | `22px` | Bar width |
|
|
183
|
+
| `--tab-nav-burger-height` | `1.5px` | Bar height |
|
|
184
|
+
| `--tab-nav-burger-gap` | `5px` | Gap between bars |
|
|
185
|
+
| `--tab-nav-burger-transition` | `300ms ease` | Open/close animation |
|
|
186
|
+
|
|
187
|
+
### Backdrop
|
|
188
|
+
|
|
189
|
+
| Token | Default | Controls |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| `--tab-nav-backdrop-bg` | `oklch(0% 0 0 / 55%)` | Overlay colour |
|
|
192
|
+
| `--tab-nav-backdrop-blur` | `3px` | Overlay blur |
|
|
193
|
+
| `--tab-nav-backdrop-duration` | `350ms` | Overlay fade duration |
|
|
194
|
+
|
|
195
|
+
### Page-scoped theme example
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
/* Unscoped <style> in the consuming page */
|
|
199
|
+
.services-page {
|
|
200
|
+
.tab-navigation {
|
|
201
|
+
--tab-nav-decorator-indicator-color: var(--brand-accent);
|
|
202
|
+
--tab-nav-decorator-hovered-bg: oklch(from var(--brand-accent) l c h / 0.1);
|
|
203
|
+
--tab-nav-link-active-color: var(--brand-accent);
|
|
204
|
+
--tab-nav-panel-bg: var(--page-bg);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Notes
|
|
212
|
+
|
|
213
|
+
- Auto-imported in Nuxt — no manual import needed.
|
|
214
|
+
- The indicator and hover pill use CSS Anchor Positioning and animate via `left`/`right` transitions
|
|
215
|
+
— set `--tab-nav-decorator-hovered-bg` to a semi-transparent colour to enable the pill; it is
|
|
216
|
+
invisible by default.
|
|
217
|
+
- `--tab-nav-transition` controls only the link colour fade; the indicator slide animation is
|
|
218
|
+
internally managed and does not inherit this token.
|
|
219
|
+
- The panel teleports its backdrop to `<body>` so it escapes any parent `overflow: hidden` or
|
|
220
|
+
stacking-context constraint.
|
|
221
|
+
- `--tab-nav-panel-bg` should match `--page-bg` for a seamless panel appearance in the collapsed
|
|
222
|
+
state.
|
|
@@ -2,77 +2,89 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
`useDialogControls` manages open/closed state for one or more named dialogs
|
|
5
|
+
`useDialogControls` manages open/closed state for one or more named dialogs with optional
|
|
6
|
+
confirm/cancel callbacks. Pass all dialog IDs and their callbacks in a single config object
|
|
7
|
+
at call time — no separate initialisation or registration step needed.
|
|
6
8
|
|
|
7
|
-
**This composable ships inside the `srcdev-nuxt-components` layer** (`app/composables/useDialogControls.ts`).
|
|
9
|
+
**This composable ships inside the `srcdev-nuxt-components` layer** (`app/composables/useDialogControls.ts`).
|
|
10
|
+
It is auto-imported via the Nuxt layer — **do not create a local copy** in the consuming app.
|
|
8
11
|
|
|
9
12
|
## Usage
|
|
10
13
|
|
|
11
|
-
### 1.
|
|
14
|
+
### 1. Set up dialog controls
|
|
12
15
|
|
|
13
|
-
Call `
|
|
16
|
+
Call `useDialogControls` with a config object whose keys are dialog IDs:
|
|
14
17
|
|
|
15
18
|
```ts
|
|
16
|
-
const { dialogsConfig,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
const { dialogsConfig, openDialog, closeDialog } = useDialogControls({
|
|
20
|
+
confirmDelete: {
|
|
21
|
+
onConfirm: () => deleteItem(),
|
|
22
|
+
onCancel: () => console.log("Cancelled"),
|
|
23
|
+
},
|
|
24
|
+
editProfile: {
|
|
25
|
+
onConfirm: () => saveProfile(),
|
|
26
|
+
},
|
|
27
|
+
})
|
|
19
28
|
```
|
|
20
29
|
|
|
21
|
-
Each
|
|
30
|
+
Each key gets a reactive boolean in `dialogsConfig` (initially `false` = closed).
|
|
31
|
+
Callbacks are optional — omit either or both.
|
|
22
32
|
|
|
23
33
|
### 2. Bind to a dialog component
|
|
24
34
|
|
|
25
|
-
Use `
|
|
35
|
+
Use `v-if` + `v-model` with `dialogsConfig[id]`:
|
|
26
36
|
|
|
27
37
|
```vue
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
<DisplayDialog
|
|
39
|
+
v-if="dialogsConfig['confirmDelete']"
|
|
40
|
+
v-model="dialogsConfig['confirmDelete']"
|
|
41
|
+
variant="confirm"
|
|
42
|
+
data-dialog-id="confirmDelete"
|
|
43
|
+
>
|
|
44
|
+
<template #dialogTitle>
|
|
45
|
+
<p>Confirm delete?</p>
|
|
46
|
+
</template>
|
|
47
|
+
<template #dialogContent>
|
|
48
|
+
<p>This action cannot be undone.</p>
|
|
34
49
|
</template>
|
|
35
|
-
|
|
50
|
+
<template #actionButtonLeft>
|
|
51
|
+
<button @click="closeDialog('confirmDelete', 'cancel')">Cancel</button>
|
|
52
|
+
</template>
|
|
53
|
+
<template #actionButtonRight>
|
|
54
|
+
<button @click="closeDialog('confirmDelete', 'confirm')">Delete</button>
|
|
55
|
+
</template>
|
|
56
|
+
</DisplayDialog>
|
|
36
57
|
```
|
|
37
58
|
|
|
38
59
|
### 3. Open and close dialogs
|
|
39
60
|
|
|
40
61
|
```ts
|
|
41
62
|
// Open
|
|
42
|
-
|
|
63
|
+
openDialog("confirmDelete")
|
|
43
64
|
|
|
44
|
-
// Close without
|
|
45
|
-
|
|
65
|
+
// Close without firing a callback
|
|
66
|
+
closeDialog("confirmDelete")
|
|
46
67
|
|
|
47
|
-
// Close and fire
|
|
48
|
-
|
|
49
|
-
controlDialogs("confirmDelete", false, "cancel") // fires onCancel if registered
|
|
50
|
-
```
|
|
68
|
+
// Close and fire onConfirm
|
|
69
|
+
closeDialog("confirmDelete", "confirm")
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Register confirm/cancel callbacks before the dialog is opened:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
registerDialogCallbacks("confirmDelete", {
|
|
58
|
-
onConfirm: () => deleteItem(),
|
|
59
|
-
onCancel: () => console.log("Cancelled"),
|
|
60
|
-
})
|
|
71
|
+
// Close and fire onCancel
|
|
72
|
+
closeDialog("confirmDelete", "cancel")
|
|
61
73
|
```
|
|
62
74
|
|
|
63
|
-
Callbacks fire when `controlDialogs` is called with a matching action string.
|
|
64
|
-
|
|
65
75
|
## API reference
|
|
66
76
|
|
|
67
|
-
| Return value | Description |
|
|
68
|
-
|
|
69
|
-
| `dialogsConfig` | Reactive
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `registerDialogCallbacks(id, { onConfirm?, onCancel? })` | Register lifecycle callbacks for a dialog ID. |
|
|
77
|
+
| Return value | Type | Description |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `dialogsConfig` | `Record<string, boolean>` | Reactive map of dialog ID → open state. Bind to `v-model` and `v-if`. |
|
|
80
|
+
| `openDialog(name)` | `(name: string) => void` | Sets `dialogsConfig[name]` to `true`. |
|
|
81
|
+
| `closeDialog(name, action?)` | `(name: string, action?: 'confirm' \| 'cancel') => void` | Sets `dialogsConfig[name]` to `false`. If `action` is provided, fires the registered callback first. |
|
|
73
82
|
|
|
74
83
|
## Notes
|
|
75
84
|
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
- `
|
|
85
|
+
- State is scoped to the component instance — each component that calls `useDialogControls` gets
|
|
86
|
+
its own `dialogsConfig`. It is not a global store.
|
|
87
|
+
- `v-if="dialogsConfig['id']"` on the dialog component ensures it only mounts when open, which
|
|
88
|
+
triggers `onMounted` body-lock logic inside `DisplayDialog`.
|
|
89
|
+
- Callbacks fire before the dialog closes — useful for async operations that need to run while
|
|
90
|
+
the dialog is still visible.
|
package/.claude/skills/index.md
CHANGED
|
@@ -48,7 +48,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
48
48
|
├── composable-whatsapp.md — useWhatsApp: open pre-filled wa.me link from form payload; runtime config, security, usage
|
|
49
49
|
├── composable-zod-validation.md — useZodValidation: schema-driven form validation, error binding, submit flow, API error push
|
|
50
50
|
├── composable-colour-scheme.md — useColourScheme: reactive light/dark/auto switching, localStorage persistence, runtime config
|
|
51
|
-
├── composable-dialog-controls.md — useDialogControls:
|
|
51
|
+
├── composable-dialog-controls.md — useDialogControls: single-call setup with config object, openDialog/closeDialog API, confirm/cancel callbacks
|
|
52
52
|
├── composable-anchor-scroll.md — useAnchorScroll: smooth anchor scrolling with reduced-motion support, dynamic offset, and TabNavigation integration
|
|
53
53
|
├── composable-tooltips-guide.md — useTooltipsGuide: sequential popover guide with auto-start, dismiss-to-advance, manual controls
|
|
54
54
|
└── components/
|
|
@@ -56,7 +56,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
56
56
|
├── eyebrow-text.md — EyebrowText props, usage patterns, styling
|
|
57
57
|
├── hero-text.md — HeroText props, usage patterns, styling
|
|
58
58
|
├── layout-grid-by-cols.md — LayoutGridByCols dynamic slots (item-{n}), props, column/gap/breakpoint decisions
|
|
59
|
-
├──
|
|
59
|
+
├── page-row.md — PageRow layout primitive: CSS grid named lines, nesting pattern, align prop, aria-labelledby, CSS token API
|
|
60
60
|
├── link-text.md — LinkText props, slots, usage patterns, styling
|
|
61
61
|
├── page-hero-highlights.md — PageHeroHighlights template: hero + highlights strip grid, CSS custom property theming
|
|
62
62
|
├── services-card.md — ServicesCard props (incl. eyebrowConfig/heroConfig), actions slot, CSS tokens, page boilerplate
|
|
@@ -74,12 +74,15 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
74
74
|
├── scroll-reveal-frame.md — ScrollRevealFrame: generic parallax clipping frame, slot API, image grid pattern, CSS tokens, browser support
|
|
75
75
|
├── scroll-reveal-image.md — ScrollRevealImage: single-image parallax reveal, focalX, imgWidth/imgHeight, responsive frame height
|
|
76
76
|
├── site-navigation.md — SiteNavigation: responsive nav with auto-collapse, burger menu, decorator indicators, CSS token API
|
|
77
|
+
├── tab-navigation.md — TabNavigation: horizontal tab nav with CSS anchor-positioning indicators, anchor scroll, burger collapse, full CSS token API
|
|
77
78
|
├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names, CSS tokens
|
|
78
79
|
├── display-qr-code.md — DisplayQrCode: QR code SVG from a string value, colour/size/variant/radius props, currentColor default
|
|
79
80
|
├── capture-qr-code.md — CaptureQrCode: live camera scanner, error state, visibility/route/KeepAlive lifecycle, media stream cleanup
|
|
80
81
|
├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
|
|
81
82
|
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-min-col-size/gap tokens, semantic tag + aria
|
|
82
83
|
├── display-avatar.md — DisplayAvatar: circular avatar with image/initials fallback, size variants, chip badge, icon slot, styleClassPassthrough
|
|
84
|
+
├── card-core.md — CardCore: generic card container, dynamic named slots as rows, 4 variants, blurred backdrop layer, full CSS token API
|
|
85
|
+
├── display-dialog.md — DisplayDialog: native <dialog> overlay, 5 variants (dialog/modal/confirm/alert/fullscreen), useDialogControls integration, CSS token API
|
|
83
86
|
├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
|
|
84
87
|
├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, full CSS token API for border/outline/colour
|
|
85
88
|
├── carousel-flip.md — CarouselFlip: FLIP-animated carousel, carouselDataIds slot API, buttonLayout variants (sides/controls-flanking/controls-grouped-right/overlay), CSS tokens
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# CardCore — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
All `--card-core-*` tokens are the stable override surface. Set them at any scope (global, page,
|
|
6
|
+
or instance) without touching the component itself.
|
|
7
|
+
|
|
8
|
+
| Token | Default | Controls |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `--card-core-row-gap` | `1rem` | Gap between card rows |
|
|
11
|
+
| `--card-core-inner-padding` | `1rem` | Internal padding (applied per row by the consumer) |
|
|
12
|
+
| `--card-core-border-radius` | `0.5rem` | Corner rounding |
|
|
13
|
+
| `--card-core-border` | `0.2rem solid var(--slate-08)` | Full border shorthand |
|
|
14
|
+
| `--card-core-box-shadow` | `0.1rem 0.1rem 0.4rem oklch(from var(--slate-08) l c h / 0.45)` | Box shadow shorthand |
|
|
15
|
+
| `--card-core-background-color` | `transparent` | Main background colour |
|
|
16
|
+
| `--card-core-background-image` | `none` | Main background image |
|
|
17
|
+
| `--card-core-lower-background-color` | `transparent` | Blurred backdrop layer colour |
|
|
18
|
+
| `--card-core-lower-background-image` | `none` | Blurred backdrop layer image |
|
|
19
|
+
| `--card-core-lower-background-position` | `center` | Backdrop image position |
|
|
20
|
+
| `--card-core-lower-background-blur` | `16px` | Backdrop blur amount |
|
|
21
|
+
| `--card-core-lower-scale` | `1.1` | Backdrop scale (zoom to hide blur edges) |
|
|
22
|
+
|
|
23
|
+
> **Variant note**: The `solid`, `subtle`, `soft`, and `outline` variants directly override the
|
|
24
|
+
> internal `--_card-background-color` and `--_card-border` tokens, bypassing the public tokens
|
|
25
|
+
> for those properties. To override a variant's colours, target the variant class directly (see
|
|
26
|
+
> [Per-variant overrides](#per-variant-overrides) below).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Global theming — app-level CSS file
|
|
31
|
+
|
|
32
|
+
Create `assets/styles/setup/07.components/card-core.css` in the consuming app and set tokens on
|
|
33
|
+
`:root`. These values apply to every `CardCore` instance across the site.
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
/* assets/styles/setup/07.components/card-core.css */
|
|
37
|
+
:root {
|
|
38
|
+
--card-core-border-radius: 1rem;
|
|
39
|
+
--card-core-border: 0.1rem solid var(--brand-border);
|
|
40
|
+
--card-core-box-shadow: 0 0.2rem 1.2rem rgb(0 0 0 / 8%);
|
|
41
|
+
--card-core-row-gap: 0;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Per-variant overrides
|
|
48
|
+
|
|
49
|
+
Variants override internal tokens directly, so target the variant class to change their colours:
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
/* assets/styles/setup/07.components/card-core.css */
|
|
53
|
+
:root {
|
|
54
|
+
/* shared geometry */
|
|
55
|
+
--card-core-border-radius: 0.8rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* variant colour overrides — set on the variant class, not :root */
|
|
59
|
+
.card-core {
|
|
60
|
+
&.solid {
|
|
61
|
+
--_card-background-color: var(--brand-surface);
|
|
62
|
+
--_card-border: 0.1rem solid var(--brand-border);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&.subtle {
|
|
66
|
+
--_card-background-color: color-mix(in oklab, var(--brand-surface) 50%, transparent);
|
|
67
|
+
--_card-border: 0.1rem solid var(--brand-border);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&.outline {
|
|
71
|
+
--_card-border: 0.15rem solid var(--brand-primary);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Blurred backdrop pattern
|
|
79
|
+
|
|
80
|
+
Set `--card-core-lower-background-image` and `--card-core-lower-background-color` to create a
|
|
81
|
+
blurred translucent layer behind the card content. The `::before` pseudo-element handles the blur
|
|
82
|
+
and the scale hides the blur-edge artefact.
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
.card-core {
|
|
86
|
+
&.hero-card {
|
|
87
|
+
--card-core-lower-background-image: url("/images/hero.jpg");
|
|
88
|
+
--card-core-lower-background-blur: 24px;
|
|
89
|
+
--card-core-lower-scale: 1.15;
|
|
90
|
+
--card-core-lower-background-color: rgb(0 0 0 / 30%); /* tint over image */
|
|
91
|
+
--card-core-background-color: transparent;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or inline for a single instance:
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<CardCore
|
|
100
|
+
variant="solid"
|
|
101
|
+
style="
|
|
102
|
+
--card-core-lower-background-image: url('/images/hero.jpg');
|
|
103
|
+
--card-core-lower-background-blur: 20px;
|
|
104
|
+
"
|
|
105
|
+
>
|
|
106
|
+
...
|
|
107
|
+
</CardCore>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Page-scoped overrides
|
|
113
|
+
|
|
114
|
+
When a card appears in a specific page context, scope overrides under the page wrapper. No
|
|
115
|
+
`:deep()` is required (component styles are unscoped).
|
|
116
|
+
|
|
117
|
+
```css
|
|
118
|
+
/* In the consuming page's unscoped <style> block */
|
|
119
|
+
.services-page {
|
|
120
|
+
.card-core {
|
|
121
|
+
--card-core-border-radius: 1.2rem;
|
|
122
|
+
--card-core-row-gap: 0;
|
|
123
|
+
|
|
124
|
+
&.solid {
|
|
125
|
+
--_card-background-color: var(--brand-surface-alt);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Per-instance overrides via styleClassPassthrough
|
|
134
|
+
|
|
135
|
+
```vue
|
|
136
|
+
<CardCore :style-class-passthrough="['featured']">
|
|
137
|
+
...
|
|
138
|
+
</CardCore>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```css
|
|
142
|
+
.card-core {
|
|
143
|
+
&.featured {
|
|
144
|
+
--card-core-border: 0.2rem solid var(--brand-primary);
|
|
145
|
+
--card-core-box-shadow: 0 0.4rem 2rem rgb(0 0 0 / 15%);
|
|
146
|
+
--card-core-border-radius: 1.6rem;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Card row targeting
|
|
154
|
+
|
|
155
|
+
Slot content is wrapped in `.card-row.card-row-{name}`. Target rows for padding, borders, or
|
|
156
|
+
backgrounds without modifying the component:
|
|
157
|
+
|
|
158
|
+
```css
|
|
159
|
+
.card-core {
|
|
160
|
+
.card-row-header {
|
|
161
|
+
padding: 1.6rem;
|
|
162
|
+
background-color: var(--brand-primary);
|
|
163
|
+
color: white;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.card-row-body {
|
|
167
|
+
padding: 1.6rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.card-row-footer {
|
|
171
|
+
padding: 1.2rem 1.6rem;
|
|
172
|
+
border-top: 0.1rem solid var(--brand-border);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
@@ -41,50 +41,77 @@ watch(
|
|
|
41
41
|
<style lang="css">
|
|
42
42
|
@layer components {
|
|
43
43
|
.card-core {
|
|
44
|
-
--
|
|
45
|
-
--
|
|
46
|
-
--
|
|
47
|
-
--
|
|
48
|
-
--
|
|
44
|
+
--_card-row-gap: var(--card-core-row-gap, 1rem);
|
|
45
|
+
--_card-padding: var(--card-core-inner-padding, 1rem);
|
|
46
|
+
--_card-lower-background-image: var(--card-core-lower-background-image, none);
|
|
47
|
+
--_card-lower-background-position: var(--card-core-lower-background-position, center);
|
|
48
|
+
--_card-lower-background-color: var(--card-core-lower-background-color, transparent);
|
|
49
|
+
--_card-lower-background-blur: var(--card-core-lower-background-blur, 16px);
|
|
50
|
+
--_card-lower-scale: var(--card-core-lower-scale, 1.1);
|
|
51
|
+
--_card-background-image: var(--card-core-background-image, none);
|
|
52
|
+
--_card-background-color: var(--card-core-background-color, transparent);
|
|
53
|
+
--_card-border: var(--card-core-border, 0.2rem solid var(--slate-08));
|
|
54
|
+
--_card-border-radius: var(--card-core-border-radius, 0.5rem);
|
|
55
|
+
--_card-box-shadow: var(--card-core-box-shadow, 0.1rem 0.1rem 0.4rem oklch(from var(--slate-08) l c h / 0.45));
|
|
49
56
|
|
|
50
57
|
display: grid;
|
|
51
58
|
grid-auto-flow: row;
|
|
52
|
-
|
|
53
|
-
border-radius:
|
|
59
|
+
gap: var(--_card-row-gap);
|
|
60
|
+
border-radius: var(--_card-border-radius);
|
|
54
61
|
overflow: hidden;
|
|
62
|
+
position: relative;
|
|
55
63
|
|
|
56
|
-
background-color: var(--
|
|
57
|
-
border: var(--
|
|
58
|
-
box-shadow:
|
|
64
|
+
background-color: var(--_card-background-color, transparent);
|
|
65
|
+
border: var(--_card-border);
|
|
66
|
+
box-shadow: var(--_card-box-shadow);
|
|
67
|
+
|
|
68
|
+
&::before {
|
|
69
|
+
content: "";
|
|
70
|
+
position: absolute;
|
|
71
|
+
inset: 0;
|
|
72
|
+
background-color: var(--_card-lower-background-color);
|
|
73
|
+
background-image: var(--_card-lower-background-image);
|
|
74
|
+
background-position: var(--_card-lower-background-position, center);
|
|
75
|
+
background-size: cover;
|
|
76
|
+
background-repeat: no-repeat;
|
|
77
|
+
filter: blur(var(--_card-lower-background-blur));
|
|
78
|
+
scale: var(--_card-lower-scale);
|
|
79
|
+
z-index: 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
> * {
|
|
83
|
+
position: relative;
|
|
84
|
+
z-index: 1;
|
|
85
|
+
}
|
|
59
86
|
|
|
60
87
|
&.no-outline {
|
|
61
|
-
--
|
|
88
|
+
--_card-border: 0 solid transparent;
|
|
89
|
+
--_card-box-shadow: none;
|
|
62
90
|
}
|
|
63
91
|
|
|
64
92
|
&.solid {
|
|
65
|
-
--
|
|
66
|
-
--
|
|
93
|
+
--_card-background-color: light-dark(var(--slate-00), var(--slate-10));
|
|
94
|
+
--_card-border: 0.2rem solid var(--slate-08);
|
|
67
95
|
}
|
|
68
96
|
|
|
69
97
|
&.subtle {
|
|
70
|
-
--
|
|
71
|
-
--
|
|
98
|
+
--_card-background-color: color-mix(in oklab, light-dark(var(--slate-01), var(--slate-08)) 50%, transparent);
|
|
99
|
+
--_card-border: 0.2rem solid var(--slate-08);
|
|
72
100
|
}
|
|
73
101
|
|
|
74
102
|
&.soft {
|
|
75
|
-
--
|
|
76
|
-
--
|
|
103
|
+
--_card-background-color: color-mix(in oklab, light-dark(var(--slate-01), var(--slate-08)) 20%, transparent);
|
|
104
|
+
--_card-box-shadow-color: color-mix(in oklab, light-dark(var(--slate-02), var(--slate-08)) 80%, transparent);
|
|
77
105
|
}
|
|
78
106
|
|
|
79
107
|
&.outline {
|
|
80
|
-
--
|
|
81
|
-
--
|
|
108
|
+
--_card-background-color: transparent;
|
|
109
|
+
--_card-border: 0.2rem solid var(--slate-08);
|
|
82
110
|
}
|
|
83
111
|
|
|
84
112
|
&.has-dividers {
|
|
85
113
|
.card-row + .card-row {
|
|
86
|
-
|
|
87
|
-
border-top: 0.2rem solid green;
|
|
114
|
+
border-top: 0.2rem solid var(--slate-08);
|
|
88
115
|
}
|
|
89
116
|
}
|
|
90
117
|
}
|