@godxjp/ui 28.7.0 → 28.9.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/components/data-display/index.d.ts +2 -0
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/marquee.d.ts +16 -0
- package/dist/components/data-display/marquee.js +155 -0
- package/dist/components/general/reveal.d.ts +23 -2
- package/dist/components/general/reveal.js +37 -7
- package/dist/components/general/typography.d.ts +4 -1
- package/dist/components/general/typography.js +14 -1
- package/dist/components/layout/affix.d.ts +86 -0
- package/dist/components/layout/affix.js +187 -0
- package/dist/components/layout/index.d.ts +4 -0
- package/dist/components/layout/index.js +4 -0
- package/dist/components/layout/legal-document-shell.js +4 -3
- package/dist/components/layout/masonry.d.ts +74 -0
- package/dist/components/layout/masonry.js +214 -0
- package/dist/components/layout/page-container.js +5 -20
- package/dist/components/navigation/anchor.d.ts +64 -0
- package/dist/components/navigation/anchor.js +284 -0
- package/dist/components/navigation/index.d.ts +4 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/navigation/mega-menu.d.ts +21 -0
- package/dist/components/navigation/mega-menu.js +526 -0
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +517 -0
- package/dist/i18n/messages/ja.json +513 -0
- package/dist/i18n/messages/vi.json +513 -0
- package/dist/lib/hooks.d.ts +68 -0
- package/dist/lib/hooks.js +52 -0
- package/dist/lib/platform.d.ts +14 -0
- package/dist/lib/platform.js +10 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +3 -2
- package/dist/lib/variants.js +4 -1
- package/dist/props/components/data-display.prop.d.ts +95 -1
- package/dist/props/components/general.prop.d.ts +47 -3
- package/dist/props/components/layout.prop.d.ts +194 -0
- package/dist/props/components/navigation.prop.d.ts +263 -0
- package/dist/props/registry.d.ts +359 -4
- package/dist/props/registry.js +472 -3
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/interaction.prop.d.ts +39 -2
- package/dist/props/vocabulary/layout.prop.d.ts +1 -1
- package/dist/styles/base.css +47 -14
- package/dist/styles/card-layout.css +2 -2
- package/dist/styles/chart-layout.css +6 -6
- package/dist/styles/control.css +15 -10
- package/dist/styles/data-display-layout.css +22 -6
- package/dist/styles/density.css +6 -0
- package/dist/styles/dialog-layout.css +4 -1
- package/dist/styles/focus-ring.css +4 -1
- package/dist/styles/layout.css +92 -3
- package/dist/styles/motion.css +121 -1
- package/dist/styles/navigation-layout.css +397 -1
- package/dist/styles/shell-layout.css +28 -21
- package/dist/styles/text-layout.css +134 -15
- package/dist/tokens/base.css +5 -0
- package/dist/tokens/components/activity.css +13 -4
- package/dist/tokens/components/affix.css +7 -0
- package/dist/tokens/components/anchor.css +17 -0
- package/dist/tokens/components/attachments.css +1 -1
- package/dist/tokens/components/badge.css +1 -1
- package/dist/tokens/components/card.css +28 -7
- package/dist/tokens/components/chart.css +4 -1
- package/dist/tokens/components/chat-composer.css +4 -1
- package/dist/tokens/components/control.css +72 -33
- package/dist/tokens/components/conversations.css +4 -1
- package/dist/tokens/components/data-display.css +42 -15
- package/dist/tokens/components/data-entry.css +8 -2
- package/dist/tokens/components/descriptions.css +1 -1
- package/dist/tokens/components/feedback.css +8 -5
- package/dist/tokens/components/float-button.css +8 -2
- package/dist/tokens/components/form.css +1 -1
- package/dist/tokens/components/legal-document.css +12 -3
- package/dist/tokens/components/logo.css +15 -6
- package/dist/tokens/components/marquee.css +7 -0
- package/dist/tokens/components/masonry.css +6 -0
- package/dist/tokens/components/mega-menu.css +71 -0
- package/dist/tokens/components/navigation.css +37 -13
- package/dist/tokens/components/segmented.css +4 -1
- package/dist/tokens/components/separator.css +4 -1
- package/dist/tokens/components/shell.css +99 -31
- package/dist/tokens/components/table.css +11 -5
- package/dist/tokens/components/thought-chain.css +4 -1
- package/dist/tokens/components/toggle.css +4 -1
- package/dist/tokens/components/tree.css +1 -1
- package/dist/tokens/components/upload.css +21 -9
- package/dist/tokens/foundation.css +35 -30
- package/dist/tokens/semantic/layout.css +26 -5
- package/docs/COMPOSITION-VS-COMPONENT.md +19 -1
- package/docs/DESIGN-AUTHORITY.md +99 -18
- package/docs/FRAME-COVERAGE-REPORT.md +7 -2
- package/docs/TOKENS.md +16 -1
- package/docs/data-display/marquee.tsx +254 -0
- package/docs/foundation/_theme-editor-scope.ts +222 -0
- package/docs/foundation/density.tsx +12 -2
- package/docs/foundation/spacing.tsx +5 -0
- package/docs/foundation/theme-editor.tsx +645 -0
- package/docs/general/activity.tsx +65 -0
- package/docs/general/reveal.tsx +290 -22
- package/docs/general/typography.tsx +91 -1
- package/docs/layout/affix.tsx +209 -0
- package/docs/layout/masonry.tsx +291 -0
- package/docs/navigation/anchor.tsx +285 -0
- package/docs/navigation/mega-menu-panel.tsx +86 -0
- package/docs/navigation/mega-menu.tsx +254 -0
- package/docs/roadmap/website-components.md +779 -0
- package/docs/showcase/acme-website.tsx +75 -39
- package/docs/showcase/case4-login.tsx +10 -2
- package/docs/showcase/case5-shift-calendar.tsx +1 -1
- package/docs/showcase/case6-agency-handy.tsx +6 -6
- package/docs/showcase/futurelastic-web.tsx +89 -49
- package/docs/showcase/marketing-page.tsx +885 -0
- package/docs/showcase/table-expandable-rows.tsx +4 -1
- package/docs/showcase/table-footer-totals.tsx +12 -2
- package/docs/showcase/theme-customization.tsx +1259 -0
- package/package.json +6 -3
- package/scripts/brand-accent.generated.mjs +27 -0
- package/scripts/ui-audit.mjs +66 -0
- package/scripts/visual-audit-rules.mjs +46 -2
package/docs/DESIGN-AUTHORITY.md
CHANGED
|
@@ -12,17 +12,17 @@ It changes no code by itself. It is the tie-breaker a reviewer points at.
|
|
|
12
12
|
|
|
13
13
|
## The layers, and who owns each
|
|
14
14
|
|
|
15
|
-
| Layer | Authority | Status in this repo
|
|
16
|
-
| ------------------------------------------------------------------ | ----------------------------------------------- |
|
|
17
|
-
| Interaction semantics, keyboard, ARIA | **WAI-ARIA APG** | already followed — 33 references in `src/`
|
|
18
|
-
| Behaviour primitives | **Radix** | already the implementation — 193 references
|
|
19
|
-
| Component composition shape | **shadcn** | already the structural convention — 23 references
|
|
20
|
-
| Component taxonomy / grouping | **Ant Design** groups | already the catalog shape: `data-entry`, `data-display`, `layout`, `feedback`, `navigation`, `general` — a naming precedent, nothing is installed
|
|
15
|
+
| Layer | Authority | Status in this repo |
|
|
16
|
+
| ------------------------------------------------------------------ | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| Interaction semantics, keyboard, ARIA | **WAI-ARIA APG** | already followed — 33 references in `src/` |
|
|
18
|
+
| Behaviour primitives | **Radix** | already the implementation — 193 references |
|
|
19
|
+
| Component composition shape | **shadcn** | already the structural convention — 23 references |
|
|
20
|
+
| Component taxonomy / grouping | **Ant Design** groups | already the catalog shape: `data-entry`, `data-display`, `layout`, `feedback`, `navigation`, `general` — a naming precedent, nothing is installed |
|
|
21
21
|
| Colour foundation | **SmartHR**, brand roles aside | the NEUTRAL spine is SmartHR's — `--foreground` = TEXT_BLACK, `--border` = BORDER. `--primary` was SmartHR MAIN `#0071bd` until identity v2.3 took the brand roles back: it is GoDX violet `#7A00FF` today |
|
|
22
|
-
| **Derived colour — the interaction states hanging off each seed** | **Measured contrast (WCAG 2.2 / JIS X 8341-3)** | Authored in `src/tokens/derived.css`; no algorithm derives them. Four contrast suites read that file and hold every value to a threshold, and a fifth holds it to the seed's HUE (gh#648) — see below
|
|
23
|
-
| **Japanese UI convention — density, JP typography, form patterns** | **SmartHR** | **NEW — this decision.** Extends SmartHR from "where the colours came from" to the authority for how a JP business screen behaves
|
|
24
|
-
| **Japanese accessibility / public-sector convention** | **デジタル庁 Design System** (Digital Agency) | **NEW — this decision.** The reference when a JP customer asks which standard a screen meets (JIS X 8341-3)
|
|
25
|
-
| **Spacing, density, type scale, information architecture** | **IBM Carbon** | **NEW — this decision**
|
|
22
|
+
| **Derived colour — the interaction states hanging off each seed** | **Measured contrast (WCAG 2.2 / JIS X 8341-3)** | Authored in `src/tokens/derived.css`; no algorithm derives them. Four contrast suites read that file and hold every value to a threshold, and a fifth holds it to the seed's HUE (gh#648) — see below |
|
|
23
|
+
| **Japanese UI convention — density, JP typography, form patterns** | **SmartHR** | **NEW — this decision.** Extends SmartHR from "where the colours came from" to the authority for how a JP business screen behaves |
|
|
24
|
+
| **Japanese accessibility / public-sector convention** | **デジタル庁 Design System** (Digital Agency) | **NEW — this decision.** The reference when a JP customer asks which standard a screen meets (JIS X 8341-3) |
|
|
25
|
+
| **Spacing, density, type scale, information architecture** | **IBM Carbon** | **NEW — this decision** |
|
|
26
26
|
|
|
27
27
|
The first five were already true and merely unwritten. The last three are the choices being made
|
|
28
28
|
here. Carbon fills the one layer that had no outside answer at all: page rhythm, table density, form layout,
|
|
@@ -282,6 +282,87 @@ bottomEnd | none` (`TablePaginationPositionProp`, default `['bottomEnd']`), the
|
|
|
282
282
|
rows, so an expanded detail row shifts every stripe after it, and `ui-audit` blocks the
|
|
283
283
|
utilities it would need.
|
|
284
284
|
|
|
285
|
+
- **`Masonry.gap` is antd `gutter`, and its breakpoint steps are this library's (gh-masonry
|
|
286
|
+
port).** Three divergences on one component, each for a reason already written down elsewhere in
|
|
287
|
+
this file:
|
|
288
|
+
|
|
289
|
+
1. **`gutter` → `gap`.** antd's `gutter` takes a raw pixel number (or a per-breakpoint map of
|
|
290
|
+
them). This library already owns that axis under the name `gap` on `Flex`, `ResponsiveGrid`
|
|
291
|
+
and `AuthStack`, `check:prop-vocabulary` maps a field called `gap` to `GapProp`, and a token
|
|
292
|
+
step is what makes spacing follow `--scaling` (density) and a tenant theme. "This library's
|
|
293
|
+
controlled vocabulary wins on values" is the rule; here it wins on the name too, because the
|
|
294
|
+
name and the value type move together. antd's tuple `[Gap, Gap]` survives as
|
|
295
|
+
`[GapProp, GapProp]` = `[inline, block]`. **`gutter` is not silently absent**: it is declared
|
|
296
|
+
on the prop type as `never`, so a consumer arriving from antd's docs gets a compile error and
|
|
297
|
+
a `@deprecated` hint naming `gap`, plus a development-time `console.warn`.
|
|
298
|
+
2. **`columns` steps are `base sm md lg xl`, not antd's `xs sm md lg xl xxl`.** antd's `xs` IS
|
|
299
|
+
this library's `base` (the mobile-first floor), and a second spelling of one axis is what
|
|
300
|
+
`check:prop-vocabulary` exists to prevent; `xxl` has no step here at all. `Flex direction`
|
|
301
|
+
is the precedent shape and the media queries are literally the `--flex-direction-*` cascade's
|
|
302
|
+
(sm 40rem · md 48rem · lg 64rem · xl 80rem). Both antd spellings FAIL LOUDLY — TypeScript
|
|
303
|
+
rejects them as excess properties and the component names them in a development warning —
|
|
304
|
+
because a silently-ignored `xs` is the worst outcome of the three.
|
|
305
|
+
3. **`MasonryItem.height` is HONOURED, where antd declares it and never reads it.** Verified in
|
|
306
|
+
antd `master`: `Masonry.tsx` builds every `ItemHeightData` from `getBoundingClientRect()`
|
|
307
|
+
alone, and all six demos carry their heights in `data`. Porting the field as inert would ship
|
|
308
|
+
a documented prop that does nothing. Here a finite `height` sizes the tile and replaces its
|
|
309
|
+
measurement, which is also what lets a first paint and an SSR render land in the right place.
|
|
310
|
+
|
|
311
|
+
Not ported, each under a rule that already exists: `classNames` / `styles` (a knob only a fork
|
|
312
|
+
could reach — `src/tokens/components/masonry.css` is the answer), `prefixCls` / `rootClassName`
|
|
313
|
+
(no `prefixCls` layer here), and rc-motion's tile-REMOVAL animation (the arrival fade and the
|
|
314
|
+
re-flow slide are ported in CSS and both snap under `prefers-reduced-motion`). `fresh` keeps
|
|
315
|
+
antd's name and antd's `false` default even though `docs/roadmap/list-masonry.md` (2026-09-10)
|
|
316
|
+
proposed renaming it `observeItems` and defaulting it on — that proposal predates the
|
|
317
|
+
2026-09-12 restatement above, and "the antd name says nothing" is exactly the reasoning the
|
|
318
|
+
restatement rejects. The same roadmap's invented `sequential` prop is not ported for the same
|
|
319
|
+
reason: antd has no such knob, and improvements come after parity, not instead of it.
|
|
320
|
+
|
|
321
|
+
- **`Affix` and `Anchor` take the LOGICAL axis where antd takes the physical one (gh#827/gh#828).**
|
|
322
|
+
Ported from `ant-design@master`, read out of `components/affix/{index.tsx,utils.ts}` and
|
|
323
|
+
`components/anchor/{Anchor,AnchorLink}.tsx` on 2026-09-21, not out of the docs tables. Every
|
|
324
|
+
antd prop is present. Four renames, and nothing else:
|
|
325
|
+
|
|
326
|
+
1. **`offsetTop` → `offsetBlockStart`, `offsetBottom` → `offsetBlockEnd`** (both components;
|
|
327
|
+
`Anchor.targetOffset` → `targetOffsetBlockStart` with it). `check:rtl` exists to keep
|
|
328
|
+
`inset-block-start` out of `top`'s hands in every stylesheet here, and a prop that names a
|
|
329
|
+
physical edge re-opens that door at the API. It is also load-bearing rather than cosmetic:
|
|
330
|
+
the offset is a CSS custom property (`--affix-inset-block-start`), read once by the sentinel
|
|
331
|
+
that detects the crossing and once by the pinned bar that paints it, so the name has to be
|
|
332
|
+
the one CSS uses. All three antd spellings are declared `never` — a compile error carrying an
|
|
333
|
+
`@deprecated` hint that names the replacement — plus a development `console.warn`, plus the
|
|
334
|
+
MCP catalog entry. The `Masonry gutter → gap` precedent, applied to a second component.
|
|
335
|
+
2. **`Anchor.onChange` → `onValueChange`,** beside new `value` / `defaultValue`. The active
|
|
336
|
+
`href` is a controlled value and this library has one spelling for that. antd has no
|
|
337
|
+
controlled form of it at all (`getCurrentAnchor` is a transform of the computed answer, not a
|
|
338
|
+
value), so this is an ADDITION with a rename attached rather than a substitution, and
|
|
339
|
+
`getCurrentAnchor` is ported unchanged beside it — a controlled `value` simply outranks it.
|
|
340
|
+
antd's own note is ported too: `onValueChange` reports the link the SCROLL POSITION resolved,
|
|
341
|
+
not the one `getCurrentAnchor` substituted.
|
|
342
|
+
|
|
343
|
+
**`Affix.onChange` is NOT renamed**, and the distinction is the point: there is no pinned VALUE
|
|
344
|
+
to control, only an observation of a derived boolean, so no triad applies. `Attachments.onChange`
|
|
345
|
+
and `ActionsFeedback.onChange` already carry antd's name with a non-DOM payload for the same
|
|
346
|
+
reason.
|
|
347
|
+
|
|
348
|
+
**Not ported, each under a rule that already exists:** `classNames` / `styles` on `Anchor` (a
|
|
349
|
+
knob only a fork could reach — `src/tokens/components/anchor.css` is the answer),
|
|
350
|
+
`prefixCls` / `rootClassName`, and antd's deprecated `Anchor children` / `Anchor.Link` JSX form
|
|
351
|
+
(antd itself warns on it and says to use `items`).
|
|
352
|
+
|
|
353
|
+
**One deliberate behavioural difference, in `Anchor`.** antd resolves from scroll position on
|
|
354
|
+
mount unconditionally, which overwrites a landing hash on any page whose hashed section happens
|
|
355
|
+
to be above the fold. Here the hash is read in the state INITIALISER and the mount resolution is
|
|
356
|
+
skipped when it matched, so `/page#section` is correct on the first render with no scroll event
|
|
357
|
+
ever firing — which is gh#828's acceptance criterion, and the one place antd's behaviour is
|
|
358
|
+
worse rather than merely differently named.
|
|
359
|
+
|
|
360
|
+
**Two things antd does not have at all**, both required by the contract rather than invented:
|
|
361
|
+
`Anchor.label` (antd ships an unnamed `<div>`; a `<nav>` landmark on a page that already has a
|
|
362
|
+
breadcrumb and a rail must be named, so `label` defaults through `t()`), and `Affix`'s
|
|
363
|
+
`scroll-padding-block-start` on the scroll box while pinned (WCAG 2.4.11 — without it the pinned
|
|
364
|
+
bar covers whatever the browser scrolls a focused element to).
|
|
365
|
+
|
|
285
366
|
- **`Command.split` is antd `List.split`, with this library's default.** Same name, same meaning:
|
|
286
367
|
a divider between rows and none after the last (gh#699). antd's `List` defaults it to `true`;
|
|
287
368
|
`Command` defaults it to `false`, because its resting home is a command palette, where inset
|
|
@@ -501,11 +582,11 @@ snapped to the seed's hue (1.008:1 / 1.010:1 from the kit value, i.e. the same c
|
|
|
501
582
|
the kit's `action.primary.hover` and then reflects the pressed step, because the dark seed sits at
|
|
502
583
|
86.9% L and the conventional step runs out of LIGHTNESS rather than out of contrast:
|
|
503
584
|
|
|
504
|
-
| token | conventional step
|
|
505
|
-
| ------------------------ |
|
|
506
|
-
| light `--primary-hover` | kit #6400D4
|
|
507
|
-
| light `--primary-active` | kit #5200B0
|
|
508
|
-
| dark `--primary-hover` | kit #E8DAFF
|
|
585
|
+
| token | conventional step | shipped |
|
|
586
|
+
| ------------------------ | ---------------------------- | ------------------------------- |
|
|
587
|
+
| light `--primary-hover` | kit #6400D4 | #6500d4 · 8.23:1 |
|
|
588
|
+
| light `--primary-active` | kit #5200B0 | #5400b0 · 10.31:1 |
|
|
589
|
+
| dark `--primary-hover` | kit #E8DAFF | #ecdaff · 13.51:1 |
|
|
509
590
|
| dark `--primary-active` | #fbf7ff · 1.056:1 from white | #cd9fff · 8.41:1 (sign flipped) |
|
|
510
591
|
|
|
511
592
|
**The ratio is blind to hue, and that cost a release (gh#648).** Identity v2.3 moved the seed 63
|
|
@@ -604,10 +685,10 @@ the **field** indicator on every control — one hairline (1px) in the focus hue
|
|
|
604
685
|
`--control-outline` halo — rather than the heavy 3px outline form. Measured, in Chromium, on
|
|
605
686
|
`ql.test` after the transition settles:
|
|
606
687
|
|
|
607
|
-
| control | switch off | switch on
|
|
608
|
-
| -------------------------- | ---------------------------------------------------- |
|
|
688
|
+
| control | switch off | switch on |
|
|
689
|
+
| -------------------------- | ---------------------------------------------------- | --------------------------------------------------------------- |
|
|
609
690
|
| Input / Select trigger | border `1px rgb(144,135,127)`, resting shadow intact | border `1px` of `--ring` + `--control-outline` halo `0 0 0 2px` |
|
|
610
|
-
| Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid` `--ring` @ `0px` + same halo
|
|
691
|
+
| Button (primary) | outline `0px`, resting shadow intact | `outline: 1px solid` `--ring` @ `0px` + same halo |
|
|
611
692
|
| Sidebar nav row / list row | outline `0px` | `outline: 1px solid` `--ring` @ **`-1px`** (inset into the row) |
|
|
612
693
|
|
|
613
694
|
The GEOMETRY above is the Chromium measurement; the COLOUR is named by token rather than pinned as
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> Generated by `scripts/frame-coverage.mjs` (issue #163). Do not edit by hand — run `pnpm check:frame-coverage`.
|
|
4
4
|
> Standard: [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md). A missing dimension is **UNTESTED**, never a pass.
|
|
5
5
|
|
|
6
|
-
- Public components: **
|
|
7
|
-
- With ≥1 frame: **
|
|
6
|
+
- Public components: **170**
|
|
7
|
+
- With ≥1 frame: **149** · zero-frame: **21**
|
|
8
8
|
- Fully covered (every contract axis declared): **0**
|
|
9
9
|
|
|
10
10
|
## UNTESTED — no `/frame/**` route
|
|
@@ -198,8 +198,13 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
|
|
|
198
198
|
| ChatComposer | ✓ | · | · | · | · | · | · | · | · | · |
|
|
199
199
|
| ChatSuggestion | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
200
200
|
| Conversations | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
201
|
+
| MegaMenu | ✓ | · | N/A | · | · | · | · | · | · | · |
|
|
201
202
|
| Welcome | ✓ | · | N/A | N/A | · | · | · | · | · | · |
|
|
202
203
|
| Actions | ✓ | · | N/A | N/A | · | · | · | · | · | · |
|
|
203
204
|
| ThoughtChain | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
204
205
|
| Attachments | ✓ | N/A | · | N/A | · | · | · | · | · | · |
|
|
206
|
+
| Marquee | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
207
|
+
| Masonry | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
208
|
+
| Affix | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
|
|
209
|
+
| Anchor | ✓ | N/A | N/A | · | · | · | · | · | · | · |
|
|
205
210
|
|
package/docs/TOKENS.md
CHANGED
|
@@ -297,7 +297,22 @@ Instead, declare the knob `initial` (a real, catalogued, guaranteed-invalid decl
|
|
|
297
297
|
}
|
|
298
298
|
```
|
|
299
299
|
|
|
300
|
-
The same rule applies to `@theme inline` (utilities re-resolve scoped roles) and to any `:root`-declared **composite** that wraps a role (e.g. a focus-ring box-shadow): read the role **directly** at the call site, never through a frozen `:root` intermediate. Pure non-colour knobs (spacing, radius, font-size) don't need this
|
|
300
|
+
The same rule applies to `@theme inline` (utilities re-resolve scoped roles) and to any `:root`-declared **composite** that wraps a role (e.g. a focus-ring box-shadow): read the role **directly** at the call site, never through a frozen `:root` intermediate. Pure non-colour knobs (spacing, radius, font-size) don't need this **as long as nothing re-scopes what they read** — see the two paragraphs below, which is where that qualifier cost a release.
|
|
301
|
+
|
|
302
|
+
**A DERIVED TIER FREEZES ON ITS OWN SEED — the `--font-size-*` ramp (gh#834).** The type scale is one base and one ratio, and every step used to be written `--font-size-5xl: var(--font-size-display)` / `--font-size-lg: calc(var(--font-size-base) * …)` on `:root`. Those substitute on `<html>`, so a `[data-tenant]` that re-seeds the base — the route level 3 of `docs/CUSTOMER-THEMING.md` documents, and which level 1 calls a seed — moved `text-base` and **nothing else**. Measured with `--font-size-base: 24px; --font-size-display: 80px` on a scope: 2xs/xs/sm/lg/xl/2xl/3xl/4xl/5xl all sat exactly where `:root` had left them, and a showcase whose comment read "80px hero via text-5xl" painted 54.
|
|
303
|
+
|
|
304
|
+
So every **derived** step is now an `initial` knob and the formula lives at the call site, spelled in terms of the un-derived knobs only (`--font-size-base`, `--font-size-ratio`, `--font-size-display`, `--font-size-display-ratio`) — an `initial` knob cannot be chained through, so `xl` reads `base × ratio³` rather than `lg × ratio`:
|
|
305
|
+
|
|
306
|
+
```css
|
|
307
|
+
:root {
|
|
308
|
+
--font-size-5xl: initial;
|
|
309
|
+
} /* documented default = var(--font-size-display) */
|
|
310
|
+
[data-slot="text"][data-size="5xl"] {
|
|
311
|
+
font-size: var(--font-size-5xl, var(--font-size-display));
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Every reader must carry the fallback**: a bare `var(--font-size-xs)` on an `initial` knob resolves to nothing, and the failure is silent and total. `check:frame-token-scope` is the gate that measures both halves — it fails on a step that stops following a scoped seed, and on a bare read of an empty knob.
|
|
301
316
|
|
|
302
317
|
**The same freeze bites any knob whose default is a RE-SCOPED tier, colour or not.** `--control-height` is re-scoped by `.ui-auth-shell` (44px comfortable) and by `.ui-auth-shell[data-variant="canonical"]` (36px), so a knob that mirrors it must follow the same `initial` + call-site-fallback shape:
|
|
303
318
|
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
Marquee,
|
|
11
|
+
} from "@godxjp/ui/data-display";
|
|
12
|
+
import { Switch } from "@godxjp/ui/data-entry";
|
|
13
|
+
import { Heading, Link, Text } from "@godxjp/ui/general";
|
|
14
|
+
import { Flex, PageContainer, Separator } from "@godxjp/ui/layout";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Marquee · a track of content that travels continuously, and the WCAG 2.2.2 pause control that
|
|
18
|
+
* makes that legal.
|
|
19
|
+
*
|
|
20
|
+
* Every card on this page is an EDGE, because the tidy demo is the one that hides the defects this
|
|
21
|
+
* component actually has:
|
|
22
|
+
* · ONE item in a full-width track, which is the case that needs the most clones;
|
|
23
|
+
* · FORTY items, which is the case that proves the pace is measured and not per-cycle;
|
|
24
|
+
* · a 71-character unbreakable string, wider than a phone;
|
|
25
|
+
* · a track NARROWER than one item, where there is nothing to clone into;
|
|
26
|
+
* · CJK beside Latin beside Vietnamese, at three different optical widths;
|
|
27
|
+
* · the paused state, which is where the content has to stay readable;
|
|
28
|
+
* · a controlled "stop all motion" switch driving two tracks at once.
|
|
29
|
+
*
|
|
30
|
+
* Composed only from real @godxjp/ui components. All copy is consumer-owned and localized.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const PARTNERS = [
|
|
34
|
+
"フジワラ運輸",
|
|
35
|
+
"Meridian Foods",
|
|
36
|
+
"Công ty Đại Việt",
|
|
37
|
+
"北陸電機",
|
|
38
|
+
"Halden Systems",
|
|
39
|
+
"三和商会",
|
|
40
|
+
"Vinh Phat Logistics",
|
|
41
|
+
"東海システム",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** Forty items · the case a per-cycle duration would send across the screen forty times too fast. */
|
|
45
|
+
const FORTY = Array.from({ length: 40 }, (_, index) => `SKU-${String(index + 1).padStart(4, "0")}`);
|
|
46
|
+
|
|
47
|
+
/** 71 characters, no break opportunity anywhere in it. */
|
|
48
|
+
const UNBREAKABLE = "SUPPLYCHAINRECONCILIATIONBATCHIDENTIFIER2026Q3NOBREAKOPPORTUNITY0000001";
|
|
49
|
+
|
|
50
|
+
export default function MarqueeDoc() {
|
|
51
|
+
const [motion, setMotion] = useState(true);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<PageContainer title="Marquee" subtitle="連続して流れるトラックと、それを止める操作">
|
|
55
|
+
<Flex direction="col" gap="lg">
|
|
56
|
+
<Card>
|
|
57
|
+
<CardHeader>
|
|
58
|
+
<CardTitle level={2}>ロゴウォール · 既定のかたち</CardTitle>
|
|
59
|
+
<CardDescription>
|
|
60
|
+
`fade` で両端をマスクし、`pauseOnHover` を足した 8 件。コピー数は測って決まる(内容幅
|
|
61
|
+
÷ 表示幅 + 1)ので、ウィンドウを横に伸ばせばクローンが増える。停止ボタンは常にあり、
|
|
62
|
+
Tab で到達できる。各社名はリンクなので、トラック内にフォーカスが入ると自動で止まる
|
|
63
|
+
(動いているリンクは押せないため)。クローン側のリンクは Tab の順路に出てこない。
|
|
64
|
+
</CardDescription>
|
|
65
|
+
</CardHeader>
|
|
66
|
+
<CardContent>
|
|
67
|
+
<Marquee fade pauseOnHover label="取引先ロゴ">
|
|
68
|
+
{PARTNERS.map((name) => (
|
|
69
|
+
<Link key={name} href={`#partner-${encodeURIComponent(name)}`}>
|
|
70
|
+
{name}
|
|
71
|
+
</Link>
|
|
72
|
+
))}
|
|
73
|
+
</Marquee>
|
|
74
|
+
</CardContent>
|
|
75
|
+
</Card>
|
|
76
|
+
|
|
77
|
+
<Card>
|
|
78
|
+
<CardHeader>
|
|
79
|
+
<CardTitle level={2}>1 件 と 40 件 · 速度は件数で変わらない</CardTitle>
|
|
80
|
+
<CardDescription>
|
|
81
|
+
1 周の所要時間ではなく「1 画面ぶんを流れる時間」が `--marquee-interval` なので、1
|
|
82
|
+
件でも 40 件でも見た目の速さは同じ。1 件のトラックはクローンが最も多く、 40
|
|
83
|
+
件のトラックはクローンが 1 枚で足りる。
|
|
84
|
+
</CardDescription>
|
|
85
|
+
</CardHeader>
|
|
86
|
+
<CardContent>
|
|
87
|
+
<Flex direction="col" gap="md">
|
|
88
|
+
<Heading level={3}>1 件</Heading>
|
|
89
|
+
<Marquee label="在庫同期の状態">
|
|
90
|
+
<Badge tone="info">在庫同期 · 実行中</Badge>
|
|
91
|
+
</Marquee>
|
|
92
|
+
<Separator />
|
|
93
|
+
<Heading level={3}>40 件</Heading>
|
|
94
|
+
<Marquee label="処理中の SKU">
|
|
95
|
+
{FORTY.map((sku) => (
|
|
96
|
+
<Text key={sku} size="xs" tone="muted">
|
|
97
|
+
{sku}
|
|
98
|
+
</Text>
|
|
99
|
+
))}
|
|
100
|
+
</Marquee>
|
|
101
|
+
</Flex>
|
|
102
|
+
</CardContent>
|
|
103
|
+
</Card>
|
|
104
|
+
|
|
105
|
+
<Card>
|
|
106
|
+
<CardHeader>
|
|
107
|
+
<CardTitle level={2}>折り返せない 71 文字と、1 件より狭いトラック</CardTitle>
|
|
108
|
+
<CardDescription>
|
|
109
|
+
分割点のない 71 文字は電話の画面より広い。狭い枠に入れると「表示幅 ÷ 内容幅」は 1
|
|
110
|
+
を下回るので、クローンは最小の 1 枚になり、それでも継ぎ目なく回る。
|
|
111
|
+
</CardDescription>
|
|
112
|
+
</CardHeader>
|
|
113
|
+
<CardContent>
|
|
114
|
+
<Flex direction="col" gap="md">
|
|
115
|
+
<Heading level={3}>全幅</Heading>
|
|
116
|
+
<Marquee label="照合バッチ識別子">
|
|
117
|
+
<Text size="sm">{UNBREAKABLE}</Text>
|
|
118
|
+
</Marquee>
|
|
119
|
+
<Separator />
|
|
120
|
+
<Heading level={3}>幅 14rem · 1 件より狭い枠</Heading>
|
|
121
|
+
<div style={{ inlineSize: "14rem", maxInlineSize: "100%" }}>
|
|
122
|
+
<Marquee label="照合バッチ識別子・狭い枠">
|
|
123
|
+
<Text size="sm">{UNBREAKABLE}</Text>
|
|
124
|
+
</Marquee>
|
|
125
|
+
</div>
|
|
126
|
+
</Flex>
|
|
127
|
+
</CardContent>
|
|
128
|
+
</Card>
|
|
129
|
+
|
|
130
|
+
<Card>
|
|
131
|
+
<CardHeader>
|
|
132
|
+
<CardTitle level={2}>日本語 · Latin · Tiếng Việt が同じ行に並ぶ</CardTitle>
|
|
133
|
+
<CardDescription>
|
|
134
|
+
全角の仮名漢字、欧文、そしてダイアクリティカルの付いたベトナム語は 1 文字あたりの幅も
|
|
135
|
+
行の高さも違う。同じトラックに置いたときの継ぎ目とベースラインはここで見る。
|
|
136
|
+
`direction="end"` は読み方向の逆に流れる版で、`dir="rtl"` では両方が反転する。
|
|
137
|
+
</CardDescription>
|
|
138
|
+
</CardHeader>
|
|
139
|
+
<CardContent>
|
|
140
|
+
<Flex direction="col" gap="md">
|
|
141
|
+
<Marquee gap={6} speed="slow" label="多言語の見出し">
|
|
142
|
+
<Text size="sm">品質保証部 · 月次レビュー</Text>
|
|
143
|
+
<Text size="sm">Quality Assurance · Monthly review</Text>
|
|
144
|
+
<Text size="sm">Đảm bảo chất lượng · Đánh giá hàng tháng</Text>
|
|
145
|
+
</Marquee>
|
|
146
|
+
<Separator />
|
|
147
|
+
<Marquee gap={6} speed="fast" direction="end" label="多言語の見出し・逆方向">
|
|
148
|
+
<Text size="sm">品質保証部 · 月次レビュー</Text>
|
|
149
|
+
<Text size="sm">Quality Assurance · Monthly review</Text>
|
|
150
|
+
<Text size="sm">Đảm bảo chất lượng · Đánh giá hàng tháng</Text>
|
|
151
|
+
</Marquee>
|
|
152
|
+
</Flex>
|
|
153
|
+
</CardContent>
|
|
154
|
+
</Card>
|
|
155
|
+
|
|
156
|
+
<Card>
|
|
157
|
+
<CardHeader>
|
|
158
|
+
<CardTitle level={2}>止まった状態 · WebAIM の推奨する既定</CardTitle>
|
|
159
|
+
<CardDescription>
|
|
160
|
+
`defaultPlay={false}` で始めると、内容は止まったまま読める。停止中もリンクは Tab
|
|
161
|
+
で辿れて、押せる。動いている最中にトラック内へフォーカスが入った場合も自動で止まる
|
|
162
|
+
(動いているリンクは押せないため)。ボタンの名前は「次に起きること」を言う。
|
|
163
|
+
</CardDescription>
|
|
164
|
+
</CardHeader>
|
|
165
|
+
<CardContent>
|
|
166
|
+
<Marquee defaultPlay={false} fade label="社内のお知らせ">
|
|
167
|
+
<Text size="sm">2026-10-01 · 社内システム定期メンテナンス</Text>
|
|
168
|
+
<Link href="#maintenance">詳細</Link>
|
|
169
|
+
<Text size="sm">2026-10-14 · 新しい経費申請フォームに切替</Text>
|
|
170
|
+
<Link href="#expense">申請の手順</Link>
|
|
171
|
+
</Marquee>
|
|
172
|
+
</CardContent>
|
|
173
|
+
</Card>
|
|
174
|
+
|
|
175
|
+
<Card>
|
|
176
|
+
<CardHeader>
|
|
177
|
+
<CardTitle level={2}>`play` で外から止める</CardTitle>
|
|
178
|
+
<CardDescription>
|
|
179
|
+
制御された `play` / `onPlayChange` は、画面全体の「動きを止める」1
|
|
180
|
+
つのスイッチから複数のトラックを同時に止めるためにある。各トラックの停止ボタンは
|
|
181
|
+
そのまま残る(2.2.2 の要件は個々の操作で満たす)。
|
|
182
|
+
</CardDescription>
|
|
183
|
+
</CardHeader>
|
|
184
|
+
<CardContent>
|
|
185
|
+
<Flex direction="col" gap="md">
|
|
186
|
+
<Flex align="center" gap="sm">
|
|
187
|
+
<Switch
|
|
188
|
+
id="stop-all-motion"
|
|
189
|
+
checked={motion}
|
|
190
|
+
onCheckedChange={setMotion}
|
|
191
|
+
aria-label="このページの動きを有効にする"
|
|
192
|
+
/>
|
|
193
|
+
<Text size="sm">
|
|
194
|
+
{motion ? "このページの動きは有効" : "このページの動きは停止中"}
|
|
195
|
+
</Text>
|
|
196
|
+
</Flex>
|
|
197
|
+
<Marquee play={motion} onPlayChange={setMotion} label="取引先ロゴ・上段">
|
|
198
|
+
{PARTNERS.slice(0, 4).map((name) => (
|
|
199
|
+
<Text key={name} size="sm" tone="muted">
|
|
200
|
+
{name}
|
|
201
|
+
</Text>
|
|
202
|
+
))}
|
|
203
|
+
</Marquee>
|
|
204
|
+
<Marquee
|
|
205
|
+
play={motion}
|
|
206
|
+
onPlayChange={setMotion}
|
|
207
|
+
direction="end"
|
|
208
|
+
label="取引先ロゴ・下段"
|
|
209
|
+
>
|
|
210
|
+
{PARTNERS.slice(4).map((name) => (
|
|
211
|
+
<Text key={name} size="sm" tone="muted">
|
|
212
|
+
{name}
|
|
213
|
+
</Text>
|
|
214
|
+
))}
|
|
215
|
+
</Marquee>
|
|
216
|
+
</Flex>
|
|
217
|
+
</CardContent>
|
|
218
|
+
</Card>
|
|
219
|
+
|
|
220
|
+
<Card>
|
|
221
|
+
<CardHeader>
|
|
222
|
+
<CardTitle level={2}>使う前に読むこと</CardTitle>
|
|
223
|
+
</CardHeader>
|
|
224
|
+
<CardContent>
|
|
225
|
+
<Flex direction="col" gap="sm">
|
|
226
|
+
<Heading level={3}>止められる動きでも、止まっている方が良いことは多い</Heading>
|
|
227
|
+
<Text tone="muted">
|
|
228
|
+
自動で流れる内容は Nielsen Norman Group も WebAIM も勧めていない。NN/g
|
|
229
|
+
は「ユーザーが操作していないのに画面が動くのは不快で、読み終える前に消える」と書き、
|
|
230
|
+
WebAIM は「アニメーションは既定で止めておくことを勧める」と書いている。この
|
|
231
|
+
コンポーネントはその判断を奪わない。止める操作は必ず付いてくるし、`defaultPlay=
|
|
232
|
+
{"{false}"}` は 1 つのプロパティで済む。動かす理由が言えないなら、静止した `Flex
|
|
233
|
+
wrap` や `ResponsiveGrid` のロゴウォールの方が良い。
|
|
234
|
+
</Text>
|
|
235
|
+
<Heading level={3}>OS のモーション低減設定では一切動かない</Heading>
|
|
236
|
+
<Text tone="muted">
|
|
237
|
+
`prefers-reduced-motion: reduce` のとき、クローンも停止ボタンも作らず、1
|
|
238
|
+
件ぶんの内容を横スクロールできる領域として出す。横方向の平行移動は前庭系の
|
|
239
|
+
トリガーそのもので、控えめにする版というものが無いため(WCAG 2.2 SC 2.2.2 / SC
|
|
240
|
+
2.3.3)。
|
|
241
|
+
</Text>
|
|
242
|
+
<Heading level={3}>テーマ側のつまみ</Heading>
|
|
243
|
+
<Text tone="muted">
|
|
244
|
+
--marquee-interval · --marquee-interval-slow · --marquee-interval-fast ·
|
|
245
|
+
--marquee-gap-inline · --marquee-mask-width。時間はモーション層
|
|
246
|
+
(foundation.css)に、幾何はコンポーネント層(tokens/components/marquee.css)にある。
|
|
247
|
+
</Text>
|
|
248
|
+
</Flex>
|
|
249
|
+
</CardContent>
|
|
250
|
+
</Card>
|
|
251
|
+
</Flex>
|
|
252
|
+
</PageContainer>
|
|
253
|
+
);
|
|
254
|
+
}
|