dslinter 0.1.13 → 0.2.2
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/CHANGELOG.md +72 -0
- package/README.md +50 -29
- package/bin/dslinter.mjs +26 -5
- package/bin/lib/config-hide-component.mjs +44 -0
- package/bin/lib/config-hide-component.test.mjs +33 -0
- package/bin/lib/constants.mjs +20 -0
- package/bin/lib/dev-banner.mjs +16 -51
- package/bin/lib/dev-banner.test.mjs +20 -18
- package/bin/lib/enrich-playgrounds-from-ts.mjs +201 -0
- package/bin/lib/enrich-playgrounds-from-ts.test.mjs +74 -0
- package/bin/lib/enrich-report-cli.mjs +14 -0
- package/bin/lib/env.mjs +20 -0
- package/bin/lib/infer-prop-types-from-ts.mjs +381 -0
- package/bin/lib/infer-prop-types-from-ts.test.mjs +174 -0
- package/bin/lib/parse-args.mjs +13 -1
- package/bin/lib/parse-args.test.mjs +7 -1
- package/bin/lib/paths.mjs +8 -0
- package/bin/lib/project-root.mjs +72 -10
- package/bin/lib/project-root.test.mjs +32 -1
- package/bin/lib/prompt.mjs +31 -0
- package/bin/lib/resolve-project.mjs +78 -0
- package/bin/lib/resolve-project.test.mjs +74 -0
- package/bin/lib/run-scanner.mjs +40 -6
- package/bin/lib/scaffold-config.mjs +128 -9
- package/bin/lib/scaffold-config.test.mjs +24 -2
- package/bin/lib/scan-host.mjs +44 -0
- package/bin/lib/scan-host.test.mjs +41 -0
- package/bin/lib/setup-readiness.mjs +153 -0
- package/bin/lib/setup-readiness.test.mjs +32 -0
- package/bin/modes/build.mjs +31 -6
- package/bin/modes/dev.mjs +55 -21
- package/bin/modes/init.mjs +3 -22
- package/bin/modes/init.test.mjs +1 -1
- package/bin/modes/mcp.mjs +49 -0
- package/bin/modes/report.mjs +29 -4
- package/bin/modes/watch.mjs +85 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-Bja3BuZZ.css +1 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-h0gP_iKd.js +1 -0
- package/dashboard-dist/assets/axe-DDaE9JTN.js +20 -0
- package/dashboard-dist/assets/index-B9sZ6wHm.css +1 -0
- package/dashboard-dist/assets/index-DIDBt5ed.js +218 -0
- package/dashboard-dist/index.html +2 -2
- package/index.cjs +53 -52
- package/index.d.ts +3 -0
- package/package.json +18 -12
- package/shared/env.ts +15 -0
- package/shared/paths.ts +8 -0
- package/shared/reportPath.test.ts +19 -0
- package/shared/reportPath.ts +12 -0
- package/shared/servePort.ts +16 -0
- package/src/components/ComponentInspectPane.tsx +67 -19
- package/src/components/ComponentPlaygroundPane.tsx +262 -113
- package/src/components/DashboardCommandPalette.tsx +6 -11
- package/src/components/GovernancePane.tsx +2 -2
- package/src/components/HideFromCatalogButton.tsx +44 -0
- package/src/components/OpenInEditorButton.tsx +36 -0
- package/src/components/PlaygroundA11yAndCode.tsx +53 -53
- package/src/components/PlaygroundAppThemeWrapper.tsx +82 -0
- package/src/components/PlaygroundControls.tsx +5 -11
- package/src/components/PlaygroundPreviewErrorBoundary.tsx +54 -0
- package/src/components/PlaygroundUsageCode.tsx +6 -4
- package/src/components/PlaygroundVariantMatrix.tsx +101 -34
- package/src/components/Section.tsx +5 -2
- package/src/components/Sidebar.tsx +131 -46
- package/src/components/TruncatedPath.tsx +44 -0
- package/src/components/controlApiTable.test.ts +29 -0
- package/src/components/controlApiTable.ts +3 -0
- package/src/components/playgroundUsageHighlight.ts +14 -3
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/table.tsx +2 -2
- package/src/dashboard/ComponentCatalog.tsx +16 -23
- package/src/dashboard/ComponentUsageDetails.tsx +6 -15
- package/src/dashboard/DashboardBody.tsx +0 -35
- package/src/dashboard/FindingsList.tsx +65 -55
- package/src/dashboard/ScannedTokenWall.tsx +3 -3
- package/src/dashboard/aggregate.test.ts +74 -0
- package/src/dashboard/aggregate.ts +145 -21
- package/src/dashboard/catalogVisibility.test.ts +93 -0
- package/src/dashboard/catalogVisibility.ts +108 -0
- package/src/dashboard/editorLink.test.ts +57 -0
- package/src/dashboard/editorLink.ts +71 -0
- package/src/dashboard/paths.test.ts +49 -0
- package/src/dashboard/paths.ts +51 -3
- package/src/dashboard/updateDslintConfig.ts +22 -0
- package/src/dashboard/useWorkspaceReport.ts +21 -17
- package/src/index.ts +26 -0
- package/src/mcp/agent-context.ts +148 -0
- package/src/mcp/agent-query.test.ts +89 -0
- package/src/mcp/agent-query.ts +373 -0
- package/src/mcp/config.ts +53 -0
- package/src/mcp/index.ts +18 -0
- package/src/mcp/normalize-paths.ts +65 -0
- package/src/mcp/report-cache.ts +212 -0
- package/src/mcp/rule-catalog.json +156 -0
- package/src/mcp/rule-catalog.ts +33 -0
- package/src/mcp/schemas.ts +54 -0
- package/src/mcp/server.test.ts +44 -0
- package/src/mcp/server.ts +343 -0
- package/src/mcp/start.ts +29 -0
- package/src/mcp/verify-loop.test.ts +49 -0
- package/src/mcp/verify-loop.ts +149 -0
- package/src/playground/appPreviewTheme.test.ts +148 -0
- package/src/playground/appPreviewTheme.ts +137 -0
- package/src/playground/buildCompoundPlaygroundEntries.test.ts +348 -0
- package/src/playground/buildCompoundPlaygroundEntries.ts +625 -0
- package/src/playground/buildPlaygroundEntriesFromReport.test.ts +420 -6
- package/src/playground/buildPlaygroundEntriesFromReport.ts +206 -285
- package/src/playground/catalogIdFromPlaygroundExport.test.ts +15 -0
- package/src/playground/catalogIdFromPlaygroundExport.ts +8 -0
- package/src/playground/collectDefinedPlaygrounds.test.ts +59 -0
- package/src/playground/collectDefinedPlaygrounds.ts +68 -0
- package/src/playground/controls.ts +177 -0
- package/src/playground/createPlaygroundRegistry.ts +1 -1
- package/src/playground/definePlayground.tsx +88 -16
- package/src/playground/definePlaygroundFromKit.ts +17 -0
- package/src/playground/embedGlobKey.ts +8 -0
- package/src/playground/enrichKitControls.test.ts +25 -0
- package/src/playground/enrichKitControls.ts +197 -0
- package/src/playground/expandPlaygroundControls.test.ts +50 -0
- package/src/playground/expandPlaygroundControls.ts +97 -0
- package/src/playground/inferKitJsx.test.ts +77 -0
- package/src/playground/inferKitJsx.ts +165 -0
- package/src/playground/inferKitParams.test.ts +41 -0
- package/src/playground/inferKitParams.ts +113 -0
- package/src/playground/inferPropTypesFromTs.d.mts +47 -0
- package/src/playground/inferPropTypesFromTs.mjs +343 -0
- package/src/playground/inferPropTypesFromTs.test.ts +227 -0
- package/src/playground/inferPropTypesFromTs.ts +17 -0
- package/src/playground/mergePlaygroundEntries.test.ts +32 -0
- package/src/playground/mergePlaygroundEntries.ts +28 -0
- package/src/playground/playgroundJoin.test.ts +79 -19
- package/src/playground/playgroundJoin.ts +47 -22
- package/src/playground/playgroundModuleExport.test.ts +42 -0
- package/src/playground/playgroundModuleExport.ts +22 -0
- package/src/playground/playgroundSpecsKey.ts +8 -0
- package/src/playground/propCoerce.ts +91 -0
- package/src/playground/scanVariantA11y.test.ts +46 -0
- package/src/playground/scanVariantA11y.ts +107 -0
- package/src/playground/snippet.ts +83 -0
- package/src/playground/usePlaygroundFromReport.test.ts +18 -8
- package/src/playground/usePlaygroundFromReport.ts +3 -1
- package/src/report/a11yForModule.ts +2 -7
- package/src/report/a11yScoring.test.ts +24 -0
- package/src/report/a11yScoring.ts +17 -0
- package/src/report/index.ts +6 -0
- package/src/shell/DashboardLayout.tsx +71 -45
- package/src/shell/DashboardLayoutAuto.tsx +0 -4
- package/src/shell/hashRoute.test.ts +7 -15
- package/src/shell/hashRoute.ts +31 -31
- package/src/shell/useHashRoute.ts +38 -13
- package/src/styles/dashboard-theme.css +18 -7
- package/src/types/controls.ts +11 -0
- package/src/types/playground.ts +4 -0
- package/src/types/report.ts +32 -9
- package/templates/playground/buildRegistry.ts +1 -1
- package/templates/vite.dslinter.snippet.ts +15 -4
- package/vite/collectScanModules.test.ts +91 -3
- package/vite/collectScanModules.ts +94 -29
- package/vite/consumer.config.mjs +6 -3
- package/vite/consumerAlias.test.ts +47 -0
- package/vite/consumerAlias.ts +114 -0
- package/vite/embedTailwindSources.test.ts +74 -0
- package/vite/embedTailwindSources.ts +97 -0
- package/vite/loadConsumerAliases.test.ts +131 -0
- package/vite/loadConsumerAliases.ts +155 -0
- package/vite/openFileInEditor.mjs +196 -0
- package/vite/openFileInEditor.test.mjs +87 -0
- package/vite/plugin.resolve.test.ts +72 -0
- package/vite/plugin.ts +216 -19
- package/vite/reportPath.test.ts +19 -0
- package/vite/resolveWayfinderImport.ts +56 -0
- package/vite/shims/inertia-react.tsx +85 -0
- package/vite/shims/wayfinder-actions.ts +33 -0
- package/vite/shims/wayfinder-routes.ts +30 -0
- package/vite/shims/ziggy-js.ts +12 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-Bm7yfyC-.css +0 -1
- package/dashboard-dist/assets/DashboardLayoutAuto-DgwO_itB.js +0 -1
- package/dashboard-dist/assets/index-Cbv7vXvH.css +0 -1
- package/dashboard-dist/assets/index-e20cwqnb.js +0 -206
- package/src/components/playgroundUsageTwoslash.ts +0 -69
- package/templates/vite.dslint-scan-alias.snippet.ts +0 -4
|
@@ -14,7 +14,11 @@ import { a11ySummaryForModule } from "../report/a11yForModule";
|
|
|
14
14
|
import { codeScoreSummaryForModule } from "../report/codeScoreForModule";
|
|
15
15
|
import { tokenStyleFindingsForModule } from "../report/tokenStyleFindingsForModule";
|
|
16
16
|
import type { WorkspaceReport } from "../types/report";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
aggregateDeclaredProps,
|
|
19
|
+
componentCatalogFamilyForName,
|
|
20
|
+
usageMap,
|
|
21
|
+
} from "../dashboard/aggregate";
|
|
18
22
|
import { defaultArgsFromControls } from "../types/controls";
|
|
19
23
|
import type { PlaygroundArgs } from "../types/controls";
|
|
20
24
|
import type { PlaygroundEntry } from "../types/playground";
|
|
@@ -26,15 +30,28 @@ import {
|
|
|
26
30
|
PlaygroundTokenStyleSection,
|
|
27
31
|
PlaygroundUsageSection,
|
|
28
32
|
} from "./PlaygroundA11yAndCode";
|
|
33
|
+
import { PlaygroundAppThemeWrapper } from "./PlaygroundAppThemeWrapper";
|
|
34
|
+
import { PlaygroundPreviewErrorBoundary } from "./PlaygroundPreviewErrorBoundary";
|
|
29
35
|
import { PlaygroundVariantMatrix } from "./PlaygroundVariantMatrix";
|
|
30
36
|
import { enumerateControlCombinations } from "../playground/enumerateControlCombinations";
|
|
37
|
+
import {
|
|
38
|
+
mergePlaygroundA11yFindings,
|
|
39
|
+
playgroundA11yScore,
|
|
40
|
+
type PlaygroundA11yFinding,
|
|
41
|
+
} from "../playground/scanVariantA11y";
|
|
42
|
+
import { HideFromCatalogButton } from "./HideFromCatalogButton";
|
|
43
|
+
import { OpenInEditorButton } from "./OpenInEditorButton";
|
|
31
44
|
import { Section } from "./Section";
|
|
45
|
+
import {
|
|
46
|
+
resolveModuleAbsolutePath,
|
|
47
|
+
} from "../dashboard/editorLink";
|
|
32
48
|
|
|
33
49
|
type Props = {
|
|
34
50
|
entry: PlaygroundEntry;
|
|
35
|
-
formatModulePath?: (modulePath: string) => string;
|
|
36
51
|
workspaceReport: WorkspaceReport | null;
|
|
37
52
|
reportReady: boolean;
|
|
53
|
+
onOpenComponent: (componentId: string) => void;
|
|
54
|
+
onHideFromCatalog?: (componentId: string) => void;
|
|
38
55
|
};
|
|
39
56
|
|
|
40
57
|
const MIN_PREVIEW_PX = 280;
|
|
@@ -59,6 +76,31 @@ function nextPreviewWidthForResize(
|
|
|
59
76
|
return clampPreviewWidth(prevPreview, nextOuter);
|
|
60
77
|
}
|
|
61
78
|
|
|
79
|
+
function PreviewResizeHandle({
|
|
80
|
+
side,
|
|
81
|
+
onPointerDown,
|
|
82
|
+
}: {
|
|
83
|
+
side: "left" | "right";
|
|
84
|
+
onPointerDown: (e: PointerEvent<HTMLButtonElement>) => void;
|
|
85
|
+
}) {
|
|
86
|
+
const positionClass =
|
|
87
|
+
side === "left" ? "left-0 -translate-x-1/2" : "right-0 translate-x-1/2";
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
className={`absolute top-0 bottom-0 z-10 flex w-4 ${positionClass} cursor-ew-resize touch-none items-center justify-center rounded border-0 bg-muted p-0 shadow-xs ring-1 ring-border hover:bg-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring`}
|
|
93
|
+
aria-label="Resize preview from center (drag left or right)"
|
|
94
|
+
onPointerDown={onPointerDown}
|
|
95
|
+
>
|
|
96
|
+
<span
|
|
97
|
+
className="h-10 w-px rounded-full bg-muted-foreground/40"
|
|
98
|
+
aria-hidden
|
|
99
|
+
/>
|
|
100
|
+
</button>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
62
104
|
function TocLink({ href, children }: { href: string; children: ReactNode }) {
|
|
63
105
|
const handleClick = (e: MouseEvent<HTMLAnchorElement>) => {
|
|
64
106
|
// Modifier keys / non-primary clicks → fall back to default browser behaviour.
|
|
@@ -95,14 +137,15 @@ function TocLink({ href, children }: { href: string; children: ReactNode }) {
|
|
|
95
137
|
|
|
96
138
|
export function ComponentPlaygroundPane({
|
|
97
139
|
entry,
|
|
98
|
-
formatModulePath,
|
|
99
140
|
workspaceReport,
|
|
100
141
|
reportReady,
|
|
142
|
+
onOpenComponent,
|
|
143
|
+
onHideFromCatalog,
|
|
101
144
|
}: Props) {
|
|
102
|
-
const {
|
|
103
|
-
const
|
|
104
|
-
?
|
|
105
|
-
:
|
|
145
|
+
const { renderPreview } = entry;
|
|
146
|
+
const sourceAbsolutePath = workspaceReport?.root
|
|
147
|
+
? resolveModuleAbsolutePath(workspaceReport.root, entry.modulePath)
|
|
148
|
+
: undefined;
|
|
106
149
|
|
|
107
150
|
const [values, setValues] = useState<PlaygroundArgs>(() =>
|
|
108
151
|
defaultArgsFromControls(entry.controls),
|
|
@@ -151,6 +194,8 @@ export function ComponentPlaygroundPane({
|
|
|
151
194
|
|
|
152
195
|
const previewMeasureRef = useRef<HTMLDivElement>(null);
|
|
153
196
|
const previewFrameRef = useRef<HTMLDivElement>(null);
|
|
197
|
+
const previewWidthLabelRef = useRef<HTMLSpanElement>(null);
|
|
198
|
+
const livePreviewWidthRef = useRef(DEFAULT_PREVIEW_PX);
|
|
154
199
|
const maxOuterRef = useRef(0);
|
|
155
200
|
const [maxOuterPx, setMaxOuterPx] = useState(0);
|
|
156
201
|
const [previewWidthPx, setPreviewWidthPx] = useState(DEFAULT_PREVIEW_PX);
|
|
@@ -159,6 +204,24 @@ export function ComponentPlaygroundPane({
|
|
|
159
204
|
null,
|
|
160
205
|
);
|
|
161
206
|
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
livePreviewWidthRef.current = previewWidthPx;
|
|
209
|
+
}, [previewWidthPx]);
|
|
210
|
+
|
|
211
|
+
const applyLivePreviewWidth = useCallback((w: number) => {
|
|
212
|
+
const clamped = clampPreviewWidth(w, maxOuterRef.current);
|
|
213
|
+
livePreviewWidthRef.current = clamped;
|
|
214
|
+
const frame = previewFrameRef.current;
|
|
215
|
+
if (frame) {
|
|
216
|
+
frame.style.width = `${clamped}px`;
|
|
217
|
+
}
|
|
218
|
+
const label = previewWidthLabelRef.current;
|
|
219
|
+
if (label) {
|
|
220
|
+
label.textContent = `${Math.round(clamped)}px`;
|
|
221
|
+
}
|
|
222
|
+
return clamped;
|
|
223
|
+
}, []);
|
|
224
|
+
|
|
162
225
|
const syncPreviewToOuterWidth = useCallback((nextOuter: number) => {
|
|
163
226
|
if (!Number.isFinite(nextOuter) || nextOuter <= 0) return;
|
|
164
227
|
const prevOuter = maxOuterRef.current;
|
|
@@ -203,32 +266,61 @@ export function ComponentPlaygroundPane({
|
|
|
203
266
|
return () => window.removeEventListener("resize", syncUsemodsBreakpoints);
|
|
204
267
|
}, [syncUsemodsBreakpoints]);
|
|
205
268
|
|
|
206
|
-
const attachSymmetricWidthDrag = useCallback(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
269
|
+
const attachSymmetricWidthDrag = useCallback(
|
|
270
|
+
(side: "left" | "right") => {
|
|
271
|
+
return (e: PointerEvent<HTMLButtonElement>) => {
|
|
272
|
+
if (e.button !== 0) return;
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
const target = e.currentTarget;
|
|
275
|
+
target.setPointerCapture(e.pointerId);
|
|
276
|
+
|
|
277
|
+
let lastX = e.clientX;
|
|
278
|
+
let currentWidth = livePreviewWidthRef.current;
|
|
279
|
+
let rafId = 0;
|
|
280
|
+
const sign = side === "right" ? 1 : -1;
|
|
281
|
+
const prevBodyCursor = document.body.style.cursor;
|
|
282
|
+
const prevBodyUserSelect = document.body.style.userSelect;
|
|
283
|
+
document.body.style.cursor = "ew-resize";
|
|
284
|
+
document.body.style.userSelect = "none";
|
|
285
|
+
|
|
286
|
+
const flushWidth = () => {
|
|
287
|
+
rafId = 0;
|
|
288
|
+
applyLivePreviewWidth(currentWidth);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const onMove = (ev: globalThis.PointerEvent) => {
|
|
292
|
+
const dx = ev.clientX - lastX;
|
|
293
|
+
lastX = ev.clientX;
|
|
294
|
+
currentWidth = clampPreviewWidth(
|
|
295
|
+
currentWidth + sign * 2 * dx,
|
|
296
|
+
maxOuterRef.current,
|
|
297
|
+
);
|
|
298
|
+
if (!rafId) {
|
|
299
|
+
rafId = requestAnimationFrame(flushWidth);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const endDrag = (ev: globalThis.PointerEvent) => {
|
|
304
|
+
if (rafId) {
|
|
305
|
+
cancelAnimationFrame(rafId);
|
|
306
|
+
applyLivePreviewWidth(currentWidth);
|
|
307
|
+
}
|
|
308
|
+
setPreviewWidthPx(livePreviewWidthRef.current);
|
|
309
|
+
document.body.style.cursor = prevBodyCursor;
|
|
310
|
+
document.body.style.userSelect = prevBodyUserSelect;
|
|
311
|
+
target.releasePointerCapture(ev.pointerId);
|
|
312
|
+
window.removeEventListener("pointermove", onMove);
|
|
313
|
+
window.removeEventListener("pointerup", endDrag);
|
|
314
|
+
window.removeEventListener("pointercancel", endDrag);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
window.addEventListener("pointermove", onMove);
|
|
318
|
+
window.addEventListener("pointerup", endDrag);
|
|
319
|
+
window.addEventListener("pointercancel", endDrag);
|
|
226
320
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
};
|
|
231
|
-
}, []);
|
|
321
|
+
},
|
|
322
|
+
[applyLivePreviewWidth],
|
|
323
|
+
);
|
|
232
324
|
|
|
233
325
|
const hasControls = entry.controls.length > 0;
|
|
234
326
|
|
|
@@ -240,38 +332,105 @@ export function ComponentPlaygroundPane({
|
|
|
240
332
|
const showVariantsSection =
|
|
241
333
|
hasControls &&
|
|
242
334
|
(variantEnumeration.combinations.length > 0 ||
|
|
243
|
-
|
|
244
|
-
|
|
335
|
+
variantEnumeration.totalCount === 0);
|
|
336
|
+
|
|
337
|
+
const [variantA11yFindings, setVariantA11yFindings] = useState<
|
|
338
|
+
PlaygroundA11yFinding[]
|
|
339
|
+
>([]);
|
|
340
|
+
const [variantScanComplete, setVariantScanComplete] = useState(false);
|
|
341
|
+
|
|
342
|
+
useEffect(() => {
|
|
343
|
+
setVariantA11yFindings([]);
|
|
344
|
+
setVariantScanComplete(false);
|
|
345
|
+
}, [entry.id, variantEnumeration.combinations]);
|
|
346
|
+
|
|
347
|
+
const handleVariantA11yScan = useCallback(
|
|
348
|
+
(findings: PlaygroundA11yFinding[]) => {
|
|
349
|
+
setVariantA11yFindings(findings);
|
|
350
|
+
setVariantScanComplete(true);
|
|
351
|
+
},
|
|
352
|
+
[],
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const combinedA11y = useMemo(() => {
|
|
356
|
+
const findings = mergePlaygroundA11yFindings(
|
|
357
|
+
a11y.findings,
|
|
358
|
+
variantA11yFindings,
|
|
359
|
+
);
|
|
360
|
+
return {
|
|
361
|
+
...a11y,
|
|
362
|
+
findings,
|
|
363
|
+
issueCount: findings.length,
|
|
364
|
+
score: playgroundA11yScore(a11y.findings, variantA11yFindings),
|
|
365
|
+
};
|
|
366
|
+
}, [a11y, variantA11yFindings]);
|
|
367
|
+
|
|
368
|
+
const hasVariantMatrix = variantEnumeration.combinations.length > 0;
|
|
369
|
+
const variantScanPending = hasVariantMatrix && !variantScanComplete;
|
|
370
|
+
const a11yScoreLabel =
|
|
371
|
+
reportReady || variantScanComplete
|
|
372
|
+
? `${combinedA11y.score}/100${variantScanPending ? "…" : ""}`
|
|
373
|
+
: "—";
|
|
374
|
+
|
|
375
|
+
const report = reportReady ? workspaceReport : null;
|
|
376
|
+
const family = useMemo(
|
|
377
|
+
() => componentCatalogFamilyForName(report, entry.id),
|
|
378
|
+
[report, entry.id],
|
|
379
|
+
);
|
|
380
|
+
const childComponents = family?.parent === entry.id ? family.children : [];
|
|
381
|
+
const resetControls = () =>
|
|
382
|
+
setValues(defaultArgsFromControls(entry.controls));
|
|
383
|
+
|
|
384
|
+
const tocItems: { href: string; label: string; show?: boolean }[] = [
|
|
385
|
+
{ href: "#api-reference", label: "API reference", show: hasControls },
|
|
386
|
+
{ href: "#usage", label: "Usage" },
|
|
387
|
+
{
|
|
388
|
+
href: "#subcomponents",
|
|
389
|
+
label: "Subcomponents",
|
|
390
|
+
show: childComponents.length > 0,
|
|
391
|
+
},
|
|
392
|
+
{ href: "#repo-usage", label: "Repo usage" },
|
|
393
|
+
{ href: "#design-tokens", label: "Design tokens" },
|
|
394
|
+
{ href: "#code-score", label: "Code score" },
|
|
395
|
+
{ href: "#accessibility", label: "Accessibility" },
|
|
396
|
+
{ href: "#variants", label: "Variants", show: showVariantsSection },
|
|
397
|
+
];
|
|
245
398
|
|
|
246
399
|
return (
|
|
247
400
|
<div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background">
|
|
248
401
|
<div className="min-h-0 flex-1 overflow-auto">
|
|
249
|
-
<header
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
402
|
+
<header
|
|
403
|
+
id="source"
|
|
404
|
+
className="scroll-mt-20 border-b border-border bg-card p-6"
|
|
405
|
+
>
|
|
406
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
407
|
+
<div className="min-w-0">
|
|
408
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
409
|
+
Components
|
|
410
|
+
{entry.meta.group ? (
|
|
411
|
+
<>
|
|
412
|
+
{" "}
|
|
413
|
+
<span className="text-muted-foreground/40">/</span>{" "}
|
|
414
|
+
<span className="capitalize text-foreground/80">
|
|
415
|
+
{entry.meta.group}
|
|
416
|
+
</span>
|
|
417
|
+
</>
|
|
418
|
+
) : null}
|
|
419
|
+
</p>
|
|
420
|
+
<h1 className="text-3xl font-semibold tracking-tight text-foreground">
|
|
421
|
+
{entry.meta.title}
|
|
422
|
+
</h1>
|
|
423
|
+
</div>
|
|
424
|
+
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
|
425
|
+
{sourceAbsolutePath ? (
|
|
426
|
+
<OpenInEditorButton filePath={sourceAbsolutePath} />
|
|
427
|
+
) : null}
|
|
428
|
+
{onHideFromCatalog ? (
|
|
429
|
+
<HideFromCatalogButton
|
|
430
|
+
componentName={entry.meta.id}
|
|
431
|
+
onHidden={onHideFromCatalog}
|
|
432
|
+
/>
|
|
433
|
+
) : null}
|
|
275
434
|
</div>
|
|
276
435
|
</div>
|
|
277
436
|
</header>
|
|
@@ -284,44 +443,39 @@ export function ComponentPlaygroundPane({
|
|
|
284
443
|
<div className="flex justify-center">
|
|
285
444
|
<div
|
|
286
445
|
ref={previewFrameRef}
|
|
287
|
-
className="relative min-w-0 shrink-0 select-none rounded-lg border border-border bg-muted/50 shadow-xs"
|
|
446
|
+
className="relative min-w-0 shrink-0 select-none rounded-lg border border-border bg-muted/50 shadow-xs will-change-[width]"
|
|
288
447
|
style={{ width: previewWidthPx }}
|
|
289
448
|
>
|
|
290
|
-
<
|
|
291
|
-
|
|
292
|
-
className="absolute left-0 top-0 bottom-0 z-10 flex w-4 -translate-x-1/2 cursor-ew-resize touch-none items-center justify-center rounded border-0 bg-muted p-0 shadow-xs ring-1 ring-border hover:bg-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
293
|
-
aria-label="Resize preview from center (drag left or right)"
|
|
449
|
+
<PreviewResizeHandle
|
|
450
|
+
side="left"
|
|
294
451
|
onPointerDown={attachSymmetricWidthDrag("left")}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
aria-hidden
|
|
299
|
-
/>
|
|
300
|
-
</button>
|
|
301
|
-
<button
|
|
302
|
-
type="button"
|
|
303
|
-
className="absolute right-0 top-0 bottom-0 z-10 flex w-4 translate-x-1/2 cursor-ew-resize touch-none items-center justify-center rounded border-0 bg-muted p-0 shadow-xs ring-1 ring-border hover:bg-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
304
|
-
aria-label="Resize preview from center (drag left or right)"
|
|
452
|
+
/>
|
|
453
|
+
<PreviewResizeHandle
|
|
454
|
+
side="right"
|
|
305
455
|
onPointerDown={attachSymmetricWidthDrag("right")}
|
|
456
|
+
/>
|
|
457
|
+
<PlaygroundAppThemeWrapper
|
|
458
|
+
workspaceReport={report}
|
|
459
|
+
className="min-w-0 p-8 backdrop-blur-2xl"
|
|
306
460
|
>
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<Preview values={values} />
|
|
314
|
-
</div>
|
|
461
|
+
<PlaygroundPreviewErrorBoundary
|
|
462
|
+
componentName={entry.meta.title}
|
|
463
|
+
>
|
|
464
|
+
{renderPreview(values)}
|
|
465
|
+
</PlaygroundPreviewErrorBoundary>
|
|
466
|
+
</PlaygroundAppThemeWrapper>
|
|
315
467
|
</div>
|
|
316
468
|
</div>
|
|
317
469
|
{maxOuterPx > 0 ? (
|
|
318
|
-
<div className="mt-4
|
|
319
|
-
<span className="p-2.5">
|
|
320
|
-
|
|
470
|
+
<div className="mx-auto mt-4 flex h-6 w-fit items-center overflow-hidden rounded-sm border border-border bg-card text-center font-mono text-xs/none tabular-nums text-muted-foreground divide-x divide-border">
|
|
471
|
+
<span ref={previewWidthLabelRef} className="p-2.5">
|
|
472
|
+
{Math.round(previewWidthPx)}px
|
|
473
|
+
</span>
|
|
474
|
+
<span className="p-2.5" title="usemods detectBreakpoint">
|
|
321
475
|
Screen: {windowBreakpoint ?? "—"}
|
|
322
476
|
</span>
|
|
323
477
|
<span
|
|
324
|
-
className="
|
|
478
|
+
className="p-2.5"
|
|
325
479
|
title="usemods detectContainerBreakpoint"
|
|
326
480
|
>
|
|
327
481
|
Container: {containerBreakpoint ?? "—"}
|
|
@@ -336,27 +490,21 @@ export function ComponentPlaygroundPane({
|
|
|
336
490
|
<div className="min-w-0 space-y-14">
|
|
337
491
|
{hasControls ? (
|
|
338
492
|
<PlaygroundApiReference
|
|
493
|
+
entry={entry}
|
|
339
494
|
controls={entry.controls}
|
|
340
495
|
values={values}
|
|
341
496
|
onChange={setValues}
|
|
342
|
-
onReset={
|
|
343
|
-
setValues(defaultArgsFromControls(entry.controls))
|
|
344
|
-
}
|
|
497
|
+
onReset={resetControls}
|
|
345
498
|
reportUsage={repoUsage}
|
|
346
499
|
declaredPropsFromScan={declaredPropsFromScan}
|
|
347
|
-
governanceReportLoaded={
|
|
348
|
-
reportReady && workspaceReport != null
|
|
349
|
-
}
|
|
500
|
+
governanceReportLoaded={report != null}
|
|
350
501
|
/>
|
|
351
502
|
) : null}
|
|
352
503
|
|
|
353
504
|
<PlaygroundUsageSection entry={entry} values={values} />
|
|
354
505
|
|
|
355
506
|
<Section id="repo-usage" title="Repo usage" description="">
|
|
356
|
-
<ComponentUsageDetails
|
|
357
|
-
report={reportReady ? workspaceReport : null}
|
|
358
|
-
componentId={entry.id}
|
|
359
|
-
/>
|
|
507
|
+
<ComponentUsageDetails report={report} componentId={entry.id} />
|
|
360
508
|
</Section>
|
|
361
509
|
|
|
362
510
|
<Section
|
|
@@ -383,10 +531,14 @@ export function ComponentPlaygroundPane({
|
|
|
383
531
|
|
|
384
532
|
<Section
|
|
385
533
|
id="accessibility"
|
|
386
|
-
title={`Accessibility: ${
|
|
387
|
-
description="
|
|
534
|
+
title={`Accessibility: ${a11yScoreLabel}`}
|
|
535
|
+
description="Static accessibility rules from the DSLinter report, plus runtime color-contrast checks on each variant preview below."
|
|
388
536
|
>
|
|
389
|
-
<PlaygroundA11ySection
|
|
537
|
+
<PlaygroundA11ySection
|
|
538
|
+
a11y={combinedA11y}
|
|
539
|
+
reportReady={reportReady || variantScanComplete}
|
|
540
|
+
variantScanPending={variantScanPending}
|
|
541
|
+
/>
|
|
390
542
|
</Section>
|
|
391
543
|
</div>
|
|
392
544
|
|
|
@@ -398,17 +550,13 @@ export function ComponentPlaygroundPane({
|
|
|
398
550
|
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
399
551
|
On this page
|
|
400
552
|
</p>
|
|
401
|
-
{
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
<TocLink href="#accessibility">Accessibility</TocLink>
|
|
409
|
-
{showVariantsSection ? (
|
|
410
|
-
<TocLink href="#variants">Variants</TocLink>
|
|
411
|
-
) : null}
|
|
553
|
+
{tocItems.map(({ href, label, show = true }) =>
|
|
554
|
+
show ? (
|
|
555
|
+
<TocLink key={href} href={href}>
|
|
556
|
+
{label}
|
|
557
|
+
</TocLink>
|
|
558
|
+
) : null,
|
|
559
|
+
)}
|
|
412
560
|
</nav>
|
|
413
561
|
</aside>
|
|
414
562
|
</div>
|
|
@@ -417,18 +565,19 @@ export function ComponentPlaygroundPane({
|
|
|
417
565
|
{variantEnumeration.combinations.length > 0 ? (
|
|
418
566
|
<section
|
|
419
567
|
id="variants"
|
|
420
|
-
className="ds-playground-dot-surface mt-8 w-full scroll-mt-20 border-t
|
|
568
|
+
className="ds-playground-dot-surface mt-8 w-full scroll-mt-20 border-t pt-10 pb-12"
|
|
421
569
|
>
|
|
422
570
|
<div className="min-w-0 w-full px-6 lg:px-12">
|
|
423
571
|
<h2 className="w-fit bg-card text-xl font-semibold tracking-tight text-foreground">
|
|
424
572
|
All variants
|
|
425
573
|
</h2>
|
|
426
574
|
<PlaygroundVariantMatrix
|
|
427
|
-
|
|
575
|
+
renderPreview={renderPreview}
|
|
428
576
|
combinations={variantEnumeration.combinations}
|
|
429
577
|
finiteAxisKeys={variantEnumeration.finiteAxisKeys}
|
|
430
578
|
totalCount={variantEnumeration.totalCount}
|
|
431
579
|
capped={variantEnumeration.capped}
|
|
580
|
+
onVariantA11yScan={handleVariantA11yScan}
|
|
432
581
|
/>
|
|
433
582
|
</div>
|
|
434
583
|
</section>
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import type { HashRoute } from "../shell/hashRoute";
|
|
13
13
|
|
|
14
14
|
type Props = {
|
|
15
|
-
|
|
15
|
+
catalogEntries: { name: string; label: string }[];
|
|
16
16
|
onNavigate: (next: HashRoute) => void;
|
|
17
17
|
open: boolean;
|
|
18
18
|
onOpenChange: (open: boolean) => void;
|
|
@@ -25,12 +25,7 @@ function eventTargetIsEditable(target: EventTarget | null): boolean {
|
|
|
25
25
|
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function DashboardCommandPalette({
|
|
29
|
-
catalogNames,
|
|
30
|
-
onNavigate,
|
|
31
|
-
open,
|
|
32
|
-
onOpenChange,
|
|
33
|
-
}: Props) {
|
|
28
|
+
export function DashboardCommandPalette({ catalogEntries, onNavigate, open, onOpenChange }: Props) {
|
|
34
29
|
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
35
30
|
|
|
36
31
|
useEffect(() => {
|
|
@@ -65,18 +60,18 @@ export function DashboardCommandPalette({
|
|
|
65
60
|
Governance
|
|
66
61
|
</CommandItem>
|
|
67
62
|
</CommandGroup>
|
|
68
|
-
{
|
|
63
|
+
{catalogEntries.length > 0 ? (
|
|
69
64
|
<CommandGroup heading="Components">
|
|
70
|
-
{
|
|
65
|
+
{catalogEntries.map(({ name, label }) => (
|
|
71
66
|
<CommandItem
|
|
72
67
|
key={name}
|
|
73
|
-
value={name}
|
|
68
|
+
value={`${name} ${label}`}
|
|
74
69
|
onSelect={() => {
|
|
75
70
|
onNavigate({ view: "component", componentId: name });
|
|
76
71
|
close();
|
|
77
72
|
}}
|
|
78
73
|
>
|
|
79
|
-
{
|
|
74
|
+
{label}
|
|
80
75
|
</CommandItem>
|
|
81
76
|
))}
|
|
82
77
|
</CommandGroup>
|
|
@@ -14,8 +14,8 @@ type Props = {
|
|
|
14
14
|
|
|
15
15
|
export function GovernancePane({
|
|
16
16
|
landing,
|
|
17
|
-
reportUrl: _reportUrl = "/
|
|
18
|
-
dslinterReportHint = "npm run
|
|
17
|
+
reportUrl: _reportUrl = "/dslinter-report.json",
|
|
18
|
+
dslinterReportHint = "npm run dslinter:report",
|
|
19
19
|
dslinterReport,
|
|
20
20
|
onOpenComponent,
|
|
21
21
|
}: Props) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { hideCatalogComponent } from "../dashboard/updateDslintConfig";
|
|
3
|
+
import { Button } from "./ui/button";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
componentName: string;
|
|
7
|
+
onHidden: (componentName: string) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function HideFromCatalogButton({ componentName, onHidden }: Props) {
|
|
11
|
+
const [pending, setPending] = useState(false);
|
|
12
|
+
|
|
13
|
+
const handleClick = useCallback(async () => {
|
|
14
|
+
if (
|
|
15
|
+
!window.confirm(
|
|
16
|
+
`Hide ${componentName} from the component catalog? This updates hidden_components in .dslinter.json.`,
|
|
17
|
+
)
|
|
18
|
+
) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
setPending(true);
|
|
22
|
+
try {
|
|
23
|
+
await hideCatalogComponent(componentName);
|
|
24
|
+
onHidden(componentName);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
27
|
+
window.alert(`Could not hide ${componentName}: ${message}`);
|
|
28
|
+
} finally {
|
|
29
|
+
setPending(false);
|
|
30
|
+
}
|
|
31
|
+
}, [componentName, onHidden]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Button
|
|
35
|
+
type="button"
|
|
36
|
+
size="sm"
|
|
37
|
+
variant="outline"
|
|
38
|
+
disabled={pending}
|
|
39
|
+
onClick={() => void handleClick()}
|
|
40
|
+
>
|
|
41
|
+
{pending ? "Hiding…" : "Hide Component"}
|
|
42
|
+
</Button>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { openSourceFile } from "../dashboard/editorLink";
|
|
3
|
+
import { Button } from "./ui/button";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
filePath: string;
|
|
7
|
+
line?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function OpenInEditorButton({ filePath, line }: Props) {
|
|
11
|
+
const [pending, setPending] = useState(false);
|
|
12
|
+
|
|
13
|
+
const handleClick = useCallback(async () => {
|
|
14
|
+
setPending(true);
|
|
15
|
+
try {
|
|
16
|
+
await openSourceFile(filePath, line);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
19
|
+
window.alert(`Could not open file: ${message}`);
|
|
20
|
+
} finally {
|
|
21
|
+
setPending(false);
|
|
22
|
+
}
|
|
23
|
+
}, [filePath, line]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Button
|
|
27
|
+
type="button"
|
|
28
|
+
size="sm"
|
|
29
|
+
variant="outline"
|
|
30
|
+
disabled={pending}
|
|
31
|
+
onClick={() => void handleClick()}
|
|
32
|
+
>
|
|
33
|
+
{pending ? "Opening…" : "Open in Editor"}
|
|
34
|
+
</Button>
|
|
35
|
+
);
|
|
36
|
+
}
|