srcdev-nuxt-components 9.3.0 → 9.3.1
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/hooks/check-component-update.sh +66 -0
- package/.claude/settings.json +1 -1
- package/.claude/skills/component-local-style-override.md +45 -0
- package/.claude/skills/components/accordian-core.md +26 -5
- package/.claude/skills/components/expanding-panel-classic.md +221 -0
- package/.claude/skills/components/expanding-panel.md +8 -8
- package/.claude/skills/components/navigation-items.md +51 -0
- package/.claude/skills/components/responsive-header.md +161 -0
- package/.claude/skills/components/site-header.md +98 -0
- package/.claude/skills/index.md +6 -1
- package/.claude/skills/page-transitions.md +116 -0
- package/.claude/skills/theming-form-geometry-tokens.md +12 -0
- package/.claude/skills/theming-override-default.md +14 -0
- package/.claude/skills/theming-partial-override.md +17 -0
- package/.vscode/srcdev-component-accordian-core.code-snippets +74 -0
- package/.vscode/srcdev-component-expanding-panel-classic.code-snippets +121 -0
- package/.vscode/srcdev-component-expanding-panel.code-snippets +1 -3
- package/.vscode/srcdev-component-responsive-header.code-snippets +46 -0
- package/.vscode/srcdev-component-site-header.code-snippets +74 -0
- package/app/components/02.molecules/expandable/accordian/AccordianCore.vue +19 -11
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +93 -0
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +31 -0
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +31 -0
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +7 -21
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +10 -10
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +35 -37
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +5 -6
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +5 -13
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +103 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +191 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +293 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +514 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +59 -0
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +66 -0
- package/app/components/{responsive-header → 03.organisms/responsive-header}/NavigationItems.vue +21 -28
- package/app/components/{responsive-header → 03.organisms/responsive-header}/ResponsiveHeader.vue +141 -43
- package/app/components/03.organisms/responsive-header/stories/NavigationItems.stories.ts +80 -0
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +122 -0
- package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +155 -0
- package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +319 -0
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +34 -0
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +72 -0
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +60 -0
- package/app/components/03.organisms/site-header/SiteHeader.vue +96 -0
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +135 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +103 -0
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +15 -0
- package/app/layouts/default.vue +64 -67
- package/package.json +4 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageRow tag="div" :variant="pageRowVariant" :style-class-passthrough="styleClassPassthrough">
|
|
3
|
+
<template #default>
|
|
4
|
+
<header class="site-header">
|
|
5
|
+
<nav class="home-navigation" aria-label="Home Navigation">
|
|
6
|
+
<SkipLinks>
|
|
7
|
+
<template #homeLink>
|
|
8
|
+
<slot name="branding"></slot>
|
|
9
|
+
</template>
|
|
10
|
+
</SkipLinks>
|
|
11
|
+
</nav>
|
|
12
|
+
<ResponsiveHeader
|
|
13
|
+
:responsive-nav-links="responsiveNavLinks"
|
|
14
|
+
:gap-between-first-and-second-nav="gapBetweenFirstAndSecondNav"
|
|
15
|
+
:overflow-details-summary-icons="overflowDetailsSummaryIcons"
|
|
16
|
+
:collapse-breakpoint="collapseBreakpoint"
|
|
17
|
+
:collapse-at-main-nav-intersection="collapseAtMainNavIntersection"
|
|
18
|
+
:allow-expand-on-gesture="allowExpandOnGesture"
|
|
19
|
+
:style-class-passthrough="navStyleClassPassthrough"
|
|
20
|
+
>
|
|
21
|
+
<template v-if="slots.secondaryNavigation" #secondaryNavigation>
|
|
22
|
+
<slot name="secondaryNavigation"></slot>
|
|
23
|
+
</template>
|
|
24
|
+
</ResponsiveHeader>
|
|
25
|
+
</header>
|
|
26
|
+
</template>
|
|
27
|
+
</PageRow>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import type { ResponsiveHeaderProp } from "../../../types/components";
|
|
32
|
+
import PageRow from "../../01.atoms/page-row/PageRow.vue";
|
|
33
|
+
import SkipLinks from "../../skip-links/SkipLinks.vue";
|
|
34
|
+
import ResponsiveHeader from "../responsive-header/ResponsiveHeader.vue";
|
|
35
|
+
|
|
36
|
+
interface Props {
|
|
37
|
+
responsiveNavLinks?: ResponsiveHeaderProp;
|
|
38
|
+
gapBetweenFirstAndSecondNav?: number;
|
|
39
|
+
overflowDetailsSummaryIcons?: Record<string, string>;
|
|
40
|
+
collapseBreakpoint?: number | null;
|
|
41
|
+
collapseAtMainNavIntersection?: boolean;
|
|
42
|
+
allowExpandOnGesture?: boolean;
|
|
43
|
+
pageRowVariant?: "full" | "popout" | "content" | "inset-content";
|
|
44
|
+
styleClassPassthrough?: string | string[];
|
|
45
|
+
navStyleClassPassthrough?: string | string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
withDefaults(defineProps<Props>(), {
|
|
49
|
+
responsiveNavLinks: () => ({}),
|
|
50
|
+
gapBetweenFirstAndSecondNav: 12,
|
|
51
|
+
overflowDetailsSummaryIcons: () => ({
|
|
52
|
+
more: "gravity-ui:ellipsis",
|
|
53
|
+
burger: "gravity-ui:bars",
|
|
54
|
+
}),
|
|
55
|
+
collapseBreakpoint: null,
|
|
56
|
+
collapseAtMainNavIntersection: false,
|
|
57
|
+
allowExpandOnGesture: true,
|
|
58
|
+
pageRowVariant: "content",
|
|
59
|
+
styleClassPassthrough: () => [],
|
|
60
|
+
navStyleClassPassthrough: () => [],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const slots = useSlots();
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<style lang="css">
|
|
67
|
+
@layer components {
|
|
68
|
+
/* ─── Public CSS tokens ─────────────────────────────────────────────────
|
|
69
|
+
Override on the consumer's scope class (via styleClassPassthrough).
|
|
70
|
+
Nested --responsive-header-* / --overflow-nav-* tokens still apply —
|
|
71
|
+
forward a scope class via navStyleClassPassthrough to reach them.
|
|
72
|
+
|
|
73
|
+
--site-header-gap (default: 2.4rem)
|
|
74
|
+
--site-header-padding-inline (default: 2.4rem)
|
|
75
|
+
--site-header-padding-block (default: 0px)
|
|
76
|
+
--site-header-bg (default: transparent)
|
|
77
|
+
--site-header-position (default: static)
|
|
78
|
+
--site-header-sticky-offset (default: 0px)
|
|
79
|
+
--site-header-z-index (default: 9)
|
|
80
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
81
|
+
|
|
82
|
+
.site-header {
|
|
83
|
+
display: grid;
|
|
84
|
+
grid-template-columns: auto 1fr;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: var(--site-header-gap, 2.4rem);
|
|
87
|
+
padding-block: var(--site-header-padding-block, 0px);
|
|
88
|
+
padding-inline: var(--site-header-padding-inline, 2.4rem);
|
|
89
|
+
background-color: var(--site-header-bg, transparent);
|
|
90
|
+
position: var(--site-header-position, static);
|
|
91
|
+
top: var(--site-header-sticky-offset, 0px);
|
|
92
|
+
z-index: var(--site-header-z-index, 9);
|
|
93
|
+
width: 100%;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import SiteHeader from "../SiteHeader.vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof SiteHeader> = {
|
|
5
|
+
title: "Organisms/Site Header",
|
|
6
|
+
component: SiteHeader,
|
|
7
|
+
argTypes: {
|
|
8
|
+
responsiveNavLinks: {
|
|
9
|
+
control: "object",
|
|
10
|
+
description: "Nav groups keyed by name — each item is either a link (`path`) or a dropdown (`childLinks`)",
|
|
11
|
+
},
|
|
12
|
+
gapBetweenFirstAndSecondNav: {
|
|
13
|
+
control: { type: "number", min: 0, step: 4 },
|
|
14
|
+
description: "Gap in pixels between the first and second nav groups",
|
|
15
|
+
},
|
|
16
|
+
collapseAtMainNavIntersection: {
|
|
17
|
+
control: "boolean",
|
|
18
|
+
description: "Collapse the main nav into the overflow burger menu once it no longer fits",
|
|
19
|
+
},
|
|
20
|
+
allowExpandOnGesture: {
|
|
21
|
+
control: "boolean",
|
|
22
|
+
description: "Allow the collapsed nav to expand on touch/gesture",
|
|
23
|
+
},
|
|
24
|
+
pageRowVariant: {
|
|
25
|
+
control: { type: "radio" },
|
|
26
|
+
options: ["full", "popout", "content", "inset-content"],
|
|
27
|
+
description: "Variant forwarded to the root PageRow",
|
|
28
|
+
},
|
|
29
|
+
styleClassPassthrough: {
|
|
30
|
+
control: "object",
|
|
31
|
+
description: "Extra CSS classes applied to the root PageRow (targets --site-header-* tokens)",
|
|
32
|
+
},
|
|
33
|
+
navStyleClassPassthrough: {
|
|
34
|
+
control: "object",
|
|
35
|
+
description: "Extra CSS classes applied to the nested ResponsiveHeader (targets its own token API)",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
args: {
|
|
39
|
+
pageRowVariant: "content",
|
|
40
|
+
gapBetweenFirstAndSecondNav: 12,
|
|
41
|
+
collapseAtMainNavIntersection: false,
|
|
42
|
+
allowExpandOnGesture: true,
|
|
43
|
+
styleClassPassthrough: [],
|
|
44
|
+
navStyleClassPassthrough: [],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default meta;
|
|
49
|
+
type Story = StoryObj<typeof SiteHeader>;
|
|
50
|
+
|
|
51
|
+
export const Default: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
responsiveNavLinks: {
|
|
54
|
+
firstNav: [
|
|
55
|
+
{
|
|
56
|
+
name: "Components",
|
|
57
|
+
childLinksTitle: "UI Components",
|
|
58
|
+
childLinks: [
|
|
59
|
+
{ name: "Buttons", path: "/forms/examples/buttons" },
|
|
60
|
+
{ name: "Tabs", path: "/ui/tabs" },
|
|
61
|
+
{ name: "Carousels", path: "/ui/carousel-basic" },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{ name: "Typography", path: "/typography/page-heading" },
|
|
65
|
+
],
|
|
66
|
+
secondNav: [{ name: "Contact", path: "#" }],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
render: (args) => ({
|
|
70
|
+
components: { SiteHeader },
|
|
71
|
+
setup() {
|
|
72
|
+
return { args };
|
|
73
|
+
},
|
|
74
|
+
template: `
|
|
75
|
+
<SiteHeader v-bind="args">
|
|
76
|
+
<template #branding>
|
|
77
|
+
<a href="/" style="color:inherit;text-decoration:none;font-weight:600">Brand</a>
|
|
78
|
+
</template>
|
|
79
|
+
</SiteHeader>
|
|
80
|
+
`,
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** A single flat nav group with no dropdowns — the shape a lighter, few-page site would use. */
|
|
85
|
+
export const SimpleFlatNav: Story = {
|
|
86
|
+
name: "Simple Flat Nav (no dropdowns)",
|
|
87
|
+
args: {
|
|
88
|
+
responsiveNavLinks: {
|
|
89
|
+
main: [
|
|
90
|
+
{ name: "Home", path: "/" },
|
|
91
|
+
{ name: "About", path: "/about" },
|
|
92
|
+
{ name: "Services", path: "/services" },
|
|
93
|
+
{ name: "Contact", path: "/contact" },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
render: (args) => ({
|
|
98
|
+
components: { SiteHeader },
|
|
99
|
+
setup() {
|
|
100
|
+
return { args };
|
|
101
|
+
},
|
|
102
|
+
template: `
|
|
103
|
+
<SiteHeader v-bind="args">
|
|
104
|
+
<template #branding>
|
|
105
|
+
<a href="/" style="color:inherit;text-decoration:none;font-weight:600">Brand</a>
|
|
106
|
+
</template>
|
|
107
|
+
</SiteHeader>
|
|
108
|
+
`,
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const WithSecondaryNavigationSlot: Story = {
|
|
113
|
+
name: "With secondaryNavigation slot",
|
|
114
|
+
args: {
|
|
115
|
+
responsiveNavLinks: {
|
|
116
|
+
firstNav: [{ name: "Home", path: "/" }],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
render: (args) => ({
|
|
120
|
+
components: { SiteHeader },
|
|
121
|
+
setup() {
|
|
122
|
+
return { args };
|
|
123
|
+
},
|
|
124
|
+
template: `
|
|
125
|
+
<SiteHeader v-bind="args">
|
|
126
|
+
<template #branding>
|
|
127
|
+
<a href="/" style="color:inherit;text-decoration:none;font-weight:600">Brand</a>
|
|
128
|
+
</template>
|
|
129
|
+
<template #secondaryNavigation>
|
|
130
|
+
<a href="/settings" aria-label="Settings">⚙</a>
|
|
131
|
+
</template>
|
|
132
|
+
</SiteHeader>
|
|
133
|
+
`,
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
|
+
import SiteHeader from "../SiteHeader.vue";
|
|
4
|
+
import type { ResponsiveHeaderProp } from "../../../../types/components";
|
|
5
|
+
|
|
6
|
+
const navLinks: ResponsiveHeaderProp = {
|
|
7
|
+
firstNav: [{ name: "Home", path: "/" }],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const responsiveHeaderStub = {
|
|
11
|
+
name: "ResponsiveHeader",
|
|
12
|
+
props: [
|
|
13
|
+
"responsiveNavLinks",
|
|
14
|
+
"gapBetweenFirstAndSecondNav",
|
|
15
|
+
"overflowDetailsSummaryIcons",
|
|
16
|
+
"collapseBreakpoint",
|
|
17
|
+
"collapseAtMainNavIntersection",
|
|
18
|
+
"allowExpandOnGesture",
|
|
19
|
+
"styleClassPassthrough",
|
|
20
|
+
],
|
|
21
|
+
template: `<div class="responsive-header-stub"><slot name="secondaryNavigation"></slot></div>`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe("SiteHeader", () => {
|
|
25
|
+
it("mounts without error", async () => {
|
|
26
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
27
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
28
|
+
});
|
|
29
|
+
expect(wrapper.vm).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("renders the branding slot inside SkipLinks' homeLink slot", async () => {
|
|
33
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
34
|
+
slots: { branding: "<a href='/' class='brand-link'>Brand</a>" },
|
|
35
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
36
|
+
});
|
|
37
|
+
expect(wrapper.find(".home-link .brand-link").exists()).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("does not render the secondaryNavigation slot when not provided", async () => {
|
|
41
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
42
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
43
|
+
});
|
|
44
|
+
expect(wrapper.text()).not.toContain("Extra nav");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("forwards the secondaryNavigation slot through to ResponsiveHeader", async () => {
|
|
48
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
49
|
+
slots: { secondaryNavigation: "<span>Extra nav</span>" },
|
|
50
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
51
|
+
});
|
|
52
|
+
expect(wrapper.text()).toContain("Extra nav");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("forwards responsiveNavLinks and other nav props to ResponsiveHeader", async () => {
|
|
56
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
57
|
+
props: {
|
|
58
|
+
responsiveNavLinks: navLinks,
|
|
59
|
+
gapBetweenFirstAndSecondNav: 24,
|
|
60
|
+
collapseAtMainNavIntersection: true,
|
|
61
|
+
allowExpandOnGesture: false,
|
|
62
|
+
navStyleClassPassthrough: ["site-header-nav"],
|
|
63
|
+
},
|
|
64
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
65
|
+
});
|
|
66
|
+
const responsiveHeader = wrapper.findComponent(responsiveHeaderStub);
|
|
67
|
+
expect(responsiveHeader.props("responsiveNavLinks")).toEqual(navLinks);
|
|
68
|
+
expect(responsiveHeader.props("gapBetweenFirstAndSecondNav")).toBe(24);
|
|
69
|
+
expect(responsiveHeader.props("collapseAtMainNavIntersection")).toBe(true);
|
|
70
|
+
expect(responsiveHeader.props("allowExpandOnGesture")).toBe(false);
|
|
71
|
+
expect(responsiveHeader.props("styleClassPassthrough")).toEqual(["site-header-nav"]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("passes pageRowVariant through to the root PageRow", async () => {
|
|
75
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
76
|
+
props: { pageRowVariant: "full" },
|
|
77
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
78
|
+
});
|
|
79
|
+
expect(wrapper.find(".page-row.full").exists()).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("defaults pageRowVariant to content", async () => {
|
|
83
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
84
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
85
|
+
});
|
|
86
|
+
expect(wrapper.find(".page-row.content").exists()).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("applies styleClassPassthrough to the root PageRow", async () => {
|
|
90
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
91
|
+
props: { styleClassPassthrough: ["header"] },
|
|
92
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
93
|
+
});
|
|
94
|
+
expect(wrapper.find(".page-row.header").exists()).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("renders correct HTML structure with default props", async () => {
|
|
98
|
+
const wrapper = await mountSuspended(SiteHeader, {
|
|
99
|
+
global: { stubs: { ResponsiveHeader: responsiveHeaderStub } },
|
|
100
|
+
});
|
|
101
|
+
expect(wrapper.html()).toMatchSnapshot();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`SiteHeader > renders correct HTML structure with default props 1`] = `
|
|
4
|
+
"<div class="page-row content">
|
|
5
|
+
<header class="site-header">
|
|
6
|
+
<nav class="home-navigation" aria-label="Home Navigation">
|
|
7
|
+
<div class="home-link-navigation">
|
|
8
|
+
<div class="home-link"></div>
|
|
9
|
+
<nav class="skip-links-nav" aria-label="Skip navigation"><a href="#main-content" class="skip-link">Skip to main content</a><a href="#footer-content" class="skip-link">Skip to footer</a></nav>
|
|
10
|
+
</div>
|
|
11
|
+
</nav>
|
|
12
|
+
<div class="responsive-header-stub"></div>
|
|
13
|
+
</header>
|
|
14
|
+
</div>"
|
|
15
|
+
`;
|
package/app/layouts/default.vue
CHANGED
|
@@ -1,43 +1,35 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="page-layout">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
more: 'gravity-ui:ellipsis',
|
|
22
|
-
burger: 'gravity-ui:bars',
|
|
23
|
-
}"
|
|
24
|
-
:collapse-at-main-nav-intersection="false"
|
|
25
|
-
:allow-expand-on-gesture="false"
|
|
26
|
-
>
|
|
27
|
-
<template #secondaryNavigation>
|
|
28
|
-
<ul class="secondary-navigation-list">
|
|
29
|
-
<li class="secondary-navigation-item">
|
|
30
|
-
<NuxtLink class="secondary-navigation-link" to="/">
|
|
31
|
-
<Icon name="material-symbols:settings-outline-rounded" class="icon" aria-hidden="true" />
|
|
32
|
-
<span class="sr-only">Settings</span>
|
|
33
|
-
</NuxtLink>
|
|
34
|
-
</li>
|
|
35
|
-
</ul>
|
|
36
|
-
</template>
|
|
37
|
-
</ResponsiveHeader>
|
|
38
|
-
</header>
|
|
3
|
+
<SiteHeader
|
|
4
|
+
:responsive-nav-links
|
|
5
|
+
:gap-between-first-and-second-nav="12"
|
|
6
|
+
page-row-variant="full"
|
|
7
|
+
:style-class-passthrough="['header']"
|
|
8
|
+
:nav-style-class-passthrough="['site-header-nav']"
|
|
9
|
+
:overflow-details-summary-icons="{
|
|
10
|
+
more: 'gravity-ui:ellipsis',
|
|
11
|
+
burger: 'gravity-ui:bars',
|
|
12
|
+
}"
|
|
13
|
+
:collapse-at-main-nav-intersection="false"
|
|
14
|
+
:allow-expand-on-gesture="false"
|
|
15
|
+
>
|
|
16
|
+
<template #branding>
|
|
17
|
+
<NuxtLink to="/" class="home-link">
|
|
18
|
+
SRCDEV
|
|
19
|
+
<span>components</span>
|
|
20
|
+
</NuxtLink>
|
|
39
21
|
</template>
|
|
40
|
-
|
|
22
|
+
<template #secondaryNavigation>
|
|
23
|
+
<ul class="secondary-navigation-list">
|
|
24
|
+
<li class="secondary-navigation-item">
|
|
25
|
+
<NuxtLink class="secondary-navigation-link" to="/">
|
|
26
|
+
<Icon name="material-symbols:settings-outline-rounded" class="icon" aria-hidden="true" />
|
|
27
|
+
<span class="sr-only">Settings</span>
|
|
28
|
+
</NuxtLink>
|
|
29
|
+
</li>
|
|
30
|
+
</ul>
|
|
31
|
+
</template>
|
|
32
|
+
</SiteHeader>
|
|
41
33
|
<PageRow id="main-content" class="main-content" tag="main" variant="full" :is-landmark="true">
|
|
42
34
|
<template #default>
|
|
43
35
|
<slot name="layout-content">Page content goes here</slot>
|
|
@@ -231,39 +223,31 @@ onMounted(() => {
|
|
|
231
223
|
|
|
232
224
|
width: 100%;
|
|
233
225
|
|
|
234
|
-
.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
padding-inline: 24px;
|
|
241
|
-
background-color: #000;
|
|
226
|
+
/* SiteHeader token overrides — see .claude/skills/components/site-header.md */
|
|
227
|
+
--site-header-gap: 2.4rem;
|
|
228
|
+
--site-header-padding-inline: 2.4rem;
|
|
229
|
+
--site-header-bg: #000;
|
|
230
|
+
--site-header-position: relative;
|
|
231
|
+
--site-header-z-index: 9;
|
|
242
232
|
|
|
243
|
-
|
|
244
|
-
|
|
233
|
+
.home-link {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
236
|
+
text-wrap-mode: nowrap;
|
|
237
|
+
font-size: var(--step-5);
|
|
238
|
+
letter-spacing: 0.2em;
|
|
239
|
+
color: var(--slate-00);
|
|
240
|
+
text-decoration: none;
|
|
245
241
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
flex-direction: column;
|
|
251
|
-
text-wrap-mode: nowrap;
|
|
252
|
-
font-size: var(--step-5);
|
|
253
|
-
letter-spacing: 0.2em;
|
|
254
|
-
color: var(--slate-00);
|
|
255
|
-
text-decoration: none;
|
|
256
|
-
|
|
257
|
-
span {
|
|
258
|
-
font-size: var(--step-3);
|
|
259
|
-
letter-spacing: initial;
|
|
260
|
-
}
|
|
242
|
+
span {
|
|
243
|
+
font-size: var(--step-3);
|
|
244
|
+
letter-spacing: initial;
|
|
245
|
+
}
|
|
261
246
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
247
|
+
&:hover {
|
|
248
|
+
border-radius: 0.4rem;
|
|
249
|
+
outline: 2px solid var(--green-08);
|
|
250
|
+
outline-offset: 0.4rem;
|
|
267
251
|
}
|
|
268
252
|
}
|
|
269
253
|
}
|
|
@@ -282,6 +266,19 @@ onMounted(() => {
|
|
|
282
266
|
--responsive-header-color: var(--slate-00);
|
|
283
267
|
--responsive-header-link-color: var(--slate-00);
|
|
284
268
|
|
|
269
|
+
/* Fixed (non-fluid) nav-link font-size — deliberately NOT var(--step-*).
|
|
270
|
+
A vw-based clamp() here drifts with the scrollbar (see
|
|
271
|
+
ResponsiveHeader.vue's token comment) without ever re-triggering the
|
|
272
|
+
overflow-collapse ResizeObserver, silently letting one extra item fit
|
|
273
|
+
that shouldn't. Values TBC after a visual pass. */
|
|
274
|
+
--responsive-header-link-font-size: 1.2rem;
|
|
275
|
+
@media (min-width: 768px) {
|
|
276
|
+
--responsive-header-link-font-size: 1.4rem;
|
|
277
|
+
}
|
|
278
|
+
@media (min-width: 1024px) {
|
|
279
|
+
--responsive-header-link-font-size: 1.5rem;
|
|
280
|
+
}
|
|
281
|
+
|
|
285
282
|
--responsive-header-sub-nav-bg: #000;
|
|
286
283
|
--responsive-header-sub-nav-border: 1px solid #efefef75;
|
|
287
284
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srcdev-nuxt-components",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.3.
|
|
4
|
+
"version": "9.3.1",
|
|
5
5
|
"main": "nuxt.config.ts",
|
|
6
6
|
"types": "types.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=24.0.0"
|
|
10
|
+
},
|
|
8
11
|
"scripts": {
|
|
9
12
|
"postinstall": "node scripts/copy-snippets.mjs",
|
|
10
13
|
"clean": "rm -rf .nuxt && rm -rf .output",
|