@ctrl-ui/react 0.8.17 → 0.8.19

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 (52) hide show
  1. package/dist/blocks/coding-agent.js +1 -1
  2. package/dist/blocks/design-canvas-inspector.js +1 -1
  3. package/dist/blocks/design-canvas.js +9 -5
  4. package/dist/blocks/discussion-markdown-input.d.ts +12 -0
  5. package/dist/blocks/discussion-markdown-input.js +34 -0
  6. package/dist/blocks/discussion.d.ts +7 -3
  7. package/dist/blocks/discussion.js +77 -16
  8. package/dist/blocks/file-explorer.js +4 -4
  9. package/dist/blocks/settings.js +5 -3
  10. package/dist/knob-contracts/label-knobs.d.ts +4 -0
  11. package/dist/knob-contracts/label-knobs.js +4 -0
  12. package/dist/knob-contracts/markdown-editor-knobs.d.ts +4 -0
  13. package/dist/knob-contracts/markdown-editor-knobs.js +16 -0
  14. package/dist/knob-contracts/switch-knobs.d.ts +1 -1
  15. package/dist/knob-contracts/switch-knobs.js +1 -0
  16. package/dist/markdown-editor/context.d.ts +22 -0
  17. package/dist/markdown-editor/context.js +10 -0
  18. package/dist/markdown-editor/extensions.d.ts +10 -0
  19. package/dist/markdown-editor/extensions.js +139 -0
  20. package/dist/markdown-editor/toolbar.d.ts +9 -0
  21. package/dist/markdown-editor/toolbar.js +336 -0
  22. package/dist/markdown-editor/uploads.d.ts +23 -0
  23. package/dist/markdown-editor/uploads.js +32 -0
  24. package/dist/markdown-editor/use-image-uploads.d.ts +12 -0
  25. package/dist/markdown-editor/use-image-uploads.js +120 -0
  26. package/dist/markdown-editor.d.ts +27 -0
  27. package/dist/markdown-editor.js +192 -0
  28. package/dist/styles/index.css +4 -2
  29. package/dist/styles/recipes/code.css +4 -0
  30. package/dist/styles/recipes/field.css +1 -1
  31. package/dist/styles/recipes/label.css +19 -0
  32. package/dist/styles/recipes/markdown-editor.css +196 -0
  33. package/dist/styles/recipes/switch.css +17 -5
  34. package/dist/styles/theme.css +25 -0
  35. package/dist/styles/type-scale.css +28 -2
  36. package/dist/ui/app-shell.js +1 -1
  37. package/dist/ui/autocomplete.js +1 -1
  38. package/dist/ui/code.js +1 -0
  39. package/dist/ui/combobox.js +1 -1
  40. package/dist/ui/command.js +1 -1
  41. package/dist/ui/dockable-panel.js +1 -1
  42. package/dist/ui/label.d.ts +8 -0
  43. package/dist/ui/label.js +14 -0
  44. package/dist/ui/markdown.d.ts +2 -1
  45. package/dist/ui/markdown.js +3 -3
  46. package/dist/ui/responsive-dialog.js +1 -1
  47. package/dist/ui/sidebar-menu.js +1 -1
  48. package/dist/ui/sidebar.js +1 -1
  49. package/dist/ui/typography.d.ts +44 -0
  50. package/dist/ui/typography.js +59 -0
  51. package/dist/ui/use-switch-drag.js +1 -1
  52. package/package.json +37 -1
@@ -0,0 +1,192 @@
1
+ "use client";
2
+ import { cn } from "./lib/cn.js";
3
+ import { Textarea } from "./ui/textarea.js";
4
+ import { MarkdownEditorContext, useMarkdownEditor } from "./markdown-editor/context.js";
5
+ import { createMarkdownCodec, hasMarkdownContent, markdownExtensions, requiresMarkdownSource } from "./markdown-editor/extensions.js";
6
+ import { useImageUploads } from "./markdown-editor/use-image-uploads.js";
7
+ import { useEffect, useMemo, useRef, useState } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { Slice } from "@tiptap/pm/model";
10
+ import { EditorState } from "@tiptap/pm/state";
11
+ import { EditorContent, useEditor } from "@tiptap/react";
12
+ //#region src/markdown-editor.tsx
13
+ function MarkdownEditor({ value, defaultValue = "", onValueChange, disabled = false, onUploadImage, maxImageBytes = 10485760, onStatusChange, children, className, ...props }) {
14
+ const [internalValue, setInternalValue] = useState(defaultValue);
15
+ const markdown = value ?? internalValue;
16
+ const [sourceMode, setSourceMode] = useState(false);
17
+ const [contentAttributes, setContentAttributes] = useState({
18
+ role: "textbox",
19
+ "aria-multiline": "true"
20
+ });
21
+ const [codec] = useState(createMarkdownCodec);
22
+ const sourceRequired = useMemo(() => requiresMarkdownSource(markdown, codec), [markdown, codec]);
23
+ const source = sourceMode || sourceRequired;
24
+ const lastValue = useRef(markdown);
25
+ const [extensions] = useState(markdownExtensions);
26
+ const handlers = useRef(null);
27
+ function setMarkdown(next) {
28
+ lastValue.current = next;
29
+ setInternalValue(next);
30
+ onValueChange?.(next);
31
+ }
32
+ const editor = useEditor({
33
+ extensions,
34
+ immediatelyRender: false,
35
+ shouldRerenderOnTransaction: false,
36
+ content: sourceRequired ? "" : markdown,
37
+ contentType: "markdown",
38
+ editable: !disabled,
39
+ onUpdate: ({ editor: currentEditor }) => setMarkdown(currentEditor.getMarkdown()),
40
+ editorProps: {
41
+ attributes: contentAttributes,
42
+ handlePaste: (view, event) => {
43
+ if (disabled) return false;
44
+ const files = Array.from(event.clipboardData?.files ?? []);
45
+ if (onUploadImage && files.length > 0) {
46
+ event.preventDefault();
47
+ handlers.current?.upload(files);
48
+ return true;
49
+ }
50
+ const text = event.clipboardData?.getData("text/plain");
51
+ if (!text || event.clipboardData?.getData("text/html") || requiresMarkdownSource(text, codec)) return false;
52
+ const document = view.state.schema.nodeFromJSON(codec.parse(text));
53
+ event.preventDefault();
54
+ view.dispatch(view.state.tr.replaceSelection(Slice.maxOpen(document.content)).scrollIntoView());
55
+ return true;
56
+ },
57
+ handleDrop: (view, event, _slice, moved) => {
58
+ const files = Array.from(event.dataTransfer?.files ?? []);
59
+ if (moved || !onUploadImage || disabled || files.length === 0) return false;
60
+ event.preventDefault();
61
+ const position = view.posAtCoords({
62
+ left: event.clientX,
63
+ top: event.clientY
64
+ })?.pos;
65
+ handlers.current?.upload(files, position);
66
+ return true;
67
+ }
68
+ }
69
+ });
70
+ const imageUploads = useImageUploads(editor, onUploadImage, maxImageBytes);
71
+ useEffect(() => {
72
+ handlers.current = imageUploads;
73
+ });
74
+ useEffect(() => {
75
+ if (!editor) return;
76
+ editor.setEditable(!disabled, false);
77
+ }, [editor, disabled]);
78
+ useEffect(() => {
79
+ if (!editor) return;
80
+ const externalReset = lastValue.current !== markdown;
81
+ if (externalReset) handlers.current?.cancelAll();
82
+ if (source) {
83
+ lastValue.current = markdown;
84
+ return;
85
+ }
86
+ if (editor.getMarkdown() !== markdown) {
87
+ editor.commands.setContent(markdown, {
88
+ contentType: "markdown",
89
+ emitUpdate: false
90
+ });
91
+ if (externalReset) {
92
+ editor.view.updateState(EditorState.create({
93
+ schema: editor.schema,
94
+ doc: editor.state.doc,
95
+ plugins: editor.state.plugins
96
+ }));
97
+ editor.view.dispatch(editor.state.tr);
98
+ }
99
+ }
100
+ lastValue.current = markdown;
101
+ }, [
102
+ editor,
103
+ markdown,
104
+ source
105
+ ]);
106
+ const isEmpty = source ? markdown.trim() === "" : !hasMarkdownContent(codec.parse(markdown));
107
+ const hasPendingUploads = imageUploads.uploads.length > 0;
108
+ useEffect(() => {
109
+ onStatusChange?.({
110
+ isEmpty,
111
+ hasPendingUploads
112
+ });
113
+ }, [
114
+ onStatusChange,
115
+ isEmpty,
116
+ hasPendingUploads
117
+ ]);
118
+ return /* @__PURE__ */ jsx(MarkdownEditorContext, {
119
+ value: {
120
+ editor,
121
+ disabled,
122
+ source,
123
+ markdown,
124
+ sourceRequired,
125
+ setSource: setSourceMode,
126
+ setMarkdown,
127
+ setContentAttributes,
128
+ canUpload: Boolean(onUploadImage),
129
+ ...imageUploads
130
+ },
131
+ children: /* @__PURE__ */ jsx("div", {
132
+ "data-control-ui": "markdown-editor",
133
+ "data-control-family": "markdown-editor",
134
+ "data-slot": "root",
135
+ "data-disabled": disabled ? "" : void 0,
136
+ className: cn("min-w-0", className),
137
+ ...props,
138
+ children
139
+ })
140
+ });
141
+ }
142
+ function MarkdownEditorContent({ label, placeholder, autoFocus = false, className }) {
143
+ const { editor, disabled, source, sourceRequired, markdown, setMarkdown, setContentAttributes } = useMarkdownEditor();
144
+ useEffect(() => {
145
+ setContentAttributes({
146
+ role: "textbox",
147
+ "aria-multiline": "true",
148
+ "aria-label": label,
149
+ "aria-disabled": String(disabled),
150
+ "data-placeholder": placeholder ?? ""
151
+ });
152
+ }, [
153
+ setContentAttributes,
154
+ label,
155
+ disabled,
156
+ placeholder
157
+ ]);
158
+ useEffect(() => {
159
+ if (editor && autoFocus && !source && !disabled) editor.commands.focus("end");
160
+ }, [
161
+ editor,
162
+ autoFocus,
163
+ source,
164
+ disabled
165
+ ]);
166
+ if (source || !editor) return /* @__PURE__ */ jsxs("div", {
167
+ "data-control-ui": "markdown-editor",
168
+ "data-control-family": "markdown-editor",
169
+ "data-slot": "source",
170
+ className,
171
+ children: [sourceRequired ? /* @__PURE__ */ jsx("p", {
172
+ role: "status",
173
+ children: "This content uses Markdown that is only available in source mode. Its source is preserved."
174
+ }) : null, /* @__PURE__ */ jsx(Textarea, {
175
+ "aria-label": label,
176
+ placeholder,
177
+ value: markdown,
178
+ disabled: disabled || !editor,
179
+ onChange: (event) => setMarkdown(event.target.value),
180
+ rows: 5
181
+ })]
182
+ });
183
+ return /* @__PURE__ */ jsx(EditorContent, {
184
+ editor,
185
+ "data-control-ui": "markdown-editor",
186
+ "data-control-family": "markdown-editor",
187
+ "data-slot": "content",
188
+ className
189
+ });
190
+ }
191
+ //#endregion
192
+ export { MarkdownEditor, MarkdownEditorContent };
@@ -51,7 +51,11 @@
51
51
  @import "./recipes/resizable.css";
52
52
  @import "./recipes/separator.css";
53
53
  @import "./recipes/toolbar.css";
54
+ @import "./type-scale.css";
54
55
  @import "./recipes/kbd.css";
56
+ @import "./recipes/markdown.css";
57
+ @import "./recipes/label.css";
58
+ @import "./recipes/markdown-editor.css";
55
59
  @import "./recipes/dockable-panel.css";
56
60
  @import "./recipes/item.css";
57
61
  @import "./recipes/dropzone.css";
@@ -68,7 +72,6 @@
68
72
  @import "./recipes/inline-attachment.css";
69
73
  @import "./recipes/source-badge.css";
70
74
  @import "./recipes/inline-citation.css";
71
- @import "./recipes/markdown.css";
72
75
  @import "./recipes/markdown-block.css";
73
76
  @import "./recipes/range.css";
74
77
  @import "./recipes/morphing-panel.css";
@@ -86,6 +89,5 @@
86
89
  @import "./recipes/popup-toast.css";
87
90
  @import "./recipes/transcript-divider.css";
88
91
  @import "./recipes/tree.css";
89
- @import "./type-scale.css";
90
92
  @import "./recipes/user-ask.css";
91
93
  @import "./recipes/user-ask-details.css";
@@ -59,6 +59,10 @@
59
59
  margin-inline-start: auto;
60
60
  }
61
61
 
62
+ :where([data-control-family="code"][data-slot="floating-frame"]) {
63
+ padding-block-start: calc(var(--spacing) * 9);
64
+ }
65
+
62
66
  :where([data-control-family="code"][data-slot="line"]) {
63
67
  min-block-size: calc(var(--spacing) * 5);
64
68
  }
@@ -187,7 +187,7 @@
187
187
  }
188
188
 
189
189
  :where([data-control-family="field"][data-field-kind="field"][data-slot="root"]) {
190
- gap: calc(var(--spacing) * 3);
190
+ gap: calc(var(--spacing) * 1.5) calc(var(--spacing) * 3);
191
191
  }
192
192
 
193
193
  :where([data-control-family="field"][data-field-kind="field"][data-slot="content"]) {
@@ -0,0 +1,19 @@
1
+ @layer components {
2
+ :where([data-control-family="label"][data-slot="root"]) {
3
+ --cui-label-root-foreground: var(--foreground);
4
+ color: var(--cui-label-root-foreground);
5
+ font-size: var(--text-label);
6
+ font-weight: var(--font-weight-medium);
7
+ }
8
+
9
+ :where([data-control-family="label"][data-slot="root"]:has(:disabled, [data-disabled])) {
10
+ cursor: not-allowed;
11
+ opacity: var(--disabled-opacity);
12
+ }
13
+ }
14
+
15
+ @property --cui-label-root-foreground {
16
+ syntax: "<color>";
17
+ inherits: true;
18
+ initial-value: transparent;
19
+ }
@@ -0,0 +1,196 @@
1
+ @layer components {
2
+ :where([data-control-family="markdown-editor"][data-slot="root"]) {
3
+ --cui-markdown-editor-background: var(--background);
4
+ --cui-markdown-editor-foreground: var(--foreground);
5
+ --cui-markdown-editor-border-width: var(--control-rim-width);
6
+ --cui-markdown-editor-border-color: var(--border);
7
+ --cui-markdown-editor-radius: var(--radius-panel);
8
+ --cui-markdown-editor-padding: calc(var(--spacing) * 3);
9
+ --cui-markdown-editor-min-height: calc(var(--spacing) * 32);
10
+ --cui-markdown-editor-max-height: 60dvh;
11
+ --cui-markdown-editor-gap: calc(var(--spacing) * 3);
12
+ --cui-markdown-editor-muted-foreground: var(--muted-foreground);
13
+ --cui-markdown-editor-code-background: var(--muted);
14
+ color: var(--cui-markdown-editor-foreground);
15
+ background: var(--cui-markdown-editor-background);
16
+ border: var(--cui-markdown-editor-border-width) solid var(--cui-markdown-editor-border-color);
17
+ border-radius: var(--cui-markdown-editor-radius);
18
+ overflow: clip;
19
+ font-size: var(--text-body);
20
+ line-height: var(--leading-relaxed);
21
+ }
22
+ :where([data-control-family="markdown-editor"][data-slot="content"] .tiptap) {
23
+ min-block-size: var(--cui-markdown-editor-min-height);
24
+ max-block-size: var(--cui-markdown-editor-max-height);
25
+ overflow: auto;
26
+ padding: var(--cui-markdown-editor-padding);
27
+ overflow-wrap: anywhere;
28
+ white-space: pre-wrap;
29
+ }
30
+ :where([data-control-family="markdown-editor"] .tiptap:focus-visible) {
31
+ outline: 2px solid var(--ring);
32
+ outline-offset: -2px;
33
+ }
34
+ :where([data-control-family="markdown-editor"][data-slot="source"]),
35
+ :where([data-control-family="markdown-editor"][data-slot="uploads"]) {
36
+ padding: var(--cui-markdown-editor-padding);
37
+ }
38
+ :where([data-control-family="markdown-editor"] .tiptap > * + *) {
39
+ margin-block-start: var(--cui-markdown-editor-gap);
40
+ }
41
+ :where([data-control-family="markdown-editor"] .tiptap:has(> p:only-child > br:only-child))::before {
42
+ content: attr(data-placeholder);
43
+ color: var(--cui-markdown-editor-muted-foreground);
44
+ pointer-events: none;
45
+ float: inline-start;
46
+ block-size: 0;
47
+ }
48
+ :where([data-control-family="markdown-editor"] .tiptap h1) {
49
+ font-size: var(--text-heading-2);
50
+ font-weight: var(--font-weight-semibold);
51
+ }
52
+ :where([data-control-family="markdown-editor"] .tiptap h2) {
53
+ font-size: var(--text-heading-3);
54
+ font-weight: var(--font-weight-semibold);
55
+ }
56
+ :where([data-control-family="markdown-editor"] .tiptap :is(h3, h4, h5, h6)) {
57
+ font-size: var(--text-heading-4);
58
+ font-weight: var(--font-weight-semibold);
59
+ }
60
+ :where([data-control-family="markdown-editor"] .tiptap strong) {
61
+ font-weight: var(--font-weight-semibold);
62
+ }
63
+ :where([data-control-family="markdown-editor"] .tiptap em) {
64
+ font-style: italic;
65
+ }
66
+ :where([data-control-family="markdown-editor"] .tiptap s) {
67
+ text-decoration: line-through;
68
+ }
69
+ :where([data-control-family="markdown-editor"] .tiptap a) {
70
+ text-decoration: underline;
71
+ color: var(--primary);
72
+ }
73
+ :where([data-control-family="markdown-editor"] .tiptap ul) {
74
+ list-style: disc;
75
+ padding-inline-start: calc(var(--spacing) * 6);
76
+ }
77
+ :where([data-control-family="markdown-editor"] .tiptap ol) {
78
+ list-style: decimal;
79
+ padding-inline-start: calc(var(--spacing) * 6);
80
+ }
81
+ :where([data-control-family="markdown-editor"] .tiptap ul[data-type="taskList"]) {
82
+ list-style: none;
83
+ padding-inline-start: 0;
84
+ }
85
+ :where([data-control-family="markdown-editor"] .tiptap li[data-type="taskItem"]) {
86
+ display: flex;
87
+ align-items: baseline;
88
+ gap: var(--cui-markdown-editor-gap);
89
+ }
90
+ :where([data-control-family="markdown-editor"] .tiptap li[data-type="taskItem"] > div) {
91
+ flex: 1;
92
+ min-inline-size: 0;
93
+ }
94
+ :where([data-control-family="markdown-editor"] .tiptap blockquote) {
95
+ border-inline-start: var(--cui-markdown-editor-border-width) solid var(--cui-markdown-editor-border-color);
96
+ padding-inline-start: var(--cui-markdown-editor-padding);
97
+ color: var(--cui-markdown-editor-muted-foreground);
98
+ }
99
+ :where([data-control-family="markdown-editor"] .tiptap :is(code, pre)) {
100
+ font-family: var(--font-mono);
101
+ background: var(--cui-markdown-editor-code-background);
102
+ border-radius: var(--radius-sm);
103
+ }
104
+ :where([data-control-family="markdown-editor"] .tiptap pre) {
105
+ padding: var(--cui-markdown-editor-padding);
106
+ overflow: auto;
107
+ }
108
+ :where([data-control-family="markdown-editor"] .tiptap img[src]) {
109
+ display: block;
110
+ max-inline-size: 100%;
111
+ block-size: auto;
112
+ border-radius: var(--radius-sm);
113
+ }
114
+ :where([data-control-family="markdown-editor"] .tiptap .ProseMirror-selectednode) {
115
+ outline: 2px solid var(--ring);
116
+ }
117
+ :where([data-control-family="markdown-editor"] .tiptap [data-image-upload]) {
118
+ padding: var(--cui-markdown-editor-padding);
119
+ border: var(--cui-markdown-editor-border-width) dashed var(--cui-markdown-editor-border-color);
120
+ color: var(--cui-markdown-editor-muted-foreground);
121
+ }
122
+ :where([data-control-family="markdown-editor"] .tiptap table) {
123
+ border-collapse: collapse;
124
+ inline-size: 100%;
125
+ }
126
+ :where([data-control-family="markdown-editor"] .tiptap :is(td, th)) {
127
+ border: var(--cui-markdown-editor-border-width) solid var(--cui-markdown-editor-border-color);
128
+ padding: var(--cui-markdown-editor-padding);
129
+ }
130
+ }
131
+
132
+ @property --cui-markdown-editor-background {
133
+ syntax: "<color>";
134
+ inherits: true;
135
+ initial-value: transparent;
136
+ }
137
+
138
+ @property --cui-markdown-editor-foreground {
139
+ syntax: "<color>";
140
+ inherits: true;
141
+ initial-value: transparent;
142
+ }
143
+
144
+ @property --cui-markdown-editor-border-color {
145
+ syntax: "<color>";
146
+ inherits: true;
147
+ initial-value: transparent;
148
+ }
149
+
150
+ @property --cui-markdown-editor-radius {
151
+ syntax: "<length-percentage>";
152
+ inherits: true;
153
+ initial-value: 0px;
154
+ }
155
+
156
+ @property --cui-markdown-editor-padding {
157
+ syntax: "<length>";
158
+ inherits: true;
159
+ initial-value: 0px;
160
+ }
161
+
162
+ @property --cui-markdown-editor-min-height {
163
+ syntax: "<length>";
164
+ inherits: true;
165
+ initial-value: 0px;
166
+ }
167
+
168
+ @property --cui-markdown-editor-max-height {
169
+ syntax: "<length>";
170
+ inherits: true;
171
+ initial-value: 0px;
172
+ }
173
+
174
+ @property --cui-markdown-editor-gap {
175
+ syntax: "<length>";
176
+ inherits: true;
177
+ initial-value: 0px;
178
+ }
179
+
180
+ @property --cui-markdown-editor-muted-foreground {
181
+ syntax: "<color>";
182
+ inherits: true;
183
+ initial-value: transparent;
184
+ }
185
+
186
+ @property --cui-markdown-editor-code-background {
187
+ syntax: "<color>";
188
+ inherits: true;
189
+ initial-value: transparent;
190
+ }
191
+
192
+ @property --cui-markdown-editor-border-width {
193
+ syntax: "<length>";
194
+ inherits: true;
195
+ initial-value: 0px;
196
+ }
@@ -4,11 +4,13 @@
4
4
  --cui-switch-height: calc(var(--spacing) * 5);
5
5
  --cui-switch-padding: calc(var(--spacing) * 0.5);
6
6
  --cui-switch-thumb-inline-size: calc(var(--spacing) * 4);
7
- --cui-switch-thumb-press-scale: 1.25 1;
7
+ --cui-switch-thumb-press-inline-size: calc(var(--cui-switch-thumb-inline-size) * 1.25);
8
+ --cui-switch-thumb-press-scale: 1;
8
9
  --cui-switch-thumb-backdrop-filter: none;
9
10
  --_switch-direction: 1;
10
11
  --_switch-progress: 0;
11
- --_switch-travel: calc(var(--cui-switch-inline-size) - var(--cui-switch-padding) * 2 - var(--cui-switch-thumb-inline-size));
12
+ --_switch-thumb-inline-size: var(--cui-switch-thumb-inline-size);
13
+ --_switch-travel: calc(var(--cui-switch-inline-size) - var(--cui-switch-padding) * 2 - var(--_switch-thumb-inline-size));
12
14
  block-size: var(--cui-switch-height);
13
15
  inline-size: var(--cui-switch-inline-size);
14
16
  padding: var(--cui-switch-padding);
@@ -79,13 +81,13 @@
79
81
  border-radius: var(--cui-switch-thumb-radius);
80
82
  background: var(--cui-switch-thumb-background);
81
83
  box-shadow: var(--cui-switch-thumb-shadow);
82
- inline-size: var(--cui-switch-thumb-inline-size);
84
+ inline-size: var(--_switch-thumb-inline-size);
83
85
  block-size: calc(var(--cui-switch-height) - var(--cui-switch-padding) * 2);
84
86
  translate: calc(var(--_switch-direction) * var(--_switch-travel) * var(--_switch-progress)) 0;
85
87
  transform-origin: left center;
86
88
  -webkit-backdrop-filter: var(--cui-switch-thumb-backdrop-filter);
87
89
  backdrop-filter: var(--cui-switch-thumb-backdrop-filter);
88
- transition-property: translate, scale, background-color, box-shadow;
90
+ transition-property: translate, inline-size, scale, background-color, box-shadow;
89
91
  transition-duration: var(--duration-base);
90
92
  transition-timing-function: var(--ease-emphasized);
91
93
  }
@@ -136,6 +138,10 @@
136
138
  transform-origin: left center;
137
139
  }
138
140
 
141
+ :where([data-control-family="switch"][data-slot="root"]:is(:active, [data-dragging]):not([data-disabled], [data-readonly])) {
142
+ --_switch-thumb-inline-size: var(--cui-switch-thumb-press-inline-size);
143
+ }
144
+
139
145
  :where(
140
146
  [data-control-family="switch"][data-slot="root"]:is(:active, [data-dragging]):not([data-disabled], [data-readonly]) [data-slot="thumb"]
141
147
  ) {
@@ -157,7 +163,7 @@
157
163
  }
158
164
 
159
165
  :where([data-control-family="switch"][data-slot="root"][data-dragging] [data-slot="thumb"]) {
160
- transition-property: scale, background-color, box-shadow;
166
+ transition-property: inline-size, scale, background-color, box-shadow;
161
167
  }
162
168
  }
163
169
 
@@ -255,6 +261,12 @@
255
261
  initial-value: 16px;
256
262
  }
257
263
 
264
+ @property --cui-switch-thumb-press-inline-size {
265
+ syntax: "<length>";
266
+ inherits: true;
267
+ initial-value: 20px;
268
+ }
269
+
258
270
  @property --cui-switch-thumb-press-scale {
259
271
  syntax: "*";
260
272
  inherits: true;
@@ -601,6 +601,31 @@
601
601
  --default-transition-timing-function: var(--ease-standard);
602
602
  }
603
603
 
604
+ @utility text-display {
605
+ font-family: var(--font-display);
606
+ text-wrap: balance;
607
+ }
608
+
609
+ @utility text-heading-1 {
610
+ font-family: var(--font-display);
611
+ text-wrap: balance;
612
+ }
613
+
614
+ @utility text-heading-2 {
615
+ font-family: var(--font-display);
616
+ text-wrap: balance;
617
+ }
618
+
619
+ @utility text-heading-3 {
620
+ font-family: var(--font-display);
621
+ text-wrap: balance;
622
+ }
623
+
624
+ @utility text-heading-4 {
625
+ font-family: var(--font-display);
626
+ text-wrap: balance;
627
+ }
628
+
604
629
  @layer base {
605
630
  *,
606
631
  ::after,
@@ -33,13 +33,39 @@
33
33
  --text-display--letter-spacing: -0.02em;
34
34
 
35
35
  /* Font ROLES: body+display default to --font-sans; brand re-values ONE token for headings(font-display)/body(font-body)
36
- own face. Headings apply `font-display` utility on element (font-family isn't a paired --text-* prop). */
36
+ own face. */
37
37
  --font-body: var(--font-sans);
38
38
  --font-display: var(--font-sans);
39
39
  }
40
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
+
41
66
  /*
42
67
  * History: fixed 3 competing type systems (Tailwind defaults + named rungs + raw text-[Npx]) into one rung/role; 14px = body.
43
- * Usage: apply utility directly to element (no <Text> component), e.g. text-heading-1 on <h1>, text-body on <p>.
68
+ * Usage: apply the rung directly to the semantic element (no <Text> component), e.g. text-heading-1 on <h1>, text-body on <p>.
69
+ * Heading LEVEL is the document outline; the RUNG is the look. They are decoupled on purpose: an <h3> may wear text-heading-2.
44
70
  * Control chrome text is sized by its recipe (`--cui-button-font-size`, `--cui-field-font-size`), not by these utilities.
45
71
  */
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
+ import { PageScrollContext, usePageScroll } from "./page-layout.js";
3
4
  import { SidebarProvider } from "./sidebar-provider.js";
4
5
  import { SidebarInset } from "./sidebar.js";
5
- import { PageScrollContext, usePageScroll } from "./page-layout.js";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  //#region src/ui/app-shell.tsx
8
8
  function AppShell({ scroll = "auto", layout = "viewport", className, ...props }) {
@@ -85,7 +85,7 @@ function AutocompleteContent({ className, children, sideOffset = 6, ...props })
85
85
  function AutocompleteList({ className, children, ...props }) {
86
86
  return /* @__PURE__ */ jsx(ScrollArea, {
87
87
  className: "w-full",
88
- viewportClassName: "overscroll-contain",
88
+ viewportClassName: "overscroll-y-contain",
89
89
  maxHeight: "min(18rem, var(--available-height))",
90
90
  lockAxis: "x",
91
91
  children: /* @__PURE__ */ jsx(Autocomplete$1.List, {
package/dist/ui/code.js CHANGED
@@ -112,6 +112,7 @@ function CodeCopy({ value, copiedLabel, copiedAriaLabel = "Copied to clipboard",
112
112
  }
113
113
  function CodeFloatingCopy({ className, ...props }) {
114
114
  return /* @__PURE__ */ jsx(CodeCopy, {
115
+ variant: "surface",
115
116
  "data-code-floating": "true",
116
117
  className: cn("absolute z-10", className),
117
118
  ...props
@@ -115,7 +115,7 @@ function ComboboxContent({ className, children, sideOffset = 6, anchor, ...props
115
115
  function ComboboxList({ className, children, ...props }) {
116
116
  return /* @__PURE__ */ jsx(ScrollArea, {
117
117
  className: "w-full",
118
- viewportClassName: "overscroll-contain",
118
+ viewportClassName: "overscroll-y-contain",
119
119
  maxHeight: "min(18rem, var(--available-height))",
120
120
  lockAxis: "x",
121
121
  children: /* @__PURE__ */ jsx(Combobox$1.List, {
@@ -3,8 +3,8 @@ import { cn } from "../lib/cn.js";
3
3
  import { LiveStatus } from "./live-status.js";
4
4
  import { ScrollArea } from "./scroll-area.js";
5
5
  import { popupItemStructureClasses } from "../surface-variants.js";
6
- import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "./empty.js";
7
6
  import { Dialog, DialogContent, DialogDescription, DialogTitle } from "./dialog.js";
7
+ import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "./empty.js";
8
8
  import { useEffect, useRef } from "react";
9
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  import { Command as Command$1, useCommandState } from "cmdk";
@@ -4,8 +4,8 @@ import { Button } from "./button.js";
4
4
  import { LiveStatus } from "./live-status.js";
5
5
  import { ScrollArea } from "./scroll-area.js";
6
6
  import { useIsMobile } from "../hooks/use-mobile.js";
7
- import { Drawer, DrawerBody, DrawerContent } from "./drawer.js";
8
7
  import { clampDockablePanelPosition, dockablePanelSideAt, dockablePanelSideForKey, oppositeDockablePanelSide } from "./dockable-panel-geometry.js";
8
+ import { Drawer, DrawerBody, DrawerContent } from "./drawer.js";
9
9
  import { createContext, use, useEffect, useEffectEvent, useId, useRef, useState } from "react";
10
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
11
  //#region src/ui/dockable-panel.tsx
@@ -0,0 +1,8 @@
1
+ import { LabelKnobStyle } from "../knob-contracts/label-knobs.js";
2
+ import { CSSProperties, ComponentProps } from "react";
3
+ //#region src/ui/label.d.ts
4
+ export type LabelProps = Omit<ComponentProps<"label">, "style"> & {
5
+ style?: CSSProperties & LabelKnobStyle;
6
+ };
7
+ export declare function Label({ htmlFor, children, ...props }: LabelProps): import("react").JSX.Element;
8
+ //#endregion
@@ -0,0 +1,14 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ //#region src/ui/label.tsx
3
+ function Label({ htmlFor, children, ...props }) {
4
+ return /* @__PURE__ */ jsx("label", {
5
+ ...props,
6
+ htmlFor,
7
+ "data-control-ui": "label",
8
+ "data-control-family": "label",
9
+ "data-slot": "root",
10
+ children
11
+ });
12
+ }
13
+ //#endregion
14
+ export { Label };