@nadicodeai/ui 6.3.4 → 6.4.0
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/dist/components/dropdown-menu.d.ts +2 -1
- package/dist/components/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu.js +3 -2
- package/dist/components/fact-cell.js +1 -1
- package/dist/components/faq-item.js +1 -1
- package/dist/components/logo-tile.js +1 -1
- package/dist/components/menu-choice-row.d.ts +24 -0
- package/dist/components/menu-choice-row.d.ts.map +1 -0
- package/dist/components/menu-choice-row.js +17 -0
- package/dist/components/menubar.js +1 -1
- package/dist/components/pricing-card.js +2 -2
- package/dist/components/quote-card.js +1 -1
- package/dist/components/reading-column.d.ts +13 -0
- package/dist/components/reading-column.d.ts.map +1 -0
- package/dist/components/reading-column.js +12 -0
- package/dist/components/stat-block.js +1 -1
- package/dist/components/tabs.js +2 -2
- package/dist/components/theme-mode-switcher.d.ts.map +1 -1
- package/dist/components/theme-mode-switcher.js +9 -8
- package/dist/eslint/index.js +17 -0
- package/dist/eslint/rules/icon-control-accessible-name.js +156 -0
- package/dist/eslint/rules/no-nested-overlay.js +56 -0
- package/dist/eslint/rules/no-off-grid-spacing.js +115 -0
- package/dist/eslint/rules/no-raw-shadow.js +70 -0
- package/dist/eslint/rules/no-route-width-override.js +135 -0
- package/dist/eslint/rules/prefer-radio-for-few-static-options.js +77 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/docs/contract.md +18 -4
- package/package.json +2 -2
|
@@ -10,7 +10,8 @@ declare function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props): Rea
|
|
|
10
10
|
declare function DropdownMenuLabel({ className, inset, ...props }: MenuPrimitive.GroupLabel.Props & {
|
|
11
11
|
inset?: boolean;
|
|
12
12
|
}): React.JSX.Element;
|
|
13
|
-
declare function DropdownMenuItem({ className, inset, variant, ...props }: MenuPrimitive.Item.Props & {
|
|
13
|
+
declare function DropdownMenuItem({ className, iconOnly, inset, variant, ...props }: MenuPrimitive.Item.Props & {
|
|
14
|
+
iconOnly?: boolean;
|
|
14
15
|
inset?: boolean;
|
|
15
16
|
variant?: "default" | "destructive";
|
|
16
17
|
}): React.JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-menu.d.ts","sourceRoot":"","sources":["../../src/components/dropdown-menu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAM5D,iBAAS,YAAY,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,qBAE3D;AAED,iBAAS,kBAAkB,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,qBAEnE;AAED,iBAAS,mBAAmB,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,qBAErE;AAED,iBAAS,mBAAmB,CAAC,EAC3B,KAAe,EACf,WAAe,EACf,OAAmB,EACnB,IAAe,EACf,UAAc,EACd,SAAS,EACT,GAAG,KAAK,EACT,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,GAC1B,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,YAAY,CAAC,GAAG;IACtF,OAAO,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CACrC,qBAuBF;AAED,iBAAS,iBAAiB,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,qBAEjE;AAED,iBAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,GAAG;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,qBAYA;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,KAAK,EACL,OAAmB,EACnB,GAAG,KAAK,EACT,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,GAAG;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CACrC,
|
|
1
|
+
{"version":3,"file":"dropdown-menu.d.ts","sourceRoot":"","sources":["../../src/components/dropdown-menu.tsx"],"names":[],"mappings":"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"}
|
|
@@ -21,8 +21,9 @@ function DropdownMenuGroup({ ...props }) {
|
|
|
21
21
|
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
22
22
|
return (_jsx(MenuPrimitive.GroupLabel, { "data-slot": "dropdown-menu-label", "data-inset": inset, className: cn("px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7", className), ...props }));
|
|
23
23
|
}
|
|
24
|
-
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
25
|
-
return (_jsx(MenuPrimitive.Item, { "data-slot": "dropdown-menu-item", "data-inset": inset, "data-variant": variant, className: cn("group/dropdown-menu-item relative flex min-h-touch-target cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 group-data-[density=comfortable]/menu:px-3 group-data-[density=comfortable]/menu:py-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-[var(--nc-error-deep)] data-[variant=destructive]:focus:bg-[var(--nc-error-soft)] data-[variant=destructive]:focus:text-[var(--nc-error-deep)] data-disabled:pointer-events-none data-disabled:opacity-50 [@media(hover:hover)_and_(pointer:fine)]:min-h-0 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-[var(--nc-error-deep)]",
|
|
24
|
+
function DropdownMenuItem({ className, iconOnly = false, inset, variant = "default", ...props }) {
|
|
25
|
+
return (_jsx(MenuPrimitive.Item, { "data-slot": "dropdown-menu-item", "data-icon-only": iconOnly || undefined, "data-inset": inset, "data-variant": variant, className: cn("group/dropdown-menu-item relative flex min-h-touch-target cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 group-data-[density=comfortable]/menu:px-3 group-data-[density=comfortable]/menu:py-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-[var(--nc-error-deep)] data-[variant=destructive]:focus:bg-[var(--nc-error-soft)] data-[variant=destructive]:focus:text-[var(--nc-error-deep)] data-disabled:pointer-events-none data-disabled:opacity-50 [@media(hover:hover)_and_(pointer:fine)]:min-h-0 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-[var(--nc-error-deep)]", iconOnly &&
|
|
26
|
+
"size-8 min-w-touch-target shrink-0 justify-center rounded-full p-0 group-data-[density=comfortable]/menu:p-0 [@media(hover:hover)_and_(pointer:fine)]:min-w-0", className), ...props }));
|
|
26
27
|
}
|
|
27
28
|
function DropdownMenuSub({ ...props }) {
|
|
28
29
|
return _jsx(MenuPrimitive.SubmenuRoot, { "data-slot": "dropdown-menu-sub", ...props });
|
|
@@ -13,7 +13,7 @@ import { cn } from "../lib/utils.js";
|
|
|
13
13
|
* model), so sub-components stay style-light. Token-only; no `dark:` styling.
|
|
14
14
|
*/
|
|
15
15
|
function FactCell({ className, surface = "canvas", size = "default", ...props }) {
|
|
16
|
-
return (_jsx("div", { "data-slot": "fact-cell", "data-surface": surface, "data-size": size, className: cn("grid content-center gap-1.5", size === "default" ? "min-h-24 p-
|
|
16
|
+
return (_jsx("div", { "data-slot": "fact-cell", "data-surface": surface, "data-size": size, className: cn("grid content-center gap-1.5", size === "default" ? "min-h-24 p-6" : "min-h-[86px] p-4", surface === "canvas" && "bg-card", surface === "ink" && [
|
|
17
17
|
"bg-primary",
|
|
18
18
|
"[&_[data-slot=fact-cell-label]]:text-primary-foreground",
|
|
19
19
|
"[&_[data-slot=fact-cell-claim]]:text-primary-foreground",
|
|
@@ -18,7 +18,7 @@ import { cn } from "../lib/utils.js";
|
|
|
18
18
|
* no `dark:` styling.
|
|
19
19
|
*/
|
|
20
20
|
function FaqItem({ className, question, name, open, children, ...props }) {
|
|
21
|
-
return (_jsxs("details", { "data-slot": "faq-item", name: name, open: open, className: cn("group bg-muted [&_summary::-webkit-details-marker]:hidden", className), ...props, children: [_jsx("summary", { className: cn("flex min-h-16 cursor-pointer list-none items-center justify-between gap-4 px-5 py-
|
|
21
|
+
return (_jsxs("details", { "data-slot": "faq-item", name: name, open: open, className: cn("group bg-muted [&_summary::-webkit-details-marker]:hidden", className), ...props, children: [_jsx("summary", { className: cn("flex min-h-16 cursor-pointer list-none items-center justify-between gap-4 px-5 py-4", "text-base font-medium text-foreground transition-colors hover:text-[var(--nc-link)]",
|
|
22
22
|
// +/- marker: two crossed bars; the vertical bar hides when open (+ -> -)
|
|
23
23
|
"after:size-3 after:flex-none after:bg-[length:12px_1px,1px_12px] after:bg-center after:bg-no-repeat", "after:[background-image:linear-gradient(var(--muted-foreground),var(--muted-foreground)),linear-gradient(var(--muted-foreground),var(--muted-foreground))]", "group-open:after:bg-[length:12px_1px] group-open:after:[background-image:linear-gradient(var(--muted-foreground),var(--muted-foreground))]"), children: question }), _jsx("div", { "data-slot": "faq-item-answer", className: "border-t border-border px-5 pt-4 pb-5 text-sm text-muted-foreground", children: children })] }));
|
|
24
24
|
}
|
|
@@ -11,7 +11,7 @@ import { cn } from "../lib/utils.js";
|
|
|
11
11
|
* Token-only; no `dark:` styling.
|
|
12
12
|
*/
|
|
13
13
|
function LogoTile({ className, surface = "canvas", ...props }) {
|
|
14
|
-
return (_jsx("div", { "data-slot": "logo-tile", "data-surface": surface, className: cn("grid min-h-24 place-content-center justify-items-center gap-2 p-
|
|
14
|
+
return (_jsx("div", { "data-slot": "logo-tile", "data-surface": surface, className: cn("grid min-h-24 place-content-center justify-items-center gap-2 p-6", surface === "ink" ? "bg-primary" : "bg-card", className), ...props }));
|
|
15
15
|
}
|
|
16
16
|
function LogoTileMark({ className, surface = "canvas", ...props }) {
|
|
17
17
|
return (_jsx("span", { "data-slot": "logo-tile-mark", className: cn("block h-7 w-auto [&_svg]:h-full [&_svg]:w-auto [&_img]:h-full [&_img]:w-auto",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type MenuChoiceOption<Value extends string = string> = {
|
|
3
|
+
/** The accessible name and tooltip for the option. */
|
|
4
|
+
label: string;
|
|
5
|
+
/** The glyph or short label the compact radio shows. */
|
|
6
|
+
content: React.ReactNode;
|
|
7
|
+
value: Value;
|
|
8
|
+
};
|
|
9
|
+
export type MenuChoiceRowProps<Value extends string = string> = Omit<React.ComponentProps<"div">, "children" | "onChange"> & {
|
|
10
|
+
label: string;
|
|
11
|
+
options: readonly MenuChoiceOption<Value>[];
|
|
12
|
+
onValueChange: (value: Value) => void;
|
|
13
|
+
value: Value;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A named preference inside a menu: the label reads at the leading edge and the
|
|
18
|
+
* exclusive choices sit together at the trailing edge, close enough to read as
|
|
19
|
+
* one control. Selecting keeps the menu open, so a reader can see the result
|
|
20
|
+
* without reopening it.
|
|
21
|
+
*/
|
|
22
|
+
declare function MenuChoiceRow<Value extends string = string>({ className, disabled, label, onValueChange, options, value, ...props }: MenuChoiceRowProps<Value>): React.JSX.Element;
|
|
23
|
+
export { MenuChoiceRow };
|
|
24
|
+
//# sourceMappingURL=menu-choice-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"menu-choice-row.d.ts","sourceRoot":"","sources":["../../src/components/menu-choice-row.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI;IAC5D,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,CAClE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAC3B,UAAU,GAAG,UAAU,CACxB,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;IAC5C,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,iBAAS,aAAa,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EAAE,EACpD,SAAS,EACT,QAAQ,EACR,KAAK,EACL,aAAa,EACb,OAAO,EACP,KAAK,EACL,GAAG,KAAK,EACT,EAAE,kBAAkB,CAAC,KAAK,CAAC,qBAoC3B;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DropdownMenuRadioGroup, DropdownMenuRadioItem } from "./dropdown-menu.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* A named preference inside a menu: the label reads at the leading edge and the
|
|
6
|
+
* exclusive choices sit together at the trailing edge, close enough to read as
|
|
7
|
+
* one control. Selecting keeps the menu open, so a reader can see the result
|
|
8
|
+
* without reopening it.
|
|
9
|
+
*/
|
|
10
|
+
function MenuChoiceRow({ className, disabled, label, onValueChange, options, value, ...props }) {
|
|
11
|
+
return (_jsxs("div", { "data-slot": "menu-choice-row", className: cn("flex min-h-touch-target items-center justify-between gap-4 px-3 py-1 text-sm", className), ...props, children: [_jsx("span", { children: label }), _jsx(DropdownMenuRadioGroup, { "aria-label": label, className: "flex shrink-0 items-center gap-0.5 rounded-full border border-border p-0.5", value: value, onValueChange: (next) => {
|
|
12
|
+
const selected = options.find((option) => option.value === next);
|
|
13
|
+
if (selected)
|
|
14
|
+
onValueChange(selected.value);
|
|
15
|
+
}, children: options.map((option) => (_jsx(DropdownMenuRadioItem, { value: option.value, "aria-label": option.label, title: option.label, iconOnly: true, closeOnClick: false, disabled: disabled, children: option.content }, option.value))) })] }));
|
|
16
|
+
}
|
|
17
|
+
export { MenuChoiceRow };
|
|
@@ -6,7 +6,7 @@ import { cn } from "../lib/utils.js";
|
|
|
6
6
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./dropdown-menu.js";
|
|
7
7
|
import { CheckIcon } from "lucide-react";
|
|
8
8
|
function Menubar({ className, ...props }) {
|
|
9
|
-
return (_jsx(MenubarPrimitive, { "data-slot": "menubar", className: cn("flex h-control-height min-h-touch-target items-center gap-0.5 rounded-md border p-
|
|
9
|
+
return (_jsx(MenubarPrimitive, { "data-slot": "menubar", className: cn("flex h-control-height min-h-touch-target items-center gap-0.5 rounded-md border p-1 [@media(hover:hover)_and_(pointer:fine)]:min-h-0", className), ...props }));
|
|
10
10
|
}
|
|
11
11
|
function MenubarMenu({ ...props }) {
|
|
12
12
|
return _jsx(DropdownMenu, { "data-slot": "menubar-menu", ...props });
|
|
@@ -15,7 +15,7 @@ import { cn } from "../lib/utils.js";
|
|
|
15
15
|
* differs. Server-renderable; token-only; no `dark:` styling.
|
|
16
16
|
*/
|
|
17
17
|
function PricingCard({ className, variant = "default", ...props }) {
|
|
18
|
-
return (_jsx("div", { "data-slot": "pricing-card", "data-variant": variant, className: cn("flex h-full flex-col gap-
|
|
18
|
+
return (_jsx("div", { "data-slot": "pricing-card", "data-variant": variant, className: cn("flex h-full flex-col gap-4 p-8 text-card-foreground", variant === "default" && "bg-card",
|
|
19
19
|
// featured (V5): soft canvas surface — a quiet fill change only. The
|
|
20
20
|
// `muted` role (== canvas-soft) flips with any `.dark` ancestor.
|
|
21
21
|
variant === "featured" && "bg-muted", className), ...props }));
|
|
@@ -36,7 +36,7 @@ function PricingCardFeatures({ className, ...props }) {
|
|
|
36
36
|
return (_jsx("ul", { "data-slot": "pricing-card-features", className: cn("m-0 grid list-none gap-2.5 p-0", className), ...props }));
|
|
37
37
|
}
|
|
38
38
|
function PricingCardFeature({ className, children, ...props }) {
|
|
39
|
-
return (_jsxs("li", { "data-slot": "pricing-card-feature", className: cn("grid grid-cols-[auto_minmax(0,1fr)] items-start gap-2.5 text-sm text-muted-foreground", className), ...props, children: [_jsx("span", { "data-slot": "pricing-card-feature-mark", "aria-hidden": "true", className: "mt-
|
|
39
|
+
return (_jsxs("li", { "data-slot": "pricing-card-feature", className: cn("grid grid-cols-[auto_minmax(0,1fr)] items-start gap-2.5 text-sm text-muted-foreground", className), ...props, children: [_jsx("span", { "data-slot": "pricing-card-feature-mark", "aria-hidden": "true", className: "mt-1 size-2.5 rounded-[1px] border border-primary bg-[var(--nc-link-bg-soft)]" }), _jsx("span", { children: children })] }));
|
|
40
40
|
}
|
|
41
41
|
function PricingCardCta({ className, ...props }) {
|
|
42
42
|
return (_jsx("div", { "data-slot": "pricing-card-cta", className: cn("mt-auto pt-1.5 [&>*]:w-full", className), ...props }));
|
|
@@ -14,7 +14,7 @@ import { cn } from "../lib/utils.js";
|
|
|
14
14
|
* `dark:` styling.
|
|
15
15
|
*/
|
|
16
16
|
function QuoteCard({ className, surface = "canvas", ...props }) {
|
|
17
|
-
return (_jsx("figure", { "data-slot": "quote-card", "data-surface": surface, className: cn("m-0 grid content-start gap-
|
|
17
|
+
return (_jsx("figure", { "data-slot": "quote-card", "data-surface": surface, className: cn("m-0 grid content-start gap-4 p-6", surface === "canvas" && "bg-card", surface === "ink" && [
|
|
18
18
|
"bg-primary",
|
|
19
19
|
"[&_[data-slot=quote-card-body]]:text-primary-foreground",
|
|
20
20
|
"[&_[data-slot=quote-card-name]]:text-primary-foreground",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type ReadingColumnWidth = "full" | "reading";
|
|
3
|
+
/**
|
|
4
|
+
* The centered content column a settings region reads in. `reading` bounds the
|
|
5
|
+
* measure for stacked forms and record sections; `full` keeps the workspace
|
|
6
|
+
* width for a page whose table needs it. Both stay centered, so the column
|
|
7
|
+
* agrees with the centered heading above it whichever width a page takes.
|
|
8
|
+
*/
|
|
9
|
+
declare function ReadingColumn({ width, className, ...props }: React.ComponentProps<"div"> & {
|
|
10
|
+
width?: ReadingColumnWidth;
|
|
11
|
+
}): React.JSX.Element;
|
|
12
|
+
export { ReadingColumn };
|
|
13
|
+
//# sourceMappingURL=reading-column.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reading-column.d.ts","sourceRoot":"","sources":["../../src/components/reading-column.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAAC;AAEpD;;;;;GAKG;AACH,iBAAS,aAAa,CAAC,EACrB,KAAiB,EACjB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAAE,qBAa9D;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* The centered content column a settings region reads in. `reading` bounds the
|
|
5
|
+
* measure for stacked forms and record sections; `full` keeps the workspace
|
|
6
|
+
* width for a page whose table needs it. Both stay centered, so the column
|
|
7
|
+
* agrees with the centered heading above it whichever width a page takes.
|
|
8
|
+
*/
|
|
9
|
+
function ReadingColumn({ width = "reading", className, ...props }) {
|
|
10
|
+
return (_jsx("div", { "data-slot": "reading-column", "data-width": width, className: cn("mx-auto flex w-full min-w-0 flex-col gap-6", width === "reading" && "max-w-3xl", className), ...props }));
|
|
11
|
+
}
|
|
12
|
+
export { ReadingColumn };
|
|
@@ -12,7 +12,7 @@ import { cn } from "../lib/utils.js";
|
|
|
12
12
|
* Token-only; no `dark:` styling.
|
|
13
13
|
*/
|
|
14
14
|
function StatBlock({ className, surface = "canvas", ...props }) {
|
|
15
|
-
return (_jsx("div", { "data-slot": "stat-block", "data-surface": surface, className: cn("grid min-h-24 content-center gap-1.5 p-
|
|
15
|
+
return (_jsx("div", { "data-slot": "stat-block", "data-surface": surface, className: cn("grid min-h-24 content-center gap-1.5 p-6", surface === "ink" ? "bg-primary" : "bg-card", className), ...props }));
|
|
16
16
|
}
|
|
17
17
|
function StatBlockFigure({ className, surface = "canvas", ...props }) {
|
|
18
18
|
return (_jsx("div", { "data-slot": "stat-block-figure", className: cn("text-[clamp(2rem,3.2vw,2.875rem)] leading-none font-bold tabular-nums", surface === "ink" ? "text-primary-foreground" : "text-foreground", className), ...props }));
|
package/dist/components/tabs.js
CHANGED
|
@@ -5,7 +5,7 @@ import { cn } from "../lib/utils.js";
|
|
|
5
5
|
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
6
6
|
return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, orientation: orientation, className: cn("group/tabs flex gap-2 data-horizontal:flex-col", className), ...props }));
|
|
7
7
|
}
|
|
8
|
-
const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center justify-center rounded-md p-
|
|
8
|
+
const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center justify-center rounded-md p-1 text-muted-foreground group-data-horizontal/tabs:h-control-height group-data-horizontal/tabs:min-h-touch-target group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none [@media(hover:hover)_and_(pointer:fine)]:group-data-horizontal/tabs:min-h-0", {
|
|
9
9
|
variants: {
|
|
10
10
|
variant: {
|
|
11
11
|
default: "bg-muted",
|
|
@@ -20,7 +20,7 @@ function TabsList({ className, variant = "default", ...props }) {
|
|
|
20
20
|
return (_jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }), className), ...props }));
|
|
21
21
|
}
|
|
22
22
|
function TabsTrigger({ className, ...props }) {
|
|
23
|
-
return (_jsx(TabsPrimitive.Tab, { "data-slot": "tabs-trigger", className: cn("nc-transition-paint relative inline-flex h-[calc(100%-1px)] min-h-touch-target min-w-touch-target flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[variant=default]/tabs-list:data-active:shadow-none group-data-[variant=line]/tabs-list:data-active:shadow-none [@media(hover:hover)_and_(pointer:fine)]:min-h-0 [@media(hover:hover)_and_(pointer:fine)]:min-w-0 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background data-active:text-foreground", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity after:duration-[var(--nc-duration-confirm)] after:ease-[var(--nc-ease-out-strong)] group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after
|
|
23
|
+
return (_jsx(TabsPrimitive.Tab, { "data-slot": "tabs-trigger", className: cn("nc-transition-paint relative inline-flex h-[calc(100%-1px)] min-h-touch-target min-w-touch-target flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-[variant=default]/tabs-list:data-active:shadow-none group-data-[variant=line]/tabs-list:data-active:shadow-none [@media(hover:hover)_and_(pointer:fine)]:min-h-0 [@media(hover:hover)_and_(pointer:fine)]:min-w-0 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background data-active:text-foreground", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity after:duration-[var(--nc-duration-confirm)] after:ease-[var(--nc-ease-out-strong)] group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:-bottom-1 group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100 motion-reduce:after:transition-none", className), ...props }));
|
|
24
24
|
}
|
|
25
25
|
function TabsContent({ className, ...props }) {
|
|
26
26
|
return (_jsx(TabsPrimitive.Panel, { "data-slot": "tabs-content", className: cn("flex-1 text-sm outline-none", className), ...props }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-mode-switcher.d.ts","sourceRoot":"","sources":["../../src/components/theme-mode-switcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EAAE,UAAU,EAAkB,MAAM,0BAA0B,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AACnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,KAAK,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,EACvC,UAAU,GAAG,cAAc,GAAG,eAAe,GAAG,OAAO,CACxD,GAAG;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1D,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AAQF,iBAAS,iBAAiB,CAAC,EACzB,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,OAAe,EACf,QAAQ,EACR,aAAa,EACb,MAAM,EACN,aAAa,EACb,UAAU,EACV,KAAK,EACL,GAAG,KAAK,EACT,EAAE,sBAAsB,qBAkDxB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,MAAM,EACN,KAAK,EACL,aAAa,EACb,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,
|
|
1
|
+
{"version":3,"file":"theme-mode-switcher.d.ts","sourceRoot":"","sources":["../../src/components/theme-mode-switcher.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EAAE,UAAU,EAAkB,MAAM,0BAA0B,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AACnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,KAAK,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,EACvC,UAAU,GAAG,cAAc,GAAG,eAAe,GAAG,OAAO,CACxD,GAAG;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1D,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AAQF,iBAAS,iBAAiB,CAAC,EACzB,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,OAAe,EACf,QAAQ,EACR,aAAa,EACb,MAAM,EACN,aAAa,EACb,UAAU,EACV,KAAK,EACL,GAAG,KAAK,EACT,EAAE,sBAAsB,qBAkDxB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,MAAM,EACN,KAAK,EACL,aAAa,EACb,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,qBAkBA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
|
|
4
|
-
import {
|
|
4
|
+
import { MenuChoiceRow } from "./menu-choice-row.js";
|
|
5
5
|
import { RadioGroup, RadioGroupItem } from "./radio-group.js";
|
|
6
6
|
import { toggleVariants } from "./toggle.js";
|
|
7
7
|
import { isThemeMode, themeModes } from "../lib/theme-mode.js";
|
|
@@ -32,11 +32,12 @@ function ThemeModeSwitcher({ "aria-label": ariaLabel, className, compact = false
|
|
|
32
32
|
}
|
|
33
33
|
export { ThemeModeSwitcher };
|
|
34
34
|
export function ThemeModeMenu({ label, labels, value, onValueChange, disabled, }) {
|
|
35
|
-
return (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
return (_jsx(MenuChoiceRow, { "data-slot": "theme-mode-menu", label: label, disabled: disabled, value: value, onValueChange: onValueChange, options: ["system", "light", "dark"].map((mode) => {
|
|
36
|
+
const Icon = themeModeIcons[mode];
|
|
37
|
+
return {
|
|
38
|
+
value: mode,
|
|
39
|
+
label: labels[mode],
|
|
40
|
+
content: _jsx(Icon, { "aria-hidden": "true" }),
|
|
41
|
+
};
|
|
42
|
+
}) }));
|
|
42
43
|
}
|
package/dist/eslint/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
import { confirmDestructiveAction } from "./rules/confirm-destructive-action.js";
|
|
4
|
+
import { iconControlAccessibleName } from "./rules/icon-control-accessible-name.js";
|
|
4
5
|
import { noAppFeatureComponent } from "./rules/no-app-feature-component.js";
|
|
5
6
|
import { noElevatedEdge } from "./rules/no-elevated-edge.js";
|
|
7
|
+
import { noNestedOverlay } from "./rules/no-nested-overlay.js";
|
|
8
|
+
import { noOffGridSpacing } from "./rules/no-off-grid-spacing.js";
|
|
9
|
+
import { noRawShadow } from "./rules/no-raw-shadow.js";
|
|
10
|
+
import { noRouteWidthOverride } from "./rules/no-route-width-override.js";
|
|
11
|
+
import { preferRadioForFewStaticOptions } from "./rules/prefer-radio-for-few-static-options.js";
|
|
6
12
|
import { noDeprecatedZodApi } from "./rules/no-deprecated-zod-api.js";
|
|
7
13
|
import { noMonoTitleRegister } from "./rules/no-mono-title-register.js";
|
|
8
14
|
import { noPackageComponentRedefinition } from "./rules/no-package-component-redefinition.js";
|
|
@@ -49,11 +55,16 @@ function readPublishedVersion() {
|
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export const strictDesignSystemRules = {
|
|
58
|
+
"nadicodeai/icon-control-accessible-name": "error",
|
|
52
59
|
"nadicodeai/no-invalid-token-utility": "error",
|
|
53
60
|
"nadicodeai/no-native-visible-control": "error",
|
|
61
|
+
"nadicodeai/no-nested-overlay": "error",
|
|
62
|
+
"nadicodeai/no-off-grid-spacing": "error",
|
|
63
|
+
"nadicodeai/no-raw-shadow": "error",
|
|
54
64
|
"nadicodeai/no-repainted-focus": "error",
|
|
55
65
|
"nadicodeai/no-shadcn-appearance-override": "error",
|
|
56
66
|
"nadicodeai/no-token-category-bypass": "error",
|
|
67
|
+
"nadicodeai/prefer-radio-for-few-static-options": "error",
|
|
57
68
|
};
|
|
58
69
|
|
|
59
70
|
/** @type {import('eslint').ESLint.Plugin} */
|
|
@@ -65,13 +76,19 @@ const plugin = {
|
|
|
65
76
|
configs: {},
|
|
66
77
|
rules: {
|
|
67
78
|
"confirm-destructive-action": confirmDestructiveAction,
|
|
79
|
+
"icon-control-accessible-name": iconControlAccessibleName,
|
|
68
80
|
"no-app-feature-component": noAppFeatureComponent,
|
|
69
81
|
"no-elevated-edge": noElevatedEdge,
|
|
70
82
|
"no-deprecated-zod-api": noDeprecatedZodApi,
|
|
71
83
|
"no-mono-title-register": noMonoTitleRegister,
|
|
84
|
+
"no-nested-overlay": noNestedOverlay,
|
|
85
|
+
"no-off-grid-spacing": noOffGridSpacing,
|
|
72
86
|
"no-package-component-redefinition": noPackageComponentRedefinition,
|
|
73
87
|
"no-paired-size-utility": noPairedSizeUtility,
|
|
88
|
+
"no-raw-shadow": noRawShadow,
|
|
74
89
|
"no-resting-settings-control": noRestingSettingsControl,
|
|
90
|
+
"no-route-width-override": noRouteWidthOverride,
|
|
91
|
+
"prefer-radio-for-few-static-options": preferRadioForFewStaticOptions,
|
|
75
92
|
"no-shadcn-appearance-override": noShadcnAppearanceOverride,
|
|
76
93
|
"no-native-visible-control": noNativeVisibleControl,
|
|
77
94
|
"no-repainted-focus": noRepaintedFocus,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A control whose whole content is an icon still has to say what it does.
|
|
3
|
+
*
|
|
4
|
+
* An icon is a picture, and a screen reader announces "button" and nothing else
|
|
5
|
+
* for one. Sighted readers recover the meaning from the glyph; a screen-reader
|
|
6
|
+
* user, and anyone addressing the control by name through voice control, gets an
|
|
7
|
+
* unnamed button. The name comes from `aria-label`, from `aria-labelledby`, or
|
|
8
|
+
* from text inside the control that is hidden visually rather than from the
|
|
9
|
+
* accessibility tree — the `sr-only` span this repository already writes beside
|
|
10
|
+
* its close buttons. That last spelling needs no separate test here: hidden text
|
|
11
|
+
* is text, so a control carrying one is not icon-only in the first place.
|
|
12
|
+
*
|
|
13
|
+
* `title` is NOT one of them. It does name the control for most screen readers,
|
|
14
|
+
* but it also mints a browser tooltip, and the copy contract (the Tooltip
|
|
15
|
+
* contract in `skills/design/product-design/references/copy.md`) says the title
|
|
16
|
+
* attribute is not this product's help system. Accepting it here would make the
|
|
17
|
+
* rule push authors toward the one tooltip the design system refuses.
|
|
18
|
+
*
|
|
19
|
+
* Two shapes are icon-only, and both are read off the element rather than out of
|
|
20
|
+
* a list: a `Button` on one of the icon size variants (`icon`, `icon-xs`,
|
|
21
|
+
* `icon-sm`, `icon-lg`), and any `Button` or `button` whose children render
|
|
22
|
+
* elements and no text at all.
|
|
23
|
+
*
|
|
24
|
+
* Two shapes are deliberately out of scope. A `Button` written as the value of a
|
|
25
|
+
* `render` prop is a STYLE carrier: the primitive that receives it owns the
|
|
26
|
+
* rendered element's children and its label, as `<DropdownMenuTrigger
|
|
27
|
+
* aria-label={label} render={<Button size="icon-sm" />}>` does in
|
|
28
|
+
* `packages/ui/src/components/action-menu.tsx`, so the name is not this element's
|
|
29
|
+
* to carry. An element with a `{...props}` spread may be handed a label by its
|
|
30
|
+
* caller, and this rule does not guess what a spread holds.
|
|
31
|
+
*
|
|
32
|
+
* Standard tool checked first: the root config runs jsx-a11y's `alt-text`,
|
|
33
|
+
* `aria-props`, `aria-proptypes`, `aria-unsupported-elements`,
|
|
34
|
+
* `role-has-required-aria-props` and `role-supports-aria-props`, and Next's
|
|
35
|
+
* core-web-vitals config adds no more. Every one of them judges an attribute
|
|
36
|
+
* that is already present; none asks whether a control has a name at all.
|
|
37
|
+
* `control-has-associated-label` is the upstream rule that would, and it is
|
|
38
|
+
* enabled nowhere, looks for a label element rather than an accessible name, and
|
|
39
|
+
* knows nothing about a size variant that means "icon".
|
|
40
|
+
*/
|
|
41
|
+
import { jsxComponentName } from "./jsx-class-utility.js";
|
|
42
|
+
|
|
43
|
+
const CONTROL_TAGS = new Set(["Button", "button"]);
|
|
44
|
+
const ICON_SIZES = new Set(["icon", "icon-xs", "icon-sm", "icon-lg"]);
|
|
45
|
+
const NAME_ATTRIBUTES = new Set(["aria-label", "aria-labelledby"]);
|
|
46
|
+
|
|
47
|
+
function attributeName(attribute) {
|
|
48
|
+
return attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier"
|
|
49
|
+
? attribute.name.name
|
|
50
|
+
: undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The literal string an attribute carries, or undefined for anything computed. */
|
|
54
|
+
function literalAttributeValue(openingElement, name) {
|
|
55
|
+
for (const attribute of openingElement.attributes) {
|
|
56
|
+
if (attributeName(attribute) !== name) continue;
|
|
57
|
+
const value = attribute.value;
|
|
58
|
+
return value?.type === "Literal" && typeof value.value === "string" ? value.value : undefined;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True when this element stands in for another primitive's rendered element. */
|
|
64
|
+
function isRenderProp(node, sourceCode) {
|
|
65
|
+
return sourceCode.getAncestors(node).some((ancestor) => attributeName(ancestor) === "render");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* Whether an expression can only ever evaluate to JSX or to nothing. `{label}`
|
|
70
|
+
* and `{day.getDate()}` are text; `{copied ? <Check /> : <Copy />}` is not.
|
|
71
|
+
*/
|
|
72
|
+
function producesOnlyElements(expression) {
|
|
73
|
+
switch (expression.type) {
|
|
74
|
+
case "JSXEmptyExpression":
|
|
75
|
+
case "JSXElement":
|
|
76
|
+
case "JSXFragment":
|
|
77
|
+
return true;
|
|
78
|
+
case "ConditionalExpression":
|
|
79
|
+
return (
|
|
80
|
+
producesOnlyElements(expression.consequent) && producesOnlyElements(expression.alternate)
|
|
81
|
+
);
|
|
82
|
+
case "LogicalExpression":
|
|
83
|
+
return producesOnlyElements(expression.right);
|
|
84
|
+
case "Literal":
|
|
85
|
+
return expression.value === null || expression.value === false;
|
|
86
|
+
case "Identifier":
|
|
87
|
+
return expression.name === "undefined";
|
|
88
|
+
default:
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Whether a subtree renders any text, visible or visually hidden. */
|
|
94
|
+
function rendersText(node) {
|
|
95
|
+
for (const child of node.children ?? []) {
|
|
96
|
+
if (child.type === "JSXText") {
|
|
97
|
+
if (child.value.trim() !== "") return true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (child.type === "JSXElement" || child.type === "JSXFragment") {
|
|
101
|
+
if (rendersText(child)) return true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (child.type === "JSXExpressionContainer" && !producesOnlyElements(child.expression)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Whether a subtree renders at least one element of its own. */
|
|
112
|
+
function rendersElement(node) {
|
|
113
|
+
return (node.children ?? []).some(
|
|
114
|
+
(child) =>
|
|
115
|
+
child.type === "JSXElement" ||
|
|
116
|
+
child.type === "JSXFragment" ||
|
|
117
|
+
(child.type === "JSXExpressionContainer" &&
|
|
118
|
+
child.expression.type !== "JSXEmptyExpression" &&
|
|
119
|
+
producesOnlyElements(child.expression)),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const iconControlAccessibleName = {
|
|
124
|
+
meta: {
|
|
125
|
+
type: "problem",
|
|
126
|
+
docs: {
|
|
127
|
+
description: "Require an accessible name on a control whose only content is an icon",
|
|
128
|
+
},
|
|
129
|
+
schema: [],
|
|
130
|
+
messages: {
|
|
131
|
+
unnamedIconControl:
|
|
132
|
+
'A screen reader announces "{{tag}}" and nothing more for this control: its content is an icon, and an icon carries no name. Add aria-label with the action it performs, point aria-labelledby at the text that names it, or put that text in a child with the sr-only class.',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
create(context) {
|
|
136
|
+
return {
|
|
137
|
+
JSXOpeningElement(node) {
|
|
138
|
+
const tag = jsxComponentName(node, context);
|
|
139
|
+
if (!tag || !CONTROL_TAGS.has(tag)) return;
|
|
140
|
+
if (node.attributes.some((attribute) => attribute.type === "JSXSpreadAttribute")) return;
|
|
141
|
+
if (
|
|
142
|
+
node.attributes.some((attribute) => NAME_ATTRIBUTES.has(attributeName(attribute) ?? ""))
|
|
143
|
+
)
|
|
144
|
+
return;
|
|
145
|
+
if (isRenderProp(node, context.sourceCode)) return;
|
|
146
|
+
|
|
147
|
+
const element = node.parent;
|
|
148
|
+
const iconSized = ICON_SIZES.has(literalAttributeValue(node, "size") ?? "");
|
|
149
|
+
if (!iconSized && !rendersElement(element)) return;
|
|
150
|
+
if (rendersText(element)) return;
|
|
151
|
+
|
|
152
|
+
context.report({ node, messageId: "unnamedIconControl", data: { tag } });
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* One overlay at a time.
|
|
3
|
+
*
|
|
4
|
+
* `DialogContent`, `AlertDialogContent`, `SheetContent` and `DrawerContent` each
|
|
5
|
+
* mount a focus trap, a scrim and a layer of their own. Writing one inside
|
|
6
|
+
* another leaves both traps armed on the same tree: focus cannot leave the inner
|
|
7
|
+
* surface to reach the outer one's controls, Escape resolves against whichever
|
|
8
|
+
* trap won the race, and the two scrims composite into a second dim over content
|
|
9
|
+
* that is already dimmed. Nothing about this is visible while the inner overlay
|
|
10
|
+
* is closed, which is why it survives review and reaches the keyboard user.
|
|
11
|
+
*
|
|
12
|
+
* Standard tool checked first: `no-restricted-syntax` selects one node by its
|
|
13
|
+
* shape and cannot ask what encloses it. "An overlay inside an overlay" is a
|
|
14
|
+
* relationship between two elements, so it is read here, on the shared
|
|
15
|
+
* component-name reader that also resolves renamed imports.
|
|
16
|
+
*/
|
|
17
|
+
import { jsxComponentName } from "./jsx-class-utility.js";
|
|
18
|
+
|
|
19
|
+
const OVERLAY_CONTENT = new Set([
|
|
20
|
+
"AlertDialogContent",
|
|
21
|
+
"DialogContent",
|
|
22
|
+
"DrawerContent",
|
|
23
|
+
"SheetContent",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
export const noNestedOverlay = {
|
|
27
|
+
meta: {
|
|
28
|
+
type: "problem",
|
|
29
|
+
docs: {
|
|
30
|
+
description: "Disallow an overlay surface rendered inside another overlay surface",
|
|
31
|
+
},
|
|
32
|
+
schema: [],
|
|
33
|
+
messages: {
|
|
34
|
+
nestedOverlay:
|
|
35
|
+
"<{{inner}}> is rendered inside <{{outer}}>. Two overlays on one tree keep both focus traps armed, so the keyboard cannot reach the outer surface and Escape closes the wrong layer, while the scrims stack. Render the second overlay as a sibling driven by state, or replace it with an inline disclosure inside the first.",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
return {
|
|
40
|
+
JSXOpeningElement(node) {
|
|
41
|
+
const inner = jsxComponentName(node, context);
|
|
42
|
+
if (!inner || !OVERLAY_CONTENT.has(inner)) return;
|
|
43
|
+
|
|
44
|
+
for (const ancestor of context.sourceCode.getAncestors(node)) {
|
|
45
|
+
// The element this opening tag belongs to is an ancestor of it, and it
|
|
46
|
+
// is not nested inside itself.
|
|
47
|
+
if (ancestor.type !== "JSXElement" || ancestor.openingElement === node) continue;
|
|
48
|
+
const outer = jsxComponentName(ancestor.openingElement, context);
|
|
49
|
+
if (!outer || !OVERLAY_CONTENT.has(outer)) continue;
|
|
50
|
+
context.report({ node, messageId: "nestedOverlay", data: { inner, outer } });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The spacing ladder is closed. DESIGN.md, "Layout > Spacing": "One ladder
|
|
3
|
+
* carries every gap, pad, and stack step. A register picks the rungs it needs;
|
|
4
|
+
* it never interpolates."
|
|
5
|
+
*
|
|
6
|
+
* A pixel value typed into a spacing utility — `p-[13px]`, `gap-[7px]` — is that
|
|
7
|
+
* interpolation. It reads as deliberate and is usually an eyeballed nudge, it
|
|
8
|
+
* cannot move when the ladder does, and every one of them puts a second rhythm
|
|
9
|
+
* beside the one the rest of the surface is built on, which is what makes a
|
|
10
|
+
* layout look almost aligned.
|
|
11
|
+
*
|
|
12
|
+
* Only a bare pixel literal is a finding. `p-[var(--nc-cell-padding)]`,
|
|
13
|
+
* `px-[clamp(24px,5vw,64px)]` and `top-[calc(var(--titlebar-height)+0.5rem)]` are
|
|
14
|
+
* relationships to something the page owns rather than a measurement invented at
|
|
15
|
+
* the call site, so they are outside this rule. So is a value that already lands
|
|
16
|
+
* on a rung: it is the ladder, spelled the long way.
|
|
17
|
+
*
|
|
18
|
+
* Standard tool checked first: `better-tailwindcss`'s `no-restricted-classes`
|
|
19
|
+
* matches a class against a pattern and could refuse every arbitrary spacing
|
|
20
|
+
* value, but its message is the pattern's, one text for every hit. The fix here
|
|
21
|
+
* is a different token for every value, and naming the nearest rung is the whole
|
|
22
|
+
* point of the message, so the arithmetic lives in a rule.
|
|
23
|
+
*/
|
|
24
|
+
import { tokenUtilityVisitors } from "./jsx-class-utility.js";
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* The ladder as the theme emits it: `--spacing-nadicode-*`, which Tailwind
|
|
28
|
+
* spells `p-nadicode-md`, `gap-nadicode-lg` and so on. These are the rungs a
|
|
29
|
+
* step may take and the ones a message offers.
|
|
30
|
+
*/
|
|
31
|
+
const LADDER = [
|
|
32
|
+
[0, "0"],
|
|
33
|
+
[4, "nadicode-xxs"],
|
|
34
|
+
[8, "nadicode-xs"],
|
|
35
|
+
[12, "nadicode-sm"],
|
|
36
|
+
[16, "nadicode-md"],
|
|
37
|
+
[24, "nadicode-lg"],
|
|
38
|
+
[32, "nadicode-xl"],
|
|
39
|
+
[40, "nadicode-2xl"],
|
|
40
|
+
[48, "nadicode-3xl"],
|
|
41
|
+
[64, "nadicode-4xl"],
|
|
42
|
+
[96, "nadicode-5xl"],
|
|
43
|
+
[128, "nadicode-6xl"],
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Control geometry from the same section: the seam weight, the focus outline and
|
|
48
|
+
* its offset, a control's inline padding, and the coarse-pointer target. Each
|
|
49
|
+
* owns one role rather than the general rhythm, so a value that lands on one is
|
|
50
|
+
* legal and none of them is ever offered as the nearest rung for a gap.
|
|
51
|
+
*/
|
|
52
|
+
const CONTROL_GEOMETRY = new Set([1, 2, 10, 44]);
|
|
53
|
+
|
|
54
|
+
const SPACING_PREFIX =
|
|
55
|
+
"(?:p|px|py|pt|pr|pb|pl|ps|pe|m|mx|my|mt|mr|mb|ml|ms|me|gap|gap-x|gap-y|space-x|space-y|inset|inset-x|inset-y|top|right|bottom|left|start|end)";
|
|
56
|
+
const OFF_GRID = new RegExp(`^-?(${SPACING_PREFIX})-\\[(-?\\d+(?:\\.\\d+)?)px\\]$`);
|
|
57
|
+
|
|
58
|
+
/** `md:hover:p-[13px]` → `p-[13px]`, with a colon inside brackets left alone. */
|
|
59
|
+
function withoutVariants(token) {
|
|
60
|
+
let bracketDepth = 0;
|
|
61
|
+
let start = 0;
|
|
62
|
+
for (let index = 0; index < token.length; index += 1) {
|
|
63
|
+
if (token[index] === "[") bracketDepth += 1;
|
|
64
|
+
if (token[index] === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
65
|
+
if (token[index] === ":" && bracketDepth === 0) start = index + 1;
|
|
66
|
+
}
|
|
67
|
+
return token.slice(start);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The closest rung, and the larger of the two when a value sits between them. */
|
|
71
|
+
function nearestRung(pixels) {
|
|
72
|
+
return LADDER.reduce((closest, rung) =>
|
|
73
|
+
Math.abs(rung[0] - pixels) <= Math.abs(closest[0] - pixels) ? rung : closest,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* A step below zero is written both ways — `-mt-[3px]` puts the sign on the
|
|
79
|
+
* utility, `bottom-[-5px]` puts it in the value — and the replacement carries it
|
|
80
|
+
* on the utility either way, because a token holds a magnitude.
|
|
81
|
+
*/
|
|
82
|
+
function offGridFinding(utility) {
|
|
83
|
+
const unqualified = withoutVariants(utility);
|
|
84
|
+
const match = OFF_GRID.exec(unqualified);
|
|
85
|
+
if (!match) return undefined;
|
|
86
|
+
const [, prefix, value] = match;
|
|
87
|
+
const pixels = Math.abs(Number(value));
|
|
88
|
+
if (CONTROL_GEOMETRY.has(pixels)) return undefined;
|
|
89
|
+
const [rung, token] = nearestRung(pixels);
|
|
90
|
+
if (rung === pixels) return undefined;
|
|
91
|
+
const sign = unqualified.startsWith("-") || value.startsWith("-") ? "-" : "";
|
|
92
|
+
return { value: String(pixels), rung: String(rung), replacement: `${sign}${prefix}-${token}` };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const noOffGridSpacing = {
|
|
96
|
+
meta: {
|
|
97
|
+
type: "problem",
|
|
98
|
+
docs: {
|
|
99
|
+
description:
|
|
100
|
+
"Disallow a pixel value typed into a spacing utility off the design-system ladder",
|
|
101
|
+
},
|
|
102
|
+
schema: [],
|
|
103
|
+
messages: {
|
|
104
|
+
offGridSpacing:
|
|
105
|
+
'"{{utility}}" puts a {{value}}px step beside a layout built on the design-system spacing ladder, so the rhythm reads as almost aligned and the value cannot follow the tokens. The nearest rung is {{rung}}px: use {{replacement}}.',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
create(context) {
|
|
109
|
+
return tokenUtilityVisitors(context, (node, utility) => {
|
|
110
|
+
const finding = offGridFinding(utility);
|
|
111
|
+
if (!finding) return;
|
|
112
|
+
context.report({ node, messageId: "offGridSpacing", data: { utility, ...finding } });
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Depth comes off the ladder, not out of Tailwind's box.
|
|
3
|
+
*
|
|
4
|
+
* DESIGN.md, "Elevation & Depth": the four rungs are the four owned utilities,
|
|
5
|
+
* each authored per mode and carrying its own ring as the recipe's final layer.
|
|
6
|
+
* A stock `shadow-md` is a single light-mode drop shadow: it disappears against
|
|
7
|
+
* the true-black dark workspace, it leaves the surface without the ring its
|
|
8
|
+
* neighbours have, and it does not move when the tokens do. Two surfaces meant
|
|
9
|
+
* to sit at the same physical level then read as different objects.
|
|
10
|
+
*
|
|
11
|
+
* `shadow-none` stays legal — removing a shadow an inherited shape brought with
|
|
12
|
+
* it is the correct thing to write, and it is what `packages/ui` already writes.
|
|
13
|
+
*
|
|
14
|
+
* The two packages that BUILD the ladder are outside the rule: the design system
|
|
15
|
+
* authors the rung values, and `@nadicodeai/ui` composes the utilities onto its
|
|
16
|
+
* primitives. Everywhere else a shadow is a caller inventing depth.
|
|
17
|
+
*
|
|
18
|
+
* Standard tool checked first: `better-tailwindcss/no-unknown-classes` cannot
|
|
19
|
+
* see these, because the generated theme clears only the `--color-*` namespace
|
|
20
|
+
* and leaves Tailwind's own `--shadow-*` in place, so `shadow-lg` is a real
|
|
21
|
+
* class that really paints. `no-restricted-classes` in the Portal and the
|
|
22
|
+
* package configs carries `shadow-tooltip|menu|dialog|sheet` — the NAMED shadows
|
|
23
|
+
* the ladder replaced — and nothing about the size scale; `no-invalid-token-utility`
|
|
24
|
+
* judges `shadow-nc-*` against the generated colour tokens only; and the
|
|
25
|
+
* colour-boundary patterns fire on `-nc-` or on an arbitrary colour value. A
|
|
26
|
+
* probe file carrying `shadow-lg shadow-md shadow-[0_2px_4px_black]` linted
|
|
27
|
+
* clean in `apps/portal` before this rule existed. Holding it as a repo-wide
|
|
28
|
+
* restricted class would need the same entry restated in five configs, since a
|
|
29
|
+
* later `no-restricted-classes` entry replaces the earlier one rather than
|
|
30
|
+
* adding to it, so it is one rule instead.
|
|
31
|
+
*/
|
|
32
|
+
import { tokenUtilityVisitors } from "./jsx-class-utility.js";
|
|
33
|
+
|
|
34
|
+
const RAW_SHADOW = /^shadow(?:-(?:2xs|xs|sm|md|lg|xl|2xl|inner|initial|\[.*\]))?$/;
|
|
35
|
+
const PACKAGE_LADDER_PATHS = ["packages/ui/src/", "packages/design-system/"];
|
|
36
|
+
|
|
37
|
+
function withoutVariants(token) {
|
|
38
|
+
let bracketDepth = 0;
|
|
39
|
+
let start = 0;
|
|
40
|
+
for (let index = 0; index < token.length; index += 1) {
|
|
41
|
+
if (token[index] === "[") bracketDepth += 1;
|
|
42
|
+
if (token[index] === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
43
|
+
if (token[index] === ":" && bracketDepth === 0) start = index + 1;
|
|
44
|
+
}
|
|
45
|
+
return token.slice(start);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const noRawShadow = {
|
|
49
|
+
meta: {
|
|
50
|
+
type: "problem",
|
|
51
|
+
docs: {
|
|
52
|
+
description:
|
|
53
|
+
"Disallow Tailwind's stock shadow utilities outside the packages that own the material ladder",
|
|
54
|
+
},
|
|
55
|
+
schema: [],
|
|
56
|
+
messages: {
|
|
57
|
+
rawShadow:
|
|
58
|
+
'"{{utility}}" paints a light-mode drop shadow with no ring, so the surface loses its edge against the dark workspace and cannot follow the tokens. Choose the physical level instead: nc-elevation-resting, nc-elevation-raised, nc-elevation-floating, or nc-elevation-modal.',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
create(context) {
|
|
62
|
+
const filename = context.filename.replaceAll("\\", "/");
|
|
63
|
+
if (PACKAGE_LADDER_PATHS.some((path) => filename.includes(path))) return {};
|
|
64
|
+
|
|
65
|
+
return tokenUtilityVisitors(context, (node, utility) => {
|
|
66
|
+
if (!RAW_SHADOW.test(withoutVariants(utility))) return;
|
|
67
|
+
context.report({ node, messageId: "rawShadow", data: { utility } });
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A Portal route does not decide how wide it is.
|
|
3
|
+
*
|
|
4
|
+
* The signed-in chrome owns the content column: `(dashboard)/layout.tsx` renders
|
|
5
|
+
* `PortalShell`, and a reading-width surface takes `ReadingColumn` from
|
|
6
|
+
* `@nadicodeai/ui`. A route that caps its own width with `max-w-*` or centres
|
|
7
|
+
* itself with `mx-auto` makes a second owner for one measurement, which is how
|
|
8
|
+
* neighbouring pages end up at 42rem and 48rem with nothing to say which is
|
|
9
|
+
* right, and how a shell change stops reaching half the routes.
|
|
10
|
+
*
|
|
11
|
+
* What is read is the OUTERMOST element the route renders — the root of every
|
|
12
|
+
* expression its default export returns, descended through fragments and through
|
|
13
|
+
* the conditionals a route writes around a fallback. A width inside the page is
|
|
14
|
+
* a different statement: a skeleton that matches the field it stands in for, or
|
|
15
|
+
* a form column, constrains its own element, not the route, and those are
|
|
16
|
+
* ordinary. The rule's subject is the route's own frame.
|
|
17
|
+
*
|
|
18
|
+
* `max-w-full` and `max-w-none` are kept legal for the same reason: they remove
|
|
19
|
+
* a cap rather than set one, so neither can be the second owner of a width.
|
|
20
|
+
*
|
|
21
|
+
* Standard tool checked first: `better-tailwindcss/no-restricted-classes`
|
|
22
|
+
* matches a class wherever it is written and cannot ask whether the element
|
|
23
|
+
* carrying it is the one the route returns. That relationship is the rule, so it
|
|
24
|
+
* is read here.
|
|
25
|
+
*/
|
|
26
|
+
import { openingElementClassUtilities } from "./jsx-class-utility.js";
|
|
27
|
+
|
|
28
|
+
const CENTERING = "mx-auto";
|
|
29
|
+
const MAX_WIDTH = /^-?max-w-(.+)$/;
|
|
30
|
+
const UNCAPPED = new Set(["full", "none"]);
|
|
31
|
+
|
|
32
|
+
function withoutVariants(token) {
|
|
33
|
+
let bracketDepth = 0;
|
|
34
|
+
let start = 0;
|
|
35
|
+
for (let index = 0; index < token.length; index += 1) {
|
|
36
|
+
if (token[index] === "[") bracketDepth += 1;
|
|
37
|
+
if (token[index] === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
38
|
+
if (token[index] === ":" && bracketDepth === 0) start = index + 1;
|
|
39
|
+
}
|
|
40
|
+
return token.slice(start);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isWidthOverride(utility) {
|
|
44
|
+
const unqualified = withoutVariants(utility);
|
|
45
|
+
if (unqualified === CENTERING) return true;
|
|
46
|
+
const capped = MAX_WIDTH.exec(unqualified);
|
|
47
|
+
return capped ? !UNCAPPED.has(capped[1]) : false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The elements one returned expression renders at its outermost level. */
|
|
51
|
+
function rootElements(node, found = []) {
|
|
52
|
+
if (!node) return found;
|
|
53
|
+
switch (node.type) {
|
|
54
|
+
case "JSXElement":
|
|
55
|
+
found.push(node);
|
|
56
|
+
return found;
|
|
57
|
+
case "JSXFragment":
|
|
58
|
+
for (const child of node.children) rootElements(child, found);
|
|
59
|
+
return found;
|
|
60
|
+
case "JSXExpressionContainer":
|
|
61
|
+
return rootElements(node.expression, found);
|
|
62
|
+
case "ConditionalExpression":
|
|
63
|
+
rootElements(node.consequent, found);
|
|
64
|
+
return rootElements(node.alternate, found);
|
|
65
|
+
case "LogicalExpression":
|
|
66
|
+
return rootElements(node.right, found);
|
|
67
|
+
default:
|
|
68
|
+
return found;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const FUNCTION_TYPES = new Set([
|
|
73
|
+
"ArrowFunctionExpression",
|
|
74
|
+
"FunctionDeclaration",
|
|
75
|
+
"FunctionExpression",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* Every expression the route function returns, its own returns only: a helper
|
|
80
|
+
* declared inside it renders something else, and its root is not the route's.
|
|
81
|
+
*/
|
|
82
|
+
function returnedExpressions(node, found = []) {
|
|
83
|
+
if (!node || typeof node.type !== "string") return found;
|
|
84
|
+
if (node.type === "ReturnStatement") {
|
|
85
|
+
if (node.argument) found.push(node.argument);
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
88
|
+
if (FUNCTION_TYPES.has(node.type)) return found;
|
|
89
|
+
for (const [key, value] of Object.entries(node)) {
|
|
90
|
+
if (key === "parent" || key === "loc" || key === "range") continue;
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
for (const child of value) returnedExpressions(child, found);
|
|
93
|
+
} else if (value && typeof value === "object") {
|
|
94
|
+
returnedExpressions(value, found);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return found;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function routeRoots(routeFunction) {
|
|
101
|
+
const body = routeFunction.body;
|
|
102
|
+
if (body.type !== "BlockStatement") return rootElements(body);
|
|
103
|
+
return returnedExpressions(body).flatMap((expression) => rootElements(expression));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const noRouteWidthOverride = {
|
|
107
|
+
meta: {
|
|
108
|
+
type: "problem",
|
|
109
|
+
docs: {
|
|
110
|
+
description: "Disallow a Portal route file setting the width of the frame it returns",
|
|
111
|
+
},
|
|
112
|
+
schema: [],
|
|
113
|
+
messages: {
|
|
114
|
+
routeWidthOverride:
|
|
115
|
+
'"{{utility}}" makes this route a second owner of the content width, so neighbouring pages drift apart and a shell change stops reaching this one. Page width belongs to the shell column; a reading-width surface takes ReadingColumn from @nadicodeai/ui.',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
create(context) {
|
|
119
|
+
return {
|
|
120
|
+
ExportDefaultDeclaration(node) {
|
|
121
|
+
if (!FUNCTION_TYPES.has(node.declaration.type)) return;
|
|
122
|
+
for (const root of routeRoots(node.declaration)) {
|
|
123
|
+
for (const utility of openingElementClassUtilities(root.openingElement, context)) {
|
|
124
|
+
if (!isWidthOverride(utility)) continue;
|
|
125
|
+
context.report({
|
|
126
|
+
node: root.openingElement,
|
|
127
|
+
messageId: "routeWidthOverride",
|
|
128
|
+
data: { utility },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A short fixed set of choices is shown, not hidden.
|
|
3
|
+
*
|
|
4
|
+
* A `Select` spends a click and a popup to reveal what it holds. That trade pays
|
|
5
|
+
* for itself over a long or a loaded list; it does not pay for two or three
|
|
6
|
+
* options that are known at author time, where the popup costs the reader the
|
|
7
|
+
* one thing they came for — seeing what the alternatives are — and costs the
|
|
8
|
+
* keyboard user an open-then-arrow instead of a single arrow.
|
|
9
|
+
*
|
|
10
|
+
* The count has to be STATIC to be a finding. A `SelectContent` filled from
|
|
11
|
+
* `{items.map(...)}` holds however many rows the data has today, so its shape is
|
|
12
|
+
* not this rule's business and any expression child stands the rule down.
|
|
13
|
+
*
|
|
14
|
+
* Standard tool checked first: `no-restricted-syntax` can select a
|
|
15
|
+
* `SelectContent`, but not one whose children are exactly two or three elements
|
|
16
|
+
* of one name and nothing dynamic. That is a property of the subtree, so it is
|
|
17
|
+
* read here.
|
|
18
|
+
*/
|
|
19
|
+
import { jsxComponentName } from "./jsx-class-utility.js";
|
|
20
|
+
|
|
21
|
+
const CONTENT_TAG = "SelectContent";
|
|
22
|
+
const ITEM_TAG = "SelectItem";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* The items one content subtree renders, and whether anything in it is computed.
|
|
26
|
+
* Groups and separators are descended through; an item's own children are not,
|
|
27
|
+
* because a label built from a variable says nothing about how many items there
|
|
28
|
+
* are. Attributes are out of scope for the same reason: `value={option.value}`
|
|
29
|
+
* on a hand-written item is ordinary.
|
|
30
|
+
*/
|
|
31
|
+
function scanContent(node, context, found) {
|
|
32
|
+
for (const child of node.children) {
|
|
33
|
+
if (child.type === "JSXExpressionContainer") {
|
|
34
|
+
if (child.expression.type !== "JSXEmptyExpression") found.dynamic = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (child.type === "JSXFragment") {
|
|
38
|
+
scanContent(child, context, found);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (child.type !== "JSXElement") continue;
|
|
42
|
+
if (jsxComponentName(child.openingElement, context) === ITEM_TAG) {
|
|
43
|
+
found.items.push(child);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
scanContent(child, context, found);
|
|
47
|
+
}
|
|
48
|
+
return found;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const preferRadioForFewStaticOptions = {
|
|
52
|
+
meta: {
|
|
53
|
+
type: "suggestion",
|
|
54
|
+
docs: {
|
|
55
|
+
description: "Prefer RadioGroup over Select for two or three statically written options",
|
|
56
|
+
},
|
|
57
|
+
schema: [],
|
|
58
|
+
messages: {
|
|
59
|
+
fewStaticOptions:
|
|
60
|
+
"This Select holds {{count}} options fixed at author time, so a reader has to open it to learn what the alternatives are. RadioGroup shows all {{count}} at once and takes one arrow key instead of an open and an arrow. Use RadioGroup with a RadioGroupItem per option.",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
create(context) {
|
|
64
|
+
return {
|
|
65
|
+
JSXElement(node) {
|
|
66
|
+
if (jsxComponentName(node.openingElement, context) !== CONTENT_TAG) return;
|
|
67
|
+
const { items, dynamic } = scanContent(node, context, { items: [], dynamic: false });
|
|
68
|
+
if (dynamic || items.length < 2 || items.length > 3) return;
|
|
69
|
+
context.report({
|
|
70
|
+
node: node.openingElement,
|
|
71
|
+
messageId: "fewStaticOptions",
|
|
72
|
+
data: { count: String(items.length) },
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ export * from "./components/logo-band.js";
|
|
|
83
83
|
export * from "./components/logo-tile.js";
|
|
84
84
|
export * from "./components/media-mount.js";
|
|
85
85
|
export * from "./components/memory-item.js";
|
|
86
|
+
export * from "./components/menu-choice-row.js";
|
|
86
87
|
export * from "./components/menubar.js";
|
|
87
88
|
export * from "./components/native-select.js";
|
|
88
89
|
export * from "./components/nav-bar.js";
|
|
@@ -99,6 +100,7 @@ export * from "./components/progress.js";
|
|
|
99
100
|
export * from "./components/proof-slot.js";
|
|
100
101
|
export * from "./components/quote-card.js";
|
|
101
102
|
export * from "./components/radio-group.js";
|
|
103
|
+
export * from "./components/reading-column.js";
|
|
102
104
|
export * from "./components/resizable.js";
|
|
103
105
|
export * from "./components/run-timeline.js";
|
|
104
106
|
export * from "./components/scroll-area.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,7 @@ export * from "./components/logo-band.js";
|
|
|
83
83
|
export * from "./components/logo-tile.js";
|
|
84
84
|
export * from "./components/media-mount.js";
|
|
85
85
|
export * from "./components/memory-item.js";
|
|
86
|
+
export * from "./components/menu-choice-row.js";
|
|
86
87
|
export * from "./components/menubar.js";
|
|
87
88
|
export * from "./components/native-select.js";
|
|
88
89
|
export * from "./components/nav-bar.js";
|
|
@@ -99,6 +100,7 @@ export * from "./components/progress.js";
|
|
|
99
100
|
export * from "./components/proof-slot.js";
|
|
100
101
|
export * from "./components/quote-card.js";
|
|
101
102
|
export * from "./components/radio-group.js";
|
|
103
|
+
export * from "./components/reading-column.js";
|
|
102
104
|
export * from "./components/resizable.js";
|
|
103
105
|
export * from "./components/run-timeline.js";
|
|
104
106
|
export * from "./components/scroll-area.js";
|
package/docs/contract.md
CHANGED
|
@@ -208,6 +208,13 @@ missing: null and undefined values use the required localized
|
|
|
208
208
|
unknown facts or replace them with indefinite loading skeletons. Editable
|
|
209
209
|
controls remain in `SettingsSection`.
|
|
210
210
|
|
|
211
|
+
`ReadingColumn` owns the centered content column a settings region reads in.
|
|
212
|
+
`width="reading"` bounds the measure at `max-w-3xl` for stacked forms and record
|
|
213
|
+
sections; `width="full"` keeps the workspace width for a page whose table needs
|
|
214
|
+
it. Both stay centered, so the column agrees with the centered page heading
|
|
215
|
+
above it at either width. Consumers supply the sections inside it and do not set
|
|
216
|
+
their own container width or centering around them.
|
|
217
|
+
|
|
211
218
|
`SettingsSection` composes Card's named header, divider, FieldGroup body, and
|
|
212
219
|
optional divided `footer` for local save actions and feedback. The caller owns
|
|
213
220
|
the form transaction and controls; the section owns their shared geometry.
|
|
@@ -258,7 +265,14 @@ seams. Empty states and operational notices remain distinct message owners.
|
|
|
258
265
|
|
|
259
266
|
`DropdownMenuContent density="comfortable"` supplies roomier command rows
|
|
260
267
|
through the shared owner. Account-menu consumers put the label first and the
|
|
261
|
-
icon at the trailing edge. `
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
268
|
+
icon at the trailing edge. `MenuChoiceRow` owns a named inline preference row:
|
|
269
|
+
the label reads at the leading edge and the exclusive choices sit together at
|
|
270
|
+
the trailing edge as compact menu radio items, each carrying a glyph or a short
|
|
271
|
+
code. `iconOnly` retains every radio's accessible name and checked state without
|
|
272
|
+
a second checkmark, and selection keeps the menu open. `ThemeModeMenu` is the
|
|
273
|
+
theme row built on it. The account menu shows two such rows, Theme and Language.
|
|
274
|
+
|
|
275
|
+
The identity row names who is signed in and carries one `DropdownMenuItem
|
|
276
|
+
iconOnly` gear at its trailing edge, linking to the account section; the menu
|
|
277
|
+
does not repeat that section's pages as commands. Consumers bind preference
|
|
278
|
+
persistence, routing, and localized labels.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nadicodeai/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@assistant-ui/react": "0.15.18",
|
|
71
71
|
"@assistant-ui/react-markdown": "0.14.14",
|
|
72
72
|
"@base-ui/react": "^1.8.0",
|
|
73
|
-
"@nadicodeai/design-system": "6.
|
|
73
|
+
"@nadicodeai/design-system": "6.4.0",
|
|
74
74
|
"@paper-design/shaders": "0.0.80",
|
|
75
75
|
"class-variance-authority": "^0.7.1",
|
|
76
76
|
"clsx": "^2.1.1",
|