@nationaldesignstudio/react 0.1.0 → 0.3.0
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/dist/component-registry.md +46 -19
- package/dist/components/atoms/accordion/accordion.d.ts +7 -7
- package/dist/components/atoms/background/background.d.ts +13 -27
- package/dist/components/atoms/button/button.d.ts +55 -71
- package/dist/components/atoms/button/icon-button.d.ts +62 -110
- package/dist/components/atoms/input/input-group.d.ts +278 -0
- package/dist/components/atoms/input/input.d.ts +121 -0
- package/dist/components/atoms/select/select.d.ts +131 -0
- package/dist/components/organisms/card/card.d.ts +2 -2
- package/dist/components/sections/banner/banner.d.ts +9 -9
- package/dist/components/sections/faq-section/faq-section.d.ts +1 -1
- package/dist/components/sections/hero/hero.d.ts +115 -18
- package/dist/components/sections/prose/prose.d.ts +3 -3
- package/dist/components/sections/river/river.d.ts +1 -1
- package/dist/components/sections/tout/tout.d.ts +9 -9
- package/dist/components/sections/two-column-section/two-column-section.d.ts +7 -21
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11075 -7841
- package/dist/index.js.map +1 -1
- package/dist/lib/form-control.d.ts +105 -0
- package/dist/tokens.css +2144 -17341
- package/package.json +1 -1
- package/src/components/atoms/accordion/accordion.test.tsx +18 -20
- package/src/components/atoms/accordion/accordion.tsx +19 -17
- package/src/components/atoms/background/background.test.tsx +2 -2
- package/src/components/atoms/background/background.tsx +77 -96
- package/src/components/atoms/button/button.stories.tsx +42 -0
- package/src/components/atoms/button/button.test.tsx +1 -1
- package/src/components/atoms/button/button.tsx +38 -103
- package/src/components/atoms/button/button.visual.test.tsx +70 -24
- package/src/components/atoms/button/icon-button.tsx +80 -188
- package/src/components/atoms/input/index.ts +17 -0
- package/src/components/atoms/input/input-group.stories.tsx +650 -0
- package/src/components/atoms/input/input-group.test.tsx +376 -0
- package/src/components/atoms/input/input-group.tsx +384 -0
- package/src/components/atoms/input/input.stories.tsx +232 -0
- package/src/components/atoms/input/input.test.tsx +183 -0
- package/src/components/atoms/input/input.tsx +97 -0
- package/src/components/atoms/select/index.ts +18 -0
- package/src/components/atoms/select/select.stories.tsx +455 -0
- package/src/components/atoms/select/select.tsx +320 -0
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +2 -6
- package/src/components/foundation/typography/typography.stories.tsx +401 -0
- package/src/components/organisms/card/card.stories.tsx +11 -11
- package/src/components/organisms/card/card.test.tsx +5 -3
- package/src/components/organisms/card/card.tsx +2 -2
- package/src/components/organisms/card/card.visual.test.tsx +6 -6
- package/src/components/organisms/navbar/navbar.tsx +2 -2
- package/src/components/organisms/navbar/navbar.visual.test.tsx +2 -2
- package/src/components/sections/banner/banner.stories.tsx +5 -1
- package/src/components/sections/banner/banner.tsx +10 -10
- package/src/components/sections/card-grid/card-grid.tsx +1 -1
- package/src/components/sections/faq-section/faq-section.stories.tsx +7 -7
- package/src/components/sections/faq-section/faq-section.tsx +5 -5
- package/src/components/sections/hero/hero.test.tsx +5 -5
- package/src/components/sections/hero/hero.tsx +33 -51
- package/src/components/sections/prose/prose.test.tsx +2 -2
- package/src/components/sections/prose/prose.tsx +4 -5
- package/src/components/sections/river/river.stories.tsx +8 -8
- package/src/components/sections/river/river.test.tsx +1 -1
- package/src/components/sections/river/river.tsx +2 -4
- package/src/components/sections/tout/tout.stories.tsx +31 -7
- package/src/components/sections/tout/tout.test.tsx +1 -1
- package/src/components/sections/tout/tout.tsx +8 -10
- package/src/components/sections/two-column-section/two-column-section.stories.tsx +11 -11
- package/src/components/sections/two-column-section/two-column-section.tsx +16 -10
- package/src/index.ts +41 -0
- package/src/lib/form-control.ts +69 -0
- package/src/stories/Introduction.mdx +29 -15
- package/src/stories/ThemeProvider.stories.tsx +1 -3
- package/src/stories/TokenShowcase.stories.tsx +0 -19
- package/src/stories/TokenShowcase.tsx +714 -1366
- package/src/styles.css +3 -0
- package/src/tests/token-resolution.test.tsx +301 -0
package/package.json
CHANGED
|
@@ -38,7 +38,7 @@ describe("Accordion", () => {
|
|
|
38
38
|
// The first item should be expanded (has data-open attribute)
|
|
39
39
|
await expect
|
|
40
40
|
.element(page.getByRole("button", { name: "Question 1" }))
|
|
41
|
-
.toHaveAttribute("
|
|
41
|
+
.toHaveAttribute("aria-expanded", "true");
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
test("renders with multiple default expanded items", async () => {
|
|
@@ -54,10 +54,10 @@ describe("Accordion", () => {
|
|
|
54
54
|
);
|
|
55
55
|
await expect
|
|
56
56
|
.element(page.getByRole("button", { name: "Question 1" }))
|
|
57
|
-
.toHaveAttribute("
|
|
57
|
+
.toHaveAttribute("aria-expanded", "true");
|
|
58
58
|
await expect
|
|
59
59
|
.element(page.getByRole("button", { name: "Question 2" }))
|
|
60
|
-
.toHaveAttribute("
|
|
60
|
+
.toHaveAttribute("aria-expanded", "true");
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
@@ -90,7 +90,7 @@ describe("Accordion", () => {
|
|
|
90
90
|
const trigger = page.getByRole("button", { name: "Question 1" });
|
|
91
91
|
trigger.element().focus();
|
|
92
92
|
await userEvent.keyboard("{Enter}");
|
|
93
|
-
await expect.element(trigger).toHaveAttribute("
|
|
93
|
+
await expect.element(trigger).toHaveAttribute("aria-expanded", "true");
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
test("Space key expands/collapses item", async () => {
|
|
@@ -104,7 +104,7 @@ describe("Accordion", () => {
|
|
|
104
104
|
const trigger = page.getByRole("button", { name: "Question 1" });
|
|
105
105
|
trigger.element().focus();
|
|
106
106
|
await userEvent.keyboard(" ");
|
|
107
|
-
await expect.element(trigger).toHaveAttribute("
|
|
107
|
+
await expect.element(trigger).toHaveAttribute("aria-expanded", "true");
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -119,7 +119,7 @@ describe("Accordion", () => {
|
|
|
119
119
|
);
|
|
120
120
|
const trigger = page.getByRole("button", { name: "Question 1" });
|
|
121
121
|
await trigger.click();
|
|
122
|
-
await expect.element(trigger).toHaveAttribute("
|
|
122
|
+
await expect.element(trigger).toHaveAttribute("aria-expanded", "true");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
test("clicking expanded trigger collapses item", async () => {
|
|
@@ -131,9 +131,9 @@ describe("Accordion", () => {
|
|
|
131
131
|
</Accordion>,
|
|
132
132
|
);
|
|
133
133
|
const trigger = page.getByRole("button", { name: "Question 1" });
|
|
134
|
-
await expect.element(trigger).toHaveAttribute("
|
|
134
|
+
await expect.element(trigger).toHaveAttribute("aria-expanded", "true");
|
|
135
135
|
await trigger.click();
|
|
136
|
-
await expect.element(trigger).
|
|
136
|
+
await expect.element(trigger).toHaveAttribute("aria-expanded", "false");
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
test("single mode collapses other items when opening new one", async () => {
|
|
@@ -150,10 +150,10 @@ describe("Accordion", () => {
|
|
|
150
150
|
const trigger1 = page.getByRole("button", { name: "Question 1" });
|
|
151
151
|
const trigger2 = page.getByRole("button", { name: "Question 2" });
|
|
152
152
|
|
|
153
|
-
await expect.element(trigger1).toHaveAttribute("
|
|
153
|
+
await expect.element(trigger1).toHaveAttribute("aria-expanded", "true");
|
|
154
154
|
await trigger2.click();
|
|
155
|
-
await expect.element(trigger2).toHaveAttribute("
|
|
156
|
-
await expect.element(trigger1).
|
|
155
|
+
await expect.element(trigger2).toHaveAttribute("aria-expanded", "true");
|
|
156
|
+
await expect.element(trigger1).toHaveAttribute("aria-expanded", "false");
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
test("multiple mode allows multiple items open", async () => {
|
|
@@ -170,15 +170,15 @@ describe("Accordion", () => {
|
|
|
170
170
|
const trigger1 = page.getByRole("button", { name: "Question 1" });
|
|
171
171
|
const trigger2 = page.getByRole("button", { name: "Question 2" });
|
|
172
172
|
|
|
173
|
-
await expect.element(trigger1).toHaveAttribute("
|
|
173
|
+
await expect.element(trigger1).toHaveAttribute("aria-expanded", "true");
|
|
174
174
|
await trigger2.click();
|
|
175
|
-
await expect.element(trigger1).toHaveAttribute("
|
|
176
|
-
await expect.element(trigger2).toHaveAttribute("
|
|
175
|
+
await expect.element(trigger1).toHaveAttribute("aria-expanded", "true");
|
|
176
|
+
await expect.element(trigger2).toHaveAttribute("aria-expanded", "true");
|
|
177
177
|
});
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
-
describe("
|
|
181
|
-
test("applies light
|
|
180
|
+
describe("Color Schemes", () => {
|
|
181
|
+
test("applies light colorScheme by default", async () => {
|
|
182
182
|
render(
|
|
183
183
|
<Accordion>
|
|
184
184
|
<AccordionItem id="item-1" title="Question 1">
|
|
@@ -187,20 +187,18 @@ describe("Accordion", () => {
|
|
|
187
187
|
</Accordion>,
|
|
188
188
|
);
|
|
189
189
|
const trigger = page.getByRole("button", { name: "Question 1" });
|
|
190
|
-
// Light variant has gray-800 text
|
|
191
190
|
await expect.element(trigger).toHaveClass(/text-gray-800/);
|
|
192
191
|
});
|
|
193
192
|
|
|
194
|
-
test("applies dark
|
|
193
|
+
test("applies dark colorScheme classes", async () => {
|
|
195
194
|
render(
|
|
196
|
-
<Accordion
|
|
195
|
+
<Accordion colorScheme="dark">
|
|
197
196
|
<AccordionItem id="item-1" title="Question 1">
|
|
198
197
|
Answer 1
|
|
199
198
|
</AccordionItem>
|
|
200
199
|
</Accordion>,
|
|
201
200
|
);
|
|
202
201
|
const trigger = page.getByRole("button", { name: "Question 1" });
|
|
203
|
-
// Dark variant has gray-100 text
|
|
204
202
|
await expect.element(trigger).toHaveClass(/text-gray-100/);
|
|
205
203
|
});
|
|
206
204
|
});
|
|
@@ -12,26 +12,26 @@ import { cn } from "@/lib/utils";
|
|
|
12
12
|
const accordionVariants = tv({
|
|
13
13
|
base: "flex flex-col",
|
|
14
14
|
variants: {
|
|
15
|
-
|
|
15
|
+
colorScheme: {
|
|
16
16
|
dark: "",
|
|
17
17
|
light: "",
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultVariants: {
|
|
21
|
-
|
|
21
|
+
colorScheme: "light",
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
const accordionItemVariants = tv({
|
|
26
26
|
base: "border-b overflow-hidden",
|
|
27
27
|
variants: {
|
|
28
|
-
|
|
28
|
+
colorScheme: {
|
|
29
29
|
dark: "border-gray-300",
|
|
30
30
|
light: "border-gray-500",
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
33
|
defaultVariants: {
|
|
34
|
-
|
|
34
|
+
colorScheme: "light",
|
|
35
35
|
},
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -42,13 +42,13 @@ const accordionTriggerVariants = tv({
|
|
|
42
42
|
"typography-body-large transition-colors cursor-pointer",
|
|
43
43
|
],
|
|
44
44
|
variants: {
|
|
45
|
-
|
|
45
|
+
colorScheme: {
|
|
46
46
|
dark: "text-gray-100 data-[open]:text-white",
|
|
47
47
|
light: "text-gray-800 data-[open]:text-gray-900",
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
defaultVariants: {
|
|
51
|
-
|
|
51
|
+
colorScheme: "light",
|
|
52
52
|
},
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -56,13 +56,13 @@ const accordionPanelVariants = tv({
|
|
|
56
56
|
// Uses primitive spacing tokens
|
|
57
57
|
base: "typography-body-large pb-spacing-24",
|
|
58
58
|
variants: {
|
|
59
|
-
|
|
59
|
+
colorScheme: {
|
|
60
60
|
dark: "text-gray-100",
|
|
61
61
|
light: "text-gray-800",
|
|
62
62
|
},
|
|
63
63
|
},
|
|
64
64
|
defaultVariants: {
|
|
65
|
-
|
|
65
|
+
colorScheme: "light",
|
|
66
66
|
},
|
|
67
67
|
});
|
|
68
68
|
|
|
@@ -71,9 +71,9 @@ const accordionPanelVariants = tv({
|
|
|
71
71
|
// =============================================================================
|
|
72
72
|
|
|
73
73
|
const AccordionContext = React.createContext<{
|
|
74
|
-
|
|
74
|
+
colorScheme: "dark" | "light";
|
|
75
75
|
}>({
|
|
76
|
-
|
|
76
|
+
colorScheme: "light",
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
// =============================================================================
|
|
@@ -121,7 +121,7 @@ const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
|
|
|
121
121
|
className,
|
|
122
122
|
allowMultiple = false,
|
|
123
123
|
defaultExpanded,
|
|
124
|
-
|
|
124
|
+
colorScheme = "light",
|
|
125
125
|
children,
|
|
126
126
|
},
|
|
127
127
|
ref,
|
|
@@ -134,10 +134,10 @@ const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
|
|
|
134
134
|
}, [defaultExpanded]);
|
|
135
135
|
|
|
136
136
|
return (
|
|
137
|
-
<AccordionContext.Provider value={{
|
|
137
|
+
<AccordionContext.Provider value={{ colorScheme }}>
|
|
138
138
|
<BaseAccordion.Root
|
|
139
139
|
ref={ref}
|
|
140
|
-
className={accordionVariants({
|
|
140
|
+
className={accordionVariants({ colorScheme, class: className })}
|
|
141
141
|
defaultValue={defaultValue}
|
|
142
142
|
multiple={allowMultiple}
|
|
143
143
|
>
|
|
@@ -175,18 +175,18 @@ export interface AccordionItemProps
|
|
|
175
175
|
*/
|
|
176
176
|
const AccordionItem = React.forwardRef<HTMLDivElement, AccordionItemProps>(
|
|
177
177
|
({ className, id, title, children }, ref) => {
|
|
178
|
-
const {
|
|
178
|
+
const { colorScheme } = React.useContext(AccordionContext);
|
|
179
179
|
|
|
180
180
|
return (
|
|
181
181
|
<BaseAccordion.Item
|
|
182
182
|
ref={ref}
|
|
183
183
|
value={id}
|
|
184
|
-
className={accordionItemVariants({
|
|
184
|
+
className={accordionItemVariants({ colorScheme, class: className })}
|
|
185
185
|
>
|
|
186
186
|
{/* Header - always visible */}
|
|
187
187
|
<BaseAccordion.Header>
|
|
188
188
|
<BaseAccordion.Trigger
|
|
189
|
-
className={accordionTriggerVariants({
|
|
189
|
+
className={accordionTriggerVariants({ colorScheme })}
|
|
190
190
|
>
|
|
191
191
|
<span>{title}</span>
|
|
192
192
|
<span
|
|
@@ -208,7 +208,9 @@ const AccordionItem = React.forwardRef<HTMLDivElement, AccordionItemProps>(
|
|
|
208
208
|
"[&[data-starting-style]]:h-0 [&[data-ending-style]]:h-0",
|
|
209
209
|
)}
|
|
210
210
|
>
|
|
211
|
-
<div className={accordionPanelVariants({
|
|
211
|
+
<div className={accordionPanelVariants({ colorScheme })}>
|
|
212
|
+
{children}
|
|
213
|
+
</div>
|
|
212
214
|
</BaseAccordion.Panel>
|
|
213
215
|
</BaseAccordion.Item>
|
|
214
216
|
);
|
|
@@ -102,12 +102,12 @@ describe("Background", () => {
|
|
|
102
102
|
await expect.element(video).toBeInTheDocument();
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
test("has autoplay
|
|
105
|
+
test("has autoplay and loop by default", async () => {
|
|
106
106
|
render(<BackgroundVideo src="/test.mp4" data-testid="bg-video" />);
|
|
107
107
|
const video = page.getByTestId("bg-video");
|
|
108
108
|
await expect.element(video).toHaveAttribute("autoplay");
|
|
109
109
|
await expect.element(video).toHaveAttribute("loop");
|
|
110
|
-
|
|
110
|
+
// Note: muted is a boolean prop in React that may not render as an HTML attribute
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
test("has object-cover class", async () => {
|
|
@@ -50,7 +50,8 @@ const backgroundImageVariants = tv({
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
export interface BackgroundImageProps
|
|
53
|
-
extends
|
|
53
|
+
extends useRender.ComponentProps<"img">,
|
|
54
|
+
Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "render"> {
|
|
54
55
|
/**
|
|
55
56
|
* URL for the background image
|
|
56
57
|
*/
|
|
@@ -59,23 +60,6 @@ export interface BackgroundImageProps
|
|
|
59
60
|
* Object position (default: "center")
|
|
60
61
|
*/
|
|
61
62
|
position?: string;
|
|
62
|
-
/**
|
|
63
|
-
* Custom render prop for element composition.
|
|
64
|
-
* Accepts a React element or render function.
|
|
65
|
-
* @example
|
|
66
|
-
* ```tsx
|
|
67
|
-
* // Element pattern
|
|
68
|
-
* <BackgroundImage render={<img className="custom" />} src="/bg.jpg" />
|
|
69
|
-
*
|
|
70
|
-
* // Callback pattern
|
|
71
|
-
* <BackgroundImage render={(props) => <img {...props} />} src="/bg.jpg" />
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
render?:
|
|
75
|
-
| React.ReactElement
|
|
76
|
-
| ((
|
|
77
|
-
props: React.ImgHTMLAttributes<HTMLImageElement>,
|
|
78
|
-
) => React.ReactElement);
|
|
79
63
|
}
|
|
80
64
|
|
|
81
65
|
/**
|
|
@@ -83,35 +67,32 @@ export interface BackgroundImageProps
|
|
|
83
67
|
* Supports native lazy loading, srcset, and better accessibility.
|
|
84
68
|
* Supports render prop for element composition.
|
|
85
69
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
70
|
+
function BackgroundImage(props: BackgroundImageProps) {
|
|
71
|
+
const {
|
|
72
|
+
className,
|
|
73
|
+
src,
|
|
74
|
+
position = "center",
|
|
75
|
+
alt = "",
|
|
76
|
+
style,
|
|
77
|
+
render,
|
|
78
|
+
...otherProps
|
|
79
|
+
} = props;
|
|
80
|
+
|
|
81
|
+
const imgClassName = backgroundImageVariants({ class: className });
|
|
82
|
+
const imgStyle = { objectPosition: position, ...style };
|
|
83
|
+
|
|
84
|
+
return useRender({
|
|
85
|
+
render,
|
|
86
|
+
props: {
|
|
95
87
|
src,
|
|
96
88
|
alt,
|
|
97
|
-
className:
|
|
98
|
-
style:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const element = useRender({
|
|
106
|
-
// biome-ignore lint/a11y/useAltText: alt is provided via mergedProps
|
|
107
|
-
render: render ?? <img />,
|
|
108
|
-
ref,
|
|
109
|
-
props: mergedProps,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
return element;
|
|
113
|
-
},
|
|
114
|
-
);
|
|
89
|
+
className: imgClassName,
|
|
90
|
+
style: imgStyle,
|
|
91
|
+
...otherProps,
|
|
92
|
+
},
|
|
93
|
+
defaultTagName: "img",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
115
96
|
BackgroundImage.displayName = "Background.Image";
|
|
116
97
|
|
|
117
98
|
// =============================================================================
|
|
@@ -123,7 +104,11 @@ const backgroundVideoVariants = tv({
|
|
|
123
104
|
});
|
|
124
105
|
|
|
125
106
|
export interface BackgroundVideoProps
|
|
126
|
-
extends
|
|
107
|
+
extends useRender.ComponentProps<"video">,
|
|
108
|
+
Omit<
|
|
109
|
+
React.VideoHTMLAttributes<HTMLVideoElement>,
|
|
110
|
+
"children" | "render" | "src"
|
|
111
|
+
> {
|
|
127
112
|
/**
|
|
128
113
|
* URL for the video source
|
|
129
114
|
*/
|
|
@@ -136,67 +121,63 @@ export interface BackgroundVideoProps
|
|
|
136
121
|
* Poster image URL shown before video loads
|
|
137
122
|
*/
|
|
138
123
|
poster?: string;
|
|
139
|
-
/**
|
|
140
|
-
* Custom render prop for element composition.
|
|
141
|
-
* @example
|
|
142
|
-
* ```tsx
|
|
143
|
-
* <BackgroundVideo render={<video className="custom" />} src="/bg.mp4" />
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
|
-
render?:
|
|
147
|
-
| React.ReactElement
|
|
148
|
-
| ((
|
|
149
|
-
props: React.VideoHTMLAttributes<HTMLVideoElement>,
|
|
150
|
-
) => React.ReactElement);
|
|
151
124
|
}
|
|
152
125
|
|
|
153
126
|
/**
|
|
154
127
|
* Background video layer using HTML5 video element.
|
|
155
128
|
* Supports render prop for element composition.
|
|
156
129
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
130
|
+
function BackgroundVideo(props: BackgroundVideoProps) {
|
|
131
|
+
const {
|
|
132
|
+
className,
|
|
133
|
+
src,
|
|
134
|
+
type,
|
|
135
|
+
poster,
|
|
136
|
+
autoPlay = true,
|
|
137
|
+
loop = true,
|
|
138
|
+
muted = true,
|
|
139
|
+
playsInline = true,
|
|
140
|
+
render,
|
|
141
|
+
children,
|
|
142
|
+
...otherProps
|
|
143
|
+
} = props;
|
|
144
|
+
|
|
145
|
+
const videoClassName = backgroundVideoVariants({ class: className });
|
|
146
|
+
|
|
147
|
+
// useRender must be called unconditionally
|
|
148
|
+
const rendered = useRender({
|
|
149
|
+
render,
|
|
150
|
+
props: {
|
|
177
151
|
autoPlay,
|
|
178
152
|
loop,
|
|
179
153
|
muted,
|
|
180
154
|
playsInline,
|
|
181
155
|
poster,
|
|
182
|
-
className:
|
|
183
|
-
...
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
156
|
+
className: videoClassName,
|
|
157
|
+
...otherProps,
|
|
158
|
+
},
|
|
159
|
+
defaultTagName: "video",
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// If no render prop, return video with source child
|
|
163
|
+
if (!render) {
|
|
164
|
+
return (
|
|
165
|
+
<video
|
|
166
|
+
autoPlay={autoPlay}
|
|
167
|
+
loop={loop}
|
|
168
|
+
muted={muted}
|
|
169
|
+
playsInline={playsInline}
|
|
170
|
+
poster={poster}
|
|
171
|
+
className={videoClassName}
|
|
172
|
+
{...otherProps}
|
|
173
|
+
>
|
|
174
|
+
<source src={src} type={type} />
|
|
175
|
+
</video>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return rendered;
|
|
180
|
+
}
|
|
200
181
|
BackgroundVideo.displayName = "Background.Video";
|
|
201
182
|
|
|
202
183
|
// =============================================================================
|
|
@@ -45,24 +45,51 @@ Playground.args = {
|
|
|
45
45
|
|
|
46
46
|
// =============================================================================
|
|
47
47
|
// Variants (Dark Color Scheme - for light backgrounds)
|
|
48
|
+
// Figma naming: Charcoal = solid+dark, Charcoal Outline = outline+dark
|
|
48
49
|
// =============================================================================
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Solid Dark (Figma: "Charcoal" / "Black")
|
|
53
|
+
* Primary filled button with ui-button-primary-bg background and inverted text.
|
|
54
|
+
* Use on light backgrounds for primary actions.
|
|
55
|
+
*/
|
|
50
56
|
export const Solid = () => <Button variant="solid">Solid</Button>;
|
|
51
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Outline Dark (Figma: "Charcoal Outline")
|
|
60
|
+
* Primary-colored border and text on transparent background.
|
|
61
|
+
* Use on light backgrounds for secondary actions.
|
|
62
|
+
*/
|
|
52
63
|
export const Outline = () => <Button variant="outline">Outline</Button>;
|
|
53
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Ghost Dark
|
|
67
|
+
* Primary-colored text with no border or background.
|
|
68
|
+
* Use for tertiary actions on light backgrounds.
|
|
69
|
+
*/
|
|
54
70
|
export const Ghost = () => <Button variant="ghost">Ghost</Button>;
|
|
55
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Subtle Dark
|
|
74
|
+
* Subtle border with primary-colored text.
|
|
75
|
+
* Use for less prominent actions on light backgrounds.
|
|
76
|
+
*/
|
|
56
77
|
export const Subtle = () => <Button variant="subtle">Subtle</Button>;
|
|
57
78
|
|
|
58
79
|
// =============================================================================
|
|
59
80
|
// Variants (Light Color Scheme - for dark backgrounds)
|
|
81
|
+
// Figma naming: Ivory = solid+light, Ivory Outline = outline+light
|
|
60
82
|
// =============================================================================
|
|
61
83
|
|
|
62
84
|
const DarkBackground = ({ children }: { children: React.ReactNode }) => (
|
|
63
85
|
<div className="rounded-radius-12 bg-gray-1200 p-spacing-32">{children}</div>
|
|
64
86
|
);
|
|
65
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Solid Light (Figma: "Ivory")
|
|
90
|
+
* White filled button with primary-colored text.
|
|
91
|
+
* Use on dark backgrounds for primary actions.
|
|
92
|
+
*/
|
|
66
93
|
export const SolidLight = () => (
|
|
67
94
|
<DarkBackground>
|
|
68
95
|
<Button variant="solid" colorScheme="light">
|
|
@@ -71,6 +98,11 @@ export const SolidLight = () => (
|
|
|
71
98
|
</DarkBackground>
|
|
72
99
|
);
|
|
73
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Outline Light (Figma: "Ivory Outline")
|
|
103
|
+
* White border and text on transparent background.
|
|
104
|
+
* Use on dark backgrounds for secondary actions.
|
|
105
|
+
*/
|
|
74
106
|
export const OutlineLight = () => (
|
|
75
107
|
<DarkBackground>
|
|
76
108
|
<Button variant="outline" colorScheme="light">
|
|
@@ -79,6 +111,11 @@ export const OutlineLight = () => (
|
|
|
79
111
|
</DarkBackground>
|
|
80
112
|
);
|
|
81
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Ghost Light
|
|
116
|
+
* White text with no border or background.
|
|
117
|
+
* Use for tertiary actions on dark backgrounds.
|
|
118
|
+
*/
|
|
82
119
|
export const GhostLight = () => (
|
|
83
120
|
<DarkBackground>
|
|
84
121
|
<Button variant="ghost" colorScheme="light">
|
|
@@ -87,6 +124,11 @@ export const GhostLight = () => (
|
|
|
87
124
|
</DarkBackground>
|
|
88
125
|
);
|
|
89
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Subtle Light
|
|
129
|
+
* Alpha-white border with white text.
|
|
130
|
+
* Use for less prominent actions on dark backgrounds.
|
|
131
|
+
*/
|
|
90
132
|
export const SubtleLight = () => (
|
|
91
133
|
<DarkBackground>
|
|
92
134
|
<Button variant="subtle" colorScheme="light">
|
|
@@ -129,7 +129,7 @@ describe("Button", () => {
|
|
|
129
129
|
render(<Button>Default</Button>);
|
|
130
130
|
const button = page.getByRole("button", { name: "Default" });
|
|
131
131
|
// Button uses semantic token classes
|
|
132
|
-
await expect.element(button).toHaveClass(/bg-button-primary-bg/);
|
|
132
|
+
await expect.element(button).toHaveClass(/bg-ui-button-primary-bg/);
|
|
133
133
|
});
|
|
134
134
|
});
|
|
135
135
|
});
|