graphen 1.10.19 → 2.0.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/src/example.tsx CHANGED
@@ -1,663 +1,959 @@
1
- /* eslint-disable */
2
- import _ from "lodash";
3
- import React, { useState } from "react";
1
+ /* eslint-disable jsx-a11y/anchor-is-valid, react/no-deprecated */
2
+ import React, { useEffect, useMemo, useState } from "react";
4
3
  import { render } from "react-dom";
5
4
  import {
5
+ Badge,
6
6
  Button,
7
- Dialog,
8
- Icon,
7
+ Card,
8
+ Dropdown,
9
9
  Input,
10
- Image,
11
10
  Link,
12
- Loader,
13
- Card,
14
- Scroller,
15
- Joystick,
16
- Accordion,
17
- Validation,
18
- Tooltip,
19
11
  Logo,
20
- Dropdown,
21
- Switch,
22
- Flex,
23
- FlexItem,
24
- Panel,
25
- PanelFooter,
26
- PanelContent,
27
- PanelTitle,
28
- Skeleton,
12
+ Separator,
29
13
  constants,
30
14
  } from "./index";
31
15
 
32
- const appContainer = document.querySelector(".js-example");
16
+ declare const GRAPHEN_VERSION: string;
17
+
18
+ const VERSION = GRAPHEN_VERSION;
19
+
20
+ const TOKENS = [
21
+ [
22
+ "#337ab7",
23
+ "Primary",
24
+ "Default brand color, links and primary actions",
25
+ "$gb-color-primary",
26
+ ],
27
+ [
28
+ "#585858",
29
+ "Text",
30
+ "Body copy, headings, default foreground",
31
+ "$gb-color-text",
32
+ ],
33
+ ["#337ab7", "Link", "Inline and standalone hyperlinks", "$gb-color-link"],
34
+ [
35
+ "#f5f5f5",
36
+ "Component",
37
+ "Default surface for cards, inputs, buttons",
38
+ "$gb-color-component",
39
+ ],
40
+ [
41
+ "#e5e5e5",
42
+ "Component dark",
43
+ "Hover state for component surfaces",
44
+ "$gb-color-component-dark",
45
+ ],
46
+ [
47
+ "#0ea348",
48
+ "Success",
49
+ "Positive feedback, confirmation states",
50
+ "$gb-color-success",
51
+ ],
52
+ ["#337ab7", "Info", "Neutral messaging, hints", "$gb-color-info"],
53
+ ["#db5551", "Danger", "Errors, destructive actions", "$gb-color-danger"],
54
+ ] as const;
55
+
56
+ const TYPE_SCALE = [
57
+ {
58
+ meta: "36 / 1.1",
59
+ token: "$gb-fs-display",
60
+ style: { fontSize: 36, fontWeight: 500, letterSpacing: "-0.02em" },
61
+ sample: "The quick brown fox",
62
+ },
63
+ {
64
+ meta: "22 / 1.3",
65
+ token: "$gb-fs-h1",
66
+ style: { fontSize: 22, fontWeight: 500 },
67
+ sample: "The quick brown fox jumps over",
68
+ },
69
+ {
70
+ meta: "17 / 1.4",
71
+ token: "$gb-fs-h2",
72
+ style: { fontSize: 17, fontWeight: 500 },
73
+ sample: "The quick brown fox jumps over the lazy dog",
74
+ },
75
+ {
76
+ meta: "15 / 1.55",
77
+ token: "$gb-fs-body",
78
+ style: {},
79
+ sample:
80
+ "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.",
81
+ },
82
+ {
83
+ meta: "13 / 1.5",
84
+ token: "$gb-fs-small",
85
+ style: { fontSize: 13, color: "var(--text-muted)" },
86
+ sample:
87
+ "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.",
88
+ },
89
+ ];
90
+
91
+ const SPACING = [
92
+ ["$gb-sp-1", "4 px", "0.25rem", 4],
93
+ ["$gb-sp-2", "8 px", "0.5rem", 8],
94
+ ["$gb-sp-3", "12 px", "0.75rem", 12],
95
+ ["$gb-sp-4", "16 px", "1rem", 16],
96
+ ["$gb-sp-6", "24 px", "1.5rem", 24],
97
+ ["$gb-sp-8", "32 px", "2rem", 32],
98
+ ["$gb-sp-12", "48 px", "3rem", 48],
99
+ ["$gb-sp-16", "64 px", "4rem", 64],
100
+ ] as const;
33
101
 
34
- function ExampleApp() {
35
- const [isDialogVisible, setIsDialogVisible] = useState(false);
36
- const [isDialogWithOverlayVisible, setIsDialogWithOverlayVisible] =
37
- useState(false);
102
+ const RADIUS = [
103
+ ["$gb-r-sm", "4 px", 4],
104
+ ["$gb-r", "6 px", 6],
105
+ ["$gb-r-lg", "10 px", 10],
106
+ ["$gb-r-full", "9999 px", 999],
107
+ ] as const;
38
108
 
109
+ const ICONS: ReadonlyArray<readonly [string, string]> = [
110
+ ["chevron-up", "M6 15l6-6 6 6"],
111
+ ["chevron-right", "M9 6l6 6-6 6"],
112
+ ["chevron-down", "M6 9l6 6 6-6"],
113
+ ["chevron-left", "M15 6l-6 6 6 6"],
114
+ ["flame", "M12 3s4 4 4 8a4 4 0 0 1-8 0c0-2 1-3 1-3s0 2 2 2 1-3 1-7z"],
115
+ ["user", ""],
116
+ ["menu", "M4 7h16M4 12h16M4 17h16"],
117
+ ["menu-add", "M4 7h16M4 12h10M4 17h16M17 12h5M19.5 9.5v5"],
118
+ ["menu-sort", "M4 7h12M4 12h8M4 17h12 m6 -3 3 3-3 3"],
119
+ ["thermometer", "M14 14V5a2 2 0 1 0-4 0v9a4 4 0 1 0 4 0z"],
120
+ ["target", ""],
121
+ ["record", ""],
122
+ ["circle", ""],
123
+ ["plus", "M12 5v14M5 12h14"],
124
+ ];
125
+
126
+ const NAV = [
127
+ {
128
+ title: "Getting started",
129
+ items: [
130
+ { id: "introduction", label: "Introduction" },
131
+ { id: "installation", label: "Installation" },
132
+ ],
133
+ },
134
+ {
135
+ title: "Foundations",
136
+ items: [
137
+ { id: "colors", label: "Colors" },
138
+ { id: "typography", label: "Typography" },
139
+ { id: "spacing", label: "Spacing & radius" },
140
+ { id: "iconography", label: "Iconography", count: "14" },
141
+ ],
142
+ },
143
+ {
144
+ title: "Components",
145
+ items: [
146
+ { id: "logo", label: "Logo" },
147
+ { id: "button", label: "Button" },
148
+ { id: "link", label: "Link" },
149
+ { id: "header-comp", label: "Header" },
150
+ { id: "separator", label: "Separator" },
151
+ { id: "input", label: "Input" },
152
+ { id: "dropdown", label: "Dropdown" },
153
+ { id: "card", label: "Card" },
154
+ { id: "badge", label: "Badge" },
155
+ ],
156
+ },
157
+ ];
158
+
159
+ const TOC = [
160
+ ["introduction", "Introduction"],
161
+ ["colors", "Colors"],
162
+ ["typography", "Typography"],
163
+ ["spacing", "Spacing & radius"],
164
+ ["iconography", "Iconography"],
165
+ ["logo", "Logo"],
166
+ ["button", "Button"],
167
+ ["link", "Link"],
168
+ ["header-comp", "Header"],
169
+ ["separator", "Separator"],
170
+ ["input", "Input"],
171
+ ["dropdown", "Dropdown"],
172
+ ["card", "Card"],
173
+ ["badge", "Badge"],
174
+ ] as const;
175
+
176
+ type IconProps = {
177
+ d?: string;
178
+ children?: React.ReactNode;
179
+ size?: number;
180
+ stroke?: number;
181
+ };
182
+ function StrokeIcon({ d, children, size = 16, stroke = 1.7 }: IconProps) {
39
183
  return (
40
- <>
41
- <Panel>
42
- <PanelContent>
43
- <Card className="example__splash">
44
- <div className="example__splash-image" />
45
- </Card>
46
- </PanelContent>
47
- </Panel>
48
- <article className="tst-colors gc-panel gc-panel--separator">
49
- <header className="gc-panel__title">Colors</header>
50
- <div className="gc-panel__content">
51
- <ul className="gc-list">
52
- <li className="gc-list__item">
53
- <div className="tst-colors-primary gc-sample gc-sample--brand-primary" />{" "}
54
- - Brand color primary / $gb-color-primary
55
- </li>
56
- <li className="gc-list__item">
57
- <div className="tst-colors-text gc-sample gc-sample--brand-text" />{" "}
58
- - Brand color text / $gb-color-text
59
- </li>
60
- <li className="gc-list__item">
61
- <div className="tst-colors-link gc-sample gc-sample--brand-link" />{" "}
62
- - Brand color link / $gb-color-link
63
- </li>
64
- <li className="gc-list__item">
65
- <div className="tst-colors-component gc-sample gc-sample--brand-component" />{" "}
66
- - Brand color component / $gb-color-component
67
- </li>
68
- <li className="gc-list__item">
69
- <div className="tst-colors-component gc-sample gc-sample--brand-component-dark" />{" "}
70
- - Brand color component dark / $gb-color-component-dark
71
- </li>
72
- <li className="gc-list__item">
73
- <div className="gc-sample gc-sample--brand-success" /> - Brand
74
- color success / $gb-color-success
75
- </li>
76
- <li className="gc-list__item">
77
- <div className="gc-sample gc-sample--brand-info" /> - Brand color
78
- info / $gb-color-info
79
- </li>
80
- <li className="gc-list__item">
81
- <div className="gc-sample gc-sample--brand-danger" /> - Brand
82
- color danger / $gb-color-danger
83
- </li>
84
- </ul>
85
- </div>
86
- </article>
87
- <article className="gc-panel gc-panel--separator">
88
- <header className="gc-panel__title">Logo</header>
89
- <div className="gc-panel__content">
90
- <Logo />
91
- </div>
92
- </article>
93
- <article className="gc-panel gc-panel--separator">
94
- <header className="gc-panel__title">Icons</header>
95
- <div className="gc-panel__content">
96
- <Icon type="circle-up" className="gc-icon--large" />{" "}
97
- <Icon type="circle-right" className="gc-icon--large" />{" "}
98
- <Icon type="circle-down" className="gc-icon--large" />{" "}
99
- <Icon type="circle-left" className="gc-icon--large" />{" "}
100
- <Icon type="fire" className="gc-icon--large" />{" "}
101
- <Icon type="man" className="gc-icon--large" />{" "}
102
- <Icon type="menu" className="gc-icon--large" />{" "}
103
- <Icon type="menu2" className="gc-icon--large" />{" "}
104
- <Icon type="menu3" className="gc-icon--large" />{" "}
105
- <Icon type="menu4" className="gc-icon--large" />{" "}
106
- <Icon type="thermometer-half" className="gc-icon--large" />{" "}
107
- <Icon type="radio-checked" className="gc-icon--large" />{" "}
108
- <Icon type="radio-checked2" className="gc-icon--large" />{" "}
109
- <Icon type="radio-unchecked" className="gc-icon--large" />
110
- </div>
111
- </article>
112
- <article className="gc-panel gc-panel--separator">
113
- <header className="gc-panel__title">Separator</header>
114
- <div className="gc-separator" />
115
- </article>
116
- <article className="gc-panel gc-panel--separator">
117
- <header className="gc-panel__title">Link</header>
118
- <div className="gc-panel__content">
119
- <Link link="http://some-url">Primary Link</Link> ,{" "}
120
- <Link link="http://some-url" skin={constants.SKIN_DEFAULT}>
121
- Default Link
122
- </Link>
123
- </div>
124
- </article>
125
- <article className="gc-panel gc-panel--separator">
126
- <header className="gc-panel__title">Header</header>
127
- <div className="gc-panel__content">
128
- <header className="gc-header">
129
- <Link className="gc-header__logo" link="/">
130
- <Logo />
131
- </Link>
132
- <nav className="gc-header__navigation">
133
- <ul className="gc-navigation">
134
- <li className="gc-navigation__option">
135
- <Link className="gc-navigation__link" link="/">
136
- Item 1
137
- </Link>
138
- </li>
139
- <li className="gc-navigation__option gc-navigation__option--active">
140
- <Link className="gc-navigation__link" link="/">
141
- Deep Item 2
142
- </Link>
143
- <div className="gc-navigation__suboption gc-submenu">
144
- <div className="gc-submenu__content">
145
- <Link className="gc-submenu__item" link="/">
146
- Sub Item 2a
147
- </Link>
148
- <Link className="gc-submenu__item" link="/">
149
- Sub Item 2b
150
- </Link>
151
- </div>
152
- </div>
153
- </li>
154
- <li className="gc-navigation__option">
155
- <Link className="gc-navigation__link" link="/">
156
- Item 3
157
- </Link>
158
- </li>
159
- <li className="gc-navigation__option">
160
- <Link className="gc-navigation__link" link="/">
161
- Item 4
162
- </Link>
163
- </li>
164
- <li className="gc-navigation__option">
165
- <Link className="gc-navigation__link" link="/">
166
- Item 5
167
- </Link>
168
- </li>
169
- </ul>
170
- </nav>
171
- </header>
172
- <header className="gc-header gc-header--default">
173
- <Link className="gc-header__logo" link="/">
174
- <Logo />
175
- </Link>
176
- <nav className="gc-header__navigation">
177
- <ul className="gc-navigation">
178
- <li className="gc-navigation__option">
179
- <Link className="gc-navigation__link" link="/">
180
- Item 1
181
- </Link>
182
- </li>
183
- <li className="gc-navigation__option gc-navigation__option--active">
184
- <Link className="gc-navigation__link" link="/">
185
- Deep Item 2
186
- </Link>
187
- <div className="gc-navigation__suboption gc-submenu">
188
- <div className="gc-submenu__content">
189
- <Link className="gc-submenu__item" link="/">
190
- Sub Item 2a
191
- </Link>
192
- <Link className="gc-submenu__item" link="/">
193
- Sub Item 2b
194
- </Link>
195
- </div>
196
- </div>
197
- </li>
198
- <li className="gc-navigation__option">
199
- <Link className="gc-navigation__link" link="/">
200
- Item 3
201
- </Link>
202
- </li>
203
- <li className="gc-navigation__option">
204
- <Link className="gc-navigation__link" link="/">
205
- Item 4
206
- </Link>
207
- </li>
208
- <li className="gc-navigation__option">
209
- <Link className="gc-navigation__link" link="/">
210
- Item 5
211
- </Link>
212
- </li>
213
- </ul>
214
- </nav>
215
- </header>
216
- </div>
217
- </article>
218
- <article className="gc-panel gc-panel--separator">
219
- <header className="gc-panel__title">Footer</header>
220
- <div className="gc-panel__content">
221
- <footer className="gc-footer">Footer</footer>
222
- <footer className="gc-footer gc-footer--default">
223
- Footer + default
224
- </footer>
225
- </div>
226
- </article>
227
- <article className="gc-panel gc-panel--separator">
228
- <header className="gc-panel__title">Navigation</header>
229
- <div className="gc-panel__content">
230
- <ul className="gc-navigation">
231
- <li className="gc-navigation__option">
232
- <Link className="gc-navigation__link" link="/">
233
- Item 1
234
- </Link>
235
- </li>
236
- <li className="gc-navigation__option gc-navigation__option--active">
237
- <Link className="gc-navigation__link" link="/">
238
- Deep Item 2
239
- </Link>
240
- <div className="gc-navigation__suboption gc-submenu">
241
- <div className="gc-submenu__content">
242
- <Link className="gc-submenu__item" link="/">
243
- Sub Item 2a
244
- </Link>
245
- <Link className="gc-submenu__item" link="/">
246
- Sub Item 2b
247
- </Link>
248
- </div>
249
- </div>
250
- </li>
251
- <li className="gc-navigation__option">
252
- <Link className="gc-navigation__link" link="/">
253
- Item 3
254
- </Link>
255
- </li>
256
- <li className="gc-navigation__option">
257
- <Link className="gc-navigation__link" link="/">
258
- Item 4
259
- </Link>
260
- </li>
261
- <li className="gc-navigation__option">
262
- <Link className="gc-navigation__link" link="/">
263
- Item 5
264
- </Link>
265
- </li>
266
- </ul>
267
- </div>
268
- </article>
269
- <article className="gc-panel gc-panel--separator">
270
- <header className="gc-panel__title">Panel</header>
271
- <div className="gc-panel__content">
272
- <article className="gc-panel">
273
- <header className="gc-panel__title">Panel title</header>
274
- <div className="gc-panel__content">
275
- <p>Panel content paragraph 1</p>
276
- <p>Panel content paragraph 2</p>
277
- </div>
278
- </article>
279
- </div>
280
- </article>
281
- <article className="gc-panel gc-panel--separator">
282
- <header className="gc-panel__title">Buttons</header>
283
- <div className="gc-panel__content">
284
- <p>
285
- <Button>Button</Button>{" "}
286
- <Button className="gc-btn--danger">Button + danger</Button>{" "}
287
- <Button className="gc-btn--primary">Button + primary</Button>{" "}
288
- <Button className="gc-btn--secondary">Button + secondary</Button>{" "}
289
- <Button className="gc-btn--tertiary">Button + tertiary</Button>
290
- <Button isDisabled>Button + isDisabled</Button>
291
- </p>
292
- <p>
293
- <Button className="gc-btn--small">Button + small</Button>{" "}
294
- <Button className="gc-btn--small gc-btn--danger">
295
- Button + small + danger
296
- </Button>{" "}
297
- <Button className="gc-btn--small gc-btn--primary">
298
- Button + small + primary
299
- </Button>{" "}
300
- <Button className="gc-btn--small gc-btn--secondary">
301
- Button + small + secondary
302
- </Button>{" "}
303
- <Button className="gc-btn--small gc-btn--tertiary">
304
- Button + small + tertiary
305
- </Button>
306
- </p>
307
- <p>
308
- <Button className="gc-btn--full">Button + full</Button>
309
- </p>
310
- <p>
311
- <Button className="gc-btn--full gc-btn--danger">
312
- Button + full + danger
313
- </Button>
314
- </p>
315
- <p>
316
- <Button className="gc-btn--full gc-btn--primary">
317
- Button + full + primary
318
- </Button>
319
- </p>
320
- <p>
321
- <Button className="gc-btn--full gc-btn--secondary">
322
- Button + full + secondary
323
- </Button>
324
- </p>
325
- <p>
326
- <Button className="gc-btn--full gc-btn--tertiary">
327
- Button + full + tertiary
328
- </Button>
329
- </p>
330
- </div>
331
- </article>
332
- <article className="gc-panel gc-panel--separator">
333
- <header className="gc-panel__title">Switches</header>
334
- <div className="gc-panel__content">
335
- <p>
336
- <Switch /> <Switch type="success" isSwitched />{" "}
337
- <Switch type="info" isSwitched />{" "}
338
- <Switch type="danger" isSwitched />
339
- </p>
340
- </div>
341
- </article>
342
- <article className="gc-panel gc-panel--separator">
343
- <header className="gc-panel__title">Images</header>
344
- <div className="gc-panel__content">
345
- <Image
346
- className="gm-spacing-rl"
347
- src="no-image.jpg"
348
- height={200}
349
- width={400}
350
- />
351
- <Image
352
- src="./can-t-look-over-1312680-639x469.jpg"
353
- height={200}
354
- width={400}
355
- />
184
+ <svg
185
+ width={size}
186
+ height={size}
187
+ viewBox="0 0 24 24"
188
+ fill="none"
189
+ stroke="currentColor"
190
+ strokeWidth={stroke}
191
+ strokeLinecap="round"
192
+ strokeLinejoin="round"
193
+ aria-hidden="true"
194
+ >
195
+ {d ? <path d={d} /> : children}
196
+ </svg>
197
+ );
198
+ }
199
+
200
+ function CopyButton({
201
+ value,
202
+ label = "copy",
203
+ }: {
204
+ value: string;
205
+ label?: string;
206
+ }) {
207
+ const [copied, setCopied] = useState(false);
208
+ return (
209
+ <button
210
+ type="button"
211
+ className={`docs-copy-btn${copied ? " copied" : ""}`}
212
+ onClick={async () => {
213
+ try {
214
+ await navigator.clipboard.writeText(value);
215
+ } catch {
216
+ /* noop */
217
+ }
218
+ setCopied(true);
219
+ window.setTimeout(() => setCopied(false), 1100);
220
+ }}
221
+ >
222
+ {copied ? "copied" : label}
223
+ </button>
224
+ );
225
+ }
226
+
227
+ type DemoProps = {
228
+ code: React.ReactNode;
229
+ stageClass?: string;
230
+ children: React.ReactNode;
231
+ };
232
+ function Demo({ code, stageClass = "", children }: DemoProps) {
233
+ const [active, setActive] = useState<"preview" | "code">("preview");
234
+ return (
235
+ <div className="docs-demo" data-active={active}>
236
+ <div className="tabs" role="tablist">
237
+ <button
238
+ type="button"
239
+ role="tab"
240
+ className="tab"
241
+ aria-selected={active === "preview"}
242
+ onClick={() => setActive("preview")}
243
+ >
244
+ <svg
245
+ className="ico"
246
+ viewBox="0 0 24 24"
247
+ fill="none"
248
+ stroke="currentColor"
249
+ strokeWidth={2}
250
+ >
251
+ <circle cx="12" cy="12" r="3" />
252
+ <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" />
253
+ </svg>
254
+ Preview
255
+ </button>
256
+ <button
257
+ type="button"
258
+ role="tab"
259
+ className="tab"
260
+ aria-selected={active === "code"}
261
+ onClick={() => setActive("code")}
262
+ >
263
+ <svg
264
+ className="ico"
265
+ viewBox="0 0 24 24"
266
+ fill="none"
267
+ stroke="currentColor"
268
+ strokeWidth={2}
269
+ strokeLinecap="round"
270
+ >
271
+ <path d="m8 6-6 6 6 6M16 6l6 6-6 6" />
272
+ </svg>
273
+ Code
274
+ </button>
275
+ </div>
276
+ <div className={`stage ${stageClass}`}>{children}</div>
277
+ <pre className="code">{code}</pre>
278
+ </div>
279
+ );
280
+ }
281
+
282
+ function HexLogoMark() {
283
+ return (
284
+ <svg className="docs-hex" viewBox="0 0 24 24" aria-hidden="true">
285
+ <polygon
286
+ points="12,2 21,7 21,17 12,22 3,17 3,7"
287
+ fill="none"
288
+ stroke="currentColor"
289
+ strokeWidth={1.6}
290
+ strokeLinejoin="round"
291
+ />
292
+ <polygon
293
+ points="12,7 17,9.5 17,14.5 12,17 7,14.5 7,9.5"
294
+ fill="currentColor"
295
+ opacity={0.18}
296
+ />
297
+ </svg>
298
+ );
299
+ }
300
+
301
+ const INSTALL_CMDS: Record<string, string> = {
302
+ npm: "npm i graphen",
303
+ pnpm: "pnpm add graphen",
304
+ yarn: "yarn add graphen",
305
+ };
306
+
307
+ const DROPDOWN_ITEMS = [
308
+ { value: "red", label: "Red" },
309
+ { value: "blue", label: "Blue" },
310
+ { value: "green", label: "Green" },
311
+ ] as const;
312
+
313
+ function IconCell({ name, d }: { name: string; d: string }) {
314
+ const [copied, setCopied] = useState(false);
315
+ return (
316
+ <div
317
+ className="docs-icon-cell"
318
+ role="button"
319
+ tabIndex={0}
320
+ onClick={async () => {
321
+ try {
322
+ await navigator.clipboard.writeText(name);
323
+ } catch {
324
+ /* noop */
325
+ }
326
+ setCopied(true);
327
+ window.setTimeout(() => setCopied(false), 900);
328
+ }}
329
+ onKeyDown={(e) => {
330
+ if (e.key === "Enter" || e.key === " ") {
331
+ e.preventDefault();
332
+ (e.currentTarget as HTMLDivElement).click();
333
+ }
334
+ }}
335
+ >
336
+ <svg
337
+ viewBox="0 0 24 24"
338
+ fill="none"
339
+ stroke="currentColor"
340
+ strokeWidth={1.6}
341
+ strokeLinecap="round"
342
+ strokeLinejoin="round"
343
+ >
344
+ <path d={d} />
345
+ </svg>
346
+ <span style={copied ? { color: "var(--gb-success)" } : undefined}>
347
+ {copied ? "copied!" : name}
348
+ </span>
349
+ </div>
350
+ );
351
+ }
352
+
353
+ function App() {
354
+ const [theme, setTheme] = useState<"light" | "dark">(() => {
355
+ if (typeof localStorage === "undefined") return "light";
356
+ return (
357
+ (localStorage.getItem("graphen-theme") as "light" | "dark") || "light"
358
+ );
359
+ });
360
+ const [installTab, setInstallTab] =
361
+ useState<keyof typeof INSTALL_CMDS>("npm");
362
+ const [activeId, setActiveId] = useState<string>("introduction");
363
+ const [installCopied, setInstallCopied] = useState(false);
364
+ const [dropdownValue, setDropdownValue] = useState("red");
365
+
366
+ useEffect(() => {
367
+ document.documentElement.setAttribute("data-theme", theme);
368
+ try {
369
+ localStorage.setItem("graphen-theme", theme);
370
+ } catch {
371
+ /* noop */
372
+ }
373
+ }, [theme]);
374
+
375
+ useEffect(() => {
376
+ const sections = Array.from(
377
+ document.querySelectorAll<HTMLElement>("section.docs-section[id]")
378
+ );
379
+ if (sections.length === 0) return undefined;
380
+ const io = new IntersectionObserver(
381
+ (entries) => {
382
+ const visible = entries
383
+ .filter((e) => e.isIntersecting)
384
+ .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
385
+ if (visible[0]) setActiveId(visible[0].target.id);
386
+ },
387
+ { rootMargin: "-72px 0px -65% 0px", threshold: 0 }
388
+ );
389
+ sections.forEach((s) => io.observe(s));
390
+ return () => io.disconnect();
391
+ }, []);
392
+
393
+ const installCmd = INSTALL_CMDS[installTab];
394
+
395
+ const colorRows = useMemo(
396
+ () =>
397
+ TOKENS.map(([hex, name, desc, varName]) => (
398
+ <div className="docs-token-row" key={varName}>
399
+ <div className="swatch" style={{ background: hex }} />
400
+ <div className="name">
401
+ {name}
402
+ <span className="desc">{desc}</span>
403
+ </div>
404
+ <div className="var">{varName}</div>
405
+ <div className="docs-row" style={{ gap: 8 }}>
406
+ <span className="hex-val">{hex.toUpperCase()}</span>
407
+ <CopyButton value={hex.toUpperCase()} />
408
+ </div>
356
409
  </div>
357
- </article>
358
- <article className="gc-panel gc-panel--separator">
359
- <header className="gc-panel__title">Tooltips</header>
360
- <div className="gc-panel__content">
361
- <Flex className="gm-spacing-bl">
362
- <FlexItem isGrow>
363
- <Tooltip type="danger">Tooltip danger message</Tooltip>
364
- </FlexItem>
365
- <FlexItem isGrow>
366
- <Tooltip type="success">Tooltip success message</Tooltip>
367
- </FlexItem>
368
- </Flex>
410
+ )),
411
+ []
412
+ );
413
+
414
+ return (
415
+ <div className="docs">
416
+ <header className="docs-topbar">
417
+ <a href="#top" className="docs-brand">
418
+ <HexLogoMark />
419
+ <span>graphen</span>
420
+ <span className="ver">{VERSION}</span>
421
+ </a>
422
+ <div
423
+ className="docs-search"
424
+ role="search"
425
+ aria-label="Search documentation"
426
+ >
427
+ <StrokeIcon size={14} stroke={2}>
428
+ <circle cx="11" cy="11" r="7" />
429
+ <path d="m20 20-3.5-3.5" />
430
+ </StrokeIcon>
431
+ <span>Search components, tokens…</span>
432
+ <span className="kbd">
433
+ <span>⌘</span>
434
+ <span>K</span>
435
+ </span>
369
436
  </div>
370
- </article>
371
- <article className="gc-panel gc-panel--separator gm-spacing-tl">
372
- <header className="gc-panel__title">Textarea</header>
373
- <div className="gc-panel__content">
374
- <textarea className="gc-textarea" />
437
+ <div className="docs-tools">
438
+ <button
439
+ type="button"
440
+ className="docs-icon-btn"
441
+ onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
442
+ aria-label="Toggle theme"
443
+ title="Toggle theme"
444
+ >
445
+ {theme === "dark" ? (
446
+ <StrokeIcon stroke={1.8}>
447
+ <circle cx="12" cy="12" r="4" />
448
+ <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
449
+ </StrokeIcon>
450
+ ) : (
451
+ <StrokeIcon stroke={1.8}>
452
+ <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
453
+ </StrokeIcon>
454
+ )}
455
+ </button>
456
+ <a
457
+ className="docs-icon-btn"
458
+ href="https://github.com/coda-it/graphen"
459
+ aria-label="GitHub"
460
+ title="GitHub"
461
+ >
462
+ <svg
463
+ width={16}
464
+ height={16}
465
+ viewBox="0 0 24 24"
466
+ fill="currentColor"
467
+ aria-hidden="true"
468
+ >
469
+ <path d="M12 .5a11.5 11.5 0 0 0-3.64 22.41c.58.1.79-.25.79-.56v-2c-3.21.7-3.88-1.55-3.88-1.55-.52-1.33-1.28-1.69-1.28-1.69-1.05-.72.08-.7.08-.7 1.16.08 1.78 1.2 1.78 1.2 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.56-.29-5.26-1.28-5.26-5.69 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.45.11-3.02 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.78 0c2.21-1.49 3.18-1.18 3.18-1.18.62 1.57.23 2.73.11 3.02.74.81 1.18 1.84 1.18 3.1 0 4.42-2.7 5.39-5.27 5.68.41.36.78 1.07.78 2.16v3.2c0 .31.21.67.8.55A11.5 11.5 0 0 0 12 .5z" />
470
+ </svg>
471
+ </a>
375
472
  </div>
376
- </article>
377
- <article className="gc-panel gc-panel--separator">
378
- <header className="gc-panel__title">Input</header>
379
- <div className="gc-panel__content">
380
- <p>
381
- <div className="gc-input">
382
- <label htmlFor="input-1" className="gc-input__label">
383
- Input
384
- </label>
385
- <input id="input-1" className="gc-input__field" />
386
- </div>{" "}
387
- <div className="gc-input">
388
- <label htmlFor="input-2" className="gc-input__label">
389
- Inline
390
- </label>
391
- <input id="input-2" className="gc-input__field" />
392
- </div>
393
- </p>
394
- <p>
395
- <div className="gc-input gc-input--full">
396
- <label htmlFor="input-3" className="gc-input__label">
397
- Full Input
398
- </label>
399
- <input id="input-3" className="gc-input__field" />
473
+ </header>
474
+
475
+ <div className="docs-app" id="top">
476
+ <nav className="docs-sidebar" aria-label="Documentation">
477
+ {NAV.map((group) => (
478
+ <div className="group" key={group.title}>
479
+ <div className="group-title">{group.title}</div>
480
+ {group.items.map((item) => (
481
+ <a
482
+ key={item.id}
483
+ href={`#${item.id}`}
484
+ className={activeId === item.id ? "active" : ""}
485
+ >
486
+ {item.label}
487
+ {"count" in item && item.count ? (
488
+ <span className="count">{item.count}</span>
489
+ ) : null}
490
+ </a>
491
+ ))}
400
492
  </div>
401
- </p>
402
- <p>
403
- <div className="gc-input gc-input--full">
404
- <label htmlFor="input-4" className="gc-input__label">
405
- Disabled Input
406
- </label>
407
- <input
408
- id="input-4"
409
- className="gc-input__field"
410
- disabled
411
- value="Disabled input"
412
- />
493
+ ))}
494
+ <div className="group">
495
+ <div className="group-title">Resources</div>
496
+ <a href="https://github.com/coda-it/graphen">Source on GitHub ↗</a>
497
+ </div>
498
+ </nav>
499
+
500
+ <main className="docs-content">
501
+ <section className="docs-section" id="introduction">
502
+ <div className="docs-eyebrow">
503
+ <span className="dot" /> Component library · {VERSION} · MIT
413
504
  </div>
414
- </p>
415
- <p>
416
- <Input label="Success input" type="text" validation="success" />
417
- </p>
418
- <p>
419
- <Validation type="danger" message="Validation error message">
420
- <Input
421
- label="Validated input with tooltip"
422
- type="text"
423
- validation="danger"
424
- />
425
- </Validation>
426
- </p>
427
- </div>
428
- </article>
429
- <article className="gc-panel gc-panel--separator">
430
- <header className="gc-panel__title">LED</header>
431
- <div className="gc-panel__content">
432
- <p>
433
- <div className="gc-led gc-led--red" />{" "}
434
- <div className="gc-led gc-led--red gc-led--blink" />
435
- </p>
436
- <p>
437
- <div className="gc-led gc-led--green" />{" "}
438
- <div className="gc-led gc-led--green gc-led--blink" />
439
- </p>
440
- <p>
441
- <div className="gc-led gc-led--blue" />{" "}
442
- <div className="gc-led gc-led--blue gc-led--blink" />
443
- </p>
444
- </div>
445
- </article>
446
- <article className="gc-panel gc-panel--separator">
447
- <header className="gc-panel__title">Card</header>
448
- <div className="gc-panel__content">
449
- <div className="gc-flex gm-spacing-bl">
450
- <div className="gc-flex__item gc-card gc-panel">
451
- <div className="gc-panel__title">Card</div>
452
- <div className="gc-panel__content">
453
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
454
- eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
455
- enim ad minim veniam, quis nostrud exercitation ullamco laboris
456
- nisi ut aliquip ex ea commodo consequat.
457
- </div>
458
- <div className="gc-panel__footer">
459
- <button className="gc-btn">Button</button>
460
- </div>
505
+ <h1 className="docs-title">
506
+ A minimal toolkit for clean&nbsp;interfaces.
507
+ </h1>
508
+ <p className="docs-lede">
509
+ Graphen is a small, opinionated set of UI primitives — built on
510
+ plain CSS variables and lightweight markup. No runtime, no theming
511
+ engine. Drop the stylesheet, use the classes, ship.
512
+ </p>
513
+
514
+ <div className="docs-meta-row">
515
+ <span>
516
+ <strong>14</strong> components
517
+ </span>
518
+ <span className="sep" />
519
+ <span>
520
+ <strong>32</strong> tokens
521
+ </span>
522
+ <span className="sep" />
523
+ <span>Zero runtime</span>
524
+ <span className="sep" />
525
+ <span>SCSS first</span>
461
526
  </div>
462
- </div>
463
- <div className="gc-flex">
464
- <div className="gc-flex__item gc-card gc-card--default gc-panel gm-spacing-rl">
465
- <div className="gc-panel__title">Card + default</div>
466
- <div className="gc-panel__content">
467
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
468
- eiusmod tempor incididunt ut labore et dolore magna aliqua.
527
+
528
+ <div
529
+ className="docs-install"
530
+ id="installation"
531
+ role="group"
532
+ aria-label="Install Graphen"
533
+ >
534
+ <div className="tabs" role="tablist">
535
+ {(
536
+ Object.keys(INSTALL_CMDS) as Array<keyof typeof INSTALL_CMDS>
537
+ ).map((k) => (
538
+ <button
539
+ key={k}
540
+ type="button"
541
+ role="tab"
542
+ className="tab"
543
+ aria-selected={installTab === k}
544
+ onClick={() => setInstallTab(k)}
545
+ >
546
+ {k}
547
+ </button>
548
+ ))}
469
549
  </div>
470
- <div className="gc-panel__footer">
471
- <button className="gc-btn gc-btn--primary">
472
- Button + primary
550
+ <div className="cmd">
551
+ <span className="prompt">$</span>
552
+ <span>{installCmd}</span>
553
+ <button
554
+ type="button"
555
+ aria-label="Copy install command"
556
+ title="Copy"
557
+ onClick={async () => {
558
+ try {
559
+ await navigator.clipboard.writeText(installCmd);
560
+ } catch {
561
+ /* noop */
562
+ }
563
+ setInstallCopied(true);
564
+ window.setTimeout(() => setInstallCopied(false), 900);
565
+ }}
566
+ style={
567
+ installCopied ? { color: "var(--gb-success)" } : undefined
568
+ }
569
+ >
570
+ <StrokeIcon size={14}>
571
+ <rect x="9" y="9" width="11" height="11" rx="2" />
572
+ <path d="M5 15V5a2 2 0 0 1 2-2h10" />
573
+ </StrokeIcon>
473
574
  </button>
474
575
  </div>
475
576
  </div>
476
- <div className="gc-flex__item gc-card gc-card--gradient gc-panel">
477
- <div className="gc-panel__title">Card + gradient</div>
478
- <div className="gc-panel__content">
479
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
480
- eiusmod tempor incididunt ut labore et dolore magna aliqua.
481
- </div>
482
- <div className="gc-separator" />
483
- <div className="gc-panel__footer gc-panel__footer--separated">
484
- <button className="gc-btn gc-btn--primary">
485
- Button + primary
486
- </button>
577
+
578
+ <div className="docs-comp-index" aria-label="Component index">
579
+ <a href="#colors">
580
+ <span className="nm">Colors</span>
581
+ <span className="st">8 tokens</span>
582
+ </a>
583
+ <a href="#typography">
584
+ <span className="nm">Typography</span>
585
+ <span className="st">5 sizes</span>
586
+ </a>
587
+ <a href="#button">
588
+ <span className="nm">Button</span>
589
+ <span className="st">5 variants</span>
590
+ </a>
591
+ <a href="#input">
592
+ <span className="nm">Input</span>
593
+ <span className="st">stable</span>
594
+ </a>
595
+ </div>
596
+ </section>
597
+
598
+ <section className="docs-section" id="colors">
599
+ <div className="docs-section-eyebrow">Foundations / 01</div>
600
+ <h2 className="docs-section-title">Colors</h2>
601
+ <p className="docs-section-desc">
602
+ Brand tokens are exposed as Sass variables and matching CSS custom
603
+ properties. Click any value to copy. Component tokens (
604
+ <code>$gb-color-component</code>) are the only colors used as
605
+ backgrounds for surfaces — everything else is reserved for accent
606
+ or status.
607
+ </p>
608
+ <div className="docs-tokens" role="table" aria-label="Color tokens">
609
+ {colorRows}
610
+ </div>
611
+ </section>
612
+
613
+ <section className="docs-section" id="typography">
614
+ <div className="docs-section-eyebrow">Foundations / 02</div>
615
+ <h2 className="docs-section-title">Typography</h2>
616
+ <p className="docs-section-desc">
617
+ Graphen uses the system font stack by default, with a single
618
+ optical scale of five steps. Numerics are tabular for tables;{" "}
619
+ <code>code</code> uses the monospace stack.
620
+ </p>
621
+ {TYPE_SCALE.map((row) => (
622
+ <div className="docs-type-row" key={row.token}>
623
+ <div className="meta">{row.meta}</div>
624
+ <div className="meta">{row.token}</div>
625
+ <div className="sample" style={row.style}>
626
+ {row.sample}
627
+ </div>
487
628
  </div>
629
+ ))}
630
+ </section>
631
+
632
+ <section className="docs-section" id="spacing">
633
+ <div className="docs-section-eyebrow">Foundations / 03</div>
634
+ <h2 className="docs-section-title">Spacing &amp; radius</h2>
635
+ <p className="docs-section-desc">
636
+ A four-pixel base scale. Use the smallest token that still reads
637
+ as deliberate; double up rather than introduce new sizes.
638
+ </p>
639
+ <div className="docs-space-grid">
640
+ {SPACING.map(([token, px, rem, w]) => (
641
+ <div className="docs-space-row" key={token}>
642
+ <span className="token">{token}</span>
643
+ <span className="px">{px}</span>
644
+ <span className="px">{rem}</span>
645
+ <div className="vis" style={{ width: w }} />
646
+ </div>
647
+ ))}
488
648
  </div>
489
- </div>
490
- </div>
491
- </article>
492
- <article className="gc-panel gc-panel--separator">
493
- <header className="gc-panel__title">Alerts</header>
494
- <div className="gc-panel__content">
495
- <p>
496
- <div className="gc-alert">Alert</div>
497
- </p>
498
- <p>
499
- <div className="gc-alert gc-alert--success">Alert + success</div>
500
- </p>
501
- <p>
502
- <div className="gc-alert gc-alert--info">Alert + info</div>
503
- </p>
504
- <p>
505
- <div className="gc-alert gc-alert--danger">Alert + danger</div>
506
- </p>
507
- </div>
508
- </article>
509
- <article className="gc-panel gc-panel--separator">
510
- <header className="gc-panel__title">Loader</header>
511
- <div className="gc-panel__content">
512
- <Loader />
513
- </div>
514
- </article>
515
- <article className="gc-panel gc-panel--separator">
516
- <header className="gc-panel__title">Scroller</header>
517
- <div className="gc-panel__content">
518
- <Scroller onScrollChange={_.noop} min={10} max={100} />
519
- </div>
520
- </article>
521
- <article className="gc-panel gc-panel--separator">
522
- <header className="gc-panel__title">Joystick</header>
523
- <div className="gc-panel__content">
524
- <Joystick onPositionChange={_.noop} isEnabled />
525
- </div>
526
- </article>
527
- <article className="gc-panel gc-panel--separator">
528
- <header className="gc-panel__title">Accordion</header>
529
- <div className="gc-panel__content">
530
- <Accordion title="Accordion title">
531
- <p>
532
- Some content <span>here</span>
649
+ <div className="docs-spacer-y" />
650
+ <h3 className="docs-subsection-title">Radius</h3>
651
+ <div className="docs-space-grid">
652
+ {RADIUS.map(([token, px, r]) => (
653
+ <div className="docs-space-row" key={token}>
654
+ <span className="token">{token}</span>
655
+ <span className="px">{px}</span>
656
+ <span className="px" />
657
+ <div className="swatch-radius" style={{ borderRadius: r }} />
658
+ </div>
659
+ ))}
660
+ </div>
661
+ </section>
662
+
663
+ <section className="docs-section" id="iconography">
664
+ <div className="docs-section-eyebrow">Foundations / 04</div>
665
+ <h2 className="docs-section-title">Iconography</h2>
666
+ <p className="docs-section-desc">
667
+ Stroke-based, 24 × 24 viewBox, 1.6 px stroke. Inherit color from
668
+ the parent. Click an icon to copy its name.
533
669
  </p>
534
- <p>Multiple elements are allowed</p>
535
- </Accordion>
536
- </div>
537
- </article>
538
- <article className="gc-panel gc-panel--separator">
539
- <header className="gc-panel__title">Dialog</header>
540
- <div className="gc-panel__content">
541
- <Flex>
542
- <FlexItem className="gm-spacing-rl">
543
- <Button
544
- className="gc-btn--primary"
545
- onClick={() => setIsDialogVisible(true)}
546
- >
547
- Open dialog
548
- </Button>
549
- </FlexItem>
550
- <FlexItem>
551
- <Button
552
- className="gc-btn--primary"
553
- onClick={() => setIsDialogWithOverlayVisible(true)}
554
- >
555
- Open dialog with overlay
670
+ <div className="docs-icon-grid">
671
+ {ICONS.map(([name, d]) => (
672
+ <IconCell key={name} name={name} d={d} />
673
+ ))}
674
+ </div>
675
+ </section>
676
+
677
+ <section className="docs-section" id="logo">
678
+ <div className="docs-section-eyebrow">Components / 01</div>
679
+ <h2 className="docs-section-title">Logo</h2>
680
+ <p className="docs-section-desc">
681
+ A monospace wordmark with a single blinking-cursor accent. The
682
+ cursor blink is a CSS animation; pause it via{" "}
683
+ <code>prefers-reduced-motion</code>.
684
+ </p>
685
+ <Demo stageClass="center" code="<Logo />">
686
+ <Logo />
687
+ </Demo>
688
+ </section>
689
+
690
+ <section className="docs-section" id="button">
691
+ <div className="docs-section-eyebrow">Components / 02</div>
692
+ <h2 className="docs-section-title">Button</h2>
693
+ <p className="docs-section-desc">
694
+ Five visual variants and a small size. Buttons are{" "}
695
+ <code>&lt;button&gt;</code> elements; never wrap them in anchors.
696
+ </p>
697
+
698
+ <h3 className="docs-subsection-title">Variants</h3>
699
+ <Demo
700
+ code={`<Button className="gc-btn--primary">Primary</Button>
701
+ <Button>Default</Button>
702
+ <Button className="gc-btn--outline">Outline</Button>
703
+ <Button className="gc-btn--ghost">Ghost</Button>
704
+ <Button className="gc-btn--success">Success</Button>
705
+ <Button className="gc-btn--danger">Danger</Button>
706
+ <Button className="gc-btn--primary" isDisabled>Disabled</Button>`}
707
+ >
708
+ <Button className="gc-btn--primary">Primary</Button>
709
+ <Button>Default</Button>
710
+ <Button className="gc-btn--outline">Outline</Button>
711
+ <Button className="gc-btn--ghost">Ghost</Button>
712
+ <Button className="gc-btn--success">Success</Button>
713
+ <Button className="gc-btn--danger">Danger</Button>
714
+ <Button className="gc-btn--primary" isDisabled>
715
+ Disabled
556
716
  </Button>
557
- </FlexItem>
558
- </Flex>
559
-
560
- {isDialogVisible && (
561
- <Dialog>
562
- <Panel>
563
- <PanelTitle>Dialog</PanelTitle>
564
- <PanelContent>
565
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
566
- do eiusmod tempor incididunt ut labore et dolore magna aliqua.
567
- Ut enim ad minim veniam, quis nostrud exercitation ullamco
568
- laboris nisi ut aliquip ex ea commodo consequat. Duis aute
569
- irure dolor in reprehenderit in voluptate velit esse cillum
570
- dolore eu fugiat nulla pariatur.
571
- </PanelContent>
572
- <PanelFooter>
573
- <Button
574
- onClick={() => setIsDialogVisible(false)}
575
- className="gc-btn--primary"
576
- >
577
- Close
578
- </Button>
579
- </PanelFooter>
580
- </Panel>
581
- </Dialog>
582
- )}
583
-
584
- {isDialogWithOverlayVisible && (
585
- <Dialog isOverlay>
586
- <Panel>
587
- <PanelTitle>Dialog</PanelTitle>
588
- <PanelContent>
589
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
590
- do eiusmod tempor incididunt ut labore et dolore magna aliqua.
591
- Ut enim ad minim veniam, quis nostrud exercitation ullamco
592
- laboris nisi ut aliquip ex ea commodo consequat. Duis aute
593
- irure dolor in reprehenderit in voluptate velit esse cillum
594
- dolore eu fugiat nulla pariatur.
595
- </PanelContent>
596
- <PanelFooter>
597
- <Button
598
- onClick={() => setIsDialogWithOverlayVisible(false)}
599
- className="gc-btn--primary"
600
- >
601
- Close
602
- </Button>
603
- </PanelFooter>
604
- </Panel>
605
- </Dialog>
606
- )}
607
- </div>
608
- </article>
609
- <article className="gc-panel gc-panel--separator">
610
- <header className="gc-panel__title">Dropdown Menu</header>
611
- <div className="gc-panel__content">
612
- <p>
613
- <Dropdown
614
- initValue={{ label: "Select value", value: "selectValue" }}
615
- label="Dropdown label"
616
- items={[
617
- { label: "Red", value: "red" },
618
- { label: "Blue", value: "blue" },
619
- ]}
620
- onChange={_.noop}
621
- />
622
- </p>
623
- <p>
624
- <Dropdown
625
- initValue={{ label: "Select value", value: "selectValue" }}
626
- label="Disabled dropdown"
627
- items={[
628
- { label: "Red", value: "red" },
629
- { label: "Blue", value: "blue" },
630
- ]}
631
- onChange={_.noop}
632
- isDisabled
633
- />
634
- </p>
635
- </div>
636
- </article>
637
- <article className="gc-panel gc-panel--separator">
638
- <header className="gc-panel__title">Skeleton</header>
639
- <div className="gc-panel__content">
640
- <p>
641
- <Skeleton />
642
- </p>
643
- <p>
644
- <Skeleton />
645
- </p>
646
- <p>
647
- <Skeleton />
648
- </p>
649
- </div>
650
- </article>
651
- </>
717
+ </Demo>
718
+
719
+ <h3 className="docs-subsection-title">Sizes</h3>
720
+ <Demo
721
+ code={`<Button className="gc-btn--small gc-btn--primary">Small</Button>
722
+ <Button className="gc-btn--primary">Default</Button>
723
+ <Button className="gc-btn--large gc-btn--primary">Large</Button>`}
724
+ >
725
+ <Button className="gc-btn--small gc-btn--primary">Small</Button>
726
+ <Button className="gc-btn--primary">Default</Button>
727
+ <Button className="gc-btn--large gc-btn--primary">Large</Button>
728
+ </Demo>
729
+ </section>
730
+
731
+ <section className="docs-section" id="link">
732
+ <div className="docs-section-eyebrow">Components / 03</div>
733
+ <h2 className="docs-section-title">Link</h2>
734
+ <p className="docs-section-desc">
735
+ Two flavors: <em>primary</em> (brand-colored, for navigation and
736
+ calls to action) and <em>default</em> (inherits text color, for
737
+ inline references).
738
+ </p>
739
+ <Demo
740
+ stageClass="column"
741
+ code={`<Link link="#">Primary link</Link>
742
+ <Link link="#" skin={constants.SKIN_DEFAULT}>Default link</Link>`}
743
+ >
744
+ <p style={{ margin: 0, maxWidth: "60ch" }}>
745
+ For more on the system, see{" "}
746
+ <Link link="#">the design principles</Link>, or read our{" "}
747
+ <Link link="#" skin={constants.SKIN_DEFAULT}>
748
+ release notes
749
+ </Link>{" "}
750
+ from last quarter.
751
+ </p>
752
+ </Demo>
753
+ </section>
754
+
755
+ <section className="docs-section" id="header-comp">
756
+ <div className="docs-section-eyebrow">Components / 04</div>
757
+ <h2 className="docs-section-title">Header</h2>
758
+ <p className="docs-section-desc">
759
+ A small section header pattern — icon, title, optional metadata.
760
+ Used to introduce dashboards, lists and panels.
761
+ </p>
762
+ <Demo
763
+ stageClass="column"
764
+ code={`<header className="gc-section-header">
765
+ <span className="gc-section-header__icon">…</span>
766
+ <span className="gc-section-header__title">Component overview</span>
767
+ <span className="gc-section-header__sub">14 items</span>
768
+ </header>`}
769
+ >
770
+ <header className="gc-section-header">
771
+ <span className="gc-section-header__icon">
772
+ <StrokeIcon stroke={1.8}>
773
+ <polygon points="12,3 21,7.5 21,16.5 12,21 3,16.5 3,7.5" />
774
+ </StrokeIcon>
775
+ </span>
776
+ <span className="gc-section-header__title">
777
+ Component overview
778
+ </span>
779
+ <span className="gc-section-header__sub">14 items</span>
780
+ </header>
781
+ <header className="gc-section-header gc-section-header--success">
782
+ <span className="gc-section-header__icon">
783
+ <StrokeIcon size={14} stroke={2.2}>
784
+ <path d="m5 12 5 5 9-11" />
785
+ </StrokeIcon>
786
+ </span>
787
+ <span className="gc-section-header__title">Build passing</span>
788
+ <span className="gc-section-header__sub">2 min ago</span>
789
+ </header>
790
+ </Demo>
791
+ </section>
792
+
793
+ <section className="docs-section" id="separator">
794
+ <div className="docs-section-eyebrow">Components / 05</div>
795
+ <h2 className="docs-section-title">Separator</h2>
796
+ <p className="docs-section-desc">
797
+ Horizontal dividers. Prefer over borders on parent containers when
798
+ content needs to feel grouped, not boxed.
799
+ </p>
800
+ <Demo stageClass="column" code="<Separator />">
801
+ <div style={{ width: "100%", maxWidth: 480 }}>
802
+ <div style={{ fontSize: 13.5, padding: "6px 0" }}>
803
+ Section A
804
+ </div>
805
+ <Separator />
806
+ <div style={{ fontSize: 13.5, padding: "6px 0" }}>
807
+ Section B
808
+ </div>
809
+ <Separator />
810
+ <div style={{ fontSize: 13.5, padding: "6px 0" }}>
811
+ Section C
812
+ </div>
813
+ </div>
814
+ </Demo>
815
+ </section>
816
+
817
+ <section className="docs-section" id="input">
818
+ <div className="docs-section-eyebrow">Components / 06</div>
819
+ <h2 className="docs-section-title">Input</h2>
820
+ <p className="docs-section-desc">
821
+ Single-line text input with focus ring keyed to the brand color.
822
+ Try typing — focus state is real.
823
+ </p>
824
+ <Demo
825
+ stageClass="column"
826
+ code={`<Input label="Search" type="text" />
827
+ <Input label="Email" type="email" validation="success" />`}
828
+ >
829
+ <Input label="Search" type="text" />
830
+ <Input label="Email" type="email" validation="success" />
831
+ </Demo>
832
+ </section>
833
+
834
+ <section className="docs-section" id="dropdown">
835
+ <div className="docs-section-eyebrow">Components / 07</div>
836
+ <h2 className="docs-section-title">Dropdown</h2>
837
+ <p className="docs-section-desc">
838
+ A compact selector for short option lists. Keep labels clear and
839
+ avoid using it when users need to compare many values at once.
840
+ </p>
841
+ <Demo
842
+ code={`<Dropdown
843
+ label="Theme accent"
844
+ initValue={{ value: "${dropdownValue}", label: "${
845
+ DROPDOWN_ITEMS.find((item) => item.value === dropdownValue)
846
+ ?.label
847
+ }" }}
848
+ items={[
849
+ { value: "red", label: "Red" },
850
+ { value: "blue", label: "Blue" },
851
+ { value: "green", label: "Green" },
852
+ ]}
853
+ onChange={setDropdownValue}
854
+ />`}
855
+ >
856
+ <Dropdown
857
+ label="Theme accent"
858
+ initValue={
859
+ DROPDOWN_ITEMS.find((item) => item.value === dropdownValue) ||
860
+ DROPDOWN_ITEMS[0]
861
+ }
862
+ items={DROPDOWN_ITEMS}
863
+ onChange={setDropdownValue}
864
+ />
865
+ </Demo>
866
+ </section>
867
+
868
+ <section className="docs-section" id="card">
869
+ <div className="docs-section-eyebrow">Components / 08</div>
870
+ <h2 className="docs-section-title">Card</h2>
871
+ <p className="docs-section-desc">
872
+ A single-content surface. Don&apos;t nest cards. If a card has
873
+ more than one CTA, you probably want a list.
874
+ </p>
875
+ <Demo
876
+ code={`<Card>
877
+ <div className="docs-card-eyebrow">Release</div>
878
+ <h4>Graphen 1.0 is here</h4>
879
+ <p>A small, opinionated set …</p>
880
+ <Button className="gc-btn--small gc-btn--primary">Read announcement</Button>
881
+ </Card>`}
882
+ >
883
+ <Card>
884
+ <div className="docs-card-eyebrow">Release</div>
885
+ <h4 className="docs-card-title">Graphen 1.0 is here</h4>
886
+ <p className="docs-card-text">
887
+ A small, opinionated set of UI primitives — finally stable.
888
+ </p>
889
+ <Button className="gc-btn--small gc-btn--primary">
890
+ Read announcement
891
+ </Button>
892
+ </Card>
893
+ <Card>
894
+ <div className="docs-card-eyebrow">Tutorial</div>
895
+ <h4 className="docs-card-title">Theming with CSS variables</h4>
896
+ <p className="docs-card-text">
897
+ Override <code>--gb-primary</code> on <code>:root</code> to
898
+ retheme everything in one line.
899
+ </p>
900
+ <Button className="gc-btn--small gc-btn--secondary">
901
+ Open guide
902
+ </Button>
903
+ </Card>
904
+ </Demo>
905
+ </section>
906
+
907
+ <section className="docs-section" id="badge">
908
+ <div className="docs-section-eyebrow">Components / 09</div>
909
+ <h2 className="docs-section-title">Badge</h2>
910
+ <p className="docs-section-desc">
911
+ Small status pills. Use sparingly — one badge per row, never
912
+ decorative.
913
+ </p>
914
+ <Demo
915
+ code={`<Badge showPulse>idle</Badge>
916
+ <Badge type="info" showPulse>info</Badge>
917
+ <Badge type="success" showPulse>success</Badge>
918
+ <Badge type="danger" showPulse>danger</Badge>`}
919
+ >
920
+ <Badge showPulse>idle</Badge>
921
+ <Badge type="info" showPulse>
922
+ info
923
+ </Badge>
924
+ <Badge type="success" showPulse>
925
+ success
926
+ </Badge>
927
+ <Badge type="danger" showPulse>
928
+ danger
929
+ </Badge>
930
+ </Demo>
931
+ </section>
932
+
933
+ <footer className="docs-footer">
934
+ <div>graphen · {VERSION} · MIT</div>
935
+ <div>Built by the CODA_ team</div>
936
+ </footer>
937
+ </main>
938
+
939
+ <aside className="docs-toc" aria-label="On this page">
940
+ <div className="toc-title">On this page</div>
941
+ {TOC.map(([id, label]) => (
942
+ <a
943
+ key={id}
944
+ href={`#${id}`}
945
+ className={activeId === id ? "active" : ""}
946
+ >
947
+ {label}
948
+ </a>
949
+ ))}
950
+ </aside>
951
+ </div>
952
+ </div>
652
953
  );
653
954
  }
654
955
 
956
+ const appContainer = document.querySelector(".js-example");
655
957
  if (appContainer) {
656
- render(
657
- <section className="gc-page">
658
- <ExampleApp />
659
- </section>,
660
- appContainer
661
- );
958
+ render(<App />, appContainer);
662
959
  }
663
- /* eslint-enable */