@vegastack/design 0.7.53 → 0.7.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.
- package/bin/doctor.mjs +81 -1
- package/dist/{chunk-MJM6KH7P.js → chunk-5YMLB7DY.js} +10 -3
- package/dist/create-animated-icon.js +1 -1
- package/dist/icons/index.js +1 -1
- package/dist/index.cjs +10 -3
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/vegastack-design-system/SKILL.md +4 -2
- package/skills/vegastack-design-system/references/components.md +2 -1
package/bin/doctor.mjs
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
// renders as body text. The Regent consumer carried 338 such classes past every other check
|
|
19
19
|
// (2026-09-23), which is why this is a failing check and not a guide section.
|
|
20
20
|
//
|
|
21
|
+
// And it holds pages to the one page layout: an `AppShellPage` picks its width with `size`
|
|
22
|
+
// (`prose` · `default` · `full`) and pads with the shared page gutter, so a `max-w-*`, `mx-*`,
|
|
23
|
+
// `w-*` or padding class on one is a page choosing its own width or gutter, and fails.
|
|
24
|
+
//
|
|
21
25
|
// Read-only: it never writes, installs, or edits. Exit 0 = all good, 1 = a real problem,
|
|
22
26
|
// so it composes into CI as `vegastack-design doctor`.
|
|
23
27
|
|
|
@@ -48,7 +52,8 @@ Usage: vegastack-design doctor [options]
|
|
|
48
52
|
|
|
49
53
|
Checks a consuming project's VegaStack setup and reports what is wrong and how to fix it.
|
|
50
54
|
Also scans the project's own source (not node_modules, build output, or the components.json
|
|
51
|
-
\`ui\` alias directory) for vocabulary the shadcn reset retired, and
|
|
55
|
+
\`ui\` alias directory) for vocabulary the shadcn reset retired, and for an AppShellPage that sets
|
|
56
|
+
its own width or gutter, and reports each as file:line.
|
|
52
57
|
|
|
53
58
|
Options:
|
|
54
59
|
--dir <path> Project root to inspect (default: the current directory)
|
|
@@ -584,6 +589,58 @@ export function scanRetiredVocabulary(
|
|
|
584
589
|
};
|
|
585
590
|
}
|
|
586
591
|
|
|
592
|
+
// ---- page layout -----------------------------------------------------------------------------
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* A class on an `AppShellPage` that picks the page's width or gutter — which `size` and the page
|
|
596
|
+
* gutter own. `pb-*` stays legal: room at the bottom for a docked save bar is not a gutter.
|
|
597
|
+
*/
|
|
598
|
+
const PAGE_OVERRIDE =
|
|
599
|
+
/(?<![\w-])(?:[\w@\[\]()/.:-]*:)?(?:max-w|min-w|w|mx|ms|me|px|ps|pe|p|py|pt)-[^\s"'`]+/g;
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Every `AppShellPage` in the project's own source that sets its own width or gutter (a failure)
|
|
603
|
+
* or still says `size="narrow"` (a warning — `prose` is its name now).
|
|
604
|
+
*/
|
|
605
|
+
export function scanPageLayout(root, { skipDirs = [] } = {}) {
|
|
606
|
+
const skip = new Set(skipDirs.map((d) => resolve(d)));
|
|
607
|
+
const findings = [];
|
|
608
|
+
const deprecated = [];
|
|
609
|
+
const walk = (dir) => {
|
|
610
|
+
let entries;
|
|
611
|
+
try {
|
|
612
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
613
|
+
} catch {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
for (const e of entries) {
|
|
617
|
+
if (e.name.startsWith(".") || SCAN_SKIP_DIRS.has(e.name)) continue;
|
|
618
|
+
const full = join(dir, e.name);
|
|
619
|
+
if (e.isDirectory()) {
|
|
620
|
+
if (!skip.has(resolve(full))) walk(full);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (!e.isFile() || !/\.[jt]sx$/.test(e.name)) continue;
|
|
624
|
+
const src = readIfExists(full);
|
|
625
|
+
if (src == null || !src.includes("<AppShellPage")) continue;
|
|
626
|
+
const file = relative(root, full).split(sep).join("/");
|
|
627
|
+
for (const tag of src.matchAll(/<AppShellPage\b(?:[^>{]|\{[^}]*\})*>/g)) {
|
|
628
|
+
const line = src.slice(0, tag.index).split("\n").length;
|
|
629
|
+
const cls = /\bclassName=(?:"([^"]*)"|\{([\s\S]*?)\}(?=\s|\/?>))/.exec(
|
|
630
|
+
tag[0],
|
|
631
|
+
);
|
|
632
|
+
const text = cls ? (cls[1] ?? cls[2] ?? "") : "";
|
|
633
|
+
for (const m of text.matchAll(PAGE_OVERRIDE))
|
|
634
|
+
findings.push({ file, line, match: m[0] });
|
|
635
|
+
if (/\bsize=(?:"narrow"|\{\s*["']narrow["']\s*\})/.test(tag[0]))
|
|
636
|
+
deprecated.push({ file, line, match: 'size="narrow"' });
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
walk(root);
|
|
641
|
+
return { findings, deprecated };
|
|
642
|
+
}
|
|
643
|
+
|
|
587
644
|
export function main(argv = []) {
|
|
588
645
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
589
646
|
console.log(USAGE);
|
|
@@ -802,6 +859,29 @@ export function main(argv = []) {
|
|
|
802
859
|
});
|
|
803
860
|
}
|
|
804
861
|
|
|
862
|
+
// ---- 8. every page takes its width from AppShellPage's size ------------------------------
|
|
863
|
+
const layout = scanPageLayout(root, { skipDirs });
|
|
864
|
+
if (layout.findings.length > 0) {
|
|
865
|
+
results.push({
|
|
866
|
+
level: "fail",
|
|
867
|
+
name: "page layout",
|
|
868
|
+
detail: `${layout.findings.length} class(es) on AppShellPage set the page's own width or gutter`,
|
|
869
|
+
fix: "delete them — pick the width with size (prose · default · full) from the route map; the gutter is --page-gutter (https://design.vegastack.com/docs/foundations/page-layout)",
|
|
870
|
+
findings: layout.findings.map((f) => `${f.file}:${f.line} ${f.match}`),
|
|
871
|
+
});
|
|
872
|
+
} else {
|
|
873
|
+
ok("page layout", "no AppShellPage sets its own width or gutter");
|
|
874
|
+
}
|
|
875
|
+
if (layout.deprecated.length > 0) {
|
|
876
|
+
results.push({
|
|
877
|
+
level: "warn",
|
|
878
|
+
name: "page width names",
|
|
879
|
+
detail: `${layout.deprecated.length} AppShellPage(s) still say size="narrow"`,
|
|
880
|
+
fix: 'rename to size="prose" — narrow is removed in the next minor',
|
|
881
|
+
findings: layout.deprecated.map((f) => `${f.file}:${f.line} ${f.match}`),
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
|
|
805
885
|
// ---- report -------------------------------------------------------------------------------
|
|
806
886
|
const glyph = { ok: "✓", warn: "!", fail: "✗" };
|
|
807
887
|
console.log("");
|
|
@@ -74,11 +74,18 @@ var prose = {
|
|
|
74
74
|
pre: "[&_pre:not([data-slot='code-block-pre'])]:my-2 [&_pre:not([data-slot='code-block-pre'])]:overflow-x-auto [&_pre:not([data-slot='code-block-pre'])]:rounded-lg [&_pre:not([data-slot='code-block-pre'])]:border [&_pre:not([data-slot='code-block-pre'])]:border-border [&_pre:not([data-slot='code-block-pre'])]:bg-muted [&_pre:not([data-slot='code-block-pre'])]:p-4 [&_pre:not([data-slot='code-block-pre'])]:text-foreground",
|
|
75
75
|
preCode: "[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:font-mono [&_pre_code]:text-sm [&_pre_code]:text-foreground",
|
|
76
76
|
hr: "[&_hr]:my-4 [&_hr]:border-border",
|
|
77
|
-
// GFM tables
|
|
78
|
-
//
|
|
77
|
+
// GFM tables, Notion's simple table: a rounded outer border, a rule between every cell, and a
|
|
78
|
+
// header row on a subtle muted ground at medium weight. The scroll container is structural and
|
|
79
|
+
// belongs to the consumer (MarkdownView and TextEdit each wrap the table in an `overflow-x-auto`
|
|
80
|
+
// box); these are the cell and rule tokens. `border-separate` with zero spacing is what lets the
|
|
81
|
+
// table itself take the radius (a collapsed table cannot); each cell draws only its bottom and
|
|
82
|
+
// end rule, the last row and column drop theirs against the outer border, and the corner cells
|
|
83
|
+
// take the radius so a header or selected fill never pokes past the curve. Cells take
|
|
79
84
|
// `break-word`, not the root's `anywhere`, so a column keeps its words whole and the table scrolls
|
|
80
85
|
// in its box rather than crushing to one character per line.
|
|
81
|
-
table: "[&_table]:w-full [&_table]:border-
|
|
86
|
+
table: "[&_table]:w-full [&_table]:border-separate [&_table]:border-spacing-0 [&_table]:rounded-lg [&_table]:border [&_table]:border-border",
|
|
87
|
+
tableRules: "[&_th]:border-e [&_th]:border-b [&_th]:border-border [&_td]:border-e [&_td]:border-b [&_td]:border-border [&_tr>*:last-child]:border-e-0 [&_tbody>tr:last-child>*]:border-b-0 [&_tr:first-child>*:first-child]:rounded-ss-lg [&_tr:first-child>*:last-child]:rounded-se-lg [&_tbody>tr:last-child>*:first-child]:rounded-es-lg [&_tbody>tr:last-child>*:last-child]:rounded-ee-lg",
|
|
88
|
+
tableCells: "[&_th]:bg-muted/50 [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:align-top [&_th]:font-medium [&_td]:px-3 [&_td]:py-2 [&_td]:align-top [&_td_p]:my-0 [&_th_p]:my-0 [&_th]:wrap-break-word [&_td]:wrap-break-word",
|
|
82
89
|
img: "[&_img]:my-2 [&_img]:max-w-full [&_img]:rounded-lg [&_img]:border [&_img]:border-border"
|
|
83
90
|
};
|
|
84
91
|
var proseClassName = Object.values(prose).join(" ");
|
package/dist/icons/index.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -103,11 +103,18 @@ var prose = {
|
|
|
103
103
|
pre: "[&_pre:not([data-slot='code-block-pre'])]:my-2 [&_pre:not([data-slot='code-block-pre'])]:overflow-x-auto [&_pre:not([data-slot='code-block-pre'])]:rounded-lg [&_pre:not([data-slot='code-block-pre'])]:border [&_pre:not([data-slot='code-block-pre'])]:border-border [&_pre:not([data-slot='code-block-pre'])]:bg-muted [&_pre:not([data-slot='code-block-pre'])]:p-4 [&_pre:not([data-slot='code-block-pre'])]:text-foreground",
|
|
104
104
|
preCode: "[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:font-mono [&_pre_code]:text-sm [&_pre_code]:text-foreground",
|
|
105
105
|
hr: "[&_hr]:my-4 [&_hr]:border-border",
|
|
106
|
-
// GFM tables
|
|
107
|
-
//
|
|
106
|
+
// GFM tables, Notion's simple table: a rounded outer border, a rule between every cell, and a
|
|
107
|
+
// header row on a subtle muted ground at medium weight. The scroll container is structural and
|
|
108
|
+
// belongs to the consumer (MarkdownView and TextEdit each wrap the table in an `overflow-x-auto`
|
|
109
|
+
// box); these are the cell and rule tokens. `border-separate` with zero spacing is what lets the
|
|
110
|
+
// table itself take the radius (a collapsed table cannot); each cell draws only its bottom and
|
|
111
|
+
// end rule, the last row and column drop theirs against the outer border, and the corner cells
|
|
112
|
+
// take the radius so a header or selected fill never pokes past the curve. Cells take
|
|
108
113
|
// `break-word`, not the root's `anywhere`, so a column keeps its words whole and the table scrolls
|
|
109
114
|
// in its box rather than crushing to one character per line.
|
|
110
|
-
table: "[&_table]:w-full [&_table]:border-
|
|
115
|
+
table: "[&_table]:w-full [&_table]:border-separate [&_table]:border-spacing-0 [&_table]:rounded-lg [&_table]:border [&_table]:border-border",
|
|
116
|
+
tableRules: "[&_th]:border-e [&_th]:border-b [&_th]:border-border [&_td]:border-e [&_td]:border-b [&_td]:border-border [&_tr>*:last-child]:border-e-0 [&_tbody>tr:last-child>*]:border-b-0 [&_tr:first-child>*:first-child]:rounded-ss-lg [&_tr:first-child>*:last-child]:rounded-se-lg [&_tbody>tr:last-child>*:first-child]:rounded-es-lg [&_tbody>tr:last-child>*:last-child]:rounded-ee-lg",
|
|
117
|
+
tableCells: "[&_th]:bg-muted/50 [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:align-top [&_th]:font-medium [&_td]:px-3 [&_td]:py-2 [&_td]:align-top [&_td_p]:my-0 [&_th_p]:my-0 [&_th]:wrap-break-word [&_td]:wrap-break-word",
|
|
111
118
|
img: "[&_img]:my-2 [&_img]:max-w-full [&_img]:rounded-lg [&_img]:border [&_img]:border-border"
|
|
112
119
|
};
|
|
113
120
|
var proseClassName = Object.values(prose).join(" ");
|
package/dist/index.d.cts
CHANGED
|
@@ -81,7 +81,9 @@ declare const prose: {
|
|
|
81
81
|
readonly pre: "[&_pre:not([data-slot='code-block-pre'])]:my-2 [&_pre:not([data-slot='code-block-pre'])]:overflow-x-auto [&_pre:not([data-slot='code-block-pre'])]:rounded-lg [&_pre:not([data-slot='code-block-pre'])]:border [&_pre:not([data-slot='code-block-pre'])]:border-border [&_pre:not([data-slot='code-block-pre'])]:bg-muted [&_pre:not([data-slot='code-block-pre'])]:p-4 [&_pre:not([data-slot='code-block-pre'])]:text-foreground";
|
|
82
82
|
readonly preCode: "[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:font-mono [&_pre_code]:text-sm [&_pre_code]:text-foreground";
|
|
83
83
|
readonly hr: "[&_hr]:my-4 [&_hr]:border-border";
|
|
84
|
-
readonly table: "[&_table]:w-full [&_table]:border-
|
|
84
|
+
readonly table: "[&_table]:w-full [&_table]:border-separate [&_table]:border-spacing-0 [&_table]:rounded-lg [&_table]:border [&_table]:border-border";
|
|
85
|
+
readonly tableRules: "[&_th]:border-e [&_th]:border-b [&_th]:border-border [&_td]:border-e [&_td]:border-b [&_td]:border-border [&_tr>*:last-child]:border-e-0 [&_tbody>tr:last-child>*]:border-b-0 [&_tr:first-child>*:first-child]:rounded-ss-lg [&_tr:first-child>*:last-child]:rounded-se-lg [&_tbody>tr:last-child>*:first-child]:rounded-es-lg [&_tbody>tr:last-child>*:last-child]:rounded-ee-lg";
|
|
86
|
+
readonly tableCells: "[&_th]:bg-muted/50 [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:align-top [&_th]:font-medium [&_td]:px-3 [&_td]:py-2 [&_td]:align-top [&_td_p]:my-0 [&_th_p]:my-0 [&_th]:wrap-break-word [&_td]:wrap-break-word";
|
|
85
87
|
readonly img: "[&_img]:my-2 [&_img]:max-w-full [&_img]:rounded-lg [&_img]:border [&_img]:border-border";
|
|
86
88
|
};
|
|
87
89
|
/** The element roles the prose recipe covers. */
|
package/dist/index.d.ts
CHANGED
|
@@ -81,7 +81,9 @@ declare const prose: {
|
|
|
81
81
|
readonly pre: "[&_pre:not([data-slot='code-block-pre'])]:my-2 [&_pre:not([data-slot='code-block-pre'])]:overflow-x-auto [&_pre:not([data-slot='code-block-pre'])]:rounded-lg [&_pre:not([data-slot='code-block-pre'])]:border [&_pre:not([data-slot='code-block-pre'])]:border-border [&_pre:not([data-slot='code-block-pre'])]:bg-muted [&_pre:not([data-slot='code-block-pre'])]:p-4 [&_pre:not([data-slot='code-block-pre'])]:text-foreground";
|
|
82
82
|
readonly preCode: "[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:font-mono [&_pre_code]:text-sm [&_pre_code]:text-foreground";
|
|
83
83
|
readonly hr: "[&_hr]:my-4 [&_hr]:border-border";
|
|
84
|
-
readonly table: "[&_table]:w-full [&_table]:border-
|
|
84
|
+
readonly table: "[&_table]:w-full [&_table]:border-separate [&_table]:border-spacing-0 [&_table]:rounded-lg [&_table]:border [&_table]:border-border";
|
|
85
|
+
readonly tableRules: "[&_th]:border-e [&_th]:border-b [&_th]:border-border [&_td]:border-e [&_td]:border-b [&_td]:border-border [&_tr>*:last-child]:border-e-0 [&_tbody>tr:last-child>*]:border-b-0 [&_tr:first-child>*:first-child]:rounded-ss-lg [&_tr:first-child>*:last-child]:rounded-se-lg [&_tbody>tr:last-child>*:first-child]:rounded-es-lg [&_tbody>tr:last-child>*:last-child]:rounded-ee-lg";
|
|
86
|
+
readonly tableCells: "[&_th]:bg-muted/50 [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:align-top [&_th]:font-medium [&_td]:px-3 [&_td]:py-2 [&_td]:align-top [&_td_p]:my-0 [&_th_p]:my-0 [&_th]:wrap-break-word [&_td]:wrap-break-word";
|
|
85
87
|
readonly img: "[&_img]:my-2 [&_img]:max-w-full [&_img]:rounded-lg [&_img]:border [&_img]:border-border";
|
|
86
88
|
};
|
|
87
89
|
/** The element roles the prose recipe covers. */
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vegastack/design",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.55",
|
|
4
4
|
"description": "VegaStack design system — cn utility, icon runtime, Tailwind v4 preset, and the vegastack-design CLI (tokens ship separately as @vegastack/design-tokens)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -149,8 +149,10 @@ A component's name undersells it. Before composing something by hand, check this
|
|
|
149
149
|
|
|
150
150
|
### Which component for X
|
|
151
151
|
|
|
152
|
-
- **A page** → `AppShell` › `AppShellContent` › `AppShellPage` (`size`: `
|
|
153
|
-
settings, `default
|
|
152
|
+
- **A page** → `AppShell` › `AppShellContent` › `AppShellPage` (`size`: `prose` for forms and
|
|
153
|
+
settings, `default` for lists and record pages, `full` for boards and canvases — picked per route
|
|
154
|
+
from one `definePageWidths` map, never a `max-w-*` or padding class; see
|
|
155
|
+
<https://design.vegastack.com/docs/foundations/page-layout>) › `PageHeader` › `FilterBar` › `DataList` (or
|
|
154
156
|
`DataGrid`) › the `Empty` tier that fits. The spacing between them is the page-rhythm recipe
|
|
155
157
|
(<https://design.vegastack.com/docs/foundations/spacing#page-rhythm>).
|
|
156
158
|
- **Inline editing** → `EditableCell` with `onSave` returning a promise: `variant="cell"` inside a
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<!-- GENERATED — do not hand-edit. Regenerated from the design system's component contract,
|
|
4
4
|
which is the authority for membership and counts. -->
|
|
5
5
|
|
|
6
|
-
**
|
|
6
|
+
**134 components**, plus 467 animated-icon items, 13 hooks (`use-animation-replay`, `use-announcer`, `use-async-search`, `use-drag-reorder`, `use-file-drop`, `use-inline-edit`, `use-list-nav`, `use-media-query`, `use-mobile`, `use-modal-inert`, `use-overflow`, `use-platform`, `use-tabs-swipe`), 11 starter blocks (`app-shell-01`, `board-01`, `issue-detail-01`, `command-search-01`, `list-page-01`, `login-01`, `notifications-01`, `review-split-01`, `settings-01`, `settings-02`, `status-pages-01`), 68 chart blocks across 7 families, and 6 data libs (`date-time`, `geo-data`, `emoji-data`, `drag-item`, `page-layout`, `tile-overlay`) — 699 registry items in total.
|
|
7
7
|
|
|
8
8
|
Install any of them with `shadcn add @vegastack/<name>`. Animated icons install as
|
|
9
9
|
`@vegastack/icon-<name>`; the bare name is reserved for components, so a component whose name
|
|
@@ -186,3 +186,4 @@ starts with `icon-` is a component and never an icon.
|
|
|
186
186
|
## forms
|
|
187
187
|
|
|
188
188
|
- **`avatar-picker`** — A person's avatar that opens a dialog to upload, change or remove their photo — the file is staged in a 128px drop circle and saved only on Update.
|
|
189
|
+
- **`upload-dialog`** — The "Add files" modal: a drop zone and the staged files (thumbnail or icon, name, size, remove), then an optional per-file details view the host renders, and Add. It stages files only; the host uploads them.
|