@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.
- package/README.md +8 -6
- package/dist/admin/admin-sections.d.ts +8 -0
- package/dist/admin/admin-sections.js +2 -1
- package/dist/admin/admin-sections.js.map +1 -1
- package/dist/auth/verify-email.d.ts +9 -1
- package/dist/auth/verify-email.js +22 -13
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/components/alert-banner.d.ts +29 -1
- package/dist/components/alert-banner.js +84 -7
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/button-look.d.ts +4 -0
- package/dist/components/button-look.js +2 -0
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/combobox-core.js +5 -1
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/data-table-column-settings.d.ts +6 -1
- package/dist/components/data-table-column-settings.js +12 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-fit.d.ts +44 -0
- package/dist/components/data-table-fit.js +103 -0
- package/dist/components/data-table-fit.js.map +1 -0
- package/dist/components/data-table-groups.d.ts +95 -0
- package/dist/components/data-table-groups.js +165 -0
- package/dist/components/data-table-groups.js.map +1 -0
- package/dist/components/data-table-head.js +3 -1
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +7 -0
- package/dist/components/data-table-labels.js +3 -0
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-leading-row.d.ts +17 -0
- package/dist/components/data-table-leading-row.js +41 -0
- package/dist/components/data-table-leading-row.js.map +1 -0
- package/dist/components/data-table-resize.js +1 -1
- package/dist/components/data-table-resize.js.map +1 -1
- package/dist/components/data-table.d.ts +78 -2
- package/dist/components/data-table.js +355 -247
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/disclosure.d.ts +3 -1
- package/dist/components/disclosure.js +10 -4
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/dropdown.js +8 -1
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/floating-panel.d.ts +14 -1
- package/dist/components/floating-panel.js +7 -2
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/hover-menu.js +5 -1
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.js +2 -1
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/popover.js +5 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.d.ts +25 -0
- package/dist/components/row-actions.js +13 -3
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/table-skeleton.d.ts +35 -0
- package/dist/components/table-skeleton.js +75 -0
- package/dist/components/table-skeleton.js.map +1 -0
- package/dist/components/tabs.d.ts +22 -16
- package/dist/components/tabs.js +47 -31
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/ui-labels.js +1 -1
- package/dist/components/ui-labels.js.map +1 -1
- package/dist/components/use-table-state.d.ts +8 -7
- package/dist/components/use-table-state.js +37 -3
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.d.ts +1 -0
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-attachment-download.d.ts +19 -0
- package/dist/feedback/feedback-attachment-download.js +59 -0
- package/dist/feedback/feedback-attachment-download.js.map +1 -0
- package/dist/feedback/feedback-comment-thread.d.ts +17 -6
- package/dist/feedback/feedback-comment-thread.js +21 -2
- package/dist/feedback/feedback-comment-thread.js.map +1 -1
- package/dist/feedback/feedback-entry-file.d.ts +61 -0
- package/dist/feedback/feedback-entry-file.js +76 -0
- package/dist/feedback/feedback-entry-file.js.map +1 -0
- package/dist/feedback/feedback-public-list.d.ts +18 -5
- package/dist/feedback/feedback-public-list.js +8 -0
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-public-types.d.ts +7 -2
- package/dist/feedback/feedback-replies.d.ts +26 -9
- package/dist/feedback/feedback-replies.js +20 -10
- package/dist/feedback/feedback-replies.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +0 -1
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +23 -10
- package/dist/feedback/feedback-row-detail.js +10 -54
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +5 -0
- package/dist/feedback/feedback-table.js +30 -7
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback.d.ts +1 -0
- package/dist/feedback.js +1 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +30 -1
- package/dist/hooks/use-dismiss.js +30 -2
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/i18n/german.js +7 -7
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +4 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +7 -7
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +7 -7
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +7 -7
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +7 -7
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +7 -7
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +7 -7
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +0 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +3 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/float-clearance.d.ts +8 -0
- package/dist/lib/float-clearance.js +29 -0
- package/dist/lib/float-clearance.js.map +1 -0
- package/dist/notifications/device-zone-report.d.ts +1 -1
- package/dist/notifications/device-zone-report.js.map +1 -1
- package/dist/notifications/notification-types.d.ts +0 -3
- package/dist/notifications/notifications-labels.d.ts +2 -15
- package/dist/notifications/notifications-labels.js +0 -4
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/quiet-hours-setting.d.ts +3 -8
- package/dist/notifications/quiet-hours-setting.js +8 -3
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/settings/settings-catalogue.d.ts +10 -0
- package/dist/settings/settings-catalogue.js.map +1 -1
- package/dist/settings/settings-layout.js +10 -3
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/shell/app-shell.d.ts +5 -0
- package/dist/shell/app-shell.js +5 -3
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.d.ts +2 -0
- package/dist/shell/option-switcher-menu.js +6 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +2 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.js +5 -3
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/theme/theme-store.d.ts +4 -4
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour.js +1 -0
- package/dist/tour/tour.js.map +1 -1
- package/package.json +2 -2
- package/src/admin/admin-sections.tsx +8 -0
- package/src/auth/verify-email.tsx +34 -9
- package/src/components/alert-banner.tsx +131 -7
- package/src/components/button-look.ts +6 -0
- package/src/components/combobox-core.tsx +7 -1
- package/src/components/data-table-column-settings.tsx +23 -2
- package/src/components/data-table-fit.ts +187 -0
- package/src/components/data-table-groups.tsx +275 -0
- package/src/components/data-table-head.tsx +4 -1
- package/src/components/data-table-labels.ts +13 -0
- package/src/components/data-table-leading-row.tsx +55 -0
- package/src/components/data-table-resize.ts +2 -1
- package/src/components/data-table.tsx +274 -45
- package/src/components/disclosure.tsx +22 -5
- package/src/components/dropdown.tsx +11 -1
- package/src/components/floating-panel.tsx +24 -1
- package/src/components/hover-menu.tsx +6 -1
- package/src/components/icon-button.tsx +3 -3
- package/src/components/popover.tsx +7 -1
- package/src/components/row-actions.tsx +42 -3
- package/src/components/table-skeleton.tsx +133 -0
- package/src/components/tabs.tsx +102 -50
- package/src/components/ui-labels.ts +1 -1
- package/src/components/use-table-state.ts +54 -3
- package/src/data-table.ts +2 -0
- package/src/feedback/feedback-attachment-download.tsx +77 -0
- package/src/feedback/feedback-comment-thread.tsx +42 -7
- package/src/feedback/feedback-entry-file.tsx +150 -0
- package/src/feedback/feedback-public-list.tsx +25 -4
- package/src/feedback/feedback-public-types.ts +7 -2
- package/src/feedback/feedback-replies.tsx +46 -19
- package/src/feedback/feedback-row-detail-labels.ts +0 -1
- package/src/feedback/feedback-row-detail.tsx +35 -86
- package/src/feedback/feedback-table.tsx +33 -1
- package/src/feedback.ts +1 -0
- package/src/hooks/use-dismiss.ts +65 -3
- package/src/i18n/german.ts +6 -6
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/es.ts +6 -6
- package/src/i18n/locales/fr.ts +6 -6
- package/src/i18n/locales/hu.ts +6 -6
- package/src/i18n/locales/it.ts +6 -6
- package/src/i18n/locales/zh.ts +6 -6
- package/src/i18n/review.ts +0 -1
- package/src/i18n/shared-labels.ts +2 -0
- package/src/index.ts +13 -3
- package/src/lib/float-clearance.ts +54 -0
- package/src/notifications/device-zone-report.ts +1 -1
- package/src/notifications/notification-types.ts +0 -3
- package/src/notifications/notifications-labels.ts +2 -19
- package/src/notifications/quiet-hours-setting.tsx +16 -11
- package/src/settings/settings-catalogue.ts +10 -0
- package/src/settings/settings-layout.tsx +18 -4
- package/src/shell/app-shell.tsx +17 -3
- package/src/shell/option-switcher-menu.tsx +16 -4
- package/src/shell/topbar-action-menu.tsx +22 -10
- package/src/shell/topbar-controls.tsx +18 -12
- package/src/theme/theme-store.ts +13 -5
- package/src/tour/tour.tsx +5 -0
- package/dist/notifications/time-zone-select.d.ts +0 -54
- package/dist/notifications/time-zone-select.js +0 -71
- package/dist/notifications/time-zone-select.js.map +0 -1
- package/src/notifications/time-zone-select.tsx +0 -125
package/src/shell/app-shell.tsx
CHANGED
|
@@ -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>{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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={
|
|
63
|
-
|
|
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
|
}
|
package/src/theme/theme-store.ts
CHANGED
|
@@ -47,20 +47,28 @@ function preferenceFromState(
|
|
|
47
47
|
return "system";
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
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,
|
|
61
|
-
* already has the right theme (no flash). Returns the resolved
|
|
62
|
-
* can chain {@link applyPersistedPalette}. Encapsulates the
|
|
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
|
-
}
|