@_linked/primitives 1.2.4 → 1.3.1

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 (33) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/lib/cjs/components/Badge.d.ts +9 -2
  3. package/lib/cjs/components/Badge.js.map +1 -1
  4. package/lib/cjs/components/Badge.module.css +2 -0
  5. package/lib/cjs/components/Heading.d.ts +16 -2
  6. package/lib/cjs/components/Heading.js +2 -2
  7. package/lib/cjs/components/Heading.js.map +1 -1
  8. package/lib/cjs/components/Heading.module.css +70 -72
  9. package/lib/cjs/components/Text.d.ts +3 -1
  10. package/lib/cjs/components/Text.js.map +1 -1
  11. package/lib/cjs/components/Typography.d.ts +7 -1
  12. package/lib/cjs/components/Typography.js.map +1 -1
  13. package/lib/cjs/components/elements.d.ts +42 -0
  14. package/lib/cjs/components/elements.js +27 -0
  15. package/lib/cjs/components/elements.js.map +1 -0
  16. package/lib/cjs/package.json +1 -1
  17. package/lib/esm/components/Badge.d.ts +9 -2
  18. package/lib/esm/components/Badge.js.map +1 -1
  19. package/lib/esm/components/Badge.module.css +2 -0
  20. package/lib/esm/components/Heading.d.ts +16 -2
  21. package/lib/esm/components/Heading.js +2 -2
  22. package/lib/esm/components/Heading.js.map +1 -1
  23. package/lib/esm/components/Heading.module.css +70 -72
  24. package/lib/esm/components/Text.d.ts +3 -1
  25. package/lib/esm/components/Text.js.map +1 -1
  26. package/lib/esm/components/Typography.d.ts +7 -1
  27. package/lib/esm/components/Typography.js.map +1 -1
  28. package/lib/esm/components/elements.d.ts +42 -0
  29. package/lib/esm/components/elements.js +26 -0
  30. package/lib/esm/components/elements.js.map +1 -0
  31. package/lib/esm/package.json +1 -1
  32. package/package.json +2 -1
  33. package/test/prop-unions-are-styled.test.mjs +53 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @\_linked/primitives
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#34](https://github.com/linked-fw/primitives/pull/34) [`6514c31`](https://github.com/linked-fw/primitives/commit/6514c31aef96a8b4d087e1edf48aa86abe693b87) Thanks [@flyon](https://github.com/flyon)! - Add a root `types` field so a bare import gets types under node10 resolution.
8
+
9
+ There was no `types`/`typings` field at all, so a `moduleResolution: node10` consumer doing
10
+ `import … from '@_linked/primitives'` got `TS2307` — `exports` is ignored under node10. The value
11
+ `index.d.ts` is redirected by the existing `typesVersions` to `lib/esm/index.d.ts`, matching the
12
+ sibling packages.
13
+
14
+ ## 1.3.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [#32](https://github.com/linked-fw/primitives/pull/32) [`ec82d1a`](https://github.com/linked-fw/primitives/commit/ec82d1abf39af62f8954f0cc4057dcd4ec7cabff) Thanks [@flyon](https://github.com/flyon)! - Widen the `as` and `color` prop unions so they describe what the components
19
+ actually render.
20
+
21
+ `Typography`, `Text` and `Heading` spread `as` straight into JSX, so every
22
+ intrinsic tag has always worked at runtime, but the types named four or six
23
+ tags each — `as="strong"`, `as="code"`, `as="small"` were type errors for
24
+ behaviour that works. They now take a shared, curated element union
25
+ (`TextElement` / `HeadingAsElement`, exported from `components/elements.js`)
26
+ covering phrasing and text-block elements. Void, interactive and SVG tags are
27
+ deliberately excluded: these components require `children` and type their props
28
+ as `HtmlHTMLAttributes<HTMLElement>`, so those tags could not be rendered
29
+ correctly anyway.
30
+
31
+ `Heading` gains an optional `level` prop. Its stylesheet keys off the tag
32
+ (`h1.Root`), so `<Heading as="p">` renders with no heading typography; `level`
33
+ applies a level's tokens to any tag. Omitting it changes nothing.
34
+
35
+ `Badge` gains `accent` and `secondary` colours, each with a real rule in
36
+ `Badge.module.css`, and the union is now the exported `BadgeColor` type.
37
+
3
38
  ## 1.2.4
4
39
 
5
40
  ### Patch Changes
@@ -6,7 +6,8 @@ import * as React from 'react';
6
6
  * swap variation without new components —
7
7
  *
8
8
  * variant solid | soft | outline | ghost fill strength
9
- * color neutral | primary | success | warning | danger | info
9
+ * color neutral | primary | accent | secondary |
10
+ * success | warning | danger | info
10
11
  * size small | medium | large
11
12
  * shape pill | rounded
12
13
  * dot leading status dot in the badge color
@@ -17,10 +18,16 @@ import * as React from 'react';
17
18
  * variables with working fallbacks, so it renders sensibly out of the box and
18
19
  * apps re-skin it from their theme without touching the component.
19
20
  */
21
+ /**
22
+ * Every colour here has a `--_badge-color` rule in Badge.module.css — the union
23
+ * and the stylesheet are one list, so a colour can never type-check its way to
24
+ * an unstyled badge.
25
+ */
26
+ export type BadgeColor = 'neutral' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'danger' | 'info';
20
27
  export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
21
28
  asChild?: boolean;
22
29
  variant?: 'solid' | 'soft' | 'outline' | 'ghost';
23
- color?: 'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
30
+ color?: BadgeColor;
24
31
  size?: 'small' | 'medium' | 'large';
25
32
  shape?: 'pill' | 'rounded';
26
33
  /** Leading status dot in the badge color. */
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../src/components/Badge.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,0EAAuC;AACvC,gEAAqD;AACrD,qDAA4C;AA8B5C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAC5B,CACE,EAUC,EACD,GAAG,EACH,EAAE;QAZF,EACE,SAAS,EACT,OAAO,GAAG,MAAM,EAChB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,QAAQ,EACf,KAAK,GAAG,MAAM,EACd,GAAG,GAAG,KAAK,EACX,OAAO,GAAG,KAAK,EACf,QAAQ,OAET,EADI,KAAK,cATV,gFAUC,CADS;IAIV,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,iBAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAErC,OAAO,CACL,oBAAC,IAAI,kBACH,SAAS,EAAE,IAAA,eAAE,EACX,0BAAK,CAAC,IAAI,EACV,0BAAK,CAAC,OAAO,CAAC,EACd,0BAAK,CAAC,KAAK,CAAC,EACZ,0BAAK,CAAC,IAAI,CAAC,EACX,0BAAK,CAAC,KAAK,CAAC,EACZ,SAAS,CACV,EACD,GAAG,EAAE,GAAG,IACJ,KAAK,GAER,OAAO,CAAC,CAAC,CAAC,CACT,QAAQ,CACT,CAAC,CAAC,CAAC,CACF;QACG,GAAG,IAAI,8BAAM,SAAS,EAAE,0BAAK,CAAC,GAAG,wBAAgB;QACjD,QAAQ,CACR,CACJ,CACI,CACR,CAAC;AACJ,CAAC,CACF,CAAC;AAGO,sBAAK;AAFd,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC"}
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../src/components/Badge.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,0EAAuC;AACvC,gEAAqD;AACrD,qDAA4C;AA8C5C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAC5B,CACE,EAUC,EACD,GAAG,EACH,EAAE;QAZF,EACE,SAAS,EACT,OAAO,GAAG,MAAM,EAChB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,QAAQ,EACf,KAAK,GAAG,MAAM,EACd,GAAG,GAAG,KAAK,EACX,OAAO,GAAG,KAAK,EACf,QAAQ,OAET,EADI,KAAK,cATV,gFAUC,CADS;IAIV,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,iBAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAErC,OAAO,CACL,oBAAC,IAAI,kBACH,SAAS,EAAE,IAAA,eAAE,EACX,0BAAK,CAAC,IAAI,EACV,0BAAK,CAAC,OAAO,CAAC,EACd,0BAAK,CAAC,KAAK,CAAC,EACZ,0BAAK,CAAC,IAAI,CAAC,EACX,0BAAK,CAAC,KAAK,CAAC,EACZ,SAAS,CACV,EACD,GAAG,EAAE,GAAG,IACJ,KAAK,GAER,OAAO,CAAC,CAAC,CAAC,CACT,QAAQ,CACT,CAAC,CAAC,CAAC,CACF;QACG,GAAG,IAAI,8BAAM,SAAS,EAAE,0BAAK,CAAC,GAAG,wBAAgB;QACjD,QAAQ,CACR,CACJ,CACI,CACR,CAAC;AACJ,CAAC,CACF,CAAC;AAGO,sBAAK;AAFd,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC"}
@@ -21,6 +21,8 @@
21
21
  /* ── color ─────────────────────────────────────────────────────────────── */
22
22
  .neutral { --_badge-color: var(--badge-color-neutral, #64748b); }
23
23
  .primary { --_badge-color: var(--badge-color-primary, var(--primary, #4f46e5)); }
24
+ .secondary { --_badge-color: var(--badge-color-secondary, var(--secondary, #475569)); }
25
+ .accent { --_badge-color: var(--badge-color-accent, var(--control-accent, var(--primary, #4f46e5))); }
24
26
  .success { --_badge-color: var(--badge-color-success, #15803d); }
25
27
  .warning { --_badge-color: var(--badge-color-warning, #b45309); }
26
28
  .danger { --_badge-color: var(--badge-color-danger, #b91c1c); }
@@ -1,7 +1,21 @@
1
1
  import * as React from 'react';
2
+ import type { HeadingAsElement } from './elements.js';
2
3
  interface HeadingProps extends React.HtmlHTMLAttributes<HTMLElement> {
3
- as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
4
+ /**
5
+ * The tag to render. `h1`–`h6` pick up that level's heading tokens from the
6
+ * tag itself; any other container renders with no heading typography unless
7
+ * you also pass {@link HeadingProps.level}.
8
+ */
9
+ as: HeadingAsElement;
10
+ /**
11
+ * Visual heading level, independent of the tag. Use it when the heading is
12
+ * visual rather than semantic — a stat card's big number is a `p`, not an
13
+ * `h2`. Omitted, nothing is added: `as="h3"` styles as an h3 and `as="p"`
14
+ * stays unstyled, exactly as before.
15
+ */
16
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
4
17
  children: React.ReactNode;
5
18
  }
6
- declare const Heading: ({ as: Component, className, children, ...props }: HeadingProps) => React.JSX.Element;
19
+ declare const Heading: ({ as: Component, level, className, children, ...props }: HeadingProps) => React.JSX.Element;
7
20
  export { Heading };
21
+ export type { HeadingProps };
@@ -52,8 +52,8 @@ const React = __importStar(require("react"));
52
52
  const Heading_module_css_1 = __importDefault(require("./Heading.module.css"));
53
53
  const ClassNames_1 = require("@_linked/react/utils/ClassNames");
54
54
  const Heading = (_a) => {
55
- var { as: Component, className, children } = _a, props = __rest(_a, ["as", "className", "children"]);
56
- const combinedClassName = (0, ClassNames_1.cl)(Heading_module_css_1.default.Root, className);
55
+ var { as: Component, level, className, children } = _a, props = __rest(_a, ["as", "level", "className", "children"]);
56
+ const combinedClassName = (0, ClassNames_1.cl)(Heading_module_css_1.default.Root, level ? Heading_module_css_1.default[`level${level}`] : undefined, className);
57
57
  return (React.createElement(Component, Object.assign({ className: combinedClassName }, props), children));
58
58
  };
59
59
  exports.Heading = Heading;
@@ -1 +1 @@
1
- {"version":3,"file":"Heading.js","sourceRoot":"","sources":["../../../src/components/Heading.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,8EAAyC;AACzC,gEAAqD;AAMrD,MAAM,OAAO,GAAG,CAAC,EAKF,EAAE,EAAE;QALF,EACf,EAAE,EAAE,SAAS,EACb,SAAS,EACT,QAAQ,OAEK,EADV,KAAK,cAJO,+BAKhB,CADS;IAER,MAAM,iBAAiB,GAAG,IAAA,eAAE,EAAC,4BAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAEpD,OAAO,CACL,oBAAC,SAAS,kBAAC,SAAS,EAAE,iBAAiB,IAAM,KAAK,GAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEO,0BAAO"}
1
+ {"version":3,"file":"Heading.js","sourceRoot":"","sources":["../../../src/components/Heading.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,8EAAyC;AACzC,gEAAqD;AAmBrD,MAAM,OAAO,GAAG,CAAC,EAMF,EAAE,EAAE;QANF,EACf,EAAE,EAAE,SAAS,EACb,KAAK,EACL,SAAS,EACT,QAAQ,OAEK,EADV,KAAK,cALO,wCAMhB,CADS;IAER,MAAM,iBAAiB,GAAG,IAAA,eAAE,EAC1B,4BAAK,CAAC,IAAI,EACV,KAAK,CAAC,CAAC,CAAC,4BAAK,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,EAC1C,SAAS,CACV,CAAC;IAEF,OAAO,CACL,oBAAC,SAAS,kBAAC,SAAS,EAAE,iBAAiB,IAAM,KAAK,GAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEO,0BAAO"}
@@ -1,83 +1,81 @@
1
- h1 {
2
- &.Root {
3
- font-size: var(--heading-1-font-size);
4
- font-family: var(--heading-1-font-family);
5
- font-weight: var(--heading-1-font-weight);
6
- line-height: var(--heading-1-line-height);
7
- letter-spacing: var(--heading-1-letter-spacing);
8
- color: var(--heading-1-color);
9
- font-style: var(--heading-1-font-style);
10
- margin-top: var(--heading-1-margin-top);
11
- margin-bottom: var(--heading-1-margin-bottom);
12
- }
1
+ /* Two selectors per level: the tag itself (an `h2` is an h2, no extra props
2
+ needed) and an explicit `level` class for a heading that is visual rather
3
+ than semantic — `<Heading as="p" level={2}>` on a stat card. */
4
+
5
+ h1.Root,
6
+ .Root.level1 {
7
+ font-size: var(--heading-1-font-size);
8
+ font-family: var(--heading-1-font-family);
9
+ font-weight: var(--heading-1-font-weight);
10
+ line-height: var(--heading-1-line-height);
11
+ letter-spacing: var(--heading-1-letter-spacing);
12
+ color: var(--heading-1-color);
13
+ font-style: var(--heading-1-font-style);
14
+ margin-top: var(--heading-1-margin-top);
15
+ margin-bottom: var(--heading-1-margin-bottom);
13
16
  }
14
17
 
15
- h2 {
16
- &.Root {
17
- font-size: var(--heading-2-font-size);
18
- font-family: var(--heading-2-font-family);
19
- font-weight: var(--heading-2-font-weight);
20
- line-height: var(--heading-2-line-height);
21
- letter-spacing: var(--heading-2-letter-spacing);
22
- color: var(--heading-2-color);
23
- font-style: var(--heading-2-font-style);
24
- margin-top: var(--heading-2-margin-top);
25
- margin-bottom: var(--heading-2-margin-bottom);
26
- }
18
+ h2.Root,
19
+ .Root.level2 {
20
+ font-size: var(--heading-2-font-size);
21
+ font-family: var(--heading-2-font-family);
22
+ font-weight: var(--heading-2-font-weight);
23
+ line-height: var(--heading-2-line-height);
24
+ letter-spacing: var(--heading-2-letter-spacing);
25
+ color: var(--heading-2-color);
26
+ font-style: var(--heading-2-font-style);
27
+ margin-top: var(--heading-2-margin-top);
28
+ margin-bottom: var(--heading-2-margin-bottom);
27
29
  }
28
30
 
29
- h3 {
30
- &.Root {
31
- font-size: var(--heading-3-font-size);
32
- font-family: var(--heading-3-font-family);
33
- font-weight: var(--heading-3-font-weight);
34
- line-height: var(--heading-3-line-height);
35
- letter-spacing: var(--heading-3-letter-spacing);
36
- color: var(--heading-3-color);
37
- font-style: var(--heading-3-font-style);
38
- margin-top: var(--heading-3-margin-top);
39
- margin-bottom: var(--heading-3-margin-bottom);
40
- }
31
+ h3.Root,
32
+ .Root.level3 {
33
+ font-size: var(--heading-3-font-size);
34
+ font-family: var(--heading-3-font-family);
35
+ font-weight: var(--heading-3-font-weight);
36
+ line-height: var(--heading-3-line-height);
37
+ letter-spacing: var(--heading-3-letter-spacing);
38
+ color: var(--heading-3-color);
39
+ font-style: var(--heading-3-font-style);
40
+ margin-top: var(--heading-3-margin-top);
41
+ margin-bottom: var(--heading-3-margin-bottom);
41
42
  }
42
43
 
43
- h4 {
44
- &.Root {
45
- font-size: var(--heading-4-font-size);
46
- font-family: var(--heading-4-font-family);
47
- font-weight: var(--heading-4-font-weight);
48
- line-height: var(--heading-4-line-height);
49
- letter-spacing: var(--heading-4-letter-spacing);
50
- color: var(--heading-4-color);
51
- font-style: var(--heading-4-font-style);
52
- margin-top: var(--heading-4-margin-top);
53
- margin-bottom: var(--heading-4-margin-bottom);
54
- }
44
+ h4.Root,
45
+ .Root.level4 {
46
+ font-size: var(--heading-4-font-size);
47
+ font-family: var(--heading-4-font-family);
48
+ font-weight: var(--heading-4-font-weight);
49
+ line-height: var(--heading-4-line-height);
50
+ letter-spacing: var(--heading-4-letter-spacing);
51
+ color: var(--heading-4-color);
52
+ font-style: var(--heading-4-font-style);
53
+ margin-top: var(--heading-4-margin-top);
54
+ margin-bottom: var(--heading-4-margin-bottom);
55
55
  }
56
56
 
57
- h5 {
58
- &.Root {
59
- font-size: var(--heading-5-font-size);
60
- font-family: var(--heading-5-font-family);
61
- font-weight: var(--heading-5-font-weight);
62
- line-height: var(--heading-5-line-height);
63
- letter-spacing: var(--heading-5-letter-spacing);
64
- color: var(--heading-5-color);
65
- font-style: var(--heading-5-font-style);
66
- margin-top: var(--heading-5-margin-top);
67
- margin-bottom: var(--heading-5-margin-bottom);
68
- }
57
+ h5.Root,
58
+ .Root.level5 {
59
+ font-size: var(--heading-5-font-size);
60
+ font-family: var(--heading-5-font-family);
61
+ font-weight: var(--heading-5-font-weight);
62
+ line-height: var(--heading-5-line-height);
63
+ letter-spacing: var(--heading-5-letter-spacing);
64
+ color: var(--heading-5-color);
65
+ font-style: var(--heading-5-font-style);
66
+ margin-top: var(--heading-5-margin-top);
67
+ margin-bottom: var(--heading-5-margin-bottom);
69
68
  }
70
69
 
71
- h6 {
72
- &.Root {
73
- font-size: var(--heading-6-font-size);
74
- font-family: var(--heading-6-font-family);
75
- font-weight: var(--heading-6-font-weight);
76
- line-height: var(--heading-6-line-height);
77
- letter-spacing: var(--heading-6-letter-spacing);
78
- color: var(--heading-6-color);
79
- font-style: var(--heading-6-font-style);
80
- margin-top: var(--heading-6-margin-top);
81
- margin-bottom: var(--heading-6-margin-bottom);
82
- }
70
+ h6.Root,
71
+ .Root.level6 {
72
+ font-size: var(--heading-6-font-size);
73
+ font-family: var(--heading-6-font-family);
74
+ font-weight: var(--heading-6-font-weight);
75
+ line-height: var(--heading-6-line-height);
76
+ letter-spacing: var(--heading-6-letter-spacing);
77
+ color: var(--heading-6-color);
78
+ font-style: var(--heading-6-font-style);
79
+ margin-top: var(--heading-6-margin-top);
80
+ margin-bottom: var(--heading-6-margin-bottom);
83
81
  }
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react';
2
+ import type { TextElement } from './elements.js';
2
3
  interface TextProps extends React.HtmlHTMLAttributes<HTMLElement> {
3
- as?: 'span' | 'label' | 'p' | 'div';
4
+ /** The tag to render — see {@link TextElement}. */
5
+ as?: TextElement;
4
6
  size?: 'small' | 'medium' | 'large' | 'default';
5
7
  children: React.ReactNode;
6
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Text.js","sourceRoot":"","sources":["../../../src/components/Text.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,wEAAsC;AACtC,gEAAqD;AAOrD,MAAM,IAAI,GAAG,CAAC,EAKF,EAAE,EAAE;QALF,EACZ,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,QAAQ,OAEE,EADP,KAAK,cAJI,0BAKb,CADS;IAER,OAAO,CACL,oBAAC,SAAS,kBAAC,SAAS,EAAE,IAAA,eAAE,EAAC,yBAAK,CAAC,IAAI,EAAE,yBAAK,CAAC,IAAI,CAAC,CAAC,IAAM,KAAK,GACzD,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEO,oBAAI"}
1
+ {"version":3,"file":"Text.js","sourceRoot":"","sources":["../../../src/components/Text.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,wEAAsC;AACtC,gEAAqD;AASrD,MAAM,IAAI,GAAG,CAAC,EAKF,EAAE,EAAE;QALF,EACZ,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,QAAQ,OAEE,EADP,KAAK,cAJI,0BAKb,CADS;IAER,OAAO,CACL,oBAAC,SAAS,kBAAC,SAAS,EAAE,IAAA,eAAE,EAAC,yBAAK,CAAC,IAAI,EAAE,yBAAK,CAAC,IAAI,CAAC,CAAC,IAAM,KAAK,GACzD,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEO,oBAAI"}
@@ -1,6 +1,12 @@
1
1
  import * as React from 'react';
2
+ import type { TextElement } from './elements.js';
2
3
  interface TypographyProps extends React.HtmlHTMLAttributes<HTMLElement> {
3
- as?: 'span' | 'label' | 'p' | 'div';
4
+ /**
5
+ * The tag to render. Spread straight into JSX and styled tag-agnostically, so
6
+ * every element in {@link TextElement} renders correctly — see `elements.ts`
7
+ * for why this is a curated list and not every intrinsic element.
8
+ */
9
+ as?: TextElement;
4
10
  size?: 'small' | 'medium' | 'large' | 'default';
5
11
  children: React.ReactNode;
6
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Typography.js","sourceRoot":"","sources":["../../../src/components/Typography.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,oFAA4C;AAC5C,gEAAqD;AAOrD,MAAM,UAAU,GAAG,CAAC,EAMF,EAAE,EAAE;QANF,EAClB,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,SAAS,EACT,QAAQ,OAEQ,EADb,KAAK,cALU,uCAMnB,CADS;IAER,MAAM,iBAAiB,GAAG,IAAA,eAAE,EAAC,+BAAK,CAAC,IAAI,EAAE,+BAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IAEjE,OAAO,CACL,oBAAC,SAAS,kBAAC,SAAS,EAAE,iBAAiB,IAAM,KAAK,GAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEO,gCAAU"}
1
+ {"version":3,"file":"Typography.js","sourceRoot":"","sources":["../../../src/components/Typography.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAE/B,oFAA4C;AAC5C,gEAAqD;AAarD,MAAM,UAAU,GAAG,CAAC,EAMF,EAAE,EAAE;QANF,EAClB,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,SAAS,EACT,QAAQ,OAEQ,EADb,KAAK,cALU,uCAMnB,CADS;IAER,MAAM,iBAAiB,GAAG,IAAA,eAAE,EAAC,+BAAK,CAAC,IAAI,EAAE,+BAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IAEjE,OAAO,CACL,oBAAC,SAAS,kBAAC,SAAS,EAAE,iBAAiB,IAAM,KAAK,GAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEO,gCAAU"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Element unions for the `as` prop of the typographic primitives.
3
+ *
4
+ * These components spread `as` straight into JSX, so at runtime *any* intrinsic
5
+ * tag works. The types used to name a handful of tags each, which made ordinary
6
+ * usage (`as="strong"`, `as="code"`, `as="small"`) a type error for behaviour
7
+ * that has always worked.
8
+ *
9
+ * We deliberately do NOT use `keyof React.JSX.IntrinsicElements`:
10
+ *
11
+ * - these components take required `children`, and React throws at runtime for
12
+ * a void element with children (`<input>`, `<img>`, `<br>`, `<hr>`…), so the
13
+ * full key set would type-check code that crashes;
14
+ * - their props are `React.HtmlHTMLAttributes<HTMLElement>`, which cannot carry
15
+ * element-specific attributes (`href`, `type`, `value`), so interactive and
16
+ * form tags would be offered without the attributes that make them useful;
17
+ * - `svg`/`math` and the SVG child tags take a different attribute namespace
18
+ * entirely.
19
+ *
20
+ * What is left — every HTML element that holds phrasing or flow content and is
21
+ * styled purely by the component's own class — is exactly the set below. It is
22
+ * wide enough that drift is unlikely, and honest about what the component can
23
+ * actually render.
24
+ */
25
+ /** Inline (phrasing) elements: text-level semantics. */
26
+ export type PhrasingElement = 'span' | 'label' | 'strong' | 'em' | 'b' | 'i' | 'u' | 's' | 'small' | 'mark' | 'code' | 'kbd' | 'samp' | 'var' | 'sub' | 'sup' | 'abbr' | 'cite' | 'dfn' | 'q' | 'time' | 'data' | 'del' | 'ins' | 'bdi' | 'bdo' | 'output' | 'ruby' | 'rt' | 'rp';
27
+ /** Block-level containers that hold text. */
28
+ export type TextBlockElement = 'p' | 'div' | 'pre' | 'blockquote' | 'address' | 'figcaption' | 'caption' | 'legend' | 'summary' | 'li' | 'dt' | 'dd' | 'td' | 'th';
29
+ /** The six HTML heading levels. */
30
+ export type HeadingElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
31
+ /**
32
+ * Anything `Typography` / `Text` can render: inline text, a text block, or a
33
+ * heading tag (a caption-styled heading is a normal thing to want).
34
+ */
35
+ export type TextElement = PhrasingElement | TextBlockElement | HeadingElement;
36
+ /**
37
+ * Anything `Heading` can render. A heading is often visual rather than
38
+ * semantic — a stat card's big number is a `p`, not an `h2` — so the non-heading
39
+ * containers are allowed too. See `Heading`'s `level` prop for the styling that
40
+ * goes with them.
41
+ */
42
+ export type HeadingAsElement = HeadingElement | TextBlockElement | PhrasingElement;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * Element unions for the `as` prop of the typographic primitives.
4
+ *
5
+ * These components spread `as` straight into JSX, so at runtime *any* intrinsic
6
+ * tag works. The types used to name a handful of tags each, which made ordinary
7
+ * usage (`as="strong"`, `as="code"`, `as="small"`) a type error for behaviour
8
+ * that has always worked.
9
+ *
10
+ * We deliberately do NOT use `keyof React.JSX.IntrinsicElements`:
11
+ *
12
+ * - these components take required `children`, and React throws at runtime for
13
+ * a void element with children (`<input>`, `<img>`, `<br>`, `<hr>`…), so the
14
+ * full key set would type-check code that crashes;
15
+ * - their props are `React.HtmlHTMLAttributes<HTMLElement>`, which cannot carry
16
+ * element-specific attributes (`href`, `type`, `value`), so interactive and
17
+ * form tags would be offered without the attributes that make them useful;
18
+ * - `svg`/`math` and the SVG child tags take a different attribute namespace
19
+ * entirely.
20
+ *
21
+ * What is left — every HTML element that holds phrasing or flow content and is
22
+ * styled purely by the component's own class — is exactly the set below. It is
23
+ * wide enough that drift is unlikely, and honest about what the component can
24
+ * actually render.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ //# sourceMappingURL=elements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elements.js","sourceRoot":"","sources":["../../../src/components/elements.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/primitives.git"
12
12
  },
13
- "version": "1.2.4",
13
+ "version": "1.3.1",
14
14
  "packageManager": "npm@11.19.1",
15
15
  "linkedPackage": true,
16
16
  "license": "ISC",
@@ -6,7 +6,8 @@ import * as React from 'react';
6
6
  * swap variation without new components —
7
7
  *
8
8
  * variant solid | soft | outline | ghost fill strength
9
- * color neutral | primary | success | warning | danger | info
9
+ * color neutral | primary | accent | secondary |
10
+ * success | warning | danger | info
10
11
  * size small | medium | large
11
12
  * shape pill | rounded
12
13
  * dot leading status dot in the badge color
@@ -17,10 +18,16 @@ import * as React from 'react';
17
18
  * variables with working fallbacks, so it renders sensibly out of the box and
18
19
  * apps re-skin it from their theme without touching the component.
19
20
  */
21
+ /**
22
+ * Every colour here has a `--_badge-color` rule in Badge.module.css — the union
23
+ * and the stylesheet are one list, so a colour can never type-check its way to
24
+ * an unstyled badge.
25
+ */
26
+ export type BadgeColor = 'neutral' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'danger' | 'info';
20
27
  export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
21
28
  asChild?: boolean;
22
29
  variant?: 'solid' | 'soft' | 'outline' | 'ghost';
23
- color?: 'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
30
+ color?: BadgeColor;
24
31
  size?: 'small' | 'medium' | 'large';
25
32
  shape?: 'pill' | 'rounded';
26
33
  /** Leading status dot in the badge color. */
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../src/components/Badge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,oBAAoB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AA8B5C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAC5B,CACE,EACE,SAAS,EACT,OAAO,GAAG,MAAM,EAChB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,QAAQ,EACf,KAAK,GAAG,MAAM,EACd,GAAG,GAAG,KAAK,EACX,OAAO,GAAG,KAAK,EACf,QAAQ,EACR,GAAG,KAAK,EACT,EACD,GAAG,EACH,EAAE;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAErC,OAAO,CACL,oBAAC,IAAI,IACH,SAAS,EAAE,EAAE,CACX,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,OAAO,CAAC,EACd,KAAK,CAAC,KAAK,CAAC,EACZ,KAAK,CAAC,IAAI,CAAC,EACX,KAAK,CAAC,KAAK,CAAC,EACZ,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,IAER,OAAO,CAAC,CAAC,CAAC,CACT,QAAQ,CACT,CAAC,CAAC,CAAC,CACF;QACG,GAAG,IAAI,8BAAM,SAAS,EAAE,KAAK,CAAC,GAAG,wBAAgB;QACjD,QAAQ,CACR,CACJ,CACI,CACR,CAAC;AACJ,CAAC,CACF,CAAC;AACF,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC;AAE5B,OAAO,EAAE,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../src/components/Badge.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,oBAAoB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AA8C5C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAC5B,CACE,EACE,SAAS,EACT,OAAO,GAAG,MAAM,EAChB,KAAK,GAAG,SAAS,EACjB,IAAI,GAAG,QAAQ,EACf,KAAK,GAAG,MAAM,EACd,GAAG,GAAG,KAAK,EACX,OAAO,GAAG,KAAK,EACf,QAAQ,EACR,GAAG,KAAK,EACT,EACD,GAAG,EACH,EAAE;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAErC,OAAO,CACL,oBAAC,IAAI,IACH,SAAS,EAAE,EAAE,CACX,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,OAAO,CAAC,EACd,KAAK,CAAC,KAAK,CAAC,EACZ,KAAK,CAAC,IAAI,CAAC,EACX,KAAK,CAAC,KAAK,CAAC,EACZ,SAAS,CACV,EACD,GAAG,EAAE,GAAG,KACJ,KAAK,IAER,OAAO,CAAC,CAAC,CAAC,CACT,QAAQ,CACT,CAAC,CAAC,CAAC,CACF;QACG,GAAG,IAAI,8BAAM,SAAS,EAAE,KAAK,CAAC,GAAG,wBAAgB;QACjD,QAAQ,CACR,CACJ,CACI,CACR,CAAC;AACJ,CAAC,CACF,CAAC;AACF,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC;AAE5B,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -21,6 +21,8 @@
21
21
  /* ── color ─────────────────────────────────────────────────────────────── */
22
22
  .neutral { --_badge-color: var(--badge-color-neutral, #64748b); }
23
23
  .primary { --_badge-color: var(--badge-color-primary, var(--primary, #4f46e5)); }
24
+ .secondary { --_badge-color: var(--badge-color-secondary, var(--secondary, #475569)); }
25
+ .accent { --_badge-color: var(--badge-color-accent, var(--control-accent, var(--primary, #4f46e5))); }
24
26
  .success { --_badge-color: var(--badge-color-success, #15803d); }
25
27
  .warning { --_badge-color: var(--badge-color-warning, #b45309); }
26
28
  .danger { --_badge-color: var(--badge-color-danger, #b91c1c); }
@@ -1,7 +1,21 @@
1
1
  import * as React from 'react';
2
+ import type { HeadingAsElement } from './elements.js';
2
3
  interface HeadingProps extends React.HtmlHTMLAttributes<HTMLElement> {
3
- as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
4
+ /**
5
+ * The tag to render. `h1`–`h6` pick up that level's heading tokens from the
6
+ * tag itself; any other container renders with no heading typography unless
7
+ * you also pass {@link HeadingProps.level}.
8
+ */
9
+ as: HeadingAsElement;
10
+ /**
11
+ * Visual heading level, independent of the tag. Use it when the heading is
12
+ * visual rather than semantic — a stat card's big number is a `p`, not an
13
+ * `h2`. Omitted, nothing is added: `as="h3"` styles as an h3 and `as="p"`
14
+ * stays unstyled, exactly as before.
15
+ */
16
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
4
17
  children: React.ReactNode;
5
18
  }
6
- declare const Heading: ({ as: Component, className, children, ...props }: HeadingProps) => React.JSX.Element;
19
+ declare const Heading: ({ as: Component, level, className, children, ...props }: HeadingProps) => React.JSX.Element;
7
20
  export { Heading };
21
+ export type { HeadingProps };
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import style from './Heading.module.css';
3
3
  import { cl } from '@_linked/react/utils/ClassNames';
4
- const Heading = ({ as: Component, className, children, ...props }) => {
5
- const combinedClassName = cl(style.Root, className);
4
+ const Heading = ({ as: Component, level, className, children, ...props }) => {
5
+ const combinedClassName = cl(style.Root, level ? style[`level${level}`] : undefined, className);
6
6
  return (React.createElement(Component, { className: combinedClassName, ...props }, children));
7
7
  };
8
8
  export { Heading };
@@ -1 +1 @@
1
- {"version":3,"file":"Heading.js","sourceRoot":"","sources":["../../../src/components/Heading.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,sBAAsB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AAMrD,MAAM,OAAO,GAAG,CAAC,EACf,EAAE,EAAE,SAAS,EACb,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACK,EAAE,EAAE;IACjB,MAAM,iBAAiB,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAEpD,OAAO,CACL,oBAAC,SAAS,IAAC,SAAS,EAAE,iBAAiB,KAAM,KAAK,IAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"Heading.js","sourceRoot":"","sources":["../../../src/components/Heading.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,sBAAsB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AAmBrD,MAAM,OAAO,GAAG,CAAC,EACf,EAAE,EAAE,SAAS,EACb,KAAK,EACL,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACK,EAAE,EAAE;IACjB,MAAM,iBAAiB,GAAG,EAAE,CAC1B,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,EAC1C,SAAS,CACV,CAAC;IAEF,OAAO,CACL,oBAAC,SAAS,IAAC,SAAS,EAAE,iBAAiB,KAAM,KAAK,IAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -1,83 +1,81 @@
1
- h1 {
2
- &.Root {
3
- font-size: var(--heading-1-font-size);
4
- font-family: var(--heading-1-font-family);
5
- font-weight: var(--heading-1-font-weight);
6
- line-height: var(--heading-1-line-height);
7
- letter-spacing: var(--heading-1-letter-spacing);
8
- color: var(--heading-1-color);
9
- font-style: var(--heading-1-font-style);
10
- margin-top: var(--heading-1-margin-top);
11
- margin-bottom: var(--heading-1-margin-bottom);
12
- }
1
+ /* Two selectors per level: the tag itself (an `h2` is an h2, no extra props
2
+ needed) and an explicit `level` class for a heading that is visual rather
3
+ than semantic — `<Heading as="p" level={2}>` on a stat card. */
4
+
5
+ h1.Root,
6
+ .Root.level1 {
7
+ font-size: var(--heading-1-font-size);
8
+ font-family: var(--heading-1-font-family);
9
+ font-weight: var(--heading-1-font-weight);
10
+ line-height: var(--heading-1-line-height);
11
+ letter-spacing: var(--heading-1-letter-spacing);
12
+ color: var(--heading-1-color);
13
+ font-style: var(--heading-1-font-style);
14
+ margin-top: var(--heading-1-margin-top);
15
+ margin-bottom: var(--heading-1-margin-bottom);
13
16
  }
14
17
 
15
- h2 {
16
- &.Root {
17
- font-size: var(--heading-2-font-size);
18
- font-family: var(--heading-2-font-family);
19
- font-weight: var(--heading-2-font-weight);
20
- line-height: var(--heading-2-line-height);
21
- letter-spacing: var(--heading-2-letter-spacing);
22
- color: var(--heading-2-color);
23
- font-style: var(--heading-2-font-style);
24
- margin-top: var(--heading-2-margin-top);
25
- margin-bottom: var(--heading-2-margin-bottom);
26
- }
18
+ h2.Root,
19
+ .Root.level2 {
20
+ font-size: var(--heading-2-font-size);
21
+ font-family: var(--heading-2-font-family);
22
+ font-weight: var(--heading-2-font-weight);
23
+ line-height: var(--heading-2-line-height);
24
+ letter-spacing: var(--heading-2-letter-spacing);
25
+ color: var(--heading-2-color);
26
+ font-style: var(--heading-2-font-style);
27
+ margin-top: var(--heading-2-margin-top);
28
+ margin-bottom: var(--heading-2-margin-bottom);
27
29
  }
28
30
 
29
- h3 {
30
- &.Root {
31
- font-size: var(--heading-3-font-size);
32
- font-family: var(--heading-3-font-family);
33
- font-weight: var(--heading-3-font-weight);
34
- line-height: var(--heading-3-line-height);
35
- letter-spacing: var(--heading-3-letter-spacing);
36
- color: var(--heading-3-color);
37
- font-style: var(--heading-3-font-style);
38
- margin-top: var(--heading-3-margin-top);
39
- margin-bottom: var(--heading-3-margin-bottom);
40
- }
31
+ h3.Root,
32
+ .Root.level3 {
33
+ font-size: var(--heading-3-font-size);
34
+ font-family: var(--heading-3-font-family);
35
+ font-weight: var(--heading-3-font-weight);
36
+ line-height: var(--heading-3-line-height);
37
+ letter-spacing: var(--heading-3-letter-spacing);
38
+ color: var(--heading-3-color);
39
+ font-style: var(--heading-3-font-style);
40
+ margin-top: var(--heading-3-margin-top);
41
+ margin-bottom: var(--heading-3-margin-bottom);
41
42
  }
42
43
 
43
- h4 {
44
- &.Root {
45
- font-size: var(--heading-4-font-size);
46
- font-family: var(--heading-4-font-family);
47
- font-weight: var(--heading-4-font-weight);
48
- line-height: var(--heading-4-line-height);
49
- letter-spacing: var(--heading-4-letter-spacing);
50
- color: var(--heading-4-color);
51
- font-style: var(--heading-4-font-style);
52
- margin-top: var(--heading-4-margin-top);
53
- margin-bottom: var(--heading-4-margin-bottom);
54
- }
44
+ h4.Root,
45
+ .Root.level4 {
46
+ font-size: var(--heading-4-font-size);
47
+ font-family: var(--heading-4-font-family);
48
+ font-weight: var(--heading-4-font-weight);
49
+ line-height: var(--heading-4-line-height);
50
+ letter-spacing: var(--heading-4-letter-spacing);
51
+ color: var(--heading-4-color);
52
+ font-style: var(--heading-4-font-style);
53
+ margin-top: var(--heading-4-margin-top);
54
+ margin-bottom: var(--heading-4-margin-bottom);
55
55
  }
56
56
 
57
- h5 {
58
- &.Root {
59
- font-size: var(--heading-5-font-size);
60
- font-family: var(--heading-5-font-family);
61
- font-weight: var(--heading-5-font-weight);
62
- line-height: var(--heading-5-line-height);
63
- letter-spacing: var(--heading-5-letter-spacing);
64
- color: var(--heading-5-color);
65
- font-style: var(--heading-5-font-style);
66
- margin-top: var(--heading-5-margin-top);
67
- margin-bottom: var(--heading-5-margin-bottom);
68
- }
57
+ h5.Root,
58
+ .Root.level5 {
59
+ font-size: var(--heading-5-font-size);
60
+ font-family: var(--heading-5-font-family);
61
+ font-weight: var(--heading-5-font-weight);
62
+ line-height: var(--heading-5-line-height);
63
+ letter-spacing: var(--heading-5-letter-spacing);
64
+ color: var(--heading-5-color);
65
+ font-style: var(--heading-5-font-style);
66
+ margin-top: var(--heading-5-margin-top);
67
+ margin-bottom: var(--heading-5-margin-bottom);
69
68
  }
70
69
 
71
- h6 {
72
- &.Root {
73
- font-size: var(--heading-6-font-size);
74
- font-family: var(--heading-6-font-family);
75
- font-weight: var(--heading-6-font-weight);
76
- line-height: var(--heading-6-line-height);
77
- letter-spacing: var(--heading-6-letter-spacing);
78
- color: var(--heading-6-color);
79
- font-style: var(--heading-6-font-style);
80
- margin-top: var(--heading-6-margin-top);
81
- margin-bottom: var(--heading-6-margin-bottom);
82
- }
70
+ h6.Root,
71
+ .Root.level6 {
72
+ font-size: var(--heading-6-font-size);
73
+ font-family: var(--heading-6-font-family);
74
+ font-weight: var(--heading-6-font-weight);
75
+ line-height: var(--heading-6-line-height);
76
+ letter-spacing: var(--heading-6-letter-spacing);
77
+ color: var(--heading-6-color);
78
+ font-style: var(--heading-6-font-style);
79
+ margin-top: var(--heading-6-margin-top);
80
+ margin-bottom: var(--heading-6-margin-bottom);
83
81
  }
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react';
2
+ import type { TextElement } from './elements.js';
2
3
  interface TextProps extends React.HtmlHTMLAttributes<HTMLElement> {
3
- as?: 'span' | 'label' | 'p' | 'div';
4
+ /** The tag to render — see {@link TextElement}. */
5
+ as?: TextElement;
4
6
  size?: 'small' | 'medium' | 'large' | 'default';
5
7
  children: React.ReactNode;
6
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Text.js","sourceRoot":"","sources":["../../../src/components/Text.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AAOrD,MAAM,IAAI,GAAG,CAAC,EACZ,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,QAAQ,EACR,GAAG,KAAK,EACE,EAAE,EAAE;IACd,OAAO,CACL,oBAAC,SAAS,IAAC,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAM,KAAK,IACzD,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"Text.js","sourceRoot":"","sources":["../../../src/components/Text.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AASrD,MAAM,IAAI,GAAG,CAAC,EACZ,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,QAAQ,EACR,GAAG,KAAK,EACE,EAAE,EAAE;IACd,OAAO,CACL,oBAAC,SAAS,IAAC,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAM,KAAK,IACzD,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,CAAC"}
@@ -1,6 +1,12 @@
1
1
  import * as React from 'react';
2
+ import type { TextElement } from './elements.js';
2
3
  interface TypographyProps extends React.HtmlHTMLAttributes<HTMLElement> {
3
- as?: 'span' | 'label' | 'p' | 'div';
4
+ /**
5
+ * The tag to render. Spread straight into JSX and styled tag-agnostically, so
6
+ * every element in {@link TextElement} renders correctly — see `elements.ts`
7
+ * for why this is a curated list and not every intrinsic element.
8
+ */
9
+ as?: TextElement;
4
10
  size?: 'small' | 'medium' | 'large' | 'default';
5
11
  children: React.ReactNode;
6
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Typography.js","sourceRoot":"","sources":["../../../src/components/Typography.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,yBAAyB,CAAC;AAC5C,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AAOrD,MAAM,UAAU,GAAG,CAAC,EAClB,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACQ,EAAE,EAAE;IACpB,MAAM,iBAAiB,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IAEjE,OAAO,CACL,oBAAC,SAAS,IAAC,SAAS,EAAE,iBAAiB,KAAM,KAAK,IAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"Typography.js","sourceRoot":"","sources":["../../../src/components/Typography.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,MAAM,yBAAyB,CAAC;AAC5C,OAAO,EAAE,EAAE,EAAE,MAAM,iCAAiC,CAAC;AAarD,MAAM,UAAU,GAAG,CAAC,EAClB,EAAE,EAAE,SAAS,GAAG,GAAG,EACnB,IAAI,GAAG,SAAS,EAChB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACQ,EAAE,EAAE;IACpB,MAAM,iBAAiB,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IAEjE,OAAO,CACL,oBAAC,SAAS,IAAC,SAAS,EAAE,iBAAiB,KAAM,KAAK,IAC/C,QAAQ,CACC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Element unions for the `as` prop of the typographic primitives.
3
+ *
4
+ * These components spread `as` straight into JSX, so at runtime *any* intrinsic
5
+ * tag works. The types used to name a handful of tags each, which made ordinary
6
+ * usage (`as="strong"`, `as="code"`, `as="small"`) a type error for behaviour
7
+ * that has always worked.
8
+ *
9
+ * We deliberately do NOT use `keyof React.JSX.IntrinsicElements`:
10
+ *
11
+ * - these components take required `children`, and React throws at runtime for
12
+ * a void element with children (`<input>`, `<img>`, `<br>`, `<hr>`…), so the
13
+ * full key set would type-check code that crashes;
14
+ * - their props are `React.HtmlHTMLAttributes<HTMLElement>`, which cannot carry
15
+ * element-specific attributes (`href`, `type`, `value`), so interactive and
16
+ * form tags would be offered without the attributes that make them useful;
17
+ * - `svg`/`math` and the SVG child tags take a different attribute namespace
18
+ * entirely.
19
+ *
20
+ * What is left — every HTML element that holds phrasing or flow content and is
21
+ * styled purely by the component's own class — is exactly the set below. It is
22
+ * wide enough that drift is unlikely, and honest about what the component can
23
+ * actually render.
24
+ */
25
+ /** Inline (phrasing) elements: text-level semantics. */
26
+ export type PhrasingElement = 'span' | 'label' | 'strong' | 'em' | 'b' | 'i' | 'u' | 's' | 'small' | 'mark' | 'code' | 'kbd' | 'samp' | 'var' | 'sub' | 'sup' | 'abbr' | 'cite' | 'dfn' | 'q' | 'time' | 'data' | 'del' | 'ins' | 'bdi' | 'bdo' | 'output' | 'ruby' | 'rt' | 'rp';
27
+ /** Block-level containers that hold text. */
28
+ export type TextBlockElement = 'p' | 'div' | 'pre' | 'blockquote' | 'address' | 'figcaption' | 'caption' | 'legend' | 'summary' | 'li' | 'dt' | 'dd' | 'td' | 'th';
29
+ /** The six HTML heading levels. */
30
+ export type HeadingElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
31
+ /**
32
+ * Anything `Typography` / `Text` can render: inline text, a text block, or a
33
+ * heading tag (a caption-styled heading is a normal thing to want).
34
+ */
35
+ export type TextElement = PhrasingElement | TextBlockElement | HeadingElement;
36
+ /**
37
+ * Anything `Heading` can render. A heading is often visual rather than
38
+ * semantic — a stat card's big number is a `p`, not an `h2` — so the non-heading
39
+ * containers are allowed too. See `Heading`'s `level` prop for the styling that
40
+ * goes with them.
41
+ */
42
+ export type HeadingAsElement = HeadingElement | TextBlockElement | PhrasingElement;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Element unions for the `as` prop of the typographic primitives.
3
+ *
4
+ * These components spread `as` straight into JSX, so at runtime *any* intrinsic
5
+ * tag works. The types used to name a handful of tags each, which made ordinary
6
+ * usage (`as="strong"`, `as="code"`, `as="small"`) a type error for behaviour
7
+ * that has always worked.
8
+ *
9
+ * We deliberately do NOT use `keyof React.JSX.IntrinsicElements`:
10
+ *
11
+ * - these components take required `children`, and React throws at runtime for
12
+ * a void element with children (`<input>`, `<img>`, `<br>`, `<hr>`…), so the
13
+ * full key set would type-check code that crashes;
14
+ * - their props are `React.HtmlHTMLAttributes<HTMLElement>`, which cannot carry
15
+ * element-specific attributes (`href`, `type`, `value`), so interactive and
16
+ * form tags would be offered without the attributes that make them useful;
17
+ * - `svg`/`math` and the SVG child tags take a different attribute namespace
18
+ * entirely.
19
+ *
20
+ * What is left — every HTML element that holds phrasing or flow content and is
21
+ * styled purely by the component's own class — is exactly the set below. It is
22
+ * wide enough that drift is unlikely, and honest about what the component can
23
+ * actually render.
24
+ */
25
+ export {};
26
+ //# sourceMappingURL=elements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elements.js","sourceRoot":"","sources":["../../../src/components/elements.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/primitives.git"
12
12
  },
13
- "version": "1.2.4",
13
+ "version": "1.3.1",
14
14
  "packageManager": "npm@11.19.1",
15
15
  "linkedPackage": true,
16
16
  "license": "ISC",
package/package.json CHANGED
@@ -10,10 +10,11 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/linked-fw/primitives.git"
12
12
  },
13
- "version": "1.2.4",
13
+ "version": "1.3.1",
14
14
  "packageManager": "npm@11.19.1",
15
15
  "linkedPackage": true,
16
16
  "main": "lib/cjs/index.js",
17
+ "types": "index.d.ts",
17
18
  "license": "ISC",
18
19
  "scripts": {
19
20
  "start": "npm exec linked dev",
@@ -0,0 +1,53 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import test from 'node:test';
4
+
5
+ const read = (name) =>
6
+ readFileSync(new URL(`../src/components/${name}`, import.meta.url), 'utf8');
7
+
8
+ /**
9
+ * A widened union is only an improvement if every value it now admits is
10
+ * actually styled — a `color` the stylesheet has never heard of is a worse
11
+ * outcome than the type error it replaced. These tests keep the unions and the
12
+ * stylesheets in step.
13
+ */
14
+
15
+ test('every BadgeColor has a --_badge-color rule', () => {
16
+ const source = read('Badge.tsx');
17
+ const stylesheet = read('Badge.module.css');
18
+
19
+ const union = source.match(/export type BadgeColor =([^;]+);/);
20
+ assert.ok(union, 'BadgeColor union not found in Badge.tsx');
21
+
22
+ const colors = [...union[1].matchAll(/'([a-z-]+)'/g)].map((m) => m[1]);
23
+ assert.ok(colors.length >= 6, `expected the full colour set, got ${colors}`);
24
+
25
+ for (const color of colors) {
26
+ assert.match(
27
+ stylesheet,
28
+ new RegExp(`\\.${color}\\s*\\{[^}]*--_badge-color:`),
29
+ `Badge color "${color}" is in the type union but has no rule in Badge.module.css`
30
+ );
31
+ }
32
+ });
33
+
34
+ test('every Heading level has a .Root.levelN rule, and h1-h6 stay tag-styled', () => {
35
+ const source = read('Heading.tsx');
36
+ const stylesheet = read('Heading.module.css');
37
+
38
+ const levels = source.match(/level\?:([^;]+);/);
39
+ assert.ok(levels, 'level prop not found in Heading.tsx');
40
+
41
+ for (const level of [...levels[1].matchAll(/\d/g)].map((m) => m[0])) {
42
+ assert.match(
43
+ stylesheet,
44
+ new RegExp(`\\.Root\\.level${level}\\s*[,{]`),
45
+ `Heading level ${level} is accepted but has no .Root.level${level} rule`
46
+ );
47
+ assert.match(
48
+ stylesheet,
49
+ new RegExp(`h${level}\\.Root\\s*[,{]`),
50
+ `<h${level}> no longer picks up its heading tokens from the tag alone`
51
+ );
52
+ }
53
+ });