@nxgt/material 3.54.0 → 3.56.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 (132) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +8 -3
  3. package/dist/{async-slice-DDSsekfm.js → async-slice-CORlM_wu.js} +2 -2
  4. package/dist/{button-Ct8DSLVv.js → button-CdpVEza0.js} +1 -1
  5. package/dist/charts.js +3 -3
  6. package/dist/{chip-BzoNsd-W.js → chip-C0yNYz9m.js} +2 -2
  7. package/dist/{code-editor-DsLQeiZc.js → code-editor-Bwzp2038.js} +6 -6
  8. package/dist/code-editor.js +5 -5
  9. package/dist/{code-language-items-4zXlIEZ0.js → code-language-items-nP-pF7yH.js} +2 -2
  10. package/dist/components/components.css +8 -0
  11. package/dist/components/ui/display/seat-ring/seat-ring-index.css +125 -0
  12. package/dist/components/ui/display/seat-ring/seat-ring-ring.css +87 -0
  13. package/dist/components/ui/display/seat-ring/seat-ring.css +139 -0
  14. package/dist/components/ui/display/secret-card/secret-card.css +65 -0
  15. package/dist/components/ui/display/vote-tally/vote-tally.css +94 -0
  16. package/dist/{components-DFG2mgsM.js → components-Dl2hVfzt.js} +16273 -15482
  17. package/dist/components.js +26 -26
  18. package/dist/{dialog-DwPmwmA3.js → dialog-BVgsN9Ch.js} +4 -4
  19. package/dist/dnd.js +1 -1
  20. package/dist/{dropdown-menu-CDN6Nqx6.js → dropdown-menu-C8PBmMIa.js} +1 -1
  21. package/dist/editor.js +9 -9
  22. package/dist/{empty-state-Bb9W9did.js → empty-state-C5wfbbW9.js} +2 -2
  23. package/dist/{escape-owner-DI_lg2ID.js → escape-owner-CYnfySUl.js} +1 -1
  24. package/dist/{field-blur-BcJH8H9B.js → field-blur-Nid7-bt_.js} +1 -1
  25. package/dist/{field-blur-context-Dg1nwWOV.js → field-blur-context-DxdIuHAY.js} +1 -1
  26. package/dist/hooks.js +28 -28
  27. package/dist/{hover-card-Qse4gnop.js → hover-card-C_9jiwp1.js} +2 -2
  28. package/dist/{i18n-provider-DlVPS58N.js → i18n-provider-CLVZK6tQ.js} +1 -1
  29. package/dist/i18n.js +5 -5
  30. package/dist/{icon-badge-DVPBQOKe.js → icon-badge-Dcjg_SKJ.js} +3 -3
  31. package/dist/{icon-button-C3wDxsh6.js → icon-button-Bo-VuZ2n.js} +1 -1
  32. package/dist/lib/components/ui/actions/link/link.d.ts +8 -0
  33. package/dist/lib/components/ui/collections/data-table/partials/data-table-page-control.d.ts +1 -1
  34. package/dist/lib/components/ui/collections/data-table/partials/data-table-row.d.ts +1 -1
  35. package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-card.d.ts +1 -1
  36. package/dist/lib/components/ui/collections/server-table/partials/server-table-past-end.d.ts +1 -1
  37. package/dist/lib/components/ui/display/index.d.ts +3 -0
  38. package/dist/lib/components/ui/display/list-tile/partials/list-tile-line.d.ts +1 -1
  39. package/dist/lib/components/ui/display/period-cards/period-cards.d.ts +1 -1
  40. package/dist/lib/components/ui/display/seat-ring/index.d.ts +2 -0
  41. package/dist/lib/components/ui/display/seat-ring/partials/empty-seat.d.ts +5 -0
  42. package/dist/lib/components/ui/display/seat-ring/partials/seat-body.d.ts +9 -0
  43. package/dist/lib/components/ui/display/seat-ring/partials/seat-item.d.ts +17 -0
  44. package/dist/lib/components/ui/display/seat-ring/seat-ring.d.ts +9 -0
  45. package/dist/lib/components/ui/display/seat-ring/seat-ring.utils.d.ts +9 -0
  46. package/dist/lib/components/ui/display/seat-ring/types.d.ts +67 -0
  47. package/dist/lib/components/ui/display/seat-ring/use-seat-ring.d.ts +12 -0
  48. package/dist/lib/components/ui/display/secret-card/index.d.ts +2 -0
  49. package/dist/lib/components/ui/display/secret-card/secret-card.d.ts +11 -0
  50. package/dist/lib/components/ui/display/secret-card/types.d.ts +49 -0
  51. package/dist/lib/components/ui/display/secret-card/use-secret-card.d.ts +20 -0
  52. package/dist/lib/components/ui/display/vote-tally/announcer.d.ts +11 -0
  53. package/dist/lib/components/ui/display/vote-tally/index.d.ts +2 -0
  54. package/dist/lib/components/ui/display/vote-tally/partials/tally-bar.d.ts +8 -0
  55. package/dist/lib/components/ui/display/vote-tally/partials/tally-row.d.ts +9 -0
  56. package/dist/lib/components/ui/display/vote-tally/partials/tally-voters.d.ts +9 -0
  57. package/dist/lib/components/ui/display/vote-tally/types.d.ts +73 -0
  58. package/dist/lib/components/ui/display/vote-tally/use-announcement.d.ts +7 -0
  59. package/dist/lib/components/ui/display/vote-tally/use-tally-summary.d.ts +3 -0
  60. package/dist/lib/components/ui/display/vote-tally/vote-tally.d.ts +10 -0
  61. package/dist/lib/components/ui/display/vote-tally/vote-tally.utils.d.ts +18 -0
  62. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  63. package/dist/lib/components/ui/layout/account-menu/account-menu.d.ts +1 -1
  64. package/dist/lib/components/ui/layout/app-shell/app-sidebar-icon-item.d.ts +1 -1
  65. package/dist/lib/components/ui/layout/app-shell/app-sidebar-label-item.d.ts +1 -1
  66. package/dist/lib/components/ui/layout/app-shell/app-sidebar.d.ts +1 -1
  67. package/dist/lib/components/ui/layout/pager/pager.d.ts +1 -1
  68. package/dist/lib/components/ui/layout/see-also/see-also.d.ts +1 -1
  69. package/dist/lib/components/ui/layout/toc/toc.d.ts +1 -1
  70. package/dist/lib/components/ui/primitives/index.d.ts +1 -0
  71. package/dist/lib/components/ui/primitives/link/index.d.ts +2 -0
  72. package/dist/lib/components/ui/primitives/link/link-component-provider.d.ts +26 -0
  73. package/dist/lib/components/ui/widgets/education/index.d.ts +1 -0
  74. package/dist/lib/components/ui/widgets/education/navigator/index.d.ts +2 -0
  75. package/dist/lib/components/ui/widgets/education/navigator/partials/navigator-item.d.ts +10 -0
  76. package/dist/lib/components/ui/widgets/education/navigator/question-navigator.d.ts +27 -0
  77. package/dist/lib/components/ui/widgets/education/questions/index.d.ts +1 -1
  78. package/dist/lib/components/ui/widgets/education/questions/partials/question-media.d.ts +5 -0
  79. package/dist/lib/components/ui/widgets/education/questions/partials/question-verdict.d.ts +12 -0
  80. package/dist/lib/components/ui/widgets/education/questions/question-options.d.ts +2 -2
  81. package/dist/lib/components/ui/widgets/education/questions/question-text.d.ts +1 -1
  82. package/dist/lib/components/ui/widgets/education/questions/question.d.ts +1 -1
  83. package/dist/lib/components/ui/widgets/education/questions/types.d.ts +33 -1
  84. package/dist/lib/hooks/index.d.ts +1 -0
  85. package/dist/lib/hooks/use-countdown.d.ts +18 -0
  86. package/dist/lib/i18n/resources/en/en.d.ts +34 -0
  87. package/dist/lib/i18n/resources/en/material.json.d.ts +34 -0
  88. package/dist/lib/i18n/resources/fr/fr.d.ts +34 -0
  89. package/dist/lib/i18n/resources/fr/material.json.d.ts +34 -0
  90. package/dist/lib/i18n/resources/resources.d.ts +68 -0
  91. package/dist/lib/i18n/translator.d.ts +1 -1
  92. package/dist/lib.js +1 -1
  93. package/dist/main.js +43 -43
  94. package/dist/map.js +8 -8
  95. package/dist/material.css +1 -1
  96. package/dist/pdf-reader.js +1 -1
  97. package/dist/print.js +2 -2
  98. package/dist/{public-BoVE0ffh.js → public-BtKAthSs.js} +9 -9
  99. package/dist/{rich-code-block-DAUM7Yh_.js → rich-code-block-BZYZ6ewr.js} +5 -5
  100. package/dist/{sortable-provider-BLXsGE_0.js → sortable-provider-hUCwt69K.js} +2 -2
  101. package/dist/{spinner-Rg4PnBPH.js → spinner-BmUXTzvJ.js} +1 -1
  102. package/dist/{stored-language-B1lJMPbR.js → stored-language-_OGPBCn0.js} +64 -0
  103. package/dist/theme-picker.js +2 -2
  104. package/dist/{theme-toggle-jVdxF_mc.js → theme-toggle-DdvEafno.js} +1 -1
  105. package/dist/{toolbar-items-BFY_hns5.js → toolbar-items-DKfY_6AG.js} +4 -4
  106. package/dist/{translate-material-q-JNxP1m.js → translate-material-DzBMPErh.js} +2 -2
  107. package/dist/{translator-BU1o3_l9.js → translator-BIVSiq4X.js} +1 -1
  108. package/dist/{use-chat-composer-DGbDuIUa.js → use-chat-composer-DCQW2EJ8.js} +16 -16
  109. package/dist/{use-heading-spy-CP63sAcS.js → use-heading-spy-Cr2C_Px8.js} +32 -5
  110. package/dist/{use-material-translation-nNF8fUXz.js → use-material-translation-C_bAxfvp.js} +2 -2
  111. package/docs/components/README.md +1 -1
  112. package/docs/components/buttons-and-actions.md +1 -0
  113. package/docs/components/data-display.md +212 -2
  114. package/docs/components/data-table-filter-and-lists.md +3 -3
  115. package/docs/components/feedback-and-status.md +5 -3
  116. package/docs/components/navigation-and-layout.md +4 -4
  117. package/docs/components/primitives.md +62 -0
  118. package/docs/components/rich-widgets.md +51 -0
  119. package/docs/components/selection-and-choice.md +1 -1
  120. package/docs/guides/app-shell.md +2 -2
  121. package/docs/guides/helpers.md +40 -0
  122. package/docs/previews/data-display-seatring.webp +0 -0
  123. package/docs/previews/data-display-secretcard.webp +0 -0
  124. package/docs/previews/data-display-votetally.webp +0 -0
  125. package/lib/components/components.css +8 -0
  126. package/lib/components/ui/display/seat-ring/seat-ring-index.css +125 -0
  127. package/lib/components/ui/display/seat-ring/seat-ring-ring.css +87 -0
  128. package/lib/components/ui/display/seat-ring/seat-ring.css +139 -0
  129. package/lib/components/ui/display/secret-card/secret-card.css +65 -0
  130. package/lib/components/ui/display/vote-tally/vote-tally.css +94 -0
  131. package/package.json +1 -1
  132. package/dist/lib/components/ui/feedback/countdown-timer/use-countdown.d.ts +0 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @nxgt/material
2
2
 
3
+ ## 3.56.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1952](https://github.com/softistx/nxgt-material/pull/1952) [`1f3d75d`](https://github.com/softistx/nxgt-material/commit/1f3d75dc6fec114442c911b57de69b5c882cd37e) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New `LinkComponentProvider` in `@nxgt/material/components`: set your router's link once (`<LinkComponentProvider component={RouterLink}>`) and `LinkButton`, `IconLinkButton`, `ResponsiveLinkButton`, the sidebar and app shell, `AccountMenu`, `Pager`, `SeeAlso`, `Toc`, `PeriodCards`, `NotificationRow`, `Doc`, `DataTable`, `ServerTable`, `ListEmpty`, `Switcher`, `ListTile`, `PageFooter`, `Notifications`, `Breadcrumb`, `Pagination`, `LinkCard` and `QuestionNavigator` render their links with it. A component's own `linkComponent` prop still wins, and nothing changes without the provider. `LinkComponent` and `LinkComponentProps` are now exported, and `useLinkComponent()` reads the provider's link in your own components. The link buttons gain a `linkComponent` prop. Fixes [#1950](https://github.com/softistx/nxgt-material/issues/1950).
8
+
9
+ - [#1936](https://github.com/softistx/nxgt-material/pull/1936) [`4078fc4`](https://github.com/softistx/nxgt-material/commit/4078fc456d91eafdb4622b6edd3762adb1f89fcc) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `QuestionCard`: a server-given `verdict` (`'correct' | 'incorrect' | 'pending'`) is shown as given, with no comparison against `question.correct` in the browser (the correct choices can still be passed to highlight them). `points` (`{ awarded, possible }`) and `feedback` fill the verdict banner, and `pending` reads "Awaiting grading" with a clock icon. `question.media` also accepts audio (an `AudioPlayer`) and a document link (a `FilePreview`) through `kind` or a MIME `type` and a `name` as text alternative. New types `QuestionMedia`, `QuestionMediaKind`, `QuestionVerdict`, `QuestionPoints`. `revealed` without `verdict` is unchanged ([#1933](https://github.com/softistx/nxgt-material/issues/1933)).
10
+
11
+ - [#1949](https://github.com/softistx/nxgt-material/pull/1949) [`1a1b722`](https://github.com/softistx/nxgt-material/commit/1a1b722b7fe8091ac811611d2dd32797f25c484c) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New `QuestionNavigator` in `@nxgt/material/widgets/education`: the numbered questions of a quiz or exam player, with `count`, `current` (from 1), `answered` and `flagged` (arrays or Sets), and `onSelect(n)` or `getHref(n)` for a link variant. Targets are at least 44px, the current one has `aria-current="step"`, answered and flagged show an icon and are named in the accessible label, and it stays one scrolling row that keeps the current number in view. Labels under `material.question-navigator.*` (en, fr). Fixes [#1947](https://github.com/softistx/nxgt-material/issues/1947).
12
+
13
+ - [#1939](https://github.com/softistx/nxgt-material/pull/1939) [`1693f0f`](https://github.com/softistx/nxgt-material/commit/1693f0fb28053603a02bb101473b63b239770f81) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New `SecretCard`: a card whose secret (`back`) is not rendered, so it is not in the markup, read out or copied, until a deliberate click or tap on its reveal button, a disclosure with one name and `aria-expanded` (and `aria-controls`). Hide it again with the same button, or let `autoHideMs` do it. `revealed` with `onRevealedChange` controls it, `defaultRevealed` starts it open; the two never go together. `title`, `description` (a hint shown while hidden), `icon`, `tone`, `front` (the hidden face), `actions` and `labels` shape it. Changes a click does not explain (auto-hide, the parent) are announced through a polite live region, focus moves to the button when the face that held it is swapped, the flip runs only after a change and without reduced motion, and it is full width up to 28rem with a 44px button. New `material.secret-card.*` keys in English and French. Closes [#1912](https://github.com/softistx/nxgt-material/issues/1912).
14
+
15
+ - [#1946](https://github.com/softistx/nxgt-material/pull/1946) [`5e2b8c9`](https://github.com/softistx/nxgt-material/commit/5e2b8c99c3e95cf08c5ce29baaaa1afc3f14f44a) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `useCountdown` is public, from `@nxgt/material/hooks`: the clock behind `CountdownTimer`, for a button or a message that waits (a rate limit's `retryAfter`, a resend delay). `useCountdown(duration, { paused, onExpire, key })` returns the seconds left, ticking on the client only; a new `duration` **or a new `key`** starts over, so the same duration twice restarts it. `CountdownTimer` is unchanged.
16
+
17
+ - [#1942](https://github.com/softistx/nxgt-material/pull/1942) [`803afd5`](https://github.com/softistx/nxgt-material/commit/803afd57f5215b7d48081799015549b61632da16) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New `VoteTally`: a live ranked tally of a vote, poll or ballot. One row per option with its count and share written as text ("Ada Example, 3 votes, 60%"), a decorative bar, and who voted for it as an avatar group or a line of names. `sort` ranks by count (equal counts share a rank), a single top option is marked "Leader" and several are each marked "Tie" (never an arbitrary leader), `selectedId` marks "Your vote", and `abstainCount` shows abstentions beneath the bars. Bars ease only without `prefers-reduced-motion` (`animated` turns it off); a polite, debounced live status (`announceDelay`) says where the vote stands after live changes. `renderOption`, `renderVoters`, `header` and `footer` replace the defaults. Bar widths are SVG attributes, so server markup carries no `style`. New `material.vote-tally.*` keys in English and French. Closes [#1913](https://github.com/softistx/nxgt-material/issues/1913).
18
+
19
+ ## 3.55.0
20
+
21
+ ### Minor Changes
22
+
23
+ - [#1930](https://github.com/softistx/nxgt-material/pull/1930) [`ea953b1`](https://github.com/softistx/nxgt-material/commit/ea953b15b2d0878c065d4df29d8d5e9e2e5e94cd) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New `SeatRing`: participants as seats around a circle, or in a two-column grid when its own box is narrow (a container query, so it follows a column or a dialog, not the window). Each seat takes an avatar (default: the label's initials), a label, a status written in words with a tone, a badge and `disabled` / `empty` flags; `renderAvatar`, `renderStatus`, `renderBadge`, `renderEmptySeat` and `centerSlot` replace the defaults, `highlightedId` marks whose turn it is, and `size` is `sm`, `md` or `lg`. Without `selectionMode` it is a `list`; with `selectionMode="single"` or `"multiple"` (`value`, `defaultValue`, `onValueChange`) it is a `listbox` of `option`s with a roving tab stop: arrows, Home and End move, Space or Enter picks, and the selection is shown with a check as well as a tint. New `material.seat-ring.empty` key in English and French. Closes [#1911](https://github.com/softistx/nxgt-material/issues/1911).
24
+
3
25
  ## 3.54.0
4
26
 
5
27
  ### Minor Changes
package/README.md CHANGED
@@ -19,7 +19,8 @@ drag-and-drop, i18n, and `Icon`. Tailwind CSS v4 + Base UI.
19
19
  - **Helpers** ([helpers guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/helpers.md)): `cn`, `clientOnly`,
20
20
  date, number, storage and URL utilities, a Zod error map, and Redux Toolkit
21
21
  slice factories in `/lib`. Theme, i18n-aware formatting, option, focus
22
- (`useFocusOnMount`) and one-submit (`useSubmitOnce`) hooks are in `/hooks`.
22
+ (`useFocusOnMount`), countdown (`useCountdown`) and one-submit
23
+ (`useSubmitOnce`) hooks are in `/hooks`.
23
24
  - **i18n** ([i18n guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/i18n.md)): an `I18nProvider` with ICU
24
25
  messages and shipped `en` / `fr` strings. Your app merges in its own
25
26
  resources.
@@ -217,7 +218,7 @@ reads `duotone.svg`, the file that exists (it used to ask for the missing
217
218
  | `@nxgt/material/draw/png` | `drawingToPng(sceneOrSvg, { scale?, background?, colorsFrom? })` and `downloadDrawingPng(source, fileName)` — a drawing (a scene, or a live `<svg>` such as `svgRef.current` of a `<Drawing ref={svgRef}>`) as a PNG `Blob` in the browser, with the page's theme colours resolved, `data:` images inlined and CORS errors named; its own entry, DOM only when called ([drawing guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/drawing.md#png), [reference](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/draw.md#png-nxgtmaterial-coredrawpng)) |
218
219
  | `@nxgt/material/vite` | build-time helpers for consumers: `nxgtMaterial()` (a strict CSP by hash or nonce, self-hosted fonts, a styles check, serves the pdf.js worker on the Vite 5–7 dev server, generates your app themes' stylesheets with `nxgtMaterial({ themes })` and the `nxgt-theme` bin: [Themes guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/themes.md), a reduced backdrop icon sprite from your Font Awesome install with `nxgtMaterial({ backdropSprite })` and the `nxgt-backdrop-sprite` bin, PDFs on a bundled server with `nxgtMaterial({ pdf: { bundle: true } })`: [Vite plugin guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/vite-plugin.md)), `pdfkitStandardFonts()` ([printing guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/printing.md)), `cspAssetsInlineLimit`, a `build.assetsInlineLimit` that never inlines a font ([CSP guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/csp.md#katex-fonts)) |
219
220
  | `@nxgt/material/next` | `withNxgtPrint(nextConfig)`: the `next.config.ts` a Next.js route handler that imports `/print` needs, `output: 'standalone'` included. Node only, no dependency on `next` ([printing guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/printing.md#nextjs-serverexternalpackages-and-output-standalone)) |
220
- | `@nxgt/material/hooks` | `useTheme`, `useIsClient`, `useIsMobile`, `useLocalizedDate`, `useCspNonce`, `useFocusOnMount`, `useSubmitOnce`, … ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/helpers.md#nxgtmaterialhooks)) |
221
+ | `@nxgt/material/hooks` | `useTheme`, `useIsClient`, `useIsMobile`, `useLocalizedDate`, `useCspNonce`, `useFocusOnMount`, `useCountdown`, `useSubmitOnce`, … ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/helpers.md#nxgtmaterialhooks)) |
221
222
  | `@nxgt/material/dnd` | `@dnd-kit` primitives ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/helpers.md#nxgtmaterialdnd)) |
222
223
  | `@nxgt/material/i18n` | `I18nProvider`, `mergeResources`, `createTranslator`, `translateMaterial`, `formatMessage` ([i18n guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/i18n.md)) |
223
224
  | `@nxgt/material/lib` | `cn`, `clientOnly`, date, number and storage utilities, `useSliceReducer`, `getCspNonce` / `setCspNonce`, the server-side CSP helpers (`createCspNonce`, `cspPolicy`, `cspHash`, `applyCspNonce`, `CSP_NONCE_PLACEHOLDER`, `ZOD_JITLESS_SCRIPT`, `zodJitlessTag`: [CSP, SSR](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/csp.md#ssr)) ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/helpers.md#nxgtmateriallib)) |
@@ -500,6 +501,7 @@ Not components: what the stylesheet gives any element.
500
501
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/primitives-togglegroup.webp" width="120" alt="ToggleGroup"> | [`ToggleGroup`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/primitives.md#togglegroup), `ToggleGroupItem`, `ToggleGroupSeparator` | Toggle group in three looks: connected (pill morph), segmented (sliding pill), separate; sizes sm, md, lg; horizontal or vertical. | `ToggleGroup`, `ToggleGroupItem` |
501
502
  | — | [`Activity`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/primitives.md#activity) | React `<Activity>` switched by a boolean `visible`. | `Activity` |
502
503
  | — | [`NonceProvider`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/primitives.md#nonceprovider) | Gives the page's CSP nonce to every component below; `useCspNonce()` reads it ([CSP guide](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/guides/csp.md#nonceprovider)). | `NonceProvider` |
504
+ | — | [`LinkComponentProvider`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/primitives.md#linkcomponentprovider) | Sets, once, the link component (your router's `Link`) of the components that render a link (buttons, shell, tables, pager, notifications, doc); `LinkComponentProps` types the adapter and `useLinkComponent()` reads it. | `LinkComponentProvider`, `useLinkComponent`, `LinkComponent`, `LinkComponentProps` |
503
505
 
504
506
  ### Buttons & actions
505
507
 
@@ -599,6 +601,8 @@ Not components: what the stylesheet gives any element.
599
601
  | --- | --- | --- | --- |
600
602
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-avatar.webp" width="120" alt="Avatar"> | [`Avatar`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#avatar), `AvatarImage`, `AvatarFallback`, `AvatarBase` | Image with a fallback and an optional online / offline dot; `shape` clips it to an expressive shape; the initials scale with the size (40 % of the diameter); `tone` colours the fallback. | `Avatar`, `AvatarImage`, `AvatarFallback` |
601
603
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-avatargroup.webp" width="120" alt="AvatarGroup"> | [`AvatarGroup`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#avatargroup) | Overlapping avatars with a `+N` overflow. | `AvatarGroup` |
604
+ | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-seatring.webp" width="120" alt="SeatRing"> | [`SeatRing`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#seatring) | Participants as seats around a circle, or in a grid on a narrow box; avatar, name, status, badge and centre slots; optionally a listbox to pick one or several (`selectionMode`). | — |
605
+ | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-secretcard.webp" width="120" alt="SecretCard"> | [`SecretCard`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#secretcard) | A card whose secret is not rendered until a deliberate tap or click reveals it (a role, a one-time code, a recovery key); hide again, optional `autoHideMs`, controlled or not, polite announcement. | — |
602
606
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-shape.webp" width="120" alt="Shape"> | [`Shape`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#shape) | Material 3 Expressive's 35 shapes and a squircle as a backdrop, an icon container or a mask for a photo, with a morph; eight tones or an app tone. | — |
603
607
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-badge.webp" width="120" alt="Badge"> | [`Badge`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#badge) | Status pill, 20 px: default, secondary, error, success, info, warning, `soft` (tonal, for states), outlined; an optional decorative `icon` before the words. | `Badge` |
604
608
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-countbadge.webp" width="120" alt="CountBadge"> | [`CountBadge`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#countbadge) | Count in the corner of its children, capped at `99+` and hidden at 0; `animated` pops it in when it changes. | `CountBadge` |
@@ -618,6 +622,7 @@ Not components: what the stylesheet gives any element.
618
622
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-timeline.webp" width="120" alt="Timeline"> | [`Timeline`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#timeline), `TimelineItem`, `RelativeTime` | Vertical event timeline with shaped, toned markers, cards, alternating sides in a wide container, optional day groups and relative times. | `Timeline`, `TimelineItem`, `RelativeTime` |
619
623
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-changehistory.webp" width="120" alt="ChangeHistory"> | [`ChangeHistory`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#changehistory) | History of the changes made to anything (a document, a project's settings, a file): a rail of toned dots or tinted cards with a Before / After block, a heading per day (sticky on the rail), long entries collapsed behind "N fields changed", redacted values shown as "Hidden", the exact time on hover and focus, load more. | — (later, #1626) |
620
624
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-zonedtime.webp" width="120" alt="ZonedTime"> | [`ZonedTime`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#zonedtime) | A moment in a named IANA zone, with the zone as a computed offset (`Mar 12, 2026, 4:04 PM UTC+1`), identical on the server and the client (no hydration warning): the date and the time are formatted apart, `seconds` and `dateStyle` are optional, in a `<time dateTime>`. | — |
625
+ | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-votetally.webp" width="120" alt="VoteTally"> | [`VoteTally`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#votetally) | A live ranked tally of a vote: one row per option with its count and share as text, a decorative bar, and who voted for it (avatars or names); marks the leader, a tie and the viewer's own vote in words, counts abstentions, eases bars only without reduced motion, and announces changes through a debounced polite status. | — |
621
626
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-eventchip.webp" width="120" alt="EventChip"> | [`EventChip`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#eventchip) | Calendar event pill — soft, solid or outlined — with its times, all-day and continuation edges. | `EventChip` |
622
627
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-codeblock.webp" width="120" alt="CodeBlock"> | [`CodeBlock`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#codeblock), `JsonViewer`, `CodeGroup` | Highlighted code with line numbers and copy, a collapsible JSON tree, and tabbed samples (`Tabs`, underlined). `CodeDiff` is under Rich widgets. | `CodeBlock`, `JsonViewer`, `CodeGroup` |
623
628
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/data-display-markdown.webp" width="120" alt="Markdown"> | [`Markdown`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/data-display.md#markdown), `MermaidDiagram` | GFM markdown with KaTeX math and mermaid diagrams (`clientOnly`). Fences reuse `CodeBlock`. `ChatMarkdown` renders through it. | `Markdown`, `MermaidDiagram` |
@@ -758,7 +763,7 @@ Not components: what the stylesheet gives any element.
758
763
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/rich-widgets-richtextviewer.webp" width="120" alt="RichTextViewer"> | [`RichTextViewer`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/rich-widgets.md#richtextviewer) | Read-only view of a saved document (HTML, JSON or Markdown) with the editor's typography; sanitised, server-safe, no editor. From `@nxgt/material/editor/viewer`. | — (a `vue` issue, not a port) |
759
764
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/rich-widgets-codeeditor.webp" width="120" alt="CodeEditor"> | [`CodeEditor`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/rich-widgets.md#codeeditor) · `CodeEditorFormField` | Monaco code field in theme presets (App, Moonlight, Soft, or your own `defineCodeTheme`), with a floating tool pill, a status bar, `markers` for your own problems, a `readOnly` reader look, view props, a `ref` handle, file tabs (`files`) and full screen. From `@nxgt/material/code-editor`. | `CodeEditor`, `CodeEditorFormField` |
760
765
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/rich-widgets-codeeditor-diff.webp" width="120" alt="CodeDiff"> | [`CodeDiff`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/rich-widgets.md#codediff) | A Monaco diff in the code editor's frame and themes: side by side or inline (always inline on a phone), previous/next change, `+N −M` counts and copy. From `@nxgt/material/code-editor`. | `CodeDiff` |
761
- | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/rich-widgets-quiz.webp" width="120" alt="Quiz"> | [`Quiz`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/rich-widgets.md#quiz), `QuestionCard` | Exam or practice quizzes: step progress, instant feedback with explanations and hints, a time limit, keyboard shortcuts, and a results screen with a score ring, a breakdown and a retry of the misses. Container queries. | `Quiz`, `QuestionCard` (modes and results: React only for now) |
766
+ | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/rich-widgets-quiz.webp" width="120" alt="Quiz"> | [`Quiz`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/rich-widgets.md#quiz), `QuestionCard` · [`QuestionNavigator`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/rich-widgets.md#questionnavigator) | Exam or practice quizzes: step progress, instant feedback with explanations and hints, a time limit, keyboard shortcuts, and a results screen with a score ring, a breakdown and a retry of the misses. Container queries. | `Quiz`, `QuestionCard` (modes and results: React only for now); `QuestionNavigator` React only for now |
762
767
  | <img src="https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/previews/rich-widgets-drawing.webp" width="120" alt="Drawing"> | [`Drawing`, `Rect`, `Ellipse`, `Circle`, `Line`, `Polygon`, `Path`, `Text`, `Group`, `Image`](https://cdn.jsdelivr.net/npm/@nxgt/material@3/docs/components/rich-widgets.md#drawing) from `@nxgt/material/draw` | Vector drawings from a scene of plain data or from JSX: theme-role colours that follow light and dark, no inline style (strict CSP), a `title` or `decorative` enforced by the type, focusable nodes with `onClick` and a focus ring, text that wraps with Inter's metrics and is refined by the browser. Client-only entry. | React only for now |
763
768
 
764
769
  `RadioGroupField`, `SliderField` and `SwitchField` are aliases of the
@@ -1,5 +1,5 @@
1
- import { i as e, r as t, t as n } from "./stored-language-B1lJMPbR.js";
2
- import { r } from "./translator-BU1o3_l9.js";
1
+ import { i as e, r as t, t as n } from "./stored-language-_OGPBCn0.js";
2
+ import { r } from "./translator-BIVSiq4X.js";
3
3
  import { t as i } from "./_baseIsEqual-C6ksIHya.js";
4
4
  import { useMemo as a } from "react";
5
5
  import { asyncThunkCreator as o, buildCreateSlice as s } from "@reduxjs/toolkit";
@@ -1,7 +1,7 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
2
  import { t } from "./is-production-GLw59WU8.js";
3
3
  import { n, t as r } from "./dist-DVRQkaTK.js";
4
- import { n as i, r as a, t as o } from "./spinner-Rg4PnBPH.js";
4
+ import { n as i, r as a, t as o } from "./spinner-BmUXTzvJ.js";
5
5
  import { a as s, i as c, n as l, o as u, s as d, t as f } from "./use-click-lock-BZYtTmVt.js";
6
6
  import { t as p } from "./button-group-context-CaP0X5p_.js";
7
7
  import { t as m } from "./has-text-content-Ds2dkHX_.js";
package/dist/charts.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { t as e } from "./ui.utils-CAnKTCw5.js";
2
2
  import { t } from "./utils-BLfntITJ.js";
3
3
  import { t as n } from "./warn-once-C_e47JM8.js";
4
- import { t as r } from "./use-material-translation-nNF8fUXz.js";
5
- import { t as i } from "./button-Ct8DSLVv.js";
4
+ import { t as r } from "./use-material-translation-C_bAxfvp.js";
5
+ import { t as i } from "./button-CdpVEza0.js";
6
6
  import { t as a } from "./use-ssr-media-query-C6M5m8sf.js";
7
7
  import { i as o, n as s, r as c, t as l } from "./util-4heV2LPI.js";
8
8
  import { t as u } from "./useIsomorphicLayoutEffect-DfgAhWcH.js";
9
- import { t as d } from "./chip-BzoNsd-W.js";
9
+ import { t as d } from "./chip-C0yNYz9m.js";
10
10
  import { c as f, d as p, f as m, i as h, o as g, p as _, s as v, t as y } from "./line-path-C5pZGC49.js";
11
11
  import { t as b } from "./live-status-BGPaN8r8.js";
12
12
  import { t as x } from "./linear-G97mfnKF.js";
@@ -1,7 +1,7 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
- import { r as t } from "./spinner-Rg4PnBPH.js";
2
+ import { r as t } from "./spinner-BmUXTzvJ.js";
3
3
  import { s as n } from "./use-click-lock-BZYtTmVt.js";
4
- import { t as r } from "./use-material-translation-nNF8fUXz.js";
4
+ import { t as r } from "./use-material-translation-C_bAxfvp.js";
5
5
  import { t as i } from "./react-node-text-Crkpfpco.js";
6
6
  import { cloneElement as a, isValidElement as o } from "react";
7
7
  import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
@@ -2,13 +2,13 @@ import { r as e } from "./rolldown-runtime-BFFgcFxR.js";
2
2
  import { t } from "./utils-BLfntITJ.js";
3
3
  import { t as n } from "./is-production-GLw59WU8.js";
4
4
  import { a as r } from "./dist-lmHmz_bu.js";
5
- import { c as i, d as a, f as o, n as s, t as c } from "./dropdown-menu-CDN6Nqx6.js";
6
- import { a as l, i as u, n as d } from "./field-blur-context-Dg1nwWOV.js";
7
- import { a as f, d as p, r as m, t as h, u as g } from "./toolbar-items-BFY_hns5.js";
8
- import { t as _ } from "./use-material-translation-nNF8fUXz.js";
9
- import { i as v, n as y, o as b, t as x } from "./code-language-items-4zXlIEZ0.js";
5
+ import { c as i, d as a, f as o, n as s, t as c } from "./dropdown-menu-C8PBmMIa.js";
6
+ import { a as l, i as u, n as d } from "./field-blur-context-DxdIuHAY.js";
7
+ import { a as f, d as p, r as m, t as h, u as g } from "./toolbar-items-DKfY_6AG.js";
8
+ import { t as _ } from "./use-material-translation-C_bAxfvp.js";
9
+ import { i as v, n as y, o as b, t as x } from "./code-language-items-nP-pF7yH.js";
10
10
  import { t as S } from "./useIsomorphicLayoutEffect-DfgAhWcH.js";
11
- import { n as C, t as w } from "./escape-owner-DI_lg2ID.js";
11
+ import { n as C, t as w } from "./escape-owner-CYnfySUl.js";
12
12
  import { t as T } from "./skeleton-DSdtr2qc.js";
13
13
  import { t as E } from "./react-node-text-Crkpfpco.js";
14
14
  import D, { memo as O, useCallback as ee, useEffect as k, useId as A, useImperativeHandle as j, useMemo as M, useRef as N, useState as P } from "react";
@@ -1,14 +1,14 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
2
  import { a as t } from "./dist-lmHmz_bu.js";
3
- import { d as n, r, t as i, u as a } from "./toolbar-items-BFY_hns5.js";
4
- import { t as o } from "./use-material-translation-nNF8fUXz.js";
5
- import { i as s, n as c, o as l } from "./code-language-items-4zXlIEZ0.js";
6
- import { r as u, t as d } from "./field-blur-BcJH8H9B.js";
3
+ import { d as n, r, t as i, u as a } from "./toolbar-items-DKfY_6AG.js";
4
+ import { t as o } from "./use-material-translation-C_bAxfvp.js";
5
+ import { i as s, n as c, o as l } from "./code-language-items-nP-pF7yH.js";
6
+ import { r as u, t as d } from "./field-blur-Nid7-bt_.js";
7
7
  import { t as f } from "./skeleton-DSdtr2qc.js";
8
8
  import { t as p } from "./live-status-BGPaN8r8.js";
9
9
  import { t as m } from "./use-mobile-BDro060N.js";
10
10
  import { t as h } from "./react-node-text-Crkpfpco.js";
11
- import { a as g, c as _, d as v, f as y, i as b, l as x, m as S, o as C, p as w, r as T, s as E, t as D, u as O } from "./code-editor-DsLQeiZc.js";
11
+ import { a as g, c as _, d as v, f as y, i as b, l as x, m as S, o as C, p as w, r as T, s as E, t as D, u as O } from "./code-editor-Bwzp2038.js";
12
12
  import { useEffect as k, useRef as A, useState as j } from "react";
13
13
  import { Fragment as M, jsx as N, jsxs as P } from "react/jsx-runtime";
14
14
  import { ChevronDown as F, ChevronUp as I, Columns2 as L } from "lucide-react";
@@ -1,5 +1,5 @@
1
- import { d as e, f as t } from "./dropdown-menu-CDN6Nqx6.js";
2
- import { t as n } from "./use-material-translation-nNF8fUXz.js";
1
+ import { d as e, f as t } from "./dropdown-menu-C8PBmMIa.js";
2
+ import { t as n } from "./use-material-translation-C_bAxfvp.js";
3
3
  import { t as r } from "./live-status-BGPaN8r8.js";
4
4
  import { useEffect as i, useRef as a, useState as o } from "react";
5
5
  import { jsx as s } from "react/jsx-runtime";
@@ -134,6 +134,14 @@
134
134
  @import "./ui/display/period-cards/period-cards.css";
135
135
  @import "./ui/layout/sidebar/sidebar-tokens.css";
136
136
  @import "./ui/display/avatar/avatar.css";
137
+ /* SeatRing: seats as a grid or on a circle by its own width. */
138
+ @import "./ui/display/seat-ring/seat-ring.css";
139
+ @import "./ui/display/seat-ring/seat-ring-index.css";
140
+ @import "./ui/display/seat-ring/seat-ring-ring.css";
141
+ /* VoteTally: ranked bars with counts, shares and voters. */
142
+ @import "./ui/display/vote-tally/vote-tally.css";
143
+ /* SecretCard: a face that is not rendered until it is revealed. */
144
+ @import "./ui/display/secret-card/secret-card.css";
137
145
  /* Alert and Banner span the row in the form grids (#1497). */
138
146
  @import "./ui/feedback/alert/alert.css";
139
147
  @import "./ui/display/stat-card/stat-card.css";
@@ -0,0 +1,125 @@
1
+ /* SeatRing: the seat count `--n` and the seat's index `--i`, from data
2
+ * attributes, for the circle's trigonometry (seat-ring-ring.css). Server
3
+ * markup carries no `style`. Up to 10 seats (RING_LIMIT); more is a grid. */
4
+ @layer components {
5
+ .nxgt-seat-ring[data-count="1"] {
6
+ --n: 1;
7
+ }
8
+ .nxgt-seat-ring[data-count="2"] {
9
+ --n: 2;
10
+ }
11
+ .nxgt-seat-ring[data-count="3"] {
12
+ --n: 3;
13
+ }
14
+ .nxgt-seat-ring[data-count="4"] {
15
+ --n: 4;
16
+ }
17
+ .nxgt-seat-ring[data-count="5"] {
18
+ --n: 5;
19
+ }
20
+ .nxgt-seat-ring[data-count="6"] {
21
+ --n: 6;
22
+ }
23
+ .nxgt-seat-ring[data-count="7"] {
24
+ --n: 7;
25
+ }
26
+ .nxgt-seat-ring[data-count="8"] {
27
+ --n: 8;
28
+ }
29
+ .nxgt-seat-ring[data-count="9"] {
30
+ --n: 9;
31
+ }
32
+ .nxgt-seat-ring[data-count="10"] {
33
+ --n: 10;
34
+ }
35
+ .nxgt-seat-ring[data-count="11"] {
36
+ --n: 11;
37
+ }
38
+ .nxgt-seat-ring[data-count="12"] {
39
+ --n: 12;
40
+ }
41
+ .nxgt-seat-ring[data-count="13"] {
42
+ --n: 13;
43
+ }
44
+ .nxgt-seat-ring[data-count="14"] {
45
+ --n: 14;
46
+ }
47
+ .nxgt-seat-ring[data-count="15"] {
48
+ --n: 15;
49
+ }
50
+ .nxgt-seat-ring[data-count="16"] {
51
+ --n: 16;
52
+ }
53
+ .nxgt-seat-ring[data-count="17"] {
54
+ --n: 17;
55
+ }
56
+ .nxgt-seat-ring[data-count="18"] {
57
+ --n: 18;
58
+ }
59
+ .nxgt-seat-ring[data-count="19"] {
60
+ --n: 19;
61
+ }
62
+ .nxgt-seat-ring[data-count="20"] {
63
+ --n: 20;
64
+ }
65
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="0"] {
66
+ --i: 0;
67
+ }
68
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="1"] {
69
+ --i: 1;
70
+ }
71
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="2"] {
72
+ --i: 2;
73
+ }
74
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="3"] {
75
+ --i: 3;
76
+ }
77
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="4"] {
78
+ --i: 4;
79
+ }
80
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="5"] {
81
+ --i: 5;
82
+ }
83
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="6"] {
84
+ --i: 6;
85
+ }
86
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="7"] {
87
+ --i: 7;
88
+ }
89
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="8"] {
90
+ --i: 8;
91
+ }
92
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="9"] {
93
+ --i: 9;
94
+ }
95
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="10"] {
96
+ --i: 10;
97
+ }
98
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="11"] {
99
+ --i: 11;
100
+ }
101
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="12"] {
102
+ --i: 12;
103
+ }
104
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="13"] {
105
+ --i: 13;
106
+ }
107
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="14"] {
108
+ --i: 14;
109
+ }
110
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="15"] {
111
+ --i: 15;
112
+ }
113
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="16"] {
114
+ --i: 16;
115
+ }
116
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="17"] {
117
+ --i: 17;
118
+ }
119
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="18"] {
120
+ --i: 18;
121
+ }
122
+ .nxgt-seat-ring [data-slot="seat-ring-seat"][data-index="19"] {
123
+ --i: 19;
124
+ }
125
+ }
@@ -0,0 +1,87 @@
1
+ /* SeatRing's circle: CSS trigonometry on `--i`, `--n` (seat-ring-index.css)
2
+ * and `--seat-dir` (-1 in a right-to-left box, so the ring is mirrored). The
3
+ * three container queries only switch `--ring-k` on the orbit once the box
4
+ * fits the seat size (32rem small, 38rem medium, 46rem large); every rule
5
+ * below reads it, as 0 (the grid of seat-ring.css) or 1 (the circle), so the
6
+ * circle's rules are written once. */
7
+ @layer components {
8
+ @container seat-ring (min-width: 32rem) {
9
+ .nxgt-seat-ring[data-layout="ring"][data-size="sm"]
10
+ [data-slot="seat-ring-orbit"] {
11
+ --ring-k: 1;
12
+ --ring-abs: absolute;
13
+ --ring-block: block;
14
+ --ring-ratio: 1;
15
+ --ring-margin: auto;
16
+ --ring-size: min(
17
+ 100%,
18
+ max(calc(var(--seat) * 3.2), calc(var(--seat) * (var(--n) * 0.6 + 1))),
19
+ 56rem
20
+ );
21
+ --ring-w: var(--seat);
22
+ }
23
+ }
24
+ @container seat-ring (min-width: 38rem) {
25
+ .nxgt-seat-ring[data-layout="ring"][data-size="md"]
26
+ [data-slot="seat-ring-orbit"] {
27
+ --ring-k: 1;
28
+ --ring-abs: absolute;
29
+ --ring-block: block;
30
+ --ring-ratio: 1;
31
+ --ring-margin: auto;
32
+ --ring-size: min(
33
+ 100%,
34
+ max(calc(var(--seat) * 3.2), calc(var(--seat) * (var(--n) * 0.6 + 1))),
35
+ 56rem
36
+ );
37
+ --ring-w: var(--seat);
38
+ }
39
+ }
40
+ @container seat-ring (min-width: 46rem) {
41
+ .nxgt-seat-ring[data-layout="ring"][data-size="lg"]
42
+ [data-slot="seat-ring-orbit"] {
43
+ --ring-k: 1;
44
+ --ring-abs: absolute;
45
+ --ring-block: block;
46
+ --ring-ratio: 1;
47
+ --ring-margin: auto;
48
+ --ring-size: min(
49
+ 100%,
50
+ max(calc(var(--seat) * 3.2), calc(var(--seat) * (var(--n) * 0.6 + 1))),
51
+ 56rem
52
+ );
53
+ --ring-w: var(--seat);
54
+ }
55
+ }
56
+
57
+ .nxgt-seat-ring [data-slot="seat-ring-orbit"] {
58
+ position: relative;
59
+ display: var(--ring-block, flex);
60
+ inline-size: var(--ring-size, auto);
61
+ aspect-ratio: var(--ring-ratio, auto);
62
+ margin-inline: var(--ring-margin, 0);
63
+ }
64
+ .nxgt-seat-ring [data-slot="seat-ring-list"] {
65
+ position: var(--ring-abs, static);
66
+ inset: 0;
67
+ }
68
+ .nxgt-seat-ring [data-slot="seat-ring-seat"] {
69
+ --a: calc(360deg * var(--i, 0) / var(--n, 1));
70
+ position: var(--ring-abs, relative);
71
+ inline-size: var(--ring-w, auto);
72
+ left: calc(
73
+ var(--ring-k, 0) *
74
+ (50% + (50% - var(--seat) / 2) * sin(var(--a)) * var(--seat-dir))
75
+ );
76
+ top: calc(
77
+ var(--ring-k, 0) *
78
+ (50% - (50% - var(--seat) / 2) * cos(var(--a)))
79
+ );
80
+ translate: calc(var(--ring-k, 0) * -50%) calc(var(--ring-k, 0) * -50%);
81
+ }
82
+ .nxgt-seat-ring [data-slot="seat-ring-center"] {
83
+ position: var(--ring-abs, static);
84
+ /* The square inside the circle of seats: the orbit's radius less a seat and a half. */
85
+ inset: calc(50% - 0.5 * max(0px, 50% - 1.1 * var(--seat)));
86
+ }
87
+ }
@@ -0,0 +1,139 @@
1
+ /* SeatRing: seats as a grid of cards, or on a circle when the box is wide
2
+ * enough. The component is a size container, so the switch follows its own
3
+ * width (a column, a dialog, a phone), never the window. The ring is pure CSS
4
+ * (trigonometry on `--i`, the seat's index, and `--n`, their count, both set by
5
+ * the attribute rules below, so server markup carries no `style`). It holds up
6
+ * to 10 seats (RING_LIMIT); more is a grid. Selected: the primary tint, a
7
+ * border and a check mark, so colour is never the only cue. */
8
+ @layer components {
9
+ .nxgt-seat-ring {
10
+ --seat: 6rem;
11
+ --seat-avatar: 2.5rem;
12
+ --seat-dir: 1;
13
+ container: seat-ring / inline-size;
14
+ }
15
+ .nxgt-seat-ring:dir(rtl) {
16
+ --seat-dir: -1;
17
+ }
18
+ .nxgt-seat-ring[data-size="sm"] {
19
+ --seat: 4.75rem;
20
+ --seat-avatar: 2rem;
21
+ }
22
+ .nxgt-seat-ring[data-size="lg"] {
23
+ --seat: 8rem;
24
+ --seat-avatar: 3.5rem;
25
+ }
26
+
27
+ .nxgt-seat-ring [data-slot="seat-ring-orbit"] {
28
+ display: flex;
29
+ flex-direction: column;
30
+ gap: 0.75rem;
31
+ }
32
+ .nxgt-seat-ring [data-slot="seat-ring-center"] {
33
+ order: -1;
34
+ display: grid;
35
+ place-items: center;
36
+ text-align: center;
37
+ }
38
+ .nxgt-seat-ring [data-slot="seat-ring-list"] {
39
+ display: grid;
40
+ grid-template-columns: repeat(
41
+ auto-fill,
42
+ minmax(min(var(--seat), 100%), 1fr)
43
+ );
44
+ gap: 0.5rem;
45
+ margin: 0;
46
+ padding: 0;
47
+ list-style: none;
48
+ }
49
+ @container seat-ring (max-width: 30rem) {
50
+ .nxgt-seat-ring [data-slot="seat-ring-list"] {
51
+ grid-template-columns: repeat(2, minmax(0, 1fr));
52
+ }
53
+ }
54
+
55
+ .nxgt-seat-ring [data-slot="seat-ring-seat"] {
56
+ position: relative;
57
+ display: flex;
58
+ flex-direction: column;
59
+ align-items: center;
60
+ gap: 0.25rem;
61
+ min-inline-size: 0;
62
+ min-block-size: 2.75rem;
63
+ padding: 0.5rem 0.375rem;
64
+ border: 1px solid var(--border);
65
+ border-radius: var(--radius-lg, 0.75rem);
66
+ background: var(--card, var(--background));
67
+ color: var(--foreground);
68
+ text-align: center;
69
+ transition:
70
+ background-color 150ms,
71
+ border-color 150ms;
72
+ }
73
+ .nxgt-seat-ring [data-slot="seat-ring-label"] {
74
+ max-inline-size: 100%;
75
+ overflow-wrap: anywhere;
76
+ font-size: 0.875rem;
77
+ font-weight: 500;
78
+ line-height: 1.25;
79
+ }
80
+ .nxgt-seat-ring [data-slot="seat-ring-badge"] {
81
+ position: absolute;
82
+ inset-block-start: -0.5rem;
83
+ inset-inline-end: 0.25rem;
84
+ }
85
+ .nxgt-seat-ring [data-slot="seat-ring-mark"] {
86
+ position: absolute;
87
+ inset-block-start: 0.25rem;
88
+ inset-inline-start: 0.25rem;
89
+ display: grid;
90
+ inline-size: 1rem;
91
+ block-size: 1rem;
92
+ color: var(--primary-text, var(--primary));
93
+ }
94
+ .nxgt-seat-ring [data-slot="seat-ring-empty-circle"] {
95
+ inline-size: var(--seat-avatar);
96
+ block-size: var(--seat-avatar);
97
+ border: 2px dashed var(--border);
98
+ border-radius: 9999px;
99
+ }
100
+ .nxgt-seat-ring [data-empty] {
101
+ border-style: dashed;
102
+ background: transparent;
103
+ }
104
+ .nxgt-seat-ring [data-highlighted] {
105
+ border-color: var(--primary-indicator, var(--primary));
106
+ box-shadow: 0 0 0 2px var(--primary-indicator, var(--primary));
107
+ }
108
+ .nxgt-seat-ring [role="option"]:not([aria-disabled="true"]) {
109
+ cursor: pointer;
110
+ }
111
+ .nxgt-seat-ring [role="option"]:not([aria-disabled="true"]):hover {
112
+ background: color-mix(
113
+ in oklab,
114
+ var(--primary-text, var(--primary)) 8%,
115
+ var(--card, var(--background))
116
+ );
117
+ }
118
+ .nxgt-seat-ring [role="option"][aria-selected="true"] {
119
+ border-color: var(--primary-text, var(--primary));
120
+ background: color-mix(
121
+ in oklab,
122
+ var(--primary-text, var(--primary)) 12%,
123
+ var(--card, var(--background))
124
+ );
125
+ }
126
+ .nxgt-seat-ring [role="option"][aria-disabled="true"],
127
+ .nxgt-seat-ring [aria-disabled="true"] {
128
+ opacity: 0.6;
129
+ }
130
+ .nxgt-seat-ring [role="option"]:focus-visible {
131
+ outline: 2px solid var(--ring);
132
+ outline-offset: 2px;
133
+ }
134
+ @media (prefers-reduced-motion: reduce) {
135
+ .nxgt-seat-ring [data-slot="seat-ring-seat"] {
136
+ transition: none;
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,65 @@
1
+ /* SecretCard: a card whose secret face is not rendered until it is revealed.
2
+ * It carries `data-tone`, so the tone's tokens resolve on it. The hidden face
3
+ * is a quiet diagonal pattern in the tone's ring colour. The card's border is
4
+ * that colour too (set by a utility class on the Card, which wins over the
5
+ * Card's own). A change flips the face in, never on mount, and only when the person has not asked for reduced motion. The card is
6
+ * full width up to a reading width and keeps the reveal button a 44px target
7
+ * on a phone. */
8
+ @layer components {
9
+ .nxgt-secret-card {
10
+ width: 100%;
11
+ max-width: 28rem;
12
+ margin-inline: auto;
13
+ --secret-card-line: var(--nxgt-tone-ring, var(--border));
14
+ border-color: var(--secret-card-line);
15
+ perspective: 60rem;
16
+ }
17
+ .nxgt-secret-card [data-slot="secret-card-title"] {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 0.5rem;
21
+ color: var(--nxgt-tone-text, inherit);
22
+ }
23
+ .nxgt-secret-card [data-slot="secret-card-pattern"] {
24
+ min-height: 6rem;
25
+ border-radius: 0.5rem;
26
+ border: 1px dashed var(--secret-card-line);
27
+ background-image: repeating-linear-gradient(
28
+ 45deg,
29
+ color-mix(in oklab, var(--secret-card-line) 25%, transparent) 0 0.5rem,
30
+ transparent 0.5rem 1rem
31
+ );
32
+ }
33
+ .nxgt-secret-card [data-slot="secret-card-back"] {
34
+ min-height: 6rem;
35
+ display: grid;
36
+ place-items: center;
37
+ text-align: center;
38
+ overflow-wrap: anywhere;
39
+ font-size: 1.25rem;
40
+ font-weight: 600;
41
+ user-select: text;
42
+ }
43
+ .nxgt-secret-card [data-slot="secret-card-actions"] {
44
+ flex-wrap: wrap;
45
+ gap: 0.5rem;
46
+ }
47
+ .nxgt-secret-card [data-slot="secret-card-actions"] > button {
48
+ min-height: 44px;
49
+ }
50
+ @media (prefers-reduced-motion: no-preference) {
51
+ .nxgt-secret-card [data-slot="secret-card-face"][data-changed] {
52
+ animation: nxgt-secret-flip 0.35s ease-out;
53
+ }
54
+ }
55
+ @keyframes nxgt-secret-flip {
56
+ from {
57
+ opacity: 0;
58
+ transform: rotateY(90deg);
59
+ }
60
+ to {
61
+ opacity: 1;
62
+ transform: rotateY(0);
63
+ }
64
+ }
65
+ }