@eifi1/ui-kit 0.11.0 → 0.12.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 +57 -32
- package/dist/chart.d.ts +3 -1
- package/dist/chart.js +2 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/account-settings-labels.d.ts +105 -0
- package/dist/components/account-settings-labels.js +81 -0
- package/dist/components/account-settings-labels.js.map +1 -0
- package/dist/components/account-settings.d.ts +36 -52
- package/dist/components/account-settings.js +44 -8
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +24 -4
- package/dist/components/amount-input.js +5 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/authed-image.d.ts +68 -0
- package/dist/components/authed-image.js +117 -0
- package/dist/components/authed-image.js.map +1 -0
- package/dist/components/breadcrumbs.d.ts +2 -1
- package/dist/components/breadcrumbs.js +5 -2
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-group.d.ts +51 -2
- package/dist/components/button-group.js +5 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +15 -4
- package/dist/components/calendar-heatmap.d.ts +41 -3
- package/dist/components/calendar-heatmap.js +48 -7
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +6 -3
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +35 -3
- package/dist/components/choice-card.js +83 -25
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/combobox-core.js +2 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +2 -2
- package/dist/components/combobox.js +31 -9
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +51 -70
- package/dist/components/copy-button.js +1 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +63 -0
- package/dist/components/data-table-cells.js +64 -0
- package/dist/components/data-table-cells.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +31 -1
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +13 -0
- package/dist/components/data-table-labels.js +8 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +102 -12
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +3 -1
- package/dist/components/date-picker.js +15 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +23 -3
- package/dist/components/description-list.js +23 -3
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +1 -0
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js +18 -3
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/error-boundary.d.ts +92 -0
- package/dist/components/error-boundary.js +111 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/components/field.d.ts +30 -4
- package/dist/components/field.js +40 -17
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +50 -208
- package/dist/components/file-dropzone.d.ts +49 -2
- package/dist/components/floating-panel.d.ts +13 -4
- package/dist/components/floating-panel.js +7 -3
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +53 -0
- package/dist/components/form-actions.js +98 -0
- package/dist/components/form-actions.js.map +1 -0
- package/dist/components/image-grid.d.ts +67 -0
- package/dist/components/image-grid.js +112 -0
- package/dist/components/image-grid.js.map +1 -0
- package/dist/components/lightbox.d.ts +104 -0
- package/dist/components/lightbox.js +229 -0
- package/dist/components/lightbox.js.map +1 -0
- package/dist/components/line-items.d.ts +114 -0
- package/dist/components/line-items.js +238 -0
- package/dist/components/line-items.js.map +1 -0
- package/dist/components/list.d.ts +23 -2
- package/dist/components/list.js +32 -13
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-state.d.ts +24 -0
- package/dist/components/loading-state.js +32 -0
- package/dist/components/loading-state.js.map +1 -0
- package/dist/components/menu-item.d.ts +2 -1
- package/dist/components/menu-item.js +5 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +13 -1
- package/dist/components/mini-calendar.js +46 -2
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +20 -1
- package/dist/components/modal.js +26 -2
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +21 -1
- package/dist/components/month-picker.js +139 -26
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +1 -1
- package/dist/components/multi-entity-combobox.js +19 -3
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/nav-pills.d.ts +2 -1
- package/dist/components/nav-pills.js +5 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-field.d.ts +24 -5
- package/dist/components/number-field.js +5 -7
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +25 -5
- package/dist/components/number-input.js +7 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +15 -4
- package/dist/components/passkeys-setting.d.ts +67 -0
- package/dist/components/passkeys-setting.js +214 -0
- package/dist/components/passkeys-setting.js.map +1 -0
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +20 -0
- package/dist/components/pie-chart-labels.js +12 -0
- package/dist/components/pie-chart-labels.js.map +1 -0
- package/dist/components/pie-chart.d.ts +101 -0
- package/dist/components/pie-chart.js +283 -0
- package/dist/components/pie-chart.js.map +1 -0
- package/dist/components/progress-bar.d.ts +44 -3
- package/dist/components/progress-bar.js +58 -22
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/qr-code.d.ts +34 -0
- package/dist/components/qr-code.js +57 -0
- package/dist/components/qr-code.js.map +1 -0
- package/dist/components/search-field.d.ts +15 -3
- package/dist/components/search-field.js +5 -1
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/settings-fields.d.ts +49 -1
- package/dist/components/signed-amount.d.ts +121 -0
- package/dist/components/signed-amount.js +152 -0
- package/dist/components/signed-amount.js.map +1 -0
- package/dist/components/skeleton.d.ts +10 -3
- package/dist/components/skeleton.js +12 -2
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/stat-tile.d.ts +2 -1
- package/dist/components/stat-tile.js +5 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/table.d.ts +39 -5
- package/dist/components/table.js +45 -11
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +176 -0
- package/dist/components/text-link.js +109 -0
- package/dist/components/text-link.js.map +1 -0
- package/dist/components/time-input.d.ts +49 -1
- package/dist/components/toggle-group.d.ts +13 -0
- package/dist/components/toggle-group.js +35 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +36 -3
- package/dist/components/toggle-legend.js +49 -0
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/ui.d.ts +51 -779
- package/dist/components/ui.js +215 -23
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +3 -52
- package/dist/components/use-table-state.js +128 -30
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
- package/dist/data-table.d.ts +2 -1
- package/dist/data-table.js +5 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +26 -3
- package/dist/feedback/feedback-dialog.js +16 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +133 -0
- package/dist/feedback/feedback-thread.js +237 -0
- package/dist/feedback/feedback-thread.js.map +1 -0
- package/dist/feedback.d.ts +2 -1
- package/dist/feedback.js +1 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-authed-src.d.ts +71 -0
- package/dist/hooks/use-authed-src.js +63 -0
- package/dist/hooks/use-authed-src.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +49 -2
- package/dist/hooks/use-hotkey.d.ts +59 -0
- package/dist/hooks/use-hotkey.js +105 -0
- package/dist/hooks/use-hotkey.js.map +1 -0
- package/dist/hooks/use-search-param-state.d.ts +83 -0
- package/dist/hooks/use-search-param-state.js +94 -0
- package/dist/hooks/use-search-param-state.js.map +1 -0
- package/dist/i18n/defaults.d.ts +15 -4
- package/dist/i18n/defaults.js +31 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +46 -312
- package/dist/i18n/kit-labels.js +20 -5
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +15 -4
- package/dist/i18n/locales/de-CH.d.ts +15 -4
- package/dist/i18n/locales/de-informal.d.ts +15 -4
- package/dist/i18n/locales/de.d.ts +15 -4
- package/dist/i18n/locales/de.js +156 -3
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +15 -4
- package/dist/i18n/locales/es.js +153 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +15 -4
- package/dist/i18n/locales/fr.js +153 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +15 -4
- package/dist/i18n/locales/hu.js +153 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +15 -4
- package/dist/i18n/locales/it.js +153 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +15 -4
- package/dist/i18n/locales/zh.js +153 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +37 -17
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
- package/dist/lib/format.d.ts +144 -0
- package/dist/lib/format.js +149 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/qr-encode.d.ts +44 -0
- package/dist/lib/qr-encode.js +338 -0
- package/dist/lib/qr-encode.js.map +1 -0
- package/dist/rhf/fields.d.ts +241 -0
- package/dist/rhf/fields.js +601 -0
- package/dist/rhf/fields.js.map +1 -0
- package/dist/rhf/form.d.ts +49 -1
- package/dist/rhf/line-items.d.ts +31 -0
- package/dist/rhf/line-items.js +59 -0
- package/dist/rhf/line-items.js.map +1 -0
- package/dist/rhf.d.ts +61 -1
- package/dist/rhf.js +2 -0
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +99 -2
- package/dist/shell/app-shell.js +58 -3
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/auth-layout.d.ts +74 -0
- package/dist/shell/auth-layout.js +97 -0
- package/dist/shell/auth-layout.js.map +1 -0
- package/dist/shell/top-bar-brand.d.ts +81 -0
- package/dist/shell/top-bar-brand.js +47 -0
- package/dist/shell/top-bar-brand.js.map +1 -0
- package/dist/shell/topbar-action-menu.d.ts +12 -1
- package/dist/shell/topbar-action-menu.js +16 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +56 -1
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +48 -1
- package/dist/wizard/wizard-context.d.ts +24 -1
- package/dist/wizard/wizard-context.js +19 -2
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.d.ts +20 -5
- package/dist/wizard/wizard-step.js +16 -3
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard.d.ts +50 -3
- package/package.json +1 -1
- package/src/chart.ts +4 -0
- package/src/components/account-settings-labels.ts +190 -0
- package/src/components/account-settings.tsx +110 -63
- package/src/components/amount-input.tsx +13 -1
- package/src/components/authed-image.tsx +190 -0
- package/src/components/breadcrumbs.tsx +8 -2
- package/src/components/button-group.tsx +7 -2
- package/src/components/calendar-heatmap.tsx +105 -10
- package/src/components/chip.tsx +8 -4
- package/src/components/choice-card.tsx +136 -25
- package/src/components/combobox-core.tsx +5 -0
- package/src/components/combobox.tsx +31 -9
- package/src/components/copy-button.tsx +3 -2
- package/src/components/data-table-cells.tsx +119 -0
- package/src/components/data-table-filters.ts +77 -0
- package/src/components/data-table-labels.ts +23 -0
- package/src/components/data-table.tsx +224 -13
- package/src/components/date-picker.tsx +19 -3
- package/src/components/description-list.tsx +52 -3
- package/src/components/entity-combobox.tsx +18 -3
- package/src/components/error-boundary.tsx +216 -0
- package/src/components/field.tsx +56 -5
- package/src/components/floating-panel.tsx +20 -7
- package/src/components/form-actions.tsx +191 -0
- package/src/components/image-grid.tsx +181 -0
- package/src/components/lightbox.tsx +374 -0
- package/src/components/line-items.tsx +385 -0
- package/src/components/list.tsx +65 -16
- package/src/components/loading-state.tsx +47 -0
- package/src/components/menu-item.tsx +7 -2
- package/src/components/mini-calendar.tsx +72 -3
- package/src/components/modal.tsx +61 -3
- package/src/components/month-picker.tsx +181 -26
- package/src/components/multi-entity-combobox.tsx +19 -3
- package/src/components/nav-pills.tsx +8 -1
- package/src/components/number-field.tsx +15 -10
- package/src/components/number-input.tsx +16 -1
- package/src/components/passkeys-setting.tsx +331 -0
- package/src/components/picker-sheet.tsx +7 -1
- package/src/components/pie-chart-labels.ts +32 -0
- package/src/components/pie-chart.tsx +479 -0
- package/src/components/progress-bar.tsx +129 -19
- package/src/components/qr-code.tsx +83 -0
- package/src/components/search-field.tsx +20 -4
- package/src/components/signed-amount.tsx +286 -0
- package/src/components/skeleton.tsx +21 -3
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/table.tsx +102 -13
- package/src/components/text-link.tsx +248 -0
- package/src/components/toggle-group.tsx +58 -10
- package/src/components/toggle-legend.tsx +87 -2
- package/src/components/ui.tsx +426 -36
- package/src/components/use-table-state.ts +265 -33
- package/src/data-table.ts +10 -0
- package/src/feedback/feedback-dialog.tsx +42 -3
- package/src/feedback/feedback-thread.tsx +434 -0
- package/src/feedback.ts +1 -0
- package/src/hooks/use-authed-src.ts +161 -0
- package/src/hooks/use-hotkey.ts +172 -0
- package/src/hooks/use-search-param-state.ts +187 -0
- package/src/i18n/defaults.ts +30 -0
- package/src/i18n/kit-labels.tsx +102 -8
- package/src/i18n/locales/de.ts +153 -0
- package/src/i18n/locales/es.ts +150 -0
- package/src/i18n/locales/fr.ts +153 -0
- package/src/i18n/locales/hu.ts +151 -0
- package/src/i18n/locales/it.ts +151 -0
- package/src/i18n/locales/zh.ts +150 -0
- package/src/index.ts +59 -0
- package/src/lib/format.ts +325 -0
- package/src/lib/qr-encode.ts +420 -0
- package/src/rhf/fields.tsx +1054 -0
- package/src/rhf/line-items.tsx +130 -0
- package/src/rhf.ts +6 -0
- package/src/shell/app-shell.tsx +147 -2
- package/src/shell/auth-layout.tsx +190 -0
- package/src/shell/top-bar-brand.tsx +76 -0
- package/src/shell/topbar-action-menu.tsx +21 -0
- package/src/shell.ts +2 -0
- package/src/wizard/wizard-context.tsx +41 -1
- package/src/wizard/wizard-step.tsx +43 -6
- package/tokens.css +12 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import type { KeyboardEvent, ReactNode } from "react";
|
|
3
|
+
import { KeyRound } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/cn";
|
|
5
|
+
import { useKitLocale } from "../i18n/kit-labels";
|
|
6
|
+
import { Button, Card, EmptyState, Input } from "./ui";
|
|
7
|
+
import { Skeleton } from "./skeleton";
|
|
8
|
+
import { useAccountSettingsLabels } from "./account-settings-labels";
|
|
9
|
+
import type { PasskeysSettingLabels } from "./account-settings-labels";
|
|
10
|
+
|
|
11
|
+
/** One registered passkey, as the app's API lists it. */
|
|
12
|
+
export interface PasskeyItem {
|
|
13
|
+
id: string | number;
|
|
14
|
+
name: string;
|
|
15
|
+
/** A `Date`, an ISO string or epoch ms. */
|
|
16
|
+
createdAt?: Date | string | number | null;
|
|
17
|
+
/** Null (or left out) reads as "Never used". */
|
|
18
|
+
lastUsedAt?: Date | string | number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PasskeysSettingProps {
|
|
22
|
+
/** `undefined` while the first load is in flight (or pass `loading`). */
|
|
23
|
+
passkeys: readonly PasskeyItem[] | undefined;
|
|
24
|
+
loading?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Run the WebAuthn ceremony and register the passkey — app-side, since the options
|
|
27
|
+
* and the attestation are the app's server's business. Called with the trimmed name
|
|
28
|
+
* field, which may be EMPTY: pick your own default ("Passkey", the device name).
|
|
29
|
+
* Return the promise and the field clears when it resolves; a rejection (including
|
|
30
|
+
* the user cancelling the OS prompt) keeps what was typed.
|
|
31
|
+
*/
|
|
32
|
+
onAdd: (name: string) => void | Promise<unknown>;
|
|
33
|
+
/** The ceremony is running. The add button says so and does not start a second one. */
|
|
34
|
+
adding?: boolean;
|
|
35
|
+
/** Offer "Rename" on each row. Return a promise to leave edit mode only on success. */
|
|
36
|
+
onRename?: (id: PasskeyItem["id"], name: string) => void | Promise<unknown>;
|
|
37
|
+
/** Offer "Delete" on each row — always behind an inline confirmation. */
|
|
38
|
+
onDelete?: (id: PasskeyItem["id"]) => void | Promise<unknown>;
|
|
39
|
+
/** A row whose rename or delete is in flight; its buttons are disabled. */
|
|
40
|
+
busyId?: PasskeyItem["id"] | null;
|
|
41
|
+
/** Show the name field above the add button. Default true. */
|
|
42
|
+
nameField?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Shown INSTEAD of the list and the add button — passkeys are not supported by this
|
|
45
|
+
* browser, or the page is not a secure context. Its wording is the app's: only the
|
|
46
|
+
* app knows which of those it detected.
|
|
47
|
+
*/
|
|
48
|
+
unavailable?: ReactNode;
|
|
49
|
+
/** A date for the "Added …" / "Last used …" lines. Default: the locale's medium date. */
|
|
50
|
+
formatDate?: (date: Date) => string;
|
|
51
|
+
/** Overrides the provider's locale for the default date format. */
|
|
52
|
+
locale?: string;
|
|
53
|
+
/** Prop > `<UiKitProvider labels={{ accountSettings: { passkeys } }}>` > English. */
|
|
54
|
+
labels?: Partial<PasskeysSettingLabels>;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The passkeys section of an account page: what is registered, when it was last used,
|
|
60
|
+
* rename and delete, and "Add passkey" — the fourth card beside `ProfileSetting`,
|
|
61
|
+
* `PasswordSetting` and `TwoFactorSetting`, and built the same way (a self-contained
|
|
62
|
+
* `<Card>` owning its transient form state; the app owns the data and the API).
|
|
63
|
+
*
|
|
64
|
+
* Lifted from kastlan's passkeys-card. **The WebAuthn ceremony stays app-side**:
|
|
65
|
+
* `navigator.credentials.create` needs options minted by the app's server and hands
|
|
66
|
+
* back an attestation only that server can verify, so the kit calls `onAdd` and waits.
|
|
67
|
+
*
|
|
68
|
+
* **Delete confirms inline**, in the row, rather than through `useConfirm`: this card
|
|
69
|
+
* must work in an app without a `ConfirmProvider`, and the question belongs next to
|
|
70
|
+
* the passkey it names. Focus moves to Cancel, as in every destructive confirm in the
|
|
71
|
+
* kit, so the Enter that pressed Delete cannot also answer it.
|
|
72
|
+
*/
|
|
73
|
+
export function PasskeysSetting({
|
|
74
|
+
passkeys,
|
|
75
|
+
loading,
|
|
76
|
+
onAdd,
|
|
77
|
+
adding = false,
|
|
78
|
+
onRename,
|
|
79
|
+
onDelete,
|
|
80
|
+
busyId,
|
|
81
|
+
nameField = true,
|
|
82
|
+
unavailable,
|
|
83
|
+
formatDate,
|
|
84
|
+
locale: localeProp,
|
|
85
|
+
labels: labelsProp,
|
|
86
|
+
className,
|
|
87
|
+
}: PasskeysSettingProps) {
|
|
88
|
+
const labels = useAccountSettingsLabels("passkeys", labelsProp);
|
|
89
|
+
const locale = useKitLocale(localeProp);
|
|
90
|
+
const [name, setName] = useState("");
|
|
91
|
+
|
|
92
|
+
const date = (value: PasskeyItem["createdAt"]): string | null => {
|
|
93
|
+
if (value === null || value === undefined || value === "") return null;
|
|
94
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
95
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
96
|
+
return formatDate ? formatDate(d) : new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(d);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const add = () => {
|
|
100
|
+
if (adding) return;
|
|
101
|
+
const typed = name;
|
|
102
|
+
Promise.resolve(onAdd(typed.trim()))
|
|
103
|
+
// Clear only what was sent: the field stays editable while the OS prompt is up.
|
|
104
|
+
.then(() => setName((current) => (current === typed ? "" : current)))
|
|
105
|
+
.catch(() => {});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const isLoading = loading || passkeys === undefined;
|
|
109
|
+
const items = passkeys ?? [];
|
|
110
|
+
|
|
111
|
+
const body = (() => {
|
|
112
|
+
if (unavailable != null && unavailable !== false) {
|
|
113
|
+
return <EmptyState variant="inline" size="sm" icon={<KeyRound />} title={unavailable} />;
|
|
114
|
+
}
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
{isLoading ? (
|
|
118
|
+
<Skeleton lines={2} label={labels.loading} />
|
|
119
|
+
) : items.length === 0 ? (
|
|
120
|
+
<EmptyState variant="inline" size="sm" icon={<KeyRound />} title={labels.empty} />
|
|
121
|
+
) : (
|
|
122
|
+
<ul aria-label={labels.list} className="divide-y divide-[var(--border)]">
|
|
123
|
+
{items.map((item) => (
|
|
124
|
+
<PasskeyRow
|
|
125
|
+
key={item.id}
|
|
126
|
+
item={item}
|
|
127
|
+
created={date(item.createdAt)}
|
|
128
|
+
lastUsed={date(item.lastUsedAt)}
|
|
129
|
+
busy={busyId !== undefined && busyId !== null && busyId === item.id}
|
|
130
|
+
onRename={onRename}
|
|
131
|
+
onDelete={onDelete}
|
|
132
|
+
labels={labels}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
</ul>
|
|
136
|
+
)}
|
|
137
|
+
<div className="space-y-2 pt-1">
|
|
138
|
+
{nameField && (
|
|
139
|
+
<Input
|
|
140
|
+
label={labels.nameLabel}
|
|
141
|
+
placeholder={labels.namePlaceholder}
|
|
142
|
+
value={name}
|
|
143
|
+
maxLength={120}
|
|
144
|
+
onChange={(e) => setName(e.target.value)}
|
|
145
|
+
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
146
|
+
if (e.key === "Enter") {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
add();
|
|
149
|
+
}
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
)}
|
|
153
|
+
<Button onClick={add} disabled={adding} aria-busy={adding || undefined}>
|
|
154
|
+
<KeyRound className="size-4" aria-hidden />
|
|
155
|
+
{adding ? labels.adding : labels.add}
|
|
156
|
+
</Button>
|
|
157
|
+
</div>
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
})();
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Card className={cn("p-4 space-y-3", className)}>
|
|
164
|
+
<div>
|
|
165
|
+
<div className="text-sm font-medium">{labels.title}</div>
|
|
166
|
+
<div className="text-xs text-[var(--text-muted)]">
|
|
167
|
+
{labels.description}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
{body}
|
|
171
|
+
</Card>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
type RowMode = "view" | "rename" | "delete";
|
|
176
|
+
|
|
177
|
+
function PasskeyRow({
|
|
178
|
+
item,
|
|
179
|
+
created,
|
|
180
|
+
lastUsed,
|
|
181
|
+
busy,
|
|
182
|
+
onRename,
|
|
183
|
+
onDelete,
|
|
184
|
+
labels,
|
|
185
|
+
}: {
|
|
186
|
+
item: PasskeyItem;
|
|
187
|
+
created: string | null;
|
|
188
|
+
lastUsed: string | null;
|
|
189
|
+
busy: boolean;
|
|
190
|
+
onRename?: PasskeysSettingProps["onRename"];
|
|
191
|
+
onDelete?: PasskeysSettingProps["onDelete"];
|
|
192
|
+
labels: PasskeysSettingLabels;
|
|
193
|
+
}) {
|
|
194
|
+
const [mode, setMode] = useState<RowMode>("view");
|
|
195
|
+
const [draft, setDraft] = useState(item.name);
|
|
196
|
+
// Which button opened the sub-mode, so leaving it puts focus back there — the
|
|
197
|
+
// confirm or the field unmounts under the focus, which otherwise falls to <body>.
|
|
198
|
+
const [returnTo, setReturnTo] = useState<"rename" | "delete" | null>(null);
|
|
199
|
+
const cancelDeleteRef = useRef<HTMLButtonElement>(null);
|
|
200
|
+
const renameRef = useRef<HTMLInputElement>(null);
|
|
201
|
+
const renameButtonRef = useRef<HTMLButtonElement>(null);
|
|
202
|
+
const deleteButtonRef = useRef<HTMLButtonElement>(null);
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (mode === "delete") cancelDeleteRef.current?.focus();
|
|
206
|
+
else if (mode === "rename") renameRef.current?.select();
|
|
207
|
+
else if (returnTo) (returnTo === "rename" ? renameButtonRef : deleteButtonRef).current?.focus();
|
|
208
|
+
}, [mode, returnTo]);
|
|
209
|
+
|
|
210
|
+
const open = (next: "rename" | "delete") => {
|
|
211
|
+
setReturnTo(next);
|
|
212
|
+
setDraft(item.name);
|
|
213
|
+
setMode(next);
|
|
214
|
+
};
|
|
215
|
+
const leave = () => setMode("view");
|
|
216
|
+
const escapeLeaves = (e: KeyboardEvent<HTMLButtonElement>) => {
|
|
217
|
+
if (e.key === "Escape") {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
e.stopPropagation();
|
|
220
|
+
leave();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const saveRename = () => {
|
|
225
|
+
const next = draft.trim();
|
|
226
|
+
if (!onRename || !next || busy) return;
|
|
227
|
+
if (next === item.name) {
|
|
228
|
+
leave();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
Promise.resolve(onRename(item.id, next))
|
|
232
|
+
.then(leave)
|
|
233
|
+
.catch(() => {});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const confirmDelete = () => {
|
|
237
|
+
if (!onDelete || busy) return;
|
|
238
|
+
// The row unmounts once the list refreshes; on a failure it stays, confirm open.
|
|
239
|
+
Promise.resolve(onDelete(item.id))
|
|
240
|
+
.then(() => setMode("view"))
|
|
241
|
+
.catch(() => {});
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const meta = [created && labels.created(created), lastUsed ? labels.lastUsed(lastUsed) : labels.neverUsed]
|
|
245
|
+
.filter(Boolean)
|
|
246
|
+
.join(" · ");
|
|
247
|
+
|
|
248
|
+
if (mode === "rename") {
|
|
249
|
+
return (
|
|
250
|
+
<li className="flex flex-wrap items-center gap-2 py-2">
|
|
251
|
+
<Input
|
|
252
|
+
ref={renameRef}
|
|
253
|
+
aria-label={labels.renameField(item.name)}
|
|
254
|
+
className="min-w-0 flex-1"
|
|
255
|
+
value={draft}
|
|
256
|
+
maxLength={120}
|
|
257
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
258
|
+
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
|
259
|
+
if (e.key === "Enter") {
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
saveRename();
|
|
262
|
+
} else if (e.key === "Escape") {
|
|
263
|
+
e.preventDefault();
|
|
264
|
+
e.stopPropagation();
|
|
265
|
+
leave();
|
|
266
|
+
}
|
|
267
|
+
}}
|
|
268
|
+
/>
|
|
269
|
+
<Button size="sm" onClick={saveRename} disabled={busy || !draft.trim()}>
|
|
270
|
+
{labels.save}
|
|
271
|
+
</Button>
|
|
272
|
+
<Button size="sm" variant="ghost" onClick={leave}>
|
|
273
|
+
{labels.cancel}
|
|
274
|
+
</Button>
|
|
275
|
+
</li>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (mode === "delete") {
|
|
280
|
+
return (
|
|
281
|
+
<li className="space-y-2 py-2">
|
|
282
|
+
<p role="alert" className="text-sm">
|
|
283
|
+
{labels.deleteConfirm(item.name)}
|
|
284
|
+
</p>
|
|
285
|
+
<div className="flex flex-wrap gap-2">
|
|
286
|
+
<Button size="sm" variant="danger" onClick={confirmDelete} onKeyDown={escapeLeaves} disabled={busy}>
|
|
287
|
+
{labels.confirmDelete}
|
|
288
|
+
</Button>
|
|
289
|
+
<Button ref={cancelDeleteRef} size="sm" variant="ghost" onClick={leave} onKeyDown={escapeLeaves}>
|
|
290
|
+
{labels.cancel}
|
|
291
|
+
</Button>
|
|
292
|
+
</div>
|
|
293
|
+
</li>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<li className="flex items-center gap-3 py-2">
|
|
299
|
+
<KeyRound className="size-4 shrink-0 text-[var(--text-muted)]" aria-hidden />
|
|
300
|
+
<div className="min-w-0 flex-1">
|
|
301
|
+
<div className="truncate text-sm font-medium">{item.name}</div>
|
|
302
|
+
<div className="text-xs text-[var(--text-muted)]">{meta}</div>
|
|
303
|
+
</div>
|
|
304
|
+
{onRename && (
|
|
305
|
+
<Button
|
|
306
|
+
ref={renameButtonRef}
|
|
307
|
+
size="sm"
|
|
308
|
+
variant="ghost"
|
|
309
|
+
disabled={busy}
|
|
310
|
+
onClick={() => open("rename")}
|
|
311
|
+
aria-label={labels.renameItem(item.name)}
|
|
312
|
+
>
|
|
313
|
+
{labels.rename}
|
|
314
|
+
</Button>
|
|
315
|
+
)}
|
|
316
|
+
{onDelete && (
|
|
317
|
+
<Button
|
|
318
|
+
ref={deleteButtonRef}
|
|
319
|
+
size="sm"
|
|
320
|
+
variant="ghost"
|
|
321
|
+
tone="danger"
|
|
322
|
+
disabled={busy}
|
|
323
|
+
onClick={() => open("delete")}
|
|
324
|
+
aria-label={labels.deleteItem(item.name)}
|
|
325
|
+
>
|
|
326
|
+
{labels.delete}
|
|
327
|
+
</Button>
|
|
328
|
+
)}
|
|
329
|
+
</li>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
@@ -3,6 +3,7 @@ import { createPortal } from "react-dom";
|
|
|
3
3
|
import { X } from "lucide-react";
|
|
4
4
|
import type { ComponentPropsWithoutRef, KeyboardEvent, ReactNode, RefObject } from "react";
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
6
|
+
import { CLIPS_ATTRIBUTE } from "../lib/clipping";
|
|
6
7
|
import { DropdownSearchHeader } from "./dropdown";
|
|
7
8
|
import { useBodyScrollLock } from "../hooks/use-body-scroll-lock";
|
|
8
9
|
import { useOverlayHistory } from "../hooks/use-overlay-history";
|
|
@@ -237,7 +238,12 @@ export function PickerSheet({
|
|
|
237
238
|
/>
|
|
238
239
|
{/* min-h-0 so the LIST scrolls rather than the dialog growing past the
|
|
239
240
|
viewport — a flex child defaults to min-height:auto. */}
|
|
240
|
-
|
|
241
|
+
{/* Marked as the scroller it is (`CLIPS_ATTRIBUTE`), so a Tooltip in a row —
|
|
242
|
+
`Combobox`'s `optionAdornment` — portals out under jsdom as it does in the
|
|
243
|
+
browser rather than joining the option's name (keksdose E8). */}
|
|
244
|
+
<div {...{ [CLIPS_ATTRIBUTE]: "" }} className="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
245
|
+
{children}
|
|
246
|
+
</div>
|
|
241
247
|
</div>,
|
|
242
248
|
document.body,
|
|
243
249
|
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// All user-facing strings for `PieChart`, as the `pieChart` namespace of
|
|
2
|
+
// `<UiKitProvider labels>`, and per chart the `labels` prop over that.
|
|
3
|
+
//
|
|
4
|
+
// A module of its own, with no recharts in it, for the reason `series-chart-labels`
|
|
5
|
+
// is one: the label registry (src/i18n) imports the defaults, and must not pull the
|
|
6
|
+
// chart library in behind them.
|
|
7
|
+
|
|
8
|
+
export interface PieChartLabels {
|
|
9
|
+
/** Accessible name of the group of keyboard stops laid over the slices — one tab
|
|
10
|
+
* stop, arrow keys between them. */
|
|
11
|
+
slices: string;
|
|
12
|
+
/**
|
|
13
|
+
* One slice's accessible name. `value` arrives through `formatValue`, `percent` as
|
|
14
|
+
* the slice's share of what is drawn, already formatted in the locale ("42 %" in
|
|
15
|
+
* German, "42%" in English) — the function only arranges them.
|
|
16
|
+
*/
|
|
17
|
+
slice: (label: string, value: string, percent: string) => string;
|
|
18
|
+
/** The caption over the donut's centre figure. */
|
|
19
|
+
total: string;
|
|
20
|
+
/** What a chart with nothing to draw says, when the caller passes no `empty`. */
|
|
21
|
+
empty: string;
|
|
22
|
+
/** Accessible name of the chart's legend (a group of switches, one per slice). */
|
|
23
|
+
legend: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_PIE_CHART_LABELS: PieChartLabels = {
|
|
27
|
+
slices: "Chart slices",
|
|
28
|
+
slice: (label, value, percent) => `${label}: ${value} (${percent})`,
|
|
29
|
+
total: "Total",
|
|
30
|
+
empty: "No data",
|
|
31
|
+
legend: "Categories",
|
|
32
|
+
};
|