@ctrl-ui/react 0.9.5 → 0.9.8

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 (165) hide show
  1. package/README.md +38 -2
  2. package/dist/action-bar.js +16 -8
  3. package/dist/activity.d.ts +4 -2
  4. package/dist/activity.js +5 -2
  5. package/dist/audio-visualizer.js +1 -1
  6. package/dist/blocks/agent-run-clock.d.ts +5 -0
  7. package/dist/blocks/agent-run-clock.js +52 -0
  8. package/dist/blocks/agent-run-sidebar.d.ts +13 -0
  9. package/dist/blocks/agent-run-sidebar.js +101 -0
  10. package/dist/blocks/agent-run-status.d.ts +32 -0
  11. package/dist/blocks/agent-run-status.js +27 -0
  12. package/dist/blocks/agent-run.d.ts +82 -0
  13. package/dist/blocks/agent-run.js +262 -0
  14. package/dist/blocks/coding-agent.d.ts +1 -1
  15. package/dist/blocks/coding-agent.js +50 -69
  16. package/dist/blocks/design-canvas-data.d.ts +1 -1
  17. package/dist/blocks/design-canvas-inspector.js +1 -1
  18. package/dist/blocks/design-canvas.d.ts +2 -1
  19. package/dist/blocks/design-canvas.js +56 -42
  20. package/dist/blocks/discussion.js +1 -1
  21. package/dist/blocks/file-explorer.js +66 -47
  22. package/dist/blocks/settings.js +37 -41
  23. package/dist/blocks/team-chat.js +2 -1
  24. package/dist/blocks/theme-toggle.d.ts +2 -1
  25. package/dist/blocks/theme-toggle.js +13 -4
  26. package/dist/chat-message.js +33 -39
  27. package/dist/hooks/use-mobile.d.ts +1 -1
  28. package/dist/hooks/use-mobile.js +1 -1
  29. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  30. package/dist/knob-contracts/badge-knobs.js +3 -1
  31. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  32. package/dist/knob-contracts/button-knobs.js +1 -0
  33. package/dist/knob-contracts/calendar-knobs.d.ts +1 -1
  34. package/dist/knob-contracts/calendar-knobs.js +9 -1
  35. package/dist/knob-contracts/card-knobs.d.ts +1 -1
  36. package/dist/knob-contracts/card-knobs.js +5 -1
  37. package/dist/knob-contracts/choice-knobs.d.ts +1 -1
  38. package/dist/knob-contracts/choice-knobs.js +1 -0
  39. package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
  40. package/dist/knob-contracts/collapsible-knobs.js +5 -1
  41. package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
  42. package/dist/knob-contracts/emoji-picker-knobs.js +3 -1
  43. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  44. package/dist/knob-contracts/field-knobs.js +17 -1
  45. package/dist/knob-contracts/infinite-canvas-knobs.d.ts +1 -1
  46. package/dist/knob-contracts/infinite-canvas-knobs.js +1 -7
  47. package/dist/knob-contracts/item-knobs.d.ts +1 -1
  48. package/dist/knob-contracts/item-knobs.js +5 -1
  49. package/dist/knob-contracts/kbd-knobs.d.ts +1 -1
  50. package/dist/knob-contracts/kbd-knobs.js +4 -1
  51. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  52. package/dist/knob-contracts/page-layout-knobs.js +1 -0
  53. package/dist/knob-contracts/pagination-knobs.d.ts +1 -1
  54. package/dist/knob-contracts/pagination-knobs.js +2 -1
  55. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  56. package/dist/knob-contracts/popup-knobs.js +9 -0
  57. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  58. package/dist/knob-contracts/range-knobs.js +2 -0
  59. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  60. package/dist/knob-contracts/sidebar-knobs.js +2 -0
  61. package/dist/knob-contracts/swap-knobs.d.ts +4 -0
  62. package/dist/knob-contracts/swap-knobs.js +9 -0
  63. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  64. package/dist/knob-contracts/tabs-knobs.js +3 -0
  65. package/dist/knob-contracts/timeline-knobs.d.ts +1 -1
  66. package/dist/knob-contracts/timeline-knobs.js +5 -1
  67. package/dist/knob-contracts/tool-approval-knobs.d.ts +4 -0
  68. package/dist/knob-contracts/tool-approval-knobs.js +32 -0
  69. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  70. package/dist/knob-contracts/tree-knobs.js +3 -1
  71. package/dist/lib/infinite-canvas-camera-store.d.ts +65 -0
  72. package/dist/lib/infinite-canvas-camera-store.js +233 -0
  73. package/dist/lib/infinite-canvas-camera.d.ts +33 -0
  74. package/dist/lib/infinite-canvas-camera.js +98 -0
  75. package/dist/lib/infinite-canvas-navigation.d.ts +19 -0
  76. package/dist/lib/infinite-canvas-navigation.js +395 -0
  77. package/dist/markdown-editor/bubble-menu.d.ts +6 -0
  78. package/dist/markdown-editor/bubble-menu.js +45 -0
  79. package/dist/markdown-editor/toolbar.d.ts +17 -1
  80. package/dist/markdown-editor/toolbar.js +103 -81
  81. package/dist/skin.d.ts +2 -0
  82. package/dist/styles/index.css +11 -10
  83. package/dist/styles/recipes/activity.css +12 -0
  84. package/dist/styles/recipes/avatar.css +1 -1
  85. package/dist/styles/recipes/badge.css +17 -0
  86. package/dist/styles/recipes/button-group.css +12 -6
  87. package/dist/styles/recipes/button.css +20 -3
  88. package/dist/styles/recipes/calendar.css +82 -6
  89. package/dist/styles/recipes/card.css +40 -5
  90. package/dist/styles/recipes/chat-message-edit.css +6 -21
  91. package/dist/styles/recipes/choice.css +9 -2
  92. package/dist/styles/recipes/collapsible.css +8 -1
  93. package/dist/styles/recipes/color-picker-values.css +4 -0
  94. package/dist/styles/recipes/emoji-picker.css +29 -8
  95. package/dist/styles/recipes/field-parts.css +80 -5
  96. package/dist/styles/recipes/field.css +113 -40
  97. package/dist/styles/recipes/infinite-canvas.css +17 -57
  98. package/dist/styles/recipes/item.css +33 -5
  99. package/dist/styles/recipes/kbd.css +26 -6
  100. package/dist/styles/recipes/markdown-editor.css +12 -0
  101. package/dist/styles/recipes/morphing-panel.css +1 -1
  102. package/dist/styles/recipes/page-layout.css +18 -2
  103. package/dist/styles/recipes/pagination.css +8 -0
  104. package/dist/styles/recipes/popup-controls.css +9 -0
  105. package/dist/styles/recipes/popup-drawer.css +1 -0
  106. package/dist/styles/recipes/popup-lists.css +55 -6
  107. package/dist/styles/recipes/popup-motion.css +7 -1
  108. package/dist/styles/recipes/popup.css +10 -0
  109. package/dist/styles/recipes/range.css +16 -2
  110. package/dist/styles/recipes/sidebar-mobile.css +1 -1
  111. package/dist/styles/recipes/sidebar-parts.css +17 -1
  112. package/dist/styles/recipes/sidebar.css +11 -0
  113. package/dist/styles/recipes/swap.css +143 -0
  114. package/dist/styles/recipes/tabs.css +41 -8
  115. package/dist/styles/recipes/timeline.css +36 -2
  116. package/dist/styles/recipes/tool-approval.css +267 -0
  117. package/dist/styles/recipes/track-highlight.css +19 -6
  118. package/dist/styles/recipes/tree.css +33 -0
  119. package/dist/styles/theme.css +17 -6
  120. package/dist/tool-approval.d.ts +49 -0
  121. package/dist/tool-approval.js +260 -0
  122. package/dist/ui/alert-dialog.js +1 -1
  123. package/dist/ui/badge.js +1 -1
  124. package/dist/ui/calendar.js +9 -4
  125. package/dist/ui/card.d.ts +3 -1
  126. package/dist/ui/card.js +2 -1
  127. package/dist/ui/code.js +13 -3
  128. package/dist/ui/command.js +1 -0
  129. package/dist/ui/dialog.js +1 -1
  130. package/dist/ui/dropdown-menu.d.ts +1 -1
  131. package/dist/ui/dropdown-menu.js +23 -6
  132. package/dist/ui/dropzone.js +10 -11
  133. package/dist/ui/field.d.ts +6 -2
  134. package/dist/ui/field.js +4 -2
  135. package/dist/ui/infinite-canvas.d.ts +19 -22
  136. package/dist/ui/infinite-canvas.js +264 -325
  137. package/dist/ui/morphing-panel.d.ts +5 -1
  138. package/dist/ui/morphing-panel.js +4 -2
  139. package/dist/ui/rich-tooltip.d.ts +2 -1
  140. package/dist/ui/rich-tooltip.js +32 -29
  141. package/dist/ui/select.js +2 -2
  142. package/dist/ui/sidebar-mobile.js +1 -1
  143. package/dist/ui/sidebar-provider.d.ts +3 -1
  144. package/dist/ui/sidebar-provider.js +3 -2
  145. package/dist/ui/sidebar-resize-rail.js +1 -1
  146. package/dist/ui/sidebar.d.ts +6 -5
  147. package/dist/ui/sidebar.js +10 -9
  148. package/dist/ui/swap.d.ts +14 -0
  149. package/dist/ui/swap.js +69 -0
  150. package/dist/ui/tabs.d.ts +1 -1
  151. package/dist/ui/tabs.js +2 -13
  152. package/dist/ui/timeline.d.ts +1 -1
  153. package/dist/ui/timeline.js +2 -2
  154. package/dist/ui/typography.d.ts +11 -5
  155. package/dist/ui/typography.js +18 -6
  156. package/dist/user-ask.js +1 -1
  157. package/lint/biome-preset.json +3 -0
  158. package/lint/class-policy.mjs +17 -0
  159. package/lint/eslint-plugin.mjs +87 -0
  160. package/lint/grit-rules.mjs +40 -0
  161. package/lint/no-component-style-classes.grit +16 -0
  162. package/lint/no-raw-type-classes.grit +3 -4
  163. package/lint/write-grit-rules.mjs +19 -0
  164. package/package.json +12 -3
  165. package/dist/styles/type-scale.css +0 -72
@@ -0,0 +1,87 @@
1
+ import { componentStyleClass, componentStyleMessage, controlUiImportPattern, rawTypeClass, rawTypeMessage } from "./class-policy.mjs";
2
+
3
+ const componentStyle = new RegExp(componentStyleClass);
4
+ const rawType = new RegExp(rawTypeClass);
5
+
6
+ function stringsIn(node) {
7
+ if (Array.isArray(node)) return node.flatMap(stringsIn);
8
+ if (!node || typeof node !== "object") return [];
9
+ if (node.type === "Literal") return typeof node.value === "string" ? [node.value] : [];
10
+ if (node.type === "TemplateElement") return [node.value.cooked ?? node.value.raw];
11
+ return Object.entries(node).flatMap(([key, child]) => (key === "parent" ? [] : stringsIn(child)));
12
+ }
13
+
14
+ function classNameMatches(attribute, pattern) {
15
+ if (attribute.name.name !== "className" || !attribute.value) return false;
16
+ return stringsIn(attribute.value).some((text) => pattern.test(text));
17
+ }
18
+
19
+ function elementRootName(name) {
20
+ if (name.type === "JSXIdentifier") return name.name;
21
+ if (name.type === "JSXMemberExpression") return elementRootName(name.object);
22
+ return undefined;
23
+ }
24
+
25
+ function controlUiLocalNames(program, controlUiImport) {
26
+ const names = new Set();
27
+ for (const statement of program.body) {
28
+ if (statement.type !== "ImportDeclaration" || statement.importKind === "type") continue;
29
+ if (!controlUiImport.test(statement.source.value)) continue;
30
+ for (const specifier of statement.specifiers) names.add(specifier.local.name);
31
+ }
32
+ return names;
33
+ }
34
+
35
+ const noComponentStyleClasses = {
36
+ meta: {
37
+ type: "problem",
38
+ docs: { description: "Keep className on Control UI components to layout; the component owns its look." },
39
+ messages: { componentStyle: componentStyleMessage },
40
+ schema: [
41
+ {
42
+ type: "object",
43
+ properties: { importSources: { type: "array", items: { type: "string" } } },
44
+ additionalProperties: false,
45
+ },
46
+ ],
47
+ },
48
+ create(context) {
49
+ const controlUiImport = new RegExp(`^${controlUiImportPattern(context.options[0]?.importSources)}`);
50
+ let controlUiNames = new Set();
51
+ return {
52
+ Program(program) {
53
+ controlUiNames = controlUiLocalNames(program, controlUiImport);
54
+ },
55
+ JSXAttribute(attribute) {
56
+ if (!controlUiNames.has(elementRootName(attribute.parent.name))) return;
57
+ if (classNameMatches(attribute, componentStyle)) context.report({ node: attribute, messageId: "componentStyle" });
58
+ },
59
+ };
60
+ },
61
+ };
62
+
63
+ const noRawTypeClasses = {
64
+ meta: {
65
+ type: "problem",
66
+ docs: { description: "Size text through the Control UI type scale instead of text-* classes." },
67
+ messages: { rawType: rawTypeMessage },
68
+ schema: [],
69
+ },
70
+ create(context) {
71
+ return {
72
+ JSXAttribute(attribute) {
73
+ if (classNameMatches(attribute, rawType)) context.report({ node: attribute, messageId: "rawType" });
74
+ },
75
+ };
76
+ },
77
+ };
78
+
79
+ const plugin = {
80
+ meta: { name: "control-ui" },
81
+ rules: {
82
+ "no-component-style-classes": noComponentStyleClasses,
83
+ "no-raw-type-classes": noRawTypeClasses,
84
+ },
85
+ };
86
+
87
+ export default plugin;
@@ -0,0 +1,40 @@
1
+ import { componentStyleClass, componentStyleMessage, controlUiImportPattern, rawTypeClass, rawTypeMessage } from "./class-policy.mjs";
2
+
3
+ function gritRegex(pattern) {
4
+ return `r"(?s).*(?:${pattern.replaceAll('"', '\\"')}).*"`;
5
+ }
6
+
7
+ function gritString(text) {
8
+ return JSON.stringify(text);
9
+ }
10
+
11
+ const stringTexts = "or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() }";
12
+
13
+ export function gritRules(importSources = []) {
14
+ return {
15
+ "no-component-style-classes.grit": `language js(typescript, jsx)
16
+
17
+ or {
18
+ JsxSelfClosingElement(name=$name, attributes=$attributes),
19
+ JsxOpeningElement(name=$name, attributes=$attributes)
20
+ } as $element where {
21
+ $name <: r"([A-Z]\\w*)(?:\\.[\\w.]+)?"($component),
22
+ $attributes <: some JsxAttribute(name=\`className\`) as $attr,
23
+ $attr <: contains ${stringTexts} as $text where { $text <: ${gritRegex(componentStyleClass)} },
24
+ $element <: within JsModule() as $module,
25
+ $module <: contains JsImport() as $import where {
26
+ $import <: ${gritRegex(String.raw`from\s+['"]${controlUiImportPattern(importSources)}`)},
27
+ $import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
28
+ },
29
+ register_diagnostic(span=$attr, message=${gritString(componentStyleMessage)}, severity="error")
30
+ }
31
+ `,
32
+ "no-raw-type-classes.grit": `language js(typescript, jsx)
33
+
34
+ JsxAttribute(name=\`className\`) as $attr where {
35
+ $attr <: contains ${stringTexts} as $text where { $text <: ${gritRegex(rawTypeClass)} },
36
+ register_diagnostic(span=$attr, message=${gritString(rawTypeMessage)}, severity="error")
37
+ }
38
+ `,
39
+ };
40
+ }
@@ -0,0 +1,16 @@
1
+ language js(typescript, jsx)
2
+
3
+ or {
4
+ JsxSelfClosingElement(name=$name, attributes=$attributes),
5
+ JsxOpeningElement(name=$name, attributes=$attributes)
6
+ } as $element where {
7
+ $name <: r"([A-Z]\w*)(?:\.[\w.]+)?"($component),
8
+ $attributes <: some JsxAttribute(name=`className`) as $attr,
9
+ $attr <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() } as $text where { $text <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:foreground|muted|primary|secondary|accent|destructive|success|warning|info|card|popover|sidebar|background|canvas|current|inherit|transparent|white|black|[a-z]+-\d{2,3}|\[(?:#|oklch|rgb|hsl|lab|lch|color|var))|font-|leading-|tracking-|border|divide-|rounded|shadow|ring\b|ring-|outline|opacity-|backdrop-|fill-|stroke-|decoration-|underline|line-through|italic|uppercase|lowercase|capitalize|tabular-nums|(?:hover|focus|focus-visible|focus-within|active|disabled|open|checked|dark|placeholder|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)).*" },
10
+ $element <: within JsModule() as $module,
11
+ $module <: contains JsImport() as $import where {
12
+ $import <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
13
+ $import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
14
+ },
15
+ register_diagnostic(span=$attr, message="Control UI component styled through className: color, type, padding, borders, radius, shadows, state styles and !important belong to the component. Use its props or a --cui-* knob; keep className for layout (flex, gap, width, margin, placement).", severity="error")
16
+ }
@@ -1,7 +1,6 @@
1
1
  language js(typescript, jsx)
2
2
 
3
- JsxAttribute(name=$name, initializer=$value) as $attr where {
4
- $name <: `className`,
5
- $value <: contains r".*\btext-(?:micro|caption|label|body|body-lg|heading-[1-4]|display)\b.*",
6
- register_diagnostic(span=$attr, message="Type rung in className: render text through <Text>/<Heading>, or size the control with its size prop or --cui-*-font-size knob.", severity="warn")
3
+ JsxAttribute(name=`className`) as $attr where {
4
+ $attr <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() } as $text where { $text <: r"(?s).*(?:(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b|(?:^|[^\w-])text-\[\d[^\]]*\]).*" },
5
+ register_diagnostic(span=$attr, message="Text size in className: render text through <Text>/<Heading> on the type scale, or size the control with its size prop or --cui-*-font-size knob.", severity="error")
7
6
  }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ import { gritRules } from "./grit-rules.mjs";
6
+
7
+ const { values } = parseArgs({
8
+ options: {
9
+ "import-source": { type: "string", multiple: true, default: [] },
10
+ out: { type: "string" },
11
+ },
12
+ });
13
+
14
+ if (!values.out) throw new Error("control-ui-grit needs --out <directory>");
15
+
16
+ mkdirSync(values.out, { recursive: true });
17
+ for (const [file, source] of Object.entries(gritRules(values["import-source"]))) {
18
+ writeFileSync(path.join(values.out, file), source);
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl-ui/react",
3
- "version": "0.9.5",
3
+ "version": "0.9.8",
4
4
  "description": "100+ React components for Tailwind CSS and shadcn/ui: pickers, trees, editors, canvases, app templates, and AI chat surfaces, with neutral defaults and consumer-owned themes.",
5
5
  "keywords": [
6
6
  "react",
@@ -28,12 +28,17 @@
28
28
  },
29
29
  "homepage": "https://control-ui.dev",
30
30
  "type": "module",
31
+ "bin": {
32
+ "control-ui-grit": "./lint/write-grit-rules.mjs"
33
+ },
31
34
  "sideEffects": [
32
35
  "**/*.css"
33
36
  ],
34
37
  "files": [
35
38
  "dist",
36
- "lint/*.grit"
39
+ "lint/*.grit",
40
+ "lint/*.mjs",
41
+ "lint/biome-preset.json"
37
42
  ],
38
43
  "publishConfig": {
39
44
  "access": "public"
@@ -41,6 +46,8 @@
41
46
  "exports": {
42
47
  "./package.json": "./package.json",
43
48
  "./styles/*": "./dist/styles/*",
49
+ "./eslint-plugin": "./lint/eslint-plugin.mjs",
50
+ "./biome": "./lint/biome-preset.json",
44
51
  "./*": {
45
52
  "types": "./dist/*.d.ts",
46
53
  "default": "./dist/*.js"
@@ -53,7 +60,7 @@
53
60
  "lint": "biome lint ."
54
61
  },
55
62
  "peerDependencies": {
56
- "@base-ui/react": "^1.8.0",
63
+ "@base-ui/react": "^1.9.0",
57
64
  "@tanstack/charts": "^1.1.0",
58
65
  "@tanstack/react-virtual": "^3.14.13",
59
66
  "@tiptap/core": "3.31.4",
@@ -182,6 +189,8 @@
182
189
  "devDependencies": {
183
190
  "@types/bun": "^1.4.2",
184
191
  "@types/react": "^19.3.0",
192
+ "eslint": "10.10.0",
193
+ "oxlint": "1.81.0",
185
194
  "tsdown": "0.23.0"
186
195
  }
187
196
  }
@@ -1,72 +0,0 @@
1
- /*
2
- * Type scale, canonical excerpt. Tokens live in the installed theme; this is the docs-facing font-size-only slice.
3
- * ONE rung per size by ROLE — only source of font-size; never text-[Npx] or Tailwind's text-xs/sm defaults. Paired --line-height/--font-weight ride along per rung.
4
- */
5
- @theme {
6
- /* Body/UI family: 10-16px band, ~95% of interface text; line-height stays a utility (leading-*) for per-surface density. */
7
- --text-micro: 0.625rem; /* 10px — kbd, badge counters, dense numeric meta */
8
- --text-caption: 0.6875rem; /* 11px — overlines, timestamps, section / group labels */
9
- --text-label: 0.75rem; /* 12px — form labels + small chrome (nav item, tooltip, shortcut) */
10
- --text-body: 0.875rem; /* 14px — DEFAULT body & control text (the workhorse) */
11
- --text-body-lg: 1rem; /* 16px — emphasized / larger body */
12
-
13
- /* Heading family: HIERARCHY, numbered (heading-1=largest); each pairs line-height+weight so `text-heading-N`
14
- carries all three, letting a skin retune headings by re-valuing these alone. */
15
- --text-heading-4: 0.9375rem; /* 15px */
16
- --text-heading-4--line-height: 1.4;
17
- --text-heading-4--font-weight: 600;
18
- --text-heading-3: 1.125rem; /* 18px */
19
- --text-heading-3--line-height: 1.35;
20
- --text-heading-3--font-weight: 600;
21
- --text-heading-2: 1.375rem; /* 22px */
22
- --text-heading-2--line-height: 1.25;
23
- --text-heading-2--font-weight: 600;
24
- --text-heading-1: 1.875rem; /* 30px — content h1 */
25
- --text-heading-1--line-height: 1.15;
26
- --text-heading-1--font-weight: 700;
27
- --text-heading-1--letter-spacing: -0.01em;
28
-
29
- /* display — the biggest rung, above the content h1: page titles (PageHeader) and heroes. */
30
- --text-display: 2.25rem; /* 36px */
31
- --text-display--line-height: 1.1;
32
- --text-display--font-weight: 600;
33
- --text-display--letter-spacing: -0.02em;
34
-
35
- /* Font ROLES: body+display default to --font-sans; brand re-values ONE token for headings(font-display)/body(font-body)
36
- own face. */
37
- --font-body: var(--font-sans);
38
- --font-display: var(--font-sans);
39
- }
40
-
41
- /*
42
- * Heading rungs also carry the display face and balanced wrapping (Tailwind merges this into the same `text-heading-N` class),
43
- * so a heading is ONE class: `text-heading-2`, never `text-heading-2 font-display text-balance font-semibold`.
44
- */
45
- @utility text-display {
46
- font-family: var(--font-display);
47
- text-wrap: balance;
48
- }
49
- @utility text-heading-1 {
50
- font-family: var(--font-display);
51
- text-wrap: balance;
52
- }
53
- @utility text-heading-2 {
54
- font-family: var(--font-display);
55
- text-wrap: balance;
56
- }
57
- @utility text-heading-3 {
58
- font-family: var(--font-display);
59
- text-wrap: balance;
60
- }
61
- @utility text-heading-4 {
62
- font-family: var(--font-display);
63
- text-wrap: balance;
64
- }
65
-
66
- /*
67
- * History: fixed 3 competing type systems (Tailwind defaults + named rungs + raw text-[Npx]) into one rung/role; 14px = body.
68
- * Usage: React text goes through <Text>/<Heading> (ui/typography): size, tone and weight are props, className stays layout-only.
69
- * A bare rung is for markup React doesn't render as components (MDX, markdown, email HTML), e.g. text-heading-1 on <h1>.
70
- * Heading LEVEL is the document outline; the RUNG is the look. They are decoupled on purpose: an <h3> may wear text-heading-2.
71
- * Control chrome text is sized by its recipe (`--cui-button-font-size`, `--cui-field-font-size`), not by these utilities.
72
- */