@dillingerstaffing/strand-ui 0.5.0 → 0.6.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/HTML_REFERENCE.md CHANGED
@@ -8,9 +8,20 @@
8
8
 
9
9
  ## Required CSS
10
10
 
11
+ **CDN (no install):**
12
+
13
+ ```html
14
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@dillingerstaffing/strand@0.5/css/tokens.css">
15
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@dillingerstaffing/strand@0.5/css/reset.css">
16
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@dillingerstaffing/strand@0.5/css/base.css">
17
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@dillingerstaffing/strand-ui@0.5/dist/css/strand-ui.css">
18
+ ```
19
+
20
+ **npm:**
21
+
11
22
  ```html
12
- <link rel="stylesheet" href="path/to/@dillingerstaffing/strand/css/tokens.css">
13
- <link rel="stylesheet" href="path/to/@dillingerstaffing/strand-ui/dist/css/strand-ui.css">
23
+ <link rel="stylesheet" href="node_modules/@dillingerstaffing/strand/css/tokens.css">
24
+ <link rel="stylesheet" href="node_modules/@dillingerstaffing/strand-ui/dist/css/strand-ui.css">
14
25
  ```
15
26
 
16
27
  > **Token roles:** Don't guess which token to use. See [DESIGN_LANGUAGE.md 3.8: Color Roles (L290-L311)](./DESIGN_LANGUAGE.md#L290) for which color in which context, and [Appendix B: Token Quick Reference (L1372-L1431)](./DESIGN_LANGUAGE.md#L1372) for the full lookup table.
@@ -608,6 +619,7 @@ All container components (Grid, Stack, Card, Container) enforce boundary integri
608
619
 
609
620
  ```html
610
621
  <div class="strand-toast strand-toast--info" role="status" aria-live="polite">
622
+ <span class="strand-toast__status">INFO</span>
611
623
  <span class="strand-toast__message">Changes saved successfully.</span>
612
624
  <button type="button" class="strand-toast__dismiss" aria-label="Dismiss">&times;</button>
613
625
  </div>
@@ -630,6 +642,7 @@ All container components (Grid, Stack, Card, Container) enforce boundary integri
630
642
 
631
643
  ```html
632
644
  <div class="strand-alert strand-alert--info" role="status">
645
+ <span class="strand-alert__status">INFO</span>
633
646
  <div class="strand-alert__content">This is an informational message.</div>
634
647
  </div>
635
648
  ```
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @dillingerstaffing/strand-ui
2
+
3
+ Preact/React component library built on the [Strand Design Language](https://github.com/dillingerstaffing/strand/blob/main/DESIGN_LANGUAGE.md). 32 components. Zero-runtime CSS. WCAG 2.2 AA.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @dillingerstaffing/strand @dillingerstaffing/strand-ui
9
+ ```
10
+
11
+ Import CSS in your app entry point:
12
+
13
+ ```css
14
+ @import '@dillingerstaffing/strand/css/reset.css';
15
+ @import '@dillingerstaffing/strand/css/tokens.css';
16
+ @import '@dillingerstaffing/strand/css/base.css';
17
+ @import '@dillingerstaffing/strand-ui/css/strand-ui.css';
18
+ ```
19
+
20
+ Use components:
21
+
22
+ ```jsx
23
+ import { Button, Card, Stack, Input } from '@dillingerstaffing/strand-ui';
24
+
25
+ function App() {
26
+ return (
27
+ <Card variant="elevated" padding="lg">
28
+ <Stack gap={4}>
29
+ <Input placeholder="Enter your email" />
30
+ <Button>Get Started</Button>
31
+ </Stack>
32
+ </Card>
33
+ );
34
+ }
35
+ ```
36
+
37
+ ## Also Available
38
+
39
+ - **Vue 3:** [@dillingerstaffing/strand-vue](https://www.npmjs.com/package/@dillingerstaffing/strand-vue)
40
+ - **Svelte:** [@dillingerstaffing/strand-svelte](https://www.npmjs.com/package/@dillingerstaffing/strand-svelte)
41
+ - **CSS Only:** Use classes directly per [HTML_REFERENCE.md](https://github.com/dillingerstaffing/strand/blob/main/HTML_REFERENCE.md)
42
+
43
+ ## Links
44
+
45
+ - [GitHub](https://github.com/dillingerstaffing/strand)
46
+ - [Design Language](https://github.com/dillingerstaffing/strand/blob/main/DESIGN_LANGUAGE.md)
47
+ - [Documentation](https://dillingerstaffing.com/labs/strand)
48
+
49
+ Created by [Dillinger Staffing](https://dillingerstaffing.com)
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.d.ts","sourceRoot":"","sources":["../../../src/components/Alert/Alert.tsx"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGrD,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1D,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IAClD,0BAA0B;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,eAAO,MAAM,KAAK;;EAuCjB,CAAC"}
1
+ {"version":3,"file":"Alert.d.ts","sourceRoot":"","sources":["../../../src/components/Alert/Alert.tsx"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGrD,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1D,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IAClD,0BAA0B;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,eAAO,MAAM,KAAK;;EA2CjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.tsx"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAKrD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,UAAU,iBAAiB;IACzB,KAAK,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;CACxC;AAID,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,aAAa;8BAAkC,kBAAkB;;CAqC7E,CAAC;AAoDF,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1D,oBAAoB;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,KAAK;;EAkCjB,CAAC"}
1
+ {"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/components/Toast/Toast.tsx"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAKrD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,UAAU,iBAAiB;IACzB,KAAK,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;CACxC;AAID,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C;AAID,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,aAAa;8BAAkC,kBAAkB;;CAqC7E,CAAC;AAwDF,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1D,oBAAoB;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,KAAK;;EAsCjB,CAAC"}
@@ -11,34 +11,45 @@
11
11
  justify-content: space-between;
12
12
  width: 100%;
13
13
  padding: var(--strand-space-6);
14
- padding-left: var(--strand-space-5);
15
14
  border-radius: var(--strand-radius-md);
16
- border-left: 4px solid transparent;
17
15
  font-family: var(--strand-font-sans);
18
16
  font-size: var(--strand-text-sm);
17
+ background: var(--strand-surface-recessed);
19
18
  }
20
19
 
21
- /* ── Status variants ── */
20
+ /* ── Status variants (neutral background, status color on prefix only) ── */
22
21
  .strand-alert--info {
23
- background: var(--strand-blue-glow);
24
- border-left-color: var(--strand-blue-primary);
22
+ background: var(--strand-surface-recessed);
25
23
  }
26
24
 
27
25
  .strand-alert--success {
28
- background: rgba(34, 197, 94, 0.08);
29
- border-left-color: var(--strand-green-positive);
26
+ background: var(--strand-surface-recessed);
30
27
  }
31
28
 
32
29
  .strand-alert--warning {
33
- background: rgba(245, 158, 11, 0.08);
34
- border-left-color: var(--strand-amber-caution);
30
+ background: var(--strand-surface-recessed);
35
31
  }
36
32
 
37
33
  .strand-alert--error {
38
- background: rgba(239, 68, 68, 0.08);
39
- border-left-color: var(--strand-red-alert);
34
+ background: var(--strand-surface-recessed);
35
+ }
36
+
37
+ /* ── Status prefix ── */
38
+ .strand-alert__status {
39
+ font-family: var(--strand-font-mono);
40
+ font-size: var(--strand-text-xs);
41
+ font-weight: var(--strand-weight-semibold);
42
+ letter-spacing: var(--strand-tracking-wider);
43
+ text-transform: uppercase;
44
+ margin-right: var(--strand-space-3);
45
+ flex-shrink: 0;
40
46
  }
41
47
 
48
+ .strand-alert--info .strand-alert__status { color: var(--strand-blue-primary); }
49
+ .strand-alert--success .strand-alert__status { color: var(--strand-teal-vital); }
50
+ .strand-alert--warning .strand-alert__status { color: var(--strand-amber-caution); }
51
+ .strand-alert--error .strand-alert__status { color: var(--strand-red-alert); }
52
+
42
53
  /* ── Content ── */
43
54
  .strand-alert__content {
44
55
  flex: 1;
@@ -51,8 +62,8 @@
51
62
  display: inline-flex;
52
63
  align-items: center;
53
64
  justify-content: center;
54
- width: 24px;
55
- height: 24px;
65
+ width: var(--strand-space-6);
66
+ height: var(--strand-space-6);
56
67
  margin-left: var(--strand-space-4);
57
68
  padding: 0;
58
69
  border: none;
@@ -660,7 +671,7 @@
660
671
  line-height: var(--strand-leading-relaxed);
661
672
  color: var(--strand-blue-midnight);
662
673
  background: var(--strand-surface-recessed);
663
- box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06);
674
+ box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06); /* strand-blue-abyss at 6% opacity */
664
675
  border-radius: var(--strand-radius-lg);
665
676
  padding: var(--strand-space-3) var(--strand-space-4);
666
677
  overflow-x: auto;
@@ -893,10 +904,12 @@
893
904
 
894
905
  /* ── Label ── */
895
906
  .strand-form-field__label {
896
- font-family: var(--strand-font-sans);
897
- font-size: var(--strand-text-sm);
907
+ font-family: var(--strand-font-mono);
908
+ font-size: var(--strand-text-xs);
898
909
  font-weight: var(--strand-weight-medium);
899
- color: var(--strand-gray-700);
910
+ letter-spacing: var(--strand-tracking-widest);
911
+ text-transform: uppercase;
912
+ color: var(--strand-gray-500);
900
913
  line-height: var(--strand-leading-snug);
901
914
  }
902
915
 
@@ -2065,7 +2078,7 @@
2065
2078
  .strand-tabs [role="tablist"] {
2066
2079
  display: flex;
2067
2080
  gap: var(--strand-space-1);
2068
- border-bottom: 1px solid var(--strand-gray-400);
2081
+ border-bottom: 1px solid var(--strand-gray-200);
2069
2082
  }
2070
2083
 
2071
2084
  /* ── Tab button ── */
@@ -2137,7 +2150,7 @@
2137
2150
  }
2138
2151
 
2139
2152
  .strand-tag--solid.strand-tag--teal {
2140
- background: rgba(20, 184, 166, 0.1);
2153
+ background: var(--strand-surface-recessed);
2141
2154
  color: var(--strand-on-teal-tint);
2142
2155
  }
2143
2156
 
@@ -2147,12 +2160,12 @@
2147
2160
  }
2148
2161
 
2149
2162
  .strand-tag--solid.strand-tag--amber {
2150
- background: rgba(245, 158, 11, 0.1);
2163
+ background: var(--strand-surface-recessed);
2151
2164
  color: var(--strand-on-amber-tint);
2152
2165
  }
2153
2166
 
2154
2167
  .strand-tag--solid.strand-tag--red {
2155
- background: rgba(239, 68, 68, 0.1);
2168
+ background: var(--strand-surface-recessed);
2156
2169
  color: var(--strand-on-red-tint);
2157
2170
  }
2158
2171
 
@@ -2321,7 +2334,6 @@
2321
2334
  padding: var(--strand-space-4) var(--strand-space-5);
2322
2335
  background: var(--strand-surface-elevated);
2323
2336
  border-radius: var(--strand-radius-lg);
2324
- border-left: 4px solid transparent;
2325
2337
  box-shadow: var(--strand-elevation-3);
2326
2338
  font-family: var(--strand-font-sans);
2327
2339
  font-size: var(--strand-text-sm);
@@ -2329,22 +2341,21 @@
2329
2341
  animation: strand-toast-in var(--strand-duration-normal) var(--strand-ease-out-expo);
2330
2342
  }
2331
2343
 
2332
- /* ── Status accents ── */
2333
- .strand-toast--info {
2334
- border-left-color: var(--strand-blue-primary);
2335
- }
2336
-
2337
- .strand-toast--success {
2338
- border-left-color: var(--strand-green-positive);
2339
- }
2340
-
2341
- .strand-toast--warning {
2342
- border-left-color: var(--strand-amber-caution);
2344
+ /* ── Status prefix ── */
2345
+ .strand-toast__status {
2346
+ font-family: var(--strand-font-mono);
2347
+ font-size: var(--strand-text-xs);
2348
+ font-weight: var(--strand-weight-semibold);
2349
+ letter-spacing: var(--strand-tracking-wider);
2350
+ text-transform: uppercase;
2351
+ margin-right: var(--strand-space-3);
2352
+ flex-shrink: 0;
2343
2353
  }
2344
2354
 
2345
- .strand-toast--error {
2346
- border-left-color: var(--strand-red-alert);
2347
- }
2355
+ .strand-toast--info .strand-toast__status { color: var(--strand-blue-primary); }
2356
+ .strand-toast--success .strand-toast__status { color: var(--strand-teal-vital); }
2357
+ .strand-toast--warning .strand-toast__status { color: var(--strand-amber-caution); }
2358
+ .strand-toast--error .strand-toast__status { color: var(--strand-red-alert); }
2348
2359
 
2349
2360
  /* ── Message ── */
2350
2361
  .strand-toast__message {
@@ -2359,8 +2370,8 @@
2359
2370
  display: inline-flex;
2360
2371
  align-items: center;
2361
2372
  justify-content: center;
2362
- width: 24px;
2363
- height: 24px;
2373
+ width: var(--strand-space-6);
2374
+ height: var(--strand-space-6);
2364
2375
  margin-left: var(--strand-space-3);
2365
2376
  padding: 0;
2366
2377
  border: none;
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { jsxs as _, jsx as s } from "preact/jsx-runtime";
2
2
  import { forwardRef as p } from "preact/compat";
3
- import { useRef as k, useCallback as v, useEffect as x, useState as B, useContext as R } from "preact/hooks";
4
- import { createContext as I } from "preact";
5
- const E = p(
3
+ import { useRef as k, useCallback as v, useEffect as C, useState as B, useContext as E } from "preact/hooks";
4
+ import { createContext as R } from "preact";
5
+ const I = p(
6
6
  ({
7
7
  variant: a = "primary",
8
8
  size: t = "md",
@@ -51,8 +51,8 @@ const E = p(
51
51
  );
52
52
  }
53
53
  );
54
- E.displayName = "Button";
55
- const K = p(
54
+ I.displayName = "Button";
55
+ const L = p(
56
56
  ({
57
57
  type: a = "text",
58
58
  error: t = !1,
@@ -87,8 +87,8 @@ const K = p(
87
87
  ] });
88
88
  }
89
89
  );
90
- K.displayName = "Input";
91
- const A = p(
90
+ L.displayName = "Input";
91
+ const K = p(
92
92
  ({
93
93
  autoResize: a = !1,
94
94
  showCount: t = !1,
@@ -139,8 +139,8 @@ const A = p(
139
139
  ] });
140
140
  }
141
141
  );
142
- A.displayName = "Textarea";
143
- const L = p(
142
+ K.displayName = "Textarea";
143
+ const M = p(
144
144
  ({
145
145
  options: a,
146
146
  value: t,
@@ -178,8 +178,8 @@ const L = p(
178
178
  ] });
179
179
  }
180
180
  );
181
- L.displayName = "Select";
182
- const M = p(
181
+ M.displayName = "Select";
182
+ const A = p(
183
183
  ({
184
184
  checked: a = !1,
185
185
  indeterminate: t = !1,
@@ -190,7 +190,7 @@ const M = p(
190
190
  ...r
191
191
  }, i) => {
192
192
  const c = k(null);
193
- x(() => {
193
+ C(() => {
194
194
  const f = typeof i == "function" ? c.current : (i == null ? void 0 : i.current) ?? c.current;
195
195
  f && (f.indeterminate = t);
196
196
  }, [t, i]);
@@ -272,7 +272,7 @@ const M = p(
272
272
  );
273
273
  }
274
274
  );
275
- M.displayName = "Checkbox";
275
+ A.displayName = "Checkbox";
276
276
  const P = p(
277
277
  ({
278
278
  checked: a = !1,
@@ -351,7 +351,7 @@ const F = p(
351
351
  }
352
352
  );
353
353
  F.displayName = "Switch";
354
- const q = p(
354
+ const U = p(
355
355
  ({
356
356
  min: a = 0,
357
357
  max: t = 100,
@@ -387,8 +387,8 @@ const q = p(
387
387
  ) });
388
388
  }
389
389
  );
390
- q.displayName = "Slider";
391
- const G = p(
390
+ U.displayName = "Slider";
391
+ const q = p(
392
392
  ({
393
393
  label: a,
394
394
  htmlFor: t,
@@ -421,8 +421,8 @@ const G = p(
421
421
  ] });
422
422
  }
423
423
  );
424
- G.displayName = "FormField";
425
- const H = p(
424
+ q.displayName = "FormField";
425
+ const O = p(
426
426
  ({
427
427
  variant: a = "elevated",
428
428
  padding: t = "md",
@@ -439,8 +439,8 @@ const H = p(
439
439
  return /* @__PURE__ */ s("div", { ref: o, className: r, ...l, children: n });
440
440
  }
441
441
  );
442
- H.displayName = "Card";
443
- const U = p(
442
+ O.displayName = "Card";
443
+ const G = p(
444
444
  ({
445
445
  variant: a = "count",
446
446
  status: t = "default",
@@ -470,8 +470,8 @@ const U = p(
470
470
  ] });
471
471
  }
472
472
  );
473
- U.displayName = "Badge";
474
- const V = p(
473
+ G.displayName = "Badge";
474
+ const H = p(
475
475
  ({
476
476
  src: a,
477
477
  alt: t = "",
@@ -498,8 +498,8 @@ const V = p(
498
498
  ) : /* @__PURE__ */ s("span", { className: "strand-avatar__initials", "aria-hidden": "true", children: h }) });
499
499
  }
500
500
  );
501
- V.displayName = "Avatar";
502
- const O = p(
501
+ H.displayName = "Avatar";
502
+ const V = p(
503
503
  ({
504
504
  variant: a = "solid",
505
505
  status: t = "default",
@@ -548,7 +548,7 @@ const O = p(
548
548
  ] });
549
549
  }
550
550
  );
551
- O.displayName = "Tag";
551
+ V.displayName = "Tag";
552
552
  const z = p(
553
553
  ({ columns: a, data: t, onSort: e, className: n = "", ...l }, o) => {
554
554
  const [r, i] = B(null), [c, u] = B("asc"), d = v(
@@ -950,9 +950,9 @@ const na = p(
950
950
  }
951
951
  );
952
952
  na.displayName = "Nav";
953
- const S = I(null);
953
+ const S = R(null);
954
954
  function ga() {
955
- const a = R(S);
955
+ const a = E(S);
956
956
  if (!a)
957
957
  throw new Error("useToast must be used within a ToastProvider");
958
958
  return a;
@@ -985,10 +985,10 @@ const la = ({ children: a, className: t = "" }) => {
985
985
  la.displayName = "ToastProvider";
986
986
  function ia({ entry: a, onDismiss: t }) {
987
987
  const e = k(null);
988
- x(() => (a.duration > 0 && (e.current = setTimeout(t, a.duration)), () => {
988
+ C(() => (a.duration > 0 && (e.current = setTimeout(t, a.duration)), () => {
989
989
  e.current !== null && clearTimeout(e.current);
990
990
  }), [a.duration, t]);
991
- const n = a.status === "error" || a.status === "warning", l = ["strand-toast", `strand-toast--${a.status}`].filter(Boolean).join(" ");
991
+ const n = a.status === "error" || a.status === "warning", l = ["strand-toast", `strand-toast--${a.status}`].filter(Boolean).join(" "), o = a.status === "success" ? "COMPLETE" : a.status.toUpperCase();
992
992
  return /* @__PURE__ */ _(
993
993
  "div",
994
994
  {
@@ -996,6 +996,7 @@ function ia({ entry: a, onDismiss: t }) {
996
996
  role: "status",
997
997
  "aria-live": n ? "assertive" : "polite",
998
998
  children: [
999
+ /* @__PURE__ */ s("span", { className: "strand-toast__status", children: o }),
999
1000
  /* @__PURE__ */ s("span", { className: "strand-toast__message", children: a.message }),
1000
1001
  /* @__PURE__ */ s(
1001
1002
  "button",
@@ -1017,7 +1018,7 @@ const oa = p(
1017
1018
  "strand-toast",
1018
1019
  `strand-toast--${a}`,
1019
1020
  n
1020
- ].filter(Boolean).join(" ");
1021
+ ].filter(Boolean).join(" "), c = a === "success" ? "COMPLETE" : a.toUpperCase();
1021
1022
  return /* @__PURE__ */ _(
1022
1023
  "div",
1023
1024
  {
@@ -1027,6 +1028,7 @@ const oa = p(
1027
1028
  "aria-live": r ? "assertive" : "polite",
1028
1029
  ...l,
1029
1030
  children: [
1031
+ /* @__PURE__ */ s("span", { className: "strand-toast__status", children: c }),
1030
1032
  /* @__PURE__ */ s("span", { className: "strand-toast__message", children: t }),
1031
1033
  e && /* @__PURE__ */ s(
1032
1034
  "button",
@@ -1057,8 +1059,9 @@ const da = p(
1057
1059
  "strand-alert",
1058
1060
  `strand-alert--${a}`,
1059
1061
  n
1060
- ].filter(Boolean).join(" ");
1062
+ ].filter(Boolean).join(" "), u = a === "success" ? "COMPLETE" : a.toUpperCase();
1061
1063
  return /* @__PURE__ */ _("div", { ref: r, className: c, role: i, ...o, children: [
1064
+ /* @__PURE__ */ s("span", { className: "strand-alert__status", children: u }),
1062
1065
  /* @__PURE__ */ s("div", { className: "strand-alert__content", children: l }),
1063
1066
  t && /* @__PURE__ */ s(
1064
1067
  "button",
@@ -1088,7 +1091,7 @@ const ua = p(
1088
1091
  ...i
1089
1092
  }, c) => {
1090
1093
  const u = k(null), d = k(null), m = k(`strand-dialog-title-${++ca}`).current;
1091
- x(() => {
1094
+ C(() => {
1092
1095
  if (!a) return;
1093
1096
  d.current = document.activeElement;
1094
1097
  const b = requestAnimationFrame(() => {
@@ -1102,7 +1105,7 @@ const ua = p(
1102
1105
  const y = d.current;
1103
1106
  y && y instanceof HTMLElement && y.focus();
1104
1107
  };
1105
- }, [a]), x(() => {
1108
+ }, [a]), C(() => {
1106
1109
  if (!a) return;
1107
1110
  const b = document.body.style.overflow;
1108
1111
  return document.body.style.overflow = "hidden", () => {
@@ -1224,7 +1227,7 @@ const ha = p(
1224
1227
  }
1225
1228
  );
1226
1229
  ha.displayName = "Tooltip";
1227
- const D = { sm: 24, md: 40, lg: 56 }, C = 3, pa = p(
1230
+ const D = { sm: 24, md: 40, lg: 56 }, x = 3, pa = p(
1228
1231
  ({
1229
1232
  variant: a = "bar",
1230
1233
  value: t,
@@ -1244,7 +1247,7 @@ const D = { sm: 24, md: 40, lg: 56 }, C = 3, pa = p(
1244
1247
  "aria-valuemax": 100
1245
1248
  };
1246
1249
  if (r && (c["aria-valuenow"] = t), a === "ring") {
1247
- const u = D[e] ?? D.md, d = (u - C) / 2, h = 2 * Math.PI * d, m = r ? h - h * t / 100 : 0;
1250
+ const u = D[e] ?? D.md, d = (u - x) / 2, h = 2 * Math.PI * d, m = r ? h - h * t / 100 : 0;
1248
1251
  return /* @__PURE__ */ s("div", { ref: o, className: i, ...c, ...l, children: /* @__PURE__ */ _(
1249
1252
  "svg",
1250
1253
  {
@@ -1260,7 +1263,7 @@ const D = { sm: 24, md: 40, lg: 56 }, C = 3, pa = p(
1260
1263
  cy: u / 2,
1261
1264
  r: d,
1262
1265
  fill: "none",
1263
- "stroke-width": C,
1266
+ "stroke-width": x,
1264
1267
  className: "strand-progress__track"
1265
1268
  }
1266
1269
  ),
@@ -1271,7 +1274,7 @@ const D = { sm: 24, md: 40, lg: 56 }, C = 3, pa = p(
1271
1274
  cy: u / 2,
1272
1275
  r: d,
1273
1276
  fill: "none",
1274
- "stroke-width": C,
1277
+ "stroke-width": x,
1275
1278
  "stroke-dasharray": h,
1276
1279
  "stroke-dashoffset": r ? m : void 0,
1277
1280
  "stroke-linecap": "round",
@@ -1343,35 +1346,35 @@ const _a = p(
1343
1346
  _a.displayName = "Skeleton";
1344
1347
  export {
1345
1348
  da as Alert,
1346
- V as Avatar,
1347
- U as Badge,
1349
+ H as Avatar,
1350
+ G as Badge,
1348
1351
  ta as Breadcrumb,
1349
- E as Button,
1350
- H as Card,
1351
- M as Checkbox,
1352
+ I as Button,
1353
+ O as Card,
1354
+ A as Checkbox,
1352
1355
  Z as CodeBlock,
1353
1356
  X as Container,
1354
1357
  W as DataReadout,
1355
1358
  ua as Dialog,
1356
1359
  Y as Divider,
1357
- G as FormField,
1360
+ q as FormField,
1358
1361
  Q as Grid,
1359
- K as Input,
1362
+ L as Input,
1360
1363
  ea as Link,
1361
1364
  na as Nav,
1362
1365
  pa as Progress,
1363
1366
  P as Radio,
1364
1367
  aa as Section,
1365
- L as Select,
1368
+ M as Select,
1366
1369
  _a as Skeleton,
1367
- q as Slider,
1370
+ U as Slider,
1368
1371
  fa as Spinner,
1369
1372
  J as Stack,
1370
1373
  F as Switch,
1371
1374
  z as Table,
1372
1375
  sa as Tabs,
1373
- O as Tag,
1374
- A as Textarea,
1376
+ V as Tag,
1377
+ K as Textarea,
1375
1378
  oa as Toast,
1376
1379
  la as ToastProvider,
1377
1380
  ha as Tooltip,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dillingerstaffing/strand-ui",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Strand UI - Preact/React component library built on the Strand Design Language",
5
5
  "author": "Dillinger Staffing <engineering@dillingerstaffing.com> (https://dillingerstaffing.com)",
6
6
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  }
61
61
  },
62
62
  "dependencies": {
63
- "@dillingerstaffing/strand": "^0.5.0"
63
+ "@dillingerstaffing/strand": "^0.6.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@testing-library/preact": "^3.2.0",
@@ -8,34 +8,45 @@
8
8
  justify-content: space-between;
9
9
  width: 100%;
10
10
  padding: var(--strand-space-6);
11
- padding-left: var(--strand-space-5);
12
11
  border-radius: var(--strand-radius-md);
13
- border-left: 4px solid transparent;
14
12
  font-family: var(--strand-font-sans);
15
13
  font-size: var(--strand-text-sm);
14
+ background: var(--strand-surface-recessed);
16
15
  }
17
16
 
18
- /* ── Status variants ── */
17
+ /* ── Status variants (neutral background, status color on prefix only) ── */
19
18
  .strand-alert--info {
20
- background: var(--strand-blue-glow);
21
- border-left-color: var(--strand-blue-primary);
19
+ background: var(--strand-surface-recessed);
22
20
  }
23
21
 
24
22
  .strand-alert--success {
25
- background: rgba(34, 197, 94, 0.08);
26
- border-left-color: var(--strand-green-positive);
23
+ background: var(--strand-surface-recessed);
27
24
  }
28
25
 
29
26
  .strand-alert--warning {
30
- background: rgba(245, 158, 11, 0.08);
31
- border-left-color: var(--strand-amber-caution);
27
+ background: var(--strand-surface-recessed);
32
28
  }
33
29
 
34
30
  .strand-alert--error {
35
- background: rgba(239, 68, 68, 0.08);
36
- border-left-color: var(--strand-red-alert);
31
+ background: var(--strand-surface-recessed);
37
32
  }
38
33
 
34
+ /* ── Status prefix ── */
35
+ .strand-alert__status {
36
+ font-family: var(--strand-font-mono);
37
+ font-size: var(--strand-text-xs);
38
+ font-weight: var(--strand-weight-semibold);
39
+ letter-spacing: var(--strand-tracking-wider);
40
+ text-transform: uppercase;
41
+ margin-right: var(--strand-space-3);
42
+ flex-shrink: 0;
43
+ }
44
+
45
+ .strand-alert--info .strand-alert__status { color: var(--strand-blue-primary); }
46
+ .strand-alert--success .strand-alert__status { color: var(--strand-teal-vital); }
47
+ .strand-alert--warning .strand-alert__status { color: var(--strand-amber-caution); }
48
+ .strand-alert--error .strand-alert__status { color: var(--strand-red-alert); }
49
+
39
50
  /* ── Content ── */
40
51
  .strand-alert__content {
41
52
  flex: 1;
@@ -48,8 +59,8 @@
48
59
  display: inline-flex;
49
60
  align-items: center;
50
61
  justify-content: center;
51
- width: 24px;
52
- height: 24px;
62
+ width: var(--strand-space-6);
63
+ height: var(--strand-space-6);
53
64
  margin-left: var(--strand-space-4);
54
65
  padding: 0;
55
66
  border: none;
@@ -79,6 +79,36 @@ describe("Alert", () => {
79
79
  expect(queryByLabelText("Dismiss")).toBeNull();
80
80
  });
81
81
 
82
+ // ── Status prefix ──
83
+
84
+ it("renders status prefix for info", () => {
85
+ const { container } = render(<Alert status="info">Info</Alert>);
86
+ const status = container.querySelector(".strand-alert__status");
87
+ expect(status).toBeTruthy();
88
+ expect(status!.textContent).toBe("INFO");
89
+ });
90
+
91
+ it("renders status prefix for success as COMPLETE", () => {
92
+ const { container } = render(<Alert status="success">OK</Alert>);
93
+ const status = container.querySelector(".strand-alert__status");
94
+ expect(status).toBeTruthy();
95
+ expect(status!.textContent).toBe("COMPLETE");
96
+ });
97
+
98
+ it("renders status prefix for warning", () => {
99
+ const { container } = render(<Alert status="warning">Warn</Alert>);
100
+ const status = container.querySelector(".strand-alert__status");
101
+ expect(status).toBeTruthy();
102
+ expect(status!.textContent).toBe("WARNING");
103
+ });
104
+
105
+ it("renders status prefix for error", () => {
106
+ const { container } = render(<Alert status="error">Fail</Alert>);
107
+ const status = container.querySelector(".strand-alert__status");
108
+ expect(status).toBeTruthy();
109
+ expect(status!.textContent).toBe("ERROR");
110
+ });
111
+
82
112
  // ── Custom className ──
83
113
 
84
114
  it("merges custom className", () => {
@@ -38,8 +38,12 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
38
38
  .filter(Boolean)
39
39
  .join(" ");
40
40
 
41
+ const statusLabel =
42
+ status === "success" ? "COMPLETE" : status.toUpperCase();
43
+
41
44
  return (
42
45
  <div ref={ref} className={classes} role={role} {...rest}>
46
+ <span className="strand-alert__status">{statusLabel}</span>
43
47
  <div className="strand-alert__content">{children}</div>
44
48
  {dismissible && (
45
49
  <button
@@ -24,7 +24,7 @@
24
24
  line-height: var(--strand-leading-relaxed);
25
25
  color: var(--strand-blue-midnight);
26
26
  background: var(--strand-surface-recessed);
27
- box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06);
27
+ box-shadow: inset 0 1px 3px rgba(15, 23, 42, 0.06); /* strand-blue-abyss at 6% opacity */
28
28
  border-radius: var(--strand-radius-lg);
29
29
  padding: var(--strand-space-3) var(--strand-space-4);
30
30
  overflow-x: auto;
@@ -9,10 +9,12 @@
9
9
 
10
10
  /* ── Label ── */
11
11
  .strand-form-field__label {
12
- font-family: var(--strand-font-sans);
13
- font-size: var(--strand-text-sm);
12
+ font-family: var(--strand-font-mono);
13
+ font-size: var(--strand-text-xs);
14
14
  font-weight: var(--strand-weight-medium);
15
- color: var(--strand-gray-700);
15
+ letter-spacing: var(--strand-tracking-widest);
16
+ text-transform: uppercase;
17
+ color: var(--strand-gray-500);
16
18
  line-height: var(--strand-leading-snug);
17
19
  }
18
20
 
@@ -4,7 +4,7 @@
4
4
  .strand-tabs [role="tablist"] {
5
5
  display: flex;
6
6
  gap: var(--strand-space-1);
7
- border-bottom: 1px solid var(--strand-gray-400);
7
+ border-bottom: 1px solid var(--strand-gray-200);
8
8
  }
9
9
 
10
10
  /* ── Tab button ── */
@@ -22,7 +22,7 @@
22
22
  }
23
23
 
24
24
  .strand-tag--solid.strand-tag--teal {
25
- background: rgba(20, 184, 166, 0.1);
25
+ background: var(--strand-surface-recessed);
26
26
  color: var(--strand-on-teal-tint);
27
27
  }
28
28
 
@@ -32,12 +32,12 @@
32
32
  }
33
33
 
34
34
  .strand-tag--solid.strand-tag--amber {
35
- background: rgba(245, 158, 11, 0.1);
35
+ background: var(--strand-surface-recessed);
36
36
  color: var(--strand-on-amber-tint);
37
37
  }
38
38
 
39
39
  .strand-tag--solid.strand-tag--red {
40
- background: rgba(239, 68, 68, 0.1);
40
+ background: var(--strand-surface-recessed);
41
41
  color: var(--strand-on-red-tint);
42
42
  }
43
43
 
@@ -22,7 +22,6 @@
22
22
  padding: var(--strand-space-4) var(--strand-space-5);
23
23
  background: var(--strand-surface-elevated);
24
24
  border-radius: var(--strand-radius-lg);
25
- border-left: 4px solid transparent;
26
25
  box-shadow: var(--strand-elevation-3);
27
26
  font-family: var(--strand-font-sans);
28
27
  font-size: var(--strand-text-sm);
@@ -30,22 +29,21 @@
30
29
  animation: strand-toast-in var(--strand-duration-normal) var(--strand-ease-out-expo);
31
30
  }
32
31
 
33
- /* ── Status accents ── */
34
- .strand-toast--info {
35
- border-left-color: var(--strand-blue-primary);
36
- }
37
-
38
- .strand-toast--success {
39
- border-left-color: var(--strand-green-positive);
40
- }
41
-
42
- .strand-toast--warning {
43
- border-left-color: var(--strand-amber-caution);
32
+ /* ── Status prefix ── */
33
+ .strand-toast__status {
34
+ font-family: var(--strand-font-mono);
35
+ font-size: var(--strand-text-xs);
36
+ font-weight: var(--strand-weight-semibold);
37
+ letter-spacing: var(--strand-tracking-wider);
38
+ text-transform: uppercase;
39
+ margin-right: var(--strand-space-3);
40
+ flex-shrink: 0;
44
41
  }
45
42
 
46
- .strand-toast--error {
47
- border-left-color: var(--strand-red-alert);
48
- }
43
+ .strand-toast--info .strand-toast__status { color: var(--strand-blue-primary); }
44
+ .strand-toast--success .strand-toast__status { color: var(--strand-teal-vital); }
45
+ .strand-toast--warning .strand-toast__status { color: var(--strand-amber-caution); }
46
+ .strand-toast--error .strand-toast__status { color: var(--strand-red-alert); }
49
47
 
50
48
  /* ── Message ── */
51
49
  .strand-toast__message {
@@ -60,8 +58,8 @@
60
58
  display: inline-flex;
61
59
  align-items: center;
62
60
  justify-content: center;
63
- width: 24px;
64
- height: 24px;
61
+ width: var(--strand-space-6);
62
+ height: var(--strand-space-6);
65
63
  margin-left: var(--strand-space-3);
66
64
  padding: 0;
67
65
  border: none;
@@ -87,6 +87,34 @@ describe("Toast", () => {
87
87
  container.querySelector(".strand-toast--info"),
88
88
  ).toBeTruthy();
89
89
  });
90
+
91
+ it("renders status prefix for info", () => {
92
+ const { container } = render(<Toast message="Note" status="info" />);
93
+ const status = container.querySelector(".strand-toast__status");
94
+ expect(status).toBeTruthy();
95
+ expect(status!.textContent).toBe("INFO");
96
+ });
97
+
98
+ it("renders status prefix for success as COMPLETE", () => {
99
+ const { container } = render(<Toast message="OK" status="success" />);
100
+ const status = container.querySelector(".strand-toast__status");
101
+ expect(status).toBeTruthy();
102
+ expect(status!.textContent).toBe("COMPLETE");
103
+ });
104
+
105
+ it("renders status prefix for warning", () => {
106
+ const { container } = render(<Toast message="Warn" status="warning" />);
107
+ const status = container.querySelector(".strand-toast__status");
108
+ expect(status).toBeTruthy();
109
+ expect(status!.textContent).toBe("WARNING");
110
+ });
111
+
112
+ it("renders status prefix for error", () => {
113
+ const { container } = render(<Toast message="Fail" status="error" />);
114
+ const status = container.querySelector(".strand-toast__status");
115
+ expect(status).toBeTruthy();
116
+ expect(status!.textContent).toBe("ERROR");
117
+ });
90
118
  });
91
119
 
92
120
  describe("ToastProvider + useToast", () => {
@@ -107,12 +107,16 @@ function ToastItem({ entry, onDismiss }: ToastItemProps) {
107
107
  .filter(Boolean)
108
108
  .join(" ");
109
109
 
110
+ const statusLabel =
111
+ entry.status === "success" ? "COMPLETE" : entry.status.toUpperCase();
112
+
110
113
  return (
111
114
  <div
112
115
  className={classes}
113
116
  role="status"
114
117
  aria-live={isUrgent ? "assertive" : "polite"}
115
118
  >
119
+ <span className="strand-toast__status">{statusLabel}</span>
116
120
  <span className="strand-toast__message">{entry.message}</span>
117
121
  <button
118
122
  type="button"
@@ -150,6 +154,9 @@ export const Toast = forwardRef<HTMLDivElement, ToastProps>(
150
154
  .filter(Boolean)
151
155
  .join(" ");
152
156
 
157
+ const statusLabel =
158
+ status === "success" ? "COMPLETE" : status.toUpperCase();
159
+
153
160
  return (
154
161
  <div
155
162
  ref={ref}
@@ -158,6 +165,7 @@ export const Toast = forwardRef<HTMLDivElement, ToastProps>(
158
165
  aria-live={isUrgent ? "assertive" : "polite"}
159
166
  {...rest}
160
167
  >
168
+ <span className="strand-toast__status">{statusLabel}</span>
161
169
  <span className="strand-toast__message">{message}</span>
162
170
  {onDismiss && (
163
171
  <button