@nadicodeai/ui 6.5.0 → 6.6.1

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 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.
@@ -1 +1 @@
1
- {"version":3,"file":"action-status.d.ts","sourceRoot":"","sources":["../../src/components/action-status.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AACD,iBAAS,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAe,EAAE,EAAE,iBAAiB,+BAiBpF;AACD,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"action-status.d.ts","sourceRoot":"","sources":["../../src/components/action-status.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AACD,iBAAS,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAe,EAAE,EAAE,iBAAiB,+BAapF;AACD,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { CheckIcon, Loader2Icon } from "lucide-react";
2
+ import { CheckIcon } from "lucide-react";
3
+ import { ActivityOrb } from "./activity-orb.js";
3
4
  function ActionStatus({ state, idle, busy, done, idleIcon = null }) {
4
- return (_jsxs(_Fragment, { children: [state === "saving" ? (_jsx(Loader2Icon, { "aria-hidden": "true", className: "motion-safe:animate-spin motion-reduce:animate-pulse", "data-icon": "inline-start" })) : state === "done" ? (_jsx(CheckIcon, { "aria-hidden": "true", "data-icon": "inline-start" })) : (idleIcon), state === "saving" ? busy : state === "done" ? done : idle] }));
5
+ return (_jsxs(_Fragment, { children: [state === "saving" ? (_jsx(ActivityOrb, { activity: "processing", "data-icon": "inline-start", decorative: true })) : state === "done" ? (_jsx(CheckIcon, { "aria-hidden": "true", "data-icon": "inline-start" })) : (idleIcon), state === "saving" ? busy : state === "done" ? done : idle] }));
5
6
  }
6
7
  export { ActionStatus };
@@ -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;AAoBrD;;;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,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,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 has direction without implying success or failure. */
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;AAKtD,OAAO,EAAS,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAGtE,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,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAQtD,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
+ {"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 { Delta } from "./chart-delta.js";
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 flex min-w-0 flex-col gap-1.5", className), ...props, children: [_jsx("dt", { "data-slot": "kpi-stat-label", className: "min-h-8 text-xs font-medium text-muted-foreground", children: label }), _jsxs("dd", { className: "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?.neutral ? (_jsxs("p", { className: "text-sm tabular-nums text-muted-foreground", children: [delta.value, " \u00B7 ", delta.vsLabel] })) : delta ? (_jsx(Delta, { value: delta.value, direction: delta.direction, upIsGood: delta.upIsGood, vsLabel: delta.vsLabel })) : null, note ? _jsx("p", { className: "text-sm font-medium text-muted-foreground", children: note }) : null] })] }));
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({
@@ -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";
@@ -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/activity.md CHANGED
@@ -65,6 +65,10 @@ Use 20 CSS pixels inline, including buttons and rows with room for that size;
65
65
  separately tuned, and must not be obtained by shrinking the 64 preset. The 88
66
66
  extent uses the 64 preset. A 12–16 pixel badge slot keeps its existing compact
67
67
  spinner until its surrounding component explicitly accommodates this family.
68
+ Nadia's desktop accommodates it once, in the spinner itself: the spinner's box
69
+ stays one text line tall and at least 20 pixels wide, and the 20 pixel orb sits
70
+ centred in it and overhangs it above and below, so a row keeps its height and
71
+ the orb never meets the text beside it.
68
72
 
69
73
  ## React
70
74
 
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.5.0",
3
+ "version": "6.6.1",
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.5.0",
73
+ "@nadicodeai/design-system": "6.6.1",
74
74
  "@paper-design/shaders": "0.0.80",
75
75
  "class-variance-authority": "^0.7.1",
76
76
  "clsx": "^2.1.1",