@nadicodeai/ui 6.5.0 → 6.6.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/AGENTS.md +7 -0
- package/dist/components/card-row.d.ts +19 -0
- package/dist/components/card-row.d.ts.map +1 -0
- package/dist/components/card-row.js +55 -0
- package/dist/components/chart-delta.d.ts +3 -1
- package/dist/components/chart-delta.d.ts.map +1 -1
- package/dist/components/chart-delta.js +1 -1
- package/dist/components/chart-kpi-stat.d.ts +1 -1
- package/dist/components/chart-kpi-stat.d.ts.map +1 -1
- package/dist/components/chart-kpi-stat.js +22 -2
- package/dist/eslint/index.js +3 -0
- package/dist/eslint/rules/card-row-owner.js +167 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/docs/contract.md +11 -0
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -21,3 +21,10 @@ validation policy is in `../../docs/agents/develop.md`.
|
|
|
21
21
|
When the brand-icon allow-list changes, run `generate:brand-icons`.
|
|
22
22
|
Use current shadcn or assistant-ui discovery for their components;
|
|
23
23
|
`--overwrite` requires authorization to replace the affected component.
|
|
24
|
+
|
|
25
|
+
Charts are shadcn charts: `src/components/chart.tsx` is shadcn's `chart` over
|
|
26
|
+
Recharts, and the `chart-*.tsx` kit is built on it. A surface takes its chart
|
|
27
|
+
from this kit. When the kit lacks one, look it up in the shadcn registry
|
|
28
|
+
(`npx shadcn@latest search @shadcn -q chart`, run here) and add it to the kit
|
|
29
|
+
with NadicodeAI tokens before any app draws its own. `recharts` is a dependency
|
|
30
|
+
of this package only.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
2
|
+
type CardRowColumns = 2 | 3 | 4 | "auto";
|
|
3
|
+
type CardRowFrom = "sm" | "md" | "lg" | "xl";
|
|
4
|
+
/**
|
|
5
|
+
* The row sibling cards sit in. The grid stretches its cells and every direct
|
|
6
|
+
* card child fills the one it lands in, so a shorter card grows to the tallest
|
|
7
|
+
* in the row instead of leaving a ragged bottom edge
|
|
8
|
+
* (`rule/card-row-equal-height`). Below `from` the row is one column, where
|
|
9
|
+
* each card is its own row and stretching means nothing.
|
|
10
|
+
*
|
|
11
|
+
* Callers pass layout — `columns`, `from`, and a `gap-*` on `className` when
|
|
12
|
+
* the default rhythm is wrong. Appearance stays with `Card`.
|
|
13
|
+
*/
|
|
14
|
+
declare function CardRow({ className, columns, from, render, ...props }: useRender.ComponentProps<"div"> & {
|
|
15
|
+
columns?: CardRowColumns;
|
|
16
|
+
from?: CardRowFrom;
|
|
17
|
+
}): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
18
|
+
export { CardRow, type CardRowColumns, type CardRowFrom };
|
|
19
|
+
//# sourceMappingURL=card-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card-row.d.ts","sourceRoot":"","sources":["../../src/components/card-row.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAItD,KAAK,cAAc,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;AACzC,KAAK,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAkC7C;;;;;;;;;GASG;AACH,iBAAS,OAAO,CAAC,EACf,SAAS,EACT,OAAW,EACX,IAAW,EACX,MAAM,EACN,GAAG,KAAK,EACT,EAAE,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG;IACnC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB,8FAgBA;AAED,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* One column below `from`, then `columns` columns — `auto` giving each card an
|
|
6
|
+
* equal share of the row however many there are. Written out so every class
|
|
7
|
+
* string is literal and the Tailwind scanner can see it.
|
|
8
|
+
*/
|
|
9
|
+
const columnClasses = {
|
|
10
|
+
sm: {
|
|
11
|
+
2: "sm:grid-cols-2",
|
|
12
|
+
3: "sm:grid-cols-3",
|
|
13
|
+
4: "sm:grid-cols-4",
|
|
14
|
+
auto: "sm:grid-cols-[repeat(auto-fit,minmax(0,1fr))]",
|
|
15
|
+
},
|
|
16
|
+
md: {
|
|
17
|
+
2: "md:grid-cols-2",
|
|
18
|
+
3: "md:grid-cols-3",
|
|
19
|
+
4: "md:grid-cols-4",
|
|
20
|
+
auto: "md:grid-cols-[repeat(auto-fit,minmax(0,1fr))]",
|
|
21
|
+
},
|
|
22
|
+
lg: {
|
|
23
|
+
2: "lg:grid-cols-2",
|
|
24
|
+
3: "lg:grid-cols-3",
|
|
25
|
+
4: "lg:grid-cols-4",
|
|
26
|
+
auto: "lg:grid-cols-[repeat(auto-fit,minmax(0,1fr))]",
|
|
27
|
+
},
|
|
28
|
+
xl: {
|
|
29
|
+
2: "xl:grid-cols-2",
|
|
30
|
+
3: "xl:grid-cols-3",
|
|
31
|
+
4: "xl:grid-cols-4",
|
|
32
|
+
auto: "xl:grid-cols-[repeat(auto-fit,minmax(0,1fr))]",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The row sibling cards sit in. The grid stretches its cells and every direct
|
|
37
|
+
* card child fills the one it lands in, so a shorter card grows to the tallest
|
|
38
|
+
* in the row instead of leaving a ragged bottom edge
|
|
39
|
+
* (`rule/card-row-equal-height`). Below `from` the row is one column, where
|
|
40
|
+
* each card is its own row and stretching means nothing.
|
|
41
|
+
*
|
|
42
|
+
* Callers pass layout — `columns`, `from`, and a `gap-*` on `className` when
|
|
43
|
+
* the default rhythm is wrong. Appearance stays with `Card`.
|
|
44
|
+
*/
|
|
45
|
+
function CardRow({ className, columns = 2, from = "md", render, ...props }) {
|
|
46
|
+
return useRender({
|
|
47
|
+
defaultTagName: "div",
|
|
48
|
+
props: mergeProps({
|
|
49
|
+
className: cn("grid grid-cols-1 items-stretch gap-4 [&>[data-slot=card]]:h-full [&>li]:flex [&>li>[data-slot=card]]:h-full [&>li>[data-slot=card]]:w-full", columnClasses[from][columns], className),
|
|
50
|
+
}, props),
|
|
51
|
+
render,
|
|
52
|
+
state: { columns: String(columns), from, slot: "card-row" },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export { CardRow };
|
|
@@ -9,6 +9,8 @@ import * as React from "react";
|
|
|
9
9
|
*/
|
|
10
10
|
type DeltaDirection = "up" | "down" | "flat";
|
|
11
11
|
type DeltaTone = "positive" | "negative" | "neutral";
|
|
12
|
+
declare const DELTA_DIRECTION_TEXT: Record<DeltaDirection, string>;
|
|
13
|
+
declare const DELTA_TONE_CLASSES: Record<DeltaTone, string>;
|
|
12
14
|
/**
|
|
13
15
|
* Maps a delta direction onto its tone: direction crossed with whether up is
|
|
14
16
|
* good in this context. A flat delta is always neutral.
|
|
@@ -23,6 +25,6 @@ declare function Delta({ value, direction, upIsGood, vsLabel, className, ...prop
|
|
|
23
25
|
/** The named comparison period, rendered as "vs {vsLabel}". */
|
|
24
26
|
vsLabel?: string;
|
|
25
27
|
}): React.JSX.Element;
|
|
26
|
-
export { Delta, deltaTone };
|
|
28
|
+
export { Delta, deltaTone, DELTA_DIRECTION_TEXT, DELTA_TONE_CLASSES };
|
|
27
29
|
export type { DeltaDirection, DeltaTone };
|
|
28
30
|
//# sourceMappingURL=chart-delta.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-delta.d.ts","sourceRoot":"","sources":["../../src/components/chart-delta.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B;;;;;;;GAOG;AAEH,KAAK,cAAc,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7C,KAAK,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"chart-delta.d.ts","sourceRoot":"","sources":["../../src/components/chart-delta.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B;;;;;;;GAOG;AAEH,KAAK,cAAc,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7C,KAAK,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAQrD,QAAA,MAAM,oBAAoB,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAIxD,CAAC;AAEF,QAAA,MAAM,kBAAkB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAIjD,CAAC;AAEF;;;GAGG;AACH,iBAAS,SAAS,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,UAAO,GAAG,SAAS,CAMxE;AAED,iBAAS,KAAK,CAAC,EACb,KAAK,EACL,SAAS,EACT,QAAe,EACf,OAAO,EACP,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAClD,mDAAmD;IACnD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,SAAS,EAAE,cAAc,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,qBAqBA;AAED,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,CAAC;AACtE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -29,4 +29,4 @@ function Delta({ value, direction, upIsGood = true, vsLabel, className, ...props
|
|
|
29
29
|
const tone = deltaTone(direction, upIsGood);
|
|
30
30
|
return (_jsxs("span", { "data-slot": "delta", "data-direction": direction, "data-tone": tone, className: cn("inline-flex items-baseline gap-1 text-sm font-medium", DELTA_TONE_CLASSES[tone], className), ...props, children: [_jsx("span", { "aria-hidden": "true", children: DELTA_GLYPHS[direction] }), _jsx("span", { className: "sr-only", children: DELTA_DIRECTION_TEXT[direction] }), _jsx("span", { children: value }), vsLabel ? _jsxs("span", { className: "font-normal text-muted-foreground", children: ["vs ", vsLabel] }) : null] }));
|
|
31
31
|
}
|
|
32
|
-
export { Delta, deltaTone };
|
|
32
|
+
export { Delta, deltaTone, DELTA_DIRECTION_TEXT, DELTA_TONE_CLASSES };
|
|
@@ -9,7 +9,7 @@ type KpiStatDelta = {
|
|
|
9
9
|
upIsGood?: boolean;
|
|
10
10
|
/** The named comparison period, e.g. "previous 30 days". */
|
|
11
11
|
vsLabel: string;
|
|
12
|
-
/** A volume change
|
|
12
|
+
/** A volume change keeps its arrow, which is a fact, and takes no tone, which would be a judgment. */
|
|
13
13
|
neutral?: boolean;
|
|
14
14
|
};
|
|
15
15
|
type KpiStatTone = "default" | "warning" | "critical";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-kpi-stat.d.ts","sourceRoot":"","sources":["../../src/components/chart-kpi-stat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"chart-kpi-stat.d.ts","sourceRoot":"","sources":["../../src/components/chart-kpi-stat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAOtD,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,0BAA0B,CAAC;AAGlC,KAAK,YAAY,GAAG;IAClB,yCAAyC;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,SAAS,EAAE,cAAc,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,sGAAsG;IACtG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAyCtD,iBAAS,OAAO,CAAC,EACf,KAAK,EACL,KAAK,EACL,KAAK,EACL,SAAS,EACT,IAAI,EACJ,IAAgB,EAChB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,GAAG;IAChD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,uEAAuE;IACvE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB,qBA+CA;AAED,KAAK,gBAAgB,GAAG,IAAI,CAC1B,KAAK,CAAC,cAAc,CAAC,OAAO,OAAO,CAAC,EACpC,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAC9C,GAAG;IACF,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,qFAAqF;IACrF,MAAM,CAAC,EAAE,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAqBF,iBAAS,YAAY,CAAC,EACpB,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACnC,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD,4BA2BA;AAED,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AACjC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { MinusIcon, TrendingDownIcon, TrendingUpIcon } from "lucide-react";
|
|
3
4
|
import { cn } from "../lib/utils.js";
|
|
4
5
|
import { Card, CardDescription, CardHeader, CardTitle } from "./card.js";
|
|
5
6
|
import { Separator } from "./separator.js";
|
|
6
|
-
import {
|
|
7
|
+
import { Badge } from "./badge.js";
|
|
8
|
+
import { DELTA_DIRECTION_TEXT, DELTA_TONE_CLASSES, deltaTone, } from "./chart-delta.js";
|
|
7
9
|
import { Sparkline } from "./chart-sparkline.js";
|
|
8
10
|
const valueTones = {
|
|
9
11
|
default: "text-foreground",
|
|
10
12
|
warning: "text-(--nc-warning-deep)",
|
|
11
13
|
critical: "text-(--nc-error-deep)",
|
|
12
14
|
};
|
|
15
|
+
const trendIcons = {
|
|
16
|
+
up: TrendingUpIcon,
|
|
17
|
+
down: TrendingDownIcon,
|
|
18
|
+
flat: MinusIcon,
|
|
19
|
+
};
|
|
20
|
+
/*
|
|
21
|
+
* The change sits top right as an arrow plus a number, the way shadcn's
|
|
22
|
+
* dashboard-01 card shows it (`rule/kpi-card-trend-top-right`). The arrow is
|
|
23
|
+
* the direction glyph DESIGN.md requires; tone still comes from direction
|
|
24
|
+
* crossed with `upIsGood`. A neutral change keeps its arrow and takes no tone.
|
|
25
|
+
* The badge is a grid cell, so a long change wraps the label instead of
|
|
26
|
+
* covering it.
|
|
27
|
+
*/
|
|
28
|
+
function KpiStatTrend({ delta }) {
|
|
29
|
+
const Icon = trendIcons[delta.direction];
|
|
30
|
+
const tone = delta.neutral ? "neutral" : deltaTone(delta.direction, delta.upIsGood);
|
|
31
|
+
return (_jsx("dd", { "data-slot": "kpi-stat-trend", "data-direction": delta.direction, "data-tone": tone, className: "col-start-2 row-start-1 justify-self-end", children: _jsxs(Badge, { variant: "outline", className: cn("tabular-nums", DELTA_TONE_CLASSES[tone]), children: [_jsx(Icon, { "aria-hidden": "true" }), _jsx("span", { className: "sr-only", children: DELTA_DIRECTION_TEXT[delta.direction] }), delta.value] }) }));
|
|
32
|
+
}
|
|
13
33
|
function KpiStat({ label, value, delta, sparkline, note, tone = "default", className, ...props }) {
|
|
14
|
-
return (_jsxs("dl", { "data-slot": "kpi-stat", className: cn("@container/kpi-stat
|
|
34
|
+
return (_jsxs("dl", { "data-slot": "kpi-stat", className: cn("@container/kpi-stat grid min-w-0 grid-cols-[minmax(0,1fr)_auto] gap-x-2 gap-y-1.5", className), ...props, children: [_jsx("dt", { "data-slot": "kpi-stat-label", className: "col-start-1 row-start-1 min-h-8 min-w-0 text-xs font-medium text-muted-foreground", children: label }), delta ? _jsx(KpiStatTrend, { delta: delta }) : null, _jsxs("dd", { className: "col-span-2 flex min-w-0 flex-col gap-1.5", children: [_jsxs("div", { className: "flex items-end justify-between gap-3", children: [_jsx("div", { "data-slot": "kpi-stat-value", className: cn("min-w-0 shrink-0 text-[clamp(1.125rem,12cqw,1.875rem)] leading-none font-semibold", valueTones[tone]), children: value }), sparkline ? (_jsx("div", { "data-slot": "kpi-stat-sparkline", "aria-hidden": "true", className: "hidden min-w-0 max-w-24 @[15rem]/kpi-stat:block [&>svg]:h-auto [&>svg]:max-w-full", children: sparkline })) : null] }), delta ? (_jsxs("p", { "data-slot": "kpi-stat-comparison", className: "text-sm text-muted-foreground", children: ["vs ", delta.vsLabel] })) : null, note ? _jsx("p", { className: "text-sm font-medium text-muted-foreground", children: note }) : null] })] }));
|
|
15
35
|
}
|
|
16
36
|
function KpiStatTile({ render, interactive, sparkline, ...stat }) {
|
|
17
37
|
return useRender({
|
package/dist/eslint/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
+
import { cardRowOwner } from "./rules/card-row-owner.js";
|
|
3
4
|
import { confirmDestructiveAction } from "./rules/confirm-destructive-action.js";
|
|
4
5
|
import { iconControlAccessibleName } from "./rules/icon-control-accessible-name.js";
|
|
5
6
|
import { noAppFeatureComponent } from "./rules/no-app-feature-component.js";
|
|
@@ -55,6 +56,7 @@ function readPublishedVersion() {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export const strictDesignSystemRules = {
|
|
59
|
+
"nadicodeai/card-row-owner": "error",
|
|
58
60
|
"nadicodeai/icon-control-accessible-name": "error",
|
|
59
61
|
"nadicodeai/no-invalid-token-utility": "error",
|
|
60
62
|
"nadicodeai/no-native-visible-control": "error",
|
|
@@ -75,6 +77,7 @@ const plugin = {
|
|
|
75
77
|
},
|
|
76
78
|
configs: {},
|
|
77
79
|
rules: {
|
|
80
|
+
"card-row-owner": cardRowOwner,
|
|
78
81
|
"confirm-destructive-action": confirmDestructiveAction,
|
|
79
82
|
"icon-control-accessible-name": iconControlAccessibleName,
|
|
80
83
|
"no-app-feature-component": noAppFeatureComponent,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cards that share a row share one height, and one component makes that true.
|
|
3
|
+
*
|
|
4
|
+
* What is read is a JSX element's OWN class utilities together with its OWN
|
|
5
|
+
* direct children: the utilities say whether the element puts its children side
|
|
6
|
+
* by side — a grid with more than one column, or a flex that is not a column —
|
|
7
|
+
* and the children say whether those are cards. A `Card`, anything whose name
|
|
8
|
+
* ends in `Card` (`OverviewChartCard`), and a `.map()` whose callback returns
|
|
9
|
+
* one all count, because a mapped list is the row that goes ragged first.
|
|
10
|
+
*
|
|
11
|
+
* Why: a hand-written row leaves each card at its content's height, so three
|
|
12
|
+
* cards with 4, 5 and 5 rows end on three different lines and a finished
|
|
13
|
+
* analytics surface reads as broken (`rule/card-row-equal-height`). `CardRow`
|
|
14
|
+
* from `@nadicodeai/ui` stretches its cells and makes each card fill the one it
|
|
15
|
+
* lands in; a row written by hand has to remember both halves and usually
|
|
16
|
+
* remembers neither.
|
|
17
|
+
*
|
|
18
|
+
* A vertical stack is not a row and is never reported: `flex-col`, a `grid`
|
|
19
|
+
* with one column or no column count at all, and `space-y-*` each put one card
|
|
20
|
+
* under the next, where equal height means nothing.
|
|
21
|
+
*
|
|
22
|
+
* What it cannot see, by design, because each would need another file: a card
|
|
23
|
+
* behind an intermediate wrapper element, a card a child component renders from
|
|
24
|
+
* its own file, and a class list the shared reader's one hop cannot resolve.
|
|
25
|
+
* Those stay review findings. `packages/ui/src` is exempt by default, since that
|
|
26
|
+
* is where `CardRow` and the package's own compositions are built;
|
|
27
|
+
* `packageOwnedPrimitives: "lint"` holds the package to the rule too.
|
|
28
|
+
*
|
|
29
|
+
* Standard tool checked first: `better-tailwindcss/no-restricted-classes`
|
|
30
|
+
* matches a class wherever it is written and would have to ban `grid-cols-2`
|
|
31
|
+
* outright; it cannot ask what the element carrying it contains. "A multi-column
|
|
32
|
+
* container whose children are cards" is a relationship between a class list and
|
|
33
|
+
* a child list, so it is read here, on the shared class and component-name
|
|
34
|
+
* readers.
|
|
35
|
+
*/
|
|
36
|
+
import {
|
|
37
|
+
jsxComponentName,
|
|
38
|
+
openingElementClassUtilities,
|
|
39
|
+
packageOwnedPrimitivesSchema,
|
|
40
|
+
skipsPackageOwnedPrimitive,
|
|
41
|
+
} from "./jsx-class-utility.js";
|
|
42
|
+
|
|
43
|
+
const OWNER = "CardRow";
|
|
44
|
+
|
|
45
|
+
function withoutVariants(token) {
|
|
46
|
+
let bracketDepth = 0;
|
|
47
|
+
let start = 0;
|
|
48
|
+
for (let index = 0; index < token.length; index += 1) {
|
|
49
|
+
if (token[index] === "[") bracketDepth += 1;
|
|
50
|
+
if (token[index] === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
51
|
+
if (token[index] === ":" && bracketDepth === 0) start = index + 1;
|
|
52
|
+
}
|
|
53
|
+
return token.slice(start);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The utility that makes this element a row, or undefined. A `grid-cols-*` of
|
|
58
|
+
* anything but one, an arbitrary column template, an explicit `flex-row`, or a
|
|
59
|
+
* bare `flex` that no `flex-col` turns on its side — at any breakpoint, since a
|
|
60
|
+
* row at `lg` is still a row.
|
|
61
|
+
*/
|
|
62
|
+
function rowUtility(utilities) {
|
|
63
|
+
const unqualified = utilities.map(withoutVariants);
|
|
64
|
+
for (const utility of unqualified) {
|
|
65
|
+
if (!utility.startsWith("grid-cols-")) continue;
|
|
66
|
+
const columns = utility.slice("grid-cols-".length);
|
|
67
|
+
if (columns !== "1") return utility;
|
|
68
|
+
}
|
|
69
|
+
if (unqualified.includes("flex-row")) return "flex-row";
|
|
70
|
+
if (unqualified.includes("flex") && !unqualified.includes("flex-col")) return "flex";
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isCardName(name) {
|
|
75
|
+
return typeof name === "string" && (name === "Card" || /Card$/.test(name));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The element a `.map()` or `Array.from()` callback renders, descended through its return. */
|
|
79
|
+
function mappedElement(expression) {
|
|
80
|
+
if (expression?.type !== "CallExpression") return undefined;
|
|
81
|
+
const { callee } = expression;
|
|
82
|
+
if (callee.type !== "MemberExpression" || callee.computed) return undefined;
|
|
83
|
+
if (callee.property.name !== "map" && callee.property.name !== "from") return undefined;
|
|
84
|
+
const callback = expression.arguments.find(
|
|
85
|
+
(argument) =>
|
|
86
|
+
argument.type === "ArrowFunctionExpression" || argument.type === "FunctionExpression",
|
|
87
|
+
);
|
|
88
|
+
if (!callback) return undefined;
|
|
89
|
+
if (callback.body.type !== "BlockStatement") return returnedElement(callback.body);
|
|
90
|
+
for (const statement of callback.body.body) {
|
|
91
|
+
if (statement.type !== "ReturnStatement") continue;
|
|
92
|
+
const element = returnedElement(statement.argument);
|
|
93
|
+
if (element) return element;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The JSX element one expression renders, through parentheses and conditionals. */
|
|
99
|
+
function returnedElement(node) {
|
|
100
|
+
if (!node) return undefined;
|
|
101
|
+
switch (node.type) {
|
|
102
|
+
case "JSXElement":
|
|
103
|
+
return node;
|
|
104
|
+
case "ConditionalExpression":
|
|
105
|
+
return returnedElement(node.consequent) ?? returnedElement(node.alternate);
|
|
106
|
+
case "LogicalExpression":
|
|
107
|
+
return returnedElement(node.right);
|
|
108
|
+
default:
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* How many cards this element lays out directly. A `.map()` counts as two: one
|
|
115
|
+
* expression stands for however many records there are, which is the case the
|
|
116
|
+
* ragged edge shows up in. A card behind a condition counts as one.
|
|
117
|
+
*/
|
|
118
|
+
function cardChildCount(node, context) {
|
|
119
|
+
let count = 0;
|
|
120
|
+
for (const child of node.children) {
|
|
121
|
+
if (child.type === "JSXElement") {
|
|
122
|
+
if (isCardName(jsxComponentName(child.openingElement, context))) count += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (child.type !== "JSXExpressionContainer") continue;
|
|
126
|
+
const mapped = mappedElement(child.expression);
|
|
127
|
+
if (mapped) {
|
|
128
|
+
if (isCardName(jsxComponentName(mapped.openingElement, context))) count += 2;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const conditional = returnedElement(child.expression);
|
|
132
|
+
if (conditional && isCardName(jsxComponentName(conditional.openingElement, context)))
|
|
133
|
+
count += 1;
|
|
134
|
+
}
|
|
135
|
+
return count;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const cardRowOwner = {
|
|
139
|
+
meta: {
|
|
140
|
+
type: "problem",
|
|
141
|
+
docs: {
|
|
142
|
+
description: "Require the shared CardRow owner for a row of sibling cards",
|
|
143
|
+
},
|
|
144
|
+
schema: [packageOwnedPrimitivesSchema],
|
|
145
|
+
messages: {
|
|
146
|
+
cardRowOwner:
|
|
147
|
+
'"{{utility}}" lays these cards side by side without the owner of that row. An alignment class or a wrapper around a card is then enough for the shortest card to stop at its content and the row to end on a ragged edge. Use CardRow from @nadicodeai/ui, which stretches its cells and makes every card fill the one it lands in, wrapped in a list item or not (rule/card-row-equal-height).',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
create(context) {
|
|
151
|
+
if (skipsPackageOwnedPrimitive(context)) return {};
|
|
152
|
+
return {
|
|
153
|
+
JSXElement(node) {
|
|
154
|
+
if (jsxComponentName(node.openingElement, context) === OWNER) return;
|
|
155
|
+
const utility = rowUtility(openingElementClassUtilities(node.openingElement, context));
|
|
156
|
+
if (!utility) return;
|
|
157
|
+
const count = cardChildCount(node, context);
|
|
158
|
+
if (count < 2) return;
|
|
159
|
+
context.report({
|
|
160
|
+
node: node.openingElement,
|
|
161
|
+
messageId: "cardRowOwner",
|
|
162
|
+
data: { utility },
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export * from "./components/calendar.js";
|
|
|
23
23
|
export * from "./components/card.js";
|
|
24
24
|
export * from "./components/collection-scope.js";
|
|
25
25
|
export * from "./components/card-marketing.js";
|
|
26
|
+
export * from "./components/card-row.js";
|
|
26
27
|
export * from "./components/carousel.js";
|
|
27
28
|
export * from "./components/chart.js";
|
|
28
29
|
export * from "./components/chart-breakdown-table.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
|
@@ -23,6 +23,7 @@ export * from "./components/calendar.js";
|
|
|
23
23
|
export * from "./components/card.js";
|
|
24
24
|
export * from "./components/collection-scope.js";
|
|
25
25
|
export * from "./components/card-marketing.js";
|
|
26
|
+
export * from "./components/card-row.js";
|
|
26
27
|
export * from "./components/carousel.js";
|
|
27
28
|
export * from "./components/chart.js";
|
|
28
29
|
export * from "./components/chart-breakdown-table.js";
|
package/docs/contract.md
CHANGED
|
@@ -145,6 +145,7 @@ this same renderer.
|
|
|
145
145
|
- **Package surface is source-backed.** Public components stay flat under `src/components/`. Reviewed registry source under `src/internal/` is implementation, not a package namespace. Do not add top-level exports for recipes, blocks, agent, terminal, or vendor groupings unless a later package API decision adds concrete public source, built files, docs, and tests in the same change.
|
|
146
146
|
- **Agents is the client-only conversation interface.** `@nadicodeai/ui/agents` re-exports the complete `@assistant-ui/react` runtime and primitive API plus the reviewed, branded registry composition held under `src/internal/assistant-ui/`. Its built entry retains `"use client"`. Consumers receive Thread, attachments, Markdown, reasoning, tools, suggestions, providers, runtimes, hooks, and types from that one domain namespace. Agent identity components use their direct component subpaths so server compositions do not import the conversation runtime. The vendor name is not a public NadicodeAI subpath, and the package does not rename, wrap, or rebuild assistant-ui behavior.
|
|
147
147
|
- **Base UI is the active primitive base.** `components.json` must resolve through a Base style, currently `base-nova`, and `npx shadcn@latest info --json` must report `base: "base"`. Do not reintroduce `radix-ui`, `@radix-ui/*`, or `vaul` to package source, dependencies, or app optimizer configuration. Registry-owned shadcn primitives stay aligned with current `shadcn@latest` Base UI output; use Base UI `render` composition instead of Radix `asChild`. `cmdk` is allowed only for the stock shadcn Command component while the current `base-nova` registry declares that dependency; do not use it elsewhere or replace it without a shadcn/compatibility decision and tests.
|
|
148
|
+
- **Charts are shadcn charts.** `chart.tsx` is shadcn's `chart` component over Recharts (`ChartContainer`, `ChartTooltip`, `ChartLegend`, theme-keyed `ChartConfig`), and the chart kit (`chart-time-series`, `chart-breakdown-table`, `chart-sparkline`, `chart-bullet-bar`, `chart-kpi-stat`, `chart-delta`, `chart-date-range-control`, `chart-entity-colors`) is built on it. An app composes these and never imports `recharts` or draws SVG charts of its own; `recharts` is declared by this package alone. A chart the kit lacks is first looked up in the shadcn registry through the CLI or MCP, then added to the kit with NadicodeAI tokens and the chart accessibility contract. What a chart shows is decided by the product; how it is drawn comes from here.
|
|
148
149
|
- **Named shadcn blocks stay named.** When a user, plan, or issue selects a shadcn block/example such as `login-02`, `signup-02`, `dashboard-01`, or `sidebar-*`, use that registry item as the source of truth through shadcn CLI/MCP. Do not replace it with a package-authored abstraction unless the plan explicitly records the substitution, the reason, and the true consumer route that proves it.
|
|
149
150
|
- **A change to what the package ships bumps the version.** Any change to exported components, `src/index.ts`, package `exports`, `globals.css`, theme behavior, package docs, or design-system token, CSS, or theme output this package consumes bumps both package versions in lockstep before handoff, and `npm pack --dry-run -w @nadicodeai/ui` shows exactly what consumers receive (procedure: `../../../docs/agents/publish.md`).
|
|
150
151
|
|
|
@@ -215,6 +216,16 @@ it. Both stay centered, so the column agrees with the centered page heading
|
|
|
215
216
|
above it at either width. Consumers supply the sections inside it and do not set
|
|
216
217
|
their own container width or centering around them.
|
|
217
218
|
|
|
219
|
+
`CardRow` owns a row of sibling cards. The grid stretches its cells and every
|
|
220
|
+
direct card child fills the one it lands in, so cards that share a row share one
|
|
221
|
+
height instead of ending on a ragged edge (`rule/card-row-equal-height`).
|
|
222
|
+
`columns` takes 2, 3, 4, or `auto` for an equal share however many cards there
|
|
223
|
+
are, and `from` names the breakpoint the row starts at (`sm`, `md`, `lg`, `xl`);
|
|
224
|
+
below it the row is one column, where each card is its own row. Consumers pass
|
|
225
|
+
layout — the two props, a `gap-*` when the default rhythm is wrong, and `render`
|
|
226
|
+
when the row is a `section` or a list — never appearance. `nadicodeai/card-row-owner`
|
|
227
|
+
reports a multi-column grid or row flex of cards written without it.
|
|
228
|
+
|
|
218
229
|
`SettingsSection` composes Card's named header, divider, FieldGroup body, and
|
|
219
230
|
optional divided `footer` for local save actions and feedback. The caller owns
|
|
220
231
|
the form transaction and controls; the section owns their shared geometry.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nadicodeai/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.6.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.6.0",
|
|
74
74
|
"@paper-design/shaders": "0.0.80",
|
|
75
75
|
"class-variance-authority": "^0.7.1",
|
|
76
76
|
"clsx": "^2.1.1",
|