@eifi1/ui-kit 0.46.1 → 0.48.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 (216) hide show
  1. package/README.md +8 -6
  2. package/dist/admin/admin-sections.d.ts +8 -0
  3. package/dist/admin/admin-sections.js +2 -1
  4. package/dist/admin/admin-sections.js.map +1 -1
  5. package/dist/auth/verify-email.d.ts +9 -1
  6. package/dist/auth/verify-email.js +22 -13
  7. package/dist/auth/verify-email.js.map +1 -1
  8. package/dist/components/alert-banner.d.ts +29 -1
  9. package/dist/components/alert-banner.js +84 -7
  10. package/dist/components/alert-banner.js.map +1 -1
  11. package/dist/components/button-look.d.ts +4 -0
  12. package/dist/components/button-look.js +2 -0
  13. package/dist/components/button-look.js.map +1 -1
  14. package/dist/components/combobox-core.js +5 -1
  15. package/dist/components/combobox-core.js.map +1 -1
  16. package/dist/components/data-table-column-settings.d.ts +6 -1
  17. package/dist/components/data-table-column-settings.js +12 -2
  18. package/dist/components/data-table-column-settings.js.map +1 -1
  19. package/dist/components/data-table-fit.d.ts +44 -0
  20. package/dist/components/data-table-fit.js +103 -0
  21. package/dist/components/data-table-fit.js.map +1 -0
  22. package/dist/components/data-table-groups.d.ts +95 -0
  23. package/dist/components/data-table-groups.js +165 -0
  24. package/dist/components/data-table-groups.js.map +1 -0
  25. package/dist/components/data-table-head.js +3 -1
  26. package/dist/components/data-table-head.js.map +1 -1
  27. package/dist/components/data-table-labels.d.ts +7 -0
  28. package/dist/components/data-table-labels.js +3 -0
  29. package/dist/components/data-table-labels.js.map +1 -1
  30. package/dist/components/data-table-leading-row.d.ts +17 -0
  31. package/dist/components/data-table-leading-row.js +41 -0
  32. package/dist/components/data-table-leading-row.js.map +1 -0
  33. package/dist/components/data-table-resize.js +1 -1
  34. package/dist/components/data-table-resize.js.map +1 -1
  35. package/dist/components/data-table.d.ts +78 -2
  36. package/dist/components/data-table.js +355 -247
  37. package/dist/components/data-table.js.map +1 -1
  38. package/dist/components/disclosure.d.ts +3 -1
  39. package/dist/components/disclosure.js +10 -4
  40. package/dist/components/disclosure.js.map +1 -1
  41. package/dist/components/dropdown.js +8 -1
  42. package/dist/components/dropdown.js.map +1 -1
  43. package/dist/components/floating-panel.d.ts +14 -1
  44. package/dist/components/floating-panel.js +7 -2
  45. package/dist/components/floating-panel.js.map +1 -1
  46. package/dist/components/hover-menu.js +5 -1
  47. package/dist/components/hover-menu.js.map +1 -1
  48. package/dist/components/icon-button.js +2 -1
  49. package/dist/components/icon-button.js.map +1 -1
  50. package/dist/components/popover.js +5 -1
  51. package/dist/components/popover.js.map +1 -1
  52. package/dist/components/row-actions.d.ts +25 -0
  53. package/dist/components/row-actions.js +13 -3
  54. package/dist/components/row-actions.js.map +1 -1
  55. package/dist/components/table-skeleton.d.ts +35 -0
  56. package/dist/components/table-skeleton.js +75 -0
  57. package/dist/components/table-skeleton.js.map +1 -0
  58. package/dist/components/tabs.d.ts +22 -16
  59. package/dist/components/tabs.js +47 -31
  60. package/dist/components/tabs.js.map +1 -1
  61. package/dist/components/ui-labels.js +1 -1
  62. package/dist/components/ui-labels.js.map +1 -1
  63. package/dist/components/use-table-state.d.ts +8 -7
  64. package/dist/components/use-table-state.js +37 -3
  65. package/dist/components/use-table-state.js.map +1 -1
  66. package/dist/data-table.d.ts +1 -0
  67. package/dist/data-table.js.map +1 -1
  68. package/dist/eslint/index.js +1 -1
  69. package/dist/feedback/feedback-attachment-download.d.ts +19 -0
  70. package/dist/feedback/feedback-attachment-download.js +59 -0
  71. package/dist/feedback/feedback-attachment-download.js.map +1 -0
  72. package/dist/feedback/feedback-comment-thread.d.ts +17 -6
  73. package/dist/feedback/feedback-comment-thread.js +21 -2
  74. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  75. package/dist/feedback/feedback-entry-file.d.ts +61 -0
  76. package/dist/feedback/feedback-entry-file.js +76 -0
  77. package/dist/feedback/feedback-entry-file.js.map +1 -0
  78. package/dist/feedback/feedback-public-list.d.ts +18 -5
  79. package/dist/feedback/feedback-public-list.js +8 -0
  80. package/dist/feedback/feedback-public-list.js.map +1 -1
  81. package/dist/feedback/feedback-public-types.d.ts +7 -2
  82. package/dist/feedback/feedback-replies.d.ts +26 -9
  83. package/dist/feedback/feedback-replies.js +20 -10
  84. package/dist/feedback/feedback-replies.js.map +1 -1
  85. package/dist/feedback/feedback-row-detail-labels.js +0 -1
  86. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  87. package/dist/feedback/feedback-row-detail.d.ts +23 -10
  88. package/dist/feedback/feedback-row-detail.js +10 -54
  89. package/dist/feedback/feedback-row-detail.js.map +1 -1
  90. package/dist/feedback/feedback-table.d.ts +5 -0
  91. package/dist/feedback/feedback-table.js +30 -7
  92. package/dist/feedback/feedback-table.js.map +1 -1
  93. package/dist/feedback.d.ts +1 -0
  94. package/dist/feedback.js +1 -0
  95. package/dist/feedback.js.map +1 -1
  96. package/dist/hooks/use-dismiss.d.ts +30 -1
  97. package/dist/hooks/use-dismiss.js +30 -2
  98. package/dist/hooks/use-dismiss.js.map +1 -1
  99. package/dist/i18n/german.js +7 -7
  100. package/dist/i18n/german.js.map +1 -1
  101. package/dist/i18n/kit-labels.d.ts +4 -0
  102. package/dist/i18n/kit-labels.js.map +1 -1
  103. package/dist/i18n/locales/en.js +7 -7
  104. package/dist/i18n/locales/en.js.map +2 -2
  105. package/dist/i18n/locales/es.js +7 -7
  106. package/dist/i18n/locales/es.js.map +1 -1
  107. package/dist/i18n/locales/fr.js +7 -7
  108. package/dist/i18n/locales/fr.js.map +1 -1
  109. package/dist/i18n/locales/hu.js +7 -7
  110. package/dist/i18n/locales/hu.js.map +1 -1
  111. package/dist/i18n/locales/it.js +7 -7
  112. package/dist/i18n/locales/it.js.map +1 -1
  113. package/dist/i18n/locales/zh.js +7 -7
  114. package/dist/i18n/locales/zh.js.map +1 -1
  115. package/dist/i18n/review.js +0 -1
  116. package/dist/i18n/review.js.map +1 -1
  117. package/dist/i18n/shared-labels.js +3 -1
  118. package/dist/i18n/shared-labels.js.map +1 -1
  119. package/dist/index.d.ts +4 -4
  120. package/dist/index.js +13 -4
  121. package/dist/index.js.map +1 -1
  122. package/dist/lib/float-clearance.d.ts +8 -0
  123. package/dist/lib/float-clearance.js +29 -0
  124. package/dist/lib/float-clearance.js.map +1 -0
  125. package/dist/notifications/device-zone-report.d.ts +1 -1
  126. package/dist/notifications/device-zone-report.js.map +1 -1
  127. package/dist/notifications/notification-types.d.ts +0 -3
  128. package/dist/notifications/notifications-labels.d.ts +2 -15
  129. package/dist/notifications/notifications-labels.js +0 -4
  130. package/dist/notifications/notifications-labels.js.map +1 -1
  131. package/dist/notifications/quiet-hours-setting.d.ts +3 -8
  132. package/dist/notifications/quiet-hours-setting.js +8 -3
  133. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  134. package/dist/settings/settings-catalogue.d.ts +10 -0
  135. package/dist/settings/settings-catalogue.js.map +1 -1
  136. package/dist/settings/settings-layout.js +10 -3
  137. package/dist/settings/settings-layout.js.map +1 -1
  138. package/dist/shell/app-shell.d.ts +5 -0
  139. package/dist/shell/app-shell.js +5 -3
  140. package/dist/shell/app-shell.js.map +1 -1
  141. package/dist/shell/option-switcher-menu.d.ts +2 -0
  142. package/dist/shell/option-switcher-menu.js +6 -2
  143. package/dist/shell/option-switcher-menu.js.map +1 -1
  144. package/dist/shell/topbar-action-menu.js +2 -1
  145. package/dist/shell/topbar-action-menu.js.map +1 -1
  146. package/dist/shell/topbar-controls.js +5 -3
  147. package/dist/shell/topbar-controls.js.map +1 -1
  148. package/dist/theme/theme-store.d.ts +4 -4
  149. package/dist/theme/theme-store.js +1 -0
  150. package/dist/theme/theme-store.js.map +1 -1
  151. package/dist/tour/tour.js +1 -0
  152. package/dist/tour/tour.js.map +1 -1
  153. package/package.json +2 -2
  154. package/src/admin/admin-sections.tsx +8 -0
  155. package/src/auth/verify-email.tsx +34 -9
  156. package/src/components/alert-banner.tsx +131 -7
  157. package/src/components/button-look.ts +6 -0
  158. package/src/components/combobox-core.tsx +7 -1
  159. package/src/components/data-table-column-settings.tsx +23 -2
  160. package/src/components/data-table-fit.ts +187 -0
  161. package/src/components/data-table-groups.tsx +275 -0
  162. package/src/components/data-table-head.tsx +4 -1
  163. package/src/components/data-table-labels.ts +13 -0
  164. package/src/components/data-table-leading-row.tsx +55 -0
  165. package/src/components/data-table-resize.ts +2 -1
  166. package/src/components/data-table.tsx +274 -45
  167. package/src/components/disclosure.tsx +22 -5
  168. package/src/components/dropdown.tsx +11 -1
  169. package/src/components/floating-panel.tsx +24 -1
  170. package/src/components/hover-menu.tsx +6 -1
  171. package/src/components/icon-button.tsx +3 -3
  172. package/src/components/popover.tsx +7 -1
  173. package/src/components/row-actions.tsx +42 -3
  174. package/src/components/table-skeleton.tsx +133 -0
  175. package/src/components/tabs.tsx +102 -50
  176. package/src/components/ui-labels.ts +1 -1
  177. package/src/components/use-table-state.ts +54 -3
  178. package/src/data-table.ts +2 -0
  179. package/src/feedback/feedback-attachment-download.tsx +77 -0
  180. package/src/feedback/feedback-comment-thread.tsx +42 -7
  181. package/src/feedback/feedback-entry-file.tsx +150 -0
  182. package/src/feedback/feedback-public-list.tsx +25 -4
  183. package/src/feedback/feedback-public-types.ts +7 -2
  184. package/src/feedback/feedback-replies.tsx +46 -19
  185. package/src/feedback/feedback-row-detail-labels.ts +0 -1
  186. package/src/feedback/feedback-row-detail.tsx +35 -86
  187. package/src/feedback/feedback-table.tsx +33 -1
  188. package/src/feedback.ts +1 -0
  189. package/src/hooks/use-dismiss.ts +65 -3
  190. package/src/i18n/german.ts +6 -6
  191. package/src/i18n/kit-labels.tsx +4 -0
  192. package/src/i18n/locales/es.ts +6 -6
  193. package/src/i18n/locales/fr.ts +6 -6
  194. package/src/i18n/locales/hu.ts +6 -6
  195. package/src/i18n/locales/it.ts +6 -6
  196. package/src/i18n/locales/zh.ts +6 -6
  197. package/src/i18n/review.ts +0 -1
  198. package/src/i18n/shared-labels.ts +2 -0
  199. package/src/index.ts +13 -3
  200. package/src/lib/float-clearance.ts +54 -0
  201. package/src/notifications/device-zone-report.ts +1 -1
  202. package/src/notifications/notification-types.ts +0 -3
  203. package/src/notifications/notifications-labels.ts +2 -19
  204. package/src/notifications/quiet-hours-setting.tsx +16 -11
  205. package/src/settings/settings-catalogue.ts +10 -0
  206. package/src/settings/settings-layout.tsx +18 -4
  207. package/src/shell/app-shell.tsx +17 -3
  208. package/src/shell/option-switcher-menu.tsx +16 -4
  209. package/src/shell/topbar-action-menu.tsx +22 -10
  210. package/src/shell/topbar-controls.tsx +18 -12
  211. package/src/theme/theme-store.ts +13 -5
  212. package/src/tour/tour.tsx +5 -0
  213. package/dist/notifications/time-zone-select.d.ts +0 -54
  214. package/dist/notifications/time-zone-select.js +0 -71
  215. package/dist/notifications/time-zone-select.js.map +0 -1
  216. package/src/notifications/time-zone-select.tsx +0 -125
@@ -18,7 +18,7 @@ import { useAnchoredRect } from "../hooks/use-anchored-rect";
18
18
  import { anchoredInline } from "../hooks/use-anchored-panel";
19
19
  import { useDismissLayer } from "../hooks/use-dismiss";
20
20
  import { useBreakpoint } from "../hooks/use-breakpoint";
21
- import { useTextSize } from "../theme/text-size";
21
+ import { TEXT_SIZES, useTextSize } from "../theme/text-size";
22
22
  import type { TextSize } from "../theme/text-size";
23
23
  import { DEFAULT_APP_SHELL_LABELS, useKitLabels, useKitLink, type LabelOverride } from "../i18n/kit-labels";
24
24
  import type { KitLinkComponent, KitLinkProps } from "../i18n/kit-labels";
@@ -45,6 +45,11 @@ export interface AppShellNavItem {
45
45
  * shortening the real label, which the sidebar has ample room for and which is the
46
46
  * name the rest of the product uses. */
47
47
  shortLabel?: string;
48
+ /** 0.48: the text size from which the bar uses {@link shortLabel} — `"large"` keeps the
49
+ * full label at Normal, where five cells hold "Transactions" whole, and shortens it
50
+ * from Large up (keksdose dev#613, which read `useTextSize()` in its layout to decide).
51
+ * Default `"normal"`: the short label at every size, as before. */
52
+ shortLabelFrom?: TextSize;
48
53
  /** 0.31.0: leave this entry out of the MOBILE BOTTOM BAR; the sidebar keeps it.
49
54
  * For an entry a phone reaches another way — the brand link to the start page —
50
55
  * when one cell fewer is what keeps the others' labels whole (the bar divides its
@@ -544,9 +549,13 @@ export function AppShell({
544
549
  one that scrolls into empty space. Guarding it per component (see
545
550
  sr-only-containment.test) could never be complete: any page's own
546
551
  `sr-only` span re-opened it. Here it is closed for every child at once. */}
552
+ {/* Below `md` the bottom room is the nav plus the larger of 1.5rem and what the
553
+ floating launchers take above it (`--app-float-clearance`, published by
554
+ every kit FAB, FloatingPanel and FloatingActionGroup while mounted — keksdose
555
+ dev#612: the last row ended under the help launcher). */}
547
556
  <main
548
557
  className={cn(
549
- "relative flex-1 max-w-full overflow-x-clip pb-[calc(var(--app-nav-h,4rem)+1.5rem)] md:pb-0 md:min-h-0 md:overflow-y-auto md:[scrollbar-gutter:stable_both-edges]",
558
+ "relative flex-1 max-w-full overflow-x-clip pb-[calc(var(--app-nav-h,4rem)+max(1.5rem,var(--app-float-clearance,0px)))] md:pb-0 md:min-h-0 md:overflow-y-auto md:[scrollbar-gutter:stable_both-edges]",
550
559
  // The list page's fill (0.40, docs/page-layout-harmonization.md §1.3): while
551
560
  // a `fillHeight` DataTable is inside, a flex column — a banner above the
552
561
  // page keeps its own height and the page (a `FillHeight`) takes the rest,
@@ -764,6 +773,11 @@ function MobileCellLabel({ children }: { children: ReactNode }) {
764
773
  * nothing marked on every sub-page (the same defect the sidebar had). */
765
774
  function MobileNavItem({ item }: { item: AppShellNavItem }) {
766
775
  const subActive = useSubItemActive(item);
776
+ const { size } = useTextSize();
777
+ const short =
778
+ item.shortLabel !== undefined && TEXT_SIZES.indexOf(size) >= TEXT_SIZES.indexOf(item.shortLabelFrom ?? "normal")
779
+ ? item.shortLabel
780
+ : undefined;
767
781
  return (
768
782
  <NavLink
769
783
  to={item.to}
@@ -774,7 +788,7 @@ function MobileNavItem({ item }: { item: AppShellNavItem }) {
774
788
  <span className="relative">
775
789
  <item.icon className="size-5" />
776
790
  </span>
777
- <MobileCellLabel>{item.shortLabel ?? item.label}</MobileCellLabel>
791
+ <MobileCellLabel>{short ?? item.label}</MobileCellLabel>
778
792
  </NavLink>
779
793
  );
780
794
  }
@@ -28,6 +28,8 @@ export function OptionSwitcherMenu<T extends string>({
28
28
  /** Names the trigger and the menu. Required: the trigger is an icon (refactor audit
29
29
  * 2026-10, API-1 — the `ariaLabel` spelling went in 0.35). */
30
30
  "aria-label": string;
31
+ /** The trigger's tooltip. Default: `aria-label` (0.48) — the name, shown on hover and
32
+ * focus. A different text also describes the trigger (`aria-describedby`). */
31
33
  title?: string;
32
34
  heading?: string;
33
35
  options: OptionSwitcherOption<T>[];
@@ -37,13 +39,23 @@ export function OptionSwitcherMenu<T extends string>({
37
39
  return (
38
40
  <HoverMenu
39
41
  aria-label={ariaLabel}
40
- trigger={({ toggle }) => (
41
- <Tooltip label={title} portal>
42
+ trigger={({ toggle }) => {
43
+ const button = (
42
44
  <button type="button" onClick={toggle} aria-label={ariaLabel} className={TOPBAR_TRIGGER_CLASS}>
43
45
  {icon}
44
46
  </button>
45
- </Tooltip>
46
- )}
47
+ );
48
+ // The name on hover and focus even without a `title` (0.48, keksdose dev#610). A
49
+ // bubble that repeats the name sits round the button in a fragment, as IconButton's
50
+ // does, so it does not describe the button by its own name — a reader heard "Text
51
+ // size, button, Text size". A `title` that says more still describes it.
52
+ const tip = title ?? ariaLabel;
53
+ return (
54
+ <Tooltip label={tip} portal>
55
+ {tip === ariaLabel ? <>{button}</> : button}
56
+ </Tooltip>
57
+ );
58
+ }}
47
59
  >
48
60
  {(close) => (
49
61
  <ul className="py-1">
@@ -6,6 +6,7 @@ import { HoverMenu, type HoverMenuProps } from "../components/hover-menu";
6
6
  import { MenuItem } from "../components/menu-item";
7
7
  import type { MenuItemTone } from "../components/menu-item";
8
8
  import { StatusDot } from "../components/status-dot";
9
+ import { Tooltip } from "../components/tooltip";
9
10
  import type { UserAvatarBadge } from "../components/user-avatar";
10
11
  import { TOPBAR_TRIGGER_CLASS } from "./topbar-controls";
11
12
 
@@ -296,16 +297,27 @@ export function TopBarActionMenu({
296
297
  trigger={({ open, toggle, close }) => {
297
298
  // One element for both kinds: the router's link when `href` is set, else the
298
299
  // kit's button. Same classes and face either way.
299
- const control = (className: string, children: ReactNode, label?: string) =>
300
- href ? (
301
- <Link {...triggerAttributes} to={href} onClick={close} aria-label={label} className={className}>
302
- {children}
303
- </Link>
304
- ) : (
305
- <button {...triggerAttributes} type="button" onClick={toggle} aria-label={label} className={className}>
306
- {children}
307
- </button>
308
- );
300
+ //
301
+ // Its name in the kit's Tooltip on hover and focus (0.48, keksdose dev#610): an
302
+ // icon alone told a sighted user nothing the screen reader was told. In a
303
+ // fragment, as IconButton does it, so the bubble is not the trigger's
304
+ // `aria-describedby` — the name it repeats is the trigger's own, and a reader
305
+ // would hear it twice. Portalled, like the top bar's other triggers.
306
+ const control = (className: string, children: ReactNode, label?: string) => (
307
+ <Tooltip label={ariaLabel} portal>
308
+ <>
309
+ {href ? (
310
+ <Link {...triggerAttributes} to={href} onClick={close} aria-label={label} className={className}>
311
+ {children}
312
+ </Link>
313
+ ) : (
314
+ <button {...triggerAttributes} type="button" onClick={toggle} aria-label={label} className={className}>
315
+ {children}
316
+ </button>
317
+ )}
318
+ </>
319
+ </Tooltip>
320
+ );
309
321
  if (trigger) {
310
322
  return control(
311
323
  cn(TOPBAR_TRIGGER_CLASS, "relative", triggerClassName),
@@ -58,19 +58,25 @@ export function ThemeToggle({
58
58
  // icon button with no name — a reader heard "button" and nothing else.
59
59
  const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { theme: ariaLabel });
60
60
  const Icon = mode === "dark" ? Moon : Sun;
61
+ const button = (
62
+ <button
63
+ type="button"
64
+ onClick={() => {
65
+ onChange?.(mode === "dark" ? "light" : "dark");
66
+ onToggle?.();
67
+ }}
68
+ aria-label={labels.theme}
69
+ className={cn(TOPBAR_TRIGGER_CLASS, className)}
70
+ >
71
+ <Icon className="size-5" />
72
+ </button>
73
+ );
74
+ // The name on hover and focus even without a `title` (0.48, keksdose dev#610); a bubble
75
+ // that only repeats it is visual, in a fragment as on IconButton — see OptionSwitcherMenu.
76
+ const tip = title ?? labels.theme;
61
77
  return (
62
- <Tooltip label={title} portal>
63
- <button
64
- type="button"
65
- onClick={() => {
66
- onChange?.(mode === "dark" ? "light" : "dark");
67
- onToggle?.();
68
- }}
69
- aria-label={labels.theme}
70
- className={cn(TOPBAR_TRIGGER_CLASS, className)}
71
- >
72
- <Icon className="size-5" />
73
- </button>
78
+ <Tooltip label={tip} portal>
79
+ {tip === labels.theme ? <>{button}</> : button}
74
80
  </Tooltip>
75
81
  );
76
82
  }
@@ -47,20 +47,28 @@ function preferenceFromState(
47
47
  return "system";
48
48
  }
49
49
 
50
- /** Toggle the `.dark` class on <html> for a resolved light/dark mode. */
50
+ /**
51
+ * Toggle the `.dark` class on <html> for a resolved light/dark mode, and say the same
52
+ * to the BROWSER through `color-scheme` (0.47, keksdose dev #600): the class switches
53
+ * the kit's tokens, but everything the browser paints itself — a native `<select>`'s
54
+ * open list, a date input's popup, scrollbars, autofill — follows `color-scheme` only,
55
+ * and stayed light on the dark theme. Inline on <html>, so it holds whatever an app's
56
+ * stylesheet says, and it is set by the pre-paint {@link applyPersistedTheme} too.
57
+ */
51
58
  function applyThemeClass(mode: ThemeMode): void {
52
59
  const root = document.documentElement;
53
60
  if (mode === "dark") root.classList.add("dark");
54
61
  else root.classList.remove("dark");
62
+ root.style.colorScheme = mode;
55
63
  }
56
64
 
57
65
  /**
58
66
  * Pre-hydration: read the theme PREFERENCE persisted under `storageKey` straight
59
67
  * from localStorage, resolve it to a concrete light/dark mode, and toggle the
60
- * `.dark` class on <html> — BEFORE React and the store mount, so the first paint
61
- * already has the right theme (no flash). Returns the resolved mode so the caller
62
- * can chain {@link applyPersistedPalette}. Encapsulates the zustand-persist
63
- * envelope + the legacy `mode`→`preference` fallback so apps never re-type the
68
+ * `.dark` class (and `color-scheme`) on <html> — BEFORE React and the store mount,
69
+ * so the first paint already has the right theme (no flash). Returns the resolved
70
+ * mode so the caller can chain {@link applyPersistedPalette}. Encapsulates the
71
+ * zustand-persist envelope + the legacy `mode`→`preference` fallback so apps never re-type the
64
72
  * storage shape in their entrypoint.
65
73
  */
66
74
  export function applyPersistedTheme(storageKey: string): ThemeMode {
package/src/tour/tour.tsx CHANGED
@@ -615,6 +615,11 @@ function TourOverlay({
615
615
  className={cn("pointer-events-none fixed inset-0", interactive ? "z-[45]" : "z-[60]")}
616
616
  role="dialog"
617
617
  aria-modal={interactive ? undefined : "true"}
618
+ // A stable marker for the overlay, on every step (0.48, keksdose dev#611–613): an
619
+ // app that hides its floating help launcher while a modal is open excepts the
620
+ // tour, which may spotlight that launcher — `[aria-modal="true"]:not([data-tour-overlay])`.
621
+ // keksdose matched the `pointer-events-none` class until now, which is the kit's to change.
622
+ data-tour-overlay=""
618
623
  data-tour-interactive={interactive ? "" : undefined}
619
624
  aria-label={step.title}
620
625
  dir={dir}
@@ -1,54 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import type { LabelOverride } from "../i18n/kit-labels.js";
3
- import type { ComboOption } from "../components/combobox-core.js";
4
- import type { CommitScope } from "../components/write-lock.js";
5
- import type { NotificationRegionLabels, NotificationsLabels } from "./notifications-labels.js";
6
- /**
7
- * This device's IANA zone — the same function as the package root's, which lives beside
8
- * `useDeviceZoneReport` now (0.46.1).
9
- * @deprecated 0.46 — import `deviceTimeZone` from `@eifi1/ui-kit` (notifications/device-zone-report); this
10
- * module is removed in 0.47.
11
- */
12
- export declare const deviceTimeZone: () => string;
13
- /**
14
- * The zone picker's rows (keksdose `notifications-card.tsx:131-146`): every zone `Intl`
15
- * lists, as its city ("Argentina / Buenos Aires") under its region, the region named by
16
- * `regionLabel` — which answers `undefined` for a region it does not know, and the region
17
- * keeps its IANA name. A zone with no region ("UTC") comes first, ahead of every heading,
18
- * rather than trailing the last group as if it were Pacific. `current` is kept even when
19
- * the runtime does not list it (an old alias such as "Europe/Kiev"): a picker without its
20
- * own value would show an empty field.
21
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
22
- */
23
- export declare function timeZoneOptions(current: string, regionLabel: (region: string) => string | undefined): ComboOption<string>[];
24
- /** The region's name in `regions`, or `undefined` for one the labels do not know.
25
- * @internal Not part of the barrel. */
26
- export declare function regionName(regions: NotificationRegionLabels, region: string): string | undefined;
27
- /** A zone as the picker shows it, for a sentence: "Europe / Zurich", the region in the
28
- * kit's words. @internal Not part of the barrel. */
29
- export declare function zoneName(zone: string, regions: NotificationRegionLabels): string;
30
- /** @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47. */
31
- export interface TimeZoneSelectProps {
32
- /** The IANA zone. Not clearable: a zone can be changed, never emptied. */
33
- value: string;
34
- /** The zone picked. Never called for the value it already has. */
35
- onChange: (zone: string) => void;
36
- /** Default `labels.timeZone`. */
37
- label?: ReactNode;
38
- disabled?: boolean;
39
- /** The input's id. */
40
- id?: string;
41
- className?: string;
42
- /** The picker SAVES on change (quiet hours do): locked under a write lock in scope. */
43
- commit?: CommitScope;
44
- labels?: LabelOverride<NotificationsLabels>;
45
- }
46
- /**
47
- * An IANA time zone, searchable (docs/notifications-harmonization.md §4.2, keksdose's
48
- * quiet-hours picker): type "Zur" for Europe/Zurich, and on a phone the kit's sheet with
49
- * a search box instead of a 400-row wheel. Rows are the city under its region, the region
50
- * in the kit's words (`labels.regions`); a zone with no region first; a stored zone the
51
- * runtime does not list is kept. On the kit's {@link InlineEntityCombobox}.
52
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
53
- */
54
- export declare function TimeZoneSelect({ value, onChange, label, disabled, id, className, commit, labels: labelsProp, }: TimeZoneSelectProps): import("react").JSX.Element;
@@ -1,71 +0,0 @@
1
- "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { useMemo } from "react";
4
- import { InlineEntityCombobox } from "../components/inline-entity-combobox.js";
5
- import { deviceTimeZone as zoneOfDevice } from "./device-zone-report.js";
6
- import { useNotificationsLabels } from "./notification-parts.js";
7
- const deviceTimeZone = zoneOfDevice;
8
- function runtimeZones() {
9
- const intl = Intl;
10
- return typeof intl.supportedValuesOf === "function" ? intl.supportedValuesOf("timeZone") : [];
11
- }
12
- function cityOf(rest) {
13
- return rest.replace(/_/g, " ").replace(/\//g, " / ");
14
- }
15
- function timeZoneOptions(current, regionLabel) {
16
- const zones = runtimeZones();
17
- const all = !current || zones.includes(current) ? zones : [...zones, current].sort();
18
- const rows = all.map((zone) => {
19
- const slash = zone.indexOf("/");
20
- if (slash < 0) return { value: zone, label: zone };
21
- const region = zone.slice(0, slash);
22
- return { value: zone, label: cityOf(zone.slice(slash + 1)), group: regionLabel(region) ?? region };
23
- });
24
- return [...rows.filter((row) => !row.group), ...rows.filter((row) => row.group)];
25
- }
26
- function regionName(regions, region) {
27
- return Object.prototype.hasOwnProperty.call(regions, region) ? regions[region] : void 0;
28
- }
29
- function zoneName(zone, regions) {
30
- const slash = zone.indexOf("/");
31
- if (slash < 0) return zone;
32
- const region = zone.slice(0, slash);
33
- return `${regionName(regions, region) ?? region} / ${cityOf(zone.slice(slash + 1))}`;
34
- }
35
- function TimeZoneSelect({
36
- value,
37
- onChange,
38
- label,
39
- disabled,
40
- id,
41
- className,
42
- commit,
43
- labels: labelsProp
44
- }) {
45
- const labels = useNotificationsLabels(labelsProp);
46
- const { regions } = labels;
47
- const options = useMemo(() => timeZoneOptions(value, (region) => regionName(regions, region)), [value, regions]);
48
- return /* @__PURE__ */ jsx(
49
- InlineEntityCombobox,
50
- {
51
- id,
52
- label: label ?? labels.timeZone,
53
- value,
54
- options,
55
- disabled,
56
- commit,
57
- onChange: (zone) => {
58
- if (zone && zone !== value) onChange(zone);
59
- },
60
- className
61
- }
62
- );
63
- }
64
- export {
65
- TimeZoneSelect,
66
- deviceTimeZone,
67
- regionName,
68
- timeZoneOptions,
69
- zoneName
70
- };
71
- //# sourceMappingURL=time-zone-select.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/notifications/time-zone-select.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport type { ComboOption } from \"../components/combobox-core\";\nimport { InlineEntityCombobox } from \"../components/inline-entity-combobox\";\nimport type { CommitScope } from \"../components/write-lock\";\nimport { deviceTimeZone as zoneOfDevice } from \"./device-zone-report\";\nimport { useNotificationsLabels } from \"./notification-parts\";\nimport type { NotificationRegionLabels, NotificationsLabels } from \"./notifications-labels\";\n\n/**\n * This device's IANA zone — the same function as the package root's, which lives beside\n * `useDeviceZoneReport` now (0.46.1).\n * @deprecated 0.46 — import `deviceTimeZone` from `@eifi1/ui-kit` (notifications/device-zone-report); this\n * module is removed in 0.47.\n */\nexport const deviceTimeZone: () => string = zoneOfDevice;\n\n/** Every zone the runtime lists; `[]` where `Intl.supportedValuesOf` is missing. */\nfunction runtimeZones(): string[] {\n const intl = Intl as typeof Intl & { supportedValuesOf?: (key: \"timeZone\") => string[] };\n return typeof intl.supportedValuesOf === \"function\" ? intl.supportedValuesOf(\"timeZone\") : [];\n}\n\n/** \"Argentina/Buenos_Aires\" → \"Argentina / Buenos Aires\": the zone's city, as a row. */\nfunction cityOf(rest: string): string {\n return rest.replace(/_/g, \" \").replace(/\\//g, \" / \");\n}\n\n/**\n * The zone picker's rows (keksdose `notifications-card.tsx:131-146`): every zone `Intl`\n * lists, as its city (\"Argentina / Buenos Aires\") under its region, the region named by\n * `regionLabel` — which answers `undefined` for a region it does not know, and the region\n * keeps its IANA name. A zone with no region (\"UTC\") comes first, ahead of every heading,\n * rather than trailing the last group as if it were Pacific. `current` is kept even when\n * the runtime does not list it (an old alias such as \"Europe/Kiev\"): a picker without its\n * own value would show an empty field.\n * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.\n */\nexport function timeZoneOptions(\n current: string,\n regionLabel: (region: string) => string | undefined,\n): ComboOption<string>[] {\n const zones = runtimeZones();\n const all = !current || zones.includes(current) ? zones : [...zones, current].sort();\n const rows = all.map((zone): ComboOption<string> => {\n const slash = zone.indexOf(\"/\");\n if (slash < 0) return { value: zone, label: zone };\n const region = zone.slice(0, slash);\n return { value: zone, label: cityOf(zone.slice(slash + 1)), group: regionLabel(region) ?? region };\n });\n return [...rows.filter((row) => !row.group), ...rows.filter((row) => row.group)];\n}\n\n/** The region's name in `regions`, or `undefined` for one the labels do not know.\n * @internal Not part of the barrel. */\nexport function regionName(regions: NotificationRegionLabels, region: string): string | undefined {\n return Object.prototype.hasOwnProperty.call(regions, region)\n ? regions[region as keyof NotificationRegionLabels]\n : undefined;\n}\n\n/** A zone as the picker shows it, for a sentence: \"Europe / Zurich\", the region in the\n * kit's words. @internal Not part of the barrel. */\nexport function zoneName(zone: string, regions: NotificationRegionLabels): string {\n const slash = zone.indexOf(\"/\");\n if (slash < 0) return zone;\n const region = zone.slice(0, slash);\n return `${regionName(regions, region) ?? region} / ${cityOf(zone.slice(slash + 1))}`;\n}\n\n/** @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47. */\nexport interface TimeZoneSelectProps {\n /** The IANA zone. Not clearable: a zone can be changed, never emptied. */\n value: string;\n /** The zone picked. Never called for the value it already has. */\n onChange: (zone: string) => void;\n /** Default `labels.timeZone`. */\n label?: ReactNode;\n disabled?: boolean;\n /** The input's id. */\n id?: string;\n className?: string;\n /** The picker SAVES on change (quiet hours do): locked under a write lock in scope. */\n commit?: CommitScope;\n labels?: LabelOverride<NotificationsLabels>;\n}\n\n/**\n * An IANA time zone, searchable (docs/notifications-harmonization.md §4.2, keksdose's\n * quiet-hours picker): type \"Zur\" for Europe/Zurich, and on a phone the kit's sheet with\n * a search box instead of a 400-row wheel. Rows are the city under its region, the region\n * in the kit's words (`labels.regions`); a zone with no region first; a stored zone the\n * runtime does not list is kept. On the kit's {@link InlineEntityCombobox}.\n * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.\n */\nexport function TimeZoneSelect({\n value,\n onChange,\n label,\n disabled,\n id,\n className,\n commit,\n labels: labelsProp,\n}: TimeZoneSelectProps) {\n const labels = useNotificationsLabels(labelsProp);\n const { regions } = labels;\n const options = useMemo(() => timeZoneOptions(value, (region) => regionName(regions, region)), [value, regions]);\n return (\n <InlineEntityCombobox<string>\n id={id}\n label={label ?? labels.timeZone}\n value={value}\n options={options}\n disabled={disabled}\n commit={commit}\n onChange={(zone) => {\n if (zone && zone !== value) onChange(zone);\n }}\n className={className}\n />\n );\n}\n"],"mappings":";AA+GI;AA/GJ,SAAS,eAAe;AAKxB,SAAS,4BAA4B;AAErC,SAAS,kBAAkB,oBAAoB;AAC/C,SAAS,8BAA8B;AAShC,MAAM,iBAA+B;AAG5C,SAAS,eAAyB;AAChC,QAAM,OAAO;AACb,SAAO,OAAO,KAAK,sBAAsB,aAAa,KAAK,kBAAkB,UAAU,IAAI,CAAC;AAC9F;AAGA,SAAS,OAAO,MAAsB;AACpC,SAAO,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,OAAO,KAAK;AACrD;AAYO,SAAS,gBACd,SACA,aACuB;AACvB,QAAM,QAAQ,aAAa;AAC3B,QAAM,MAAM,CAAC,WAAW,MAAM,SAAS,OAAO,IAAI,QAAQ,CAAC,GAAG,OAAO,OAAO,EAAE,KAAK;AACnF,QAAM,OAAO,IAAI,IAAI,CAAC,SAA8B;AAClD,UAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,QAAI,QAAQ,EAAG,QAAO,EAAE,OAAO,MAAM,OAAO,KAAK;AACjD,UAAM,SAAS,KAAK,MAAM,GAAG,KAAK;AAClC,WAAO,EAAE,OAAO,MAAM,OAAO,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,GAAG,OAAO,YAAY,MAAM,KAAK,OAAO;AAAA,EACnG,CAAC;AACD,SAAO,CAAC,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,GAAG,GAAG,KAAK,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;AACjF;AAIO,SAAS,WAAW,SAAmC,QAAoC;AAChG,SAAO,OAAO,UAAU,eAAe,KAAK,SAAS,MAAM,IACvD,QAAQ,MAAwC,IAChD;AACN;AAIO,SAAS,SAAS,MAAc,SAA2C;AAChF,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,MAAI,QAAQ,EAAG,QAAO;AACtB,QAAM,SAAS,KAAK,MAAM,GAAG,KAAK;AAClC,SAAO,GAAG,WAAW,SAAS,MAAM,KAAK,MAAM,MAAM,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,CAAC;AACpF;AA2BO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAAwB;AACtB,QAAM,SAAS,uBAAuB,UAAU;AAChD,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,UAAU,QAAQ,MAAM,gBAAgB,OAAO,CAAC,WAAW,WAAW,SAAS,MAAM,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC;AAC/G,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAO,SAAS,OAAO;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,CAAC,SAAS;AAClB,YAAI,QAAQ,SAAS,MAAO,UAAS,IAAI;AAAA,MAC3C;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -1,125 +0,0 @@
1
- import { useMemo } from "react";
2
- import type { ReactNode } from "react";
3
-
4
- import type { LabelOverride } from "../i18n/kit-labels";
5
- import type { ComboOption } from "../components/combobox-core";
6
- import { InlineEntityCombobox } from "../components/inline-entity-combobox";
7
- import type { CommitScope } from "../components/write-lock";
8
- import { deviceTimeZone as zoneOfDevice } from "./device-zone-report";
9
- import { useNotificationsLabels } from "./notification-parts";
10
- import type { NotificationRegionLabels, NotificationsLabels } from "./notifications-labels";
11
-
12
- /**
13
- * This device's IANA zone — the same function as the package root's, which lives beside
14
- * `useDeviceZoneReport` now (0.46.1).
15
- * @deprecated 0.46 — import `deviceTimeZone` from `@eifi1/ui-kit` (notifications/device-zone-report); this
16
- * module is removed in 0.47.
17
- */
18
- export const deviceTimeZone: () => string = zoneOfDevice;
19
-
20
- /** Every zone the runtime lists; `[]` where `Intl.supportedValuesOf` is missing. */
21
- function runtimeZones(): string[] {
22
- const intl = Intl as typeof Intl & { supportedValuesOf?: (key: "timeZone") => string[] };
23
- return typeof intl.supportedValuesOf === "function" ? intl.supportedValuesOf("timeZone") : [];
24
- }
25
-
26
- /** "Argentina/Buenos_Aires" → "Argentina / Buenos Aires": the zone's city, as a row. */
27
- function cityOf(rest: string): string {
28
- return rest.replace(/_/g, " ").replace(/\//g, " / ");
29
- }
30
-
31
- /**
32
- * The zone picker's rows (keksdose `notifications-card.tsx:131-146`): every zone `Intl`
33
- * lists, as its city ("Argentina / Buenos Aires") under its region, the region named by
34
- * `regionLabel` — which answers `undefined` for a region it does not know, and the region
35
- * keeps its IANA name. A zone with no region ("UTC") comes first, ahead of every heading,
36
- * rather than trailing the last group as if it were Pacific. `current` is kept even when
37
- * the runtime does not list it (an old alias such as "Europe/Kiev"): a picker without its
38
- * own value would show an empty field.
39
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
40
- */
41
- export function timeZoneOptions(
42
- current: string,
43
- regionLabel: (region: string) => string | undefined,
44
- ): ComboOption<string>[] {
45
- const zones = runtimeZones();
46
- const all = !current || zones.includes(current) ? zones : [...zones, current].sort();
47
- const rows = all.map((zone): ComboOption<string> => {
48
- const slash = zone.indexOf("/");
49
- if (slash < 0) return { value: zone, label: zone };
50
- const region = zone.slice(0, slash);
51
- return { value: zone, label: cityOf(zone.slice(slash + 1)), group: regionLabel(region) ?? region };
52
- });
53
- return [...rows.filter((row) => !row.group), ...rows.filter((row) => row.group)];
54
- }
55
-
56
- /** The region's name in `regions`, or `undefined` for one the labels do not know.
57
- * @internal Not part of the barrel. */
58
- export function regionName(regions: NotificationRegionLabels, region: string): string | undefined {
59
- return Object.prototype.hasOwnProperty.call(regions, region)
60
- ? regions[region as keyof NotificationRegionLabels]
61
- : undefined;
62
- }
63
-
64
- /** A zone as the picker shows it, for a sentence: "Europe / Zurich", the region in the
65
- * kit's words. @internal Not part of the barrel. */
66
- export function zoneName(zone: string, regions: NotificationRegionLabels): string {
67
- const slash = zone.indexOf("/");
68
- if (slash < 0) return zone;
69
- const region = zone.slice(0, slash);
70
- return `${regionName(regions, region) ?? region} / ${cityOf(zone.slice(slash + 1))}`;
71
- }
72
-
73
- /** @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47. */
74
- export interface TimeZoneSelectProps {
75
- /** The IANA zone. Not clearable: a zone can be changed, never emptied. */
76
- value: string;
77
- /** The zone picked. Never called for the value it already has. */
78
- onChange: (zone: string) => void;
79
- /** Default `labels.timeZone`. */
80
- label?: ReactNode;
81
- disabled?: boolean;
82
- /** The input's id. */
83
- id?: string;
84
- className?: string;
85
- /** The picker SAVES on change (quiet hours do): locked under a write lock in scope. */
86
- commit?: CommitScope;
87
- labels?: LabelOverride<NotificationsLabels>;
88
- }
89
-
90
- /**
91
- * An IANA time zone, searchable (docs/notifications-harmonization.md §4.2, keksdose's
92
- * quiet-hours picker): type "Zur" for Europe/Zurich, and on a phone the kit's sheet with
93
- * a search box instead of a 400-row wheel. Rows are the city under its region, the region
94
- * in the kit's words (`labels.regions`); a zone with no region first; a stored zone the
95
- * runtime does not list is kept. On the kit's {@link InlineEntityCombobox}.
96
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
97
- */
98
- export function TimeZoneSelect({
99
- value,
100
- onChange,
101
- label,
102
- disabled,
103
- id,
104
- className,
105
- commit,
106
- labels: labelsProp,
107
- }: TimeZoneSelectProps) {
108
- const labels = useNotificationsLabels(labelsProp);
109
- const { regions } = labels;
110
- const options = useMemo(() => timeZoneOptions(value, (region) => regionName(regions, region)), [value, regions]);
111
- return (
112
- <InlineEntityCombobox<string>
113
- id={id}
114
- label={label ?? labels.timeZone}
115
- value={value}
116
- options={options}
117
- disabled={disabled}
118
- commit={commit}
119
- onChange={(zone) => {
120
- if (zone && zone !== value) onChange(zone);
121
- }}
122
- className={className}
123
- />
124
- );
125
- }