Skip to content

Commit b2b847a

Browse files
committed
add design tokens in Storybook
1 parent 9cac22e commit b2b847a

File tree

6 files changed

+1998
-0
lines changed

6 files changed

+1998
-0
lines changed
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
import cx from "classnames";
2+
import React, { useState } from "react";
3+
import { Meta, StoryObj } from "@storybook/react";
4+
import { Copy, CopyIcon } from "~/storybook/bootstrap/utils.tsx";
5+
6+
const tokenData = {
7+
border: {
8+
"border-width": { value: "1px" },
9+
"border-style": { value: "solid" },
10+
},
11+
borderRadius: {
12+
"rounded-0": {
13+
value: "0rem",
14+
type: "borderRadius",
15+
description: "No border radius",
16+
extensions: {
17+
px: "0px",
18+
},
19+
},
20+
"rounded-1": {
21+
value: "0.2rem",
22+
type: "borderRadius",
23+
description: "Extra small border radius",
24+
extensions: {
25+
px: "3.2px",
26+
},
27+
},
28+
"rounded-2": {
29+
value: "0.25rem",
30+
type: "borderRadius",
31+
description: "Small border radius",
32+
extensions: {
33+
px: "4px",
34+
},
35+
},
36+
"rounded-3": {
37+
value: "0.3rem",
38+
type: "borderRadius",
39+
description: "Medium border radius",
40+
extensions: {
41+
px: "4.8px",
42+
},
43+
},
44+
"rounded-circle": {
45+
value: "50%",
46+
type: "borderRadius",
47+
description: "Perfect circle border radius",
48+
extensions: {
49+
px: "",
50+
},
51+
},
52+
"rounded-pill": {
53+
value: "50rem",
54+
type: "borderRadius",
55+
description: "Fully rounded pill shape",
56+
extensions: {
57+
px: "800px",
58+
},
59+
},
60+
},
61+
shadow: {
62+
shadow: {
63+
value: "0 .5rem 1rem rgba(0, 0, 0, .15)",
64+
description: "Default box shadow",
65+
},
66+
"shadow-sm": {
67+
value: "0 .125rem .25rem rgba(0, 0, 0, .075)",
68+
description: "Small box shadow",
69+
},
70+
"shadow-lg": {
71+
value: "0 1rem 3rem rgba(0, 0, 0, .175)",
72+
description: "Large box shadow",
73+
},
74+
"shadow-inset": {
75+
value: "inset 0 1px 2px rgba(0, 0, 0, .075)",
76+
description: "Inset box shadow",
77+
},
78+
},
79+
};
80+
81+
interface PropertyCardProps {
82+
token: string;
83+
value: string;
84+
px?: string;
85+
notes?: string;
86+
}
87+
88+
const PropertyCard: React.FC<PropertyCardProps> = ({
89+
token,
90+
value,
91+
px,
92+
notes,
93+
}) => (
94+
<div
95+
className={cx(
96+
"bg-white",
97+
"p-3",
98+
"border",
99+
"rounded",
100+
"shadow-sm",
101+
"d-flex",
102+
"flex-column",
103+
"justify-content-between"
104+
)}
105+
>
106+
<div>
107+
<div
108+
className={cx("fw-semibold", "text-primary", "mb-1")}
109+
style={{ fontSize: "14px" }}
110+
>
111+
{token}
112+
</div>
113+
<div className={cx("text-dark", "mb-1")} style={{ fontSize: "13px" }}>
114+
Value: <strong>{value}</strong>
115+
</div>
116+
{px && (
117+
<div className="text-muted" style={{ fontSize: "13px" }}>
118+
PX: {px}
119+
</div>
120+
)}
121+
</div>
122+
{notes && (
123+
<div className={cx("text-muted", "mt-auto")} style={{ fontSize: "11px" }}>
124+
{notes}
125+
</div>
126+
)}
127+
</div>
128+
);
129+
130+
interface BorderRadiusExampleCardProps {
131+
token: string;
132+
value: string;
133+
px: string;
134+
}
135+
136+
const BorderRadiusExampleCard: React.FC<BorderRadiusExampleCardProps> = ({
137+
token,
138+
value,
139+
px,
140+
}) => {
141+
const [copied, setCopied] = useState("");
142+
const isCircle = token === "rounded-circle";
143+
144+
return (
145+
<div
146+
className={cx(
147+
"border",
148+
"border-2",
149+
"border-primary",
150+
"bg-light",
151+
"d-flex",
152+
"align-items-center",
153+
"justify-content-center",
154+
"text-center",
155+
"flex-column",
156+
"shadow-sm",
157+
token
158+
)}
159+
style={{
160+
width: "200px",
161+
height: isCircle ? "200px" : "150px",
162+
fontSize: "0.8rem",
163+
}}
164+
>
165+
<div className="fw-semibold">{token}</div>
166+
<div>
167+
{value} ({px})
168+
</div>
169+
<div
170+
className={cx("mt-3", "cursor-pointer")}
171+
style={{ fontSize: "13px" }}
172+
onClick={() => token && Copy(token, setCopied)}
173+
>
174+
<code>{token}</code>
175+
{copied === token && (
176+
<span className={cx("ms-1", "text-success")}></span>
177+
)}
178+
{copied !== token && <CopyIcon />}
179+
</div>
180+
</div>
181+
);
182+
};
183+
184+
interface ShadowExampleCardProps {
185+
token: string;
186+
value: string;
187+
description: string;
188+
cssClass?: string;
189+
}
190+
191+
const ShadowExampleCard: React.FC<ShadowExampleCardProps> = ({
192+
token,
193+
value,
194+
description,
195+
}) => {
196+
const [copied, setCopied] = useState("");
197+
198+
return (
199+
<div
200+
className={cx(
201+
"bg-white",
202+
"rounded",
203+
"border",
204+
"border-light-subtle",
205+
"d-flex",
206+
"flex-column",
207+
"align-items-center",
208+
"justify-content-center",
209+
"text-center",
210+
"p-3"
211+
)}
212+
style={{
213+
width: "250px",
214+
height: "150px",
215+
fontSize: "0.9rem",
216+
boxShadow: value,
217+
}}
218+
>
219+
<div className={cx("fw-semibold", "mb-2")}>{token}</div>
220+
<div className="text-muted" style={{ fontSize: "0.85rem" }}>
221+
{description}
222+
</div>
223+
<div
224+
className={cx("text-muted", "mt-2", "cursor-pointer")}
225+
style={{ fontSize: "0.75rem" }}
226+
onClick={() => token && Copy(token, setCopied)}
227+
>
228+
<code>{token || "Custom Shadow"}</code>
229+
{copied === token && (
230+
<span className={cx("ms-1", "text-success")}></span>
231+
)}
232+
{copied !== token && <CopyIcon />}
233+
</div>
234+
</div>
235+
);
236+
};
237+
238+
const SectionHeader: React.FC<{ children: React.ReactNode }> = ({
239+
children,
240+
}) => (
241+
<h2
242+
className={cx(
243+
"fw-bold",
244+
"text-primary",
245+
"border-bottom",
246+
"border-primary",
247+
"border-2",
248+
"pb-2",
249+
"mb-3"
250+
)}
251+
>
252+
{children}
253+
</h2>
254+
);
255+
256+
const SectionDescription: React.FC<{ children: React.ReactNode }> = ({
257+
children,
258+
}) => (
259+
<p
260+
className="text-muted mb-4"
261+
style={{ fontSize: "14px", maxWidth: "800px" }}
262+
>
263+
{children}
264+
</p>
265+
);
266+
267+
const meta: Meta = {
268+
title: "Design Tokens/Borders & Shadows",
269+
component: () => <div />,
270+
parameters: {
271+
docs: {
272+
description: {
273+
component:
274+
"This section defines our visual treatments for **borders** and **shadows**, crucial for defining element boundaries and conveying depth in our UI. These tokens are designed to align with **Bootstrap's border and shadow utilities** (e.g., `.border-radius-*`, `.shadow-*`), ensuring consistent visual presentation and seamless integration across all components. They empower designers and developers to create clear, layered, and modern interfaces.",
275+
},
276+
},
277+
layout: "centered",
278+
},
279+
};
280+
281+
export default meta;
282+
283+
type Story = StoryObj<typeof meta>;
284+
285+
export const BordersAndShadows: Story = {
286+
render: () => (
287+
<div className={cx("p-4", "mx-auto")} style={{ maxWidth: "1200px" }}>
288+
<section className="mb-5">
289+
<SectionHeader>1. Core Borders</SectionHeader>
290+
<SectionDescription>
291+
Fundamental properties defining the default appearance of borders.
292+
</SectionDescription>
293+
<div className={cx("d-flex", "flex-wrap", "gap-3")}>
294+
{Object.entries(tokenData.border).map(([key, data]) => (
295+
<PropertyCard
296+
key={key}
297+
token={key}
298+
value={data.value}
299+
notes={
300+
key === "border-width"
301+
? "Default border thickness"
302+
: key === "border-style"
303+
? "Default border line style"
304+
: ""
305+
}
306+
/>
307+
))}
308+
</div>
309+
</section>
310+
311+
<section className="mb-5">
312+
<SectionHeader>2. Border Radius</SectionHeader>
313+
<SectionDescription>
314+
Defines the roundness of element corners, aligning with
315+
Bootstrap&apos;s `.rounded-*` classes for consistent visual softness.
316+
</SectionDescription>
317+
<div
318+
className={cx(
319+
"d-flex",
320+
"flex-wrap",
321+
"gap-4",
322+
"justify-content-center"
323+
)}
324+
>
325+
{Object.entries(tokenData.borderRadius).map(([key, data]) => (
326+
<BorderRadiusExampleCard
327+
key={key}
328+
token={key}
329+
value={data.value}
330+
px={data.extensions?.px || ""}
331+
/>
332+
))}
333+
</div>
334+
</section>
335+
336+
<section>
337+
<SectionHeader>3. Box Shadows</SectionHeader>
338+
<SectionDescription>
339+
Adds depth and visual hierarchy using predefined shadow values,
340+
directly corresponding to Bootstrap&apos;s `.shadow-*` classes.
341+
</SectionDescription>
342+
<div
343+
className={cx(
344+
"d-flex",
345+
"flex-wrap",
346+
"gap-4",
347+
"justify-content-center"
348+
)}
349+
>
350+
<ShadowExampleCard
351+
token="shadow-sm"
352+
value={tokenData.shadow["shadow-sm"].value}
353+
description={tokenData.shadow["shadow-sm"].description}
354+
cssClass="shadow-sm"
355+
/>
356+
<ShadowExampleCard
357+
token="shadow"
358+
value={tokenData.shadow["shadow"].value}
359+
description={tokenData.shadow["shadow"].description}
360+
cssClass="shadow"
361+
/>
362+
<ShadowExampleCard
363+
token="shadow-lg"
364+
value={tokenData.shadow["shadow-lg"].value}
365+
description={tokenData.shadow["shadow-lg"].description}
366+
cssClass="shadow-lg"
367+
/>
368+
<ShadowExampleCard
369+
token="shadow-inset"
370+
value={tokenData.shadow["shadow-inset"].value}
371+
description={tokenData.shadow["shadow-inset"].description}
372+
cssClass="shadow-inset"
373+
/>
374+
</div>
375+
</section>
376+
</div>
377+
),
378+
};

0 commit comments

Comments
 (0)