@godxjp/ui 18.8.0 → 18.10.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.
Files changed (83) hide show
  1. package/LICENSE +202 -0
  2. package/dist/components/data-display/badge.d.ts +7 -0
  3. package/dist/components/data-display/badge.js +1 -1
  4. package/dist/components/data-display/card.d.ts +3 -27
  5. package/dist/components/data-display/card.js +1 -4
  6. package/dist/components/data-display/data-table.js +2 -2
  7. package/dist/components/data-display/index.d.ts +2 -0
  8. package/dist/components/data-display/index.js +2 -0
  9. package/dist/components/data-display/permission-matrix.d.ts +26 -0
  10. package/dist/components/data-display/permission-matrix.js +219 -0
  11. package/dist/components/data-display/table.d.ts +12 -0
  12. package/dist/components/data-entry/branch-scope-picker.d.ts +43 -0
  13. package/dist/components/data-entry/branch-scope-picker.js +200 -0
  14. package/dist/components/data-entry/command-palette.js +8 -1
  15. package/dist/components/data-entry/index.d.ts +2 -0
  16. package/dist/components/data-entry/index.js +2 -0
  17. package/dist/components/data-entry/input-otp.d.ts +1 -1
  18. package/dist/components/feedback/alert.d.ts +2 -20
  19. package/dist/components/feedback/alert.js +1 -11
  20. package/dist/components/feedback/banner.d.ts +21 -0
  21. package/dist/components/feedback/banner.js +15 -0
  22. package/dist/components/feedback/index.d.ts +4 -2
  23. package/dist/components/feedback/index.js +2 -2
  24. package/dist/components/general/typography.d.ts +1 -0
  25. package/dist/components/general/typography.js +32 -13
  26. package/dist/components/layout/auth-shell.d.ts +2 -2
  27. package/dist/components/layout/index.d.ts +2 -2
  28. package/dist/components/layout/index.js +2 -2
  29. package/dist/components/layout/page-container.d.ts +1 -1
  30. package/dist/components/layout/page-container.js +45 -19
  31. package/dist/components/layout/service-role-panel.d.ts +25 -0
  32. package/dist/components/layout/service-role-panel.js +206 -0
  33. package/dist/components/navigation/filter-bar.d.ts +2 -2
  34. package/dist/components/navigation/filter-bar.js +113 -34
  35. package/dist/components/navigation/index.d.ts +1 -1
  36. package/dist/components/navigation/steps.d.ts +2 -2
  37. package/dist/components/navigation/steps.js +2 -4
  38. package/dist/components/ui/avatar.d.ts +1 -1
  39. package/dist/components/ui/avatar.js +1 -2
  40. package/dist/components/ui/input-otp.d.ts +3 -15
  41. package/dist/components/ui/input-otp.js +1 -2
  42. package/dist/email/tokens.generated.d.ts +2 -2
  43. package/dist/email/tokens.generated.js +2 -2
  44. package/dist/i18n/messages/en.json +56 -4
  45. package/dist/i18n/messages/ja.json +51 -5
  46. package/dist/i18n/messages/vi.json +50 -4
  47. package/dist/props/components/data-display.prop.d.ts +66 -16
  48. package/dist/props/components/data-entry.prop.d.ts +54 -7
  49. package/dist/props/components/feedback.prop.d.ts +3 -3
  50. package/dist/props/components/general.prop.d.ts +8 -1
  51. package/dist/props/components/layout.prop.d.ts +75 -54
  52. package/dist/props/components/navigation.prop.d.ts +77 -59
  53. package/dist/props/registry.d.ts +273 -72
  54. package/dist/props/registry.js +361 -92
  55. package/dist/props/vocabulary/content.prop.d.ts +5 -0
  56. package/dist/props/vocabulary/index.d.ts +1 -1
  57. package/dist/props/vocabulary/interaction.prop.d.ts +4 -7
  58. package/dist/props/vocabulary/layout.prop.d.ts +2 -2
  59. package/dist/styles/alert-layout.css +15 -32
  60. package/dist/styles/badge-layout.css +5 -0
  61. package/dist/styles/card-layout.css +13 -49
  62. package/dist/styles/control.css +2 -12
  63. package/dist/styles/data-display-layout.css +0 -16
  64. package/dist/styles/layout.css +102 -108
  65. package/dist/styles/navigation-layout.css +3 -8
  66. package/dist/styles/shell-layout.css +17 -17
  67. package/dist/styles/table-layout.css +77 -2
  68. package/dist/styles/text-layout.css +13 -0
  69. package/dist/tokens/base.css +1 -0
  70. package/dist/tokens/components/badge.css +5 -0
  71. package/dist/tokens/components/banner.css +16 -0
  72. package/dist/tokens/components/card.css +0 -12
  73. package/dist/tokens/components/control.css +0 -16
  74. package/dist/tokens/components/data-display.css +0 -12
  75. package/dist/tokens/components/email.css +2 -8
  76. package/dist/tokens/components/feedback.css +0 -20
  77. package/dist/tokens/components/navigation.css +10 -34
  78. package/dist/tokens/components/shell.css +24 -36
  79. package/dist/tokens/components/table.css +41 -1
  80. package/dist/tokens/semantic/layout.css +5 -12
  81. package/package.json +15 -2
  82. package/dist/components/layout/page-header.d.ts +0 -22
  83. package/dist/components/layout/page-header.js +0 -89
@@ -0,0 +1,16 @@
1
+ /* Banner — the full-bleed attention strip (<Banner> = Alert variant="banner").
2
+ * Colour stays with the alert tone rules; these tokens own ONLY the strip geometry,
3
+ * so a service retunes the banner once here instead of forking alert-layout.css
4
+ * (rule #45 — every service-tunable constant gets a knob). */
5
+
6
+ :root {
7
+ /* Square corners — a strip spans its container edge-to-edge, so it carries no radius. */
8
+ --banner-radius: 0;
9
+ /* The strip keeps a single hairline block-end rule (tone-coloured via the alert
10
+ * border rules); inline/block-start edges are open so it reads as part of the frame. */
11
+ --banner-border-width: 1px;
12
+ /* Strip inset. Block is denser than the inline Alert card; inline defaults to the
13
+ * page gutter so banner text aligns with page content under AppShell/PageContainer. */
14
+ --banner-space-inset-block: var(--space-3);
15
+ --banner-space-inset-inline: var(--space-page-active-x, var(--space-6));
16
+ }
@@ -74,18 +74,6 @@
74
74
  * The slot padding compensation in card-layout.css subtracts the same token,
75
75
  * so content stays aligned on the shell whatever the rail width. */
76
76
  --card-accent-rail-width: 6px;
77
- /* Accent placement `perimeter` (gh#12) — the FULL attention border. Two knobs so a service can
78
- * tune the edge weight and the outer ring independently; the defaults reproduce the optical
79
- * weight of `variant="featured"` (1px border + 1px ring) in the card's own semantic accent tone
80
- * instead of --primary. Structural 1px literals, like the base card hairline. */
81
- --card-accent-perimeter-width: 1px;
82
- --card-accent-perimeter-ring-width: 1px;
83
- /* `variant="featured"` edge — role-mirror knob (docs/TOKENS.md). `initial` so the --primary
84
- * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary
85
- * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card
86
- * at once. Default = hsl(var(--primary)). */
87
- --card-featured-border-color: initial;
88
- --card-featured-ring-width: 1px;
89
77
  --stat-card-label-font-size: var(--font-size-xs);
90
78
  --stat-card-label-font-weight: var(--font-weight-medium);
91
79
  --stat-card-label-letter-spacing: 0.04em;
@@ -67,22 +67,6 @@
67
67
  * re-scopes --control-height to 36px, would silently shrink. Verified in Chromium: 36px before
68
68
  * and after. A service opts in with a NAMED tier (`var(--control-height-lg)`), never a calc. */
69
69
  --otp-slot-size: initial; /* default = var(--control-height) at the call site */
70
- /* Per-AXIS overrides of the slot box (gh#12). --otp-slot-size stays the square shorthand; these
71
- * two win over it when set, so a code field can be WIDER-than-tall or TALLER-than-wide without
72
- * abandoning the token. A canonical device-grant code field is 27.5x52 per slot (a 4-slot
73
- * `appearance="grouped"` box of 112x54 with its 1px group border) — not expressible with one
74
- * square knob, which is why grouped OTP measured 146x38 against that artboard. `initial` for the
75
- * same tier-mirror reason as --otp-slot-size: the whole fallback chain
76
- * (axis → square → --control-height) must resolve at the CALL SITE so a shell that re-scopes
77
- * --control-height still reaches an OTP row that sets neither axis. */
78
- --otp-slot-inline-size: initial; /* default = var(--otp-slot-size, var(--control-height)) */
79
- --otp-slot-block-size: initial; /* default = var(--otp-slot-size, var(--control-height)) */
80
- /* Main-axis alignment of the whole OTP row (rule #44/#45 — chrome is a token, default quiet).
81
- * `flex-start` is the historical behaviour, so an existing field is byte-identical; a centred
82
- * code field is `align="center"` on InputOTP (which wins over this knob) or this token set once
83
- * in a service theme. It exists because EVERY consumer was wrapping .ui-otp-container in a
84
- * flex-centring div to get a centred challenge. */
85
- --otp-container-align: flex-start;
86
70
 
87
71
  /* Checked/on/active fills — `initial` so the --primary default re-resolves at the call site
88
72
  * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped
@@ -28,18 +28,6 @@
28
28
  --avatar-square-background: initial;
29
29
  --avatar-square-foreground: initial;
30
30
 
31
- /* Capability medallion — `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability /
32
- feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square"
33
- for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are
34
- role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL
35
- SITE and a scoped [data-tenant]/.dark override of --primary reaches them — the exact reason
36
- this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page
37
- CSS. Defaults = hsl(var(--primary) / 0.1) wash · hsl(var(--primary)) glyph ·
38
- --control-icon-size glyph box. */
39
- --avatar-tinted-background: initial;
40
- --avatar-tinted-foreground: initial;
41
- --avatar-tinted-glyph-size: var(--control-icon-size);
42
-
43
31
  /* Progress track + fill — `initial` so the role defaults re-resolve under a scoped theme.
44
32
  Track reads --secondary, fill reads --success; a service re-tones once.
45
33
  Defaults = hsl(var(--secondary)) track · hsl(var(--success)) fill. */
@@ -63,14 +63,8 @@
63
63
  --email-heading-font-weight: 500; /* mirrors --font-weight-medium — an email title is calm, not bold */
64
64
 
65
65
  /* ── Primary CTA — the one action button ────────────────────────────────────────────────── */
66
- /* TOUCH TARGET, deliberately DECOUPLED from --control-height-lg (36px). Do not "restore" the
67
- * mirror: email is a mobile-first, touch-only medium. There is no hover state, no precise
68
- * pointer, and mail clients do not reliably offer zoom or a focus affordance — so the web's AA
69
- * floor (SC 2.5.8, 24x24, which 36px clears) is the wrong bar here. 44px is the AAA target size
70
- * (SC 2.5.5, 44x44) and the Apple HIG 44pt / Material 48dp convention. A service that must keep
71
- * the old box sets --email-cta-height + --email-cta-line-height in its own theme. */
72
- --email-cta-height: 44px;
73
- --email-cta-line-height: 44px; /* equal to the height: bulletproof vertical centring in Outlook */
66
+ --email-cta-height: 36px; /* mirrors --control-height-lg (2.25rem) the canonical CTA box */
67
+ --email-cta-line-height: 36px; /* equal to the height: bulletproof vertical centring in Outlook */
74
68
  --email-cta-padding-x: 16px; /* mirrors --space-4 */
75
69
  --email-cta-radius: 6px; /* mirrors the --radius base (0.375rem) */
76
70
  --email-cta-font-size: 14px; /* mirrors --font-size-base */
@@ -29,26 +29,6 @@
29
29
  * (a brand's success-bg/-border are often more present than the faint 5%/30% default). */
30
30
  --alert-bg-alpha: 0.05;
31
31
  --alert-border-alpha: 0.3;
32
- /* BANNER — the page-level Alert treatment (gh#255). Every constant a service would want to
33
- * match to its page grid is a knob (rule #45): a banner that must keep the app's rounded card
34
- * language sets --banner-radius, one that rules its top edge too sets --banner-border-width.
35
- * The inline inset tracks the live page gutter, so a banner mounted above a PageContainer lines
36
- * its text up with the page title instead of sitting at a second, unrelated margin. */
37
- --banner-radius: 0;
38
- --banner-border-width: 0;
39
- --banner-border-block-end-width: 1px;
40
- --banner-space-block: var(--space-3);
41
- /* Inline inset tracks the page gutter so a banner's text lines up with the page title.
42
- * `--space-page-active-x` steps down to the compact gutter on `.ui-page-container` only, and
43
- * custom properties inherit — so a banner rendered INSIDE the container picks the compact value
44
- * up for free, while the normal case (a banner mounted ABOVE the container, or in AppShell) would
45
- * keep the desktop gutter and sit 8px out at 390px. The compact step below is what actually keeps
46
- * the two aligned; retune it rather than hard-coding a mobile inset at the call site. */
47
- --banner-space-inline: var(--space-page-active-x);
48
- --banner-space-inline-compact: var(--space-page-compact-x);
49
- /* Dismiss offset is measured from the banner's own (shorter) block inset, so the ✕ stays
50
- * optically centred on a one-line strip instead of floating at the inline-alert offset. */
51
- --banner-dismiss-space-offset: var(--space-2);
52
32
  /* Brand glow layer for the raised dialog/sheet panel — invisible no-op at rest (rule #44).
53
33
  * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the
54
34
  * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change. */
@@ -14,15 +14,6 @@
14
14
  --steps-inline-item-gap: var(--space-1);
15
15
  --steps-inline-font-size: var(--font-size-xs);
16
16
  --steps-inline-separator-size: var(--control-icon-size-sm);
17
- /* Inline-step emphasis (gh#12). The step number's weight and tint are separate knobs so a
18
- * service can express progression the canonical way — an accent TINT at normal weight — instead
19
- * of the original bold. --steps-inline-index-color / --steps-inline-separator-color are
20
- * role-mirror knobs (docs/TOKENS.md): `initial` so their defaults (the inherited step colour and
21
- * the --muted-foreground role) resolve at the CALL SITE and a scoped [data-tenant]/.dark
22
- * override still reaches them. Defaults reproduce today's row byte for byte. */
23
- --steps-inline-index-font-weight: var(--font-weight-bold);
24
- --steps-inline-index-color: initial; /* default = currentColor at the call site */
25
- --steps-inline-separator-color: initial; /* default = hsl(var(--muted-foreground)) */
26
17
 
27
18
  /* AppSettingPicker `compact` (gh#217) — the small, content-hugging labelled trigger used in an
28
19
  * auth/legal footer, where the square icon-only default reads as a stray button and the full
@@ -65,29 +56,14 @@
65
56
  * (0 on an overlay-scrollbar platform). */
66
57
  --filter-bar-scroll-padding-y: var(--space-1);
67
58
 
68
- /* FilterBar typed model (#258) — the search / chip / result-count regions the bar now OWNS, so a
69
- * list page never re-decides them. Every constant is a knob (rule #45): a service matches its own
70
- * grid by retuning these once instead of per page.
71
- *
72
- * Search MEASURE — the whole point of the `search` slot. Below the 640px step the control is
73
- * full-width (a bounded search box on a 390px screen wastes the row), so this binds only at and
74
- * above it. 18rem = 288px: comfortable for a JA/VI query without crowding the filter groups. */
75
- --filter-bar-search-width: 18rem;
76
-
77
- /* Applied-filter chips. */
78
- --filter-bar-chip-gap: var(--space-inline-xs);
79
- --filter-bar-chip-row-gap: var(--space-inline-xs);
80
- --filter-bar-chip-padding-inline: var(--space-2);
81
- --filter-bar-chip-padding-block: var(--space-1);
82
- --filter-bar-chip-radius: var(--radius-sm);
83
- --filter-bar-chip-font-size: var(--font-size-xs);
84
- /* Role-mirror knobs (docs/TOKENS.md) — `initial` at :root so --muted / --muted-foreground
85
- * re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme. Binding a role var here
86
- * would freeze the chip at the :root palette and a tenant override would never reach it. */
87
- --filter-bar-chip-background: initial;
88
- --filter-bar-chip-foreground: initial;
89
-
90
- /* Result-count live region. */
91
- --filter-bar-count-font-size: var(--font-size-xs);
92
- --filter-bar-count-foreground: initial;
59
+ /* FilterBar typed model (gh#258) — token-owned geometry so every list page gets the SAME
60
+ * search width, filter width, chip rhythm and section stacking without page-local CSS.
61
+ * All are rule-#45 knobs: a service theme retunes them once, props/className override per
62
+ * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from
63
+ * the 640px breakpoint up, always clamped by min(100%, …) so a narrow container never
64
+ * overflows at 390px with long JA/EN/VI labels. */
65
+ --filter-bar-search-width: 20rem;
66
+ --filter-bar-filter-width: var(--filter-picker-width-md);
67
+ --filter-bar-chip-gap: var(--space-2);
68
+ --filter-bar-section-gap: var(--space-2);
93
69
  }
@@ -81,21 +81,10 @@
81
81
  --topbar-height: auto;
82
82
  --topbar-inset: 0px;
83
83
  --topbar-gap: var(--space-2);
84
- /* THIS KNOB DELETES CONTENT AT 1100px AND BELOW read before you ship a center slot.
85
- * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three
86
- * intrinsically-sized clusters, so the package hides the optional center slot before it can
87
- * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a
88
- * global search trigger placed in `center` is INVISIBLE from 1100px down — including on every
89
- * phone — unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot
90
- * for consumers who never changed a line of their own code (gh#12); it stays because the
91
- * overlap it prevents is a real defect and flipping a shipped default twice is worse than
92
- * documenting it once, but it is a DECISION, not an accident:
93
- *
94
- * :root { --topbar-center-compact-display: flex; } ← restore the slot at every width
95
- *
96
- * Opt back in only once the center content has a compact presentation of its own (an icon-only
97
- * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob
98
- * replaces. */
84
+ /* At compact desktop/tablet widths the docked sidebar leaves too little inline room for three
85
+ * intrinsically-sized clusters. The package hides the optional center slot before it can cover
86
+ * the breadcrumb/title or end utilities. Consumers may opt back into another display strategy
87
+ * (for example `flex`) once their center content has its own compact presentation. */
99
88
  --topbar-center-compact-display: none;
100
89
  --org-switcher-trigger-height: 2.75rem;
101
90
  --org-switcher-trigger-padding-x: var(--space-2);
@@ -183,27 +172,17 @@
183
172
  --auth-shell-login-main-padding-block-end: 1rem;
184
173
  --auth-shell-login-identity-slot-block-size: 7rem;
185
174
  --auth-shell-login-card-stack-gap: 1.25rem;
186
- --auth-shell-login-card-padding-block-compact: calc(
187
- var(--auth-shell-compact-card-inset) + var(--control-border-width) + var(--control-border-width)
188
- );
175
+ /* Canonical SCR-001 block inset is a FLAT 24px — the same measure as the inline inset. The card
176
+ * draws its own 1px border and a border-box rect already contains it, so the former
177
+ * `+ 2 x --control-border-width` double-counted the border and pushed the passkey CTA to y=390
178
+ * instead of the canonical y=388 (gh#263). */
179
+ --auth-shell-login-card-padding-block-compact: var(--auth-shell-compact-card-inset);
189
180
 
190
181
  /* Device-authorization preset — 380px card measure, 15px block · 5px inline mobile gutter
191
182
  * (at a 390px viewport the card is x=5px, width=380px). */
192
183
  --auth-shell-device-card-max-width: 23.75rem;
193
184
  --auth-shell-device-main-padding: 1rem;
194
185
  --auth-shell-device-main-padding-mobile: 0.9375rem 0.3125rem;
195
- /* CODE FIELD measure — the preset owns it (gh#12). A device grant is the one canonical flow whose
196
- * PRIMARY control is the code field, so leaving it on the generic square --otp-slot-size made the
197
- * preset own the page but not its own subject: two 4-slot `appearance="grouped"` boxes rendered
198
- * 146x38 (4 x the canonical 36px control tier + the 1px group border) against a 112x54 artboard.
199
- * These are the per-slot boxes that ADD UP to that artboard group:
200
- * inline 27.5px x 4 + 2 x 1px group border = 112px
201
- * block 52px + 2 x 1px group border = 54px
202
- * Literal artboard lengths, like every other preset measure here — they must not drift with
203
- * --scaling. A service that groups its code differently overrides --otp-slot-inline-size /
204
- * --otp-slot-block-size on its own field; the generic square knob is untouched everywhere else. */
205
- --auth-shell-device-otp-slot-inline-size: 1.71875rem;
206
- --auth-shell-device-otp-slot-block-size: 3.25rem;
207
186
 
208
187
  /* Context-selection preset — 25rem card measure on desktop/tablet, edge-to-edge on mobile
209
188
  * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row. */
@@ -256,14 +235,19 @@
256
235
  --auth-shell-registration-main-padding-block-end-mobile: 2rem;
257
236
  --auth-shell-registration-card-stack-gap: 1.25rem;
258
237
  /* Fixed identity track, the same 112px `login` proved (gh#237). Without it the card anchor moves
259
- * with the title/requester line count measured here at 82.69px for one wrapped requester so
260
- * the canonical y above would hold for exactly one copy length and drift for every other. The
261
- * slot absorbs absent / one-line / two-line identity content instead. */
238
+ * with the title/requester line count, so the canonical y above would hold for exactly one copy
239
+ * length and drift for every other. The slot absorbs absent / one-line / two-line identity
240
+ * content instead. */
262
241
  --auth-shell-registration-identity-slot-block-size: 7rem;
263
242
 
264
- /* Labelled auth divider geometry. */
243
+ /* Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at the 11px
244
+ * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line
245
+ * box). 19/11 pins the row at exactly 19px and keeps it proportional if a service retunes the
246
+ * label font-size. The previous hardcoded `line-height: 1` rendered an 11px row, sitting the
247
+ * whole lower half of the Login card 8px high (gh#263). */
265
248
  --auth-shell-divider-gap: 0.625rem;
266
249
  --auth-shell-divider-label-font-size: 0.6875rem;
250
+ --auth-shell-divider-label-line-height: calc(19 / 11);
267
251
  --auth-shell-divider-rule-color: var(--border);
268
252
  --auth-shell-divider-label-color: var(--muted-foreground);
269
253
  --auth-identity-gap: 0.375rem;
@@ -281,8 +265,12 @@
281
265
  --auth-footer-text-font-size: 0.6875rem;
282
266
  --auth-stack-gap: var(--space-3);
283
267
  --auth-shell-field-label-gap: var(--space-1);
284
- --auth-shell-field-label-font-size: var(--font-size-xs);
285
- --auth-shell-field-label-line-height: 1.25;
268
+ /* Canonical SCR-001 field label: a LITERAL 12px (the golden-ratio --font-size-xs ≈ 12.47px would
269
+ * drift the 18px line box to 18.7px and push the email input off its y=489 anchor) with a 1.5
270
+ * line box = 18px. The former var(--font-size-xs)/1.25 pair rendered a 14px box, sitting the
271
+ * email input 4px high (gh#263). */
272
+ --auth-shell-field-label-font-size: 0.75rem;
273
+ --auth-shell-field-label-line-height: 1.5;
286
274
 
287
275
  /* CenteredShell — authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page",
288
276
  * account, standalone settings). The bar mirrors AppShell's `.app-topbar` chrome (fixed height +
@@ -59,11 +59,51 @@
59
59
  --table-action-collection-secondary-width: 22%; /* target / resource */
60
60
  --table-action-collection-meta-width: 12%; /* timestamps, ids — lowest priority */
61
61
  --table-action-collection-actions-width: 3.5rem; /* row-action affordance, never clipped */
62
- /* Compact tier, applied below the collapse step (the 390px acceptance frame). */
62
+ /* Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages,
63
+ * like the desktop tier: while the priority ratios FIT (the canonical queues up to the
64
+ * six-column budget below), they keep the table filling its container edge-to-edge with no
65
+ * horizontal scroll — the gh#253 acceptance. */
63
66
  --table-action-collection-primary-width-compact: 24%;
64
67
  --table-action-collection-secondary-width-compact: 22%;
65
68
  --table-action-collection-meta-width-compact: 20%;
66
69
  --table-action-collection-actions-width-compact: 2.75rem;
70
+ /* Wide-collection FLOORS — the compact measures from SEVEN columns up (gh#262).
71
+ *
72
+ * Percentage measures stop working once a queue carries enough columns for the shares to sum
73
+ * past 100%: `table-layout: fixed` NORMALIZES them back into the table's specified width, so at
74
+ * ~10 columns on a 390px frame a "24%" column really renders at a tenth of the frame, every
75
+ * column drops below one CJK glyph, and JA headers shred into a vertical one-character-per-line
76
+ * column (the failure mode WCAG 2.2 SC 1.4.10 Reflow exists to prevent). Cell `min-inline-size`
77
+ * and `max(%, length)` widths are equally inert in the fixed algorithm, and the intrinsic
78
+ * (max-content) size of a fixed-layout table is degenerate — all verified in Chromium — so a
79
+ * LENGTH is the only floor an engine respects: length columns are never squeezed, and when
80
+ * their sum outgrows the container the table's used width grows past its specified 100%
81
+ * (CSS 2.1 §17.5.2.1) into the keyboard-reachable overflow-x wrapper, which scrolls.
82
+ * ONE-dimensional scrolling of a data table is explicitly permitted by SC 1.4.10;
83
+ * one-character-per-line is not readable at all.
84
+ *
85
+ * The stylesheet switches tiers on a COLUMN BUDGET, not on width: up to six columns the
86
+ * percentage ratios above still resolve to readable measures at every acceptance artboard
87
+ * (320 · 375 · 390), so the canonical five-column approval queue keeps its documented
88
+ * scroll-free 390 frame byte-for-byte; from seven columns the shares are guaranteed to
89
+ * over-sum and the floors + intentional scroll take over. That budget is the preset's
90
+ * documented column-count ceiling (gh#262 expectation 3): past six columns the preset stops
91
+ * compressing and starts scrolling.
92
+ *
93
+ * Units are rem, not ch: `ch` tracks the "0" glyph (~half a CJK em), so a ch-denominated floor
94
+ * would silently halve for the JA copy these floors exist to protect. At the compact type tier
95
+ * (font-size-xs = 0.75rem, space-2 inline padding each side) the 5rem meta floor keeps a ~4rem
96
+ * text measure = 5 full-width glyphs per line — a JA header wraps to two readable lines, never
97
+ * a character column. */
98
+ --table-action-collection-primary-width-floor: 6rem;
99
+ --table-action-collection-secondary-width-floor: 5.5rem;
100
+ --table-action-collection-meta-width-floor: 5rem;
101
+ /* Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It
102
+ * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the
103
+ * fixed algorithm hands an auto column exactly 0px (verified in Chromium) — the same
104
+ * shredding, moved to the free-text column. The actions floor is simply
105
+ * `--table-action-collection-actions-width-compact`, already an absolute measure. */
106
+ --table-action-collection-flex-width-floor: 5rem;
67
107
  --table-action-collection-font-size-compact: var(--font-size-xs);
68
108
  --table-action-collection-cell-space-x-compact: var(--space-2);
69
109
  --table-action-collection-cell-padding-y-compact: var(--space-2);
@@ -83,18 +83,11 @@
83
83
  * page moves. 11rem = 176px, i.e. the control ends flush with the compact page gutter. */
84
84
  --page-header-extra-measure: 11rem; /* 176px */
85
85
 
86
- /* Inline gap between the `<h1>` and the PageHeader `meta` slot (a status Badge, a record ID) when
87
- * they share the title band (gh#255). Only consumed when `meta` is passed a header without it
88
- * emits no band wrapper at all, so no existing page moves. */
89
- --page-header-meta-gap: var(--space-inline-sm);
90
-
91
- /* PageHeader `loading` placeholders (gh#255). The title/subtitle elements wear the shared
92
- * `.ui-skeleton-block` skin, so they need only a MEASURE and a block size — a service retunes the
93
- * pending band's proportions here instead of per page. */
94
- --page-title-placeholder-measure: 14rem; /* 224px */
95
- --page-title-placeholder-block-size: 1.5em;
96
- --page-subtitle-placeholder-measure: 22rem; /* 352px */
97
- --page-subtitle-placeholder-block-size: 1.25em;
86
+ /* Gap between the page title and its status/meta band (PageContainer `status`,
87
+ * godxjp-ui#255), and between the band's own items. One knob aligns the badge row
88
+ * to a service's grid; a page that never passes `status` reads neither the token
89
+ * nor the wrapping row. */
90
+ --page-header-status-gap: var(--space-inline-sm);
98
91
 
99
92
  /* Bounded page MEASURE (gh#245 / gh#247) — the shared inline cap applied to the page HEADER and
100
93
  * BODY together, so the header `extra` action ends on the same edge as the body surface (the
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "18.8.0",
4
- "godxUiMcp": "18.8.0",
3
+ "version": "18.10.0",
4
+ "godxUiMcp": "18.10.0",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/godx-jp/godxjp-ui.git"
9
+ },
10
+ "homepage": "https://github.com/godx-jp/godxjp-ui#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/godx-jp/godxjp-ui/issues"
13
+ },
14
+ "author": "GoDX (https://godx.jp)",
5
15
  "type": "module",
6
16
  "packageManager": "pnpm@10.29.1",
7
17
  "pnpm": {
@@ -12,6 +22,7 @@
12
22
  "sideEffects": false,
13
23
  "description": "@godxjp/ui — shared React UI framework (shadcn + Radix + Tailwind v4).",
14
24
  "files": [
25
+ "LICENSE",
15
26
  "dist",
16
27
  "scripts/ui-audit.mjs",
17
28
  "README.md",
@@ -296,7 +307,9 @@
296
307
  "format:fix": "prettier --write .",
297
308
  "test": "vitest run",
298
309
  "test:visual:auth-login": "node scripts/auth-shell-login-visual.mjs",
310
+ "test:visual:auth-registration": "node scripts/auth-shell-registration-visual.mjs",
299
311
  "test:visual:topbar-collision": "node scripts/topbar-collision-visual.mjs",
312
+ "test:visual:table-collection-cjk": "node scripts/table-collection-cjk-visual.mjs",
300
313
  "test:watch": "vitest",
301
314
  "test:coverage": "vitest run --coverage",
302
315
  "check:example-imports": "node scripts/check-example-imports.mjs",
@@ -1,22 +0,0 @@
1
- /**
2
- * PageHeader — the canonical page title band (gh#255).
3
- *
4
- * This is the geometry `PageContainer` has always rendered, lifted into its own export so a
5
- * consumer can mount it OUTSIDE a full page shell — a Sheet detail, a `MasterDetail` detail pane, a
6
- * tab body, a nested route header — without re-authoring `.ui-page-header` CSS locally. There is
7
- * exactly ONE implementation: `PageContainer` renders this component, so the two can never drift.
8
- *
9
- * The band owns: breadcrumbs (a named `<nav>` landmark with `aria-current="page"` on the leaf), the
10
- * `<h1>` title, the subtitle/description, a status/meta slot, the `extra` action region, the
11
- * responsive overflow arrangement (`layout`), and the pending state (`loading`). It owns no page
12
- * chrome — padding, measure and body rhythm stay with `PageContainer`.
13
- *
14
- * DENIED / ERROR are deliberately NOT states of this component: a page the user may not see must
15
- * not render its title, breadcrumb trail or actions at all (they leak the resource's existence and
16
- * its name). That whole-surface contract belongs to `ErrorSurface`, which is the canonical public
17
- * replacement — see `docs/CANONICAL-CONTRACTS.md`.
18
- */
19
- import * as React from "react";
20
- import type { PageHeaderProp } from "../../props/components/layout.prop.js";
21
- export type { PageHeaderProp, PageHeaderProp as PageHeaderProps, } from "../../props/components/layout.prop.js";
22
- export declare const PageHeader: React.ForwardRefExoticComponent<PageHeaderProp & React.RefAttributes<HTMLElement>>;
@@ -1,89 +0,0 @@
1
- "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import * as React from "react";
4
- import { ChevronRight } from "lucide-react";
5
- import { useTranslation } from "../../i18n/use-translation.js";
6
- import { cn } from "../../lib/utils.js";
7
- const PageHeader = React.forwardRef(function PageHeader2({
8
- title,
9
- subtitle,
10
- meta,
11
- extra,
12
- breadcrumb,
13
- breadcrumbLabel,
14
- breadcrumbAriaLabel,
15
- linkComponent: LinkComponent = "a",
16
- layout = "stack",
17
- loading = false,
18
- className
19
- }, ref) {
20
- const { t } = useTranslation();
21
- return (
22
- // `data-layout` is the only hook the header arrangement needs: `stack` (the default)
23
- // matches no rule, so the historical geometry is untouched, while `responsive-inline`
24
- // selects the compact-range rules that keep `extra` beside the title band (gh#231).
25
- /* @__PURE__ */ jsxs(
26
- "header",
27
- {
28
- ref,
29
- className: cn("ui-page-header", className),
30
- "data-layout": layout,
31
- "aria-busy": loading ? "true" : void 0,
32
- children: [
33
- breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx(
34
- "nav",
35
- {
36
- "aria-label": breadcrumbLabel ?? breadcrumbAriaLabel ?? t("navigation.breadcrumb.ariaLabel"),
37
- className: "ui-breadcrumb",
38
- children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
39
- const isLast = i === breadcrumb.length - 1;
40
- return /* @__PURE__ */ jsxs("li", { className: "ui-inline-xs", children: [
41
- item.to && !isLast ? /* @__PURE__ */ jsx(
42
- LinkComponent,
43
- {
44
- href: item.to,
45
- to: item.to,
46
- className: "hover:text-foreground hover:underline",
47
- children: item.label
48
- }
49
- ) : /* @__PURE__ */ jsx(
50
- "span",
51
- {
52
- className: isLast ? "text-foreground" : "",
53
- "aria-current": isLast ? "page" : void 0,
54
- children: item.label
55
- }
56
- ),
57
- !isLast && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3", "aria-hidden": "true" })
58
- ] }, i);
59
- }) })
60
- }
61
- ),
62
- /* @__PURE__ */ jsxs("div", { className: "ui-page-header-row", children: [
63
- /* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading min-w-0", children: [
64
- loading ? /* @__PURE__ */ jsx("h1", { className: "ui-page-title ui-skeleton-block ui-page-title-placeholder", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("layout.pageHeader.loading") }) }) : meta != null ? /* @__PURE__ */ jsxs("div", { className: "ui-page-title-band", children: [
65
- /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
66
- /* @__PURE__ */ jsx("div", { className: "ui-page-header-meta", children: meta })
67
- ] }) : /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
68
- loading ? (
69
- // Decorative only — the pending state is already announced once by the heading above,
70
- // so a second live placeholder here would double-announce it.
71
- /* @__PURE__ */ jsx(
72
- "p",
73
- {
74
- className: "ui-page-subtitle ui-skeleton-block ui-page-subtitle-placeholder",
75
- "aria-hidden": "true"
76
- }
77
- )
78
- ) : subtitle && /* @__PURE__ */ jsx("p", { className: "ui-page-subtitle", children: subtitle })
79
- ] }),
80
- extra && /* @__PURE__ */ jsx("div", { className: "ui-page-header-extra", children: extra })
81
- ] })
82
- ]
83
- }
84
- )
85
- );
86
- });
87
- export {
88
- PageHeader
89
- };