@nadicodeai/ui 12.1.53 → 12.1.55

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 (38) hide show
  1. package/dist/eslint/index.js +27 -69
  2. package/dist/eslint/rules/jsx-class-utility.js +2 -56
  3. package/dist/eslint/rules/no-choice-control-wrapper.js +95 -0
  4. package/dist/eslint/rules/no-comment.js +44 -0
  5. package/dist/eslint/rules/no-subject-derived-expectation.js +55 -1
  6. package/dist/eslint/strict-design-system.js +224 -0
  7. package/dist/styles/components.css +1 -1
  8. package/package.json +3 -3
  9. package/dist/.build-kind +0 -1
  10. package/dist/eslint/rules/card-row-owner.js +0 -167
  11. package/dist/eslint/rules/confirm-destructive-action.js +0 -177
  12. package/dist/eslint/rules/drawer-owns-its-edges.js +0 -66
  13. package/dist/eslint/rules/expect-chain.js +0 -60
  14. package/dist/eslint/rules/icon-control-accessible-name.js +0 -156
  15. package/dist/eslint/rules/menu-command-owner.js +0 -45
  16. package/dist/eslint/rules/no-bare-page-navigation.js +0 -46
  17. package/dist/eslint/rules/no-elevated-edge.js +0 -141
  18. package/dist/eslint/rules/no-existence-only-assertion.js +0 -89
  19. package/dist/eslint/rules/no-invalid-token-utility.js +0 -46
  20. package/dist/eslint/rules/no-mono-title-register.js +0 -73
  21. package/dist/eslint/rules/no-native-visible-control.js +0 -92
  22. package/dist/eslint/rules/no-nested-overlay.js +0 -56
  23. package/dist/eslint/rules/no-off-grid-spacing.js +0 -115
  24. package/dist/eslint/rules/no-paired-size-utility.js +0 -66
  25. package/dist/eslint/rules/no-raw-shadow.js +0 -70
  26. package/dist/eslint/rules/no-repainted-focus.js +0 -75
  27. package/dist/eslint/rules/no-resting-settings-control.js +0 -89
  28. package/dist/eslint/rules/no-route-width-override.js +0 -135
  29. package/dist/eslint/rules/no-store-cast.js +0 -46
  30. package/dist/eslint/rules/no-token-category-bypass.js +0 -53
  31. package/dist/eslint/rules/no-unpinned-vercel-functions-import.js +0 -104
  32. package/dist/eslint/rules/one-control-height.js +0 -163
  33. package/dist/eslint/rules/overlay-body-owns-scroll.js +0 -62
  34. package/dist/eslint/rules/overlay-mounts-closed.js +0 -102
  35. package/dist/eslint/rules/prefer-radio-for-few-static-options.js +0 -77
  36. package/dist/eslint/rules/settings-section-description.js +0 -45
  37. package/dist/eslint/rules/shadcn-composition.js +0 -197
  38. package/dist/eslint/rules/table-column-wraps-by-meta.js +0 -55
@@ -1,197 +0,0 @@
1
- /*
2
- * The composition conventions the shared components are built to, checked
3
- * where they are composed:
4
- *
5
- * - an overlay carries its title, so a screen reader announces what opened
6
- * (`className="sr-only"` when the title is not drawn);
7
- * - a `SelectItem` sits inside a `SelectGroup`, which is what gives the listbox
8
- * its accessible grouping;
9
- * - an `AvatarImage` has an `AvatarFallback` beside it, or the avatar is blank
10
- * until the image loads;
11
- * - a lucide icon inside a `Button` carries `data-icon`, the hook the button's
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;
15
- * - the label and description beside a control in a `Field` sit in
16
- * `FieldContent`, the slot a horizontal field keys on to align its checkbox
17
- * with the first line of text rather than the middle of the block.
18
- *
19
- * Standard tool checked first: `jsx-a11y` covers roles and ARIA on
20
- * intrinsic elements and says nothing about these composed components;
21
- * `no-restricted-syntax` selects a node by its own shape, and every one of
22
- * these is a statement about an ancestor or a descendant. Replaces the JSX half
23
- * of the retired @nadicodeai/ui composition-conventions scanner (#822); stock
24
- * upstream shapes carry an inline disable with the reason at the site.
25
- */
26
- import {
27
- jsxComponentName,
28
- packageOwnedPrimitivesSchema,
29
- skipsPackageOwnedPrimitive,
30
- } from "./jsx-class-utility.js";
31
-
32
- const OVERLAY_TITLES = {
33
- AlertDialogContent: "AlertDialogTitle",
34
- DialogContent: "DialogTitle",
35
- DrawerContent: "DrawerTitle",
36
- SheetContent: "SheetTitle",
37
- };
38
-
39
- function elementName(node) {
40
- if (node?.type === "JSXElement") return elementName(node.openingElement);
41
- if (node?.type !== "JSXOpeningElement") return undefined;
42
- if (node.name.type === "JSXIdentifier") return node.name.name;
43
- if (node.name.type === "JSXMemberExpression") return node.name.property.name;
44
- return undefined;
45
- }
46
-
47
- function hasAttribute(node, attributeName) {
48
- return node.attributes.some(
49
- (attribute) =>
50
- attribute.type === "JSXAttribute" &&
51
- attribute.name.type === "JSXIdentifier" &&
52
- attribute.name.name === attributeName,
53
- );
54
- }
55
-
56
- /**
57
- * True when `tagName` is rendered anywhere below this element — through a
58
- * conditional or a map as much as directly, so the whole subtree is walked
59
- * rather than the JSX children alone.
60
- */
61
- function hasDescendant(node, tagName, root = node) {
62
- if (!node || typeof node.type !== "string") return false;
63
- if (node !== root && node.type === "JSXElement" && elementName(node) === tagName) return true;
64
- for (const [key, value] of Object.entries(node)) {
65
- if (key === "parent") continue;
66
- if (Array.isArray(value)) {
67
- if (value.some((child) => hasDescendant(child, tagName, root))) return true;
68
- } else if (value && typeof value === "object" && hasDescendant(value, tagName, root)) {
69
- return true;
70
- }
71
- }
72
- return false;
73
- }
74
-
75
- function ancestorNames(node, sourceCode) {
76
- return sourceCode
77
- .getAncestors(node)
78
- .filter((ancestor) => ancestor.type === "JSXElement")
79
- .map((ancestor) => elementName(ancestor))
80
- .filter(Boolean);
81
- }
82
-
83
- /** A FieldLabel or FieldDescription grouped in a plain element directly inside a Field. */
84
- function fieldTextOutsideContent(node) {
85
- const tag = elementName(node);
86
- if (tag !== "FieldLabel" && tag !== "FieldDescription") return false;
87
- const group = node.parent?.parent;
88
- if (group?.type !== "JSXElement") return false;
89
- const groupName = elementName(group);
90
- if (!groupName || groupName[0] !== groupName[0].toLowerCase()) return false;
91
- const field = group.parent;
92
- return field?.type === "JSXElement" && elementName(field) === "Field";
93
- }
94
-
95
- function isIconTag(tagName) {
96
- return tagName === "Icon" || tagName.endsWith("Icon");
97
- }
98
-
99
- function misplacedSettingsSubmit(node, context) {
100
- if (jsxComponentName(node, context) !== "Button") return false;
101
- const attribute = node.attributes.find(
102
- (entry) => entry.type === "JSXAttribute" && entry.name.name === "type",
103
- );
104
- const value =
105
- attribute?.value?.type === "JSXExpressionContainer"
106
- ? attribute.value.expression
107
- : attribute?.value;
108
- const type =
109
- value?.type === "TemplateLiteral" && value.expressions.length === 0
110
- ? value.quasis[0]?.value.cooked
111
- : value?.value;
112
- if (type !== "submit") return false;
113
- const ancestors = context.sourceCode.getAncestors(node);
114
- const section = ancestors.findLast(
115
- (ancestor) =>
116
- ancestor.type === "JSXElement" &&
117
- jsxComponentName(ancestor.openingElement, context) === "SettingsSection",
118
- );
119
- if (!section) return false;
120
- return !ancestors.slice(ancestors.indexOf(section) + 1).some((ancestor) => {
121
- if (ancestor.type === "JSXAttribute")
122
- return ancestor.name.name === "footer" && ancestor.parent === section.openingElement;
123
- return (
124
- ancestor.type === "JSXElement" &&
125
- ["DialogContent", "DrawerContent", "AlertDialogContent"].includes(
126
- jsxComponentName(ancestor.openingElement, context),
127
- )
128
- );
129
- });
130
- }
131
-
132
- export const shadcnComposition = {
133
- meta: {
134
- type: "problem",
135
- docs: {
136
- description: "Require the shadcn primitives' composition conventions where they are composed",
137
- },
138
- schema: [packageOwnedPrimitivesSchema],
139
- messages: {
140
- overlayTitle:
141
- '{{tag}} must include {{title}}; use className="sr-only" when the title is visually hidden.',
142
- selectGroup: "SelectItem must be rendered inside SelectGroup.",
143
- avatarFallback: "AvatarImage requires an AvatarFallback in the same Avatar.",
144
- buttonIcon: "An icon inside Button must carry data-icon.",
145
- settingsFooter:
146
- "Put a SettingsSection submit action in its footer prop; the shared component owns the divider, spacing, and action position.",
147
- fieldContent:
148
- "Group a Field's label and description in FieldContent, not a plain element; a horizontal Field aligns its control to the text through that slot.",
149
- },
150
- },
151
- create(context) {
152
- if (skipsPackageOwnedPrimitive(context)) {
153
- return {};
154
- }
155
-
156
- return {
157
- JSXOpeningElement(node) {
158
- const tag = elementName(node);
159
- if (!tag) return;
160
- const element = node.parent;
161
-
162
- if (misplacedSettingsSubmit(node, context)) {
163
- context.report({ node, messageId: "settingsFooter" });
164
- }
165
-
166
- if (fieldTextOutsideContent(node)) {
167
- context.report({ node, messageId: "fieldContent" });
168
- }
169
-
170
- const title = OVERLAY_TITLES[tag];
171
- if (title && !hasDescendant(element, title)) {
172
- context.report({ node, messageId: "overlayTitle", data: { tag, title } });
173
- }
174
-
175
- if (tag === "Avatar" && hasDescendant(element, "AvatarImage")) {
176
- if (!hasDescendant(element, "AvatarFallback")) {
177
- context.report({ node, messageId: "avatarFallback" });
178
- }
179
- return;
180
- }
181
-
182
- if (tag !== "SelectItem" && tag !== "AvatarImage" && !isIconTag(tag)) return;
183
-
184
- const ancestors = ancestorNames(node, context.sourceCode);
185
- if (tag === "SelectItem" && !ancestors.includes("SelectGroup")) {
186
- context.report({ node, messageId: "selectGroup" });
187
- }
188
- if (tag === "AvatarImage" && !ancestors.includes("Avatar")) {
189
- context.report({ node, messageId: "avatarFallback" });
190
- }
191
- if (isIconTag(tag) && ancestors.includes("Button") && !hasAttribute(node, "data-icon")) {
192
- context.report({ node, messageId: "buttonIcon" });
193
- }
194
- },
195
- };
196
- },
197
- };
@@ -1,55 +0,0 @@
1
- /*
2
- * A table column wraps by its definition, never by its cells. DataTable holds a
3
- * fact on one line and gives a column marked `meta: { prose: true }` a floor
4
- * and wrapping between words (DESIGN.md, "Tables and missing values"). A cell
5
- * that sets its own wrapping gets neither the floor nor the identity's share
6
- * of the width, and the table squeezes it until each word takes a line.
7
- *
8
- * Standard tool checked first: `better-tailwindcss`'s `no-restricted-classes`
9
- * judges one class at a time, and `whitespace-normal` is right in a button or a
10
- * card. What is wrong is where it is written: inside a column's `cell`, which
11
- * this rule reads on the shared className reader.
12
- */
13
- import { openingElementClassUtilities } from "./jsx-class-utility.js";
14
-
15
- const WRAPPING = /^(?:.*:)?whitespace-(?:normal|pre-line|pre-wrap)$/;
16
-
17
- /** Inside the `cell` renderer of a column definition object. */
18
- function insideColumnCell(node) {
19
- for (let current = node.parent; current; current = current.parent) {
20
- if (
21
- current.type === "Property" &&
22
- !current.computed &&
23
- current.key.type === "Identifier" &&
24
- current.key.name === "cell"
25
- ) {
26
- return true;
27
- }
28
- }
29
- return false;
30
- }
31
-
32
- export const tableColumnWrapsByMeta = {
33
- meta: {
34
- type: "problem",
35
- docs: {
36
- description: "Disallow a table cell setting its own wrapping; mark the column prose",
37
- },
38
- schema: [],
39
- messages: {
40
- cellWraps:
41
- '"{{token}}" wraps one cell of a table column. Mark the column `meta: { prose: true }` so DataTable gives it a floor and wraps it between words.',
42
- },
43
- },
44
- create(context) {
45
- return {
46
- JSXOpeningElement(node) {
47
- if (!insideColumnCell(node)) return;
48
- for (const token of openingElementClassUtilities(node, context)) {
49
- if (WRAPPING.test(token))
50
- context.report({ node, messageId: "cellWraps", data: { token } });
51
- }
52
- },
53
- };
54
- },
55
- };