srcdev-nuxt-components 9.2.4 → 9.2.6
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 +35 -1
- package/.claude/skills/components/content-docs.md +165 -0
- package/.claude/skills/components/expanding-panel.md +55 -0
- package/.claude/skills/index.md +4 -2
- package/.claude/skills/qa-panel.md +5 -6
- package/.claude/skills/setup-postinstall.md +11 -5
- package/.claude/skills/theming-typography-tokens.md +109 -0
- package/.vscode/srcdev-component-content-docs.code-snippets +129 -0
- package/.vscode/srcdev-component-expanding-panel.code-snippets +123 -0
- package/README.md +10 -4
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +410 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/playwright/content-docs.playwright.ts +53 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/playwright/content-docs.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/playwright/content-docs.playwright.ts-snapshots/state-desktop-chromium-darwin.png +0 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/playwright/content-docs.playwright.ts-snapshots/state-mobile-chromium-darwin.png +0 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/playwright/content-docs.playwright.ts-snapshots/state-tablet-chromium-darwin.png +0 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/stories/ContentDocs.stories.ts +172 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/tests/ContentDocs.spec.ts +218 -0
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +103 -0
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +97 -56
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +50 -2
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +79 -2
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +25 -4
- package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +4 -0
- package/app/components/02.molecules/pricing-card/PricingCard.vue +5 -2
- package/app/components/02.molecules/pricing-card/tests/PricingCard.spec.ts +15 -1
- package/app/components/layout-grids/LayoutGridA.vue +59 -59
- package/app/composables/tests/useContainerBreakpoints.spec.ts +91 -0
- package/app/composables/useContainerBreakpoints.ts +71 -0
- package/app/layouts/default.vue +1 -0
- package/app/pages/ui/expanding-panel.vue +266 -129
- package/app/pages/ui/layout-content-docs.vue +72 -0
- package/app/pages/ui/layout-grid-a.vue +5 -58
- package/app/types/components/content-docs.d.ts +5 -0
- package/app/types/components/index.ts +1 -0
- package/package.json +1 -1
|
@@ -23,83 +23,83 @@ const props = defineProps({
|
|
|
23
23
|
type: [String, Array] as PropType<string | string[]>,
|
|
24
24
|
default: () => [],
|
|
25
25
|
},
|
|
26
|
-
})
|
|
26
|
+
});
|
|
27
27
|
|
|
28
|
-
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
|
|
28
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
29
29
|
|
|
30
30
|
watch(
|
|
31
31
|
() => props.styleClassPassthrough,
|
|
32
32
|
() => {
|
|
33
|
-
resetElementClasses(props.styleClassPassthrough)
|
|
33
|
+
resetElementClasses(props.styleClassPassthrough);
|
|
34
34
|
}
|
|
35
|
-
)
|
|
35
|
+
);
|
|
36
36
|
</script>
|
|
37
37
|
|
|
38
38
|
<style lang="css">
|
|
39
39
|
@layer components {
|
|
40
|
-
.layout-grid-a-wrapper {
|
|
41
|
-
|
|
40
|
+
.layout-grid-a-wrapper {
|
|
41
|
+
container-type: inline-size;
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
.layout-grid-a {
|
|
44
|
+
display: grid;
|
|
45
|
+
gap: 20px;
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@container (min-width: 768px) {
|
|
54
|
-
grid-template-columns: repeat(2, auto);
|
|
55
|
-
grid-template-rows: repeat(3, auto);
|
|
56
|
-
|
|
57
|
-
.slot1 {
|
|
58
|
-
grid-column: 1 / span 2;
|
|
59
|
-
grid-row: 1;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.slot2 {
|
|
63
|
-
grid-column: 1 / span 2;
|
|
64
|
-
grid-row: 2;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.slot3 {
|
|
68
|
-
grid-column: 1;
|
|
69
|
-
grid-row: 3;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.slot4 {
|
|
73
|
-
grid-column: 2;
|
|
74
|
-
grid-row: 3;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
@container (min-width: 1060px) {
|
|
79
|
-
grid-template-columns: repeat(3, auto);
|
|
80
|
-
grid-template-rows: repeat(2, auto);
|
|
81
|
-
|
|
82
|
-
.slot1 {
|
|
83
|
-
grid-column: 1;
|
|
84
|
-
grid-row: 1 / span 2;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.slot2 {
|
|
88
|
-
grid-column: 2 / span 2;
|
|
89
|
-
grid-row: 1;
|
|
47
|
+
& > div {
|
|
48
|
+
padding: 20px;
|
|
49
|
+
outline: 1px solid light-dark(black, white);
|
|
50
|
+
border-radius: 5px;
|
|
90
51
|
}
|
|
91
52
|
|
|
92
|
-
|
|
93
|
-
grid-
|
|
94
|
-
grid-
|
|
53
|
+
@container (min-width: 768px) {
|
|
54
|
+
grid-template-columns: repeat(2, auto);
|
|
55
|
+
grid-template-rows: repeat(3, auto);
|
|
56
|
+
|
|
57
|
+
.slot1 {
|
|
58
|
+
grid-column: 1 / span 2;
|
|
59
|
+
grid-row: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.slot2 {
|
|
63
|
+
grid-column: 1 / span 2;
|
|
64
|
+
grid-row: 2;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.slot3 {
|
|
68
|
+
grid-column: 1;
|
|
69
|
+
grid-row: 3;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.slot4 {
|
|
73
|
+
grid-column: 2;
|
|
74
|
+
grid-row: 3;
|
|
75
|
+
}
|
|
95
76
|
}
|
|
96
77
|
|
|
97
|
-
|
|
98
|
-
grid-
|
|
99
|
-
grid-
|
|
78
|
+
@container (min-width: 1060px) {
|
|
79
|
+
grid-template-columns: repeat(3, auto);
|
|
80
|
+
grid-template-rows: repeat(2, auto);
|
|
81
|
+
|
|
82
|
+
.slot1 {
|
|
83
|
+
grid-column: 1;
|
|
84
|
+
grid-row: 1 / span 2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.slot2 {
|
|
88
|
+
grid-column: 2 / span 2;
|
|
89
|
+
grid-row: 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.slot3 {
|
|
93
|
+
grid-column: 2;
|
|
94
|
+
grid-row: 2;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.slot4 {
|
|
98
|
+
grid-column: 3;
|
|
99
|
+
grid-row: 2;
|
|
100
|
+
}
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
|
-
}
|
|
105
105
|
</style>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { nextTick, ref } from "vue";
|
|
3
|
+
import { useContainerBreakpoints } from "../useContainerBreakpoints";
|
|
4
|
+
|
|
5
|
+
let resizeCallback: ResizeObserverCallback | null = null;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
resizeCallback = null;
|
|
9
|
+
vi.stubGlobal(
|
|
10
|
+
"ResizeObserver",
|
|
11
|
+
vi.fn((callback: ResizeObserverCallback) => {
|
|
12
|
+
resizeCallback = callback;
|
|
13
|
+
return {
|
|
14
|
+
observe: vi.fn(),
|
|
15
|
+
unobserve: vi.fn(),
|
|
16
|
+
disconnect: vi.fn(),
|
|
17
|
+
};
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function triggerResize(el: HTMLElement, width: number) {
|
|
23
|
+
Object.defineProperty(el, "offsetWidth", { value: width, configurable: true });
|
|
24
|
+
resizeCallback?.(
|
|
25
|
+
[{ contentRect: { width, height: 0 } } as unknown as ResizeObserverEntry],
|
|
26
|
+
{} as ResizeObserver
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("useContainerBreakpoints", () => {
|
|
31
|
+
it("defaults active to base when narrower than the smallest breakpoint", () => {
|
|
32
|
+
const el = ref<HTMLElement | null>(document.createElement("div"));
|
|
33
|
+
const { active } = useContainerBreakpoints(undefined, el);
|
|
34
|
+
expect(active.value).toBe("base");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("updates active as the observed element's width crosses breakpoints", async () => {
|
|
38
|
+
const el = ref<HTMLElement | null>(document.createElement("div"));
|
|
39
|
+
const { active } = useContainerBreakpoints(undefined, el);
|
|
40
|
+
|
|
41
|
+
triggerResize(el.value as HTMLElement, 700);
|
|
42
|
+
await nextTick();
|
|
43
|
+
expect(active.value).toBe("sm");
|
|
44
|
+
|
|
45
|
+
triggerResize(el.value as HTMLElement, 1300);
|
|
46
|
+
await nextTick();
|
|
47
|
+
expect(active.value).toBe("xl");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("greaterOrEqual/smaller reflect the current width against a named breakpoint", async () => {
|
|
51
|
+
const el = ref<HTMLElement | null>(document.createElement("div"));
|
|
52
|
+
const { greaterOrEqual, smaller } = useContainerBreakpoints(undefined, el);
|
|
53
|
+
const isLg = greaterOrEqual("lg");
|
|
54
|
+
const isSmallerThanMd = smaller("md");
|
|
55
|
+
|
|
56
|
+
expect(isLg.value).toBe(false);
|
|
57
|
+
expect(isSmallerThanMd.value).toBe(true);
|
|
58
|
+
|
|
59
|
+
triggerResize(el.value as HTMLElement, 1100);
|
|
60
|
+
await nextTick();
|
|
61
|
+
expect(isLg.value).toBe(true);
|
|
62
|
+
expect(isSmallerThanMd.value).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("between returns true only within the [min, max) range", async () => {
|
|
66
|
+
const el = ref<HTMLElement | null>(document.createElement("div"));
|
|
67
|
+
const { between } = useContainerBreakpoints(undefined, el);
|
|
68
|
+
const isTablet = between("sm", "lg");
|
|
69
|
+
|
|
70
|
+
triggerResize(el.value as HTMLElement, 800);
|
|
71
|
+
await nextTick();
|
|
72
|
+
expect(isTablet.value).toBe(true);
|
|
73
|
+
|
|
74
|
+
triggerResize(el.value as HTMLElement, 1024);
|
|
75
|
+
await nextTick();
|
|
76
|
+
expect(isTablet.value).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("supports a custom breakpoints map", async () => {
|
|
80
|
+
const el = ref<HTMLElement | null>(document.createElement("div"));
|
|
81
|
+
const { active } = useContainerBreakpoints({ narrow: 400, wide: 900 }, el);
|
|
82
|
+
|
|
83
|
+
triggerResize(el.value as HTMLElement, 500);
|
|
84
|
+
await nextTick();
|
|
85
|
+
expect(active.value).toBe("narrow");
|
|
86
|
+
|
|
87
|
+
triggerResize(el.value as HTMLElement, 950);
|
|
88
|
+
await nextTick();
|
|
89
|
+
expect(active.value).toBe("wide");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Ref } from "vue";
|
|
2
|
+
import { useElementSize } from "@vueuse/core";
|
|
3
|
+
|
|
4
|
+
export const containerBreakpointsDefault = {
|
|
5
|
+
sm: 640,
|
|
6
|
+
md: 768,
|
|
7
|
+
lg: 1024,
|
|
8
|
+
xl: 1280,
|
|
9
|
+
"2xl": 1536,
|
|
10
|
+
"4k": 2560,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export type ContainerBreakpointName = keyof typeof containerBreakpointsDefault;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* useContainerBreakpoints composable
|
|
17
|
+
* Container-query equivalent of VueUse's useBreakpoints — tracks an element's
|
|
18
|
+
* own width via ResizeObserver instead of the viewport, for layouts where
|
|
19
|
+
* page decoration (nav, sidebars) means viewport width != available width.
|
|
20
|
+
* @param breakpoints - map of breakpoint name to min-width in px (default: containerBreakpointsDefault)
|
|
21
|
+
* @param target - optional existing element ref to observe; creates its own if omitted
|
|
22
|
+
* @returns { el, width, active, greater, greaterOrEqual, smaller, smallerOrEqual, between }
|
|
23
|
+
*/
|
|
24
|
+
export function useContainerBreakpoints<K extends string = ContainerBreakpointName>(
|
|
25
|
+
breakpoints: Record<K, number> = containerBreakpointsDefault as unknown as Record<K, number>,
|
|
26
|
+
target?: Ref<HTMLElement | null>
|
|
27
|
+
) {
|
|
28
|
+
const el = target ?? ref<HTMLElement | null>(null);
|
|
29
|
+
const { width } = useElementSize(el);
|
|
30
|
+
|
|
31
|
+
const sortedEntries = (Object.entries(breakpoints) as [K, number][]).sort((a, b) => a[1] - b[1]);
|
|
32
|
+
|
|
33
|
+
function greaterOrEqual(name: K) {
|
|
34
|
+
return computed(() => width.value >= breakpoints[name]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function greater(name: K) {
|
|
38
|
+
return computed(() => width.value > breakpoints[name]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function smaller(name: K) {
|
|
42
|
+
return computed(() => width.value < breakpoints[name]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function smallerOrEqual(name: K) {
|
|
46
|
+
return computed(() => width.value <= breakpoints[name]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function between(min: K, max: K) {
|
|
50
|
+
return computed(() => width.value >= breakpoints[min] && width.value < breakpoints[max]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const active = computed<K | "base">(() => {
|
|
54
|
+
let current: K | "base" = "base";
|
|
55
|
+
for (const [name, minWidth] of sortedEntries) {
|
|
56
|
+
if (width.value >= minWidth) current = name;
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
el,
|
|
63
|
+
width,
|
|
64
|
+
active,
|
|
65
|
+
greater,
|
|
66
|
+
greaterOrEqual,
|
|
67
|
+
smaller,
|
|
68
|
+
smallerOrEqual,
|
|
69
|
+
between,
|
|
70
|
+
};
|
|
71
|
+
}
|
package/app/layouts/default.vue
CHANGED
|
@@ -110,6 +110,7 @@ const responsiveNavLinks = {
|
|
|
110
110
|
{ name: "Layout Grid A", path: "/ui/layout-grid-a" },
|
|
111
111
|
{ name: "Layout Grid B", path: "/ui/layout-grid-b" },
|
|
112
112
|
{ name: "Simple Grid", path: "/ui/simple-grid" },
|
|
113
|
+
{ name: "Layout Content Docs", path: "/ui/layout-content-docs" },
|
|
113
114
|
{ name: "Masonry Grid Simple", path: "/ui/masonry-grid" },
|
|
114
115
|
{ name: "Masonry Grid Sorted", path: "/ui/masonry-grid-sorted" },
|
|
115
116
|
{ name: "Masonry Grid Ordered", path: "/ui/masonry-grid-ordered" },
|