@ctrl-ui/react 0.9.6 → 0.9.9
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 +38 -2
- package/dist/activity.d.ts +3 -1
- package/dist/activity.js +2 -1
- package/dist/blocks/agent-run-sidebar.js +11 -10
- package/dist/blocks/agent-run.d.ts +3 -1
- package/dist/blocks/agent-run.js +21 -13
- package/dist/blocks/coding-agent.js +50 -69
- package/dist/blocks/design-canvas-inspector.js +1 -1
- package/dist/blocks/design-canvas.js +41 -34
- package/dist/blocks/discussion.js +1 -1
- package/dist/blocks/file-explorer.js +66 -47
- package/dist/blocks/settings.js +37 -41
- package/dist/blocks/team-chat.js +2 -1
- package/dist/blocks/theme-toggle.d.ts +2 -1
- package/dist/blocks/theme-toggle.js +13 -4
- package/dist/hooks/use-mobile.d.ts +1 -1
- package/dist/hooks/use-mobile.js +1 -1
- package/dist/knob-contracts/badge-knobs.d.ts +1 -1
- package/dist/knob-contracts/badge-knobs.js +3 -1
- package/dist/knob-contracts/button-knobs.d.ts +1 -1
- package/dist/knob-contracts/button-knobs.js +1 -0
- package/dist/knob-contracts/calendar-knobs.d.ts +1 -1
- package/dist/knob-contracts/calendar-knobs.js +9 -1
- package/dist/knob-contracts/card-knobs.d.ts +1 -1
- package/dist/knob-contracts/card-knobs.js +5 -1
- package/dist/knob-contracts/choice-knobs.d.ts +1 -1
- package/dist/knob-contracts/choice-knobs.js +1 -0
- package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
- package/dist/knob-contracts/collapsible-knobs.js +5 -1
- package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
- package/dist/knob-contracts/emoji-picker-knobs.js +3 -1
- package/dist/knob-contracts/field-knobs.d.ts +1 -1
- package/dist/knob-contracts/field-knobs.js +17 -1
- package/dist/knob-contracts/item-knobs.d.ts +1 -1
- package/dist/knob-contracts/item-knobs.js +6 -1
- package/dist/knob-contracts/kbd-knobs.d.ts +1 -1
- package/dist/knob-contracts/kbd-knobs.js +4 -1
- package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
- package/dist/knob-contracts/page-layout-knobs.js +1 -0
- package/dist/knob-contracts/pagination-knobs.d.ts +1 -1
- package/dist/knob-contracts/pagination-knobs.js +2 -1
- package/dist/knob-contracts/popup-knobs.d.ts +1 -1
- package/dist/knob-contracts/popup-knobs.js +9 -0
- package/dist/knob-contracts/range-knobs.d.ts +1 -1
- package/dist/knob-contracts/range-knobs.js +2 -0
- package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
- package/dist/knob-contracts/sidebar-knobs.js +2 -0
- package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
- package/dist/knob-contracts/tabs-knobs.js +3 -0
- package/dist/knob-contracts/timeline-knobs.d.ts +1 -1
- package/dist/knob-contracts/timeline-knobs.js +4 -1
- package/dist/knob-contracts/tree-knobs.d.ts +1 -1
- package/dist/knob-contracts/tree-knobs.js +3 -1
- package/dist/skin.d.ts +2 -0
- package/dist/styles/index.css +2 -3
- package/dist/styles/recipes/activity.css +4 -0
- package/dist/styles/recipes/avatar.css +1 -1
- package/dist/styles/recipes/badge.css +17 -0
- package/dist/styles/recipes/button.css +8 -1
- package/dist/styles/recipes/calendar.css +82 -6
- package/dist/styles/recipes/card.css +40 -5
- package/dist/styles/recipes/choice.css +9 -2
- package/dist/styles/recipes/collapsible.css +8 -1
- package/dist/styles/recipes/color-picker-values.css +4 -0
- package/dist/styles/recipes/emoji-picker.css +29 -8
- package/dist/styles/recipes/empty.css +5 -0
- package/dist/styles/recipes/field-parts.css +80 -5
- package/dist/styles/recipes/field.css +113 -40
- package/dist/styles/recipes/item.css +44 -5
- package/dist/styles/recipes/kbd.css +26 -6
- package/dist/styles/recipes/morphing-panel.css +1 -1
- package/dist/styles/recipes/page-layout.css +18 -2
- package/dist/styles/recipes/pagination.css +8 -0
- package/dist/styles/recipes/popup-controls.css +41 -0
- package/dist/styles/recipes/popup-drawer.css +1 -0
- package/dist/styles/recipes/popup-lists.css +55 -6
- package/dist/styles/recipes/popup-motion.css +7 -1
- package/dist/styles/recipes/popup.css +10 -0
- package/dist/styles/recipes/range.css +16 -2
- package/dist/styles/recipes/sidebar-mobile.css +1 -1
- package/dist/styles/recipes/sidebar-parts.css +17 -1
- package/dist/styles/recipes/sidebar.css +11 -0
- package/dist/styles/recipes/skeleton.css +8 -0
- package/dist/styles/recipes/tabs.css +41 -8
- package/dist/styles/recipes/timeline.css +25 -2
- package/dist/styles/recipes/tree.css +33 -0
- package/dist/styles/theme.css +11 -1
- package/dist/surface-variants.d.ts +2 -0
- package/dist/surface-variants.js +2 -1
- package/dist/ui/alert-dialog.js +1 -1
- package/dist/ui/badge.js +1 -1
- package/dist/ui/calendar.js +9 -4
- package/dist/ui/card.d.ts +3 -1
- package/dist/ui/card.js +2 -1
- package/dist/ui/context-menu.d.ts +3 -1
- package/dist/ui/context-menu.js +2 -1
- package/dist/ui/dialog.js +1 -1
- package/dist/ui/dropdown-menu.d.ts +6 -3
- package/dist/ui/dropdown-menu.js +25 -7
- package/dist/ui/dropzone.js +10 -11
- package/dist/ui/empty.d.ts +4 -1
- package/dist/ui/empty.js +4 -2
- package/dist/ui/field.d.ts +6 -2
- package/dist/ui/field.js +4 -2
- package/dist/ui/menubar.d.ts +3 -1
- package/dist/ui/menubar.js +2 -1
- package/dist/ui/morphing-panel.d.ts +5 -1
- package/dist/ui/morphing-panel.js +4 -2
- package/dist/ui/select.js +2 -2
- package/dist/ui/sheet.d.ts +9 -0
- package/dist/ui/sheet.js +23 -1
- package/dist/ui/sidebar-mobile.js +1 -1
- package/dist/ui/sidebar-provider.d.ts +3 -1
- package/dist/ui/sidebar-provider.js +3 -2
- package/dist/ui/sidebar-resize-rail.js +1 -1
- package/dist/ui/sidebar.d.ts +6 -5
- package/dist/ui/sidebar.js +10 -9
- package/dist/ui/skeleton.d.ts +4 -1
- package/dist/ui/skeleton.js +8 -2
- package/dist/ui/tabs.d.ts +1 -1
- package/dist/ui/tabs.js +2 -13
- package/dist/ui/timeline.js +1 -1
- package/dist/ui/typography.d.ts +11 -5
- package/dist/ui/typography.js +18 -6
- package/lint/biome-preset.json +3 -0
- package/lint/class-policy.mjs +17 -0
- package/lint/eslint-plugin.mjs +87 -0
- package/lint/grit-rules.mjs +40 -0
- package/lint/no-component-style-classes.grit +16 -0
- package/lint/no-raw-type-classes.grit +2 -3
- package/lint/write-grit-rules.mjs +19 -0
- package/package.json +11 -2
- package/dist/styles/type-scale.css +0 -72
|
@@ -9,7 +9,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
//#region src/ui/sidebar-provider.tsx
|
|
10
10
|
const SIDEBAR_COOKIE_MAX_AGE = 604800;
|
|
11
11
|
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
|
12
|
-
const SIDEBAR_MOBILE_BREAKPOINT = "--breakpoint-
|
|
12
|
+
const SIDEBAR_MOBILE_BREAKPOINT = "--breakpoint-sidebar";
|
|
13
13
|
const SidebarElementsContext = createContext(null);
|
|
14
14
|
const SidebarSurfaceContext = createContext(null);
|
|
15
15
|
function useSidebarElements() {
|
|
@@ -35,7 +35,7 @@ function useSidebar() {
|
|
|
35
35
|
if (!context) throw new Error("useSidebar must be used within a SidebarProvider.");
|
|
36
36
|
return context;
|
|
37
37
|
}
|
|
38
|
-
function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange, defaultWidth, minWidth = 224, maxWidth = 420, width: widthProp, onWidthChange, persistOpen = true, keyboardShortcut = SIDEBAR_KEYBOARD_SHORTCUT, layout = "viewport", skipLinkLabel = "Skip to content", contentId: contentIdProp, ref, className, style, children, ...props }) {
|
|
38
|
+
function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange, defaultWidth, minWidth = 224, maxWidth = 420, width: widthProp, onWidthChange, persistOpen = true, keyboardShortcut = SIDEBAR_KEYBOARD_SHORTCUT, layout = "viewport", surface = "transparent", skipLinkLabel = "Skip to content", contentId: contentIdProp, ref, className, style, children, ...props }) {
|
|
39
39
|
const isMobile = useIsMobile(SIDEBAR_MOBILE_BREAKPOINT);
|
|
40
40
|
const [openMobile, setOpenMobile] = useState(false);
|
|
41
41
|
if (!isMobile && openMobile) setOpenMobile(false);
|
|
@@ -135,6 +135,7 @@ function SidebarProvider({ defaultOpen = true, open: openProp, onOpenChange, def
|
|
|
135
135
|
"data-control-family": "sidebar",
|
|
136
136
|
"data-slot": "wrapper",
|
|
137
137
|
"data-layout": layout,
|
|
138
|
+
"data-surface": surface,
|
|
138
139
|
style: wrapperStyle,
|
|
139
140
|
className: cn("group/sidebar-wrapper relative flex w-full", layout === "contained" ? "h-full min-h-0 overflow-hidden" : "min-h-svh", className),
|
|
140
141
|
children: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("a", {
|
|
@@ -183,7 +183,7 @@ function SidebarResizeRail({ className, ref, onPointerDown, onClick, onKeyDown,
|
|
|
183
183
|
onPointerDown: handlePointerDown,
|
|
184
184
|
onClick: handleClick,
|
|
185
185
|
onKeyDown: handleKeyDown,
|
|
186
|
-
className: cn("absolute inset-y-0 z-20 hidden cursor-col-resize touch-pan-y group-data-[side=left]:-end-1 group-data-[side=right]:-start-1 group-data-[side=left]:group-data-[collapsible=offcanvas]:-end-2 group-data-[side=right]:group-data-[collapsible=offcanvas]:-start-2
|
|
186
|
+
className: cn("absolute inset-y-0 z-20 hidden cursor-col-resize touch-pan-y group-data-[side=left]:-end-1 group-data-[side=right]:-start-1 group-data-[side=left]:group-data-[collapsible=offcanvas]:-end-2 group-data-[side=right]:group-data-[collapsible=offcanvas]:-start-2 sidebar:block", className),
|
|
187
187
|
children: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
188
188
|
ref: widthMeasureRef,
|
|
189
189
|
"aria-hidden": true,
|
package/dist/ui/sidebar.d.ts
CHANGED
|
@@ -8,11 +8,11 @@ import { SidebarMobileNav, SidebarMobileNavItem, SidebarMobileNavProps, SidebarM
|
|
|
8
8
|
import { SidebarLayoutMode, SidebarProvider, SidebarProviderProps, SidebarStyle, useSidebar } from "./sidebar-provider.js";
|
|
9
9
|
import { CSSProperties, ComponentProps } from "react";
|
|
10
10
|
//#region src/ui/sidebar.d.ts
|
|
11
|
-
export type SidebarRailProps = (
|
|
12
|
-
resizable?:
|
|
11
|
+
export type SidebarRailProps = (SidebarResizeRailProps & {
|
|
12
|
+
resizable?: true;
|
|
13
|
+
}) | (Omit<ComponentProps<"button">, "style"> & {
|
|
14
|
+
resizable: false;
|
|
13
15
|
style?: CSSProperties & SidebarKnobStyle;
|
|
14
|
-
}) | (SidebarResizeRailProps & {
|
|
15
|
-
resizable: true;
|
|
16
16
|
});
|
|
17
17
|
export type SidebarInsetProps = Omit<ComponentProps<"main">, "style" | "id"> & {
|
|
18
18
|
render?: RenderProp<ComponentProps<"main">>;
|
|
@@ -33,7 +33,8 @@ export declare function SidebarTrigger({ className, onClick, size, label, ref, .
|
|
|
33
33
|
}): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
34
34
|
export declare function SidebarRail(props: SidebarRailProps): any;
|
|
35
35
|
export declare function SidebarInset({ className, render, ref, ...props }: SidebarInsetProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
36
|
-
export declare function SidebarHeader({ className, ...props }: ComponentProps<"div"> & {
|
|
36
|
+
export declare function SidebarHeader({ className, divider, ...props }: ComponentProps<"div"> & {
|
|
37
|
+
divider?: boolean;
|
|
37
38
|
style?: CSSProperties & SidebarKnobStyle;
|
|
38
39
|
}): import("react").JSX.Element;
|
|
39
40
|
export declare function SidebarFooter({ className, ...props }: ComponentProps<"div"> & {
|
package/dist/ui/sidebar.js
CHANGED
|
@@ -53,7 +53,7 @@ function SidebarSurface({ variant, mobileVariant = "sheet", label = "Navigation"
|
|
|
53
53
|
"data-control-family": "sidebar",
|
|
54
54
|
"data-slot": "container",
|
|
55
55
|
tabIndex: -1,
|
|
56
|
-
className: cn(layout === "contained" ? "absolute h-full" : "fixed h-svh", "inset-y-0 z-10 hidden w-(--sidebar-width)
|
|
56
|
+
className: cn(layout === "contained" ? "absolute h-full" : "fixed h-svh", "inset-y-0 z-10 hidden w-(--sidebar-width) sidebar:flex", side === "left" ? "start-0" : "end-0", padded ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)", className),
|
|
57
57
|
children: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
58
58
|
"data-control-ui": "sidebar",
|
|
59
59
|
"data-control-family": "sidebar",
|
|
@@ -94,7 +94,7 @@ function SidebarSurface({ variant, mobileVariant = "sheet", label = "Navigation"
|
|
|
94
94
|
})]
|
|
95
95
|
});
|
|
96
96
|
return /* @__PURE__ */ jsxs("div", {
|
|
97
|
-
className: cn("group peer hidden
|
|
97
|
+
className: cn("group peer hidden sidebar:block", side === "right" && "order-last"),
|
|
98
98
|
"data-control-ui": "sidebar",
|
|
99
99
|
"data-control-family": "sidebar",
|
|
100
100
|
"data-slot": "root",
|
|
@@ -232,12 +232,12 @@ function SidebarRail(props) {
|
|
|
232
232
|
const { isMobile } = useSidebar();
|
|
233
233
|
const { railContainer } = useSidebarSurface();
|
|
234
234
|
if (isMobile || !railContainer) return null;
|
|
235
|
-
if (props.resizable) {
|
|
236
|
-
const { resizable, ...
|
|
237
|
-
return createPortal(/* @__PURE__ */ jsx(
|
|
235
|
+
if (props.resizable === false) {
|
|
236
|
+
const { resizable, ...toggleProps } = props;
|
|
237
|
+
return createPortal(/* @__PURE__ */ jsx(SidebarToggleRail, { ...toggleProps }), railContainer);
|
|
238
238
|
}
|
|
239
|
-
const { resizable, ...
|
|
240
|
-
return createPortal(/* @__PURE__ */ jsx(
|
|
239
|
+
const { resizable, ...resizeProps } = props;
|
|
240
|
+
return createPortal(/* @__PURE__ */ jsx(SidebarResizeRail, { ...resizeProps }), railContainer);
|
|
241
241
|
}
|
|
242
242
|
function SidebarToggleRail({ className, ref, onClick, ...props }) {
|
|
243
243
|
const { toggleSidebar, open } = useSidebar();
|
|
@@ -259,7 +259,7 @@ function SidebarToggleRail({ className, ref, onClick, ...props }) {
|
|
|
259
259
|
if (!event.defaultPrevented) toggleSidebar();
|
|
260
260
|
},
|
|
261
261
|
title: props.title ?? "Toggle sidebar",
|
|
262
|
-
className: cn("absolute inset-y-0 z-20 hidden -translate-x-1/2 rtl:translate-x-1/2 group-data-[side=left]:-end-4 group-data-[side=right]:start-0
|
|
262
|
+
className: cn("absolute inset-y-0 z-20 hidden -translate-x-1/2 rtl:translate-x-1/2 group-data-[side=left]:-end-4 group-data-[side=right]:start-0 sidebar:flex", "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", "group-data-[collapsible=offcanvas]:translate-x-0", className)
|
|
263
263
|
}
|
|
264
264
|
});
|
|
265
265
|
}
|
|
@@ -281,11 +281,12 @@ function SidebarInset({ className, render, ref, ...props }) {
|
|
|
281
281
|
}
|
|
282
282
|
});
|
|
283
283
|
}
|
|
284
|
-
function SidebarHeader({ className, ...props }) {
|
|
284
|
+
function SidebarHeader({ className, divider = false, ...props }) {
|
|
285
285
|
return /* @__PURE__ */ jsx("div", {
|
|
286
286
|
"data-control-ui": "sidebar",
|
|
287
287
|
"data-control-family": "sidebar",
|
|
288
288
|
"data-slot": "header",
|
|
289
|
+
"data-divider": divider ? "" : void 0,
|
|
289
290
|
className: cn("flex flex-col", className),
|
|
290
291
|
...props
|
|
291
292
|
});
|
package/dist/ui/skeleton.d.ts
CHANGED
|
@@ -3,9 +3,12 @@ import { CSSProperties, ComponentProps } from "react";
|
|
|
3
3
|
//#region src/ui/skeleton.d.ts
|
|
4
4
|
export declare const skeletonVariants: readonly ["shimmer", "pulse", "none"];
|
|
5
5
|
export type SkeletonVariant = (typeof skeletonVariants)[number];
|
|
6
|
+
export declare const skeletonShapes: readonly ["default", "circle", "panel"];
|
|
7
|
+
export type SkeletonShape = (typeof skeletonShapes)[number];
|
|
6
8
|
export type SkeletonProps = Omit<ComponentProps<"div">, "style"> & {
|
|
7
9
|
style?: CSSProperties & SkeletonKnobStyle;
|
|
8
10
|
variant?: SkeletonVariant;
|
|
11
|
+
shape?: SkeletonShape;
|
|
9
12
|
};
|
|
10
|
-
export declare function Skeleton({ variant, className, ...props }: SkeletonProps): import("react").JSX.Element;
|
|
13
|
+
export declare function Skeleton({ variant, shape, className, ...props }: SkeletonProps): import("react").JSX.Element;
|
|
11
14
|
//#endregion
|
package/dist/ui/skeleton.js
CHANGED
|
@@ -5,16 +5,22 @@ const skeletonVariants = [
|
|
|
5
5
|
"pulse",
|
|
6
6
|
"none"
|
|
7
7
|
];
|
|
8
|
-
|
|
8
|
+
const skeletonShapes = [
|
|
9
|
+
"default",
|
|
10
|
+
"circle",
|
|
11
|
+
"panel"
|
|
12
|
+
];
|
|
13
|
+
function Skeleton({ variant = "shimmer", shape = "default", className, ...props }) {
|
|
9
14
|
return /* @__PURE__ */ jsx("div", {
|
|
10
15
|
"aria-hidden": "true",
|
|
11
16
|
"data-control-ui": "skeleton",
|
|
12
17
|
"data-control-family": "skeleton",
|
|
13
18
|
"data-slot": "root",
|
|
14
19
|
"data-variant": variant,
|
|
20
|
+
"data-shape": shape,
|
|
15
21
|
className,
|
|
16
22
|
...props
|
|
17
23
|
});
|
|
18
24
|
}
|
|
19
25
|
//#endregion
|
|
20
|
-
export { Skeleton, skeletonVariants };
|
|
26
|
+
export { Skeleton, skeletonShapes, skeletonVariants };
|
package/dist/ui/tabs.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type TabsSurfaceProps = Omit<ComponentProps<"div">, "style"> & {
|
|
|
37
37
|
};
|
|
38
38
|
export declare function TabsSurface({ className, ...props }: TabsSurfaceProps): import("react").JSX.Element;
|
|
39
39
|
export declare function Tabs<TValue extends string = string>({ className, onValueChange, children, ...props }: TabsProps<TValue>): import("react").JSX.Element;
|
|
40
|
-
export declare function TabsList({ size, variant, className, children,
|
|
40
|
+
export declare function TabsList({ size, variant, className, children, ...props }: TabsListProps): import("react").JSX.Element;
|
|
41
41
|
export declare function TabsTab({ className, ...props }: TabsTabProps): import("react").JSX.Element;
|
|
42
42
|
export declare function TabsPanel({ className, value, ref, ...props }: TabsPanelProps): import("react").JSX.Element;
|
|
43
43
|
//#endregion
|
package/dist/ui/tabs.js
CHANGED
|
@@ -109,12 +109,6 @@ function Tabs({ className, onValueChange, children, ...props }) {
|
|
|
109
109
|
})
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
|
-
const controlHeights = {
|
|
113
|
-
xs: "var(--control-h-xs)",
|
|
114
|
-
sm: "var(--control-h-sm)",
|
|
115
|
-
md: "var(--control-h-md)",
|
|
116
|
-
lg: "var(--control-h-lg)"
|
|
117
|
-
};
|
|
118
112
|
function countTabs(children) {
|
|
119
113
|
return Children.toArray(children).reduce((count, child) => {
|
|
120
114
|
if (!isValidElement(child)) return count;
|
|
@@ -123,12 +117,8 @@ function countTabs(children) {
|
|
|
123
117
|
return count;
|
|
124
118
|
}, 0);
|
|
125
119
|
}
|
|
126
|
-
function TabsList({ size = "sm", variant = "default", className, children,
|
|
120
|
+
function TabsList({ size = "sm", variant = "default", className, children, ...props }) {
|
|
127
121
|
const isSingle = countTabs(children) === 1;
|
|
128
|
-
const controlStyle = {
|
|
129
|
-
"--_tabs-trigger-h": controlHeights[size],
|
|
130
|
-
...style
|
|
131
|
-
};
|
|
132
122
|
return /* @__PURE__ */ jsxs(Tabs$1.List, {
|
|
133
123
|
"data-control-ui": "tabs",
|
|
134
124
|
"data-control-family": "tabs",
|
|
@@ -137,13 +127,12 @@ function TabsList({ size = "sm", variant = "default", className, children, style
|
|
|
137
127
|
"data-variant": variant,
|
|
138
128
|
"data-single": isSingle ? "true" : void 0,
|
|
139
129
|
className: cn(variant === "browser" && "gap-0 p-0 px-(--_tabs-browser-inset) pt-(--cui-tabs-list-padding)", className),
|
|
140
|
-
style: controlStyle,
|
|
141
130
|
...props,
|
|
142
131
|
children: [children, isSingle ? null : /* @__PURE__ */ jsx(Tabs$1.Indicator, {
|
|
143
132
|
"data-control-ui": "tabs",
|
|
144
133
|
"data-control-family": "tabs",
|
|
145
134
|
"data-slot": "indicator",
|
|
146
|
-
className: variant === "browser" ? "top-auto bottom-0 left-(--_tabs-browser-indicator-x) z-0 h-(--
|
|
135
|
+
className: variant === "browser" ? "top-auto bottom-0 left-(--_tabs-browser-indicator-x) z-0 h-(--cui-tabs-trigger-height) w-(--_tabs-browser-indicator-width) transform-none" : void 0
|
|
147
136
|
})]
|
|
148
137
|
});
|
|
149
138
|
}
|
package/dist/ui/timeline.js
CHANGED
|
@@ -41,7 +41,7 @@ function TimelineSeparator({ className, ...props }) {
|
|
|
41
41
|
"data-control-family": "timeline",
|
|
42
42
|
"data-slot": "separator",
|
|
43
43
|
...props,
|
|
44
|
-
className: cn("absolute top-5 bottom-0
|
|
44
|
+
className: cn("absolute top-5 bottom-0 col-start-1 group-last/timeline-item:hidden", className)
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
function TimelineContent({ className, ...props }) {
|
package/dist/ui/typography.d.ts
CHANGED
|
@@ -30,21 +30,27 @@ export type TypeSize = keyof typeof sizeClassNames;
|
|
|
30
30
|
export type TypeTone = keyof typeof toneClassNames;
|
|
31
31
|
export type TextWeight = keyof typeof weightClassNames;
|
|
32
32
|
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
33
|
-
|
|
33
|
+
type TypeFace = {
|
|
34
|
+
/** Sets the text in the monospace face, for code, paths, and identifiers. */
|
|
35
|
+
mono?: boolean;
|
|
36
|
+
/** Uses tabular figures so changing numbers keep their width. */
|
|
37
|
+
numeric?: boolean;
|
|
38
|
+
};
|
|
39
|
+
export type HeadingProps = ComponentProps<"h1"> & TypeFace & {
|
|
34
40
|
level: HeadingLevel;
|
|
35
41
|
size?: TypeSize;
|
|
36
42
|
weight?: TextWeight;
|
|
37
43
|
tone?: TypeTone;
|
|
38
44
|
};
|
|
39
|
-
export declare function headingClassName({ level, size, weight, tone, className }: HeadingProps): string;
|
|
40
|
-
export declare function Heading({ level, size, weight, tone, className, ...props }: HeadingProps): import("react").JSX.Element;
|
|
45
|
+
export declare function headingClassName({ level, size, weight, tone, mono, numeric, className }: HeadingProps): string;
|
|
46
|
+
export declare function Heading({ level, size, weight, tone, mono, numeric, className, ...props }: HeadingProps): import("react").JSX.Element;
|
|
41
47
|
export type TextTag = "p" | "span" | "div" | "small" | "strong" | "em" | "code" | "li" | "dt" | "dd" | "figcaption" | "legend" | "blockquote";
|
|
42
|
-
export type TextProps = HTMLAttributes<HTMLElement> & {
|
|
48
|
+
export type TextProps = HTMLAttributes<HTMLElement> & TypeFace & {
|
|
43
49
|
ref?: Ref<HTMLElement>;
|
|
44
50
|
as?: TextTag;
|
|
45
51
|
size?: TypeSize;
|
|
46
52
|
weight?: TextWeight;
|
|
47
53
|
tone?: TypeTone;
|
|
48
54
|
};
|
|
49
|
-
export declare function Text({ as: Tag, size, weight, tone, className, ref, ...props }: TextProps): import("react").JSX.Element;
|
|
55
|
+
export declare function Text({ as: Tag, size, weight, tone, mono, numeric, className, ref, ...props }: TextProps): import("react").JSX.Element;
|
|
50
56
|
//#endregion
|
package/dist/ui/typography.js
CHANGED
|
@@ -35,10 +35,17 @@ const headingSizeByLevel = {
|
|
|
35
35
|
5: "heading-4",
|
|
36
36
|
6: "heading-4"
|
|
37
37
|
};
|
|
38
|
-
function
|
|
39
|
-
return cn(
|
|
38
|
+
function faceClassNames({ mono, numeric }) {
|
|
39
|
+
return cn(mono && "font-mono", numeric && "tabular-nums");
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function headingClassName({ level, size = headingSizeByLevel[level], weight, tone = "default", mono, numeric, className }) {
|
|
42
|
+
return cn(sizeClassNames[size], weight ? weightClassNames[weight] : void 0, toneClassNames[tone], faceClassNames({
|
|
43
|
+
mono,
|
|
44
|
+
numeric
|
|
45
|
+
}), className);
|
|
46
|
+
}
|
|
47
|
+
const programmaticFocusTarget = "[&[tabindex='-1']]:outline-none";
|
|
48
|
+
function Heading({ level, size, weight, tone, mono, numeric, className, ...props }) {
|
|
42
49
|
const Tag = `h${level}`;
|
|
43
50
|
return /* @__PURE__ */ jsx(Tag, {
|
|
44
51
|
"data-control-ui": "typography",
|
|
@@ -49,7 +56,9 @@ function Heading({ level, size, weight, tone, className, ...props }) {
|
|
|
49
56
|
size,
|
|
50
57
|
weight,
|
|
51
58
|
tone,
|
|
52
|
-
|
|
59
|
+
mono,
|
|
60
|
+
numeric,
|
|
61
|
+
className: cn(programmaticFocusTarget, className)
|
|
53
62
|
}),
|
|
54
63
|
...props
|
|
55
64
|
});
|
|
@@ -58,13 +67,16 @@ function attachRef(ref, node) {
|
|
|
58
67
|
if (typeof ref === "function") return ref(node);
|
|
59
68
|
if (ref) ref.current = node;
|
|
60
69
|
}
|
|
61
|
-
function Text({ as: Tag = "span", size = "body", weight, tone = "default", className, ref, ...props }) {
|
|
70
|
+
function Text({ as: Tag = "span", size = "body", weight, tone = "default", mono, numeric, className, ref, ...props }) {
|
|
62
71
|
return /* @__PURE__ */ jsx(Tag, {
|
|
63
72
|
ref: ref ? (node) => attachRef(ref, node) : void 0,
|
|
64
73
|
"data-control-ui": "typography",
|
|
65
74
|
"data-control-family": "typography",
|
|
66
75
|
"data-slot": "text",
|
|
67
|
-
className: cn(sizeClassNames[size], weight ? weightClassNames[weight] : void 0, toneClassNames[tone],
|
|
76
|
+
className: cn(sizeClassNames[size], weight ? weightClassNames[weight] : void 0, toneClassNames[tone], faceClassNames({
|
|
77
|
+
mono,
|
|
78
|
+
numeric
|
|
79
|
+
}), className),
|
|
68
80
|
...props
|
|
69
81
|
});
|
|
70
82
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const controlUiImportPath = `(?:@ctrl-ui/react|[^'"]*components/control-ui)/`;
|
|
2
|
+
|
|
3
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4
|
+
|
|
5
|
+
export function controlUiImportPattern(importSources = []) {
|
|
6
|
+
return `(?:${[controlUiImportPath, ...importSources.map(escapeRegExp)].join("|")})`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const componentStyleClass = String.raw`[^\s"'\x60]!(?:[\s"'\x60]|$)|(?:^|[\s"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:foreground|muted|primary|secondary|accent|destructive|success|warning|info|card|popover|sidebar|background|canvas|current|inherit|transparent|white|black|[a-z]+-\d{2,3}|\[(?:#|oklch|rgb|hsl|lab|lch|color|var))|font-|leading-|tracking-|border|divide-|rounded|shadow|ring\b|ring-|outline|opacity-|backdrop-|fill-|stroke-|decoration-|underline|line-through|italic|uppercase|lowercase|capitalize|tabular-nums|(?:hover|focus|focus-visible|focus-within|active|disabled|open|checked|dark|placeholder|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)`;
|
|
10
|
+
|
|
11
|
+
export const rawTypeClass = String.raw`(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b|(?:^|[^\w-])text-\[\d[^\]]*\]`;
|
|
12
|
+
|
|
13
|
+
export const componentStyleMessage =
|
|
14
|
+
"Control UI component styled through className: color, type, padding, borders, radius, shadows, state styles and !important belong to the component. Use its props or a --cui-* knob; keep className for layout (flex, gap, width, margin, placement).";
|
|
15
|
+
|
|
16
|
+
export const rawTypeMessage =
|
|
17
|
+
"Text size in className: render text through <Text>/<Heading> on the type scale, or size the control with its size prop or --cui-*-font-size knob.";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { componentStyleClass, componentStyleMessage, controlUiImportPattern, rawTypeClass, rawTypeMessage } from "./class-policy.mjs";
|
|
2
|
+
|
|
3
|
+
const componentStyle = new RegExp(componentStyleClass);
|
|
4
|
+
const rawType = new RegExp(rawTypeClass);
|
|
5
|
+
|
|
6
|
+
function stringsIn(node) {
|
|
7
|
+
if (Array.isArray(node)) return node.flatMap(stringsIn);
|
|
8
|
+
if (!node || typeof node !== "object") return [];
|
|
9
|
+
if (node.type === "Literal") return typeof node.value === "string" ? [node.value] : [];
|
|
10
|
+
if (node.type === "TemplateElement") return [node.value.cooked ?? node.value.raw];
|
|
11
|
+
return Object.entries(node).flatMap(([key, child]) => (key === "parent" ? [] : stringsIn(child)));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function classNameMatches(attribute, pattern) {
|
|
15
|
+
if (attribute.name.name !== "className" || !attribute.value) return false;
|
|
16
|
+
return stringsIn(attribute.value).some((text) => pattern.test(text));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function elementRootName(name) {
|
|
20
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
21
|
+
if (name.type === "JSXMemberExpression") return elementRootName(name.object);
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function controlUiLocalNames(program, controlUiImport) {
|
|
26
|
+
const names = new Set();
|
|
27
|
+
for (const statement of program.body) {
|
|
28
|
+
if (statement.type !== "ImportDeclaration" || statement.importKind === "type") continue;
|
|
29
|
+
if (!controlUiImport.test(statement.source.value)) continue;
|
|
30
|
+
for (const specifier of statement.specifiers) names.add(specifier.local.name);
|
|
31
|
+
}
|
|
32
|
+
return names;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const noComponentStyleClasses = {
|
|
36
|
+
meta: {
|
|
37
|
+
type: "problem",
|
|
38
|
+
docs: { description: "Keep className on Control UI components to layout; the component owns its look." },
|
|
39
|
+
messages: { componentStyle: componentStyleMessage },
|
|
40
|
+
schema: [
|
|
41
|
+
{
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: { importSources: { type: "array", items: { type: "string" } } },
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
create(context) {
|
|
49
|
+
const controlUiImport = new RegExp(`^${controlUiImportPattern(context.options[0]?.importSources)}`);
|
|
50
|
+
let controlUiNames = new Set();
|
|
51
|
+
return {
|
|
52
|
+
Program(program) {
|
|
53
|
+
controlUiNames = controlUiLocalNames(program, controlUiImport);
|
|
54
|
+
},
|
|
55
|
+
JSXAttribute(attribute) {
|
|
56
|
+
if (!controlUiNames.has(elementRootName(attribute.parent.name))) return;
|
|
57
|
+
if (classNameMatches(attribute, componentStyle)) context.report({ node: attribute, messageId: "componentStyle" });
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const noRawTypeClasses = {
|
|
64
|
+
meta: {
|
|
65
|
+
type: "problem",
|
|
66
|
+
docs: { description: "Size text through the Control UI type scale instead of text-* classes." },
|
|
67
|
+
messages: { rawType: rawTypeMessage },
|
|
68
|
+
schema: [],
|
|
69
|
+
},
|
|
70
|
+
create(context) {
|
|
71
|
+
return {
|
|
72
|
+
JSXAttribute(attribute) {
|
|
73
|
+
if (classNameMatches(attribute, rawType)) context.report({ node: attribute, messageId: "rawType" });
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const plugin = {
|
|
80
|
+
meta: { name: "control-ui" },
|
|
81
|
+
rules: {
|
|
82
|
+
"no-component-style-classes": noComponentStyleClasses,
|
|
83
|
+
"no-raw-type-classes": noRawTypeClasses,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default plugin;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { componentStyleClass, componentStyleMessage, controlUiImportPattern, rawTypeClass, rawTypeMessage } from "./class-policy.mjs";
|
|
2
|
+
|
|
3
|
+
function gritRegex(pattern) {
|
|
4
|
+
return `r"(?s).*(?:${pattern.replaceAll('"', '\\"')}).*"`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function gritString(text) {
|
|
8
|
+
return JSON.stringify(text);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const stringTexts = "or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() }";
|
|
12
|
+
|
|
13
|
+
export function gritRules(importSources = []) {
|
|
14
|
+
return {
|
|
15
|
+
"no-component-style-classes.grit": `language js(typescript, jsx)
|
|
16
|
+
|
|
17
|
+
or {
|
|
18
|
+
JsxSelfClosingElement(name=$name, attributes=$attributes),
|
|
19
|
+
JsxOpeningElement(name=$name, attributes=$attributes)
|
|
20
|
+
} as $element where {
|
|
21
|
+
$name <: r"([A-Z]\\w*)(?:\\.[\\w.]+)?"($component),
|
|
22
|
+
$attributes <: some JsxAttribute(name=\`className\`) as $attr,
|
|
23
|
+
$attr <: contains ${stringTexts} as $text where { $text <: ${gritRegex(componentStyleClass)} },
|
|
24
|
+
$element <: within JsModule() as $module,
|
|
25
|
+
$module <: contains JsImport() as $import where {
|
|
26
|
+
$import <: ${gritRegex(String.raw`from\s+['"]${controlUiImportPattern(importSources)}`)},
|
|
27
|
+
$import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
|
|
28
|
+
},
|
|
29
|
+
register_diagnostic(span=$attr, message=${gritString(componentStyleMessage)}, severity="error")
|
|
30
|
+
}
|
|
31
|
+
`,
|
|
32
|
+
"no-raw-type-classes.grit": `language js(typescript, jsx)
|
|
33
|
+
|
|
34
|
+
JsxAttribute(name=\`className\`) as $attr where {
|
|
35
|
+
$attr <: contains ${stringTexts} as $text where { $text <: ${gritRegex(rawTypeClass)} },
|
|
36
|
+
register_diagnostic(span=$attr, message=${gritString(rawTypeMessage)}, severity="error")
|
|
37
|
+
}
|
|
38
|
+
`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
language js(typescript, jsx)
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
JsxSelfClosingElement(name=$name, attributes=$attributes),
|
|
5
|
+
JsxOpeningElement(name=$name, attributes=$attributes)
|
|
6
|
+
} as $element where {
|
|
7
|
+
$name <: r"([A-Z]\w*)(?:\.[\w.]+)?"($component),
|
|
8
|
+
$attributes <: some JsxAttribute(name=`className`) as $attr,
|
|
9
|
+
$attr <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() } as $text where { $text <: r"(?s).*(?:[^\s\"'\x60]!(?:[\s\"'\x60]|$)|(?:^|[\s\"'\x60])![\w\[-]|(?:^|[^\w-])(?:p[xytrblse]?-|bg-[\w\[]|text-(?:foreground|muted|primary|secondary|accent|destructive|success|warning|info|card|popover|sidebar|background|canvas|current|inherit|transparent|white|black|[a-z]+-\d{2,3}|\[(?:#|oklch|rgb|hsl|lab|lch|color|var))|font-|leading-|tracking-|border|divide-|rounded|shadow|ring\b|ring-|outline|opacity-|backdrop-|fill-|stroke-|decoration-|underline|line-through|italic|uppercase|lowercase|capitalize|tabular-nums|(?:hover|focus|focus-visible|focus-within|active|disabled|open|checked|dark|placeholder|aria-[^:\s]*|data-[^:\s]*|group-[^:\s]*|peer-[^:\s]*|has-[^:\s]*|\*|\[&[^\s]*?\]):)).*" },
|
|
10
|
+
$element <: within JsModule() as $module,
|
|
11
|
+
$module <: contains JsImport() as $import where {
|
|
12
|
+
$import <: r"(?s).*(?:from\s+['\"](?:(?:@ctrl-ui/react|[^'\"]*components/control-ui)/)).*",
|
|
13
|
+
$import <: contains JsIdentifierBinding() as $binding where { $binding <: $component }
|
|
14
|
+
},
|
|
15
|
+
register_diagnostic(span=$attr, message="Control UI component styled through className: color, type, padding, borders, radius, shadows, state styles and !important belong to the component. Use its props or a --cui-* knob; keep className for layout (flex, gap, width, margin, placement).", severity="error")
|
|
16
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
language js(typescript, jsx)
|
|
2
2
|
|
|
3
|
-
JsxAttribute(name
|
|
4
|
-
$
|
|
5
|
-
$value <: contains r".*(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b.*|.*(?:^|[^\w-])text-\[\d[^\]]*\].*",
|
|
3
|
+
JsxAttribute(name=`className`) as $attr where {
|
|
4
|
+
$attr <: contains or { JsxString(), JsStringLiteralExpression(), JsTemplateChunkElement() } as $text where { $text <: r"(?s).*(?:(?:^|[^\w-])text-(?:micro|caption|label|body|body-lg|heading-[1-4]|display|xs|sm|base|lg|xl|[2-9]xl)\b|(?:^|[^\w-])text-\[\d[^\]]*\]).*" },
|
|
6
5
|
register_diagnostic(span=$attr, message="Text size in className: render text through <Text>/<Heading> on the type scale, or size the control with its size prop or --cui-*-font-size knob.", severity="error")
|
|
7
6
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { gritRules } from "./grit-rules.mjs";
|
|
6
|
+
|
|
7
|
+
const { values } = parseArgs({
|
|
8
|
+
options: {
|
|
9
|
+
"import-source": { type: "string", multiple: true, default: [] },
|
|
10
|
+
out: { type: "string" },
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!values.out) throw new Error("control-ui-grit needs --out <directory>");
|
|
15
|
+
|
|
16
|
+
mkdirSync(values.out, { recursive: true });
|
|
17
|
+
for (const [file, source] of Object.entries(gritRules(values["import-source"]))) {
|
|
18
|
+
writeFileSync(path.join(values.out, file), source);
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctrl-ui/react",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "100+ React components for Tailwind CSS and shadcn/ui: pickers, trees, editors, canvases, app templates, and AI chat surfaces, with neutral defaults and consumer-owned themes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -28,12 +28,17 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://control-ui.dev",
|
|
30
30
|
"type": "module",
|
|
31
|
+
"bin": {
|
|
32
|
+
"control-ui-grit": "./lint/write-grit-rules.mjs"
|
|
33
|
+
},
|
|
31
34
|
"sideEffects": [
|
|
32
35
|
"**/*.css"
|
|
33
36
|
],
|
|
34
37
|
"files": [
|
|
35
38
|
"dist",
|
|
36
|
-
"lint/*.grit"
|
|
39
|
+
"lint/*.grit",
|
|
40
|
+
"lint/*.mjs",
|
|
41
|
+
"lint/biome-preset.json"
|
|
37
42
|
],
|
|
38
43
|
"publishConfig": {
|
|
39
44
|
"access": "public"
|
|
@@ -41,6 +46,8 @@
|
|
|
41
46
|
"exports": {
|
|
42
47
|
"./package.json": "./package.json",
|
|
43
48
|
"./styles/*": "./dist/styles/*",
|
|
49
|
+
"./eslint-plugin": "./lint/eslint-plugin.mjs",
|
|
50
|
+
"./biome": "./lint/biome-preset.json",
|
|
44
51
|
"./*": {
|
|
45
52
|
"types": "./dist/*.d.ts",
|
|
46
53
|
"default": "./dist/*.js"
|
|
@@ -182,6 +189,8 @@
|
|
|
182
189
|
"devDependencies": {
|
|
183
190
|
"@types/bun": "^1.4.2",
|
|
184
191
|
"@types/react": "^19.3.0",
|
|
192
|
+
"eslint": "10.10.0",
|
|
193
|
+
"oxlint": "1.81.0",
|
|
185
194
|
"tsdown": "0.23.0"
|
|
186
195
|
}
|
|
187
196
|
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Type scale, canonical excerpt. Tokens live in the installed theme; this is the docs-facing font-size-only slice.
|
|
3
|
-
* ONE rung per size by ROLE — only source of font-size; never text-[Npx] or Tailwind's text-xs/sm defaults. Paired --line-height/--font-weight ride along per rung.
|
|
4
|
-
*/
|
|
5
|
-
@theme {
|
|
6
|
-
/* Body/UI family: 10-16px band, ~95% of interface text; line-height stays a utility (leading-*) for per-surface density. */
|
|
7
|
-
--text-micro: 0.625rem; /* 10px — kbd, badge counters, dense numeric meta */
|
|
8
|
-
--text-caption: 0.6875rem; /* 11px — overlines, timestamps, section / group labels */
|
|
9
|
-
--text-label: 0.75rem; /* 12px — form labels + small chrome (nav item, tooltip, shortcut) */
|
|
10
|
-
--text-body: 0.875rem; /* 14px — DEFAULT body & control text (the workhorse) */
|
|
11
|
-
--text-body-lg: 1rem; /* 16px — emphasized / larger body */
|
|
12
|
-
|
|
13
|
-
/* Heading family: HIERARCHY, numbered (heading-1=largest); each pairs line-height+weight so `text-heading-N`
|
|
14
|
-
carries all three, letting a skin retune headings by re-valuing these alone. */
|
|
15
|
-
--text-heading-4: 0.9375rem; /* 15px */
|
|
16
|
-
--text-heading-4--line-height: 1.4;
|
|
17
|
-
--text-heading-4--font-weight: 600;
|
|
18
|
-
--text-heading-3: 1.125rem; /* 18px */
|
|
19
|
-
--text-heading-3--line-height: 1.35;
|
|
20
|
-
--text-heading-3--font-weight: 600;
|
|
21
|
-
--text-heading-2: 1.375rem; /* 22px */
|
|
22
|
-
--text-heading-2--line-height: 1.25;
|
|
23
|
-
--text-heading-2--font-weight: 600;
|
|
24
|
-
--text-heading-1: 1.875rem; /* 30px — content h1 */
|
|
25
|
-
--text-heading-1--line-height: 1.15;
|
|
26
|
-
--text-heading-1--font-weight: 700;
|
|
27
|
-
--text-heading-1--letter-spacing: -0.01em;
|
|
28
|
-
|
|
29
|
-
/* display — the biggest rung, above the content h1: page titles (PageHeader) and heroes. */
|
|
30
|
-
--text-display: 2.25rem; /* 36px */
|
|
31
|
-
--text-display--line-height: 1.1;
|
|
32
|
-
--text-display--font-weight: 600;
|
|
33
|
-
--text-display--letter-spacing: -0.02em;
|
|
34
|
-
|
|
35
|
-
/* Font ROLES: body+display default to --font-sans; brand re-values ONE token for headings(font-display)/body(font-body)
|
|
36
|
-
own face. */
|
|
37
|
-
--font-body: var(--font-sans);
|
|
38
|
-
--font-display: var(--font-sans);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/*
|
|
42
|
-
* Heading rungs also carry the display face and balanced wrapping (Tailwind merges this into the same `text-heading-N` class),
|
|
43
|
-
* so a heading is ONE class: `text-heading-2`, never `text-heading-2 font-display text-balance font-semibold`.
|
|
44
|
-
*/
|
|
45
|
-
@utility text-display {
|
|
46
|
-
font-family: var(--font-display);
|
|
47
|
-
text-wrap: balance;
|
|
48
|
-
}
|
|
49
|
-
@utility text-heading-1 {
|
|
50
|
-
font-family: var(--font-display);
|
|
51
|
-
text-wrap: balance;
|
|
52
|
-
}
|
|
53
|
-
@utility text-heading-2 {
|
|
54
|
-
font-family: var(--font-display);
|
|
55
|
-
text-wrap: balance;
|
|
56
|
-
}
|
|
57
|
-
@utility text-heading-3 {
|
|
58
|
-
font-family: var(--font-display);
|
|
59
|
-
text-wrap: balance;
|
|
60
|
-
}
|
|
61
|
-
@utility text-heading-4 {
|
|
62
|
-
font-family: var(--font-display);
|
|
63
|
-
text-wrap: balance;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/*
|
|
67
|
-
* History: fixed 3 competing type systems (Tailwind defaults + named rungs + raw text-[Npx]) into one rung/role; 14px = body.
|
|
68
|
-
* Usage: React text goes through <Text>/<Heading> (ui/typography): size, tone and weight are props, className stays layout-only.
|
|
69
|
-
* A bare rung is for HTML React doesn't render as components (email HTML, injected markdown); MDX maps its tags to <Heading>/<Text>.
|
|
70
|
-
* Heading LEVEL is the document outline; the RUNG is the look. They are decoupled on purpose: an <h3> may wear text-heading-2.
|
|
71
|
-
* Control chrome text is sized by its recipe (`--cui-button-font-size`, `--cui-field-font-size`), not by these utilities.
|
|
72
|
-
*/
|