@ctrl-ui/react 0.9.12 → 0.9.14

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 (51) hide show
  1. package/README.md +20 -7
  2. package/dist/blocks/design-canvas.js +16 -10
  3. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  4. package/dist/knob-contracts/field-knobs.js +1 -0
  5. package/dist/knob-contracts/gradient-editor-knobs.d.ts +1 -1
  6. package/dist/knob-contracts/gradient-editor-knobs.js +1 -4
  7. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  8. package/dist/knob-contracts/popup-knobs.js +2 -0
  9. package/dist/lib/phone-input-format.js +1 -1
  10. package/dist/styles/recipes/card.css +8 -0
  11. package/dist/styles/recipes/color-picker-values.css +4 -18
  12. package/dist/styles/recipes/color-picker.css +3 -4
  13. package/dist/styles/recipes/field-parts.css +8 -1
  14. package/dist/styles/recipes/gradient-editor.css +3 -47
  15. package/dist/styles/recipes/markdown.css +5 -1
  16. package/dist/styles/recipes/popup-lists.css +17 -0
  17. package/dist/styles/recipes/popup.css +3 -0
  18. package/dist/styles/theme.css +4 -0
  19. package/dist/ui/autocomplete.d.ts +3 -0
  20. package/dist/ui/autocomplete.js +17 -2
  21. package/dist/ui/chart-cartesian.d.ts +60 -57
  22. package/dist/ui/chart-cartesian.js +7 -7
  23. package/dist/ui/chart-colors.d.ts +4 -2
  24. package/dist/ui/chart-colors.js +6 -2
  25. package/dist/ui/chart-marks.d.ts +4 -4
  26. package/dist/ui/chart-marks.js +14 -5
  27. package/dist/ui/chart-series.d.ts +9 -6
  28. package/dist/ui/chart-series.js +3 -2
  29. package/dist/ui/color-picker-swatches.d.ts +4 -3
  30. package/dist/ui/color-picker-swatches.js +7 -8
  31. package/dist/ui/command.d.ts +12 -0
  32. package/dist/ui/command.js +31 -1
  33. package/dist/ui/field.js +1 -1
  34. package/dist/ui/gradient-editor.d.ts +3 -2
  35. package/dist/ui/gradient-editor.js +7 -8
  36. package/dist/ui/markdown-components.d.ts +27 -0
  37. package/dist/ui/markdown-components.js +57 -0
  38. package/dist/ui/markdown-elements.d.ts +6 -25
  39. package/dist/ui/markdown-elements.js +19 -53
  40. package/dist/ui/markdown.d.ts +2 -7
  41. package/dist/ui/markdown.js +3 -20
  42. package/dist/ui/phone-input.js +2 -2
  43. package/lint/biome-preset.json +8 -1
  44. package/lint/class-policy.mjs +50 -1
  45. package/lint/eslint-plugin.mjs +98 -49
  46. package/lint/grit-rules.mjs +131 -42
  47. package/lint/no-component-part-css.grit +16 -0
  48. package/lint/no-component-style-classes.grit +66 -36
  49. package/lint/no-component-style-props.grit +51 -0
  50. package/lint/no-raw-type-classes.grit +1 -1
  51. package/package.json +5 -1
package/README.md CHANGED
@@ -49,11 +49,15 @@ Controls size their own text: pick the control's `size`, or set its `--cui-*-fon
49
49
 
50
50
  ## Lint rules
51
51
 
52
- Two rules keep call sites honest, shipped for Biome, ESLint, and oxlint from one policy (`lint/class-policy.mjs`):
52
+ These rules keep call sites honest. They come from one policy (`lint/class-policy.mjs`) and ship for Biome, ESLint, and oxlint, except the stylesheet rule, which is Biome only:
53
53
 
54
- - `no-component-style-classes` errors when `className` on a component imported from `@ctrl-ui/react` (or a `components/control-ui` directory) sets what the component owns: colors, type, padding, borders, radius, shadows, state styles such as `hover:` and `data-[…]:`, or `!important`. Every `text-*` class counts except alignment and wrapping (`text-center`, `text-pretty`, `text-ellipsis`…). The rule also reads string constants the `className` references in the same file, and the `className` of the element passed to the component's `render` prop. Layout classes (`flex`, `gap-*`, widths, heights, margins, placement) stay allowed. Restyle through the component's props or its `--cui-*` knobs instead.
54
+ - `no-component-style-classes` errors when `className` on a component imported from `@ctrl-ui/react` (or a `components/control-ui` directory) sets what the component owns: colors, type, padding, borders, radius, shadows, filters, state styles such as `hover:` and `data-[…]:`, arbitrary properties that paint (`[background:…]`), or `!important`. Every `text-*` class counts except alignment and wrapping (`text-center`, `text-pretty`, `text-ellipsis`…). The rule also reads string constants the `className` references in the same file, and the `className` of the element passed to the component's `render` prop. On any other element, it errors on classes that reach into a component's parts, such as `[&_[data-slot=trigger]]:` or `*:data-[slot=icon]:`. Layout classes (`flex`, `gap-*`, widths, heights, margins, placement) stay allowed. Restyle through the component's props or its `--cui-*` knobs instead.
55
+ - `no-component-style-props` applies the same boundary to `style`: `--cui-*` knobs and layout keys pass, while paint keys error (`color`, `background`, `font*`, `padding*`, `border*`, `boxShadow`, `opacity`, `filter`…). It also checks same-file constant objects and the element passed to `render`. When the paint is data, such as a user-picked fill, put it on a plain element of your own and let the component inherit it.
56
+ - `no-component-part-css` errors when a stylesheet paints a component part: a rule whose subject is `[data-slot]`, `[data-control-family]`, or another `[data-control…]` attribute, including nested `&` rules and `@apply`. Setting the part's `--cui-*` knobs and layout there stays allowed. The preset skips installed Control UI source under `components/control-ui`.
55
57
  - `no-raw-type-classes` errors on text sizes in any `className`: the type rungs, Tailwind's `text-xs`…`text-9xl`, and arbitrary `text-[13px]`. It reaches beyond Control UI, so adopt it folder by folder while you migrate.
56
58
 
59
+ The Biome rules need Biome 2.5.0 or later: the preset scopes the stylesheet rule with plugin `includes`.
60
+
57
61
  Biome, extending the shipped preset so rules added later arrive with the package update:
58
62
 
59
63
  ```json
@@ -68,22 +72,31 @@ ESLint flat config:
68
72
  ```js
69
73
  import controlUi from "@ctrl-ui/react/eslint-plugin";
70
74
 
71
- export default [{ plugins: { "control-ui": controlUi }, rules: { "control-ui/no-component-style-classes": "error" } }];
75
+ export default [
76
+ {
77
+ plugins: { "control-ui": controlUi },
78
+ rules: { "control-ui/no-component-style-classes": "error", "control-ui/no-component-style-props": "error" },
79
+ },
80
+ ];
72
81
  ```
73
82
 
74
83
  oxlint, through its JS plugin support:
75
84
 
76
85
  ```json
77
- { "jsPlugins": ["@ctrl-ui/react/eslint-plugin"], "rules": { "control-ui/no-component-style-classes": "error" } }
86
+ {
87
+ "jsPlugins": ["@ctrl-ui/react/eslint-plugin"],
88
+ "rules": { "control-ui/no-component-style-classes": "error", "control-ui/no-component-style-props": "error" }
89
+ }
78
90
  ```
79
91
 
80
- When your app imports components through its own wrapper package or folder, declare that path so the rule follows those imports too:
92
+ When your app imports components through its own wrapper package or folder, declare that path so the rules follow those imports too:
81
93
 
82
94
  ```js
83
- rules: { "control-ui/no-component-style-classes": ["error", { importSources: ["@acme/ui/components/"] }] }
95
+ const options = { importSources: ["@acme/ui/components/"] };
96
+ rules: { "control-ui/no-component-style-classes": ["error", options], "control-ui/no-component-style-props": ["error", options] }
84
97
  ```
85
98
 
86
- Biome's Grit plugins take no options, so the package ships `control-ui-grit` to write the rules with your paths. Run it on install so a package update regenerates them, gitignore `.control-ui`, and list `./.control-ui/no-component-style-classes.grit` under `plugins` in place of the preset:
99
+ Biome's Grit plugins take no options, so the package ships `control-ui-grit` to write the rules with your paths. Run it on install so a package update regenerates them, gitignore `.control-ui`, and list the generated `no-component-style-classes.grit`, `no-component-style-props.grit`, and `no-component-part-css.grit` from `./.control-ui/` under `plugins` in place of the preset:
87
100
 
88
101
  ```json
89
102
  { "scripts": { "postinstall": "control-ui-grit --import-source=@acme/ui/components/ --out=.control-ui" } }
@@ -412,8 +412,7 @@ function DesignCanvasLayerNode({ layer, selected, onSelect, onKeyDown, onLayerCh
412
412
  style: {
413
413
  width: layer.width,
414
414
  height: layer.height,
415
- rotate: `${layer.rotation}deg`,
416
- opacity: layer.opacity / 100
415
+ rotate: `${layer.rotation}deg`
417
416
  },
418
417
  onPointerDown: onSelect,
419
418
  onKeyDown,
@@ -429,18 +428,25 @@ function DesignCanvasLayerNode({ layer, selected, onSelect, onKeyDown, onLayerCh
429
428
  className: "absolute start-0 bottom-full mb-1 whitespace-nowrap",
430
429
  children: layer.name
431
430
  }) : null,
432
- layer.kind === "text" ? /* @__PURE__ */ jsx(Text, {
433
- as: "p",
434
- size: "heading-2",
435
- weight: "normal",
436
- className: "size-full overflow-hidden whitespace-nowrap",
437
- style: { color: fillColor },
438
- children: layer.text
431
+ layer.kind === "text" ? /* @__PURE__ */ jsx("div", {
432
+ className: "size-full",
433
+ style: {
434
+ color: fillColor,
435
+ opacity: layer.opacity / 100
436
+ },
437
+ children: /* @__PURE__ */ jsx(Text, {
438
+ as: "p",
439
+ size: "heading-2",
440
+ weight: "normal",
441
+ className: "size-full overflow-hidden whitespace-nowrap",
442
+ children: layer.text
443
+ })
439
444
  }) : /* @__PURE__ */ jsx("div", {
440
445
  className: "size-full",
441
446
  style: {
442
447
  background: fillColor,
443
- borderRadius: layer.kind === "ellipse" ? "50%" : layer.cornerRadius
448
+ borderRadius: layer.kind === "ellipse" ? "50%" : layer.cornerRadius,
449
+ opacity: layer.opacity / 100
444
450
  }
445
451
  }),
446
452
  selected ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/field-knobs.d.ts
2
- export declare const fieldKnobs: readonly ["--cui-field-label-radius", "--cui-field-label-background", "--cui-field-label-hover-background", "--cui-field-label-checked-background", "--cui-field-label-border-color", "--cui-field-label-checked-border-color", "--cui-field-label-border-width", "--cui-field-label-padding-block", "--cui-field-label-padding-inline", "--cui-field-legend-font-size", "--cui-field-legend-font-weight", "--cui-field-radius", "--cui-field-background", "--cui-field-hover-background", "--cui-field-affordance-background", "--cui-field-affordance-shadow", "--cui-field-affordance-foreground", "--cui-field-affordance-hover-background", "--cui-field-foreground", "--cui-field-border-color", "--cui-field-border-width", "--cui-field-shadow", "--cui-field-backdrop-filter", "--cui-field-height", "--cui-field-padding-inline", "--cui-field-font-size", "--cui-field-font-weight", "--cui-field-font-family", "--cui-field-group-radius", "--cui-field-group-background", "--cui-field-group-border-color", "--cui-field-group-border-width", "--cui-field-group-shadow", "--cui-field-group-padding-block", "--cui-field-group-padding-inline", "--cui-field-group-separator-color", "--cui-field-group-separator-inset", "--cui-field-label-font-size", "--cui-field-label-font-weight", "--cui-field-description-font-size"];
2
+ export declare const fieldKnobs: readonly ["--cui-field-label-radius", "--cui-field-label-background", "--cui-field-label-hover-background", "--cui-field-label-checked-background", "--cui-field-label-border-color", "--cui-field-label-checked-border-color", "--cui-field-label-border-width", "--cui-field-label-padding-block", "--cui-field-label-padding-inline", "--cui-field-legend-foreground", "--cui-field-legend-font-size", "--cui-field-legend-font-weight", "--cui-field-radius", "--cui-field-background", "--cui-field-hover-background", "--cui-field-affordance-background", "--cui-field-affordance-shadow", "--cui-field-affordance-foreground", "--cui-field-affordance-hover-background", "--cui-field-foreground", "--cui-field-border-color", "--cui-field-border-width", "--cui-field-shadow", "--cui-field-backdrop-filter", "--cui-field-height", "--cui-field-padding-inline", "--cui-field-font-size", "--cui-field-font-weight", "--cui-field-font-family", "--cui-field-group-radius", "--cui-field-group-background", "--cui-field-group-border-color", "--cui-field-group-border-width", "--cui-field-group-shadow", "--cui-field-group-padding-block", "--cui-field-group-padding-inline", "--cui-field-group-separator-color", "--cui-field-group-separator-inset", "--cui-field-label-font-size", "--cui-field-label-font-weight", "--cui-field-description-font-size"];
3
3
  export type FieldKnobStyle = Partial<Record<(typeof fieldKnobs)[number], string>>;
4
4
  //#endregion
@@ -9,6 +9,7 @@ const fieldKnobs = [
9
9
  "--cui-field-label-border-width",
10
10
  "--cui-field-label-padding-block",
11
11
  "--cui-field-label-padding-inline",
12
+ "--cui-field-legend-foreground",
12
13
  "--cui-field-legend-font-size",
13
14
  "--cui-field-legend-font-weight",
14
15
  "--cui-field-radius",
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/gradient-editor-knobs.d.ts
2
- export declare const gradientEditorKnobs: readonly ["--cui-gradient-editor-preview-radius", "--cui-gradient-editor-preview-ring-color", "--cui-gradient-editor-track-radius", "--cui-gradient-editor-stop-radius", "--cui-gradient-editor-stop-border-color", "--cui-gradient-editor-stop-border-width", "--cui-gradient-editor-stop-shadow", "--cui-gradient-editor-add-radius", "--cui-gradient-editor-add-border-color", "--cui-gradient-editor-add-border-width"];
2
+ export declare const gradientEditorKnobs: readonly ["--cui-gradient-editor-preview-radius", "--cui-gradient-editor-preview-ring-color", "--cui-gradient-editor-track-radius", "--cui-gradient-editor-stop-radius", "--cui-gradient-editor-stop-border-color", "--cui-gradient-editor-stop-border-width", "--cui-gradient-editor-stop-shadow"];
3
3
  export type GradientEditorKnobStyle = Partial<Record<(typeof gradientEditorKnobs)[number], string>>;
4
4
  //#endregion
@@ -6,10 +6,7 @@ const gradientEditorKnobs = [
6
6
  "--cui-gradient-editor-stop-radius",
7
7
  "--cui-gradient-editor-stop-border-color",
8
8
  "--cui-gradient-editor-stop-border-width",
9
- "--cui-gradient-editor-stop-shadow",
10
- "--cui-gradient-editor-add-radius",
11
- "--cui-gradient-editor-add-border-color",
12
- "--cui-gradient-editor-add-border-width"
9
+ "--cui-gradient-editor-stop-shadow"
13
10
  ];
14
11
  //#endregion
15
12
  export { gradientEditorKnobs };
@@ -1,4 +1,4 @@
1
1
  //#region src/knob-contracts/popup-knobs.d.ts
2
- export declare const popupKnobs: readonly ["--cui-popup-title-foreground", "--cui-popup-title-font-size", "--cui-popup-title-font-weight", "--cui-popup-title-line-height", "--cui-popup-title-letter-spacing", "--cui-popup-description-foreground", "--cui-popup-description-font-size", "--cui-popup-close-inset", "--cui-popup-item-inset", "--cui-popup-label-padding-block", "--cui-popup-label-padding-inline", "--cui-popup-label-font-size", "--cui-popup-label-font-weight", "--cui-popup-label-letter-spacing", "--cui-popup-label-text-transform", "--cui-popup-separator-inset", "--cui-popup-shortcut-font-size", "--cui-popup-shortcut-letter-spacing", "--cui-popup-separator-gap", "--cui-popup-submenu-indicator-foreground", "--cui-popup-command-heading-padding-block", "--cui-popup-command-heading-padding-inline", "--cui-popup-command-heading-font-size", "--cui-popup-command-heading-letter-spacing", "--cui-popup-command-heading-text-transform", "--cui-popup-dialog-max-inline-size", "--cui-popup-shortcut-foreground", "--cui-popup-tooltip-arrow-radius", "--cui-popup-status-success-foreground", "--cui-popup-status-warning-foreground", "--cui-popup-status-info-foreground", "--cui-popup-status-error-foreground", "--cui-popup-radius", "--cui-popup-background", "--cui-popup-foreground", "--cui-popup-border-color", "--cui-popup-border-width", "--cui-popup-shadow", "--cui-popup-backdrop-filter", "--cui-popup-backdrop-background", "--cui-popup-backdrop-blur", "--cui-popup-item-radius", "--cui-popup-item-font-size", "--cui-popup-item-icon-size", "--cui-popup-item-padding-block", "--cui-popup-item-padding-inline", "--cui-popup-item-min-height", "--cui-popup-item-highlight-shadow", "--cui-popup-item-foreground", "--cui-popup-item-highlight-background", "--cui-popup-item-highlight-foreground", "--cui-popup-item-highlight-muted-foreground", "--cui-popup-item-disabled-opacity", "--cui-popup-separator-color"];
2
+ export declare const popupKnobs: readonly ["--cui-popup-title-foreground", "--cui-popup-title-font-size", "--cui-popup-title-font-weight", "--cui-popup-title-line-height", "--cui-popup-title-letter-spacing", "--cui-popup-description-foreground", "--cui-popup-description-font-size", "--cui-popup-close-inset", "--cui-popup-item-inset", "--cui-popup-label-padding-block", "--cui-popup-label-padding-inline", "--cui-popup-label-font-size", "--cui-popup-label-font-weight", "--cui-popup-label-letter-spacing", "--cui-popup-label-text-transform", "--cui-popup-separator-inset", "--cui-popup-shortcut-font-size", "--cui-popup-shortcut-letter-spacing", "--cui-popup-separator-gap", "--cui-popup-submenu-indicator-foreground", "--cui-popup-command-heading-padding-block", "--cui-popup-command-heading-padding-inline", "--cui-popup-command-heading-font-size", "--cui-popup-command-heading-letter-spacing", "--cui-popup-command-heading-text-transform", "--cui-popup-dialog-max-inline-size", "--cui-popup-item-description-foreground", "--cui-popup-item-description-font-size", "--cui-popup-shortcut-foreground", "--cui-popup-tooltip-arrow-radius", "--cui-popup-status-success-foreground", "--cui-popup-status-warning-foreground", "--cui-popup-status-info-foreground", "--cui-popup-status-error-foreground", "--cui-popup-radius", "--cui-popup-background", "--cui-popup-foreground", "--cui-popup-border-color", "--cui-popup-border-width", "--cui-popup-shadow", "--cui-popup-backdrop-filter", "--cui-popup-backdrop-background", "--cui-popup-backdrop-blur", "--cui-popup-item-radius", "--cui-popup-item-font-size", "--cui-popup-item-icon-size", "--cui-popup-item-padding-block", "--cui-popup-item-padding-inline", "--cui-popup-item-min-height", "--cui-popup-item-highlight-shadow", "--cui-popup-item-foreground", "--cui-popup-item-highlight-background", "--cui-popup-item-highlight-foreground", "--cui-popup-item-highlight-muted-foreground", "--cui-popup-item-disabled-opacity", "--cui-popup-separator-color"];
3
3
  export type PopupKnobStyle = Partial<Record<(typeof popupKnobs)[number], string>>;
4
4
  //#endregion
@@ -26,6 +26,8 @@ const popupKnobs = [
26
26
  "--cui-popup-command-heading-letter-spacing",
27
27
  "--cui-popup-command-heading-text-transform",
28
28
  "--cui-popup-dialog-max-inline-size",
29
+ "--cui-popup-item-description-foreground",
30
+ "--cui-popup-item-description-font-size",
29
31
  "--cui-popup-shortcut-foreground",
30
32
  "--cui-popup-tooltip-arrow-radius",
31
33
  "--cui-popup-status-success-foreground",
@@ -1,5 +1,5 @@
1
- import { getCountryCallingCode } from "react-phone-number-input/max";
2
1
  import { AsYouType } from "libphonenumber-js/max";
2
+ import { getCountryCallingCode } from "react-phone-number-input/max";
3
3
  //#region src/lib/phone-input-format.ts
4
4
  function normalizePhoneInputText(value, country) {
5
5
  if (!country) return value;
@@ -63,6 +63,14 @@
63
63
  border-radius: var(--cui-card-radius);
64
64
  }
65
65
 
66
+ :where(
67
+ [data-control-family="card"][data-slot="root"]:has([data-control-family="card"][data-slot="link"])
68
+ :is(a[href], button, input, select, textarea, [role="checkbox"]):not([data-control-family="card"][data-slot="link"])
69
+ ) {
70
+ position: relative;
71
+ z-index: 1;
72
+ }
73
+
66
74
  :where([data-control-family="card"][data-slot="root"][data-size="sm"]) {
67
75
  --cui-card-padding-block: calc(var(--spacing) * 3);
68
76
  --cui-card-padding-inline: calc(var(--spacing) * 4);
@@ -53,23 +53,9 @@
53
53
  font-size: var(--text-caption);
54
54
  }
55
55
 
56
- :where([data-control-family="color-picker"][data-slot="swatch-add"]) {
57
- border-radius: var(--radius-md);
58
- border-width: var(--control-rim-width);
59
- border-style: dashed;
60
- --_color-picker-swatch-add-border-color: var(--border);
61
- border-color: var(--_color-picker-swatch-add-border-color);
62
- --_color-picker-swatch-add-foreground: var(--muted-foreground);
63
- color: var(--_color-picker-swatch-add-foreground);
64
- }
65
-
66
- @media (hover: hover) {
67
- :where([data-control-family="color-picker"][data-slot="swatch-add"]:hover:not(:disabled)) {
68
- --_color-picker-swatch-add-border-color: oklch(from var(--foreground) l c h / 0.4);
69
- border-color: var(--_color-picker-swatch-add-border-color);
70
- --_color-picker-swatch-add-foreground: var(--foreground);
71
- color: var(--_color-picker-swatch-add-foreground);
72
- }
56
+ :where([data-control-family="button"][data-color-picker-swatch-add="true"]) {
57
+ --nest-radius: var(--radius-md);
58
+ --cui-button-height: calc(var(--spacing) * 6);
73
59
  }
74
60
 
75
61
  :where([data-control-family="color-picker"][data-slot="swatch"]) {
@@ -91,7 +77,7 @@
91
77
  transition: box-shadow var(--duration-fast) var(--ease-standard);
92
78
  }
93
79
 
94
- :where([data-control-family="color-picker"]:is([data-slot="swatch"], [data-slot="swatch-add"], [data-slot="contrast-fix"]):disabled) {
80
+ :where([data-control-family="color-picker"]:is([data-slot="swatch"], [data-slot="contrast-fix"]):disabled) {
95
81
  opacity: var(--disabled-opacity);
96
82
  }
97
83
 
@@ -59,8 +59,7 @@
59
59
  border-radius: 9999px;
60
60
  }
61
61
 
62
- :where([data-control-family="color-picker"][data-slot="swatch"]),
63
- :where([data-control-family="color-picker"][data-slot="swatch-add"]) {
62
+ :where([data-control-family="color-picker"][data-slot="swatch"]) {
64
63
  inline-size: calc(var(--spacing) * 6);
65
64
  block-size: calc(var(--spacing) * 6);
66
65
  }
@@ -125,7 +124,7 @@
125
124
  --_color-picker-thumb-elevation: var(--shadow-md);
126
125
  }
127
126
 
128
- :where([data-control-family="color-picker"]:is([data-slot="slider-thumb"], [data-slot="swatch-add"]))::after {
127
+ :where([data-control-family="color-picker"][data-slot="slider-thumb"])::after {
129
128
  content: "";
130
129
  position: absolute;
131
130
  inset: 50%;
@@ -135,7 +134,7 @@
135
134
  }
136
135
 
137
136
  @media (pointer: coarse) {
138
- :where([data-control-family="color-picker"]:is([data-slot="slider-thumb"], [data-slot="swatch-add"]))::after {
137
+ :where([data-control-family="color-picker"][data-slot="slider-thumb"])::after {
139
138
  inline-size: max(100%, var(--touch-target));
140
139
  block-size: max(100%, var(--touch-target));
141
140
  }
@@ -240,6 +240,7 @@
240
240
  }
241
241
 
242
242
  :where([data-control-family="field"][data-field-kind="field"]) {
243
+ --cui-field-legend-foreground: var(--foreground);
243
244
  --cui-field-legend-font-size: var(--text-label);
244
245
  --cui-field-legend-font-weight: var(--font-weight-semibold);
245
246
  --cui-field-label-radius: var(--radius-lg);
@@ -254,7 +255,7 @@
254
255
  }
255
256
 
256
257
  :where([data-control-family="field"][data-field-kind="field"][data-slot="legend"]) {
257
- color: var(--foreground);
258
+ color: var(--cui-field-legend-foreground);
258
259
  font-size: var(--cui-field-legend-font-size);
259
260
  font-weight: var(--cui-field-legend-font-weight);
260
261
  }
@@ -368,6 +369,12 @@
368
369
  initial-value: 0px;
369
370
  }
370
371
 
372
+ @property --cui-field-legend-foreground {
373
+ syntax: "<color>";
374
+ inherits: true;
375
+ initial-value: transparent;
376
+ }
377
+
371
378
  @property --cui-field-legend-font-size {
372
379
  syntax: "<length-percentage>";
373
380
  inherits: true;
@@ -5,9 +5,6 @@
5
5
 
6
6
  :where([data-control-family="gradient-editor"][data-slot="root"]) {
7
7
  gap: calc(var(--spacing) * 3);
8
- --cui-gradient-editor-add-border-color: var(--border);
9
- --cui-gradient-editor-add-border-width: var(--control-rim-width);
10
- --cui-gradient-editor-add-radius: 9999px;
11
8
  --cui-gradient-editor-preview-radius: var(--radius-field);
12
9
  --cui-gradient-editor-preview-ring-color: oklch(from var(--border) l c h / 0.6);
13
10
  --cui-gradient-editor-stop-border-color: oklch(1 0 0);
@@ -48,19 +45,9 @@
48
45
  transition: scale var(--duration-fast) var(--ease-standard);
49
46
  }
50
47
 
51
- :where([data-control-family="gradient-editor"][data-slot="stop-add"]) {
52
- inline-size: calc(var(--spacing) * 7);
53
- block-size: calc(var(--spacing) * 7);
54
- border: var(--cui-gradient-editor-add-border-width) dashed var(--cui-gradient-editor-add-border-color);
55
- border-radius: var(--cui-gradient-editor-add-radius);
56
- color: var(--muted-foreground);
57
- }
58
-
59
- @media (hover: hover) {
60
- :where([data-control-family="gradient-editor"][data-slot="stop-add"]:hover:not(:disabled)) {
61
- border-color: oklch(from var(--foreground) l c h / 0.4);
62
- color: var(--foreground);
63
- }
48
+ :where([data-control-family="button"][data-gradient-editor-stop-add="true"]) {
49
+ --nest-radius: 9999px;
50
+ --cui-button-height: calc(var(--spacing) * 7);
64
51
  }
65
52
 
66
53
  :where([data-control-family="gradient-editor"][data-slot="stop"][data-selected="true"]) {
@@ -77,19 +64,6 @@
77
64
  }
78
65
 
79
66
  @media (pointer: coarse) {
80
- :where([data-control-family="gradient-editor"][data-slot="stop-add"]) {
81
- position: relative;
82
- }
83
-
84
- :where([data-control-family="gradient-editor"][data-slot="stop-add"])::after {
85
- content: "";
86
- position: absolute;
87
- inset: 50%;
88
- translate: -50% -50%;
89
- inline-size: max(100%, var(--touch-target));
90
- block-size: max(100%, var(--touch-target));
91
- }
92
-
93
67
  :where([data-control-family="gradient-editor"][data-slot="stop"])::after {
94
68
  block-size: max(100%, var(--touch-target));
95
69
  }
@@ -136,21 +110,3 @@
136
110
  syntax: "*";
137
111
  inherits: true;
138
112
  }
139
-
140
- @property --cui-gradient-editor-add-radius {
141
- syntax: "<length-percentage>";
142
- inherits: true;
143
- initial-value: 0px;
144
- }
145
-
146
- @property --cui-gradient-editor-add-border-color {
147
- syntax: "<color>";
148
- inherits: true;
149
- initial-value: transparent;
150
- }
151
-
152
- @property --cui-gradient-editor-add-border-width {
153
- syntax: "<length>";
154
- inherits: true;
155
- initial-value: 0px;
156
- }
@@ -1,5 +1,6 @@
1
1
  @layer components {
2
- :where([data-control-family="markdown"][data-slot="root"]) {
2
+ :where([data-control-family="markdown"][data-slot="root"]),
3
+ :where([data-control-family="markdown"]:not([data-control-family="markdown"][data-slot="root"] *)) {
3
4
  --cui-markdown-foreground: oklch(from var(--foreground) l c h / 0.9);
4
5
  --cui-markdown-blockquote-border-color: var(--border);
5
6
  --cui-markdown-blockquote-foreground: var(--muted-foreground);
@@ -14,6 +15,9 @@
14
15
  --cui-markdown-line-height: var(--leading-relaxed);
15
16
  --cui-markdown-flow-gap: calc(var(--spacing) * 4);
16
17
  --cui-markdown-heading-gap: calc(var(--spacing) * 6);
18
+ }
19
+
20
+ :where([data-control-family="markdown"][data-slot="root"]) {
17
21
  color: var(--cui-markdown-foreground);
18
22
  font-size: var(--cui-markdown-font-size);
19
23
  overflow-wrap: break-word;
@@ -169,6 +169,11 @@
169
169
  letter-spacing: var(--cui-popup-shortcut-letter-spacing);
170
170
  }
171
171
 
172
+ :where([data-control-family="popup"][data-slot="item-description"]) {
173
+ color: var(--cui-popup-item-description-foreground);
174
+ font-size: var(--cui-popup-item-description-font-size);
175
+ }
176
+
172
177
  :where([data-control-family="popup"][data-popup-kind="command"][data-slot="shortcut"]) {
173
178
  --cui-popup-shortcut-font-size: var(--text-label);
174
179
  }
@@ -369,6 +374,18 @@
369
374
  initial-value: 0px;
370
375
  }
371
376
 
377
+ @property --cui-popup-item-description-foreground {
378
+ syntax: "<color>";
379
+ inherits: true;
380
+ initial-value: transparent;
381
+ }
382
+
383
+ @property --cui-popup-item-description-font-size {
384
+ syntax: "<length-percentage>";
385
+ inherits: true;
386
+ initial-value: 0px;
387
+ }
388
+
372
389
  @property --cui-popup-shortcut-foreground {
373
390
  syntax: "<color>";
374
391
  inherits: true;
@@ -28,6 +28,8 @@
28
28
  --cui-popup-item-highlight-background: var(--hover-fill);
29
29
  --cui-popup-item-highlight-foreground: var(--cui-popup-item-foreground);
30
30
  --cui-popup-item-highlight-muted-foreground: var(--muted-foreground);
31
+ --cui-popup-item-description-foreground: var(--muted-foreground);
32
+ --cui-popup-item-description-font-size: var(--text-label);
31
33
  --cui-popup-item-highlight-shadow: inset 0 0 0 1px oklch(from var(--foreground) l c h / 0.35);
32
34
  --cui-popup-item-disabled-opacity: var(--disabled-opacity);
33
35
  --cui-popup-label-padding-block: var(--cui-popup-item-padding-block);
@@ -209,6 +211,7 @@
209
211
  ) {
210
212
  --cui-popup-shortcut-foreground: var(--cui-popup-item-highlight-foreground);
211
213
  --cui-popup-submenu-indicator-foreground: var(--cui-popup-item-highlight-foreground);
214
+ --cui-popup-item-description-foreground: var(--cui-popup-item-highlight-muted-foreground);
212
215
  --muted-foreground: var(--cui-popup-item-highlight-muted-foreground);
213
216
  background-color: var(--cui-popup-item-highlight-background);
214
217
  color: var(--cui-popup-item-highlight-foreground);
@@ -710,6 +710,10 @@
710
710
  outline-width: 0;
711
711
  }
712
712
 
713
+ :where([data-control-family][data-focus-ring="none"]):focus-visible {
714
+ outline-style: none;
715
+ }
716
+
713
717
  :where([data-control-family][data-highlighted]):focus-visible {
714
718
  outline-offset: calc(var(--focus-ring-width) * -1);
715
719
  }
@@ -2,6 +2,7 @@ import { OpenChangeEventDetails } from "../control-props.js";
2
2
  import { ControlSize } from "../control-variants.js";
3
3
  import { FieldKnobStyle } from "../knob-contracts/field-knobs.js";
4
4
  import { PopupKnobStyle } from "../knob-contracts/popup-knobs.js";
5
+ import { InputGroupProps } from "./input-group.js";
5
6
  import { CSSProperties, ComponentProps, ReactNode } from "react";
6
7
  //#region src/ui/autocomplete.d.ts
7
8
  export type AutocompleteProps<Value = string> = {
@@ -29,6 +30,7 @@ export type AutocompleteInputProps = Omit<Omit<ComponentProps<"input">, "size">,
29
30
  } & {
30
31
  size?: ControlSize;
31
32
  };
33
+ export type AutocompleteInputGroupProps = Omit<InputGroupProps, "render">;
32
34
  export type AutocompleteClearProps = ComponentProps<"button"> & {
33
35
  style?: CSSProperties & FieldKnobStyle;
34
36
  };
@@ -59,6 +61,7 @@ export type AutocompleteGroupLabelProps = Omit<ComponentProps<"div">, "style"> &
59
61
  };
60
62
  export declare function Autocomplete<Value = string>({ children, autoHighlight, ...props }: AutocompleteProps<Value>): import("react").JSX.Element;
61
63
  export declare function AutocompleteClear({ className, children, ...props }: AutocompleteClearProps): import("react").JSX.Element;
64
+ export declare function AutocompleteInputGroup(props: AutocompleteInputGroupProps): import("react").JSX.Element;
62
65
  export declare function AutocompleteInput({ size, className, ...props }: AutocompleteInputProps): import("react").JSX.Element;
63
66
  export declare function AutocompleteContent({ className, children, sideOffset, ...props }: AutocompleteContentProps): import("react").JSX.Element;
64
67
  export declare function AutocompleteList<Value = unknown>({ className, children, ...props }: AutocompleteListProps<Value>): import("react").JSX.Element;
@@ -4,7 +4,8 @@ import { controlEffectsAttribute } from "../skin.js";
4
4
  import { useSkin } from "../skin-provider.js";
5
5
  import { ScrollArea } from "./scroll-area.js";
6
6
  import { popupItemStructureClasses } from "../surface-variants.js";
7
- import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { InputGroup, InputGroupAddon, useIsInsideInputGroup } from "./input-group.js";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
9
  import { Autocomplete as Autocomplete$1 } from "@base-ui/react/autocomplete";
9
10
  //#region src/ui/autocomplete.tsx
10
11
  function Autocomplete({ children, autoHighlight = true, ...props }) {
@@ -38,7 +39,21 @@ function AutocompleteClear({ className, children, ...props }) {
38
39
  })
39
40
  });
40
41
  }
42
+ function AutocompleteInputGroup(props) {
43
+ return /* @__PURE__ */ jsx(InputGroup, {
44
+ ...props,
45
+ render: /* @__PURE__ */ jsx(Autocomplete$1.InputGroup, {})
46
+ });
47
+ }
41
48
  function AutocompleteInput({ size = "md", className, ...props }) {
49
+ if (useIsInsideInputGroup()) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Autocomplete$1.Input, {
50
+ ...props,
51
+ "data-control-ui": "input-group",
52
+ "data-control-family": "field",
53
+ "data-field-kind": "input-group",
54
+ "data-slot": "input",
55
+ className: cn("h-full min-w-0 flex-1", className)
56
+ }), /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(AutocompleteClear, {}) })] });
42
57
  return /* @__PURE__ */ jsxs(Autocomplete$1.InputGroup, {
43
58
  "data-control-ui": "autocomplete",
44
59
  "data-field-kind": "autocomplete",
@@ -155,4 +170,4 @@ function AutocompleteGroupLabel({ className, children, ...props }) {
155
170
  });
156
171
  }
157
172
  //#endregion
158
- export { Autocomplete, AutocompleteClear, AutocompleteContent, AutocompleteEmpty, AutocompleteGroup, AutocompleteGroupLabel, AutocompleteInput, AutocompleteItem, AutocompleteList };
173
+ export { Autocomplete, AutocompleteClear, AutocompleteContent, AutocompleteEmpty, AutocompleteGroup, AutocompleteGroupLabel, AutocompleteInput, AutocompleteInputGroup, AutocompleteItem, AutocompleteList };