@nadicodeai/ui 6.0.1 → 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.
- package/dist/components/action-menu.d.ts +8 -0
- package/dist/components/action-menu.d.ts.map +1 -0
- package/dist/components/action-menu.js +8 -0
- package/dist/components/card.d.ts +1 -1
- package/dist/components/card.d.ts.map +1 -1
- package/dist/components/card.js +5 -3
- package/dist/components/chart-breakdown-table.d.ts +3 -2
- package/dist/components/chart-breakdown-table.d.ts.map +1 -1
- package/dist/components/chart-breakdown-table.js +13 -9
- package/dist/components/chart-bullet-bar.d.ts +13 -12
- package/dist/components/chart-bullet-bar.d.ts.map +1 -1
- package/dist/components/chart-bullet-bar.js +16 -36
- package/dist/components/chart-kpi-stat.d.ts +26 -15
- package/dist/components/chart-kpi-stat.d.ts.map +1 -1
- package/dist/components/chart-kpi-stat.js +28 -4
- package/dist/components/chart-time-series-layout.d.ts +6 -0
- package/dist/components/chart-time-series-layout.d.ts.map +1 -0
- package/dist/components/chart-time-series-layout.js +6 -0
- package/dist/components/chart-time-series.d.ts +6 -2
- package/dist/components/chart-time-series.d.ts.map +1 -1
- package/dist/components/chart-time-series.js +27 -9
- package/dist/components/chart.js +1 -1
- package/dist/components/collection-filters.d.ts +28 -0
- package/dist/components/collection-filters.d.ts.map +1 -0
- package/dist/components/collection-filters.js +54 -0
- package/dist/components/collection-scope.d.ts +15 -0
- package/dist/components/collection-scope.d.ts.map +1 -0
- package/dist/components/collection-scope.js +7 -0
- package/dist/components/collection-toolbar.d.ts +10 -0
- package/dist/components/collection-toolbar.d.ts.map +1 -0
- package/dist/components/collection-toolbar.js +4 -0
- package/dist/components/settings-facts.d.ts +28 -0
- package/dist/components/settings-facts.d.ts.map +1 -0
- package/dist/components/settings-facts.js +7 -0
- package/dist/components/settings-section.d.ts +5 -5
- package/dist/components/settings-section.d.ts.map +1 -1
- package/dist/components/settings-section.js +6 -11
- package/dist/components/sidebar.d.ts +4 -1
- package/dist/components/sidebar.d.ts.map +1 -1
- package/dist/components/sidebar.js +3 -3
- package/dist/components/table-layouts.d.ts +22 -0
- package/dist/components/table-layouts.d.ts.map +1 -0
- package/dist/components/table-layouts.js +20 -0
- package/dist/components/table.d.ts +15 -2
- package/dist/components/table.d.ts.map +1 -1
- package/dist/components/table.js +8 -4
- package/dist/eslint/rules/jsx-class-utility.js +44 -12
- package/dist/eslint/rules/no-resting-settings-control.js +15 -13
- package/dist/eslint/rules/no-shadcn-appearance-override.js +189 -36
- package/dist/eslint/rules/shadcn-composition.js +48 -3
- package/dist/hooks/use-confirm-action.d.ts +0 -6
- package/dist/hooks/use-confirm-action.d.ts.map +1 -1
- package/dist/hooks/use-confirm-action.js +5 -12
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/lib/use-copy-to-clipboard.d.ts +0 -41
- package/dist/lib/use-copy-to-clipboard.d.ts.map +1 -1
- package/dist/lib/use-copy-to-clipboard.js +6 -15
- package/docs/component-classification.md +3 -2
- package/docs/contract.md +79 -8
- package/docs/product-compositions.md +14 -11
- 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
|
|
10
|
-
*
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 =
|
|
48
|
-
return
|
|
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
|
|
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 =
|
|
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))
|
|
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
|
|
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
|
|
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
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 (
|
|
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
|
|
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 {
|
|
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 } });
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
export interface ConfirmActionGate {
|
|
2
|
-
busy: boolean;
|
|
3
|
-
}
|
|
4
|
-
export declare function requestConfirmAction<T>(gate: ConfirmActionGate, value: T, setPending: (value: T | null) => void): void;
|
|
5
|
-
export declare function cancelConfirmAction<T>(gate: ConfirmActionGate, setPending: (value: T | null) => void): void;
|
|
6
|
-
export declare function runConfirmAction<T>(gate: ConfirmActionGate, pending: T | null, onConfirm: (value: T) => void | Promise<void>, setPending: (value: T | null) => void, setBusy: (busy: boolean) => void): Promise<void>;
|
|
7
1
|
export declare function useConfirmAction<T>({ onConfirm, }: {
|
|
8
2
|
onConfirm: (value: T) => void | Promise<void>;
|
|
9
3
|
}): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-confirm-action.d.ts","sourceRoot":"","sources":["../../src/hooks/use-confirm-action.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-confirm-action.d.ts","sourceRoot":"","sources":["../../src/hooks/use-confirm-action.ts"],"names":[],"mappings":"AAwDA,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,EAClC,SAAS,GACV,EAAE;IACD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;8BAK2C,CAAC;;;;;;EAqB5C"}
|
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
// flight so the pending value cannot change under a running action.
|
|
5
|
-
export function requestConfirmAction(gate, value, setPending) {
|
|
3
|
+
function requestConfirmAction(gate, value, setPending) {
|
|
6
4
|
if (gate.busy) {
|
|
7
5
|
return;
|
|
8
6
|
}
|
|
9
7
|
setPending(value);
|
|
10
8
|
}
|
|
11
|
-
|
|
12
|
-
// so the dialog cannot be dismissed mid-action.
|
|
13
|
-
export function cancelConfirmAction(gate, setPending) {
|
|
9
|
+
function cancelConfirmAction(gate, setPending) {
|
|
14
10
|
if (gate.busy) {
|
|
15
11
|
return;
|
|
16
12
|
}
|
|
17
13
|
setPending(null);
|
|
18
14
|
}
|
|
19
|
-
|
|
20
|
-
// success. A thrown error is swallowed so the dialog stays open (the caller
|
|
21
|
-
// surfaces the error inside `onConfirm`); busy always resets in `finally`.
|
|
22
|
-
export async function runConfirmAction(gate, pending, onConfirm, setPending, setBusy) {
|
|
15
|
+
async function confirmOrKeepOpen(gate, pending, onConfirm, setPending, setBusy) {
|
|
23
16
|
if (gate.busy || pending === null) {
|
|
24
17
|
return;
|
|
25
18
|
}
|
|
@@ -30,7 +23,7 @@ export async function runConfirmAction(gate, pending, onConfirm, setPending, set
|
|
|
30
23
|
setPending(null);
|
|
31
24
|
}
|
|
32
25
|
catch {
|
|
33
|
-
|
|
26
|
+
return;
|
|
34
27
|
}
|
|
35
28
|
finally {
|
|
36
29
|
gate.busy = false;
|
|
@@ -47,7 +40,7 @@ export function useConfirmAction({ onConfirm, }) {
|
|
|
47
40
|
const cancel = React.useCallback(() => {
|
|
48
41
|
cancelConfirmAction(gateRef.current, setPending);
|
|
49
42
|
}, []);
|
|
50
|
-
const confirm = React.useCallback(() =>
|
|
43
|
+
const confirm = React.useCallback(() => confirmOrKeepOpen(gateRef.current, pending, onConfirm, setPending, setIsBusy), [pending, onConfirm]);
|
|
51
44
|
return {
|
|
52
45
|
request,
|
|
53
46
|
cancel,
|
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";
|
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
|
@@ -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,44 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The copy affordance behind every "copy" button in the package.
|
|
3
|
-
*
|
|
4
|
-
* The decision half and the timer half are exported as plain functions, with the
|
|
5
|
-
* hook a thin binding over them (the shape `use-confirm-action` established), so
|
|
6
|
-
* both are testable in the package's node-only default suite instead of needing a
|
|
7
|
-
* DOM render harness.
|
|
8
|
-
*/
|
|
9
|
-
export type CopyAttempt =
|
|
10
|
-
/** No clipboard to write to — an insecure origin, typically. */
|
|
11
|
-
{
|
|
12
|
-
kind: "unavailable";
|
|
13
|
-
error: Error;
|
|
14
|
-
}
|
|
15
|
-
/** Already showing "copied" and the caller asked not to repeat. */
|
|
16
|
-
| {
|
|
17
|
-
kind: "skipped";
|
|
18
|
-
} | {
|
|
19
|
-
kind: "copied";
|
|
20
|
-
} | {
|
|
21
|
-
kind: "failed";
|
|
22
|
-
error: Error;
|
|
23
|
-
};
|
|
24
|
-
/** Attempts the write and classifies the result; performs no state or timer work. */
|
|
25
|
-
export declare function attemptCopy(input: {
|
|
26
|
-
text: string;
|
|
27
|
-
isCopied: boolean;
|
|
28
|
-
skipWhileCopied: boolean;
|
|
29
|
-
writeText: ((text: string) => Promise<void>) | undefined;
|
|
30
|
-
}): Promise<CopyAttempt>;
|
|
31
|
-
export type ResetTimers = {
|
|
32
|
-
clear: (handle: number) => void;
|
|
33
|
-
set: (onReset: () => void, timeout: number) => number;
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Starts the "copied" reset window, dropping whichever reset was already
|
|
37
|
-
* pending. Without the clear, an earlier press's timer keeps its own schedule and
|
|
38
|
-
* fires part-way through this press's window, so the feedback disappears early.
|
|
39
|
-
* Returns the new handle for the caller to hold.
|
|
40
|
-
*/
|
|
41
|
-
export declare function restartResetTimer(timers: ResetTimers, pendingHandle: number, onReset: () => void, timeout: number): number;
|
|
42
1
|
export declare function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCopied, }: {
|
|
43
2
|
text: string;
|
|
44
3
|
onCopy?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/lib/use-copy-to-clipboard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/lib/use-copy-to-clipboard.ts"],"names":[],"mappings":"AA4BA,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAO,EACP,eAAuB,GACxB,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;EAsCA"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export async function attemptCopy(input) {
|
|
2
|
+
async function attemptCopy(input) {
|
|
4
3
|
if (!input.writeText) {
|
|
5
4
|
return { kind: "unavailable", error: new Error("Clipboard API not available") };
|
|
6
5
|
}
|
|
@@ -15,18 +14,9 @@ export async function attemptCopy(input) {
|
|
|
15
14
|
return { kind: "failed", error: error };
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Starts the "copied" reset window, dropping whichever reset was already
|
|
20
|
-
* pending. Without the clear, an earlier press's timer keeps its own schedule and
|
|
21
|
-
* fires part-way through this press's window, so the feedback disappears early.
|
|
22
|
-
* Returns the new handle for the caller to hold.
|
|
23
|
-
*/
|
|
24
|
-
export function restartResetTimer(timers, pendingHandle, onReset, timeout) {
|
|
25
|
-
timers.clear(pendingHandle);
|
|
26
|
-
return timers.set(onReset, timeout);
|
|
27
|
-
}
|
|
28
17
|
export function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCopied = false, }) {
|
|
29
|
-
const [
|
|
18
|
+
const [copiedText, setCopiedText] = useState(null);
|
|
19
|
+
const isCopied = copiedText === text;
|
|
30
20
|
const timeoutRef = useRef(0);
|
|
31
21
|
const copyToClipboard = useCallback(async () => {
|
|
32
22
|
const attempt = await attemptCopy({
|
|
@@ -44,9 +34,10 @@ export function useCopyToClipboard({ text, onCopy, onError, timeout, skipWhileCo
|
|
|
44
34
|
if (attempt.kind === "skipped") {
|
|
45
35
|
return;
|
|
46
36
|
}
|
|
47
|
-
|
|
37
|
+
setCopiedText(text);
|
|
48
38
|
onCopy?.();
|
|
49
|
-
|
|
39
|
+
window.clearTimeout(timeoutRef.current);
|
|
40
|
+
timeoutRef.current = window.setTimeout(() => setCopiedText(null), timeout);
|
|
50
41
|
}, [isCopied, onCopy, onError, skipWhileCopied, text, timeout]);
|
|
51
42
|
useEffect(() => () => {
|
|
52
43
|
window.clearTimeout(timeoutRef.current);
|