@nadicodeai/ui 6.0.2 → 6.1.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.
Files changed (57) hide show
  1. package/dist/components/action-menu.d.ts +8 -0
  2. package/dist/components/action-menu.d.ts.map +1 -0
  3. package/dist/components/action-menu.js +8 -0
  4. package/dist/components/card.d.ts +1 -1
  5. package/dist/components/card.d.ts.map +1 -1
  6. package/dist/components/card.js +5 -3
  7. package/dist/components/chart-breakdown-table.d.ts +3 -2
  8. package/dist/components/chart-breakdown-table.d.ts.map +1 -1
  9. package/dist/components/chart-breakdown-table.js +13 -9
  10. package/dist/components/chart-bullet-bar.d.ts +13 -12
  11. package/dist/components/chart-bullet-bar.d.ts.map +1 -1
  12. package/dist/components/chart-bullet-bar.js +16 -36
  13. package/dist/components/chart-kpi-stat.d.ts +26 -15
  14. package/dist/components/chart-kpi-stat.d.ts.map +1 -1
  15. package/dist/components/chart-kpi-stat.js +28 -4
  16. package/dist/components/chart-time-series-layout.d.ts +6 -0
  17. package/dist/components/chart-time-series-layout.d.ts.map +1 -0
  18. package/dist/components/chart-time-series-layout.js +6 -0
  19. package/dist/components/chart-time-series.d.ts +6 -2
  20. package/dist/components/chart-time-series.d.ts.map +1 -1
  21. package/dist/components/chart-time-series.js +27 -9
  22. package/dist/components/chart.js +1 -1
  23. package/dist/components/collection-filters.d.ts +28 -0
  24. package/dist/components/collection-filters.d.ts.map +1 -0
  25. package/dist/components/collection-filters.js +54 -0
  26. package/dist/components/collection-scope.d.ts +15 -0
  27. package/dist/components/collection-scope.d.ts.map +1 -0
  28. package/dist/components/collection-scope.js +7 -0
  29. package/dist/components/collection-toolbar.d.ts +10 -0
  30. package/dist/components/collection-toolbar.d.ts.map +1 -0
  31. package/dist/components/collection-toolbar.js +4 -0
  32. package/dist/components/settings-facts.d.ts +28 -0
  33. package/dist/components/settings-facts.d.ts.map +1 -0
  34. package/dist/components/settings-facts.js +7 -0
  35. package/dist/components/settings-section.d.ts +5 -5
  36. package/dist/components/settings-section.d.ts.map +1 -1
  37. package/dist/components/settings-section.js +6 -11
  38. package/dist/components/sidebar.d.ts +4 -1
  39. package/dist/components/sidebar.d.ts.map +1 -1
  40. package/dist/components/sidebar.js +3 -3
  41. package/dist/components/table-layouts.d.ts +22 -0
  42. package/dist/components/table-layouts.d.ts.map +1 -0
  43. package/dist/components/table-layouts.js +20 -0
  44. package/dist/components/table.d.ts +15 -2
  45. package/dist/components/table.d.ts.map +1 -1
  46. package/dist/components/table.js +8 -4
  47. package/dist/eslint/rules/jsx-class-utility.js +44 -12
  48. package/dist/eslint/rules/no-resting-settings-control.js +15 -13
  49. package/dist/eslint/rules/no-shadcn-appearance-override.js +189 -36
  50. package/dist/eslint/rules/shadcn-composition.js +48 -3
  51. package/dist/index.d.ts +5 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +5 -0
  54. package/docs/component-classification.md +3 -2
  55. package/docs/contract.md +78 -7
  56. package/docs/product-compositions.md +14 -11
  57. package/package.json +3 -2
@@ -6,8 +6,8 @@
6
6
  * editor that changes them. The regression this catches is a form control
7
7
  * rendered beside those facts rather than inside the editor they summon.
8
8
  *
9
- * "Inside the editor" means inside an `ActionDialogForm`'s `fields` prop, or
10
- * inside a `Dialog`/`Drawer` subtree for a surface that owns its own. Being a
9
+ * "Inside the editor" means inside a `SettingsSection`, an `ActionDialogForm`'s
10
+ * `fields` prop, or a `Dialog`/`Drawer` subtree for a surface that owns its own. Being a
11
11
  * CHILD of `ActionDialogForm` is the opposite: children are the resting surface
12
12
  * the dialog is opened from, which is exactly where a control must not be.
13
13
  *
@@ -21,6 +21,7 @@
21
21
  * here. Replaces `restingSettingsControlViolations` in the retired portal
22
22
  * page-grammar analyzers (#821).
23
23
  */
24
+ import { jsxComponentName } from "./jsx-class-utility.js";
24
25
 
25
26
  // The controls a person types or picks into. Lowercase `input` is deliberately
26
27
  // absent: a hidden field carries context a summoned editor already decided, and
@@ -37,15 +38,15 @@ const RESTING_CONTROL_TAGS = new Set([
37
38
 
38
39
  const FACTS_SECTION_TAG = "SettingsFactsSection";
39
40
 
40
- function elementName(node) {
41
- return node?.name?.type === "JSXIdentifier" ? node.name.name : undefined;
42
- }
43
-
44
- function summons(node) {
45
- if (node.type === "JSXAttribute") return node.name.name === "fields";
41
+ function summons(node, context) {
42
+ if (node.type === "JSXAttribute") {
43
+ return (
44
+ node.name.name === "fields" && jsxComponentName(node.parent, context) === "ActionDialogForm"
45
+ );
46
+ }
46
47
  if (node.type !== "JSXElement") return false;
47
- const name = elementName(node.openingElement);
48
- return Boolean(name && (name.startsWith("Dialog") || name.startsWith("Drawer")));
48
+ const name = jsxComponentName(node.openingElement, context);
49
+ return ["SettingsSection", "Dialog", "DialogContent", "Drawer", "DrawerContent"].includes(name);
49
50
  }
50
51
 
51
52
  export const noRestingSettingsControl = {
@@ -58,7 +59,7 @@ export const noRestingSettingsControl = {
58
59
  schema: [],
59
60
  messages: {
60
61
  restingControl:
61
- "<{{tag}}> rests beside its facts: put it in the editor the facts summon (an ActionDialogForm `fields` prop, or a Dialog/Drawer subtree).",
62
+ "<{{tag}}> rests beside its facts: use SettingsSection for a form, or put it in the summoned editor (ActionDialogForm `fields`, Dialog, or Drawer).",
62
63
  },
63
64
  },
64
65
  create(context) {
@@ -67,13 +68,14 @@ export const noRestingSettingsControl = {
67
68
 
68
69
  return {
69
70
  JSXOpeningElement(node) {
70
- const name = elementName(node);
71
+ const name = jsxComponentName(node, context);
71
72
  if (name === FACTS_SECTION_TAG) {
72
73
  declaresFacts = true;
73
74
  return;
74
75
  }
75
76
  if (!name || !RESTING_CONTROL_TAGS.has(name)) return;
76
- if (context.sourceCode.getAncestors(node).some(summons)) return;
77
+ if (context.sourceCode.getAncestors(node).some((ancestor) => summons(ancestor, context)))
78
+ return;
77
79
  resting.push({ node, name });
78
80
  },
79
81
  "Program:exit"() {
@@ -12,6 +12,8 @@
12
12
  * of which still compile and still fork the primitive. Those are what survives
13
13
  * here; the raw-palette names are gone from the sets below with the palette.
14
14
  */
15
+ import { posix } from "node:path";
16
+
15
17
  import {
16
18
  classUtilities,
17
19
  openingElementClassUtilities,
@@ -19,6 +21,127 @@ import {
19
21
  skipsPackageOwnedPrimitive,
20
22
  } from "./jsx-class-utility.js";
21
23
 
24
+ // These exact Portal adapters forward props to package primitives. Card
25
+ // chooses its typed surface default; Table owns only structural geometry.
26
+ // Their other exports are direct reexports.
27
+ const portalCardExports = new Set([
28
+ "Card",
29
+ "CardHeader",
30
+ "CardTitle",
31
+ "CardDescription",
32
+ "CardAction",
33
+ "CardContent",
34
+ ]);
35
+ const portalTableExports = new Set([
36
+ "Table",
37
+ "TableHead",
38
+ "TableCell",
39
+ "TableBody",
40
+ "TableCaption",
41
+ "TableFooter",
42
+ "TableHeader",
43
+ "TableRow",
44
+ ]);
45
+
46
+ function primitiveModule(source, filename) {
47
+ if (source.startsWith("@nadicodeai/ui/components/")) {
48
+ return { card: source === "@nadicodeai/ui/components/card", exports: undefined };
49
+ }
50
+ const normalized = filename.replaceAll("\\", "/");
51
+ const portalStart = normalized.lastIndexOf("/apps/portal/src/");
52
+ if (portalStart === -1) return undefined;
53
+ const portalSource = `${normalized.slice(0, portalStart)}/apps/portal/src`;
54
+ const resolved = source.startsWith(".")
55
+ ? posix.normalize(posix.join(posix.dirname(normalized), source)).replace(/\.[jt]sx?$/, "")
56
+ : undefined;
57
+ if (source === "@/components/kit/card" || resolved === `${portalSource}/components/kit/card`) {
58
+ return { card: true, exports: portalCardExports };
59
+ }
60
+ if (source === "@/components/kit/table" || resolved === `${portalSource}/components/kit/table`) {
61
+ return { card: false, exports: portalTableExports };
62
+ }
63
+ return undefined;
64
+ }
65
+
66
+ function primitiveExport(module, name) {
67
+ if (module.exports && !module.exports.has(name)) return undefined;
68
+ return { cardRoot: module.card && name === "Card" };
69
+ }
70
+
71
+ const appearanceStyleProperties = new Set([
72
+ "appearance",
73
+ "WebkitAppearance",
74
+ "MozAppearance",
75
+ "color",
76
+ "background",
77
+ "backgroundColor",
78
+ "backgroundImage",
79
+ "boxShadow",
80
+ "textShadow",
81
+ "opacity",
82
+ "outlineColor",
83
+ "filter",
84
+ "backdropFilter",
85
+ "WebkitBackdropFilter",
86
+ "fill",
87
+ "fillOpacity",
88
+ "stroke",
89
+ "strokeOpacity",
90
+ ...[
91
+ "",
92
+ "Top",
93
+ "Right",
94
+ "Bottom",
95
+ "Left",
96
+ "Block",
97
+ "Inline",
98
+ "BlockStart",
99
+ "BlockEnd",
100
+ "InlineStart",
101
+ "InlineEnd",
102
+ ].map((side) => `border${side}Color`),
103
+ ...[
104
+ "",
105
+ "TopLeft",
106
+ "TopRight",
107
+ "BottomLeft",
108
+ "BottomRight",
109
+ "StartStart",
110
+ "StartEnd",
111
+ "EndStart",
112
+ "EndEnd",
113
+ ].map((corner) => `border${corner}Radius`),
114
+ ]);
115
+
116
+ function staticStyleValue(node) {
117
+ if (node.type === "Literal") return node.value;
118
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
119
+ return node.quasis[0]?.value.cooked;
120
+ }
121
+ return undefined;
122
+ }
123
+
124
+ // Inline object literals only. Unknown values, computed keys, imported style
125
+ // objects and spreads remain review work; layout properties are not paint.
126
+ function inlineAppearanceProperties(attribute) {
127
+ if (attribute.type !== "JSXAttribute" || attribute.name.name !== "style") return [];
128
+ const expression =
129
+ attribute.value?.type === "JSXExpressionContainer" ? attribute.value.expression : undefined;
130
+ if (expression?.type !== "ObjectExpression") return [];
131
+ return expression.properties.filter((property) => {
132
+ if (property.type !== "Property") return false;
133
+ const name =
134
+ property.key.type === "Literal"
135
+ ? property.key.value
136
+ : !property.computed && property.key.type === "Identifier"
137
+ ? property.key.name
138
+ : undefined;
139
+ if (!appearanceStyleProperties.has(name)) return false;
140
+ const value = staticStyleValue(property.value);
141
+ return typeof value === "number" || (typeof value === "string" && value.trim() !== "");
142
+ });
143
+ }
144
+
22
145
  function withoutVariants(utility) {
23
146
  return utility.slice(utility.lastIndexOf(":") + 1);
24
147
  }
@@ -74,7 +197,10 @@ function isAppearanceUtility(utility) {
74
197
 
75
198
  function isProhibitedAppearanceUtility(utility, { cardRoot = false } = {}) {
76
199
  const base = withoutVariants(utility);
77
- return isAppearanceUtility(utility) && (!isBorderRemoval(base) || cardRoot);
200
+ return (
201
+ targetsSharedSlot(utility) ||
202
+ (isAppearanceUtility(utility) && (!isBorderRemoval(base) || cardRoot))
203
+ );
78
204
  }
79
205
 
80
206
  function targetsSharedSlot(utility) {
@@ -92,15 +218,14 @@ export const noShadcnAppearanceOverride = {
92
218
  meta: {
93
219
  type: "problem",
94
220
  docs: {
95
- description:
96
- "Disallow app callers repainting imported shadcn primitives; compose their structural layout only",
221
+ description: "Keep shared component appearance and internal slot layout at their owner",
97
222
  },
98
223
  // Default "exempt": inside @nadicodeai/ui the primitives ARE the appearance,
99
224
  // so every utility here would be a false positive.
100
225
  schema: [packageOwnedPrimitivesSchema],
101
226
  messages: {
102
227
  appearanceOverride:
103
- "Do not repaint an imported @nadicodeai/ui primitive with {{utility}}; use its semantic variant or package primitive.",
228
+ "Do not override shared component appearance or internal slot layout with {{utility}}; use a component prop or fix its shared owner.",
104
229
  },
105
230
  },
106
231
  create(context) {
@@ -108,48 +233,76 @@ export const noShadcnAppearanceOverride = {
108
233
  return {};
109
234
  }
110
235
 
111
- const sharedPrimitiveNames = new Set();
112
- const sharedCardRootNames = new Set();
113
- const isSharedPrimitive = (node) =>
114
- node?.type === "JSXOpeningElement" &&
115
- node.name.type === "JSXIdentifier" &&
116
- sharedPrimitiveNames.has(node.name.name);
117
- const isSharedCardRoot = (node) =>
118
- node?.type === "JSXOpeningElement" &&
119
- node.name.type === "JSXIdentifier" &&
120
- sharedCardRootNames.has(node.name.name);
236
+ const sourceCode = context.sourceCode;
237
+ const sharedBindings = new Map();
238
+
239
+ function importedBinding(identifier) {
240
+ for (let scope = sourceCode.getScope(identifier); scope; scope = scope.upper) {
241
+ const variable = scope.set.get(identifier.name);
242
+ if (variable) return sharedBindings.get(variable);
243
+ }
244
+ return undefined;
245
+ }
246
+
247
+ function sharedPrimitive(node) {
248
+ if (node?.type !== "JSXOpeningElement") return undefined;
249
+ if (node.name.type === "JSXIdentifier") {
250
+ const binding = importedBinding(node.name);
251
+ return binding && binding.name !== "*"
252
+ ? primitiveExport(binding.module, binding.name)
253
+ : undefined;
254
+ }
255
+ if (node.name.type === "JSXMemberExpression" && node.name.object.type === "JSXIdentifier") {
256
+ const binding = importedBinding(node.name.object);
257
+ return binding?.name === "*"
258
+ ? primitiveExport(binding.module, node.name.property.name)
259
+ : undefined;
260
+ }
261
+ return undefined;
262
+ }
263
+
121
264
  return {
122
- ImportDeclaration(node) {
123
- if (
124
- typeof node.source.value !== "string" ||
125
- !node.source.value.startsWith("@nadicodeai/ui/components/")
126
- ) {
127
- return;
128
- }
129
- for (const specifier of node.specifiers) {
130
- sharedPrimitiveNames.add(specifier.local.name);
131
- if (
132
- node.source.value === "@nadicodeai/ui/components/card" &&
133
- specifier.type === "ImportSpecifier" &&
134
- specifier.imported.name === "Card"
135
- ) {
136
- sharedCardRootNames.add(specifier.local.name);
265
+ Program(node) {
266
+ // Imports are hoisted; collect their declared variables before visiting
267
+ // JSX. Binding identity also distinguishes shadowing parameters/locals.
268
+ for (const declaration of node.body) {
269
+ if (declaration.type !== "ImportDeclaration" || declaration.importKind === "type")
270
+ continue;
271
+ const module = primitiveModule(declaration.source.value, context.filename);
272
+ if (!module) continue;
273
+ for (const specifier of declaration.specifiers) {
274
+ if (specifier.importKind === "type") continue;
275
+ const name =
276
+ specifier.type === "ImportNamespaceSpecifier"
277
+ ? "*"
278
+ : specifier.type === "ImportSpecifier"
279
+ ? (specifier.imported.name ?? specifier.imported.value)
280
+ : "default";
281
+ for (const variable of sourceCode.getDeclaredVariables(specifier)) {
282
+ sharedBindings.set(variable, { module, name });
283
+ }
137
284
  }
138
285
  }
139
286
  },
140
287
  JSXOpeningElement(node) {
141
- if (!isSharedPrimitive(node)) return;
142
- for (const utility of openingElementClassUtilities(node, context)) {
143
- if (
144
- utility &&
145
- isProhibitedAppearanceUtility(utility, { cardRoot: isSharedCardRoot(node) })
146
- ) {
288
+ const primitive = sharedPrimitive(node);
289
+ if (!primitive) return;
290
+ for (const utility of openingElementClassUtilities(node, context, true)) {
291
+ if (utility && isProhibitedAppearanceUtility(utility, primitive)) {
147
292
  context.report({ node, messageId: "appearanceOverride", data: { utility } });
148
293
  }
149
294
  }
295
+ for (const property of node.attributes.flatMap(inlineAppearanceProperties)) {
296
+ const name = property.key.name ?? property.key.value;
297
+ context.report({
298
+ node: property,
299
+ messageId: "appearanceOverride",
300
+ data: { utility: `style.${name}` },
301
+ });
302
+ }
150
303
  },
151
304
  JSXAttribute(node) {
152
- if (isSharedPrimitive(node.parent)) return;
305
+ if (sharedPrimitive(node.parent)) return;
153
306
  for (const utility of classUtilities(node, context)) {
154
307
  if (
155
308
  !targetsSharedSlot(utility) ||
@@ -1,5 +1,5 @@
1
1
  /*
2
- * The four composition conventions the shadcn primitives are built to, checked
2
+ * The composition conventions the shared components are built to, checked
3
3
  * where they are composed:
4
4
  *
5
5
  * - an overlay carries its title, so a screen reader announces what opened
@@ -9,7 +9,9 @@
9
9
  * - an `AvatarImage` has an `AvatarFallback` beside it, or the avatar is blank
10
10
  * until the image loads;
11
11
  * - a lucide icon inside a `Button` carries `data-icon`, the hook the button's
12
- * own spacing rules key on.
12
+ * own spacing rules key on;
13
+ * - a settings submit action uses SettingsSection's shared footer, so each
14
+ * caller cannot rebuild its own divider, padding, and action position.
13
15
  *
14
16
  * Standard tool checked first: `jsx-a11y` covers roles and ARIA on
15
17
  * intrinsic elements and says nothing about these composed components;
@@ -18,7 +20,11 @@
18
20
  * of the retired @nadicodeai/ui composition-conventions scanner (#822); stock
19
21
  * upstream shapes carry an inline disable with the reason at the site.
20
22
  */
21
- import { packageOwnedPrimitivesSchema, skipsPackageOwnedPrimitive } from "./jsx-class-utility.js";
23
+ import {
24
+ jsxComponentName,
25
+ packageOwnedPrimitivesSchema,
26
+ skipsPackageOwnedPrimitive,
27
+ } from "./jsx-class-utility.js";
22
28
 
23
29
  const OVERLAY_TITLES = {
24
30
  AlertDialogContent: "AlertDialogTitle",
@@ -75,6 +81,39 @@ function isIconTag(tagName) {
75
81
  return tagName === "Icon" || tagName.endsWith("Icon");
76
82
  }
77
83
 
84
+ function misplacedSettingsSubmit(node, context) {
85
+ if (jsxComponentName(node, context) !== "Button") return false;
86
+ const attribute = node.attributes.find(
87
+ (entry) => entry.type === "JSXAttribute" && entry.name.name === "type",
88
+ );
89
+ const value =
90
+ attribute?.value?.type === "JSXExpressionContainer"
91
+ ? attribute.value.expression
92
+ : attribute?.value;
93
+ const type =
94
+ value?.type === "TemplateLiteral" && value.expressions.length === 0
95
+ ? value.quasis[0]?.value.cooked
96
+ : value?.value;
97
+ if (type !== "submit") return false;
98
+ const ancestors = context.sourceCode.getAncestors(node);
99
+ const section = ancestors.findLast(
100
+ (ancestor) =>
101
+ ancestor.type === "JSXElement" &&
102
+ jsxComponentName(ancestor.openingElement, context) === "SettingsSection",
103
+ );
104
+ if (!section) return false;
105
+ return !ancestors.slice(ancestors.indexOf(section) + 1).some((ancestor) => {
106
+ if (ancestor.type === "JSXAttribute")
107
+ return ancestor.name.name === "footer" && ancestor.parent === section.openingElement;
108
+ return (
109
+ ancestor.type === "JSXElement" &&
110
+ ["DialogContent", "DrawerContent", "AlertDialogContent"].includes(
111
+ jsxComponentName(ancestor.openingElement, context),
112
+ )
113
+ );
114
+ });
115
+ }
116
+
78
117
  export const shadcnComposition = {
79
118
  meta: {
80
119
  type: "problem",
@@ -88,6 +127,8 @@ export const shadcnComposition = {
88
127
  selectGroup: "SelectItem must be rendered inside SelectGroup.",
89
128
  avatarFallback: "AvatarImage requires an AvatarFallback in the same Avatar.",
90
129
  buttonIcon: "An icon inside Button must carry data-icon.",
130
+ settingsFooter:
131
+ "Put a SettingsSection submit action in its footer prop; the shared component owns the divider, spacing, and action position.",
91
132
  },
92
133
  },
93
134
  create(context) {
@@ -101,6 +142,10 @@ export const shadcnComposition = {
101
142
  if (!tag) return;
102
143
  const element = node.parent;
103
144
 
145
+ if (misplacedSettingsSubmit(node, context)) {
146
+ context.report({ node, messageId: "settingsFooter" });
147
+ }
148
+
104
149
  const title = OVERLAY_TITLES[tag];
105
150
  if (title && !hasDescendant(element, title)) {
106
151
  context.report({ node, messageId: "overlayTitle", data: { tag, title } });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./components/accordion.js";
2
2
  export * from "./components/action-band.js";
3
+ export * from "./components/action-menu.js";
3
4
  export * from "./components/action-status.js";
4
5
  export * from "./components/activity-orb.js";
5
6
  export * from "./components/alert.js";
@@ -20,6 +21,7 @@ export * from "./components/button.js";
20
21
  export * from "./components/button-group.js";
21
22
  export * from "./components/calendar.js";
22
23
  export * from "./components/card.js";
24
+ export * from "./components/collection-scope.js";
23
25
  export * from "./components/card-marketing.js";
24
26
  export * from "./components/carousel.js";
25
27
  export * from "./components/chart.js";
@@ -105,6 +107,9 @@ export * from "./components/security-grid.js";
105
107
  export * from "./components/select.js";
106
108
  export * from "./components/separator.js";
107
109
  export * from "./components/settings-section.js";
110
+ export * from "./components/settings-facts.js";
111
+ export * from "./components/collection-toolbar.js";
112
+ export * from "./components/collection-filters.js";
108
113
  export * from "./components/sheet.js";
109
114
  export * from "./components/shift-band.js";
110
115
  export * from "./components/sidebar.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
@@ -1,5 +1,6 @@
1
1
  export * from "./components/accordion.js";
2
2
  export * from "./components/action-band.js";
3
+ export * from "./components/action-menu.js";
3
4
  export * from "./components/action-status.js";
4
5
  export * from "./components/activity-orb.js";
5
6
  export * from "./components/alert.js";
@@ -20,6 +21,7 @@ export * from "./components/button.js";
20
21
  export * from "./components/button-group.js";
21
22
  export * from "./components/calendar.js";
22
23
  export * from "./components/card.js";
24
+ export * from "./components/collection-scope.js";
23
25
  export * from "./components/card-marketing.js";
24
26
  export * from "./components/carousel.js";
25
27
  export * from "./components/chart.js";
@@ -105,6 +107,9 @@ export * from "./components/security-grid.js";
105
107
  export * from "./components/select.js";
106
108
  export * from "./components/separator.js";
107
109
  export * from "./components/settings-section.js";
110
+ export * from "./components/settings-facts.js";
111
+ export * from "./components/collection-toolbar.js";
112
+ export * from "./components/collection-filters.js";
108
113
  export * from "./components/sheet.js";
109
114
  export * from "./components/shift-band.js";
110
115
  export * from "./components/sidebar.js";
@@ -19,8 +19,9 @@ copy, and behavior.
19
19
 
20
20
  | Component | Classification | Built on | Required states | Package-owned accessibility | Fixture |
21
21
  | --- | --- | --- | --- | --- | --- |
22
- | `SettingsSection` | Net-new NadicodeAI product composition. | shadcn `FieldGroup` plus standard section markup. | header with optional actions; field group content. | section structure; no form behavior. | `../tests/guards/product-compositions.test.ts` |
23
- | `SettingsField` | Net-new NadicodeAI product composition. | shadcn `Field`, `FieldLabel`, `FieldDescription`, and `FieldError`. | valid and invalid field rows; disabled rows. | Label `htmlFor`; render props receive control attributes; element controls require an explicit matching `id` and described-by wiring. | `../tests/guards/product-compositions.test.ts` |
22
+ | `ActionMenu` | Canonical record-action composition. | shadcn `Button` and `DropdownMenu`. | closed, open, keyboard selection, focus return. | Required accessible trigger name; inherited menu mechanics. | `../src/components/action-menu.test.tsx` |
23
+ | `SettingsSection` | NadicodeAI product composition. | shadcn Card slots, Separator, and FieldGroup. | header, field group, and divided action footer. | Section structure; consumer owns the form transaction. | `../src/components/settings-section.test.tsx` |
24
+ | `SettingsField` | NadicodeAI product composition. | shadcn Field, FieldLabel, FieldDescription, and FieldError. | valid, invalid, required, and disabled field rows. | Render callback supplies the control's label and message associations in server and client components. | `../src/components/settings-section.test.tsx` |
24
25
  | `FeedbackState` | Net-new central product state composition adapted from shadcn empty-state patterns. | shadcn `Empty` and lucide status icons. | `empty`, `loading`, `saving`, `error`, `permission`, `offline`, `no-results`, `fatal`. | `role="status"` for loading/saving; `role="alert"` for error, permission, offline, and fatal. | `../tests/guards/product-compositions.test.ts` |
25
26
 
26
27
  ## App-Owned Boundaries
package/docs/contract.md CHANGED
@@ -27,6 +27,9 @@ Physical role, not content importance, chooses depth:
27
27
  work chooses it explicitly.
28
28
  - `Card surface="plain"` is the explicit structural/auth/website escape and
29
29
  carries no elevation.
30
+ - `Card surface="outlined"` is a flat panel with a single semantic border and
31
+ compact corners; it carries no elevation ring or shadow. Bounded fact cells
32
+ use it inside resting record sections. Portal's kit keeps the resting default.
30
33
  - Tooltip, menu, popover, select, combobox, navigation popup, hover card,
31
34
  chart tooltip, and toast roots own `nc-elevation-floating` internally.
32
35
  - Dialog, alert dialog, drawer, and sheet roots own `nc-elevation-modal`
@@ -64,11 +67,36 @@ transparent and unframed because the Card already owns containment.
64
67
 
65
68
  ### Tables
66
69
 
67
- `Table` owns the native table structure, solid semantic background, horizontal
68
- overflow, and one compact density: 36px header rows and 40px body rows. Consumers
70
+ `Table` owns the native table structure, solid semantic background, an overflow
71
+ safety boundary, and one compact density: 36px header rows and 40px body rows. Consumers
69
72
  compose `TableHeader`, `TableBody`, `TableRow`, `TableHead`, and `TableCell`; they
70
73
  do not replace table layout with grid or flex, hide the header to create another
71
- presentation, or reconstruct the native semantics with ARIA.
74
+ presentation, or reconstruct the native semantics with ARIA. Portal collections
75
+ must fit their available width without horizontal scrolling: preserve identity
76
+ and actions in columns and place supporting facts in native detail rows before
77
+ the full schema becomes cramped. Test the table container itself, not only the
78
+ document width; retaining the overflow safety boundary must not conceal a
79
+ layout defect.
80
+
81
+ `CollectionScope` chooses the parent of one record collection. It places the
82
+ scope selector and optional detail action above the collection toolbar. Empty
83
+ parents stay in the selector. Apps bind authorized options and URL state; clearing
84
+ record filters preserves the parent scope. Parent details belong on their detail
85
+ page, not in repeated panels surrounding identical child tables.
86
+
87
+ `BreakdownTable` owns its responsive comparison layout: identity and value stay
88
+ in the main row; share moves into a labeled detail row when space is tight.
89
+ Its caption names the table. Callers supply data and labels, not column-hiding CSS.
90
+
91
+ ### Record action menus
92
+
93
+ `ActionMenu` is the shared three-dot composition of `Button` and `DropdownMenu`:
94
+ a ghost icon trigger, horizontal ellipsis, and end-aligned popup. It inherits the
95
+ menu's glass, elevation, motion, keyboard behavior, and focus return. Supply
96
+ `label` as a localized, record-specific accessible name; `triggerId` supports
97
+ focus return from an app-owned confirmation, and `children` supplies menu items.
98
+ Apps own action selection, permission, placement, and confirmation. Reuse this
99
+ composition instead of drawing another trigger or popup.
72
100
 
73
101
  ## Status Vocabulary
74
102
 
@@ -121,12 +149,15 @@ fundamental `Card` with explicit `visual`, `name`, `job`, `status`, `meta`,
121
149
  and `action` slots and owns presentation only; when it is the page identity,
122
150
  the `name` slot carries the page's `h1`. The app owns Agent data, recipe
123
151
  assignment, status grammar, routing, and actions; it assigns a recipe
124
- explicitly and never infers or randomizes one from a free-form role, and an
125
- Agent that is a recognizable content object renders through `AgentCard`. There is no `AgentGlyph`,
152
+ explicitly and never infers or randomizes one from a free-form role.
153
+ When the product chooses a card presentation, use `AgentCard`; registry cells,
154
+ membership rows, and compact detail headers compose the shared `AgentVisual`
155
+ within the structure their reading task requires. An Agent's presence does not
156
+ by itself choose a card layout. There is no `AgentGlyph`,
126
157
  `AgentRow`, `KpiCard`, or product-domain `agent` object prop in the shared
127
158
  package.
128
159
 
129
- - **Standard components, not app-owned product features.** This package ships primitives, standard UI components, assistant-ui compositions, brand adapters, React adapters, reusable sections, and presentational product components. It must not ship app-specific feature/use-case screens or names such as operator-login, portal activation, customer credential control, or customer management. Generic data tables, command palettes, and chart cards are app-owned shadcn registry adaptations. The source-backed `chart-*` package surface is the plan-approved exception per issue #166; chart cards remain app-owned. Every data-shaped chart exposes an equivalent non-visual data alternative. A component whose complete value is already available in visible text or `aria-valuetext` does not add a redundant table.
160
+ - **Standard components, not app-owned product features.** This package ships primitives, standard UI components, assistant-ui compositions, brand adapters, React adapters, reusable sections, and presentational product components. It must not ship app-specific feature/use-case screens or names such as operator-login, portal activation, customer credential control, or customer management. Domain registries, command search providers, and chart dashboards bind app data and routes in the app; that binding is not a reason to copy reusable presentation. Portal's DataTable owns its Next.js query and registry protocol once, while consuming the shared UI foundation. Product-independent compositions belong here with a real consumer. The source-backed `chart-*` package surface is the plan-approved exception per issue #166; apps select charts and bind their titles, data, and navigation. Every data-shaped chart exposes an equivalent non-visual data alternative. A component whose complete value is already available in visible text or `aria-valuetext` does not add a redundant table.
130
161
  - **Auth routes are consumer-owned shadcn block work.** Login, signup, OTP, two-factor, magic-link, and provider-button flows start from the selected shadcn block/example in the consuming app, with app-owned Better Auth behavior. Do not ship generic auth, signup, OTP, federated-login, or provider abstractions from this package unless a later explicit boundary decision replaces the selected shadcn block.
131
162
  - **Reusable sections and compositions live here after proof.** Bento grids, media mounts, demo surfaces, page frames, section intros, CTA bands, proof slots, benefits grids, generic shells, and homepage/use-case sections belong in `@nadicodeai/ui` when a second concrete consumer, a plan-approved shared component, or a named validation slice proves the package boundary. Website, portal, and sibling Nadia routes pass data and media children into existing compositions instead of rebuilding layout locally.
132
163
  - **Brand renderers live here for React consumers.** Logo geometry, asset generation, and usage semantics are owned by [`packages/design-system/DESIGN.md`, "Brand media authority"](../../design-system/DESIGN.md#brand-media-authority). `BrandLockup` supplies the approved horizontal lockup. `BrandMark` serves tight spaces and `BrandWordmark` serves placements where the name must read by itself. All three render the package's public neutral geometry seam with React-scoped SVG ids, dynamic theme paint, and accessible names. Consuming routes use these adapters; they never import raw logo assets, draw brand SVG inline, mask or recolor assets, or create another lockup rule.
@@ -141,11 +172,51 @@ package.
141
172
 
142
173
  The source tree, barrels, and package exports own the current reusable section vocabulary. When a page needs a variation, extend the owning source component with a small typed prop or slot; do not fork the layout in a consumer or document a second component inventory here.
143
174
 
175
+ `KpiStatGroup` owns the metric strip, responsive tiles, and link treatment; `KpiStat` owns its value, note, delta, and decorative trend. Apps supply metrics and bind links through `render`. Time-series charts expose `size`; their lightweight layout module also sizes deferred placeholders, so consumers never select internal chart elements.
176
+
177
+ `CollectionToolbar` owns the collection-control layout: compact filters at the
178
+ leading edge, a flexible search field, and trailing actions. Its slot order is
179
+ also the reading and focus order, including when controls wrap. Consumers supply
180
+ existing controls and own their query state; they do not set independent search
181
+ widths or align filters to table columns. `CollectionFilters` owns the controlled
182
+ field/value picker and applied-filter chips; callers supply options, localized
183
+ copy, and callbacks. The component owns discovery, edit/remove, keyboard focus,
184
+ and empty search results. It does not own routing or fetch state.
185
+
186
+ `SettingsFactsSection` owns a named record or settings reading section: its
187
+ heading and optional action, header separator, and bounded responsive fact
188
+ cells in two equal columns, stacked on compact screens. Consumers supply labels,
189
+ values, an optional contextual footer, and a Card surface; paragraph facts take a full row. The stable heading remains a programmatic focus target
190
+ when an editor closes. Keep fact labels, order, and cells stable when data is
191
+ missing: null and undefined values use the required localized
192
+ `missingValueLabel`, while zero remains a value. Consumers do not filter out
193
+ unknown facts or replace them with indefinite loading skeletons. Editable
194
+ controls remain in `SettingsSection`.
195
+
196
+ `SettingsSection` composes Card's named header, divider, FieldGroup body, and
197
+ optional divided `footer` for local save actions and feedback. The caller owns
198
+ the form transaction and controls; the section owns their shared geometry.
199
+ Header `actions` are optional supporting commands, distinct from footer submission.
200
+ `SettingsField` uses one render callback in server and client components. Spread
201
+ its `controlProps` onto the control to retain the visible label, descriptions,
202
+ error association, and disabled/required state; do not wire another copy by hand.
203
+
204
+ `TableDetailRow` keeps supporting collection facts readable at compact widths
205
+ inside a native full-width table cell. It shares the owning column labels and
206
+ values rather than requiring another responsive cell implementation. Identity
207
+ and status retain their column headers. Missing values retain their place.
208
+ `Table compactDetails` and `TableDetailRow layout` share `tableCompactLayouts`:
209
+ `standard` retains full columns at 768px of container width, `wide` at 1024px,
210
+ and `spacious` at 1152px for schemas with additional operational columns.
211
+ The same owner switches column visibility, detail visibility, and row boundaries.
212
+ Two supporting facts use two columns; additional facts can use three when they fit.
213
+ The cell renderer supplies the value, leaving the label to the detail row.
214
+
144
215
  ## Consumer Boundary
145
216
 
146
217
  Consumers own routing, auth, app data, server actions, runtime construction and adapters, permission decisions, analytics, and app-specific copy. The package owns reusable React presentation and the public Agent UI dependency boundary. Agent state and behavior come through the Agents interface; non-React and TUI contracts belong to their consuming products.
147
218
 
148
- App source composes layout, data, behavior, and semantic content around package components. It does not restyle an imported component root: appearance changes enter through the component's typed variant or are added to the owning package component. App `className` values on package components are limited to structural layout and sizing. App source uses semantic roles such as `bg-background` and `text-foreground`; canonical `nc-*` color utilities stay behind package-owned primitives such as `BrandField`. Raw Tailwind palette colors are not available to anyone: the generated theme clears the `--color-*` namespace, so `bg-red-500` produces no CSS and `better-tailwindcss/no-unknown-classes` reports it. Visible controls come from `@nadicodeai/ui`, while hidden inputs remain valid form transport. The root ESLint rules `nadicodeai/no-shadcn-appearance-override` and `nadicodeai/no-native-visible-control` enforce the rest of this boundary; each takes a `packageOwnedPrimitives` option (`"exempt"` by default, `"lint"` to hold this package's own source to the same rule).
219
+ App source composes layout, data, behavior, and semantic content around package components. It does not restyle an imported component root: appearance changes enter through the component's typed variant or are added to the owning package component. App `className` values on package components arrange and size the component root. Descendant `data-slot` selectors are not a public styling API; internal spacing, type, and sizing belong to the shared component, enforced by `nadicodeai/no-shadcn-appearance-override`. App source uses semantic roles such as `bg-background` and `text-foreground`; canonical `nc-*` color utilities stay behind package-owned primitives such as `BrandField`. Raw Tailwind palette colors are not available to anyone: the generated theme clears the `--color-*` namespace, so `bg-red-500` produces no CSS and `better-tailwindcss/no-unknown-classes` reports it. Visible controls come from `@nadicodeai/ui`, while hidden inputs remain valid form transport. The root ESLint rules `nadicodeai/no-shadcn-appearance-override` and `nadicodeai/no-native-visible-control` enforce the rest of this boundary; each takes a `packageOwnedPrimitives` option (`"exempt"` by default, `"lint"` to hold this package's own source to the same rule).
149
220
 
150
221
  When this package is read from an installed npm package, follow the [consumer setup guide](consuming-cross-repo.md) for setup and public import paths. The styling invariant above remains the authority for component implementation.
151
222