@ctrl-ui/react 0.9.3 → 0.9.5
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.
- package/README.md +27 -0
- package/dist/blocks/coding-agent.d.ts +3 -1
- package/dist/blocks/coding-agent.js +1 -4
- package/dist/blocks/file-explorer.d.ts +4 -2
- package/dist/blocks/file-explorer.js +17 -17
- package/dist/blocks/settings.d.ts +3 -1
- package/dist/knob-contracts/range-knobs.d.ts +1 -1
- package/dist/knob-contracts/range-knobs.js +1 -0
- package/dist/lib/cn.d.ts +2 -0
- package/dist/lib/cn.js +29 -24
- package/dist/styles/recipes/button-states.css +12 -0
- package/dist/styles/recipes/button.css +1 -1
- package/dist/styles/recipes/chat-composer-attachment.css +7 -0
- package/dist/styles/recipes/color-picker-details.css +7 -0
- package/dist/styles/recipes/field.css +7 -0
- package/dist/styles/recipes/inline-attachment.css +7 -0
- package/dist/styles/recipes/kbd.css +7 -0
- package/dist/styles/recipes/pagination.css +8 -1
- package/dist/styles/recipes/range-level-meter.css +14 -0
- package/dist/styles/recipes/scroll-area.css +1 -3
- package/dist/styles/recipes/tabs.css +12 -0
- package/dist/styles/type-scale.css +2 -1
- package/dist/ui/level-meter.d.ts +2 -1
- package/dist/ui/level-meter.js +2 -1
- package/dist/ui/sidebar.js +1 -1
- package/dist/ui/toggle.js +1 -1
- package/dist/ui/typography.d.ts +2 -0
- package/dist/ui/typography.js +2 -0
- package/lint/no-raw-type-classes.grit +7 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -26,6 +26,33 @@ import { Button } from "@ctrl-ui/react/ui/button";
|
|
|
26
26
|
|
|
27
27
|
Import paths mirror the registry tree. To ship less CSS, import `styles/theme.css` and only the recipes and effects your components use.
|
|
28
28
|
|
|
29
|
+
## Text and class names
|
|
30
|
+
|
|
31
|
+
Render text with `Text` and `Heading`. Size, tone, and weight are props; `className` carries layout only:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { Text } from "@ctrl-ui/react/ui/typography";
|
|
35
|
+
|
|
36
|
+
<Text size="caption" tone="muted">Saved</Text>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Merge class names with the package's `cn`. A bare `twMerge` reads `text-micro` as a text color and silently drops it next to `text-muted-foreground`. Add your own type rungs on top of `withControlUiMerge` instead of starting a second merger:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { withControlUiMerge } from "@ctrl-ui/react/lib/cn";
|
|
43
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
44
|
+
|
|
45
|
+
const twMerge = extendTailwindMerge({ extend: { classGroups: { "font-size": [{ text: ["hero"] }] } } }, withControlUiMerge);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Controls size their own text: pick the control's `size`, or set its `--cui-*-font-size` knob in your theme, rather than wrapping the label in a `text-*` span.
|
|
49
|
+
|
|
50
|
+
With Biome, the shipped GritQL plugin warns on type rungs in `className`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{ "plugins": ["./node_modules/@ctrl-ui/react/lint/no-raw-type-classes.grit"] }
|
|
54
|
+
```
|
|
55
|
+
|
|
29
56
|
## Customize
|
|
30
57
|
|
|
31
58
|
Your application owns its theme. Override the library's neutral defaults in your CSS:
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ChatLayout } from "../chat-layout.js";
|
|
2
|
+
import { SidebarProviderProps } from "../ui/sidebar-provider.js";
|
|
3
|
+
import "../ui/sidebar.js";
|
|
2
4
|
import { ComponentProps, ReactNode } from "react";
|
|
3
5
|
//#region src/blocks/coding-agent.d.ts
|
|
4
6
|
export type CodingAgentNavigationItem = {
|
|
@@ -19,7 +21,7 @@ export type CodingAgentProject = {
|
|
|
19
21
|
tasks: readonly CodingAgentTask[];
|
|
20
22
|
open?: boolean;
|
|
21
23
|
};
|
|
22
|
-
export type CodingAgentBlockProps = Omit<ComponentProps<"div">, "children"> & {
|
|
24
|
+
export type CodingAgentBlockProps = Omit<ComponentProps<"div">, "children"> & Pick<SidebarProviderProps, "persistOpen" | "keyboardShortcut"> & {
|
|
23
25
|
appName?: string;
|
|
24
26
|
brand?: ReactNode;
|
|
25
27
|
sidebarTop?: ReactNode;
|
|
@@ -182,10 +182,7 @@ function CodingAgentSidebar({ appName, brand, sidebarTop, navigation, projects,
|
|
|
182
182
|
}, project.id))
|
|
183
183
|
})]
|
|
184
184
|
})] }),
|
|
185
|
-
sidebarFooter ? /* @__PURE__ */ jsx(SidebarFooter, {
|
|
186
|
-
className: "border-t border-sidebar-border",
|
|
187
|
-
children: sidebarFooter
|
|
188
|
-
}) : null,
|
|
185
|
+
sidebarFooter ? /* @__PURE__ */ jsx(SidebarFooter, { children: sidebarFooter }) : null,
|
|
189
186
|
/* @__PURE__ */ jsx(SidebarRail, {})
|
|
190
187
|
]
|
|
191
188
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { SidebarProviderProps } from "../ui/sidebar-provider.js";
|
|
2
|
+
import "../ui/sidebar.js";
|
|
1
3
|
import { FileExplorerBreadcrumb, FileExplorerColumn, FileExplorerEntry, FileExplorerEntryDetail, FileExplorerLocation, FileExplorerResolution, FileExplorerSearchResult } from "./file-explorer-data.js";
|
|
2
4
|
import { ComponentProps, ReactNode } from "react";
|
|
3
5
|
//#region src/blocks/file-explorer.d.ts
|
|
4
|
-
export type FileExplorerBlockProps = Omit<ComponentProps<"div">, "children" | "defaultValue" | "onChange"> & {
|
|
6
|
+
export type FileExplorerBlockProps = Omit<ComponentProps<"div">, "children" | "defaultValue" | "onChange"> & Pick<SidebarProviderProps, "persistOpen" | "keyboardShortcut"> & {
|
|
5
7
|
locations: readonly FileExplorerLocation[];
|
|
6
8
|
activeLocationId?: string;
|
|
7
9
|
defaultActiveLocationId?: string;
|
|
@@ -27,6 +29,6 @@ export type FileExplorerBlockProps = Omit<ComponentProps<"div">, "children" | "d
|
|
|
27
29
|
detailsLabel?: string;
|
|
28
30
|
layout?: "viewport" | "contained";
|
|
29
31
|
};
|
|
30
|
-
export declare function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationId, selectedPath, defaultSelectedPath, searchValue, defaultSearchValue, onActiveLocationChange, onSelectedPathChange, onSearchValueChange, onOpenEntry, sidebarTop, sidebarFooter, headerActions, headerMenuItems, searchPlaceholder, searchLabel, clearSearchLabel, searchResultsLabel, searchStatusLabel, backLabel, moreActionsLabel, detailsLabel, layout, className, style, ...props }: FileExplorerBlockProps): import("react").JSX.Element;
|
|
32
|
+
export declare function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationId, selectedPath, defaultSelectedPath, searchValue, defaultSearchValue, onActiveLocationChange, onSelectedPathChange, onSearchValueChange, onOpenEntry, sidebarTop, sidebarFooter, headerActions, headerMenuItems, searchPlaceholder, searchLabel, clearSearchLabel, searchResultsLabel, searchStatusLabel, backLabel, moreActionsLabel, detailsLabel, layout, persistOpen, keyboardShortcut, className, style, ...props }: FileExplorerBlockProps): import("react").JSX.Element;
|
|
31
33
|
//#endregion
|
|
32
34
|
export type { FileExplorerBreadcrumb, FileExplorerColumn, FileExplorerEntry, FileExplorerEntryDetail, FileExplorerLocation, FileExplorerResolution, FileExplorerSearchResult };
|
|
@@ -23,7 +23,7 @@ function defaultSearchStatusLabel(count, query) {
|
|
|
23
23
|
if (count === 0) return `No results for “${query}”`;
|
|
24
24
|
return count === 1 ? "1 result" : `${count} results`;
|
|
25
25
|
}
|
|
26
|
-
function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationId, selectedPath, defaultSelectedPath = [], searchValue, defaultSearchValue = "", onActiveLocationChange, onSelectedPathChange, onSearchValueChange, onOpenEntry, sidebarTop, sidebarFooter, headerActions, headerMenuItems, searchPlaceholder = "Search files", searchLabel = "Search files", clearSearchLabel = "Clear search", searchResultsLabel = "Search results", searchStatusLabel = defaultSearchStatusLabel, backLabel = "Go back", moreActionsLabel = "More actions", detailsLabel = "Details", layout = "viewport", className, style, ...props }) {
|
|
26
|
+
function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationId, selectedPath, defaultSelectedPath = [], searchValue, defaultSearchValue = "", onActiveLocationChange, onSelectedPathChange, onSearchValueChange, onOpenEntry, sidebarTop, sidebarFooter, headerActions, headerMenuItems, searchPlaceholder = "Search files", searchLabel = "Search files", clearSearchLabel = "Clear search", searchResultsLabel = "Search results", searchStatusLabel = defaultSearchStatusLabel, backLabel = "Go back", moreActionsLabel = "More actions", detailsLabel = "Details", layout = "viewport", persistOpen, keyboardShortcut, className, style, ...props }) {
|
|
27
27
|
const [internalLocationId, setInternalLocationId] = useState(defaultActiveLocationId ?? locations[0]?.id);
|
|
28
28
|
const [internalSelectedPath, setInternalSelectedPath] = useState(defaultSelectedPath);
|
|
29
29
|
const [internalSearchValue, setInternalSearchValue] = useState(defaultSearchValue);
|
|
@@ -68,6 +68,8 @@ function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationI
|
|
|
68
68
|
};
|
|
69
69
|
return /* @__PURE__ */ jsxs(SidebarProvider, {
|
|
70
70
|
layout,
|
|
71
|
+
persistOpen,
|
|
72
|
+
keyboardShortcut,
|
|
71
73
|
className: cn("bg-background text-foreground", !contained && "h-svh overflow-hidden", className),
|
|
72
74
|
style: providerStyle,
|
|
73
75
|
...props,
|
|
@@ -145,7 +147,7 @@ function FileExplorerSidebar({ locations, activeLocationId, sidebarTop, sidebarF
|
|
|
145
147
|
collapsible: "offcanvas",
|
|
146
148
|
children: [
|
|
147
149
|
sidebarTop ? /* @__PURE__ */ jsx(SidebarHeader, {
|
|
148
|
-
className: "min-h-11 justify-center
|
|
150
|
+
className: "min-h-11 justify-center px-3",
|
|
149
151
|
children: sidebarTop
|
|
150
152
|
}) : null,
|
|
151
153
|
/* @__PURE__ */ jsx(SidebarContent, { children: groups.map((group) => /* @__PURE__ */ jsxs(SidebarGroup, { children: [group.label ? /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group.label }) : null, /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((location) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
|
|
@@ -157,24 +159,22 @@ function FileExplorerSidebar({ locations, activeLocationId, sidebarTop, sidebarF
|
|
|
157
159
|
},
|
|
158
160
|
children: [location.icon ?? /* @__PURE__ */ jsx(FolderIcon, {}), /* @__PURE__ */ jsx("span", { children: location.label })]
|
|
159
161
|
}) }, location.id)) })] }, group.label || "locations")) }),
|
|
160
|
-
sidebarFooter ? /* @__PURE__ */ jsx(SidebarFooter, {
|
|
161
|
-
className: "border-t border-sidebar-border",
|
|
162
|
-
children: sidebarFooter
|
|
163
|
-
}) : null,
|
|
162
|
+
sidebarFooter ? /* @__PURE__ */ jsx(SidebarFooter, { children: sidebarFooter }) : null,
|
|
164
163
|
/* @__PURE__ */ jsx(SidebarRail, {})
|
|
165
164
|
]
|
|
166
165
|
});
|
|
167
166
|
}
|
|
168
167
|
function FileExplorerHeader({ location, headingLevel, breadcrumbs, query, searchPlaceholder, searchLabel, clearSearchLabel, backLabel, moreActionsLabel, canGoBack, headerActions, headerMenuItems, onBack, onBreadcrumbSelect, onQueryChange }) {
|
|
169
168
|
return /* @__PURE__ */ jsxs("header", {
|
|
170
|
-
className: "shrink-0 border-b border-border/70
|
|
169
|
+
className: "shrink-0 border-b border-border/70",
|
|
171
170
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
172
171
|
className: "flex h-12 items-center gap-2 px-3",
|
|
173
172
|
children: [
|
|
174
|
-
/* @__PURE__ */ jsx(SidebarTrigger, { size: "
|
|
173
|
+
/* @__PURE__ */ jsx(SidebarTrigger, { size: "sm" }),
|
|
175
174
|
/* @__PURE__ */ jsx(Button, {
|
|
176
175
|
variant: "ghost",
|
|
177
|
-
size: "
|
|
176
|
+
size: "sm",
|
|
177
|
+
iconOnly: true,
|
|
178
178
|
className: "hidden @xl/explorer:inline-flex",
|
|
179
179
|
disabled: !canGoBack,
|
|
180
180
|
"aria-label": backLabel,
|
|
@@ -198,7 +198,7 @@ function FileExplorerHeader({ location, headingLevel, breadcrumbs, query, search
|
|
|
198
198
|
}) : null,
|
|
199
199
|
headerMenuItems ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
200
200
|
variant: "ghost",
|
|
201
|
-
size: "
|
|
201
|
+
size: "sm",
|
|
202
202
|
iconOnly: true,
|
|
203
203
|
"aria-label": moreActionsLabel,
|
|
204
204
|
className: headerActions ? "@2xl/explorer:hidden" : void 0,
|
|
@@ -212,7 +212,7 @@ function FileExplorerHeader({ location, headingLevel, breadcrumbs, query, search
|
|
|
212
212
|
})] }) : null,
|
|
213
213
|
searchPlaceholder ? /* @__PURE__ */ jsxs(InputGroup, {
|
|
214
214
|
size: "sm",
|
|
215
|
-
className: "flex-[0_1_9rem]
|
|
215
|
+
className: "flex-[0_1_9rem] @xl/explorer:flex-[0_0_12rem]",
|
|
216
216
|
children: [
|
|
217
217
|
/* @__PURE__ */ jsx(InputGroupAddon, {
|
|
218
218
|
className: "ps-2",
|
|
@@ -406,7 +406,7 @@ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingLeve
|
|
|
406
406
|
})
|
|
407
407
|
}) }) : null })] })]
|
|
408
408
|
}), /* @__PURE__ */ jsx("div", {
|
|
409
|
-
className: "min-h-0 flex-1
|
|
409
|
+
className: "min-h-0 flex-1",
|
|
410
410
|
children: activeColumn ? renderColumn(activeColumn, activeIndex, "narrow") : null
|
|
411
411
|
})] }) : null
|
|
412
412
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -414,7 +414,7 @@ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingLeve
|
|
|
414
414
|
className: "hidden h-full min-h-0 @xl/explorer:block",
|
|
415
415
|
children: wideDisplayed ? /* @__PURE__ */ jsxs(ResizablePanelGroup, {
|
|
416
416
|
orientation: "horizontal",
|
|
417
|
-
|
|
417
|
+
variant: "nested",
|
|
418
418
|
children: [columns.map((column, columnIndex) => /* @__PURE__ */ jsx(FileExplorerPanel, {
|
|
419
419
|
defaultSize: columnSize,
|
|
420
420
|
minSize: "16%",
|
|
@@ -529,7 +529,7 @@ function FileExplorerColumnView({ column, optionRef, onSelect, onOpen, onOptionK
|
|
|
529
529
|
}
|
|
530
530
|
function FileExplorerPreview({ entry, headingLevel }) {
|
|
531
531
|
if (!entry) return /* @__PURE__ */ jsx(Empty, {
|
|
532
|
-
className: "h-full rounded-none
|
|
532
|
+
className: "h-full rounded-none",
|
|
533
533
|
children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
|
|
534
534
|
/* @__PURE__ */ jsx(EmptyMedia, { children: /* @__PURE__ */ jsx(FileIcon, {}) }),
|
|
535
535
|
/* @__PURE__ */ jsx(EmptyTitle, { children: "Select an item" }),
|
|
@@ -537,7 +537,7 @@ function FileExplorerPreview({ entry, headingLevel }) {
|
|
|
537
537
|
] })
|
|
538
538
|
});
|
|
539
539
|
return /* @__PURE__ */ jsx(ScrollArea, {
|
|
540
|
-
className: "h-full
|
|
540
|
+
className: "h-full",
|
|
541
541
|
lockAxis: "x",
|
|
542
542
|
children: /* @__PURE__ */ jsx(FileExplorerPreviewContent, {
|
|
543
543
|
entry,
|
|
@@ -679,7 +679,7 @@ function FileExplorerStatusBar({ breadcrumbs, selectedEntry }) {
|
|
|
679
679
|
const itemCount = selectedEntry?.children?.length ?? 0;
|
|
680
680
|
const itemLabel = itemCount === 1 ? "1 item" : `${itemCount} items`;
|
|
681
681
|
return /* @__PURE__ */ jsxs("footer", {
|
|
682
|
-
className: "flex h-8 shrink-0 items-center gap-3 border-t border-border/70
|
|
682
|
+
className: "flex h-8 shrink-0 items-center gap-3 border-t border-border/70 px-3 text-micro text-muted-foreground",
|
|
683
683
|
children: [/* @__PURE__ */ jsx("span", {
|
|
684
684
|
className: "min-w-0 flex-1 truncate",
|
|
685
685
|
title: path,
|
|
@@ -693,7 +693,7 @@ function FileExplorerStatusBar({ breadcrumbs, selectedEntry }) {
|
|
|
693
693
|
function FileExplorerAddressBar({ breadcrumbs, onSelect }) {
|
|
694
694
|
return /* @__PURE__ */ jsx(Breadcrumb, {
|
|
695
695
|
"aria-label": "Current folder",
|
|
696
|
-
className: "min-w-0 overflow-hidden
|
|
696
|
+
className: "min-w-0 overflow-hidden px-1",
|
|
697
697
|
children: /* @__PURE__ */ jsx(BreadcrumbList, {
|
|
698
698
|
className: "min-w-0 flex-nowrap overflow-hidden",
|
|
699
699
|
children: breadcrumbs.map((breadcrumb, index) => {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Button } from "../ui/button.js";
|
|
2
|
+
import { SidebarProviderProps } from "../ui/sidebar-provider.js";
|
|
3
|
+
import "../ui/sidebar.js";
|
|
2
4
|
import { SettingDefinition, SettingsControlProps, SettingsPageDefinition, SettingsSectionDefinition } from "./settings-data.js";
|
|
3
5
|
import { ComponentProps, ReactNode } from "react";
|
|
4
6
|
//#region src/blocks/settings.d.ts
|
|
5
|
-
export type SettingsBlockProps = Omit<ComponentProps<"div">, "children"> & {
|
|
7
|
+
export type SettingsBlockProps = Omit<ComponentProps<"div">, "children"> & Pick<SidebarProviderProps, "persistOpen" | "keyboardShortcut"> & {
|
|
6
8
|
pages: readonly SettingsPageDefinition[];
|
|
7
9
|
activePageId?: string;
|
|
8
10
|
defaultActivePageId?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
//#region src/knob-contracts/range-knobs.d.ts
|
|
2
|
-
export declare const rangeKnobs: readonly ["--cui-range-level-warning-background", "--cui-range-level-clip-background", "--cui-range-level-size", "--cui-range-level-scale-foreground", "--cui-range-indeterminate-size", "--cui-range-track-radius", "--cui-range-track-background", "--cui-range-indicator-radius", "--cui-range-indicator-background", "--cui-range-thumb-radius", "--cui-range-thumb-background", "--cui-range-thumb-border-color", "--cui-range-thumb-shadow"];
|
|
2
|
+
export declare const rangeKnobs: readonly ["--cui-range-level-warning-background", "--cui-range-level-clip-background", "--cui-range-level-muted-background", "--cui-range-level-size", "--cui-range-level-scale-foreground", "--cui-range-indeterminate-size", "--cui-range-track-radius", "--cui-range-track-background", "--cui-range-indicator-radius", "--cui-range-indicator-background", "--cui-range-thumb-radius", "--cui-range-thumb-background", "--cui-range-thumb-border-color", "--cui-range-thumb-shadow"];
|
|
3
3
|
export type RangeKnobStyle = Partial<Record<(typeof rangeKnobs)[number], string>>;
|
|
4
4
|
//#endregion
|
package/dist/lib/cn.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ClassValue } from "clsx";
|
|
2
|
+
import { Config } from "tailwind-merge";
|
|
2
3
|
//#region src/lib/cn.d.ts
|
|
4
|
+
export declare function withControlUiMerge(config: Config<string, string>): Config<string, string>;
|
|
3
5
|
export declare function cn(...inputs: ClassValue[]): string;
|
|
4
6
|
//#endregion
|
package/dist/lib/cn.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
import { clsx } from "clsx";
|
|
2
|
-
import { extendTailwindMerge } from "tailwind-merge";
|
|
2
|
+
import { extendTailwindMerge, mergeConfigs } from "tailwind-merge";
|
|
3
3
|
//#region src/lib/cn.ts
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
4
|
+
const textScale = [
|
|
5
|
+
"micro",
|
|
6
|
+
"caption",
|
|
7
|
+
"label",
|
|
8
|
+
"body",
|
|
9
|
+
"body-lg",
|
|
10
|
+
"heading-1",
|
|
11
|
+
"heading-2",
|
|
12
|
+
"heading-3",
|
|
13
|
+
"heading-4",
|
|
14
|
+
"display"
|
|
15
|
+
];
|
|
16
|
+
const shadowScale = [
|
|
17
|
+
"pop",
|
|
18
|
+
"soft",
|
|
19
|
+
"modal"
|
|
20
|
+
];
|
|
21
|
+
function withControlUiMerge(config) {
|
|
22
|
+
return mergeConfigs(config, {
|
|
23
|
+
extend: { classGroups: {
|
|
24
|
+
"font-size": [{ text: textScale }],
|
|
25
|
+
shadow: [{ shadow: shadowScale }]
|
|
26
|
+
} },
|
|
27
|
+
override: { conflictingClassGroups: { "font-size": [] } }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const twMerge = extendTailwindMerge(withControlUiMerge);
|
|
26
31
|
function cn(...inputs) {
|
|
27
32
|
return twMerge(clsx(inputs));
|
|
28
33
|
}
|
|
29
34
|
//#endregion
|
|
30
|
-
export { cn };
|
|
35
|
+
export { cn, withControlUiMerge };
|
|
@@ -63,4 +63,16 @@
|
|
|
63
63
|
cursor: not-allowed;
|
|
64
64
|
opacity: var(--disabled-opacity);
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
@media (forced-colors: active) {
|
|
68
|
+
:where([data-control-family="button"][data-control="true"]:not(:focus-visible, :has(:focus-visible))) {
|
|
69
|
+
outline: 1px solid currentColor;
|
|
70
|
+
outline-offset: -1px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:where([data-control-family="button"][data-control="true"][data-active="true"]:not(:focus-visible, :has(:focus-visible))) {
|
|
74
|
+
outline: var(--focus-ring-width) solid Highlight;
|
|
75
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
66
78
|
}
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
--cui-button-shadow: inset 0 0 0 var(--control-rim-width) var(--control-rim), var(--shadow-sm);
|
|
165
165
|
--cui-button-active-background: oklch(from var(--primary) l c h / 0.08);
|
|
166
166
|
--cui-button-active-foreground: var(--primary-text);
|
|
167
|
-
--cui-button-active-shadow: inset 0 0 0
|
|
167
|
+
--cui-button-active-shadow: inset 0 0 0 var(--control-rim-width) oklch(from var(--primary) l c h / 0.6), var(--shadow-sm);
|
|
168
168
|
--cui-button-active-hover-background: oklch(from var(--primary) l c h / 0.1);
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
transition-timing-function: var(--ease-standard);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
@media (forced-colors: active) {
|
|
29
|
+
:where([data-control-family="chat-composer-attachment"][data-slot="root"][data-variant]:not(:focus-visible, :has(:focus-visible))) {
|
|
30
|
+
outline: 1px solid CanvasText;
|
|
31
|
+
outline-offset: -1px;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
:where([data-control-family="chat-composer-attachment"][data-slot="list"]) {
|
|
29
36
|
gap: calc(var(--spacing) * 2);
|
|
30
37
|
padding: var(--cui-chat-composer-attachment-list-padding);
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
box-shadow: var(--cui-color-picker-trigger-shadow);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
@media (forced-colors: active) {
|
|
9
|
+
:where([data-control-family="color-picker"][data-slot="trigger-color"]) {
|
|
10
|
+
outline: 1px solid CanvasText;
|
|
11
|
+
outline-offset: -1px;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
:where([data-control-family="color-picker"][data-slot="trigger"]) {
|
|
9
16
|
border-radius: var(--cui-color-picker-trigger-radius);
|
|
10
17
|
transition-property: transform, translate, scale, rotate;
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
backdrop-filter: var(--cui-field-backdrop-filter);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
@media (forced-colors: active) {
|
|
29
|
+
:where([data-control-family="field"][data-control="true"]:not(:focus-visible, :has(:focus-visible))) {
|
|
30
|
+
outline: 1px solid CanvasText;
|
|
31
|
+
outline-offset: -1px;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
:where(
|
|
29
36
|
[data-control-family="field"][data-control="true"]:not([data-control-family="field"][data-field-kind="autocomplete"]):not(
|
|
30
37
|
[data-control-family="field"][data-field-kind="combobox"]
|
|
@@ -52,6 +52,13 @@
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
@media (forced-colors: active) {
|
|
56
|
+
:where([data-control-family="inline-attachment"][data-slot="root"]:not(:focus-visible)) {
|
|
57
|
+
outline: 1px solid CanvasText;
|
|
58
|
+
outline-offset: -1px;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
:where([data-control-family="inline-attachment"][data-slot="image"]) {
|
|
56
63
|
border-radius: inherit;
|
|
57
64
|
}
|
|
@@ -28,6 +28,13 @@
|
|
|
28
28
|
--cui-kbd-shadow: 0 0 transparent;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
@media (forced-colors: active) {
|
|
32
|
+
:where([data-control-family="kbd"][data-slot="root"]:not([data-variant="ghost"])) {
|
|
33
|
+
outline: 1px solid CanvasText;
|
|
34
|
+
outline-offset: -1px;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
:where([data-control-family="kbd"][data-slot="group"]) {
|
|
32
39
|
gap: calc(var(--spacing) * 1);
|
|
33
40
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
border-radius: var(--cui-pagination-link-radius);
|
|
18
18
|
background: var(--cui-pagination-link-background);
|
|
19
19
|
box-shadow:
|
|
20
|
-
inset 0 0 0
|
|
20
|
+
inset 0 0 0 var(--control-rim-width) var(--_pagination-link-border-color),
|
|
21
21
|
var(--cui-pagination-link-shadow);
|
|
22
22
|
color: var(--cui-pagination-link-foreground);
|
|
23
23
|
font-variant-numeric: tabular-nums;
|
|
@@ -39,6 +39,13 @@
|
|
|
39
39
|
opacity: var(--disabled-opacity);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
@media (forced-colors: active) {
|
|
43
|
+
:where([data-control-family="pagination"][data-slot="link"][data-active="true"]:not(:focus-visible)) {
|
|
44
|
+
outline: var(--focus-ring-width) solid Highlight;
|
|
45
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
:where([data-control-family="pagination"][data-slot="link"]:has(> svg:first-child)) {
|
|
43
50
|
padding-inline-start: calc(var(--spacing) * 2);
|
|
44
51
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
:where([data-control-family="range"][data-range-kind="level-meter"][data-slot="root"]) {
|
|
3
3
|
--cui-range-level-warning-background: var(--warning);
|
|
4
4
|
--cui-range-level-clip-background: var(--destructive);
|
|
5
|
+
--cui-range-level-muted-background: var(--muted-foreground);
|
|
5
6
|
--cui-range-level-size: calc(var(--spacing) * 2);
|
|
6
7
|
--cui-range-level-scale-foreground: var(--muted-foreground);
|
|
7
8
|
--_range-level-peak-size: 2px;
|
|
@@ -96,6 +97,13 @@
|
|
|
96
97
|
);
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
:where(
|
|
101
|
+
[data-range-kind="level-meter"][data-slot="root"][data-muted]
|
|
102
|
+
[data-control-family="range"][data-range-kind="level-meter"]:is([data-slot="bar"], [data-slot="peak"])
|
|
103
|
+
) {
|
|
104
|
+
background: var(--cui-range-level-muted-background);
|
|
105
|
+
}
|
|
106
|
+
|
|
99
107
|
:where([data-control-family="range"][data-range-kind="level-meter"][data-slot="bar"]) {
|
|
100
108
|
clip-path: inset(0 calc((1 - var(--_range-level, 0)) * 100%) 0 0 round var(--cui-range-indicator-radius));
|
|
101
109
|
}
|
|
@@ -145,6 +153,12 @@
|
|
|
145
153
|
initial-value: transparent;
|
|
146
154
|
}
|
|
147
155
|
|
|
156
|
+
@property --cui-range-level-muted-background {
|
|
157
|
+
syntax: "<color>";
|
|
158
|
+
inherits: true;
|
|
159
|
+
initial-value: transparent;
|
|
160
|
+
}
|
|
161
|
+
|
|
148
162
|
@property --cui-range-level-size {
|
|
149
163
|
syntax: "<length-percentage>";
|
|
150
164
|
inherits: true;
|
|
@@ -39,14 +39,12 @@
|
|
|
39
39
|
:where(
|
|
40
40
|
[data-control-family="scroll-area"][data-slot="root"][data-scrollbar-gutter="stable"]
|
|
41
41
|
> [data-slot="scrollbar"][data-orientation="vertical"]:not([data-has-overflow-y])
|
|
42
|
-
> [data-slot="thumb"]
|
|
43
42
|
),
|
|
44
43
|
:where(
|
|
45
44
|
[data-control-family="scroll-area"][data-slot="root"][data-scrollbar-gutter="stable"]
|
|
46
45
|
> [data-slot="scrollbar"][data-orientation="horizontal"]:not([data-has-overflow-x])
|
|
47
|
-
> [data-slot="thumb"]
|
|
48
46
|
) {
|
|
49
|
-
|
|
47
|
+
visibility: hidden;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
@supports (corner-shape: squircle) {
|
|
@@ -114,6 +114,18 @@
|
|
|
114
114
|
opacity: var(--disabled-opacity);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
@media (forced-colors: active) {
|
|
118
|
+
:where([data-control-family="tabs"][data-slot="list"]:not([data-variant="browser"], [data-single="true"])) {
|
|
119
|
+
outline: 1px solid CanvasText;
|
|
120
|
+
outline-offset: -1px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:where([data-control-family="tabs"][data-slot="list"]:not([data-variant="browser"]) [data-slot="indicator"]) {
|
|
124
|
+
outline: var(--focus-ring-width) solid Highlight;
|
|
125
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
117
129
|
:where([data-control-family="tabs"][data-slot="list"][data-variant="browser"]) {
|
|
118
130
|
--_tabs-browser-corner-double: calc(var(--_tabs-trigger-radius-fit) + var(--_tabs-trigger-radius-fit));
|
|
119
131
|
--_tabs-browser-inset: calc(var(--_tabs-trigger-radius-fit) + var(--cui-tabs-surface-radius));
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
|
|
66
66
|
/*
|
|
67
67
|
* History: fixed 3 competing type systems (Tailwind defaults + named rungs + raw text-[Npx]) into one rung/role; 14px = body.
|
|
68
|
-
* Usage:
|
|
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>.
|
|
69
70
|
* Heading LEVEL is the document outline; the RUNG is the look. They are decoupled on purpose: an <h3> may wear text-heading-2.
|
|
70
71
|
* Control chrome text is sized by its recipe (`--cui-button-font-size`, `--cui-field-font-size`), not by these utilities.
|
|
71
72
|
*/
|
package/dist/ui/level-meter.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type LevelMeterProps = Omit<ComponentProps<"div">, "style"> & {
|
|
|
12
12
|
maxDb?: number;
|
|
13
13
|
warningDb?: number;
|
|
14
14
|
clipDb?: number;
|
|
15
|
+
muted?: boolean;
|
|
15
16
|
orientation?: LevelMeterOrientation;
|
|
16
17
|
style?: CSSProperties & RangeKnobStyle;
|
|
17
18
|
};
|
|
@@ -28,7 +29,7 @@ export type LevelMeterScaleProps = Omit<ComponentProps<"div">, "style" | "childr
|
|
|
28
29
|
ticks?: readonly number[];
|
|
29
30
|
style?: CSSProperties & RangeKnobStyle;
|
|
30
31
|
};
|
|
31
|
-
export declare function LevelMeter({ value, source, minDb, maxDb, warningDb, clipDb, orientation, className, style, children, ref, ...props }: LevelMeterProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
32
|
+
export declare function LevelMeter({ value, source, minDb, maxDb, warningDb, clipDb, muted, orientation, className, style, children, ref, ...props }: LevelMeterProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
32
33
|
export declare function LevelMeterTrack({ className, ...props }: LevelMeterTrackProps): import("react").JSX.Element;
|
|
33
34
|
export declare function LevelMeterBar({ className, ...props }: LevelMeterBarProps): import("react").JSX.Element;
|
|
34
35
|
export declare function LevelMeterPeak({ className, ...props }: LevelMeterPeakProps): import("react").JSX.Element;
|
package/dist/ui/level-meter.js
CHANGED
|
@@ -77,7 +77,7 @@ function createLevelMeterPainter() {
|
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
-
function LevelMeter({ value, source, minDb = -60, maxDb = 0, warningDb = -18, clipDb = -6, orientation = "horizontal", className, style, children, ref, ...props }) {
|
|
80
|
+
function LevelMeter({ value, source, minDb = -60, maxDb = 0, warningDb = -18, clipDb = -6, muted = false, orientation = "horizontal", className, style, children, ref, ...props }) {
|
|
81
81
|
const [painter] = useState(createLevelMeterPainter);
|
|
82
82
|
const rootRef = useRef(null);
|
|
83
83
|
const labelled = Boolean(props["aria-label"] || props["aria-labelledby"]);
|
|
@@ -130,6 +130,7 @@ function LevelMeter({ value, source, minDb = -60, maxDb = 0, warningDb = -18, cl
|
|
|
130
130
|
"data-range-kind": "level-meter",
|
|
131
131
|
"data-slot": "root",
|
|
132
132
|
"data-orientation": orientation,
|
|
133
|
+
"data-muted": muted || void 0,
|
|
133
134
|
className: cn("grid", className),
|
|
134
135
|
style: zoneStyle,
|
|
135
136
|
children: /* @__PURE__ */ jsx(LevelMeterScaleContext, {
|
package/dist/ui/sidebar.js
CHANGED
|
@@ -309,7 +309,7 @@ function SidebarContent({ className, children, ...props }) {
|
|
|
309
309
|
className: cn("min-h-0 flex-1 group-data-[collapsible=icon]:overflow-hidden", className),
|
|
310
310
|
lockAxis: "x",
|
|
311
311
|
scrollbarGutter: iconOnly ? "auto" : void 0,
|
|
312
|
-
viewportClassName: "overscroll-y-contain",
|
|
312
|
+
viewportClassName: "data-has-overflow-y:overscroll-y-contain",
|
|
313
313
|
...props,
|
|
314
314
|
children: /* @__PURE__ */ jsx("div", {
|
|
315
315
|
"data-control-ui": "sidebar",
|
package/dist/ui/toggle.js
CHANGED
|
@@ -76,7 +76,7 @@ function ToggleGroup({ className, orientation = "horizontal", indicator, childre
|
|
|
76
76
|
"data-orientation": orientation,
|
|
77
77
|
"data-track": resolvedIndicator,
|
|
78
78
|
orientation,
|
|
79
|
-
className: cn(buttonTrackStructureClasses, "inline-flex items-center
|
|
79
|
+
className: cn(buttonTrackStructureClasses, "inline-flex items-center", orientation === "vertical" && "flex-col", className),
|
|
80
80
|
...props,
|
|
81
81
|
children: [children, tracksHover ? /* @__PURE__ */ jsx(TrackHighlight, { className: "z-0" }) : null]
|
|
82
82
|
})
|
package/dist/ui/typography.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ declare const toneClassNames: {
|
|
|
17
17
|
readonly foreground: "text-foreground";
|
|
18
18
|
readonly muted: "text-muted-foreground";
|
|
19
19
|
readonly primary: "text-primary-text";
|
|
20
|
+
readonly success: "text-success-text";
|
|
21
|
+
readonly warning: "text-warning-text";
|
|
20
22
|
readonly destructive: "text-destructive-text";
|
|
21
23
|
};
|
|
22
24
|
declare const weightClassNames: {
|
package/dist/ui/typography.js
CHANGED
|
@@ -18,6 +18,8 @@ const toneClassNames = {
|
|
|
18
18
|
foreground: "text-foreground",
|
|
19
19
|
muted: "text-muted-foreground",
|
|
20
20
|
primary: "text-primary-text",
|
|
21
|
+
success: "text-success-text",
|
|
22
|
+
warning: "text-warning-text",
|
|
21
23
|
destructive: "text-destructive-text"
|
|
22
24
|
};
|
|
23
25
|
const weightClassNames = {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
language js(typescript, jsx)
|
|
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")
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctrl-ui/react",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
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",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"**/*.css"
|
|
33
33
|
],
|
|
34
34
|
"files": [
|
|
35
|
-
"dist"
|
|
35
|
+
"dist",
|
|
36
|
+
"lint/*.grit"
|
|
36
37
|
],
|
|
37
38
|
"publishConfig": {
|
|
38
39
|
"access": "public"
|