@godxjp/ui 30.8.0 → 31.0.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.
Files changed (56) hide show
  1. package/README.md +25 -2
  2. package/agent/START-HERE.md +1 -1
  3. package/agent/components/PrefetchLink.json +3 -3
  4. package/agent/components-index.json +1 -1
  5. package/agent/components.json +3 -3
  6. package/agent/index.json +2 -2
  7. package/agent/llms.txt +3 -3
  8. package/agent/patterns/settings-page-responsive.json +1 -1
  9. package/agent/patterns.json +1 -1
  10. package/dist/components/query/index.d.ts +0 -2
  11. package/dist/components/query/index.js +0 -2
  12. package/dist/components/react-router/index.d.ts +2 -0
  13. package/dist/components/react-router/index.js +4 -0
  14. package/dist/contracts/measurement.json +1 -1
  15. package/dist/styles/alert-layout.css +4 -2
  16. package/dist/styles/layers.json +1182 -0
  17. package/dist/styles/vendor-day-picker.css +2 -0
  18. package/dist/styles/vendor-sonner.css +2 -0
  19. package/docs/CONSUMER-RULES.md +1 -1
  20. package/docs/data-display/permission-matrix.tsx +30 -28
  21. package/docs/data-entry/cascader.tsx +22 -20
  22. package/docs/data-entry/date-picker.tsx +47 -43
  23. package/docs/data-entry/form-field/examples/a11y-contract.tsx +80 -76
  24. package/docs/data-entry/form-field/examples/create-form.tsx +33 -31
  25. package/docs/data-entry/form-field/index.tsx +61 -57
  26. package/docs/data-entry/form.tsx +112 -106
  27. package/docs/data-entry/input.tsx +297 -270
  28. package/docs/data-entry/label.tsx +11 -9
  29. package/docs/data-entry/number-input.tsx +86 -78
  30. package/docs/data-entry/radio-group.tsx +33 -26
  31. package/docs/data-entry/rating.tsx +29 -25
  32. package/docs/data-entry/select-matrix.tsx +49 -47
  33. package/docs/data-entry/select.tsx +38 -35
  34. package/docs/data-entry/slider.tsx +45 -41
  35. package/docs/data-entry/textarea.tsx +44 -42
  36. package/docs/data-entry/time-picker.tsx +51 -47
  37. package/docs/feedback/sheet.tsx +84 -79
  38. package/docs/foundation/theme-editor.tsx +133 -124
  39. package/docs/general/button/examples/form-actions.tsx +17 -15
  40. package/docs/layout/auth-recovery/examples/password-recovery.tsx +31 -29
  41. package/docs/layout/auth-shell-registration.tsx +75 -72
  42. package/docs/layout/auth-shell-variants.tsx +55 -51
  43. package/docs/layout/space-compact.tsx +11 -8
  44. package/docs/{query → react-router}/prefetch-link.tsx +1 -1
  45. package/docs/showcase/caimono-price-comparison.tsx +59 -51
  46. package/docs/showcase/case3-approval-workflow.tsx +27 -25
  47. package/docs/showcase/case4-login.tsx +37 -34
  48. package/docs/showcase/permission-matrix.tsx +44 -42
  49. package/docs/showcase/theme-customization.tsx +108 -99
  50. package/package.json +10 -3
  51. package/scripts/cli.mjs +15 -0
  52. package/scripts/consumer-rule.md +14 -0
  53. package/scripts/prune-css.mjs +221 -0
  54. package/scripts/ui-audit.mjs +99 -2
  55. /package/dist/components/{query → react-router}/prefetch-link.d.ts +0 -0
  56. /package/dist/components/{query → react-router}/prefetch-link.js +0 -0
package/README.md CHANGED
@@ -112,7 +112,8 @@ A value is defined **once** as a CSS var (`--primary`), mapped to a utility in t
112
112
  | **Data Entry** | `@godxjp/ui/data-entry` | `Input`, `Select`, `FormField`, `Field`, `DatePicker`, `TimePicker`, `Combobox`, `Switch`, `Toggle`, `Upload`, `Cascader`, `TreeSelect`, `ColorPicker`, `Slider`, `PasswordInput`, `PasswordStrength`, `InputOTP`, `Rating`, `TagInput` |
113
113
  | **Data Display** | `@godxjp/ui/data-display` | `Table`, `DataTable`, `Card`, `StatCard`, `Badge`, `Avatar`, `Descriptions`, `Timeline`, `EmptyState`, `Progress`, `QrCode`, `CredentialReveal`, `Accordion`, `HoverCard`, `Carousel`, `Popover`, `Collapsible` |
114
114
  | **Feedback** | `@godxjp/ui/feedback` | `Dialog`, `AlertDialog`, `Sheet` (side), `Drawer` (bottom-sheet), `Toast`, `Skeleton`, `Alert`, `Tooltip` |
115
- | **Query** | `@godxjp/ui/query` | `DataState`, `InfiniteQueryState`, `PrefetchLink` (adapter subpath — pulls TanStack Query) |
115
+ | **Query** | `@godxjp/ui/query` | `DataState`, `InfiniteQueryState` (adapter subpath — pulls TanStack Query; no router) |
116
+ | **React Router** | `@godxjp/ui/react-router` | `PrefetchLink` — imports the optional `react-router-dom` peer, so it is kept out of `/query` (gh#996) |
116
117
  | **Navigation** | `@godxjp/ui/navigation` | `Tabs`, `Toolbar`, `DropdownMenu`, `Steps`, `Pagination`, `Breadcrumb`, `AppSettingPicker` |
117
118
  | **App** | `@godxjp/ui/app` | `AppProvider`, `useDateTime` (adapter — i18n/datetime singleton) |
118
119
  | **Datetime** | `@godxjp/ui/datetime` | `formatDate` (mandatory for display) |
@@ -214,7 +215,29 @@ slices below roughly 620 distinct characters. Full table and reasoning in
214
215
  > missing layer fails silently: menus render with no background, rows with no
215
216
  > height. `styles`, `styles/core`, `styles/core-with-fallbacks` and
216
217
  > `styles/core-with-jis-level1` are the four supported entries; the runtime `visual-audit` flags a
217
- > page whose layers are incomplete (`css-layers-missing`).
218
+ > page whose layers are incomplete (`css-layers-missing`). The one supported way to ship LESS
219
+ > than `core` is `prune-css` below — the tool slices along the dependency graph the package
220
+ > ships, so it cannot forget a layer the way a hand-picked list does.
221
+
222
+ ### prune-css — ship only the component layers you use (gh#971)
223
+
224
+ `core` still carries every component's layers (~64 KB gzip for all ~165). If that remainder
225
+ matters, let the package slice it:
226
+
227
+ ```bash
228
+ npx @godxjp/ui prune-css resources/js --out resources/css/godx-ui.css # --fonts for the bundled faces
229
+ ```
230
+
231
+ It scans your sources for `@godxjp/ui` imports, resolves the CSS layer dependency closure from
232
+ the graph shipped in `dist/styles/layers.json` (what each component's internals render is part
233
+ of the graph — a `DataTable` still gets its dropdown and pagination surfaces), and emits a file
234
+ that imports the foundation plus only the needed layers, in `styles/index.css`'s exact order.
235
+ Import that file INSTEAD of `@godxjp/ui/styles`. Defaults mirror `styles/core` (no
236
+ `@font-face`); the sonner / react-day-picker vendor sheets come along only when a used
237
+ component renders them. **Re-run it whenever the set of components you use changes and after
238
+ every upgrade** — the emitted header says so, and the tool refuses to run against a manifest
239
+ from a different package version. Measured on gh#971's 15-component app: 368 KB gzip
240
+ (`styles`) → 86 KB (`core`) → 75 KB pruned; a small 8-component app lands at 55 KB.
218
241
 
219
242
  ## Golden ratio (φ ≈ 1.618)
220
243
 
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 30.8.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.0.2.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
@@ -1,7 +1,7 @@
1
1
  {
2
- "example": "import { PrefetchLink } from \"@godxjp/ui/query\";\nimport { fetchInvoice } from \"@/api/invoices\";\n\n// Inside a table row or list item:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n staleTime={60_000}\n className=\"font-medium hover:underline\"\n>\n {invoice.number}\n</PrefetchLink>\n\n// Disable prefetch for rows where data is not yet stable:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n prefetchOn=\"none\"\n>\n {invoice.number}\n</PrefetchLink>",
2
+ "example": "import { PrefetchLink } from \"@godxjp/ui/react-router\";\nimport { fetchInvoice } from \"@/api/invoices\";\n\n// Inside a table row or list item:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n staleTime={60_000}\n className=\"font-medium hover:underline\"\n>\n {invoice.number}\n</PrefetchLink>\n\n// Disable prefetch for rows where data is not yet stable:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n prefetchOn=\"none\"\n>\n {invoice.number}\n</PrefetchLink>",
3
3
  "group": "data-display",
4
- "importPath": "@godxjp/ui/query",
4
+ "importPath": "@godxjp/ui/react-router",
5
5
  "name": "PrefetchLink",
6
6
  "props": [
7
7
  {
@@ -46,7 +46,7 @@
46
46
  31
47
47
  ],
48
48
  "storyPath": "data-display/PrefetchLink.stories.tsx",
49
- "tagline": "React Router Link that fires prefetchQuery on hover/focus so detail pages feel instant — requires a TanStack Query QueryClient in context.",
49
+ "tagline": "React Router Link that fires prefetchQuery on hover/focus so detail pages feel instant — requires a TanStack Query QueryClient in context. Imported from `@godxjp/ui/react-router` (not `/query`) because it imports the optional `react-router-dom` peer; in `/query` it broke every router-less app's build (gh#996).",
50
50
  "usage": [
51
51
  "DO: provide a queryKey that exactly matches the destination page's useQuery key — a mismatch means the prefetch populates a different cache slot and the page still loads cold.",
52
52
  "DO: keep queryFn lightweight and side-effect-free; it runs speculatively on hover. Avoid mutations or write operations inside queryFn.",
@@ -590,7 +590,7 @@
590
590
  {
591
591
  "group": "data-display",
592
592
  "name": "PrefetchLink",
593
- "tagline": "React Router Link that fires prefetchQuery on hover/focus so detail pages feel instant — requires a TanStack Query QueryClient in context."
593
+ "tagline": "React Router Link that fires prefetchQuery on hover/focus so detail pages feel instant — requires a TanStack Query QueryClient in context. Imported from `@godxjp/ui/react-router` (not `/query`) because it imports the optional `react-router-dom` peer; in `/query` it broke every router-less app's build (gh#996)."
594
594
  },
595
595
  {
596
596
  "group": "data-display",
@@ -11201,9 +11201,9 @@
11201
11201
  ]
11202
11202
  },
11203
11203
  {
11204
- "example": "import { PrefetchLink } from \"@godxjp/ui/query\";\nimport { fetchInvoice } from \"@/api/invoices\";\n\n// Inside a table row or list item:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n staleTime={60_000}\n className=\"font-medium hover:underline\"\n>\n {invoice.number}\n</PrefetchLink>\n\n// Disable prefetch for rows where data is not yet stable:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n prefetchOn=\"none\"\n>\n {invoice.number}\n</PrefetchLink>",
11204
+ "example": "import { PrefetchLink } from \"@godxjp/ui/react-router\";\nimport { fetchInvoice } from \"@/api/invoices\";\n\n// Inside a table row or list item:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n staleTime={60_000}\n className=\"font-medium hover:underline\"\n>\n {invoice.number}\n</PrefetchLink>\n\n// Disable prefetch for rows where data is not yet stable:\n<PrefetchLink\n to={`/invoices/${invoice.id}`}\n queryKey={[\"invoice\", invoice.id]}\n queryFn={() => fetchInvoice(invoice.id)}\n prefetchOn=\"none\"\n>\n {invoice.number}\n</PrefetchLink>",
11205
11205
  "group": "data-display",
11206
- "importPath": "@godxjp/ui/query",
11206
+ "importPath": "@godxjp/ui/react-router",
11207
11207
  "name": "PrefetchLink",
11208
11208
  "props": [
11209
11209
  {
@@ -11248,7 +11248,7 @@
11248
11248
  31
11249
11249
  ],
11250
11250
  "storyPath": "data-display/PrefetchLink.stories.tsx",
11251
- "tagline": "React Router Link that fires prefetchQuery on hover/focus so detail pages feel instant — requires a TanStack Query QueryClient in context.",
11251
+ "tagline": "React Router Link that fires prefetchQuery on hover/focus so detail pages feel instant — requires a TanStack Query QueryClient in context. Imported from `@godxjp/ui/react-router` (not `/query`) because it imports the optional `react-router-dom` peer; in `/query` it broke every router-less app's build (gh#996).",
11252
11252
  "usage": [
11253
11253
  "DO: provide a queryKey that exactly matches the destination page's useQuery key — a mismatch means the prefetch populates a different cache slot and the page still loads cold.",
11254
11254
  "DO: keep queryFn lightweight and side-effect-free; it runs speculatively on hover. Avoid mutations or write operations inside queryFn.",
package/agent/index.json CHANGED
@@ -48,7 +48,7 @@
48
48
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v30.8.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.0.2/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
@@ -62,5 +62,5 @@
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "30.8.0"
65
+ "version": "31.0.2"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
3
  > A Japanese-enterprise React design system: 175 components, 2074 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 30.8.0.
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.0.2.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@30.8.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.0.2`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v30.8.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.0.2/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -4,7 +4,7 @@
4
4
  "settings-page",
5
5
  "settings-layout"
6
6
  ],
7
- "code": "// Research basis: GitHub, Google Account, Microsoft Account, Atlassian Account settings +\n// the IBM Carbon form grid. Every destination is a URL, so deep links and browser back/forward\n// work for free and the active item is derived from the route — never from local tab state.\n//\n// Desktop (>= lg): persistent vertical local nav on the left + bounded content on the right.\n// Mobile (< lg): the SAME routes become a horizontal, scrollable tab strip above the content.\n// Styling uses ONLY real semantic token classes (bg-muted / text-foreground / text-muted-foreground)\n// and responsive/visibility utilities — the library ships NO bespoke settings shell/nav CSS classes.\nimport { NavLink, Outlet, Navigate } from \"react-router-dom\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { FormField, Input } from \"@godxjp/ui/data-entry\";\nimport { Button } from \"@godxjp/ui/general\";\n\nconst SECTIONS = [\n { to: \"general\", label: \"基本情報\" },\n { to: \"security\", label: \"セキュリティ\" },\n { to: \"notifications\", label: \"通知\" },\n] as const;\n\n// One link renderer for BOTH breakpoints. NavLink gives us the active state (and sets\n// aria-current=\"page\") straight from the URL; semantic tokens only, no invented classes.\nconst link = ({ isActive }: { isActive: boolean }) =>\n [\n \"rounded-md px-3 py-2 text-sm no-underline transition-colors whitespace-nowrap\",\n isActive\n ? \"bg-muted text-foreground font-medium\"\n : \"text-muted-foreground hover:text-foreground\",\n ].join(\" \");\n\nexport function SettingsLayout() {\n return (\n <Flex direction=\"col\" gap=\"lg\" className=\"lg:flex-row lg:items-start\">\n {/* < lg: horizontal, scrollable route tabs. Flex owns the row and the gap — hideFrom /\n hideBelow own the breakpoint, so neither nav needs a hand-rolled flex or gap utility. */}\n <Flex as=\"nav\" aria-label=\"設定\" gap={1} hideFrom=\"lg\" className=\"overflow-x-auto\">\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* >= lg: persistent vertical local nav (bounded width, does not shrink). */}\n <Flex as=\"nav\" aria-label=\"設定\" direction=\"col\" gap={1} hideBelow=\"lg\" width={224} shrink={false}>\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* Bounded content region (~42rem) — the routed section renders here. min-w-0 lets it shrink. */}\n <main className=\"min-w-0 max-w-2xl flex-1\"><Outlet /></main>\n </Flex>\n );\n}\n\n// A settings section. Horizontal label/control rows on desktop (collapse to stacked on mobile),\n// controls bounded to a semantic width. FormField OWNS the label↔control relationship — do NOT\n// hand-roll <Label> + <input>. Save feedback = Button loading prop + a toast in onSuccess.\nexport function GeneralSettingsSection({\n defaults, onSave, saving, error,\n}: { defaults: { name: string; email: string }; onSave: (v: FormData) => void; saving: boolean; error?: string }) {\n return (\n <form onSubmit={(e) => { e.preventDefault(); onSave(new FormData(e.currentTarget)); }}>\n <Flex direction=\"col\" gap=\"md\">\n <FormField id=\"ws-name\" label=\"ワークスペース名\" layout=\"horizontal\" controlWidth=\"24rem\"\n helper=\"請求書や共有リンクに表示されます。\" error={error}>\n <Input id=\"ws-name\" name=\"name\" defaultValue={defaults.name} />\n </FormField>\n <FormField id=\"ws-email\" label=\"連絡先メール\" layout=\"horizontal\" controlWidth=\"24rem\">\n <Input id=\"ws-email\" name=\"email\" type=\"email\" defaultValue={defaults.email} />\n </FormField>\n <Flex gap=\"sm\"><Button type=\"submit\" loading={saving}>変更を保存</Button></Flex>\n </Flex>\n </form>\n );\n}\n\n// Router wiring — each destination is addressable, so deep links + back/forward just work:\n// <Route path=\"settings\" element={<SettingsLayout/>}>\n// <Route index element={<Navigate to=\"general\" replace/>} />\n// <Route path=\"general\" element={<GeneralSettingsSection .../>} />\n// <Route path=\"security\" element={<SecuritySection/>} />\n// <Route path=\"notifications\" element={<NotificationsSection/>} />\n// </Route>\n\n// ── DO / DON'T ────────────────────────────────────────────────────────────────────────────\n// ✅ Tabs (navigation) are fine for a SMALL number (2–4) of PEER views inside ONE task/section\n// — e.g. \"プロフィール\" ⇄ \"環境設定\" within a single account page — where losing the URL is OK.\n// ⛔ Tabs as the PRIMARY IA for broad settings (Account / Security / Billing / Members / …):\n// use route-backed local navigation so every area is deep-linkable and back/forward works.\n// ⛔ Do NOT wrap the whole settings page in a Card, and do NOT make desktop a horizontal tab bar\n// — persistent local nav scales to many sections; a tab strip does not.\n// ⛔ Do NOT invent CSS class names for the shell/nav/content — compose Flex + semantic tokens.",
7
+ "code": "// Research basis: GitHub, Google Account, Microsoft Account, Atlassian Account settings +\n// the IBM Carbon form grid. Every destination is a URL, so deep links and browser back/forward\n// work for free and the active item is derived from the route — never from local tab state.\n//\n// Desktop (>= lg): persistent vertical local nav on the left + bounded content on the right.\n// Mobile (< lg): the SAME routes become a horizontal, scrollable tab strip above the content.\n// Styling uses ONLY real semantic token classes (bg-muted / text-foreground / text-muted-foreground)\n// and responsive/visibility utilities — the library ships NO bespoke settings shell/nav CSS classes.\nimport { NavLink, Outlet, Navigate } from \"react-router-dom\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { Form, FormField, Input } from \"@godxjp/ui/data-entry\";\nimport { Button } from \"@godxjp/ui/general\";\n\nconst SECTIONS = [\n { to: \"general\", label: \"基本情報\" },\n { to: \"security\", label: \"セキュリティ\" },\n { to: \"notifications\", label: \"通知\" },\n] as const;\n\n// One link renderer for BOTH breakpoints. NavLink gives us the active state (and sets\n// aria-current=\"page\") straight from the URL; semantic tokens only, no invented classes.\nconst link = ({ isActive }: { isActive: boolean }) =>\n [\n \"rounded-md px-3 py-2 text-sm no-underline transition-colors whitespace-nowrap\",\n isActive\n ? \"bg-muted text-foreground font-medium\"\n : \"text-muted-foreground hover:text-foreground\",\n ].join(\" \");\n\nexport function SettingsLayout() {\n return (\n <Flex direction=\"col\" gap=\"lg\" className=\"lg:flex-row lg:items-start\">\n {/* < lg: horizontal, scrollable route tabs. Flex owns the row and the gap — hideFrom /\n hideBelow own the breakpoint, so neither nav needs a hand-rolled flex or gap utility. */}\n <Flex as=\"nav\" aria-label=\"設定\" gap={1} hideFrom=\"lg\" className=\"overflow-x-auto\">\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* >= lg: persistent vertical local nav (bounded width, does not shrink). */}\n <Flex as=\"nav\" aria-label=\"設定\" direction=\"col\" gap={1} hideBelow=\"lg\" width={224} shrink={false}>\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* Bounded content region (~42rem) — the routed section renders here. min-w-0 lets it shrink. */}\n <main className=\"min-w-0 max-w-2xl flex-1\"><Outlet /></main>\n </Flex>\n );\n}\n\n// A settings section. Horizontal label/control rows on desktop (collapse to stacked on mobile),\n// controls bounded to a semantic width — set ONCE on the <Form>, which every FormField reads\n// (gh#998). FormField OWNS the label↔control relationship — do NOT hand-roll <Label> + <input>. Save feedback = Button loading prop + a toast in onSuccess.\nexport function GeneralSettingsSection({\n defaults, onSave, saving, error,\n}: { defaults: { name: string; email: string }; onSave: (v: FormData) => void; saving: boolean; error?: string }) {\n return (\n <Form layout=\"horizontal\" controlWidth=\"24rem\"\n onSubmit={(e) => { e.preventDefault(); onSave(new FormData(e.currentTarget)); }}>\n <FormField id=\"ws-name\" label=\"ワークスペース名\"\n helper=\"請求書や共有リンクに表示されます。\" error={error}>\n <Input id=\"ws-name\" name=\"name\" defaultValue={defaults.name} />\n </FormField>\n <FormField id=\"ws-email\" label=\"連絡先メール\">\n <Input id=\"ws-email\" name=\"email\" type=\"email\" defaultValue={defaults.email} />\n </FormField>\n <Flex gap=\"sm\"><Button type=\"submit\" loading={saving}>変更を保存</Button></Flex>\n </Form>\n );\n}\n\n// Router wiring — each destination is addressable, so deep links + back/forward just work:\n// <Route path=\"settings\" element={<SettingsLayout/>}>\n// <Route index element={<Navigate to=\"general\" replace/>} />\n// <Route path=\"general\" element={<GeneralSettingsSection .../>} />\n// <Route path=\"security\" element={<SecuritySection/>} />\n// <Route path=\"notifications\" element={<NotificationsSection/>} />\n// </Route>\n\n// ── DO / DON'T ────────────────────────────────────────────────────────────────────────────\n// ✅ Tabs (navigation) are fine for a SMALL number (2–4) of PEER views inside ONE task/section\n// — e.g. \"プロフィール\" ⇄ \"環境設定\" within a single account page — where losing the URL is OK.\n// ⛔ Tabs as the PRIMARY IA for broad settings (Account / Security / Billing / Members / …):\n// use route-backed local navigation so every area is deep-linkable and back/forward works.\n// ⛔ Do NOT wrap the whole settings page in a Card, and do NOT make desktop a horizontal tab bar\n// — persistent local nav scales to many sections; a tab strip does not.\n// ⛔ Do NOT invent CSS class names for the shell/nav/content — compose Flex + semantic tokens.",
8
8
  "name": "settings-page-responsive",
9
9
  "tagline": "Route-backed settings: persistent desktop local navigation, compact mobile tab strip, deep links + browser history, and bounded form content — NO Card wrapper, NO undefined CSS.",
10
10
  "tags": [
@@ -34,7 +34,7 @@
34
34
  "settings-page",
35
35
  "settings-layout"
36
36
  ],
37
- "code": "// Research basis: GitHub, Google Account, Microsoft Account, Atlassian Account settings +\n// the IBM Carbon form grid. Every destination is a URL, so deep links and browser back/forward\n// work for free and the active item is derived from the route — never from local tab state.\n//\n// Desktop (>= lg): persistent vertical local nav on the left + bounded content on the right.\n// Mobile (< lg): the SAME routes become a horizontal, scrollable tab strip above the content.\n// Styling uses ONLY real semantic token classes (bg-muted / text-foreground / text-muted-foreground)\n// and responsive/visibility utilities — the library ships NO bespoke settings shell/nav CSS classes.\nimport { NavLink, Outlet, Navigate } from \"react-router-dom\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { FormField, Input } from \"@godxjp/ui/data-entry\";\nimport { Button } from \"@godxjp/ui/general\";\n\nconst SECTIONS = [\n { to: \"general\", label: \"基本情報\" },\n { to: \"security\", label: \"セキュリティ\" },\n { to: \"notifications\", label: \"通知\" },\n] as const;\n\n// One link renderer for BOTH breakpoints. NavLink gives us the active state (and sets\n// aria-current=\"page\") straight from the URL; semantic tokens only, no invented classes.\nconst link = ({ isActive }: { isActive: boolean }) =>\n [\n \"rounded-md px-3 py-2 text-sm no-underline transition-colors whitespace-nowrap\",\n isActive\n ? \"bg-muted text-foreground font-medium\"\n : \"text-muted-foreground hover:text-foreground\",\n ].join(\" \");\n\nexport function SettingsLayout() {\n return (\n <Flex direction=\"col\" gap=\"lg\" className=\"lg:flex-row lg:items-start\">\n {/* < lg: horizontal, scrollable route tabs. Flex owns the row and the gap — hideFrom /\n hideBelow own the breakpoint, so neither nav needs a hand-rolled flex or gap utility. */}\n <Flex as=\"nav\" aria-label=\"設定\" gap={1} hideFrom=\"lg\" className=\"overflow-x-auto\">\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* >= lg: persistent vertical local nav (bounded width, does not shrink). */}\n <Flex as=\"nav\" aria-label=\"設定\" direction=\"col\" gap={1} hideBelow=\"lg\" width={224} shrink={false}>\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* Bounded content region (~42rem) — the routed section renders here. min-w-0 lets it shrink. */}\n <main className=\"min-w-0 max-w-2xl flex-1\"><Outlet /></main>\n </Flex>\n );\n}\n\n// A settings section. Horizontal label/control rows on desktop (collapse to stacked on mobile),\n// controls bounded to a semantic width. FormField OWNS the label↔control relationship — do NOT\n// hand-roll <Label> + <input>. Save feedback = Button loading prop + a toast in onSuccess.\nexport function GeneralSettingsSection({\n defaults, onSave, saving, error,\n}: { defaults: { name: string; email: string }; onSave: (v: FormData) => void; saving: boolean; error?: string }) {\n return (\n <form onSubmit={(e) => { e.preventDefault(); onSave(new FormData(e.currentTarget)); }}>\n <Flex direction=\"col\" gap=\"md\">\n <FormField id=\"ws-name\" label=\"ワークスペース名\" layout=\"horizontal\" controlWidth=\"24rem\"\n helper=\"請求書や共有リンクに表示されます。\" error={error}>\n <Input id=\"ws-name\" name=\"name\" defaultValue={defaults.name} />\n </FormField>\n <FormField id=\"ws-email\" label=\"連絡先メール\" layout=\"horizontal\" controlWidth=\"24rem\">\n <Input id=\"ws-email\" name=\"email\" type=\"email\" defaultValue={defaults.email} />\n </FormField>\n <Flex gap=\"sm\"><Button type=\"submit\" loading={saving}>変更を保存</Button></Flex>\n </Flex>\n </form>\n );\n}\n\n// Router wiring — each destination is addressable, so deep links + back/forward just work:\n// <Route path=\"settings\" element={<SettingsLayout/>}>\n// <Route index element={<Navigate to=\"general\" replace/>} />\n// <Route path=\"general\" element={<GeneralSettingsSection .../>} />\n// <Route path=\"security\" element={<SecuritySection/>} />\n// <Route path=\"notifications\" element={<NotificationsSection/>} />\n// </Route>\n\n// ── DO / DON'T ────────────────────────────────────────────────────────────────────────────\n// ✅ Tabs (navigation) are fine for a SMALL number (2–4) of PEER views inside ONE task/section\n// — e.g. \"プロフィール\" ⇄ \"環境設定\" within a single account page — where losing the URL is OK.\n// ⛔ Tabs as the PRIMARY IA for broad settings (Account / Security / Billing / Members / …):\n// use route-backed local navigation so every area is deep-linkable and back/forward works.\n// ⛔ Do NOT wrap the whole settings page in a Card, and do NOT make desktop a horizontal tab bar\n// — persistent local nav scales to many sections; a tab strip does not.\n// ⛔ Do NOT invent CSS class names for the shell/nav/content — compose Flex + semantic tokens.",
37
+ "code": "// Research basis: GitHub, Google Account, Microsoft Account, Atlassian Account settings +\n// the IBM Carbon form grid. Every destination is a URL, so deep links and browser back/forward\n// work for free and the active item is derived from the route — never from local tab state.\n//\n// Desktop (>= lg): persistent vertical local nav on the left + bounded content on the right.\n// Mobile (< lg): the SAME routes become a horizontal, scrollable tab strip above the content.\n// Styling uses ONLY real semantic token classes (bg-muted / text-foreground / text-muted-foreground)\n// and responsive/visibility utilities — the library ships NO bespoke settings shell/nav CSS classes.\nimport { NavLink, Outlet, Navigate } from \"react-router-dom\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { Form, FormField, Input } from \"@godxjp/ui/data-entry\";\nimport { Button } from \"@godxjp/ui/general\";\n\nconst SECTIONS = [\n { to: \"general\", label: \"基本情報\" },\n { to: \"security\", label: \"セキュリティ\" },\n { to: \"notifications\", label: \"通知\" },\n] as const;\n\n// One link renderer for BOTH breakpoints. NavLink gives us the active state (and sets\n// aria-current=\"page\") straight from the URL; semantic tokens only, no invented classes.\nconst link = ({ isActive }: { isActive: boolean }) =>\n [\n \"rounded-md px-3 py-2 text-sm no-underline transition-colors whitespace-nowrap\",\n isActive\n ? \"bg-muted text-foreground font-medium\"\n : \"text-muted-foreground hover:text-foreground\",\n ].join(\" \");\n\nexport function SettingsLayout() {\n return (\n <Flex direction=\"col\" gap=\"lg\" className=\"lg:flex-row lg:items-start\">\n {/* < lg: horizontal, scrollable route tabs. Flex owns the row and the gap — hideFrom /\n hideBelow own the breakpoint, so neither nav needs a hand-rolled flex or gap utility. */}\n <Flex as=\"nav\" aria-label=\"設定\" gap={1} hideFrom=\"lg\" className=\"overflow-x-auto\">\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* >= lg: persistent vertical local nav (bounded width, does not shrink). */}\n <Flex as=\"nav\" aria-label=\"設定\" direction=\"col\" gap={1} hideBelow=\"lg\" width={224} shrink={false}>\n {SECTIONS.map((s) => (\n <NavLink key={s.to} to={s.to} className={link} end>{s.label}</NavLink>\n ))}\n </Flex>\n {/* Bounded content region (~42rem) — the routed section renders here. min-w-0 lets it shrink. */}\n <main className=\"min-w-0 max-w-2xl flex-1\"><Outlet /></main>\n </Flex>\n );\n}\n\n// A settings section. Horizontal label/control rows on desktop (collapse to stacked on mobile),\n// controls bounded to a semantic width — set ONCE on the <Form>, which every FormField reads\n// (gh#998). FormField OWNS the label↔control relationship — do NOT hand-roll <Label> + <input>. Save feedback = Button loading prop + a toast in onSuccess.\nexport function GeneralSettingsSection({\n defaults, onSave, saving, error,\n}: { defaults: { name: string; email: string }; onSave: (v: FormData) => void; saving: boolean; error?: string }) {\n return (\n <Form layout=\"horizontal\" controlWidth=\"24rem\"\n onSubmit={(e) => { e.preventDefault(); onSave(new FormData(e.currentTarget)); }}>\n <FormField id=\"ws-name\" label=\"ワークスペース名\"\n helper=\"請求書や共有リンクに表示されます。\" error={error}>\n <Input id=\"ws-name\" name=\"name\" defaultValue={defaults.name} />\n </FormField>\n <FormField id=\"ws-email\" label=\"連絡先メール\">\n <Input id=\"ws-email\" name=\"email\" type=\"email\" defaultValue={defaults.email} />\n </FormField>\n <Flex gap=\"sm\"><Button type=\"submit\" loading={saving}>変更を保存</Button></Flex>\n </Form>\n );\n}\n\n// Router wiring — each destination is addressable, so deep links + back/forward just work:\n// <Route path=\"settings\" element={<SettingsLayout/>}>\n// <Route index element={<Navigate to=\"general\" replace/>} />\n// <Route path=\"general\" element={<GeneralSettingsSection .../>} />\n// <Route path=\"security\" element={<SecuritySection/>} />\n// <Route path=\"notifications\" element={<NotificationsSection/>} />\n// </Route>\n\n// ── DO / DON'T ────────────────────────────────────────────────────────────────────────────\n// ✅ Tabs (navigation) are fine for a SMALL number (2–4) of PEER views inside ONE task/section\n// — e.g. \"プロフィール\" ⇄ \"環境設定\" within a single account page — where losing the URL is OK.\n// ⛔ Tabs as the PRIMARY IA for broad settings (Account / Security / Billing / Members / …):\n// use route-backed local navigation so every area is deep-linkable and back/forward works.\n// ⛔ Do NOT wrap the whole settings page in a Card, and do NOT make desktop a horizontal tab bar\n// — persistent local nav scales to many sections; a tab strip does not.\n// ⛔ Do NOT invent CSS class names for the shell/nav/content — compose Flex + semantic tokens.",
38
38
  "name": "settings-page-responsive",
39
39
  "tagline": "Route-backed settings: persistent desktop local navigation, compact mobile tab strip, deep links + browser history, and bounded form content — NO Card wrapper, NO undefined CSS.",
40
40
  "tags": [
@@ -6,7 +6,5 @@ export { ButtonRefetch } from "./query-refetch-button.js";
6
6
  export type { ButtonRefetchProp, ButtonRefetchProps } from "./query-refetch-button.js";
7
7
  export { InfiniteQueryState, flattenItemPages } from "./infinite-query-state.js";
8
8
  export type { InfiniteQueryStateProp, InfiniteQueryStateProps } from "./infinite-query-state.js";
9
- export { PrefetchLink } from "./prefetch-link.js";
10
- export type { PrefetchLinkProp, PrefetchLinkProps } from "./prefetch-link.js";
11
9
  export { classifyQueryError, isRetryableQueryError } from "../../lib/query-error.js";
12
10
  export type { QueryErrorCategory, QueryErrorInfo } from "../../lib/query-error.js";
@@ -2,14 +2,12 @@ import { DataState } from "./data-state.js";
2
2
  import { AlertMutationFeedback } from "./mutation-feedback.js";
3
3
  import { ButtonRefetch } from "./query-refetch-button.js";
4
4
  import { InfiniteQueryState, flattenItemPages } from "./infinite-query-state.js";
5
- import { PrefetchLink } from "./prefetch-link.js";
6
5
  import { classifyQueryError, isRetryableQueryError } from "../../lib/query-error.js";
7
6
  export {
8
7
  AlertMutationFeedback,
9
8
  ButtonRefetch,
10
9
  DataState,
11
10
  InfiniteQueryState,
12
- PrefetchLink,
13
11
  classifyQueryError,
14
12
  flattenItemPages,
15
13
  isRetryableQueryError
@@ -0,0 +1,2 @@
1
+ export { PrefetchLink } from "./prefetch-link.js";
2
+ export type { PrefetchLinkProp, PrefetchLinkProps } from "./prefetch-link.js";
@@ -0,0 +1,4 @@
1
+ import { PrefetchLink } from "./prefetch-link.js";
2
+ export {
3
+ PrefetchLink
4
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "30.8.0",
3
+ "version": "31.0.2",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -2,6 +2,8 @@
2
2
 
3
3
  @layer components {
4
4
  [data-slot="alert"] {
5
+
6
+ container: alert / inline-size;
5
7
  position: relative;
6
8
  display: flex;
7
9
  align-items: flex-start;
@@ -133,7 +135,7 @@
133
135
  gap: var(--alert-inner-space-gap);
134
136
  }
135
137
 
136
- @media (min-width: 640px) {
138
+ @container alert (min-width: 40rem) {
137
139
  [data-slot="alert-body"]:has(> [data-slot="alert-actions"]) {
138
140
  display: grid;
139
141
  grid-template-columns: minmax(0, 1fr) auto;
@@ -190,7 +192,7 @@
190
192
  width: 100%;
191
193
  }
192
194
 
193
- @media (min-width: 640px) {
195
+ @container alert (min-width: 40rem) {
194
196
  [data-slot="alert-actions"] {
195
197
  width: auto;
196
198
  justify-content: flex-end;