highsoft-ui 1.0.116 → 1.0.118

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.
Files changed (94) hide show
  1. package/.cursorrules +845 -0
  2. package/CLAUDE.md +845 -0
  3. package/dist/Box-Bi-n_ZxQ.js +69 -0
  4. package/dist/Button-CJp__tle.js +63 -0
  5. package/dist/ButtonGroup-M9-A5Xva.js +53 -0
  6. package/dist/{Container-LAkF4ShX.js → Container-DI2smQRq.js} +1 -1
  7. package/dist/{CookiePolicyModal-B2mvebvV.js → CookiePolicyModal-BZkVbILl.js} +9 -43
  8. package/dist/CopyCode-BLXf0o74.js +40 -0
  9. package/dist/DiscordInvite-Mkr81Pib.js +92 -0
  10. package/dist/Footer-bUIou6Hl.js +83 -0
  11. package/dist/FooterBottom-_AezmdZB.js +133 -0
  12. package/dist/FooterHelp-CdeMRh1G.js +112 -0
  13. package/dist/FooterLinks-BUKcScC4.js +287 -0
  14. package/dist/{GPTInput-DRFGKJ2u.js → GPTInput-DKxJOGYt.js} +1 -1
  15. package/dist/{Header-BW8ilZxt.js → Header-6RIZc0v3.js} +90 -85
  16. package/dist/Heading-DuolDa27.js +19 -0
  17. package/dist/Icon-Dwo9ZDNX.js +31 -0
  18. package/dist/InputGrouped-CiA86XL7.js +221 -0
  19. package/dist/InputSolo-cLqKBh1c.js +122 -0
  20. package/dist/Label-BH3YxfNt.js +29 -0
  21. package/dist/Link-DGMiLJES.js +21 -0
  22. package/dist/{Logo-CZ5LYqub.js → Logo-h9bQL-cT.js} +1 -1
  23. package/dist/NewsletterSignup-eGx4jwiM.js +137 -0
  24. package/dist/{Notification-DHcQtd1k.js → Notification-CJ3iQv3Z.js} +6 -6
  25. package/dist/{PrefPanel-BTX6RAo4.js → PrefPanel-25erkPRB.js} +8 -8
  26. package/dist/{Search-DAPCZo13.js → Search-CDHkDXCm.js} +15 -15
  27. package/dist/Typography-nzBx3Yxn.js +49 -0
  28. package/dist/YoutubeModal-DNaiM6RL.js +89 -0
  29. package/dist/components/Box/index.js +2 -0
  30. package/dist/components/Button/index.js +1 -1
  31. package/dist/components/ButtonGroup/index.js +2 -0
  32. package/dist/components/Container/index.js +1 -1
  33. package/dist/components/CopyCode/index.js +2 -0
  34. package/dist/components/CopyCommand/index.js +2 -0
  35. package/dist/components/DiscordInvite/index.js +2 -0
  36. package/dist/components/Footer/CookiePolicyModal.js +1 -1
  37. package/dist/components/Footer/FooterBottom.js +2 -0
  38. package/dist/components/Footer/FooterHelp.js +2 -0
  39. package/dist/components/Footer/FooterLinks.js +2 -0
  40. package/dist/components/Footer/YoutubeModal.js +2 -0
  41. package/dist/components/Footer/index.js +1 -1
  42. package/dist/components/GPTInput/index.js +1 -1
  43. package/dist/components/Header/index.js +1 -1
  44. package/dist/components/Heading/index.js +2 -0
  45. package/dist/components/Icon/index.js +2 -0
  46. package/dist/components/InputGrouped/index.js +2 -0
  47. package/dist/components/InputSolo/index.js +2 -0
  48. package/dist/components/Label/index.js +2 -0
  49. package/dist/components/Link/index.js +2 -0
  50. package/dist/components/Logo/index.js +1 -1
  51. package/dist/components/NewsletterSignup/index.js +2 -0
  52. package/dist/components/Notification/index.js +1 -1
  53. package/dist/components/PrefPanel/index.js +1 -1
  54. package/dist/components/Search/index.js +1 -1
  55. package/dist/components/Typography/index.js +2 -0
  56. package/dist/{dist-CjrKJb_1.js → dist-GNbC83fc.js} +1 -1
  57. package/dist/{dist-0f0-S8_q.js → dist-fJmWSp8_.js} +1 -1
  58. package/dist/{esm-C1TDRARf.js → esm-T3Xn_ZHF.js} +208 -104
  59. package/dist/fa-ByhY-7YY.js +187 -0
  60. package/dist/highsoft-ui.css +1 -1
  61. package/dist/lib/components/Box/index.d.ts +28 -0
  62. package/dist/lib/components/Button/index.d.ts +9 -1
  63. package/dist/lib/components/ButtonGroup/index.d.ts +29 -0
  64. package/dist/lib/components/CopyCode/index.d.ts +12 -0
  65. package/dist/lib/components/CopyCommand/index.d.ts +6 -0
  66. package/dist/lib/components/DiscordInvite/index.d.ts +8 -0
  67. package/dist/lib/components/Footer/FooterBottom.d.ts +7 -0
  68. package/dist/lib/components/Footer/FooterHelp.d.ts +7 -0
  69. package/dist/lib/components/Footer/FooterLinks.d.ts +7 -0
  70. package/dist/lib/components/Footer/YoutubeModal.d.ts +1 -0
  71. package/dist/lib/components/Header/index.d.ts +14 -9
  72. package/dist/lib/components/Heading/index.d.ts +8 -0
  73. package/dist/lib/components/Icon/index.d.ts +26 -0
  74. package/dist/lib/components/InputGrouped/index.d.ts +60 -0
  75. package/dist/lib/components/InputSolo/index.d.ts +23 -0
  76. package/dist/lib/components/Label/index.d.ts +25 -0
  77. package/dist/lib/components/Link/index.d.ts +15 -0
  78. package/dist/lib/components/NewsletterSignup/index.d.ts +33 -0
  79. package/dist/lib/components/Typography/index.d.ts +13 -0
  80. package/dist/lib/main.d.ts +30 -3
  81. package/dist/lib/utils.d.ts +28 -0
  82. package/dist/main.js +23 -12
  83. package/dist/scss/mixins.scss +36 -0
  84. package/dist/scss/variables.scss +107 -0
  85. package/dist/{utils-C4QGLbxr.js → utils-BCI5dixg.js} +35 -15
  86. package/dist/utils.js +2 -2
  87. package/package.json +14 -6
  88. package/dist/Button-9RIB8ZR0.js +0 -47
  89. package/dist/Footer-COn-JnC7.js +0 -784
  90. package/dist/NewTag-C-3mRyvV.js +0 -26
  91. package/dist/components/NewTag/index.js +0 -2
  92. package/dist/lib/components/NewTag/index.d.ts +0 -8
  93. /package/dist/{clsx-ROFf4vs9.js → clsx-D7D_XZ_-.js} +0 -0
  94. /package/dist/{es2015-CzBBLWyD.js → es2015-C6iv4436.js} +0 -0
package/.cursorrules ADDED
@@ -0,0 +1,845 @@
1
+ # Highsoft Design System — AI Guide
2
+
3
+ This is the **highsoft-ui** component library and design token system for Highsoft products.
4
+
5
+ ## Quick setup
6
+
7
+ ```tsx
8
+ import 'highsoft-ui/css'
9
+ import 'highsoft-design-tokens/dist/css/tokens.css'
10
+
11
+ import { Button, Heading, Typography, Link, Container } from 'highsoft-ui'
12
+ ```
13
+
14
+ ## Core rules
15
+
16
+ 1. **NEVER use hardcoded colors.** Always use semantic CSS custom properties (`--HS-*`).
17
+ 2. **NEVER use primitives in UI.** Primitives (`--primitives-*`) are only for token definitions — use semantic tokens so dark mode works automatically.
18
+ 3. **Always use the component library** when a component exists. Don't recreate buttons, inputs, headings, etc.
19
+ 4. **Use design token animations** for all transitions — never hardcode easing or durations.
20
+
21
+ ## Semantic color tokens
22
+
23
+ Use these for all UI. They auto-switch between light and dark mode.
24
+
25
+ ### Backgrounds
26
+ | Token | Use |
27
+ |---|---|
28
+ | `--HS-surface-ui-background-0` | Card/panel background (white in light) |
29
+ | `--HS-surface-ui-background-50` | Subtle background (rows, hover states) |
30
+ | `--HS-surface-ui-background-100` | Slightly darker background (code blocks) |
31
+ | `--HS-common-background` | Page background |
32
+
33
+ ### Borders
34
+ | Token | Use |
35
+ |---|---|
36
+ | `--HS-surface-ui-border-100` | Default border (cards, dividers) |
37
+ | `--HS-surface-ui-border-200` | Stronger border (inputs, interactive) |
38
+ | `--HS-surface-ui-border-300` | Emphasis border |
39
+
40
+ ### Text
41
+ | Token | Use |
42
+ |---|---|
43
+ | `--HS-text-primary-default` | Headings, body text |
44
+ | `--HS-text-secondary-default` | Subtitles, descriptions |
45
+ | `--HS-text-tertiary-default` | Captions, hints, muted text |
46
+ | `--HS-text-brand-default-alt` | Links, accents, interactive |
47
+
48
+ ### Brand surfaces
49
+ | Token | Use |
50
+ |---|---|
51
+ | `--HS-surface-brand-background-50` | Subtle brand tint (badges, tags) |
52
+ | `--HS-surface-brand-background-100` | Brand background (selected state) |
53
+ | `--HS-surface-brand-border-200` | Brand border |
54
+
55
+ ### Status colors
56
+ Each status (success, danger, attention) has: `background`, `background-boxed`, `border`, `border-boxed` for surfaces, and text tokens like `--HS-text-success-Primary`, `--HS-text-danger-default`, `--HS-text-attention-default`.
57
+
58
+ ## Animation tokens
59
+
60
+ Never hardcode transitions. Use these CSS custom properties:
61
+
62
+ ```css
63
+ /* Bounce — for interactive elements (buttons, toggles, inputs) */
64
+ --animations-bounce-instant: 0.1s cubic-bezier(0.6, 1, 0.2, 1.2);
65
+ --animations-bounce-fast: 0.2s cubic-bezier(0.6, 1, 0.2, 1.2);
66
+ --animations-bounce-default: 0.3s cubic-bezier(0.6, 1, 0.2, 1.2);
67
+ --animations-bounce-slow: 0.4s cubic-bezier(0.6, 1, 0.2, 1.2);
68
+
69
+ /* Fade — for color changes, opacity, border transitions */
70
+ --animations-fade-instant: 0.1s cubic-bezier(0.85, 0, 0.15, 1);
71
+ --animations-fade-fast: 0.2s cubic-bezier(0.85, 0, 0.15, 1);
72
+ --animations-fade-default: 0.3s cubic-bezier(0.85, 0, 0.15, 1);
73
+
74
+ /* Classic — general purpose */
75
+ --animations-classic-default: 0.3s cubic-bezier(0.65, 0, 0.35, 1);
76
+ ```
77
+
78
+ Example:
79
+ ```css
80
+ transition: border-color var(--animations-fade-fast),
81
+ box-shadow var(--animations-bounce-default);
82
+ ```
83
+
84
+ ## Shadow tokens
85
+
86
+ ```css
87
+ --shadow-size-50 /* Subtle */
88
+ --shadow-size-100 /* Cards */
89
+ --shadow-size-200 /* Elevated (dropdowns, popovers) */
90
+ --shadow-size-300 /* Modals */
91
+ ```
92
+
93
+ ## Typography tokens
94
+
95
+ ```css
96
+ --typography-font-family /* IBM Plex Sans Variable */
97
+ --typography-size-75 /* 0.75rem — caption */
98
+ --typography-size-100 /* 0.875rem — small body */
99
+ --typography-size-200 /* 1rem — body */
100
+ --typography-size-300 /* 1.125rem — large body */
101
+ --typography-weight-normal: 400;
102
+ --typography-weight-medium: 500;
103
+ --typography-weight-semi: 600;
104
+ --typography-lineHeight-ui: 100%;
105
+ --typography-lineHeight-text: 130%;
106
+ ```
107
+
108
+ ## Components
109
+
110
+ ### Box
111
+ The foundational layout component. Use it instead of raw `<div>` with inline styles.
112
+
113
+ ```tsx
114
+ // Default card (background 0, border, radius 8, padding 24px)
115
+ <Box>
116
+ <Heading level={4}>Title</Heading>
117
+ <Typography size={200}>Content</Typography>
118
+ </Box>
119
+
120
+ // Larger card
121
+ <Box padding={700}>
122
+ Padding 32px
123
+ </Box>
124
+
125
+ // Subtle row
126
+ <Box background={50} padding={400}>
127
+ Row content
128
+ </Box>
129
+
130
+ // Interactive card with hover effect
131
+ <Box padding={700} radius={16} shadow={100} hoverable>
132
+ Hover me
133
+ </Box>
134
+
135
+ // Flex column with gap
136
+ <Box gap={700}>
137
+ <div>Item 1</div>
138
+ <div>Item 2</div>
139
+ </Box>
140
+
141
+ // No border, darker background
142
+ <Box border={false} background={100} padding={500}>
143
+ Muted section
144
+ </Box>
145
+
146
+ // Polymorphic — render as <section>, <a>, etc.
147
+ <Box as="section" background={100} padding={900}>
148
+ Full-width section
149
+ </Box>
150
+ ```
151
+
152
+ - **`background`:** `0` (card/white, default), `50` (subtle), `100` (darker)
153
+ - **`border`:** `true` (default), `200`, `300` for stronger, `false` to disable
154
+ - **`radius`:** `0`, `4`, `8` (default), `16`, `32`
155
+ - **`padding`:** `0`, `100`–`1000` (maps to `--containers-inner-padding-*`). Default `600` (24px). Use `700` (32px) for larger cards
156
+ - **`gap`:** `0`, `200`, `400`, `600`, `700`, `800`, `900`, `1000` (maps to `--item-spacing-*`, sets `display: flex; flex-direction: column`)
157
+ - **`shadow`:** `50`, `100`, `200`, `300`
158
+ - **`hoverable`:** adds animated border-highlight + shadow-lift on hover using `--animations-fade-fast` and `--animations-bounce-default`
159
+ - **`as`:** polymorphic — render as any HTML element
160
+
161
+ ### Button
162
+ ```tsx
163
+ <Button variant="brand" size={300}>Primary action</Button>
164
+ <Button variant="neutral" size={200}>Secondary</Button>
165
+ <Button variant="soft" size={200}>Soft</Button>
166
+ <Button variant="transparent" size={200}>Ghost</Button>
167
+ <Button variant="danger" size={200}>Destructive</Button>
168
+
169
+ // With icons (from @untitled-ui/icons-react)
170
+ <Button variant="brand" iconLeft={<Plus />}>Create</Button>
171
+ <Button variant="neutral" iconRight={<ArrowRight />}>Continue</Button>
172
+ <Button variant="soft" iconLeft={<Copy06 />} iconRight={<Check />}>Both sides</Button>
173
+ ```
174
+ - **Variants:** `brand`, `neutral`, `soft`, `transparent`, `plain`, `success`, `attention`, `danger`
175
+ - **Sizes:** `50`, `100`, `200`, `300`, `400`, `500`
176
+ - **`iconLeft`:** `ReactNode` — icon placed before the label
177
+ - **`iconRight`:** `ReactNode` — icon placed after the label
178
+ - **`as`:** polymorphic — auto-detects `<a>` when `href` is provided
179
+
180
+ ### Heading
181
+ ```tsx
182
+ <Heading level={1}>Page title</Heading>
183
+ <Heading level={2}>Section title</Heading>
184
+ <Heading level={4}>Card title</Heading>
185
+ ```
186
+ - **Levels:** `1`–`6`
187
+
188
+ ### Typography
189
+ ```tsx
190
+ <Typography size={200}>Body text</Typography>
191
+ <Typography size={75} style={{ color: 'var(--HS-text-tertiary-default)' }}>Caption</Typography>
192
+ <Typography size={300} weight="semi">Large semi-bold</Typography>
193
+ ```
194
+ - **Sizes:** `50`, `75`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`, `1000`
195
+ - **Weights:** `"normal"`, `"medium"`, `"semi"`
196
+
197
+ ### Link
198
+ ```tsx
199
+ <Link href="/page">Default link</Link>
200
+ <Link href="/page" variant="fancy">Animated link</Link>
201
+ ```
202
+ - **Variants:** `native` (subtle underline, default), `fancy` (animated slide-in underline)
203
+ - Polymorphic: `<Link as={RouterLink} to="/page">` for SPA navigation
204
+
205
+ ### Icon
206
+ ```tsx
207
+ <Icon name="chevron-right" size={200} />
208
+ ```
209
+
210
+ ### InputSolo
211
+ ```tsx
212
+ <InputSolo label="Email" placeholder="you@example.com" />
213
+ ```
214
+
215
+ ### InputGrouped / SelectGrouped / FormGroup / FormRow
216
+ ```tsx
217
+ <FormGroup>
218
+ <FormRow>
219
+ <InputGrouped label="First name" />
220
+ <InputGrouped label="Last name" />
221
+ </FormRow>
222
+ <SelectGrouped label="Country" options={[...]} />
223
+ </FormGroup>
224
+ ```
225
+
226
+ ### Label (formerly NewTag)
227
+ ```tsx
228
+ <Label variant="brand">New</Label> // Default with Stars02 icon
229
+ <Label variant="success" iconLeft={false}>Live</Label> // No icon
230
+ <Label variant="attention" iconLeft={<MyIcon />}>Warning</Label> // Custom icon
231
+ <Label variant="neutral" iconLeft={false}>v12.1</Label> // Plain text label
232
+ <Label variant="danger" border>Breaking</Label> // With border
233
+ ```
234
+ - **Variants:** `success` (default), `brand`, `attention`, `danger`, `neutral`
235
+ - **`iconLeft`:** `true` (Stars02, default), `false` (none), or any `ReactNode` — aligned with Button's `iconLeft` pattern
236
+ - **`icon`:** deprecated alias for `iconLeft` — still works but use `iconLeft` in new code
237
+ - **`border`:** adds a visible border
238
+ - **`children`:** custom text (defaults to "New")
239
+ - `NewTag` is a deprecated alias — use `Label` in new code
240
+
241
+ ### CopyCode
242
+ Pill-shaped inline code display with integrated copy button and success state.
243
+ ```tsx
244
+ <CopyCode>npm i highsoft-ui highsoft-design-tokens</CopyCode>
245
+ <CopyCode label="Copy" copiedLabel="Copied" duration={2000}>yarn add highsoft-ui</CopyCode>
246
+ ```
247
+ - Uses `Button` (soft → success variant) with `Copy06` / `Check` icons internally
248
+ - **`children`:** `string` — the code text to display and copy
249
+ - **`duration`:** ms for success state (default: 2000)
250
+
251
+ ### Layout
252
+ ```tsx
253
+ <Header />
254
+ <SubHeader />
255
+ <Container>{children}</Container>
256
+ <Footer />
257
+ ```
258
+
259
+ ### Notification
260
+ ```tsx
261
+ <Notification variant="success" title="Saved" />
262
+ <Notification variant="danger" title="Error" message="Something went wrong" />
263
+ ```
264
+
265
+ ## Hooks
266
+
267
+ ### useSuccessFlash
268
+ Triggers a temporary success state for any component. Use with Button variant switching for click confirmations (copy, save, submit, etc.).
269
+
270
+ ```tsx
271
+ import { useSuccessFlash } from 'highsoft-ui'
272
+
273
+ const [flash, flashing] = useSuccessFlash() // default 2s
274
+ const [flash, flashing] = useSuccessFlash(3000) // custom duration
275
+
276
+ <Button
277
+ variant={flashing ? 'success' : 'neutral'}
278
+ onClick={() => { doSomething(); flash(); }}
279
+ >
280
+ {flashing ? 'Done!' : 'Save'}
281
+ </Button>
282
+ ```
283
+
284
+ ### useClipboard
285
+ Copy text to clipboard with a built-in success state.
286
+
287
+ ```tsx
288
+ import { useClipboard } from 'highsoft-ui'
289
+
290
+ const [copy, copied] = useClipboard()
291
+
292
+ <Button
293
+ variant={copied ? 'success' : 'neutral'}
294
+ onClick={() => copy('some text')}
295
+ >
296
+ {copied ? 'Copied!' : 'Copy'}
297
+ </Button>
298
+ ```
299
+
300
+ ### Combining both for copy buttons
301
+ ```tsx
302
+ const [copy, copied] = useClipboard()
303
+ const [flash, flashing] = useSuccessFlash()
304
+ const active = copied || flashing
305
+
306
+ <button onClick={() => { copy(text); flash(); }}>
307
+ {active ? '✓ Copied!' : 'Copy'}
308
+ </button>
309
+ ```
310
+
311
+ ## Link mixin (SCSS only, no React)
312
+
313
+ For projects using only design tokens without the React library:
314
+
315
+ ```scss
316
+ @use 'highsoft-design-tokens/dist/scss/mixins/links' as *;
317
+
318
+ a { @include link; } // native — subtle underline
319
+ a.fancy { @include link(fancy); } // animated slide-in underline
320
+ ```
321
+
322
+ ## Building custom UI
323
+
324
+ Use the `<Box>` component for all layout containers. Prefer `<Box>` over raw `<div>` with inline styles.
325
+
326
+ ### Card
327
+ ```tsx
328
+ <Box background={0} border padding={700} radius={16}>
329
+ <Heading level={4}>Title</Heading>
330
+ <Typography size={200} style={{ color: 'var(--HS-text-secondary-default)' }}>
331
+ Description text
332
+ </Typography>
333
+ </Box>
334
+ ```
335
+
336
+ ### Row / list item
337
+ ```tsx
338
+ <Box background={50} border padding={400} radius={8} style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
339
+ {/* content */}
340
+ </Box>
341
+ ```
342
+
343
+ ### Interactive card
344
+ ```tsx
345
+ <Box background={0} border padding={700} radius={16} shadow={100} hoverable>
346
+ Hover lifts and highlights automatically
347
+ </Box>
348
+ ```
349
+
350
+ ### Stacked layout with gap
351
+ ```tsx
352
+ <Box background={0} border padding={700} radius={16} gap={700}>
353
+ <Heading level={4}>Section</Heading>
354
+ <Typography size={200}>Spaced with design token gap</Typography>
355
+ <Button variant="brand" size={300}>Action</Button>
356
+ </Box>
357
+ ```
358
+
359
+ ### Badge / tag pattern
360
+ ```tsx
361
+ <span style={{
362
+ display: 'inline-flex',
363
+ padding: '4px 10px',
364
+ borderRadius: 20,
365
+ fontSize: 11,
366
+ fontWeight: 600,
367
+ background: 'var(--HS-surface-brand-background-50)',
368
+ color: 'var(--HS-text-brand-default-alt)',
369
+ border: '1px solid var(--HS-surface-brand-border-200)',
370
+ }}>
371
+ Label
372
+ </span>
373
+ ```
374
+
375
+ ## Dark mode
376
+
377
+ Dark mode is automatic when using semantic tokens. Toggle with:
378
+ ```html
379
+ <html data-theme="dark">
380
+ ```
381
+ No code changes needed — all `--HS-*` tokens remap automatically.
382
+
383
+ ## Icon library
384
+
385
+ All icons come from **`@untitled-ui/icons-react`** (Untitled UI icon set). Never use emoji, ASCII characters (✓, ✗, →), or other icon libraries.
386
+
387
+ ```tsx
388
+ import { Plus, ArrowRight, Check, Copy06, Trash01, Settings04, Star01, Share07, Download01, Send01 } from '@untitled-ui/icons-react'
389
+ ```
390
+
391
+ Icons auto-size inside Button and Label based on their `size` prop:
392
+ | Button size | Icon size |
393
+ |---|---|
394
+ | `50` | 12×12 |
395
+ | `100` | 12×12 |
396
+ | `200` | 16×16 |
397
+ | `300` | 16×16 |
398
+ | `400` | 20×20 |
399
+ | `500` | 24×24 |
400
+
401
+ ### Icon prop pattern (consistent across components)
402
+
403
+ All components use the same `iconLeft` / `iconRight` naming:
404
+
405
+ ```tsx
406
+ // Button — ReactNode | undefined
407
+ <Button iconLeft={<Plus />}>Create</Button>
408
+ <Button iconRight={<ArrowRight />}>Next</Button>
409
+ <Button iconLeft={<Copy06 />} iconRight={<Check />}>Both</Button>
410
+
411
+ // Label — boolean | ReactNode (true = default Stars02 icon)
412
+ <Label iconLeft={true}>New</Label> // Stars02 (default)
413
+ <Label iconLeft={false}>v12.1</Label> // no icon
414
+ <Label iconLeft={<Check />}>Passed</Label> // custom icon
415
+
416
+ // CopyCode — icons handled internally (Copy06 → Check on success)
417
+ <CopyCode>npm i highsoft-ui</CopyCode>
418
+ ```
419
+
420
+ ## Component Do's and Don'ts
421
+
422
+ ### Button
423
+ - **Do** use `brand` for the single primary action per section
424
+ - **Don't** use multiple `brand` buttons side by side — use `neutral` or `soft` for secondary
425
+ - **Do** keep grouped buttons at the same `size`
426
+ - **Don't** mix sizes like `size={300}` next to `size={100}`
427
+ - **Do** use `iconLeft`/`iconRight` on standard-width buttons
428
+ - **Don't** add icons to `fullWidth` buttons — the wide gap looks disconnected
429
+ - **Do** use `href` for navigation so it renders as `<a>` (a11y, cmd-click, right-click)
430
+ - **Don't** use `<button>` with `onClick` + router.push for navigation
431
+ - **Do** use `useSuccessFlash` with `iconLeft={<Check />}` for success states
432
+ - **Don't** use ASCII characters like ✓ or ✗ — always use icon components
433
+ - **Do** use `danger` only for destructive actions (delete, remove, revoke)
434
+ - **Don't** use `danger` for "Cancel" or "Go back" — use `neutral` or `transparent`
435
+
436
+ ### Label
437
+ - **Do** use `iconLeft={false}` for plain text labels like version numbers
438
+ - **Don't** use Label for long text — it's designed for short status/category tags
439
+
440
+ ## WordPress blocks team guide
441
+
442
+ This section is for the team building WordPress Gutenberg blocks that match the Highsoft design system.
443
+
444
+ Gutenberg blocks use React, so there are **two contexts** with different approaches:
445
+
446
+ | Context | Technology | Approach |
447
+ |---|---|---|
448
+ | **Block editor** (`edit.js`) | React | Import and use `highsoft-ui` components directly |
449
+ | **Frontend output** (`save.js` / PHP render) | Static HTML | Use design token CSS custom properties with matching class names |
450
+
451
+ ### Block editor — use the React components directly
452
+
453
+ The block editor is React. Import components from `highsoft-ui` and map block attributes to props:
454
+
455
+ ```tsx
456
+ // edit.js — the editor preview uses real components
457
+ import 'highsoft-ui/css'
458
+ import 'highsoft-design-tokens/dist/css/tokens.css'
459
+ import { Button, Label, Heading, Typography, Box } from 'highsoft-ui'
460
+
461
+ export default function Edit({ attributes, setAttributes }) {
462
+ const { variant, size, label, href, iconName } = attributes
463
+
464
+ return (
465
+ <Box padding={700} radius={16}>
466
+ <Heading level={4}>{attributes.title}</Heading>
467
+ <Typography size={200} style={{ color: 'var(--HS-text-secondary-default)' }}>
468
+ {attributes.description}
469
+ </Typography>
470
+ <div style={{ display: 'flex', gap: 12, alignItems: 'center', marginTop: 16 }}>
471
+ <Button variant={variant} size={size} href={href}>
472
+ {label}
473
+ </Button>
474
+ <Label variant="brand" iconLeft={false}>Recommended</Label>
475
+ </div>
476
+ </Box>
477
+ )
478
+ }
479
+ ```
480
+
481
+ ```js
482
+ // block.json — map attributes to component props
483
+ {
484
+ "attributes": {
485
+ "variant": { "type": "string", "default": "brand" },
486
+ "size": { "type": "number", "default": 300 },
487
+ "label": { "type": "string", "default": "Get started" },
488
+ "href": { "type": "string" }
489
+ }
490
+ }
491
+ ```
492
+
493
+ **Block attribute names should match component prop names** (`variant`, `size`, `iconLeft`, `fullWidth`, etc.) so the mapping is 1:1.
494
+
495
+ ### Frontend output — use PHP dynamic render (recommended)
496
+
497
+ Use **`render.php`** instead of `save.js` for frontend output. The block stores only attributes (JSON), and PHP renders the HTML on every page load. This gives total control — update the template once and every block instance updates automatically, with no deprecation migrations.
498
+
499
+ ```json
500
+ // block.json — use render instead of save
501
+ {
502
+ "apiVersion": 3,
503
+ "name": "hs/button",
504
+ "title": "HS Button",
505
+ "render": "file:./render.php",
506
+ "attributes": {
507
+ "variant": { "type": "string", "default": "brand" },
508
+ "size": { "type": "number", "default": 300 },
509
+ "label": { "type": "string", "default": "Get started" },
510
+ "href": { "type": "string" },
511
+ "iconLeft": { "type": "string" }
512
+ }
513
+ }
514
+ ```
515
+
516
+ ```php
517
+ <?php
518
+ // render.php — generates the frontend HTML from attributes
519
+ $variant = $attributes['variant'] ?? 'brand';
520
+ $size = $attributes['size'] ?? 300;
521
+ $label = $attributes['label'] ?? 'Get started';
522
+ $href = $attributes['href'] ?? '#';
523
+ $icon = $attributes['iconLeft'] ?? '';
524
+
525
+ $classes = "hs-button hs-button--{$variant} hs-button--{$size}";
526
+ ?>
527
+ <a class="<?php echo esc_attr($classes); ?>" href="<?php echo esc_url($href); ?>">
528
+ <?php if ($icon) : ?>
529
+ <?php echo hs_get_icon_svg($icon, $size); // helper that returns inline SVG ?>
530
+ <?php endif; ?>
531
+ <span><?php echo esc_html($label); ?></span>
532
+ </a>
533
+ ```
534
+
535
+ ```js
536
+ // save.js — returns null for dynamic blocks
537
+ export default function Save() {
538
+ return null;
539
+ }
540
+ ```
541
+
542
+ **Why `render.php` over `save.js`:**
543
+ - Change a border-radius or token → every block updates instantly
544
+ - No "This block contains unexpected content" errors when evolving the design
545
+ - HTML stays in sync with the component library — single source of truth
546
+ - Full server-side control (conditional logic, user roles, A/B testing)
547
+
548
+ ### Frontend CSS — style the PHP output with tokens
549
+
550
+ ```php
551
+ // Enqueue the token CSS in your block or theme
552
+ wp_enqueue_style('hs-tokens', 'path/to/highsoft-design-tokens/dist/css/tokens.css');
553
+ ```
554
+
555
+ #### Button styles
556
+ ```css
557
+ /* style.css — frontend styles using the same tokens as the React components */
558
+ .hs-button {
559
+ display: inline-flex;
560
+ align-items: center;
561
+ justify-content: center;
562
+ box-sizing: border-box;
563
+ border: 1px solid transparent;
564
+ cursor: pointer;
565
+ white-space: nowrap;
566
+ text-decoration: none;
567
+ font-family: var(--typography-font-family);
568
+ transition: transform 0.2s cubic-bezier(0.075, 0.82, 0.165, 1);
569
+ }
570
+
571
+ /* Size 200 (default) */
572
+ .hs-button--200 {
573
+ height: 32px;
574
+ padding: 0 7px;
575
+ border-radius: 8px;
576
+ font-size: var(--typography-size-100);
577
+ font-weight: var(--typography-weight-medium);
578
+ gap: 8px;
579
+ }
580
+ .hs-button--200 svg { width: 16px; height: 16px; }
581
+
582
+ /* Size 300 */
583
+ .hs-button--300 {
584
+ height: 36px;
585
+ padding: 0 9px;
586
+ border-radius: 8px;
587
+ font-size: var(--typography-size-200);
588
+ font-weight: var(--typography-weight-medium);
589
+ gap: 10px;
590
+ }
591
+ .hs-button--300 svg { width: 16px; height: 16px; }
592
+
593
+ /* Variants — use button token CSS custom properties */
594
+ .hs-button--brand {
595
+ background: var(--button-colors-brand-background-default);
596
+ color: var(--button-colors-brand-text-default);
597
+ border-color: var(--button-colors-brand-stroke-outer-default);
598
+ }
599
+ .hs-button--brand:hover {
600
+ background: var(--button-colors-brand-background-hover);
601
+ }
602
+ .hs-button--neutral {
603
+ background: var(--button-colors-neutral-background-default);
604
+ color: var(--button-colors-neutral-text-default);
605
+ border-color: var(--button-colors-neutral-stroke-outer-default);
606
+ }
607
+ .hs-button--soft {
608
+ background: var(--button-colors-soft-background-default);
609
+ color: var(--button-colors-soft-text-default);
610
+ border-color: var(--button-colors-soft-stroke-outer-default);
611
+ }
612
+ ```
613
+
614
+ #### Label styles
615
+ ```css
616
+ .hs-label {
617
+ display: inline-flex;
618
+ align-items: center;
619
+ gap: 5px;
620
+ height: 18px;
621
+ padding: 0 6px;
622
+ border-radius: 4px;
623
+ font-size: 0.75rem;
624
+ font-weight: var(--typography-weight-medium);
625
+ text-transform: uppercase;
626
+ }
627
+ .hs-label svg { width: 12px; height: 12px; }
628
+ .hs-label--with-icon { height: 24px; padding: 0 9px; border-radius: 6px; }
629
+
630
+ /* Variants — use tag token CSS custom properties */
631
+ .hs-label--brand { background: var(--tags-brand-background); color: var(--tags-brand-text); }
632
+ .hs-label--success { background: var(--tags-success-background); color: var(--tags-success-text); }
633
+ .hs-label--attention { background: var(--tags-attention-background); color: var(--tags-attention-text); }
634
+ .hs-label--danger { background: var(--tags-danger-background); color: var(--tags-danger-text); }
635
+ .hs-label--neutral { background: var(--tags-neutral-background); color: var(--tags-neutral-text); }
636
+ ```
637
+
638
+ #### Link styles (SCSS mixin)
639
+ ```scss
640
+ @use 'highsoft-design-tokens/dist/scss/mixins/links' as *;
641
+
642
+ .hs-link { @include link; } // native — subtle underline
643
+ .hs-link--fancy { @include link(fancy); } // animated slide-in underline
644
+ ```
645
+
646
+ ### Token naming conventions for blocks
647
+
648
+ Use the `hs-` prefix and mirror the React component API:
649
+
650
+ | React prop | Block attribute | CSS class | Example |
651
+ |---|---|---|---|
652
+ | `variant="brand"` | `variant: "brand"` | `hs-{component}--brand` | `hs-button--brand` |
653
+ | `size={300}` | `size: 300` | `hs-{component}--300` | `hs-button--300` |
654
+ | `iconLeft={<X />}` | `iconLeft: "x"` | `hs-{component}--with-icon` | `hs-label--with-icon` |
655
+ | `fullWidth` | `fullWidth: true` | `hs-{component}--full` | `hs-button--full` |
656
+ | `border` | `border: true` | `hs-{component}--border` | `hs-label--border` |
657
+
658
+ ### Dark mode in WordPress
659
+
660
+ Add `data-theme="dark"` to the `<html>` element. All `--HS-*` tokens remap automatically — no additional CSS needed.
661
+
662
+ ```php
663
+ <html data-theme="<?php echo get_theme_mod('hs_dark_mode') ? 'dark' : 'light'; ?>">
664
+ ```
665
+
666
+ ### Common block patterns (frontend CSS)
667
+
668
+ #### Pricing card
669
+ ```css
670
+ .wp-block-hs-pricing {
671
+ background: var(--HS-surface-ui-background-0);
672
+ border: 1px solid var(--HS-surface-ui-border-100);
673
+ border-radius: 16px;
674
+ padding: 32px;
675
+ }
676
+ .wp-block-hs-pricing__title {
677
+ color: var(--HS-text-primary-default);
678
+ font-family: var(--typography-font-family);
679
+ font-size: var(--typography-size-300);
680
+ font-weight: var(--typography-weight-semi);
681
+ }
682
+ .wp-block-hs-pricing__description {
683
+ color: var(--HS-text-secondary-default);
684
+ font-size: var(--typography-size-200);
685
+ line-height: var(--typography-lineHeight-text);
686
+ }
687
+ ```
688
+
689
+ #### Feature grid item with hover
690
+ ```css
691
+ .wp-block-hs-feature {
692
+ background: var(--HS-surface-ui-background-0);
693
+ border: 1px solid var(--HS-surface-ui-border-100);
694
+ border-radius: 8px;
695
+ padding: 24px;
696
+ transition: border-color var(--animations-fade-fast),
697
+ box-shadow var(--animations-bounce-default);
698
+ }
699
+ .wp-block-hs-feature:hover {
700
+ border-color: var(--HS-surface-brand-border-200);
701
+ box-shadow: var(--shadow-size-100);
702
+ }
703
+ ```
704
+
705
+ ### Block development rules
706
+
707
+ 1. **Use the React components in `edit.js`** — import from `highsoft-ui` directly, don't rebuild components
708
+ 2. **Use `render.php` for frontend output** — dynamic blocks store only attributes, PHP renders the HTML. No `save.js` needed (return `null`)
709
+ 3. **Name block attributes after component props** — `variant`, `size`, `iconLeft`, `fullWidth` — so the editor↔frontend mapping is obvious
710
+ 4. **Always use token CSS custom properties** — never hardcode colors, shadows, radii, or animation values
711
+ 5. **Icons must be inline SVGs** on the frontend — copy the SVG source from `@untitled-ui/icons-react`. In the editor, use the React icon components directly
712
+ 6. **Use `box-sizing: border-box`** on all interactive elements to prevent height mismatches
713
+ 7. **Test in both light and dark mode** — if you only use `--HS-*` tokens, dark mode works automatically
714
+ 8. **Use the SCSS link mixin** for all anchor styling — don't write custom link hover/underline CSS
715
+
716
+ ## Headless CMS / static site guide
717
+
718
+ If the site uses a **headless CMS** (git-based, API-based, or any visual editor) with a React or Astro frontend, the components can be used **directly** — no CSS recreation needed. This is the preferred approach when developers control the frontend and a non-technical editor only needs to change content.
719
+
720
+ ### Architecture
721
+
722
+ ```
723
+ ┌─────────────────┐ ┌──────────────┐ ┌───────────────┐
724
+ │ CMS / Editor │────▶│ Content API │────▶│ React / Astro │
725
+ │ (visual editor │ │ or JSON/MD │ │ uses highsoft-ui│
726
+ │ for content) │ │ files │ │ components │
727
+ └─────────────────┘ └──────────────┘ └───────────────┘
728
+ ```
729
+
730
+ The CMS manages **content** (text, images, links). The frontend uses the **real components** from `highsoft-ui`. Design tokens, variants, sizes — all handled by the component library. One implementation, not two.
731
+
732
+ ### Content schema → component props
733
+
734
+ Define content fields that map directly to component props. Keep the CMS schema minimal — only expose what the editor needs to change:
735
+
736
+ ```ts
737
+ // Example: a CTA block schema
738
+ interface CTABlock {
739
+ // Editor-controlled content
740
+ title: string
741
+ description: string
742
+ buttonLabel: string
743
+ buttonHref: string
744
+ // Design props (can be editor-controlled or hardcoded per template)
745
+ buttonVariant?: 'brand' | 'neutral' | 'soft' // default: 'brand'
746
+ buttonSize?: 200 | 300 | 400 // default: 300
747
+ }
748
+ ```
749
+
750
+ ```tsx
751
+ // The component uses highsoft-ui directly — no CSS duplication
752
+ import { Button, Heading, Typography, Box } from 'highsoft-ui'
753
+
754
+ export function CTABlock({ title, description, buttonLabel, buttonHref, buttonVariant = 'brand', buttonSize = 300 }: CTABlock) {
755
+ return (
756
+ <Box padding={700} radius={16}>
757
+ <Heading level={3}>{title}</Heading>
758
+ <Typography size={200} style={{ color: 'var(--HS-text-secondary-default)' }}>
759
+ {description}
760
+ </Typography>
761
+ <Button variant={buttonVariant} size={buttonSize} href={buttonHref}>
762
+ {buttonLabel}
763
+ </Button>
764
+ </Box>
765
+ )
766
+ }
767
+ ```
768
+
769
+ ### Page templates with editable regions
770
+
771
+ For mostly-static pages, define templates where only the content is CMS-editable. The layout and design stay in code:
772
+
773
+ ```tsx
774
+ // A page template — layout is code, content comes from CMS
775
+ import { Button, Heading, Typography, Box, Label, CopyCode } from 'highsoft-ui'
776
+
777
+ interface PricingPageContent {
778
+ hero: { title: string; subtitle: string }
779
+ plans: Array<{
780
+ name: string
781
+ price: string
782
+ description: string
783
+ features: string[]
784
+ cta: string
785
+ href: string
786
+ recommended?: boolean
787
+ }>
788
+ }
789
+
790
+ export function PricingPage({ content }: { content: PricingPageContent }) {
791
+ return (
792
+ <>
793
+ <Heading level={1}>{content.hero.title}</Heading>
794
+ <Typography size={300} style={{ color: 'var(--HS-text-secondary-default)' }}>
795
+ {content.hero.subtitle}
796
+ </Typography>
797
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 24 }}>
798
+ {content.plans.map((plan) => (
799
+ <Box key={plan.name} padding={700} radius={16} shadow={plan.recommended ? 100 : undefined} hoverable>
800
+ {plan.recommended && <Label variant="brand" iconLeft={false}>Recommended</Label>}
801
+ <Heading level={3}>{plan.name}</Heading>
802
+ <Heading level={2}>{plan.price}</Heading>
803
+ <Typography size={200}>{plan.description}</Typography>
804
+ <Button variant={plan.recommended ? 'brand' : 'neutral'} size={300} href={plan.href} fullWidth>
805
+ {plan.cta}
806
+ </Button>
807
+ </Box>
808
+ ))}
809
+ </div>
810
+ </>
811
+ )
812
+ }
813
+ ```
814
+
815
+ ### What goes in the CMS vs. what stays in code
816
+
817
+ | In the CMS (editor can change) | In code (developer controls) |
818
+ |---|---|
819
+ | Page titles, headings, body text | Component selection and layout |
820
+ | Button labels and link URLs | Variant, size, design props |
821
+ | Image URLs and alt text | Spacing, grid structure |
822
+ | Feature lists, pricing data | Animation tokens, hover states |
823
+ | Meta descriptions, SEO fields | Dark mode, responsive breakpoints |
824
+
825
+ The editor should never need to pick a `variant` or `size` — those are design decisions that belong in the template. Only expose design props to the CMS when there's a genuine editorial need (e.g. "highlighted" toggle on a pricing card).
826
+
827
+ ### Why this approach works
828
+
829
+ - **One implementation** — the real React components, not a CSS recreation
830
+ - **Design updates propagate instantly** — change a token or component prop, rebuild, done
831
+ - **Editor gets a simple interface** — text fields and toggles, not CSS classes
832
+ - **Type safety** — content schema is TypeScript, caught at build time
833
+ - **Static output** — pages pre-render to HTML at build time, no runtime cost
834
+ - **Dark mode works automatically** — components use semantic tokens
835
+
836
+ ## File structure
837
+
838
+ ```
839
+ lib/
840
+ components/ — React components (Button, Heading, Typography, Link, Label, Box, CopyCode, etc.)
841
+ scss/ — SCSS variables and semantic token definitions
842
+ utils.ts — Shared hooks (useSuccessFlash, useClipboard, useTheme)
843
+ main.ts — Package entry point and exports
844
+ stories/ — Storybook documentation pages
845
+ ```