@godxjp/ui-mcp 0.12.0 → 0.13.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/index.js +486 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6995,6 +6995,224 @@ export function InvoiceListHeader() {
|
|
|
6995
6995
|
<TagInput name="labels" placeholder="\u30E9\u30D9\u30EB\u3092\u8FFD\u52A0\u2026" onValueChange={(tags) => setTags(tags)} />`,
|
|
6996
6996
|
storyPath: "data-entry/TagInput.stories.tsx",
|
|
6997
6997
|
rules: [3, 6, 23]
|
|
6998
|
+
},
|
|
6999
|
+
{
|
|
7000
|
+
name: "ContextMenu",
|
|
7001
|
+
group: "navigation",
|
|
7002
|
+
tagline: "Context menu primitives with keyboard support and compound parts for command-style action surfaces.",
|
|
7003
|
+
props: [
|
|
7004
|
+
{ name: "open", type: "boolean", description: "Controlled open state." },
|
|
7005
|
+
{
|
|
7006
|
+
name: "onOpenChange",
|
|
7007
|
+
type: "(open: boolean) => void",
|
|
7008
|
+
description: "Open-state callback."
|
|
7009
|
+
},
|
|
7010
|
+
{ name: "value", type: "string", description: "Selected value (for controlled patterns)." }
|
|
7011
|
+
],
|
|
7012
|
+
useCases: [
|
|
7013
|
+
"Right-click action menu",
|
|
7014
|
+
"Contextual menus for rows and cards",
|
|
7015
|
+
"Nested action rows with shortcuts"
|
|
7016
|
+
],
|
|
7017
|
+
storyPath: "navigation/ContextMenu.stories.tsx",
|
|
7018
|
+
rules: [3, 6],
|
|
7019
|
+
example: `import {
|
|
7020
|
+
ContextMenu,
|
|
7021
|
+
ContextMenuTrigger,
|
|
7022
|
+
ContextMenuContent,
|
|
7023
|
+
ContextMenuItem,
|
|
7024
|
+
} from "@godxjp/ui/navigation";
|
|
7025
|
+
|
|
7026
|
+
<ContextMenu>
|
|
7027
|
+
<ContextMenuTrigger>open</ContextMenuTrigger>
|
|
7028
|
+
<ContextMenuContent>
|
|
7029
|
+
<ContextMenuItem>Edit</ContextMenuItem>
|
|
7030
|
+
<ContextMenuItem>Delete</ContextMenuItem>
|
|
7031
|
+
</ContextMenuContent>
|
|
7032
|
+
</ContextMenu>`
|
|
7033
|
+
},
|
|
7034
|
+
{
|
|
7035
|
+
name: "Menubar",
|
|
7036
|
+
group: "navigation",
|
|
7037
|
+
tagline: "Application menubar primitives (menus, sub-menus, and check/radio items).",
|
|
7038
|
+
props: [
|
|
7039
|
+
{
|
|
7040
|
+
name: "defaultValue",
|
|
7041
|
+
type: "string",
|
|
7042
|
+
description: "Uncontrolled initial selected value."
|
|
7043
|
+
},
|
|
7044
|
+
{
|
|
7045
|
+
name: "onValueChange",
|
|
7046
|
+
type: "(value: string) => void",
|
|
7047
|
+
description: "Selection callback."
|
|
7048
|
+
}
|
|
7049
|
+
],
|
|
7050
|
+
useCases: [
|
|
7051
|
+
"Top-bar application command menus",
|
|
7052
|
+
"Workspace menus with nested items",
|
|
7053
|
+
"Desktop-like navigation shells"
|
|
7054
|
+
],
|
|
7055
|
+
storyPath: "navigation/Menubar.stories.tsx",
|
|
7056
|
+
rules: [3, 6],
|
|
7057
|
+
example: `import { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem } from "@godxjp/ui/navigation";
|
|
7058
|
+
|
|
7059
|
+
<Menubar>
|
|
7060
|
+
<MenubarMenu>
|
|
7061
|
+
<MenubarTrigger>\u30D5\u30A1\u30A4\u30EB</MenubarTrigger>
|
|
7062
|
+
<MenubarContent>
|
|
7063
|
+
<MenubarItem>\u65B0\u898F\u4F5C\u6210</MenubarItem>
|
|
7064
|
+
</MenubarContent>
|
|
7065
|
+
</MenubarMenu>
|
|
7066
|
+
</Menubar>`
|
|
7067
|
+
},
|
|
7068
|
+
{
|
|
7069
|
+
name: "NavigationMenu",
|
|
7070
|
+
group: "navigation",
|
|
7071
|
+
tagline: "Horizontal navigation menu with trigger/content/link primitives and viewport support.",
|
|
7072
|
+
props: [
|
|
7073
|
+
{
|
|
7074
|
+
name: "orientation",
|
|
7075
|
+
type: '"horizontal" | "vertical"',
|
|
7076
|
+
defaultValue: '"horizontal"',
|
|
7077
|
+
description: "Main-axis arrangement for the nav menu."
|
|
7078
|
+
},
|
|
7079
|
+
{
|
|
7080
|
+
name: "defaultValue",
|
|
7081
|
+
type: "string",
|
|
7082
|
+
description: "Uncontrolled initial selected value."
|
|
7083
|
+
},
|
|
7084
|
+
{
|
|
7085
|
+
name: "onValueChange",
|
|
7086
|
+
type: "(value: string) => void",
|
|
7087
|
+
description: "Selection callback."
|
|
7088
|
+
}
|
|
7089
|
+
],
|
|
7090
|
+
useCases: ["Primary app navigation", "Sectioned marketing navigation", "Nested link groups"],
|
|
7091
|
+
storyPath: "navigation/NavigationMenu.stories.tsx",
|
|
7092
|
+
rules: [3, 6],
|
|
7093
|
+
example: `import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger } from "@godxjp/ui/navigation";
|
|
7094
|
+
|
|
7095
|
+
<NavigationMenu>
|
|
7096
|
+
<NavigationMenuList>
|
|
7097
|
+
<NavigationMenuItem>
|
|
7098
|
+
<NavigationMenuTrigger>\u30DA\u30FC\u30B8</NavigationMenuTrigger>
|
|
7099
|
+
</NavigationMenuItem>
|
|
7100
|
+
</NavigationMenuList>
|
|
7101
|
+
</NavigationMenu>`
|
|
7102
|
+
},
|
|
7103
|
+
{
|
|
7104
|
+
name: "ResizablePanel",
|
|
7105
|
+
group: "layout",
|
|
7106
|
+
tagline: "Resizable panel group/child/handle primitives from react-resizable-panels.",
|
|
7107
|
+
props: [
|
|
7108
|
+
{ name: "id", type: "string", description: "Panel identifier for persistence." },
|
|
7109
|
+
{ name: "defaultSize", type: "number", description: "Initial panel size (percent/units)." },
|
|
7110
|
+
{ name: "minSize", type: "number", description: "Minimum size constraint." },
|
|
7111
|
+
{ name: "maxSize", type: "number", description: "Maximum size constraint." }
|
|
7112
|
+
],
|
|
7113
|
+
useCases: ["Split-pane layouts", "Resizable sidebars", "Code editors with adjustable zones"],
|
|
7114
|
+
storyPath: "layout/ResizablePanel.stories.tsx",
|
|
7115
|
+
rules: [3, 6],
|
|
7116
|
+
example: `import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@godxjp/ui/layout";
|
|
7117
|
+
|
|
7118
|
+
<ResizablePanelGroup>
|
|
7119
|
+
<ResizablePanel>Panel A</ResizablePanel>
|
|
7120
|
+
<ResizableHandle />
|
|
7121
|
+
<ResizablePanel>Panel B</ResizablePanel>
|
|
7122
|
+
</ResizablePanelGroup>`
|
|
7123
|
+
},
|
|
7124
|
+
{
|
|
7125
|
+
name: "Carousel",
|
|
7126
|
+
group: "data-display",
|
|
7127
|
+
tagline: "Embla-backed carousel primitives including previous/next controls and context API.",
|
|
7128
|
+
props: [
|
|
7129
|
+
{
|
|
7130
|
+
name: "opts",
|
|
7131
|
+
type: "Parameters<typeof useEmblaCarousel>[0]",
|
|
7132
|
+
description: "Embla options."
|
|
7133
|
+
},
|
|
7134
|
+
{
|
|
7135
|
+
name: "plugins",
|
|
7136
|
+
type: "Parameters<typeof useEmblaCarousel>[1]",
|
|
7137
|
+
description: "Embla plugins."
|
|
7138
|
+
},
|
|
7139
|
+
{
|
|
7140
|
+
name: "setApi",
|
|
7141
|
+
type: "(api: CarouselApi) => void",
|
|
7142
|
+
description: "Receive carousel API for custom logic."
|
|
7143
|
+
}
|
|
7144
|
+
],
|
|
7145
|
+
useCases: ["Feature cards", "Image galleries", "Horizontal stepping lists"],
|
|
7146
|
+
storyPath: "data-display/Carousel.stories.tsx",
|
|
7147
|
+
rules: [3, 6],
|
|
7148
|
+
example: `import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from "@godxjp/ui/data-display";
|
|
7149
|
+
|
|
7150
|
+
<Carousel>
|
|
7151
|
+
<CarouselContent>
|
|
7152
|
+
<CarouselItem>1</CarouselItem>
|
|
7153
|
+
<CarouselItem>2</CarouselItem>
|
|
7154
|
+
</CarouselContent>
|
|
7155
|
+
<CarouselPrevious />
|
|
7156
|
+
<CarouselNext />
|
|
7157
|
+
</Carousel>`
|
|
7158
|
+
},
|
|
7159
|
+
{
|
|
7160
|
+
name: "Combobox",
|
|
7161
|
+
group: "data-entry",
|
|
7162
|
+
tagline: "Single-select searchable combobox composed from Popover + Command + Button.",
|
|
7163
|
+
props: [
|
|
7164
|
+
{
|
|
7165
|
+
name: "options",
|
|
7166
|
+
type: "{ value: string; label: string }[]",
|
|
7167
|
+
required: true,
|
|
7168
|
+
description: "Available selection entries."
|
|
7169
|
+
},
|
|
7170
|
+
{ name: "value", type: "string", description: "Controlled selected value." },
|
|
7171
|
+
{ name: "defaultValue", type: "string", description: "Uncontrolled initial value." },
|
|
7172
|
+
{
|
|
7173
|
+
name: "onValueChange",
|
|
7174
|
+
type: "(value: string) => void",
|
|
7175
|
+
description: "Selection callback."
|
|
7176
|
+
},
|
|
7177
|
+
{ name: "placeholder", type: "string", description: "Trigger placeholder." },
|
|
7178
|
+
{ name: "searchPlaceholder", type: "string", description: "Input placeholder in popover." },
|
|
7179
|
+
{ name: "emptyText", type: "string", description: "Fallback when there are no matches." }
|
|
7180
|
+
],
|
|
7181
|
+
useCases: ["Searchable single-select", "Lookup pickers", "Static option lists"],
|
|
7182
|
+
storyPath: "data-entry/Combobox.stories.tsx",
|
|
7183
|
+
rules: [3, 6],
|
|
7184
|
+
example: `import { Combobox } from "@godxjp/ui/data-entry";
|
|
7185
|
+
|
|
7186
|
+
<Combobox
|
|
7187
|
+
options={[{ value: "a", label: "A" }, { value: "b", label: "B" }]}
|
|
7188
|
+
onValueChange={(value) => console.log(value)}
|
|
7189
|
+
/>`
|
|
7190
|
+
},
|
|
7191
|
+
{
|
|
7192
|
+
name: "TimeInput",
|
|
7193
|
+
group: "data-entry",
|
|
7194
|
+
tagline: "Masking HH:mm input with validation and optional minute step quantization.",
|
|
7195
|
+
props: [
|
|
7196
|
+
{ name: "value", type: "string", description: "Controlled HH:mm value." },
|
|
7197
|
+
{
|
|
7198
|
+
name: "defaultValue",
|
|
7199
|
+
type: "string",
|
|
7200
|
+
description: "Uncontrolled initial HH:mm value."
|
|
7201
|
+
},
|
|
7202
|
+
{
|
|
7203
|
+
name: "onValueChange",
|
|
7204
|
+
type: "(value: string) => void",
|
|
7205
|
+
description: "Validated value callback."
|
|
7206
|
+
},
|
|
7207
|
+
{ name: "step", type: "number", defaultValue: "1", description: "Minute step." },
|
|
7208
|
+
{ name: "name", type: "string", description: "Form field name." }
|
|
7209
|
+
],
|
|
7210
|
+
useCases: ["Time filters", "Schedule pickers (calendar-free)", "HH:mm-only forms"],
|
|
7211
|
+
storyPath: "data-entry/TimeInput.stories.tsx",
|
|
7212
|
+
rules: [3, 6],
|
|
7213
|
+
example: `import { TimeInput } from "@godxjp/ui/data-entry";
|
|
7214
|
+
|
|
7215
|
+
<TimeInput value="09:00" step={15} onValueChange={(time) => console.log(time)} />`
|
|
6998
7216
|
}
|
|
6999
7217
|
];
|
|
7000
7218
|
function findComponent(name) {
|
|
@@ -7067,26 +7285,63 @@ var PROP_VOCABULARY = [
|
|
|
7067
7285
|
];
|
|
7068
7286
|
function findVocab(name) {
|
|
7069
7287
|
const normalized = name.trim().toLowerCase().replace(/prop(?:<.*>)?$/i, "");
|
|
7070
|
-
return PROP_VOCABULARY.find(
|
|
7288
|
+
return PROP_VOCABULARY.find(
|
|
7289
|
+
(v) => v.name.toLowerCase().replace(/prop(?:<.*>)?$/i, "") === normalized
|
|
7290
|
+
);
|
|
7071
7291
|
}
|
|
7072
7292
|
|
|
7073
7293
|
// src/data/tokens.ts
|
|
7074
7294
|
var TOKENS = [
|
|
7075
|
-
{
|
|
7076
|
-
|
|
7295
|
+
{
|
|
7296
|
+
name: "--wa-*",
|
|
7297
|
+
category: "primitive",
|
|
7298
|
+
tier: "primitive",
|
|
7299
|
+
role: "Neutral decorative Japanese accent primitives for charts/tags/decoration only."
|
|
7300
|
+
},
|
|
7301
|
+
{
|
|
7302
|
+
name: "--chart-1..6",
|
|
7303
|
+
category: "primitive",
|
|
7304
|
+
tier: "primitive",
|
|
7305
|
+
role: "Neutral decorative chart primitives; @theme chart colors reference these tokens."
|
|
7306
|
+
},
|
|
7077
7307
|
{ name: "--space-0..12", category: "primitive", tier: "primitive", role: "Raw spacing scale." },
|
|
7078
|
-
{
|
|
7308
|
+
{
|
|
7309
|
+
name: "--font-size-*",
|
|
7310
|
+
category: "primitive",
|
|
7311
|
+
tier: "primitive",
|
|
7312
|
+
role: "Raw typography scale."
|
|
7313
|
+
},
|
|
7079
7314
|
{ name: "--primary", category: "semantic", tier: "semantic", role: "Brand/action color role." },
|
|
7080
7315
|
{ name: "--success", category: "semantic", tier: "semantic", role: "Success status role." },
|
|
7081
7316
|
{ name: "--warning", category: "semantic", tier: "semantic", role: "Warning status role." },
|
|
7082
|
-
{
|
|
7317
|
+
{
|
|
7318
|
+
name: "--destructive",
|
|
7319
|
+
category: "semantic",
|
|
7320
|
+
tier: "semantic",
|
|
7321
|
+
role: "Destructive/error status role."
|
|
7322
|
+
},
|
|
7083
7323
|
{ name: "--info", category: "semantic", tier: "semantic", role: "Information status role." },
|
|
7084
7324
|
{ name: "--attention", category: "semantic", tier: "semantic", role: "Attention status role." },
|
|
7085
7325
|
{ name: "--badge-space-*", category: "component", tier: "component", role: "Badge spacing." },
|
|
7086
|
-
{
|
|
7087
|
-
|
|
7326
|
+
{
|
|
7327
|
+
name: "--card-*",
|
|
7328
|
+
category: "component",
|
|
7329
|
+
tier: "component",
|
|
7330
|
+
role: "Card surface, border, spacing, and typography."
|
|
7331
|
+
},
|
|
7332
|
+
{
|
|
7333
|
+
name: "--control-*",
|
|
7334
|
+
category: "component",
|
|
7335
|
+
tier: "component",
|
|
7336
|
+
role: "Shared form control heights, padding, icons, and focus chrome."
|
|
7337
|
+
},
|
|
7088
7338
|
{ name: "--table-*", category: "component", tier: "component", role: "Table row/cell sizing." },
|
|
7089
|
-
{
|
|
7339
|
+
{
|
|
7340
|
+
name: "--dialog-* / --alert-* / --skeleton-*",
|
|
7341
|
+
category: "component",
|
|
7342
|
+
tier: "component",
|
|
7343
|
+
role: "Feedback component sizing and spacing."
|
|
7344
|
+
}
|
|
7090
7345
|
];
|
|
7091
7346
|
function tokensByCategory(category) {
|
|
7092
7347
|
return TOKENS.filter((t) => t.category === category);
|
|
@@ -7094,46 +7349,206 @@ function tokensByCategory(category) {
|
|
|
7094
7349
|
|
|
7095
7350
|
// src/data/rules.ts
|
|
7096
7351
|
var CARDINAL_RULES = [
|
|
7097
|
-
{
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
{
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
{
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
{
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
{
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
{
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
{
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
{
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7352
|
+
{
|
|
7353
|
+
number: 1,
|
|
7354
|
+
title: "Storybook is mandatory",
|
|
7355
|
+
body: "Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."
|
|
7356
|
+
},
|
|
7357
|
+
{
|
|
7358
|
+
number: 2,
|
|
7359
|
+
title: "Tokens, not utilities",
|
|
7360
|
+
body: "Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden. (ADR-0003)"
|
|
7361
|
+
},
|
|
7362
|
+
{
|
|
7363
|
+
number: 3,
|
|
7364
|
+
title: "Radix for interactive primitives",
|
|
7365
|
+
body: "Anything with keyboard / ARIA / portal wraps the relevant Radix primitive. (ADR-0001)"
|
|
7366
|
+
},
|
|
7367
|
+
{
|
|
7368
|
+
number: 4,
|
|
7369
|
+
title: "shadcn-style ownership",
|
|
7370
|
+
body: "Primitives are thin wrappers; consumers can fork the source in place. (ADR-0002)"
|
|
7371
|
+
},
|
|
7372
|
+
{
|
|
7373
|
+
number: 5,
|
|
7374
|
+
title: "One i18next singleton",
|
|
7375
|
+
body: "`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`. (ADR-0004)"
|
|
7376
|
+
},
|
|
7377
|
+
{
|
|
7378
|
+
number: 6,
|
|
7379
|
+
title: "WCAG 2.1 AA baseline",
|
|
7380
|
+
body: "Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."
|
|
7381
|
+
},
|
|
7382
|
+
{
|
|
7383
|
+
number: 7,
|
|
7384
|
+
title: "SemVer 2.0 + Keep a Changelog 1.1",
|
|
7385
|
+
body: "Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."
|
|
7386
|
+
},
|
|
7387
|
+
{
|
|
7388
|
+
number: 8,
|
|
7389
|
+
title: "Inclusive naming",
|
|
7390
|
+
body: "`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."
|
|
7391
|
+
},
|
|
7392
|
+
{
|
|
7393
|
+
number: 9,
|
|
7394
|
+
title: "No marketing speak",
|
|
7395
|
+
body: 'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'
|
|
7396
|
+
},
|
|
7397
|
+
{
|
|
7398
|
+
number: 10,
|
|
7399
|
+
title: "English is canonical for docs",
|
|
7400
|
+
body: "Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."
|
|
7401
|
+
},
|
|
7402
|
+
{
|
|
7403
|
+
number: 11,
|
|
7404
|
+
title: "Submodule discipline",
|
|
7405
|
+
body: "Two-PR workflow: (1) submodule PR \u2192 `main`, (2) umbrella PR \u2192 bump pin. Never push a pin to a SHA not on the submodule remote."
|
|
7406
|
+
},
|
|
7407
|
+
{
|
|
7408
|
+
number: 12,
|
|
7409
|
+
title: "Branch + PR workflow",
|
|
7410
|
+
body: "`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."
|
|
7411
|
+
},
|
|
7412
|
+
{
|
|
7413
|
+
number: 13,
|
|
7414
|
+
title: "TypeScript strict",
|
|
7415
|
+
body: "Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."
|
|
7416
|
+
},
|
|
7417
|
+
{
|
|
7418
|
+
number: 14,
|
|
7419
|
+
title: "Every third-party library is shadcn / Radix-recommended",
|
|
7420
|
+
body: "Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."
|
|
7421
|
+
},
|
|
7422
|
+
{
|
|
7423
|
+
number: 15,
|
|
7424
|
+
title: "No `@apply` re-encoding tokens",
|
|
7425
|
+
body: "Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."
|
|
7426
|
+
},
|
|
7427
|
+
{
|
|
7428
|
+
number: 16,
|
|
7429
|
+
title: "CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",
|
|
7430
|
+
body: "A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."
|
|
7431
|
+
},
|
|
7432
|
+
{
|
|
7433
|
+
number: 17,
|
|
7434
|
+
title: "`src/stories/` \u2194 `src/components/` parity",
|
|
7435
|
+
body: "Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."
|
|
7436
|
+
},
|
|
7437
|
+
{
|
|
7438
|
+
number: 18,
|
|
7439
|
+
title: "`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",
|
|
7440
|
+
body: "Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."
|
|
7441
|
+
},
|
|
7442
|
+
{
|
|
7443
|
+
number: 19,
|
|
7444
|
+
title: "No service-specific anything",
|
|
7445
|
+
body: '`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'
|
|
7446
|
+
},
|
|
7447
|
+
{
|
|
7448
|
+
number: 20,
|
|
7449
|
+
title: 'No "platform-only" exports',
|
|
7450
|
+
body: "Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."
|
|
7451
|
+
},
|
|
7452
|
+
{
|
|
7453
|
+
number: 21,
|
|
7454
|
+
title: "Every component honours every theme axis",
|
|
7455
|
+
body: "`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values. Verify every PR via the Storybook toolbar sweep."
|
|
7456
|
+
},
|
|
7457
|
+
{
|
|
7458
|
+
number: 22,
|
|
7459
|
+
title: "100% match to the design canon",
|
|
7460
|
+
body: 'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'
|
|
7461
|
+
},
|
|
7462
|
+
{
|
|
7463
|
+
number: 23,
|
|
7464
|
+
title: "Concept-first prop API",
|
|
7465
|
+
body: "One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."
|
|
7466
|
+
},
|
|
7467
|
+
{
|
|
7468
|
+
number: 24,
|
|
7469
|
+
title: "Mobile-first",
|
|
7470
|
+
body: "Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."
|
|
7471
|
+
},
|
|
7472
|
+
{
|
|
7473
|
+
number: 25,
|
|
7474
|
+
title: "Stories are docs; UI is the primitive",
|
|
7475
|
+
body: "When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."
|
|
7476
|
+
},
|
|
7477
|
+
{
|
|
7478
|
+
number: 26,
|
|
7479
|
+
title: "Library isolation",
|
|
7480
|
+
body: "`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."
|
|
7481
|
+
},
|
|
7482
|
+
{
|
|
7483
|
+
number: 27,
|
|
7484
|
+
title: "Per-group folder structure",
|
|
7485
|
+
body: "Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."
|
|
7486
|
+
},
|
|
7487
|
+
{
|
|
7488
|
+
number: 28,
|
|
7489
|
+
title: "`src/` folder taxonomy",
|
|
7490
|
+
body: "Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."
|
|
7491
|
+
},
|
|
7492
|
+
{
|
|
7493
|
+
number: 29,
|
|
7494
|
+
title: "Stories consume framework primitives only",
|
|
7495
|
+
body: "No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."
|
|
7496
|
+
},
|
|
7497
|
+
{
|
|
7498
|
+
number: 30,
|
|
7499
|
+
title: "Story `render` returns JSX directly",
|
|
7500
|
+
body: "No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."
|
|
7501
|
+
},
|
|
7502
|
+
{
|
|
7503
|
+
number: 31,
|
|
7504
|
+
title: "No nested wrapper / convenience primitives",
|
|
7505
|
+
body: "One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."
|
|
7506
|
+
},
|
|
7507
|
+
{
|
|
7508
|
+
number: 32,
|
|
7509
|
+
title: "No redundant props",
|
|
7510
|
+
body: "Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."
|
|
7511
|
+
},
|
|
7512
|
+
{
|
|
7513
|
+
number: 33,
|
|
7514
|
+
title: "Stories / source / docs name-synchronized",
|
|
7515
|
+
body: "No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it."
|
|
7516
|
+
},
|
|
7517
|
+
{
|
|
7518
|
+
number: 34,
|
|
7519
|
+
title: "Storybook source panel = real, copy-paste-ready code",
|
|
7520
|
+
body: 'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.'
|
|
7521
|
+
},
|
|
7522
|
+
{
|
|
7523
|
+
number: 35,
|
|
7524
|
+
title: "Status chips never wrap",
|
|
7525
|
+
body: "A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."
|
|
7526
|
+
},
|
|
7527
|
+
{
|
|
7528
|
+
number: 36,
|
|
7529
|
+
title: "Badge tone/icon are the colour escape hatch",
|
|
7530
|
+
body: "`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."
|
|
7531
|
+
},
|
|
7532
|
+
{
|
|
7533
|
+
number: 37,
|
|
7534
|
+
title: "DataTable is full-width \u2014 never inside a narrow grid column",
|
|
7535
|
+
body: "A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"
|
|
7536
|
+
},
|
|
7537
|
+
{
|
|
7538
|
+
number: 38,
|
|
7539
|
+
title: "FilterBar stays OUT of CardContent flush",
|
|
7540
|
+
body: "`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."
|
|
7541
|
+
},
|
|
7542
|
+
{
|
|
7543
|
+
number: 39,
|
|
7544
|
+
title: "Long text columns get an explicit width",
|
|
7545
|
+
body: "For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."
|
|
7546
|
+
},
|
|
7547
|
+
{
|
|
7548
|
+
number: 40,
|
|
7549
|
+
title: "Pages are mobile-first",
|
|
7550
|
+
body: "Author and verify every page at 320\u2013390px FIRST. Spacing comes only from `Stack` / `Inline` `gap` + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout. Wide tables scroll horizontally on small screens (don't force-fit them); dialogs and sheets are full-height on mobile. Touch targets \u2265 44\xD744px."
|
|
7551
|
+
}
|
|
7137
7552
|
];
|
|
7138
7553
|
function findRule(num) {
|
|
7139
7554
|
return CARDINAL_RULES.find((r) => r.number === num);
|
|
@@ -7144,7 +7559,17 @@ var PATTERNS = [
|
|
|
7144
7559
|
{
|
|
7145
7560
|
name: "common-fixes",
|
|
7146
7561
|
tagline: "Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",
|
|
7147
|
-
tags: [
|
|
7562
|
+
tags: [
|
|
7563
|
+
"fixes",
|
|
7564
|
+
"migration",
|
|
7565
|
+
"bug",
|
|
7566
|
+
"cardstat",
|
|
7567
|
+
"statusbadge",
|
|
7568
|
+
"datatable",
|
|
7569
|
+
"sidebar",
|
|
7570
|
+
"gotcha",
|
|
7571
|
+
"review"
|
|
7572
|
+
],
|
|
7148
7573
|
code: `// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7149
7574
|
// 0) \u2605 MOST COMMON: <Card> body has NO padding (content is flush against the edges)
|
|
7150
7575
|
// Cause: the bare <Card> has ZERO inner padding \u2014 it MUST contain <CardContent>.
|
|
@@ -8216,11 +8641,13 @@ function routeTask(task) {
|
|
|
8216
8641
|
"Generate design image first \u2192 analyze \u2192 implement."
|
|
8217
8642
|
);
|
|
8218
8643
|
if (matches.length === 0) {
|
|
8219
|
-
return [
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8644
|
+
return [
|
|
8645
|
+
{
|
|
8646
|
+
skill: "taste",
|
|
8647
|
+
section: "<see whenToUse>",
|
|
8648
|
+
why: `No keyword match for "${task}". Default to the "taste" baseline \u2014 see whenToUse for sections.`
|
|
8649
|
+
}
|
|
8650
|
+
];
|
|
8224
8651
|
}
|
|
8225
8652
|
return matches;
|
|
8226
8653
|
}
|
|
@@ -9665,7 +10092,11 @@ ${r.body}
|
|
|
9665
10092
|
`;
|
|
9666
10093
|
}
|
|
9667
10094
|
if (uri === "godx-ui://patterns") {
|
|
9668
|
-
return JSON.stringify(
|
|
10095
|
+
return JSON.stringify(
|
|
10096
|
+
PATTERNS.map(({ name, tagline, tags }) => ({ name, tagline, tags })),
|
|
10097
|
+
null,
|
|
10098
|
+
2
|
|
10099
|
+
);
|
|
9669
10100
|
}
|
|
9670
10101
|
if (uri.startsWith("godx-ui://patterns/")) {
|
|
9671
10102
|
const name = uri.slice("godx-ui://patterns/".length);
|