@krizaka/ui 3.5.0 → 3.6.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 (217) hide show
  1. package/README.md +1 -0
  2. package/dist/app-shell.d.ts +24 -22
  3. package/dist/app-shell.js +9 -3
  4. package/dist/app-shell.js.map +1 -1
  5. package/dist/chunk-3GODX3D6.js +25 -0
  6. package/dist/chunk-3GODX3D6.js.map +1 -0
  7. package/dist/{chunk-R6SDS7JM.js → chunk-ND3L5VHT.js} +25 -2
  8. package/dist/chunk-ND3L5VHT.js.map +1 -0
  9. package/dist/command.js +1 -1
  10. package/dist/dialog.js +7 -3
  11. package/dist/dialog.js.map +1 -1
  12. package/dist/loader.d.ts +21 -1
  13. package/dist/loader.js +7 -3
  14. package/dist/loader.js.map +1 -1
  15. package/dist/map.js +7 -1
  16. package/dist/map.js.map +1 -1
  17. package/dist/marketing-layout.d.ts +3 -9
  18. package/dist/marketing-layout.js +24 -7
  19. package/dist/marketing-layout.js.map +1 -1
  20. package/dist/match-dialog.d.ts +3 -1
  21. package/dist/match-dialog.js +25 -3
  22. package/dist/match-dialog.js.map +1 -1
  23. package/dist/native.d.ts +65 -2
  24. package/dist/native.js +960 -878
  25. package/dist/native.js.map +1 -1
  26. package/dist/skip-link.d.ts +15 -0
  27. package/dist/skip-link.js +10 -0
  28. package/dist/skip-link.js.map +1 -0
  29. package/dist/tree-view.d.ts +2 -0
  30. package/dist/tree-view.js.map +1 -1
  31. package/package.json +22 -8
  32. package/registry/achievement.json +2 -1
  33. package/registry/action-bar.json +2 -1
  34. package/registry/action-sheet.json +1 -0
  35. package/registry/activity-feed.json +2 -1
  36. package/registry/alert.json +2 -1
  37. package/registry/app-shell.json +27 -5
  38. package/registry/argument-card.json +2 -1
  39. package/registry/audio-player.json +2 -1
  40. package/registry/auth-layout.json +7 -2
  41. package/registry/avatar.json +2 -1
  42. package/registry/badge.json +2 -1
  43. package/registry/button.json +2 -1
  44. package/registry/card-grid.json +2 -1
  45. package/registry/card.json +2 -1
  46. package/registry/catalogue-page.json +5 -2
  47. package/registry/changelog-page.json +5 -2
  48. package/registry/chat.json +2 -1
  49. package/registry/checkbox.json +2 -1
  50. package/registry/chip-link.json +2 -1
  51. package/registry/chip.json +2 -1
  52. package/registry/choice-grid.json +2 -1
  53. package/registry/cn.json +1 -0
  54. package/registry/code-block.json +2 -1
  55. package/registry/combobox.json +2 -1
  56. package/registry/command.json +2 -1
  57. package/registry/component-page.json +5 -2
  58. package/registry/confirm-button.json +2 -1
  59. package/registry/contact-page.json +5 -2
  60. package/registry/continue-prompt.json +2 -1
  61. package/registry/countdown.json +2 -1
  62. package/registry/cta-panel.json +2 -1
  63. package/registry/dashboard-layout.json +5 -2
  64. package/registry/data-table.json +2 -1
  65. package/registry/date-picker.json +3 -2
  66. package/registry/decision-list.json +2 -1
  67. package/registry/description-list.json +2 -1
  68. package/registry/detail-layout.json +16 -2
  69. package/registry/dialog.json +10 -4
  70. package/registry/docs-article-page.json +16 -2
  71. package/registry/docs-hub-page.json +5 -2
  72. package/registry/docs-layout.json +2 -1
  73. package/registry/dropdown-menu.json +2 -1
  74. package/registry/empty-state.json +14 -2
  75. package/registry/error-page.json +5 -2
  76. package/registry/examples/detail-layout/checkout.d.ts +4 -0
  77. package/registry/examples/detail-layout/checkout.js +37 -0
  78. package/registry/examples/detail-layout/checkout.tsx +44 -0
  79. package/registry/examples/docs-article-page/faq.d.ts +4 -0
  80. package/registry/examples/docs-article-page/faq.js +31 -0
  81. package/registry/examples/docs-article-page/faq.tsx +40 -0
  82. package/registry/examples/empty-state/library.d.ts +4 -0
  83. package/registry/examples/empty-state/library.js +25 -0
  84. package/registry/examples/empty-state/library.tsx +20 -0
  85. package/registry/examples/loader/branded.d.ts +4 -0
  86. package/registry/examples/loader/branded.js +12 -0
  87. package/registry/examples/loader/branded.tsx +7 -0
  88. package/registry/examples/loader/native/branded.d.ts +4 -0
  89. package/registry/examples/loader/native/branded.dark.png +0 -0
  90. package/registry/examples/loader/native/branded.js +12 -0
  91. package/registry/examples/loader/native/branded.light.png +0 -0
  92. package/registry/examples/loader/native/branded.tsx +11 -0
  93. package/registry/examples/longform-page/case-study.d.ts +4 -0
  94. package/registry/examples/longform-page/case-study.js +41 -0
  95. package/registry/examples/longform-page/case-study.tsx +40 -0
  96. package/registry/examples/pull-to-refresh/native/branded.d.ts +4 -0
  97. package/registry/examples/pull-to-refresh/native/branded.dark.png +0 -0
  98. package/registry/examples/pull-to-refresh/native/branded.js +14 -0
  99. package/registry/examples/pull-to-refresh/native/branded.light.png +0 -0
  100. package/registry/examples/pull-to-refresh/native/branded.tsx +23 -0
  101. package/registry/examples/screen/native/map.d.ts +4 -0
  102. package/registry/examples/screen/native/map.dark.png +0 -0
  103. package/registry/examples/screen/native/map.js +26 -0
  104. package/registry/examples/screen/native/map.light.png +0 -0
  105. package/registry/examples/screen/native/map.tsx +31 -0
  106. package/registry/examples/screen/native/notifications.d.ts +4 -0
  107. package/registry/examples/screen/native/notifications.dark.png +0 -0
  108. package/registry/examples/screen/native/notifications.js +21 -0
  109. package/registry/examples/screen/native/notifications.light.png +0 -0
  110. package/registry/examples/screen/native/notifications.tsx +21 -0
  111. package/registry/examples/skip-link/default.d.ts +4 -0
  112. package/registry/examples/skip-link/default.js +19 -0
  113. package/registry/examples/skip-link/default.tsx +12 -0
  114. package/registry/examples/splash-handoff/native/default.d.ts +4 -0
  115. package/registry/examples/splash-handoff/native/default.dark.png +0 -0
  116. package/registry/examples/splash-handoff/native/default.js +12 -0
  117. package/registry/examples/splash-handoff/native/default.light.png +0 -0
  118. package/registry/examples/splash-handoff/native/default.tsx +11 -0
  119. package/registry/examples/split-view/nearby.d.ts +4 -0
  120. package/registry/examples/split-view/nearby.js +33 -0
  121. package/registry/examples/split-view/nearby.tsx +45 -0
  122. package/registry/examples/split-view/notifications.d.ts +4 -0
  123. package/registry/examples/split-view/notifications.js +33 -0
  124. package/registry/examples/split-view/notifications.tsx +31 -0
  125. package/registry/examples/status-page/maintenance.d.ts +4 -0
  126. package/registry/examples/status-page/maintenance.js +29 -0
  127. package/registry/examples/status-page/maintenance.tsx +24 -0
  128. package/registry/fab.json +1 -0
  129. package/registry/field.json +2 -1
  130. package/registry/file-upload.json +3 -2
  131. package/registry/filter-bar.json +2 -1
  132. package/registry/form.json +3 -2
  133. package/registry/gallery-page.json +5 -2
  134. package/registry/glow-card.json +2 -1
  135. package/registry/goal-gauge.json +2 -1
  136. package/registry/header.json +1 -0
  137. package/registry/how-it-works-page.json +5 -2
  138. package/registry/image-cropper.json +2 -1
  139. package/registry/image-viewer.json +1 -0
  140. package/registry/index.json +469 -22
  141. package/registry/iso.json +2 -1
  142. package/registry/kanban.json +2 -1
  143. package/registry/kbd.json +2 -1
  144. package/registry/landing-page.json +5 -2
  145. package/registry/landscape.json +2 -1
  146. package/registry/legal-page.json +5 -2
  147. package/registry/link-card.json +2 -1
  148. package/registry/list.json +2 -1
  149. package/registry/loader.json +47 -3
  150. package/registry/longform-page.json +16 -2
  151. package/registry/map.json +4 -3
  152. package/registry/marketing-layout.json +13 -4
  153. package/registry/marquee.json +2 -1
  154. package/registry/mascot-quote.json +2 -1
  155. package/registry/mascot-state.json +2 -1
  156. package/registry/mascots.json +2 -1
  157. package/registry/match-dialog.json +17 -2
  158. package/registry/media-capture.json +2 -1
  159. package/registry/media-picker.json +1 -0
  160. package/registry/meter.json +2 -1
  161. package/registry/nav-bar.json +2 -1
  162. package/registry/number-ticker.json +2 -1
  163. package/registry/onboarding.json +1 -0
  164. package/registry/otp-input.json +1 -0
  165. package/registry/page-header.json +2 -1
  166. package/registry/page-hero.json +2 -1
  167. package/registry/page.json +2 -1
  168. package/registry/pagination.json +2 -1
  169. package/registry/permission-prompt.json +1 -0
  170. package/registry/photo-grid.json +2 -1
  171. package/registry/pointer-light.json +2 -1
  172. package/registry/popover.json +2 -1
  173. package/registry/pricing-page.json +5 -2
  174. package/registry/product-page.json +5 -2
  175. package/registry/profile-card.json +2 -1
  176. package/registry/progress.json +2 -1
  177. package/registry/promise-card.json +2 -1
  178. package/registry/pull-to-refresh.json +68 -4
  179. package/registry/radio-group.json +2 -1
  180. package/registry/screen.json +51 -2
  181. package/registry/scroll-stage.json +2 -1
  182. package/registry/search-field.json +2 -1
  183. package/registry/section-backdrop.json +2 -1
  184. package/registry/segmented.json +1 -0
  185. package/registry/separator.json +2 -1
  186. package/registry/settings-layout.json +5 -2
  187. package/registry/skeleton.json +2 -1
  188. package/registry/skip-link.json +122 -0
  189. package/registry/slider.json +2 -1
  190. package/registry/slot.json +2 -1
  191. package/registry/spinner.json +2 -1
  192. package/registry/splash-handoff.json +138 -0
  193. package/registry/split-view.json +27 -2
  194. package/registry/stage-banner.json +2 -1
  195. package/registry/stat.json +2 -1
  196. package/registry/status-page.json +18 -2
  197. package/registry/step-progress.json +2 -1
  198. package/registry/stepper.json +2 -1
  199. package/registry/swipe-deck.json +2 -1
  200. package/registry/swipeable-row.json +1 -0
  201. package/registry/switch.json +2 -1
  202. package/registry/tab-bar.json +1 -0
  203. package/registry/tabs.json +2 -1
  204. package/registry/tag-input.json +2 -1
  205. package/registry/tag-selector.json +2 -1
  206. package/registry/terminal-replay.json +2 -1
  207. package/registry/theme.json +2 -1
  208. package/registry/toast.json +2 -1
  209. package/registry/toc.json +2 -1
  210. package/registry/tooltip.json +2 -1
  211. package/registry/tree-view.json +10 -4
  212. package/registry/txt.json +1 -0
  213. package/registry/verified-badge.json +2 -1
  214. package/registry/video-player.json +2 -1
  215. package/registry/video-trimmer.json +2 -1
  216. package/registry/virtual.json +2 -1
  217. package/dist/chunk-R6SDS7JM.js.map +0 -1
package/README.md CHANGED
@@ -144,6 +144,7 @@ light or a product's — is a set of tokens.
144
144
  | `@krizaka/ui/page-header` | `PageHeader` (`title`, `description`, `actions`, `breadcrumb`, `as` h1·h2) | server |
145
145
  | `@krizaka/ui/alert` | `Alert` (`tone` info·success·warning·danger, `icon`, `title`, `action`; `role="alert"` for danger and warning, `status` otherwise), `alertVariants` | server |
146
146
  | `@krizaka/ui/separator` | `Separator` (`orientation`, `decorative`, Radix), `separatorVariants` | server |
147
+ | `@krizaka/ui/skip-link` | `SkipLink` (`href`, default `#content`; the label as children): the first tab stop, hidden until focused, Enter moves focus to the content (WCAG 2.4.1) | client |
147
148
  | `@krizaka/ui/kbd` | `Kbd` (`size` sm·md), `kbdVariants` | server |
148
149
  | `@krizaka/ui/tabs` | `Tabs.Root/List/Trigger/Content` (`variant` underline·segmented·pills on the root, `orientation`, `value`/`defaultValue` + `onValueChange`; one light glides to the active tab), `tabs` (the slots) | client |
149
150
  | `@krizaka/ui/nav-bar` | `NavBar.Root` (`label`, `compactAfter`, `floating`) `/Brand/Items/Link` (`active`) `/Menu` (`label`, `columns`) `/MenuLink` (`title`, `description`, `icon`) `/Actions/Mobile` (`openLabel`, `closeLabel`) `/MobileLink`, `navBar` (the slots) | client |
@@ -4,19 +4,19 @@ import * as React from 'react';
4
4
  declare const appShell: tailwind_variants.TVReturnType<{
5
5
  [key: string]: {
6
6
  [key: string]: tailwind_variants.ClassValue | {
7
+ body?: tailwind_variants.ClassValue;
8
+ footer?: tailwind_variants.ClassValue;
9
+ header?: tailwind_variants.ClassValue;
10
+ main?: tailwind_variants.ClassValue;
11
+ nav?: tailwind_variants.ClassValue;
12
+ title?: tailwind_variants.ClassValue;
13
+ group?: tailwind_variants.ClassValue;
7
14
  root?: tailwind_variants.ClassValue;
8
15
  drawer?: tailwind_variants.ClassValue;
9
16
  overlay?: tailwind_variants.ClassValue;
10
17
  brand?: tailwind_variants.ClassValue;
11
- nav?: tailwind_variants.ClassValue;
12
- group?: tailwind_variants.ClassValue;
13
18
  itemLabel?: tailwind_variants.ClassValue;
14
- footer?: tailwind_variants.ClassValue;
15
- body?: tailwind_variants.ClassValue;
16
- header?: tailwind_variants.ClassValue;
17
- title?: tailwind_variants.ClassValue;
18
19
  actions?: tailwind_variants.ClassValue;
19
- main?: tailwind_variants.ClassValue;
20
20
  sidebar?: tailwind_variants.ClassValue;
21
21
  item?: tailwind_variants.ClassValue;
22
22
  itemBadge?: tailwind_variants.ClassValue;
@@ -27,19 +27,19 @@ declare const appShell: tailwind_variants.TVReturnType<{
27
27
  } | {
28
28
  [x: string]: {
29
29
  [x: string]: tailwind_variants.ClassValue | {
30
+ body?: tailwind_variants.ClassValue;
31
+ footer?: tailwind_variants.ClassValue;
32
+ header?: tailwind_variants.ClassValue;
33
+ main?: tailwind_variants.ClassValue;
34
+ nav?: tailwind_variants.ClassValue;
35
+ title?: tailwind_variants.ClassValue;
36
+ group?: tailwind_variants.ClassValue;
30
37
  root?: tailwind_variants.ClassValue;
31
38
  drawer?: tailwind_variants.ClassValue;
32
39
  overlay?: tailwind_variants.ClassValue;
33
40
  brand?: tailwind_variants.ClassValue;
34
- nav?: tailwind_variants.ClassValue;
35
- group?: tailwind_variants.ClassValue;
36
41
  itemLabel?: tailwind_variants.ClassValue;
37
- footer?: tailwind_variants.ClassValue;
38
- body?: tailwind_variants.ClassValue;
39
- header?: tailwind_variants.ClassValue;
40
- title?: tailwind_variants.ClassValue;
41
42
  actions?: tailwind_variants.ClassValue;
42
- main?: tailwind_variants.ClassValue;
43
43
  sidebar?: tailwind_variants.ClassValue;
44
44
  item?: tailwind_variants.ClassValue;
45
45
  itemBadge?: tailwind_variants.ClassValue;
@@ -69,19 +69,19 @@ declare const appShell: tailwind_variants.TVReturnType<{
69
69
  }, undefined, {
70
70
  [key: string]: {
71
71
  [key: string]: tailwind_variants.ClassValue | {
72
+ body?: tailwind_variants.ClassValue;
73
+ footer?: tailwind_variants.ClassValue;
74
+ header?: tailwind_variants.ClassValue;
75
+ main?: tailwind_variants.ClassValue;
76
+ nav?: tailwind_variants.ClassValue;
77
+ title?: tailwind_variants.ClassValue;
78
+ group?: tailwind_variants.ClassValue;
72
79
  root?: tailwind_variants.ClassValue;
73
80
  drawer?: tailwind_variants.ClassValue;
74
81
  overlay?: tailwind_variants.ClassValue;
75
82
  brand?: tailwind_variants.ClassValue;
76
- nav?: tailwind_variants.ClassValue;
77
- group?: tailwind_variants.ClassValue;
78
83
  itemLabel?: tailwind_variants.ClassValue;
79
- footer?: tailwind_variants.ClassValue;
80
- body?: tailwind_variants.ClassValue;
81
- header?: tailwind_variants.ClassValue;
82
- title?: tailwind_variants.ClassValue;
83
84
  actions?: tailwind_variants.ClassValue;
84
- main?: tailwind_variants.ClassValue;
85
85
  sidebar?: tailwind_variants.ClassValue;
86
86
  item?: tailwind_variants.ClassValue;
87
87
  itemBadge?: tailwind_variants.ClassValue;
@@ -135,9 +135,11 @@ type AppShellRootProps = React.ComponentProps<"div"> & {
135
135
  collapsed?: boolean;
136
136
  /** Called when the sidebar collapses or expands. */
137
137
  onCollapsedChange?: (collapsed: boolean) => void;
138
+ /** The words of the "skip to content" link (`Skip to content`), the first tab stop; it lands on `AppShell.Main`. Without it, no link. */
139
+ skipLabel?: string;
138
140
  };
139
141
  /** The shell: holds the collapse (desktop) and the drawer (phone) state. Children: Sidebar, then Body. */
140
- declare function AppShellRoot({ defaultCollapsed, collapsed: controlled, onCollapsedChange, className, ...props }: AppShellRootProps): React.JSX.Element;
142
+ declare function AppShellRoot({ defaultCollapsed, collapsed: controlled, onCollapsedChange, skipLabel, className, children, ...props }: AppShellRootProps): React.JSX.Element;
141
143
  type AppShellSidebarProps = Omit<React.ComponentProps<"nav">, "children"> & {
142
144
  /** The navigation's accessible name (`Studio`, `Administration`). */
143
145
  label: string;
package/dist/app-shell.js CHANGED
@@ -6,6 +6,9 @@ import {
6
6
  import {
7
7
  cn
8
8
  } from "./chunk-G3ETON2A.js";
9
+ import {
10
+ SkipLink
11
+ } from "./chunk-3GODX3D6.js";
9
12
 
10
13
  // src/app-shell/app-shell.tsx
11
14
  import { Dialog as DialogPrimitive, Slot } from "radix-ui";
@@ -41,7 +44,7 @@ var useShell = () => {
41
44
  if (!ctx) throw new Error("AppShell parts must be inside <AppShell.Root>");
42
45
  return ctx;
43
46
  };
44
- function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, className, ...props }) {
47
+ function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, skipLabel, className, children, ...props }) {
45
48
  const [own, setOwn] = React.useState(defaultCollapsed);
46
49
  const [open, setOpen] = React.useState(false);
47
50
  const collapsed = controlled ?? own;
@@ -53,7 +56,10 @@ function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onColla
53
56
  [controlled, onCollapsedChange]
54
57
  );
55
58
  const value = React.useMemo(() => ({ collapsed, setCollapsed, open, setOpen, inDrawer: false }), [collapsed, setCollapsed, open]);
56
- return /* @__PURE__ */ jsx(ShellContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: s.root({ className }), ...props }) });
59
+ return /* @__PURE__ */ jsx(ShellContext.Provider, { value, children: /* @__PURE__ */ jsxs("div", { className: s.root({ className }), ...props, children: [
60
+ skipLabel && /* @__PURE__ */ jsx(SkipLink, { href: "#content", children: skipLabel }),
61
+ children
62
+ ] }) });
57
63
  }
58
64
  function SidebarContent({ brand, children, footer, collapseLabels, closeLabel }) {
59
65
  const { collapsed, setCollapsed, inDrawer } = useShell();
@@ -112,7 +118,7 @@ function AppShellHeader({ title, menuLabel, actions, className, children, ...pro
112
118
  ] });
113
119
  }
114
120
  function AppShellMain({ className, ...props }) {
115
- return /* @__PURE__ */ jsx("main", { className: s.main({ className }), ...props });
121
+ return /* @__PURE__ */ jsx("main", { id: "content", tabIndex: -1, className: s.main({ className }), ...props });
116
122
  }
117
123
  var AppShell = {
118
124
  Root: AppShellRoot,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/app-shell/app-shell.tsx"],"sourcesContent":["// Client (the collapse, the drawer). The shell of an application screen, read from the products' consoles (Orochia\n// admin and studio, Orazaka web-admin and web-client): a sidebar of sections that collapses to its icons on a desktop,\n// a header with the screen's title and actions, the content — and below `md` the sidebar becomes a drawer opened from\n// the header (Radix Dialog: focus trap, Escape, focus return). Words arrive as props; links through `asChild`.\nimport { Dialog as DialogPrimitive, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { ChevronGlyph, CloseGlyph } from \"../glyphs\";\n\nexport const appShell = tv({\n slots: {\n root: \"flex min-h-dvh w-full bg-surface-0 text-fg\",\n sidebar:\n \"sticky top-0 hidden h-dvh shrink-0 flex-col border-r border-border-subtle bg-surface-1 transition-[width] duration-300 motion-reduce:transition-none md:flex \" +\n \"w-64 data-collapsed:w-[4.25rem]\",\n drawer:\n \"kz-dialog fixed inset-y-0 left-0 z-50 flex w-[min(20rem,88vw)] flex-col border-r border-border-default bg-surface-1 pl-[env(safe-area-inset-left)] shadow-lg focus:outline-hidden\",\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n brand: \"flex h-14 shrink-0 items-center gap-2.5 px-4 font-display text-[15px] font-bold tracking-tight [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0\",\n nav: \"flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2\",\n group: \"mt-3 mb-1 px-3 font-mono text-[10.5px] font-semibold uppercase tracking-[0.14em] text-fg-secondary [[data-collapsed]_&]:sr-only\",\n item:\n \"group/item relative flex h-10 items-center gap-3 rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n \"aria-[current=page]:bg-accent-soft aria-[current=page]:text-fg \" +\n \"aria-[current=page]:before:absolute aria-[current=page]:before:inset-y-2 aria-[current=page]:before:-left-2 aria-[current=page]:before:w-1 aria-[current=page]:before:rounded-full aria-[current=page]:before:bg-accent \" +\n \"[[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0\",\n itemLabel: \"min-w-0 flex-1 truncate [[data-collapsed]_&]:sr-only\",\n itemBadge:\n \"ml-auto inline-flex min-w-5 items-center justify-center rounded-full bg-accent px-1.5 text-[11px] font-bold text-on-accent \" +\n \"[[data-collapsed]_&]:absolute [[data-collapsed]_&]:right-1.5 [[data-collapsed]_&]:top-1 [[data-collapsed]_&]:min-w-4 [[data-collapsed]_&]:px-1 [[data-collapsed]_&]:text-[10px]\",\n footer: \"shrink-0 border-t border-border-subtle p-2\",\n collapse:\n \"hidden h-9 w-full items-center gap-2 rounded-lg px-3 text-xs font-medium text-fg-secondary hover:bg-surface-2 hover:text-fg md:flex \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0\",\n body: \"flex min-w-0 flex-1 flex-col\",\n header:\n \"sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border-subtle bg-surface-0/85 px-4 backdrop-blur-xl md:px-6\",\n menuButton:\n \"inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border-subtle bg-surface-1 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n title: \"min-w-0 flex-1 truncate font-display text-base font-bold tracking-tight\",\n actions: \"ml-auto flex shrink-0 items-center gap-2\",\n main: \"min-w-0 flex-1 px-4 py-6 md:px-6 md:py-8\",\n },\n});\nconst s = appShell();\n\ninterface ShellState {\n collapsed: boolean;\n setCollapsed: (value: boolean) => void;\n open: boolean;\n setOpen: (value: boolean) => void;\n inDrawer: boolean;\n}\nconst ShellContext = React.createContext<ShellState | null>(null);\nconst useShell = () => {\n const ctx = React.useContext(ShellContext);\n if (!ctx) throw new Error(\"AppShell parts must be inside <AppShell.Root>\");\n return ctx;\n};\n\nexport type AppShellRootProps = React.ComponentProps<\"div\"> & {\n /** The sidebar starts collapsed to its icons (desktop). */\n defaultCollapsed?: boolean;\n /** Controlled collapse (desktop): pair with `onCollapsedChange` to persist the choice. */\n collapsed?: boolean;\n /** Called when the sidebar collapses or expands. */\n onCollapsedChange?: (collapsed: boolean) => void;\n};\n\n/** The shell: holds the collapse (desktop) and the drawer (phone) state. Children: Sidebar, then Body. */\nexport function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, className, ...props }: AppShellRootProps) {\n const [own, setOwn] = React.useState(defaultCollapsed);\n const [open, setOpen] = React.useState(false);\n const collapsed = controlled ?? own;\n const setCollapsed = React.useCallback(\n (value: boolean) => {\n if (controlled === undefined) setOwn(value);\n onCollapsedChange?.(value);\n },\n [controlled, onCollapsedChange],\n );\n const value = React.useMemo(() => ({ collapsed, setCollapsed, open, setOpen, inDrawer: false }), [collapsed, setCollapsed, open]);\n return (\n <ShellContext.Provider value={value}>\n <div className={s.root({ className })} {...props} />\n </ShellContext.Provider>\n );\n}\n\nexport type AppShellSidebarProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n /** The navigation's accessible name (`Studio`, `Administration`). */\n label: string;\n /** The brand at the top: the product's mark and name (a link home through `asChild` of your router, or plain). */\n brand?: React.ReactNode;\n /** The sections: `AppShell.Group` and `AppShell.Item`. */\n children: React.ReactNode;\n /** At the bottom, above the collapse button: the account, a plan, the version. */\n footer?: React.ReactNode;\n /** The collapse button's words, `[collapse, expand]` (desktop only). Without it the sidebar cannot collapse. */\n collapseLabels?: readonly [string, string];\n /** The drawer's close button words (phone). */\n closeLabel: string;\n};\n\nfunction SidebarContent({ brand, children, footer, collapseLabels, closeLabel }: Pick<AppShellSidebarProps, \"brand\" | \"children\" | \"footer\" | \"collapseLabels\" | \"closeLabel\">) {\n const { collapsed, setCollapsed, inDrawer } = useShell();\n return (\n <>\n <div className={s.brand()}>\n {brand}\n {inDrawer && (\n <DialogPrimitive.Close className={cn(s.menuButton(), \"ml-auto flex\")} aria-label={closeLabel}>\n <CloseGlyph className=\"h-4 w-4\" />\n </DialogPrimitive.Close>\n )}\n </div>\n <div className={s.nav()}>{children}</div>\n {(footer || (collapseLabels && !inDrawer)) && (\n <div className={s.footer()}>\n {footer}\n {collapseLabels && !inDrawer && (\n <button type=\"button\" className={s.collapse()} aria-expanded={!collapsed} onClick={() => setCollapsed(!collapsed)}>\n <ChevronGlyph direction={collapsed ? \"right\" : \"left\"} className=\"h-4 w-4\" />\n <span className={collapsed ? \"sr-only\" : undefined}>{collapsed ? collapseLabels[1] : collapseLabels[0]}</span>\n </button>\n )}\n </div>\n )}\n </>\n );\n}\n\n/** The sidebar: on a desktop a column that collapses to its icons; on a phone the same content in a drawer. */\nexport function AppShellSidebar({ label, brand, children, footer, collapseLabels, closeLabel, className, ...props }: AppShellSidebarProps) {\n const shell = useShell();\n const content = { brand, children, footer, collapseLabels, closeLabel };\n return (\n <>\n <nav aria-label={label} data-collapsed={shell.collapsed ? \"\" : undefined} className={s.sidebar({ className })} {...props}>\n <SidebarContent {...content} />\n </nav>\n <DialogPrimitive.Root open={shell.open} onOpenChange={shell.setOpen}>\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay className={s.overlay()} />\n <DialogPrimitive.Content className={s.drawer()} aria-describedby={undefined}>\n <DialogPrimitive.Title className=\"sr-only\">{label}</DialogPrimitive.Title>\n <ShellContext.Provider value={{ ...shell, collapsed: false, inDrawer: true }}>\n <nav aria-label={label} className=\"flex min-h-0 flex-1 flex-col\" onClick={(e) => (e.target as Element).closest(\"a\") && shell.setOpen(false)}>\n <SidebarContent {...content} />\n </nav>\n </ShellContext.Provider>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n </>\n );\n}\n\nexport type AppShellGroupProps = React.ComponentProps<\"p\"> & {\n /** The group's name, above its items (hidden when the sidebar is collapsed). */\n children: React.ReactNode;\n};\n\n/** A heading between groups of items. */\nexport function AppShellGroup({ className, ...props }: AppShellGroupProps) {\n return <p className={s.group({ className })} {...props} />;\n}\n\nexport type AppShellItemProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The item's icon (a signature icon): it stays when the sidebar collapses. */\n icon?: React.ReactNode;\n /** Its words: the accessible name, hidden visually when collapsed (the icon carries a tooltip through `title`). */\n label: string;\n /** The screen it opens is the current one: `aria-current=\"page\"`. */\n current?: boolean;\n /** A count beside it (unread, pending): a number or a few characters. */\n badge?: React.ReactNode;\n /** Renders your link (Next's `Link`) with the item's look: pass it as the only child. */\n asChild?: boolean;\n /** Your link element when `asChild`. */\n children?: React.ReactElement;\n};\n\n/** A destination of the sidebar: icon, words, a count. */\nexport function AppShellItem({ icon, label, current, badge, asChild, children, className, ...props }: AppShellItemProps) {\n const { collapsed } = useShell();\n const Comp = asChild ? Slot.Root : \"a\";\n const inner = (\n <>\n {icon}\n <span className={s.itemLabel()}>{label}</span>\n {badge != null && <span className={s.itemBadge()}>{badge}</span>}\n </>\n );\n return (\n <Comp aria-current={current ? \"page\" : undefined} title={collapsed ? label : undefined} className={s.item({ className })} {...props}>\n {asChild && children ? React.cloneElement(children, undefined, inner) : inner}\n </Comp>\n );\n}\n\n/** The column right of the sidebar: Header, then Main. */\nexport function AppShellBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport type AppShellHeaderProps = Omit<React.ComponentProps<\"header\">, \"title\"> & {\n /** The screen's title (an `h1`). */\n title?: React.ReactNode;\n /** The button that opens the drawer on a phone: its words. */\n menuLabel: string;\n /** The screen's actions, on the right. */\n actions?: React.ReactNode;\n};\n\n/** The top of the screen: the drawer's button (phone), the title, the actions. Sticky. */\nexport function AppShellHeader({ title, menuLabel, actions, className, children, ...props }: AppShellHeaderProps) {\n const { open, setOpen } = useShell();\n return (\n <header className={s.header({ className })} {...props}>\n <button type=\"button\" className={s.menuButton()} aria-label={menuLabel} aria-expanded={open} aria-haspopup=\"dialog\" onClick={() => setOpen(true)}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M4 7h16M4 12h16M4 17h10\" />\n </svg>\n </button>\n {title && <h1 className={s.title()}>{title}</h1>}\n {children}\n {actions && <div className={s.actions()}>{actions}</div>}\n </header>\n );\n}\n\n/** The screen's content. */\nexport function AppShellMain({ className, ...props }: React.ComponentProps<\"main\">) {\n return <main className={s.main({ className })} {...props} />;\n}\n\nexport const AppShell = {\n Root: AppShellRoot,\n Sidebar: AppShellSidebar,\n Group: AppShellGroup,\n Item: AppShellItem,\n Body: AppShellBody,\n Header: AppShellHeader,\n Main: AppShellMain,\n};\n"],"mappings":"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","names":["value"]}
1
+ {"version":3,"sources":["../src/app-shell/app-shell.tsx"],"sourcesContent":["// Client (the collapse, the drawer). The shell of an application screen, read from the products' consoles (Orochia\n// admin and studio, Orazaka web-admin and web-client): a sidebar of sections that collapses to its icons on a desktop,\n// a header with the screen's title and actions, the content — and below `md` the sidebar becomes a drawer opened from\n// the header (Radix Dialog: focus trap, Escape, focus return). Words arrive as props; links through `asChild`.\nimport { SkipLink } from \"@krizaka/ui/skip-link\";\nimport { Dialog as DialogPrimitive, Slot } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { ChevronGlyph, CloseGlyph } from \"../glyphs\";\n\nexport const appShell = tv({\n slots: {\n root: \"flex min-h-dvh w-full bg-surface-0 text-fg\",\n sidebar:\n \"sticky top-0 hidden h-dvh shrink-0 flex-col border-r border-border-subtle bg-surface-1 transition-[width] duration-300 motion-reduce:transition-none md:flex \" +\n \"w-64 data-collapsed:w-[4.25rem]\",\n drawer:\n \"kz-dialog fixed inset-y-0 left-0 z-50 flex w-[min(20rem,88vw)] flex-col border-r border-border-default bg-surface-1 pl-[env(safe-area-inset-left)] shadow-lg focus:outline-hidden\",\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n brand: \"flex h-14 shrink-0 items-center gap-2.5 px-4 font-display text-[15px] font-bold tracking-tight [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0\",\n nav: \"flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2\",\n group: \"mt-3 mb-1 px-3 font-mono text-[10.5px] font-semibold uppercase tracking-[0.14em] text-fg-secondary [[data-collapsed]_&]:sr-only\",\n item:\n \"group/item relative flex h-10 items-center gap-3 rounded-lg px-3 text-sm font-medium text-fg-secondary no-underline transition-colors \" +\n \"hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n \"aria-[current=page]:bg-accent-soft aria-[current=page]:text-fg \" +\n \"aria-[current=page]:before:absolute aria-[current=page]:before:inset-y-2 aria-[current=page]:before:-left-2 aria-[current=page]:before:w-1 aria-[current=page]:before:rounded-full aria-[current=page]:before:bg-accent \" +\n \"[[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0\",\n itemLabel: \"min-w-0 flex-1 truncate [[data-collapsed]_&]:sr-only\",\n itemBadge:\n \"ml-auto inline-flex min-w-5 items-center justify-center rounded-full bg-accent px-1.5 text-[11px] font-bold text-on-accent \" +\n \"[[data-collapsed]_&]:absolute [[data-collapsed]_&]:right-1.5 [[data-collapsed]_&]:top-1 [[data-collapsed]_&]:min-w-4 [[data-collapsed]_&]:px-1 [[data-collapsed]_&]:text-[10px]\",\n footer: \"shrink-0 border-t border-border-subtle p-2\",\n collapse:\n \"hidden h-9 w-full items-center gap-2 rounded-lg px-3 text-xs font-medium text-fg-secondary hover:bg-surface-2 hover:text-fg md:flex \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring [[data-collapsed]_&]:justify-center [[data-collapsed]_&]:px-0\",\n body: \"flex min-w-0 flex-1 flex-col\",\n header:\n \"sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border-subtle bg-surface-0/85 px-4 backdrop-blur-xl md:px-6\",\n menuButton:\n \"inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border-subtle bg-surface-1 text-fg md:hidden \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n title: \"min-w-0 flex-1 truncate font-display text-base font-bold tracking-tight\",\n actions: \"ml-auto flex shrink-0 items-center gap-2\",\n main: \"min-w-0 flex-1 px-4 py-6 md:px-6 md:py-8\",\n },\n});\nconst s = appShell();\n\ninterface ShellState {\n collapsed: boolean;\n setCollapsed: (value: boolean) => void;\n open: boolean;\n setOpen: (value: boolean) => void;\n inDrawer: boolean;\n}\nconst ShellContext = React.createContext<ShellState | null>(null);\nconst useShell = () => {\n const ctx = React.useContext(ShellContext);\n if (!ctx) throw new Error(\"AppShell parts must be inside <AppShell.Root>\");\n return ctx;\n};\n\nexport type AppShellRootProps = React.ComponentProps<\"div\"> & {\n /** The sidebar starts collapsed to its icons (desktop). */\n defaultCollapsed?: boolean;\n /** Controlled collapse (desktop): pair with `onCollapsedChange` to persist the choice. */\n collapsed?: boolean;\n /** Called when the sidebar collapses or expands. */\n onCollapsedChange?: (collapsed: boolean) => void;\n /** The words of the \"skip to content\" link (`Skip to content`), the first tab stop; it lands on `AppShell.Main`. Without it, no link. */\n skipLabel?: string;\n};\n\n/** The shell: holds the collapse (desktop) and the drawer (phone) state. Children: Sidebar, then Body. */\nexport function AppShellRoot({ defaultCollapsed = false, collapsed: controlled, onCollapsedChange, skipLabel, className, children, ...props }: AppShellRootProps) {\n const [own, setOwn] = React.useState(defaultCollapsed);\n const [open, setOpen] = React.useState(false);\n const collapsed = controlled ?? own;\n const setCollapsed = React.useCallback(\n (value: boolean) => {\n if (controlled === undefined) setOwn(value);\n onCollapsedChange?.(value);\n },\n [controlled, onCollapsedChange],\n );\n const value = React.useMemo(() => ({ collapsed, setCollapsed, open, setOpen, inDrawer: false }), [collapsed, setCollapsed, open]);\n return (\n <ShellContext.Provider value={value}>\n <div className={s.root({ className })} {...props}>\n {skipLabel && <SkipLink href=\"#content\">{skipLabel}</SkipLink>}\n {children}\n </div>\n </ShellContext.Provider>\n );\n}\n\nexport type AppShellSidebarProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n /** The navigation's accessible name (`Studio`, `Administration`). */\n label: string;\n /** The brand at the top: the product's mark and name (a link home through `asChild` of your router, or plain). */\n brand?: React.ReactNode;\n /** The sections: `AppShell.Group` and `AppShell.Item`. */\n children: React.ReactNode;\n /** At the bottom, above the collapse button: the account, a plan, the version. */\n footer?: React.ReactNode;\n /** The collapse button's words, `[collapse, expand]` (desktop only). Without it the sidebar cannot collapse. */\n collapseLabels?: readonly [string, string];\n /** The drawer's close button words (phone). */\n closeLabel: string;\n};\n\nfunction SidebarContent({ brand, children, footer, collapseLabels, closeLabel }: Pick<AppShellSidebarProps, \"brand\" | \"children\" | \"footer\" | \"collapseLabels\" | \"closeLabel\">) {\n const { collapsed, setCollapsed, inDrawer } = useShell();\n return (\n <>\n <div className={s.brand()}>\n {brand}\n {inDrawer && (\n <DialogPrimitive.Close className={cn(s.menuButton(), \"ml-auto flex\")} aria-label={closeLabel}>\n <CloseGlyph className=\"h-4 w-4\" />\n </DialogPrimitive.Close>\n )}\n </div>\n <div className={s.nav()}>{children}</div>\n {(footer || (collapseLabels && !inDrawer)) && (\n <div className={s.footer()}>\n {footer}\n {collapseLabels && !inDrawer && (\n <button type=\"button\" className={s.collapse()} aria-expanded={!collapsed} onClick={() => setCollapsed(!collapsed)}>\n <ChevronGlyph direction={collapsed ? \"right\" : \"left\"} className=\"h-4 w-4\" />\n <span className={collapsed ? \"sr-only\" : undefined}>{collapsed ? collapseLabels[1] : collapseLabels[0]}</span>\n </button>\n )}\n </div>\n )}\n </>\n );\n}\n\n/** The sidebar: on a desktop a column that collapses to its icons; on a phone the same content in a drawer. */\nexport function AppShellSidebar({ label, brand, children, footer, collapseLabels, closeLabel, className, ...props }: AppShellSidebarProps) {\n const shell = useShell();\n const content = { brand, children, footer, collapseLabels, closeLabel };\n return (\n <>\n <nav aria-label={label} data-collapsed={shell.collapsed ? \"\" : undefined} className={s.sidebar({ className })} {...props}>\n <SidebarContent {...content} />\n </nav>\n <DialogPrimitive.Root open={shell.open} onOpenChange={shell.setOpen}>\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay className={s.overlay()} />\n <DialogPrimitive.Content className={s.drawer()} aria-describedby={undefined}>\n <DialogPrimitive.Title className=\"sr-only\">{label}</DialogPrimitive.Title>\n <ShellContext.Provider value={{ ...shell, collapsed: false, inDrawer: true }}>\n <nav aria-label={label} className=\"flex min-h-0 flex-1 flex-col\" onClick={(e) => (e.target as Element).closest(\"a\") && shell.setOpen(false)}>\n <SidebarContent {...content} />\n </nav>\n </ShellContext.Provider>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n </>\n );\n}\n\nexport type AppShellGroupProps = React.ComponentProps<\"p\"> & {\n /** The group's name, above its items (hidden when the sidebar is collapsed). */\n children: React.ReactNode;\n};\n\n/** A heading between groups of items. */\nexport function AppShellGroup({ className, ...props }: AppShellGroupProps) {\n return <p className={s.group({ className })} {...props} />;\n}\n\nexport type AppShellItemProps = Omit<React.ComponentProps<\"a\">, \"children\"> & {\n /** The item's icon (a signature icon): it stays when the sidebar collapses. */\n icon?: React.ReactNode;\n /** Its words: the accessible name, hidden visually when collapsed (the icon carries a tooltip through `title`). */\n label: string;\n /** The screen it opens is the current one: `aria-current=\"page\"`. */\n current?: boolean;\n /** A count beside it (unread, pending): a number or a few characters. */\n badge?: React.ReactNode;\n /** Renders your link (Next's `Link`) with the item's look: pass it as the only child. */\n asChild?: boolean;\n /** Your link element when `asChild`. */\n children?: React.ReactElement;\n};\n\n/** A destination of the sidebar: icon, words, a count. */\nexport function AppShellItem({ icon, label, current, badge, asChild, children, className, ...props }: AppShellItemProps) {\n const { collapsed } = useShell();\n const Comp = asChild ? Slot.Root : \"a\";\n const inner = (\n <>\n {icon}\n <span className={s.itemLabel()}>{label}</span>\n {badge != null && <span className={s.itemBadge()}>{badge}</span>}\n </>\n );\n return (\n <Comp aria-current={current ? \"page\" : undefined} title={collapsed ? label : undefined} className={s.item({ className })} {...props}>\n {asChild && children ? React.cloneElement(children, undefined, inner) : inner}\n </Comp>\n );\n}\n\n/** The column right of the sidebar: Header, then Main. */\nexport function AppShellBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport type AppShellHeaderProps = Omit<React.ComponentProps<\"header\">, \"title\"> & {\n /** The screen's title (an `h1`). */\n title?: React.ReactNode;\n /** The button that opens the drawer on a phone: its words. */\n menuLabel: string;\n /** The screen's actions, on the right. */\n actions?: React.ReactNode;\n};\n\n/** The top of the screen: the drawer's button (phone), the title, the actions. Sticky. */\nexport function AppShellHeader({ title, menuLabel, actions, className, children, ...props }: AppShellHeaderProps) {\n const { open, setOpen } = useShell();\n return (\n <header className={s.header({ className })} {...props}>\n <button type=\"button\" className={s.menuButton()} aria-label={menuLabel} aria-expanded={open} aria-haspopup=\"dialog\" onClick={() => setOpen(true)}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M4 7h16M4 12h16M4 17h10\" />\n </svg>\n </button>\n {title && <h1 className={s.title()}>{title}</h1>}\n {children}\n {actions && <div className={s.actions()}>{actions}</div>}\n </header>\n );\n}\n\n/** The screen's content. */\nexport function AppShellMain({ className, ...props }: React.ComponentProps<\"main\">) {\n return <main id=\"content\" tabIndex={-1} className={s.main({ className })} {...props} />;\n}\n\nexport const AppShell = {\n Root: AppShellRoot,\n Sidebar: AppShellSidebar,\n Group: AppShellGroup,\n Item: AppShellItem,\n Body: AppShellBody,\n Header: AppShellHeader,\n Main: AppShellMain,\n};\n"],"mappings":";;;;;;;;;;;;;AAKA,SAAS,UAAU,iBAAiB,YAAY;AAChD,YAAY,WAAW;AACvB,SAAS,UAAU;AAoFb,SA0BF,UAzBkB,KADhB;AA/EC,IAAM,WAAW,GAAG;AAAA,EACzB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,SACE;AAAA,IAEF,QACE;AAAA,IACF,SAAS;AAAA,IACT,OAAO;AAAA,IACP,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MACE;AAAA,IAKF,WAAW;AAAA,IACX,WACE;AAAA,IAEF,QAAQ;AAAA,IACR,UACE;AAAA,IAEF,MAAM;AAAA,IACN,QACE;AAAA,IACF,YACE;AAAA,IAEF,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EACR;AACF,CAAC;AACD,IAAM,IAAI,SAAS;AASnB,IAAM,eAAqB,oBAAiC,IAAI;AAChE,IAAM,WAAW,MAAM;AACrB,QAAM,MAAY,iBAAW,YAAY;AACzC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,+CAA+C;AACzE,SAAO;AACT;AAcO,SAAS,aAAa,EAAE,mBAAmB,OAAO,WAAW,YAAY,mBAAmB,WAAW,WAAW,UAAU,GAAG,MAAM,GAAsB;AAChK,QAAM,CAAC,KAAK,MAAM,IAAU,eAAS,gBAAgB;AACrD,QAAM,CAAC,MAAM,OAAO,IAAU,eAAS,KAAK;AAC5C,QAAM,YAAY,cAAc;AAChC,QAAM,eAAqB;AAAA,IACzB,CAACA,WAAmB;AAClB,UAAI,eAAe,OAAW,QAAOA,MAAK;AAC1C,0BAAoBA,MAAK;AAAA,IAC3B;AAAA,IACA,CAAC,YAAY,iBAAiB;AAAA,EAChC;AACA,QAAM,QAAc,cAAQ,OAAO,EAAE,WAAW,cAAc,MAAM,SAAS,UAAU,MAAM,IAAI,CAAC,WAAW,cAAc,IAAI,CAAC;AAChI,SACE,oBAAC,aAAa,UAAb,EAAsB,OACrB,+BAAC,SAAI,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OACxC;AAAA,iBAAa,oBAAC,YAAS,MAAK,YAAY,qBAAU;AAAA,IAClD;AAAA,KACH,GACF;AAEJ;AAiBA,SAAS,eAAe,EAAE,OAAO,UAAU,QAAQ,gBAAgB,WAAW,GAAkG;AAC9K,QAAM,EAAE,WAAW,cAAc,SAAS,IAAI,SAAS;AACvD,SACE,iCACE;AAAA,yBAAC,SAAI,WAAW,EAAE,MAAM,GACrB;AAAA;AAAA,MACA,YACC,oBAAC,gBAAgB,OAAhB,EAAsB,WAAW,GAAG,EAAE,WAAW,GAAG,cAAc,GAAG,cAAY,YAChF,8BAAC,cAAW,WAAU,WAAU,GAClC;AAAA,OAEJ;AAAA,IACA,oBAAC,SAAI,WAAW,EAAE,IAAI,GAAI,UAAS;AAAA,KACjC,UAAW,kBAAkB,CAAC,aAC9B,qBAAC,SAAI,WAAW,EAAE,OAAO,GACtB;AAAA;AAAA,MACA,kBAAkB,CAAC,YAClB,qBAAC,YAAO,MAAK,UAAS,WAAW,EAAE,SAAS,GAAG,iBAAe,CAAC,WAAW,SAAS,MAAM,aAAa,CAAC,SAAS,GAC9G;AAAA,4BAAC,gBAAa,WAAW,YAAY,UAAU,QAAQ,WAAU,WAAU;AAAA,QAC3E,oBAAC,UAAK,WAAW,YAAY,YAAY,QAAY,sBAAY,eAAe,CAAC,IAAI,eAAe,CAAC,GAAE;AAAA,SACzG;AAAA,OAEJ;AAAA,KAEJ;AAEJ;AAGO,SAAS,gBAAgB,EAAE,OAAO,OAAO,UAAU,QAAQ,gBAAgB,YAAY,WAAW,GAAG,MAAM,GAAyB;AACzI,QAAM,QAAQ,SAAS;AACvB,QAAM,UAAU,EAAE,OAAO,UAAU,QAAQ,gBAAgB,WAAW;AACtE,SACE,iCACE;AAAA,wBAAC,SAAI,cAAY,OAAO,kBAAgB,MAAM,YAAY,KAAK,QAAW,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAI,GAAG,OACjH,8BAAC,kBAAgB,GAAG,SAAS,GAC/B;AAAA,IACA,oBAAC,gBAAgB,MAAhB,EAAqB,MAAM,MAAM,MAAM,cAAc,MAAM,SAC1D,+BAAC,gBAAgB,QAAhB,EACC;AAAA,0BAAC,gBAAgB,SAAhB,EAAwB,WAAW,EAAE,QAAQ,GAAG;AAAA,MACjD,qBAAC,gBAAgB,SAAhB,EAAwB,WAAW,EAAE,OAAO,GAAG,oBAAkB,QAChE;AAAA,4BAAC,gBAAgB,OAAhB,EAAsB,WAAU,WAAW,iBAAM;AAAA,QAClD,oBAAC,aAAa,UAAb,EAAsB,OAAO,EAAE,GAAG,OAAO,WAAW,OAAO,UAAU,KAAK,GACzE,8BAAC,SAAI,cAAY,OAAO,WAAU,gCAA+B,SAAS,CAAC,MAAO,EAAE,OAAmB,QAAQ,GAAG,KAAK,MAAM,QAAQ,KAAK,GACxI,8BAAC,kBAAgB,GAAG,SAAS,GAC/B,GACF;AAAA,SACF;AAAA,OACF,GACF;AAAA,KACF;AAEJ;AAQO,SAAS,cAAc,EAAE,WAAW,GAAG,MAAM,GAAuB;AACzE,SAAO,oBAAC,OAAE,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AAC1D;AAkBO,SAAS,aAAa,EAAE,MAAM,OAAO,SAAS,OAAO,SAAS,UAAU,WAAW,GAAG,MAAM,GAAsB;AACvH,QAAM,EAAE,UAAU,IAAI,SAAS;AAC/B,QAAM,OAAO,UAAU,KAAK,OAAO;AACnC,QAAM,QACJ,iCACG;AAAA;AAAA,IACD,oBAAC,UAAK,WAAW,EAAE,UAAU,GAAI,iBAAM;AAAA,IACtC,SAAS,QAAQ,oBAAC,UAAK,WAAW,EAAE,UAAU,GAAI,iBAAM;AAAA,KAC3D;AAEF,SACE,oBAAC,QAAK,gBAAc,UAAU,SAAS,QAAW,OAAO,YAAY,QAAQ,QAAW,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAC3H,qBAAW,WAAiB,mBAAa,UAAU,QAAW,KAAK,IAAI,OAC1E;AAEJ;AAGO,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAgC;AACjF,SAAO,oBAAC,SAAI,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AAC3D;AAYO,SAAS,eAAe,EAAE,OAAO,WAAW,SAAS,WAAW,UAAU,GAAG,MAAM,GAAwB;AAChH,QAAM,EAAE,MAAM,QAAQ,IAAI,SAAS;AACnC,SACE,qBAAC,YAAO,WAAW,EAAE,OAAO,EAAE,UAAU,CAAC,GAAI,GAAG,OAC9C;AAAA,wBAAC,YAAO,MAAK,UAAS,WAAW,EAAE,WAAW,GAAG,cAAY,WAAW,iBAAe,MAAM,iBAAc,UAAS,SAAS,MAAM,QAAQ,IAAI,GAC7I,8BAAC,SAAI,SAAQ,aAAY,WAAU,WAAU,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,eAAW,MAC9H,8BAAC,UAAK,GAAE,2BAA0B,GACpC,GACF;AAAA,IACC,SAAS,oBAAC,QAAG,WAAW,EAAE,MAAM,GAAI,iBAAM;AAAA,IAC1C;AAAA,IACA,WAAW,oBAAC,SAAI,WAAW,EAAE,QAAQ,GAAI,mBAAQ;AAAA,KACpD;AAEJ;AAGO,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAiC;AAClF,SAAO,oBAAC,UAAK,IAAG,WAAU,UAAU,IAAI,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,OAAO;AACvF;AAEO,IAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;","names":["value"]}
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ // src/skip-link/skip-link.tsx
4
+ import { tv } from "tailwind-variants";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var skipLink = tv({
7
+ base: "sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-[100] focus:rounded-full focus:bg-surface-1 focus:px-4 focus:py-2 focus:text-sm focus:font-semibold focus:text-fg focus:no-underline focus:shadow-lg focus:outline-hidden focus:ring-2 focus:ring-ring"
8
+ });
9
+ function SkipLink({ href = "#content", className, onClick, children, ...props }) {
10
+ const handleClick = (event) => {
11
+ onClick?.(event);
12
+ if (event.defaultPrevented) return;
13
+ const target = document.getElementById(decodeURIComponent(href.slice(1)));
14
+ if (!target) return;
15
+ if (!target.hasAttribute("tabindex") && !target.matches("a[href], button, input, select, textarea, [contenteditable]")) target.setAttribute("tabindex", "-1");
16
+ target.focus();
17
+ };
18
+ return /* @__PURE__ */ jsx("a", { href, className: skipLink({ className }), onClick: handleClick, ...props, children });
19
+ }
20
+
21
+ export {
22
+ skipLink,
23
+ SkipLink
24
+ };
25
+ //# sourceMappingURL=chunk-3GODX3D6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/skip-link/skip-link.tsx"],"sourcesContent":["// Client (it moves focus). WCAG 2.4.1 Bypass Blocks: the first tab stop of a page, a link that skips the navigation and\n// lands on the content. Visually hidden until focused, then a pill above everything in the brand ring. The label is\n// passed translated; the target needs an `id` (and is made focusable, so focus really moves, router or not).\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const skipLink = tv({\n base:\n \"sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-[100] focus:rounded-full focus:bg-surface-1 focus:px-4 focus:py-2 \" +\n \"focus:text-sm focus:font-semibold focus:text-fg focus:no-underline focus:shadow-lg focus:outline-hidden focus:ring-2 focus:ring-ring\",\n});\n\nexport type SkipLinkProps = Omit<React.ComponentProps<\"a\">, \"href\"> & {\n /**\n * The content to land on: `#` and its `id`. The element becomes focusable (`tabindex=\"-1\"`) when it is not.\n * @default \"#content\"\n */\n href?: `#${string}`;\n};\n\n/** A \"skip to content\" link: the first tab stop, hidden until focused; Enter moves focus to the target. */\nexport function SkipLink({ href = \"#content\", className, onClick, children, ...props }: SkipLinkProps) {\n const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {\n onClick?.(event);\n if (event.defaultPrevented) return;\n const target = document.getElementById(decodeURIComponent(href.slice(1)));\n if (!target) return;\n if (!target.hasAttribute(\"tabindex\") && !target.matches(\"a[href], button, input, select, textarea, [contenteditable]\")) target.setAttribute(\"tabindex\", \"-1\");\n target.focus();\n };\n return (\n <a href={href} className={skipLink({ className })} onClick={handleClick} {...props}>\n {children}\n </a>\n );\n}\n"],"mappings":";;;AAIA,SAAS,UAAU;AA2Bf;AAzBG,IAAM,WAAW,GAAG;AAAA,EACzB,MACE;AAEJ,CAAC;AAWM,SAAS,SAAS,EAAE,OAAO,YAAY,WAAW,SAAS,UAAU,GAAG,MAAM,GAAkB;AACrG,QAAM,cAAc,CAAC,UAA+C;AAClE,cAAU,KAAK;AACf,QAAI,MAAM,iBAAkB;AAC5B,UAAM,SAAS,SAAS,eAAe,mBAAmB,KAAK,MAAM,CAAC,CAAC,CAAC;AACxE,QAAI,CAAC,OAAQ;AACb,QAAI,CAAC,OAAO,aAAa,UAAU,KAAK,CAAC,OAAO,QAAQ,6DAA6D,EAAG,QAAO,aAAa,YAAY,IAAI;AAC5J,WAAO,MAAM;AAAA,EACf;AACA,SACE,oBAAC,OAAE,MAAY,WAAW,SAAS,EAAE,UAAU,CAAC,GAAG,SAAS,aAAc,GAAG,OAC1E,UACH;AAEJ;","names":[]}
@@ -6,6 +6,25 @@ import {
6
6
  // src/dialog/dialog.tsx
7
7
  import { Dialog as DialogPrimitive } from "radix-ui";
8
8
  import { tv } from "tailwind-variants";
9
+
10
+ // src/dialog/return-focus.ts
11
+ import * as React from "react";
12
+ function useReturnFocus(onCloseAutoFocus) {
13
+ const [opener] = React.useState(() => typeof document === "undefined" ? null : document.activeElement);
14
+ return React.useCallback(
15
+ (event) => {
16
+ onCloseAutoFocus?.(event);
17
+ if (event.defaultPrevented) return;
18
+ if (opener instanceof HTMLElement && opener !== document.body && opener.isConnected) {
19
+ event.preventDefault();
20
+ opener.focus();
21
+ }
22
+ },
23
+ [opener, onCloseAutoFocus]
24
+ );
25
+ }
26
+
27
+ // src/dialog/dialog.tsx
9
28
  import { jsx, jsxs } from "react/jsx-runtime";
10
29
  var dialog = tv({
11
30
  slots: {
@@ -38,6 +57,9 @@ function CloseIcon() {
38
57
  return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
39
58
  }
40
59
  var prevent = (event) => event.preventDefault();
60
+ function ReturnFocusContent({ onCloseAutoFocus, ...props }) {
61
+ return /* @__PURE__ */ jsx(DialogPrimitive.Content, { onCloseAutoFocus: useReturnFocus(onCloseAutoFocus), ...props });
62
+ }
41
63
  function DialogContent({
42
64
  placement,
43
65
  size,
@@ -56,7 +78,7 @@ function DialogContent({
56
78
  return /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { container, children: [
57
79
  /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: v.overlay() }),
58
80
  /* @__PURE__ */ jsxs(
59
- DialogPrimitive.Content,
81
+ ReturnFocusContent,
60
82
  {
61
83
  "data-placement": placement ?? "center",
62
84
  "data-dismissible": dismissible ? void 0 : "false",
@@ -110,6 +132,7 @@ function Sheet(props) {
110
132
  }
111
133
 
112
134
  export {
135
+ useReturnFocus,
113
136
  dialog,
114
137
  DialogContent,
115
138
  DialogHeader,
@@ -120,4 +143,4 @@ export {
120
143
  Dialog,
121
144
  Sheet
122
145
  };
123
- //# sourceMappingURL=chunk-R6SDS7JM.js.map
146
+ //# sourceMappingURL=chunk-ND3L5VHT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dialog/dialog.tsx","../src/dialog/return-focus.ts"],"sourcesContent":["// Client (a portal). Radix does the focus trap, the scroll lock, Escape, the outside click, aria-modal, the\n// aria-labelledby / aria-describedby wiring and the focus return: this file only holds the shape.\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { IconButton } from \"../button/button\";\nimport { useReturnFocus } from \"./return-focus\";\n\nexport const dialog = tv({\n slots: {\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n content:\n \"kz-dialog fixed z-50 flex flex-col overflow-hidden border border-border-default bg-surface-1 text-fg shadow-lg focus:outline-hidden\",\n close: \"absolute right-3 top-3 h-9 w-9\",\n header: \"flex flex-col gap-1 px-5 pb-2 pr-14 pt-5\",\n title: \"text-base font-bold text-fg\",\n description: \"text-sm text-fg-secondary\",\n body: \"min-h-0 flex-1 overflow-y-auto px-5 pb-5\",\n footer:\n \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle px-5 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]\",\n },\n variants: {\n placement: {\n center: { content: \"left-1/2 top-1/2 max-h-[85dvh] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl\" },\n /* A sheet: anchored at the bottom on a phone, centred from `sm` up. */\n bottom: {\n content:\n \"inset-x-0 bottom-0 max-h-[92dvh] rounded-t-[1.75rem] sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:w-full \" +\n \"sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-[1.75rem]\",\n },\n right: { content: \"inset-y-0 right-0 h-full w-full max-w-md rounded-none border-y-0 border-r-0\" },\n /* A navigation drawer: full height, from the left, clear of the notch. */\n left: { content: \"inset-y-0 left-0 h-full w-full max-w-md rounded-none border-y-0 border-l-0 pl-[env(safe-area-inset-left)]\" },\n },\n size: { sm: { content: \"sm:max-w-sm\" }, md: { content: \"sm:max-w-md\" }, lg: { content: \"sm:max-w-2xl\" } },\n },\n defaultVariants: { placement: \"center\", size: \"md\" },\n});\nconst s = dialog();\n\nexport type DialogVariants = VariantProps<typeof dialog>;\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype CloseButtonProps =\n | {\n /** The accessible name of the close button — passed translated. */\n closeLabel: string;\n /** Leaves the close button out (the content brings its own way out, or none: see `dismissible`). */\n hideClose?: false;\n }\n | { closeLabel?: string; hideClose: true };\n\nexport type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> &\n DialogVariants &\n CloseButtonProps & {\n /**\n * center · bottom (a sheet on a phone, centred from `sm` up) · right (a side panel, full height) · left (a\n * navigation drawer, full height).\n * @default \"center\"\n */\n placement?: DialogVariants[\"placement\"];\n /**\n * sm · md · lg: the width of the dialog.\n * @default \"md\"\n */\n size?: DialogVariants[\"size\"];\n /**\n * `false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\n * through `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.\n */\n dismissible?: boolean;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n };\n\nconst prevent = (event: Event) => event.preventDefault();\n\n/** Radix's content, mounted only while open: it remembers the opener then and gives focus back on close (WCAG 2.4.3). */\nfunction ReturnFocusContent({ onCloseAutoFocus, ...props }: React.ComponentProps<typeof DialogPrimitive.Content>) {\n return <DialogPrimitive.Content onCloseAutoFocus={useReturnFocus(onCloseAutoFocus)} {...props} />;\n}\n\n/** The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`. */\nexport function DialogContent({\n placement,\n size,\n closeLabel,\n hideClose,\n dismissible = true,\n container,\n className,\n children,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n ...props\n}: DialogContentProps) {\n const v = dialog({ placement, size });\n return (\n <DialogPrimitive.Portal container={container}>\n <DialogPrimitive.Overlay className={v.overlay()} />\n <ReturnFocusContent\n data-placement={placement ?? \"center\"}\n data-dismissible={dismissible ? undefined : \"false\"}\n className={v.content({ className })}\n onEscapeKeyDown={dismissible ? onEscapeKeyDown : chain(onEscapeKeyDown, prevent)}\n onPointerDownOutside={dismissible ? onPointerDownOutside : chain(onPointerDownOutside, prevent)}\n onInteractOutside={dismissible ? onInteractOutside : chain(onInteractOutside, prevent)}\n {...props}\n >\n {children}\n {!hideClose && (\n <DialogPrimitive.Close asChild>\n <IconButton label={closeLabel as string} variant=\"ghost\" shape=\"pill\" className={v.close()}>\n <CloseIcon />\n </IconButton>\n </DialogPrimitive.Close>\n )}\n </ReturnFocusContent>\n </DialogPrimitive.Portal>\n );\n}\n\nfunction chain<E extends Event>(handler: ((event: E) => void) | undefined, then: (event: E) => void) {\n return (event: E) => {\n handler?.(event);\n then(event);\n };\n}\n\nexport function DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.header({ className })} {...props} />;\n}\n\nexport function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return <DialogPrimitive.Title className={s.title({ className })} {...props} />;\n}\n\nexport function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return <DialogPrimitive.Description className={s.description({ className })} {...props} />;\n}\n\nexport function DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport function DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.footer({ className })} {...props} />;\n}\n\n/** `Dialog.Root/Trigger/Content/Header/Title/Description/Body/Footer/Close`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const Dialog = {\n Root: DialogPrimitive.Root,\n Trigger: DialogPrimitive.Trigger,\n Close: DialogPrimitive.Close,\n Content: DialogContent,\n Header: DialogHeader,\n Title: DialogTitle,\n Description: DialogDescription,\n Body: DialogBody,\n Footer: DialogFooter,\n};\n\n/** `Omit` that keeps the close-button union (`closeLabel` required unless `hideClose`). */\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\nexport type SheetProps = DistributiveOmit<DialogContentProps, \"placement\">;\n\n/** A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`. */\nexport function Sheet(props: SheetProps) {\n return <DialogContent placement=\"bottom\" {...props} />;\n}\n","// Client. WCAG 2.4.3: when a dialog closes, focus goes back to what opened it. Radix only does it for `Dialog.Trigger`;\n// most app dialogs are controlled (`open` / `onOpenChange`) and opened by a plain button, so on Escape focus fell to\n// <body>. The content remembers the focused element when it mounts (it is still the opener: Radix moves focus in an\n// effect) and gives it back on close, unless the consumer's `onCloseAutoFocus` prevented the default.\nimport * as React from \"react\";\n\n/** The handler for `onCloseAutoFocus`: yours first, then focus back on the opener if it is still on the page. */\nexport function useReturnFocus(onCloseAutoFocus?: (event: Event) => void) {\n const [opener] = React.useState<Element | null>(() => (typeof document === \"undefined\" ? null : document.activeElement));\n return React.useCallback(\n (event: Event) => {\n onCloseAutoFocus?.(event);\n if (event.defaultPrevented) return;\n if (opener instanceof HTMLElement && opener !== document.body && opener.isConnected) {\n event.preventDefault();\n opener.focus();\n }\n },\n [opener, onCloseAutoFocus],\n );\n}\n"],"mappings":"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","names":[]}
package/dist/command.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  DialogContent
4
- } from "./chunk-R6SDS7JM.js";
4
+ } from "./chunk-ND3L5VHT.js";
5
5
  import "./chunk-RUPNPMP3.js";
6
6
  import "./chunk-WEVYKZIF.js";
7
7
  import {
package/dist/dialog.js CHANGED
@@ -8,8 +8,9 @@ import {
8
8
  DialogHeader,
9
9
  DialogTitle,
10
10
  Sheet,
11
- dialog
12
- } from "./chunk-R6SDS7JM.js";
11
+ dialog,
12
+ useReturnFocus
13
+ } from "./chunk-ND3L5VHT.js";
13
14
  import {
14
15
  Button
15
16
  } from "./chunk-RUPNPMP3.js";
@@ -20,6 +21,9 @@ import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
20
21
  import * as React from "react";
21
22
  import { jsx, jsxs } from "react/jsx-runtime";
22
23
  var s = dialog({ placement: "center", size: "sm" });
24
+ function ReturnFocusContent({ onCloseAutoFocus, ...props }) {
25
+ return /* @__PURE__ */ jsx(AlertDialogPrimitive.Content, { onCloseAutoFocus: useReturnFocus(onCloseAutoFocus), ...props });
26
+ }
23
27
  function AlertDialog({
24
28
  open: openProp,
25
29
  defaultOpen = false,
@@ -57,7 +61,7 @@ function AlertDialog({
57
61
  /* @__PURE__ */ jsxs(AlertDialogPrimitive.Portal, { container, children: [
58
62
  /* @__PURE__ */ jsx(AlertDialogPrimitive.Overlay, { className: s.overlay() }),
59
63
  /* @__PURE__ */ jsxs(
60
- AlertDialogPrimitive.Content,
64
+ ReturnFocusContent,
61
65
  {
62
66
  "data-tone": tone,
63
67
  "aria-busy": pending || void 0,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dialog/alert-dialog.tsx"],"sourcesContent":["// Client. A confirmation that interrupts: Radix AlertDialog (role=\"alertdialog\", no outside click, focus on Cancel).\n// `onConfirm` may be async: the dialog stays open with the confirm button loading until it settles, closes when it\n// resolves, and stays open when it rejects (the product shows the error).\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { Button } from \"../button/button\";\nimport { dialog } from \"./dialog\";\n\nconst s = dialog({ placement: \"center\", size: \"sm\" });\n\nexport type AlertDialogProps = {\n /** Open or closed (controlled), with `onOpenChange`. */\n open?: boolean;\n /** Open at first, uncontrolled. */\n defaultOpen?: boolean;\n /** Called when it opens or closes (never while `onConfirm` is pending). */\n onOpenChange?: (open: boolean) => void;\n /** The element that opens the dialog (rendered as is, `asChild`): a Button, an IconButton. */\n trigger?: React.ReactNode;\n /** The question asked (\"Delete this video?\"): the dialog's accessible name. */\n title: React.ReactNode;\n /** What the action does and what it costs: the dialog's description. */\n description?: React.ReactNode;\n /** More content between the description and the actions. */\n children?: React.ReactNode;\n /** The words of the confirm button — passed translated. */\n confirmLabel: string;\n /** The words of the cancel button (it takes the focus at open) — passed translated. */\n cancelLabel: string;\n /** `danger` for a destructive action (delete, leave, cancel a payment). */\n tone?: \"danger\" | \"primary\";\n /** Runs on confirm. A promise keeps the dialog open and the button loading until it settles. */\n onConfirm: () => void | Promise<unknown>;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n /** Classes of the dialog's content, merged last. */\n className?: string;\n};\n\nexport function AlertDialog({\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n trigger,\n title,\n description,\n children,\n confirmLabel,\n cancelLabel,\n tone = \"primary\",\n onConfirm,\n container,\n className,\n}: AlertDialogProps) {\n const [openState, setOpenState] = React.useState(defaultOpen);\n const [pending, setPending] = React.useState(false);\n const open = openProp ?? openState;\n\n const setOpen = (next: boolean) => {\n if (openProp === undefined) setOpenState(next);\n onOpenChange?.(next);\n };\n\n const confirm = async () => {\n setPending(true);\n try {\n await onConfirm();\n setOpen(false);\n } catch {\n // Stays open: the product reports the failure (a toast, an inline error) and the person may retry.\n } finally {\n setPending(false);\n }\n };\n\n return (\n <AlertDialogPrimitive.Root open={open} onOpenChange={(next) => (pending ? undefined : setOpen(next))}>\n {trigger && <AlertDialogPrimitive.Trigger asChild>{trigger}</AlertDialogPrimitive.Trigger>}\n <AlertDialogPrimitive.Portal container={container}>\n <AlertDialogPrimitive.Overlay className={s.overlay()} />\n <AlertDialogPrimitive.Content\n data-tone={tone}\n aria-busy={pending || undefined}\n className={s.content({ className })}\n onEscapeKeyDown={(event) => pending && event.preventDefault()}\n >\n <div className={s.header({ className: \"pr-5\" })}>\n <AlertDialogPrimitive.Title className={s.title()}>{title}</AlertDialogPrimitive.Title>\n {description && <AlertDialogPrimitive.Description className={s.description()}>{description}</AlertDialogPrimitive.Description>}\n </div>\n {children && <div className={s.body()}>{children}</div>}\n <div className={s.footer()}>\n <AlertDialogPrimitive.Cancel asChild>\n <Button variant=\"ghost\" disabled={pending}>\n {cancelLabel}\n </Button>\n </AlertDialogPrimitive.Cancel>\n <Button variant={tone === \"danger\" ? \"danger\" : \"primary\"} loading={pending} onClick={confirm}>\n {confirmLabel}\n </Button>\n </div>\n </AlertDialogPrimitive.Content>\n </AlertDialogPrimitive.Portal>\n </AlertDialogPrimitive.Root>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAGA,SAAS,eAAe,4BAA4B;AACpD,YAAY,WAAW;AA0EL,cASR,YATQ;AArElB,IAAM,IAAI,OAAO,EAAE,WAAW,UAAU,MAAM,KAAK,CAAC;AA+B7C,SAAS,YAAY;AAAA,EAC1B,MAAM;AAAA,EACN,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,WAAW,YAAY,IAAU,eAAS,WAAW;AAC5D,QAAM,CAAC,SAAS,UAAU,IAAU,eAAS,KAAK;AAClD,QAAM,OAAO,YAAY;AAEzB,QAAM,UAAU,CAAC,SAAkB;AACjC,QAAI,aAAa,OAAW,cAAa,IAAI;AAC7C,mBAAe,IAAI;AAAA,EACrB;AAEA,QAAM,UAAU,YAAY;AAC1B,eAAW,IAAI;AACf,QAAI;AACF,YAAM,UAAU;AAChB,cAAQ,KAAK;AAAA,IACf,QAAQ;AAAA,IAER,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,SACE,qBAAC,qBAAqB,MAArB,EAA0B,MAAY,cAAc,CAAC,SAAU,UAAU,SAAY,QAAQ,IAAI,GAC/F;AAAA,eAAW,oBAAC,qBAAqB,SAArB,EAA6B,SAAO,MAAE,mBAAQ;AAAA,IAC3D,qBAAC,qBAAqB,QAArB,EAA4B,WAC3B;AAAA,0BAAC,qBAAqB,SAArB,EAA6B,WAAW,EAAE,QAAQ,GAAG;AAAA,MACtD;AAAA,QAAC,qBAAqB;AAAA,QAArB;AAAA,UACC,aAAW;AAAA,UACX,aAAW,WAAW;AAAA,UACtB,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC;AAAA,UAClC,iBAAiB,CAAC,UAAU,WAAW,MAAM,eAAe;AAAA,UAE5D;AAAA,iCAAC,SAAI,WAAW,EAAE,OAAO,EAAE,WAAW,OAAO,CAAC,GAC5C;AAAA,kCAAC,qBAAqB,OAArB,EAA2B,WAAW,EAAE,MAAM,GAAI,iBAAM;AAAA,cACxD,eAAe,oBAAC,qBAAqB,aAArB,EAAiC,WAAW,EAAE,YAAY,GAAI,uBAAY;AAAA,eAC7F;AAAA,YACC,YAAY,oBAAC,SAAI,WAAW,EAAE,KAAK,GAAI,UAAS;AAAA,YACjD,qBAAC,SAAI,WAAW,EAAE,OAAO,GACvB;AAAA,kCAAC,qBAAqB,QAArB,EAA4B,SAAO,MAClC,8BAAC,UAAO,SAAQ,SAAQ,UAAU,SAC/B,uBACH,GACF;AAAA,cACA,oBAAC,UAAO,SAAS,SAAS,WAAW,WAAW,WAAW,SAAS,SAAS,SAAS,SACnF,wBACH;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/dialog/alert-dialog.tsx"],"sourcesContent":["// Client. A confirmation that interrupts: Radix AlertDialog (role=\"alertdialog\", no outside click, focus on Cancel).\n// `onConfirm` may be async: the dialog stays open with the confirm button loading until it settles, closes when it\n// resolves, and stays open when it rejects (the product shows the error).\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { Button } from \"../button/button\";\nimport { dialog } from \"./dialog\";\nimport { useReturnFocus } from \"./return-focus\";\n\nconst s = dialog({ placement: \"center\", size: \"sm\" });\n\n/** Radix's content, mounted only while open: it remembers the opener then and gives focus back on close (WCAG 2.4.3). */\nfunction ReturnFocusContent({ onCloseAutoFocus, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {\n return <AlertDialogPrimitive.Content onCloseAutoFocus={useReturnFocus(onCloseAutoFocus)} {...props} />;\n}\n\nexport type AlertDialogProps = {\n /** Open or closed (controlled), with `onOpenChange`. */\n open?: boolean;\n /** Open at first, uncontrolled. */\n defaultOpen?: boolean;\n /** Called when it opens or closes (never while `onConfirm` is pending). */\n onOpenChange?: (open: boolean) => void;\n /** The element that opens the dialog (rendered as is, `asChild`): a Button, an IconButton. */\n trigger?: React.ReactNode;\n /** The question asked (\"Delete this video?\"): the dialog's accessible name. */\n title: React.ReactNode;\n /** What the action does and what it costs: the dialog's description. */\n description?: React.ReactNode;\n /** More content between the description and the actions. */\n children?: React.ReactNode;\n /** The words of the confirm button — passed translated. */\n confirmLabel: string;\n /** The words of the cancel button (it takes the focus at open) — passed translated. */\n cancelLabel: string;\n /** `danger` for a destructive action (delete, leave, cancel a payment). */\n tone?: \"danger\" | \"primary\";\n /** Runs on confirm. A promise keeps the dialog open and the button loading until it settles. */\n onConfirm: () => void | Promise<unknown>;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n /** Classes of the dialog's content, merged last. */\n className?: string;\n};\n\nexport function AlertDialog({\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n trigger,\n title,\n description,\n children,\n confirmLabel,\n cancelLabel,\n tone = \"primary\",\n onConfirm,\n container,\n className,\n}: AlertDialogProps) {\n const [openState, setOpenState] = React.useState(defaultOpen);\n const [pending, setPending] = React.useState(false);\n const open = openProp ?? openState;\n\n const setOpen = (next: boolean) => {\n if (openProp === undefined) setOpenState(next);\n onOpenChange?.(next);\n };\n\n const confirm = async () => {\n setPending(true);\n try {\n await onConfirm();\n setOpen(false);\n } catch {\n // Stays open: the product reports the failure (a toast, an inline error) and the person may retry.\n } finally {\n setPending(false);\n }\n };\n\n return (\n <AlertDialogPrimitive.Root open={open} onOpenChange={(next) => (pending ? undefined : setOpen(next))}>\n {trigger && <AlertDialogPrimitive.Trigger asChild>{trigger}</AlertDialogPrimitive.Trigger>}\n <AlertDialogPrimitive.Portal container={container}>\n <AlertDialogPrimitive.Overlay className={s.overlay()} />\n <ReturnFocusContent\n data-tone={tone}\n aria-busy={pending || undefined}\n className={s.content({ className })}\n onEscapeKeyDown={(event) => pending && event.preventDefault()}\n >\n <div className={s.header({ className: \"pr-5\" })}>\n <AlertDialogPrimitive.Title className={s.title()}>{title}</AlertDialogPrimitive.Title>\n {description && <AlertDialogPrimitive.Description className={s.description()}>{description}</AlertDialogPrimitive.Description>}\n </div>\n {children && <div className={s.body()}>{children}</div>}\n <div className={s.footer()}>\n <AlertDialogPrimitive.Cancel asChild>\n <Button variant=\"ghost\" disabled={pending}>\n {cancelLabel}\n </Button>\n </AlertDialogPrimitive.Cancel>\n <Button variant={tone === \"danger\" ? \"danger\" : \"primary\"} loading={pending} onClick={confirm}>\n {confirmLabel}\n </Button>\n </div>\n </ReturnFocusContent>\n </AlertDialogPrimitive.Portal>\n </AlertDialogPrimitive.Root>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAGA,SAAS,eAAe,4BAA4B;AACpD,YAAY,WAAW;AAUd,cA+EC,YA/ED;AAJT,IAAM,IAAI,OAAO,EAAE,WAAW,UAAU,MAAM,KAAK,CAAC;AAGpD,SAAS,mBAAmB,EAAE,kBAAkB,GAAG,MAAM,GAA8D;AACrH,SAAO,oBAAC,qBAAqB,SAArB,EAA6B,kBAAkB,eAAe,gBAAgB,GAAI,GAAG,OAAO;AACtG;AA+BO,SAAS,YAAY;AAAA,EAC1B,MAAM;AAAA,EACN,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,WAAW,YAAY,IAAU,eAAS,WAAW;AAC5D,QAAM,CAAC,SAAS,UAAU,IAAU,eAAS,KAAK;AAClD,QAAM,OAAO,YAAY;AAEzB,QAAM,UAAU,CAAC,SAAkB;AACjC,QAAI,aAAa,OAAW,cAAa,IAAI;AAC7C,mBAAe,IAAI;AAAA,EACrB;AAEA,QAAM,UAAU,YAAY;AAC1B,eAAW,IAAI;AACf,QAAI;AACF,YAAM,UAAU;AAChB,cAAQ,KAAK;AAAA,IACf,QAAQ;AAAA,IAER,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,SACE,qBAAC,qBAAqB,MAArB,EAA0B,MAAY,cAAc,CAAC,SAAU,UAAU,SAAY,QAAQ,IAAI,GAC/F;AAAA,eAAW,oBAAC,qBAAqB,SAArB,EAA6B,SAAO,MAAE,mBAAQ;AAAA,IAC3D,qBAAC,qBAAqB,QAArB,EAA4B,WAC3B;AAAA,0BAAC,qBAAqB,SAArB,EAA6B,WAAW,EAAE,QAAQ,GAAG;AAAA,MACtD;AAAA,QAAC;AAAA;AAAA,UACC,aAAW;AAAA,UACX,aAAW,WAAW;AAAA,UACtB,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC;AAAA,UAClC,iBAAiB,CAAC,UAAU,WAAW,MAAM,eAAe;AAAA,UAE5D;AAAA,iCAAC,SAAI,WAAW,EAAE,OAAO,EAAE,WAAW,OAAO,CAAC,GAC5C;AAAA,kCAAC,qBAAqB,OAArB,EAA2B,WAAW,EAAE,MAAM,GAAI,iBAAM;AAAA,cACxD,eAAe,oBAAC,qBAAqB,aAArB,EAAiC,WAAW,EAAE,YAAY,GAAI,uBAAY;AAAA,eAC7F;AAAA,YACC,YAAY,oBAAC,SAAI,WAAW,EAAE,KAAK,GAAI,UAAS;AAAA,YACjD,qBAAC,SAAI,WAAW,EAAE,OAAO,GACvB;AAAA,kCAAC,qBAAqB,QAArB,EAA4B,SAAO,MAClC,8BAAC,UAAO,SAAQ,SAAQ,UAAU,SAC/B,uBACH,GACF;AAAA,cACA,oBAAC,UAAO,SAAS,SAAS,WAAW,WAAW,WAAW,SAAS,SAAS,SAAS,SACnF,wBACH;AAAA,eACF;AAAA;AAAA;AAAA,MACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
package/dist/loader.d.ts CHANGED
@@ -7,22 +7,26 @@ declare const loader: tailwind_variants.TVReturnType<{
7
7
  inline: {
8
8
  box: string;
9
9
  glyph: string;
10
+ mark: string;
10
11
  };
11
12
  block: {
12
13
  box: string;
13
14
  glyph: string;
15
+ mark: string;
14
16
  text: string;
15
17
  };
16
18
  overlay: {
17
19
  root: string;
18
20
  box: string;
19
21
  glyph: string;
22
+ mark: string;
20
23
  text: string;
21
24
  };
22
25
  page: {
23
26
  root: string;
24
27
  box: string;
25
28
  glyph: string;
29
+ mark: string;
26
30
  text: string;
27
31
  label: string;
28
32
  };
@@ -39,6 +43,7 @@ declare const loader: tailwind_variants.TVReturnType<{
39
43
  root: string;
40
44
  box: string;
41
45
  glyph: string;
46
+ mark: string;
42
47
  text: string;
43
48
  label: string;
44
49
  detail: string;
@@ -47,22 +52,26 @@ declare const loader: tailwind_variants.TVReturnType<{
47
52
  inline: {
48
53
  box: string;
49
54
  glyph: string;
55
+ mark: string;
50
56
  };
51
57
  block: {
52
58
  box: string;
53
59
  glyph: string;
60
+ mark: string;
54
61
  text: string;
55
62
  };
56
63
  overlay: {
57
64
  root: string;
58
65
  box: string;
59
66
  glyph: string;
67
+ mark: string;
60
68
  text: string;
61
69
  };
62
70
  page: {
63
71
  root: string;
64
72
  box: string;
65
73
  glyph: string;
74
+ mark: string;
66
75
  text: string;
67
76
  label: string;
68
77
  };
@@ -79,6 +88,7 @@ declare const loader: tailwind_variants.TVReturnType<{
79
88
  root: string;
80
89
  box: string;
81
90
  glyph: string;
91
+ mark: string;
82
92
  text: string;
83
93
  label: string;
84
94
  detail: string;
@@ -87,22 +97,26 @@ declare const loader: tailwind_variants.TVReturnType<{
87
97
  inline: {
88
98
  box: string;
89
99
  glyph: string;
100
+ mark: string;
90
101
  };
91
102
  block: {
92
103
  box: string;
93
104
  glyph: string;
105
+ mark: string;
94
106
  text: string;
95
107
  };
96
108
  overlay: {
97
109
  root: string;
98
110
  box: string;
99
111
  glyph: string;
112
+ mark: string;
100
113
  text: string;
101
114
  };
102
115
  page: {
103
116
  root: string;
104
117
  box: string;
105
118
  glyph: string;
119
+ mark: string;
106
120
  text: string;
107
121
  label: string;
108
122
  };
@@ -119,6 +133,7 @@ declare const loader: tailwind_variants.TVReturnType<{
119
133
  root: string;
120
134
  box: string;
121
135
  glyph: string;
136
+ mark: string;
122
137
  text: string;
123
138
  label: string;
124
139
  detail: string;
@@ -166,12 +181,17 @@ interface LoaderProps extends Omit<React.ComponentProps<"div">, "children"> {
166
181
  * @default "inline"
167
182
  */
168
183
  variant?: LoaderVariants["variant"];
184
+ /**
185
+ * The product's mark in place of the Krizaka glyph while it waits — `<OrochiaLogo animated />` (or `ProductLogo`) of
186
+ * `@krizaka/ui`; it takes the loader's size. The ending (check or cross) stays the glyph's. Decorative: leave `title` off.
187
+ */
188
+ mark?: React.ReactNode;
169
189
  }
170
190
  /**
171
191
  * A wait, from start to end. `role="status"` (polite): its words are announced when it shows and when it ends. Under
172
192
  * prefers-reduced-motion the glyph rests on a still frame; the delay and the ending stay.
173
193
  */
174
- declare function Loader({ label, status, successLabel, errorLabel, value, eta, delay, minDuration, endDuration, variant, className, ...props }: LoaderProps): React.JSX.Element;
194
+ declare function Loader({ label, status, successLabel, errorLabel, value, eta, delay, minDuration, endDuration, variant, mark, className, ...props }: LoaderProps): React.JSX.Element;
175
195
  interface LoadingRegionProps extends React.ComponentProps<"div"> {
176
196
  /** Whether the content is on its way. */
177
197
  loading: boolean;
package/dist/loader.js CHANGED
@@ -12,24 +12,27 @@ var loader = tv({
12
12
  root: "text-fg-secondary",
13
13
  box: "kz-fade flex items-center gap-3",
14
14
  glyph: "shrink-0 text-accent",
15
+ mark: "block shrink-0 [&>svg]:block [&>svg]:h-full [&>svg]:w-full",
15
16
  text: "flex min-w-0 flex-col",
16
17
  label: "font-medium text-fg",
17
18
  detail: "text-xs text-fg-secondary tabular-nums"
18
19
  },
19
20
  variants: {
20
21
  variant: {
21
- inline: { box: "text-sm", glyph: "h-5 w-5" },
22
- block: { box: "flex-col justify-center py-10 text-center text-sm", glyph: "h-9 w-9", text: "items-center" },
22
+ inline: { box: "text-sm", glyph: "h-5 w-5", mark: "h-5 w-5" },
23
+ block: { box: "flex-col justify-center py-10 text-center text-sm", glyph: "h-9 w-9", mark: "h-9 w-9", text: "items-center" },
23
24
  overlay: {
24
25
  root: "absolute inset-0 z-10 flex items-center justify-center rounded-[inherit] empty:hidden",
25
26
  box: "flex-col rounded-xl border border-border-default bg-surface-1/90 px-6 py-5 text-center text-sm shadow-lg backdrop-blur-sm",
26
27
  glyph: "h-9 w-9",
28
+ mark: "h-9 w-9",
27
29
  text: "items-center"
28
30
  },
29
31
  page: {
30
32
  root: "fixed inset-0 z-50 flex items-center justify-center bg-surface-0/80 backdrop-blur-sm empty:hidden",
31
33
  box: "flex-col text-center",
32
34
  glyph: "h-14 w-14",
35
+ mark: "h-14 w-14",
33
36
  text: "items-center",
34
37
  label: "font-display text-lg"
35
38
  }
@@ -66,6 +69,7 @@ function Loader({
66
69
  minDuration = 600,
67
70
  endDuration = 1600,
68
71
  variant,
72
+ mark,
69
73
  className,
70
74
  ...props
71
75
  }) {
@@ -98,7 +102,7 @@ function Loader({
98
102
  const words = phase === "success" ? successLabel ?? label : phase === "error" ? errorLabel ?? label : label;
99
103
  const detail = phase === "loading" ? [known ? `${Math.round(Math.min(1, Math.max(0, value)) * 100)} %` : null, eta].filter(Boolean).join(" \xB7 ") : "";
100
104
  return /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", "data-phase": phase ?? "idle", className: s.root({ className }), ...props, children: phase && /* @__PURE__ */ jsxs("div", { className: s.box(), children: [
101
- /* @__PURE__ */ jsx(LoaderGlyph, { state: phase, value: known ? value : null, className: s.glyph() }),
105
+ mark && phase === "loading" ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: s.mark(), "data-mark": "", children: mark }) : /* @__PURE__ */ jsx(LoaderGlyph, { state: phase, value: known ? value : null, className: s.glyph() }),
102
106
  /* @__PURE__ */ jsxs("span", { className: s.text(), children: [
103
107
  /* @__PURE__ */ jsx("span", { className: s.label(), children: words }),
104
108
  detail && /* @__PURE__ */ jsx("span", { className: s.detail(), children: detail })