@ctrl-ui/react 0.9.6 → 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 (119) hide show
  1. package/README.md +38 -2
  2. package/dist/activity.d.ts +3 -1
  3. package/dist/activity.js +2 -1
  4. package/dist/blocks/agent-run-sidebar.js +11 -10
  5. package/dist/blocks/agent-run.d.ts +3 -1
  6. package/dist/blocks/agent-run.js +21 -13
  7. package/dist/blocks/coding-agent.js +50 -69
  8. package/dist/blocks/design-canvas-inspector.js +1 -1
  9. package/dist/blocks/design-canvas.js +41 -34
  10. package/dist/blocks/discussion.js +1 -1
  11. package/dist/blocks/file-explorer.js +66 -47
  12. package/dist/blocks/settings.js +37 -41
  13. package/dist/blocks/team-chat.js +2 -1
  14. package/dist/blocks/theme-toggle.d.ts +2 -1
  15. package/dist/blocks/theme-toggle.js +13 -4
  16. package/dist/hooks/use-mobile.d.ts +1 -1
  17. package/dist/hooks/use-mobile.js +1 -1
  18. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  19. package/dist/knob-contracts/badge-knobs.js +3 -1
  20. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  21. package/dist/knob-contracts/button-knobs.js +1 -0
  22. package/dist/knob-contracts/calendar-knobs.d.ts +1 -1
  23. package/dist/knob-contracts/calendar-knobs.js +9 -1
  24. package/dist/knob-contracts/card-knobs.d.ts +1 -1
  25. package/dist/knob-contracts/card-knobs.js +5 -1
  26. package/dist/knob-contracts/choice-knobs.d.ts +1 -1
  27. package/dist/knob-contracts/choice-knobs.js +1 -0
  28. package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
  29. package/dist/knob-contracts/collapsible-knobs.js +5 -1
  30. package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
  31. package/dist/knob-contracts/emoji-picker-knobs.js +3 -1
  32. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  33. package/dist/knob-contracts/field-knobs.js +17 -1
  34. package/dist/knob-contracts/item-knobs.d.ts +1 -1
  35. package/dist/knob-contracts/item-knobs.js +5 -1
  36. package/dist/knob-contracts/kbd-knobs.d.ts +1 -1
  37. package/dist/knob-contracts/kbd-knobs.js +4 -1
  38. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  39. package/dist/knob-contracts/page-layout-knobs.js +1 -0
  40. package/dist/knob-contracts/pagination-knobs.d.ts +1 -1
  41. package/dist/knob-contracts/pagination-knobs.js +2 -1
  42. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  43. package/dist/knob-contracts/popup-knobs.js +9 -0
  44. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  45. package/dist/knob-contracts/range-knobs.js +2 -0
  46. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  47. package/dist/knob-contracts/sidebar-knobs.js +2 -0
  48. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  49. package/dist/knob-contracts/tabs-knobs.js +3 -0
  50. package/dist/knob-contracts/timeline-knobs.d.ts +1 -1
  51. package/dist/knob-contracts/timeline-knobs.js +4 -1
  52. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  53. package/dist/knob-contracts/tree-knobs.js +3 -1
  54. package/dist/skin.d.ts +2 -0
  55. package/dist/styles/index.css +2 -3
  56. package/dist/styles/recipes/activity.css +4 -0
  57. package/dist/styles/recipes/avatar.css +1 -1
  58. package/dist/styles/recipes/badge.css +17 -0
  59. package/dist/styles/recipes/button.css +8 -1
  60. package/dist/styles/recipes/calendar.css +82 -6
  61. package/dist/styles/recipes/card.css +40 -5
  62. package/dist/styles/recipes/choice.css +9 -2
  63. package/dist/styles/recipes/collapsible.css +8 -1
  64. package/dist/styles/recipes/color-picker-values.css +4 -0
  65. package/dist/styles/recipes/emoji-picker.css +29 -8
  66. package/dist/styles/recipes/field-parts.css +80 -5
  67. package/dist/styles/recipes/field.css +113 -40
  68. package/dist/styles/recipes/item.css +33 -5
  69. package/dist/styles/recipes/kbd.css +26 -6
  70. package/dist/styles/recipes/morphing-panel.css +1 -1
  71. package/dist/styles/recipes/page-layout.css +18 -2
  72. package/dist/styles/recipes/pagination.css +8 -0
  73. package/dist/styles/recipes/popup-controls.css +9 -0
  74. package/dist/styles/recipes/popup-drawer.css +1 -0
  75. package/dist/styles/recipes/popup-lists.css +55 -6
  76. package/dist/styles/recipes/popup-motion.css +7 -1
  77. package/dist/styles/recipes/popup.css +10 -0
  78. package/dist/styles/recipes/range.css +16 -2
  79. package/dist/styles/recipes/sidebar-mobile.css +1 -1
  80. package/dist/styles/recipes/sidebar-parts.css +17 -1
  81. package/dist/styles/recipes/sidebar.css +11 -0
  82. package/dist/styles/recipes/tabs.css +41 -8
  83. package/dist/styles/recipes/timeline.css +25 -2
  84. package/dist/styles/recipes/tree.css +33 -0
  85. package/dist/styles/theme.css +11 -1
  86. package/dist/ui/alert-dialog.js +1 -1
  87. package/dist/ui/badge.js +1 -1
  88. package/dist/ui/calendar.js +9 -4
  89. package/dist/ui/card.d.ts +3 -1
  90. package/dist/ui/card.js +2 -1
  91. package/dist/ui/dialog.js +1 -1
  92. package/dist/ui/dropdown-menu.d.ts +1 -1
  93. package/dist/ui/dropdown-menu.js +23 -6
  94. package/dist/ui/dropzone.js +10 -11
  95. package/dist/ui/field.d.ts +6 -2
  96. package/dist/ui/field.js +4 -2
  97. package/dist/ui/morphing-panel.d.ts +5 -1
  98. package/dist/ui/morphing-panel.js +4 -2
  99. package/dist/ui/select.js +2 -2
  100. package/dist/ui/sidebar-mobile.js +1 -1
  101. package/dist/ui/sidebar-provider.d.ts +3 -1
  102. package/dist/ui/sidebar-provider.js +3 -2
  103. package/dist/ui/sidebar-resize-rail.js +1 -1
  104. package/dist/ui/sidebar.d.ts +6 -5
  105. package/dist/ui/sidebar.js +10 -9
  106. package/dist/ui/tabs.d.ts +1 -1
  107. package/dist/ui/tabs.js +2 -13
  108. package/dist/ui/timeline.js +1 -1
  109. package/dist/ui/typography.d.ts +11 -5
  110. package/dist/ui/typography.js +18 -6
  111. package/lint/biome-preset.json +3 -0
  112. package/lint/class-policy.mjs +17 -0
  113. package/lint/eslint-plugin.mjs +87 -0
  114. package/lint/grit-rules.mjs +40 -0
  115. package/lint/no-component-style-classes.grit +16 -0
  116. package/lint/no-raw-type-classes.grit +2 -3
  117. package/lint/write-grit-rules.mjs +19 -0
  118. package/package.json +11 -2
  119. package/dist/styles/type-scale.css +0 -72
@@ -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".*(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b.*|.*(?:^|[^\w-])text-\[\d[^\]]*\].*",
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[^\]]*\]).*" },
6
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.6",
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"
@@ -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 HTML React doesn't render as components (email HTML, injected markdown); MDX maps its tags to <Heading>/<Text>.
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
- */