@eifi1/ui-kit 0.34.0 → 0.35.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 +82 -38
- package/dist/billing/billing-banner.d.ts +5 -2
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/plan-limit-notice.d.ts +5 -2
- package/dist/billing/plan-limit-notice.js.map +1 -1
- package/dist/chart.js.map +1 -1
- package/dist/components/amount-input.d.ts +0 -3
- package/dist/components/amount-input.js +2 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +1 -4
- package/dist/components/calculator.js +1 -3
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +0 -37
- package/dist/components/chart-zoom.js +1 -28
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/chip-input.d.ts +0 -4
- package/dist/components/chip-input.js +1 -5
- package/dist/components/chip-input.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/currency-option-list.d.ts +3 -8
- package/dist/components/currency-option-list.js +1 -29
- package/dist/components/currency-option-list.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +3 -3
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filter-popover.js +1 -1
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +1 -42
- package/dist/components/data-table-labels.js +2 -31
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +17 -1
- package/dist/components/data-table-shared.js +22 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +12 -2
- package/dist/components/data-table.js +18 -14
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +3 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/dropdown.d.ts +18 -15
- package/dist/components/dropdown.js +11 -3
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-base.d.ts +0 -5
- package/dist/components/field-base.js +0 -5
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/field-parts.d.ts +3 -11
- package/dist/components/field-parts.js +1 -9
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +8 -23
- package/dist/components/field-strip.js +24 -22
- package/dist/components/field-strip.js.map +1 -1
- package/dist/components/field-sync.d.ts +0 -4
- package/dist/components/field-sync.js +0 -4
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/field.d.ts +4 -4
- package/dist/components/field.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +2 -2
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/hover-menu.d.ts +6 -13
- package/dist/components/hover-menu.js +3 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/inline-entity-combobox.d.ts +1 -1
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/modal.d.ts +9 -1
- package/dist/components/modal.js +7 -4
- package/dist/components/modal.js.map +1 -1
- package/dist/components/multi-select.d.ts +5 -16
- package/dist/components/multi-select.js +5 -10
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.d.ts +0 -3
- package/dist/components/number-input.js +2 -4
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/popover.js +2 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/server-wake.d.ts +0 -6
- package/dist/components/server-wake.js.map +1 -1
- package/dist/components/settings-fields.d.ts +4 -2
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/stat-tile.d.ts +8 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +2 -17
- package/dist/components/swipeable-row.js +11 -16
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tooltip.d.ts +9 -1
- package/dist/components/tooltip.js +20 -5
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review.d.ts +10 -2
- package/dist/components/translation-review.js +11 -3
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/use-table-state.d.ts +31 -3
- package/dist/components/use-table-state.js +53 -8
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +2 -2
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.d.ts +1 -4
- package/dist/components/wizard-stepper.js +2 -3
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/currencies.d.ts +23 -0
- package/dist/currencies.js +38 -0
- package/dist/currencies.js.map +1 -0
- package/dist/demo/demo-banner.d.ts +7 -2
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/eslint/index.d.ts +83 -0
- package/dist/eslint/index.js +69 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/role-utilities.d.ts +48 -0
- package/dist/eslint/role-utilities.generated.d.ts +2 -0
- package/dist/eslint/role-utilities.generated.js +143 -0
- package/dist/eslint/role-utilities.generated.js.map +1 -0
- package/dist/eslint/role-utilities.js +43 -0
- package/dist/eslint/role-utilities.js.map +1 -0
- package/dist/feedback/feedback-menu.d.ts +2 -2
- package/dist/feedback/feedback-menu.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +3 -8
- package/dist/feedback/feedback-thread.js +0 -2
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +16 -3
- package/dist/hooks/use-dismiss.js +14 -2
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +14 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/fr.js +3 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/it.js +3 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/refs.d.ts +13 -0
- package/dist/lib/refs.js +15 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/rhf/fields.d.ts +10 -10
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form-core.d.ts +5 -4
- package/dist/rhf/form-core.js.map +1 -1
- package/dist/rhf/form.d.ts +7 -36
- package/dist/rhf/form.js +1 -16
- package/dist/rhf/form.js.map +1 -1
- package/dist/rhf/line-items.d.ts +1 -1
- package/dist/rhf/line-items.js.map +1 -1
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
- package/dist/rhf.js.map +1 -1
- package/dist/shell/option-switcher-menu.d.ts +5 -17
- package/dist/shell/option-switcher-menu.js +1 -3
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +1 -4
- package/dist/shell/role-switcher.js +2 -3
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/topbar-action-menu.d.ts +7 -19
- package/dist/shell/topbar-action-menu.js +1 -3
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +3 -12
- package/dist/shell/topbar-controls.js +6 -9
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/viewport.d.ts +25 -2
- package/dist/testing/viewport.js +61 -10
- package/dist/testing/viewport.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +0 -7
- package/dist/theme/palette-presets.js +0 -279
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/wizard/types.d.ts +0 -5
- package/dist/wizard/types.js +1 -5
- package/dist/wizard/types.js.map +1 -1
- package/package.json +9 -1
- package/src/billing/billing-banner.tsx +5 -2
- package/src/billing/plan-limit-notice.tsx +5 -2
- package/src/chart.ts +5 -3
- package/src/components/amount-input.tsx +2 -6
- package/src/components/calculator.tsx +2 -7
- package/src/components/chart-zoom.tsx +4 -47
- package/src/components/chip-input.tsx +0 -7
- package/src/components/combobox.tsx +1 -1
- package/src/components/copy-button.tsx +5 -1
- package/src/components/currency-option-list.tsx +8 -42
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-filter-popover.tsx +2 -1
- package/src/components/data-table-labels.ts +2 -84
- package/src/components/data-table-shared.tsx +55 -2
- package/src/components/data-table.tsx +29 -17
- package/src/components/dialog-frame.tsx +3 -1
- package/src/components/dropdown.tsx +42 -18
- package/src/components/entity-combobox.tsx +1 -1
- package/src/components/field-base.tsx +0 -11
- package/src/components/field-parts.tsx +5 -19
- package/src/components/field-strip.tsx +9 -25
- package/src/components/field-sync.tsx +0 -7
- package/src/components/field.tsx +4 -4
- package/src/components/full-bleed-dialog.tsx +4 -3
- package/src/components/hover-menu.tsx +12 -16
- package/src/components/inline-entity-combobox.tsx +1 -1
- package/src/components/modal.tsx +20 -4
- package/src/components/multi-select.tsx +13 -30
- package/src/components/number-input.tsx +3 -8
- package/src/components/popover.tsx +5 -3
- package/src/components/series-chart.tsx +1 -1
- package/src/components/server-wake.tsx +0 -6
- package/src/components/settings-fields.tsx +4 -2
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/swipeable-row.tsx +15 -37
- package/src/components/tooltip.tsx +37 -6
- package/src/components/translation-review.tsx +25 -7
- package/src/components/use-table-state.ts +91 -10
- package/src/components/user-avatar.tsx +2 -2
- package/src/components/wizard-stepper.tsx +2 -6
- package/src/currencies.ts +70 -0
- package/src/demo/demo-banner.tsx +7 -2
- package/src/eslint/index.ts +161 -0
- package/src/eslint/role-utilities.generated.ts +146 -0
- package/src/eslint/role-utilities.ts +99 -0
- package/src/feedback/feedback-menu.tsx +2 -2
- package/src/feedback/feedback-thread.tsx +3 -8
- package/src/hooks/use-dismiss.ts +39 -4
- package/src/i18n/kit-labels.tsx +15 -5
- package/src/i18n/locales/fr.ts +3 -2
- package/src/i18n/locales/it.ts +3 -2
- package/src/index.ts +3 -1
- package/src/lib/refs.ts +22 -0
- package/src/rhf/fields.tsx +10 -10
- package/src/rhf/form-core.tsx +5 -4
- package/src/rhf/form.tsx +7 -43
- package/src/rhf/line-items.tsx +1 -1
- package/src/rhf/use-rhf-wizard-step.ts +4 -4
- package/src/rhf.ts +3 -2
- package/src/shell/option-switcher-menu.tsx +5 -22
- package/src/shell/role-switcher.tsx +2 -6
- package/src/shell/topbar-action-menu.tsx +7 -24
- package/src/shell/topbar-controls.tsx +6 -18
- package/src/testing/index.ts +3 -1
- package/src/testing/viewport.ts +125 -19
- package/src/theme/palette-presets.ts +7 -293
- package/src/wizard/types.ts +0 -8
|
@@ -52,7 +52,8 @@ export interface ThemeSettingSelectProps
|
|
|
52
52
|
* choice visible without opening anything, in every app (keksdose offered light and dark
|
|
53
53
|
* only; its store already knew "system"). A {@link ToggleGroup}'s props pass through —
|
|
54
54
|
* `labelPlacement`, `disabledReason`, `commit`, `size`, `overflow` — minus the options
|
|
55
|
-
* and the value, which this owns.
|
|
55
|
+
* and the value, which this owns. `ref` too: the group, whose `focus()` lands on the
|
|
56
|
+
* checked option (pinned in round 0.35 R4; the select variant's `ref` is the `<select>`).
|
|
56
57
|
*
|
|
57
58
|
* The options wrap rather than truncate (`overflow="wrap"`), as in its two siblings
|
|
58
59
|
* {@link TextSizeSetting} and {@link ContrastSetting} (0.32.1, Kurvenschmiede's 0.32
|
|
@@ -161,7 +162,8 @@ export function LanguageSetting({ options, ...rest }: LanguageSettingProps) {
|
|
|
161
162
|
/**
|
|
162
163
|
* What {@link TextSizeSetting} and {@link ContrastSetting} take: a {@link ToggleGroup}'s
|
|
163
164
|
* props — `labelPlacement`, `disabledReason`, `commit`, `size`, `overflow` — minus the
|
|
164
|
-
* options and the value, which they own, as {@link ThemeSettingToggleProps} does.
|
|
165
|
+
* options and the value, which they own, as {@link ThemeSettingToggleProps} does. `ref`
|
|
166
|
+
* is the group's, as there (round 0.35 R4).
|
|
165
167
|
*/
|
|
166
168
|
type AppearanceToggleProps<T extends string> = Omit<ToggleGroupBaseProps<T>, "options" | "label"> & {
|
|
167
169
|
value: T;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, isValidElement, useContext, useId, useLayoutEffect, useState } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ComponentPropsWithRef, CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
import { ArrowDown, ArrowUp, Minus } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { useKitLabels, useKitLink, useKitLocale, type LabelOverride } from "../i18n/kit-labels";
|
|
@@ -115,8 +115,14 @@ export interface StatTileLinkProps {
|
|
|
115
115
|
children: ReactNode;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* A `div`'s props, `ref` included since 0.35 (round 0.35 R4, the follow-up to API-2): they
|
|
120
|
+
* ride on to the {@link Card} that is the tile, so the ref is the tile's own `<div>` — the
|
|
121
|
+
* one to scroll into view or measure. `ComponentPropsWithoutRef` only kept it out of the
|
|
122
|
+
* type; at runtime `{...rest}` already carried it there.
|
|
123
|
+
*/
|
|
118
124
|
export interface StatTileProps
|
|
119
|
-
extends Omit<
|
|
125
|
+
extends Omit<ComponentPropsWithRef<"div">, "children" | "onClick"> {
|
|
120
126
|
/** What is measured. Also the tile's accessible name when it is a link/button. */
|
|
121
127
|
label: ReactNode;
|
|
122
128
|
/**
|
|
@@ -51,9 +51,8 @@ interface SwipeActionBase {
|
|
|
51
51
|
* dark-mode pastel.
|
|
52
52
|
*
|
|
53
53
|
* **`paint`** follows the same rule for an app's documented exception (see
|
|
54
|
-
* {@link SwipePaint}).
|
|
55
|
-
*
|
|
56
|
-
* colour, and the idle panel is still dimmed.
|
|
54
|
+
* {@link SwipePaint}). The old path, a `className` / `armedClassName` pair under a white
|
|
55
|
+
* label and dimmed idle, was deprecated in 0.33 and removed in 0.35 (round 0.35 R1).
|
|
57
56
|
*/
|
|
58
57
|
export type SwipeAction = SwipeActionBase &
|
|
59
58
|
(
|
|
@@ -61,27 +60,12 @@ export type SwipeAction = SwipeActionBase &
|
|
|
61
60
|
/** The kit paints the panel: the tone's wash idle, its fill and `-contrast` armed. */
|
|
62
61
|
tone: SwipeTone;
|
|
63
62
|
paint?: never;
|
|
64
|
-
className?: never;
|
|
65
|
-
armedClassName?: never;
|
|
66
63
|
}
|
|
67
64
|
| {
|
|
68
65
|
/** A documented app exception: idle a 14 % wash of `fill` under the body ink,
|
|
69
66
|
* armed `fill` under `foreground`. */
|
|
70
67
|
paint: SwipePaint;
|
|
71
68
|
tone?: never;
|
|
72
|
-
className?: never;
|
|
73
|
-
armedClassName?: never;
|
|
74
|
-
}
|
|
75
|
-
| {
|
|
76
|
-
tone?: never;
|
|
77
|
-
paint?: never;
|
|
78
|
-
/** @deprecated 0.33 — pass `tone`, or `paint` for an exception; removed in 0.35.
|
|
79
|
-
* Background utility class while the action is previewed but not yet armed; the
|
|
80
|
-
* panel is dimmed then, and its text is white unless this sets a text colour. */
|
|
81
|
-
className: string;
|
|
82
|
-
/** @deprecated 0.33 — pass `tone`, or `paint`; removed in 0.35. Background
|
|
83
|
-
* utility class once the drag has passed this action's threshold. */
|
|
84
|
-
armedClassName: string;
|
|
85
69
|
}
|
|
86
70
|
);
|
|
87
71
|
|
|
@@ -114,21 +98,16 @@ export const SWIPE_TONE: Record<SwipeTone, { idle: string; armed: string }> = {
|
|
|
114
98
|
const PAINT_IDLE = "bg-[color-mix(in_oklab,var(--swipe-paint-fill)_14%,var(--bg-surface))] text-primary";
|
|
115
99
|
const PAINT_ARMED = "bg-[var(--swipe-paint-fill)] text-[var(--swipe-paint-fg)]";
|
|
116
100
|
|
|
117
|
-
/** The panel's colour classes
|
|
118
|
-
function panelLook(action: SwipeAction, armed: boolean): { className: string; style?: CSSProperties
|
|
119
|
-
if (action.tone) return { className: SWIPE_TONE[action.tone][armed ? "armed" : "idle"]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
dim: false,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
// The deprecated path, as it was: white text under the caller's fill, dimmed idle.
|
|
131
|
-
return { className: cn("text-white", armed ? action.armedClassName : action.className), dim: !armed };
|
|
101
|
+
/** The panel's colour classes and inline style for one action and state. */
|
|
102
|
+
function panelLook(action: SwipeAction, armed: boolean): { className: string; style?: CSSProperties } {
|
|
103
|
+
if (action.tone) return { className: SWIPE_TONE[action.tone][armed ? "armed" : "idle"] };
|
|
104
|
+
return {
|
|
105
|
+
className: armed ? PAINT_ARMED : PAINT_IDLE,
|
|
106
|
+
style: {
|
|
107
|
+
"--swipe-paint-fill": action.paint.fill,
|
|
108
|
+
"--swipe-paint-fg": action.paint.foreground,
|
|
109
|
+
} as CSSProperties,
|
|
110
|
+
};
|
|
132
111
|
}
|
|
133
112
|
|
|
134
113
|
/**
|
|
@@ -296,11 +275,10 @@ export function SwipeableRow({
|
|
|
296
275
|
className={cn(
|
|
297
276
|
"pointer-events-none absolute inset-0 flex items-center gap-2 px-4",
|
|
298
277
|
"text-xs font-medium transition-all",
|
|
278
|
+
// No dimming: a tone or a paint says "not yet armed" with the soft→solid
|
|
279
|
+
// step, which keeps its label readable at both ends (the 0.33 rule; the
|
|
280
|
+
// dimmed path went with `className` / `armedClassName` in 0.35).
|
|
299
281
|
look.className,
|
|
300
|
-
// The old path only: dimmed until the drag has passed a threshold. A tone
|
|
301
|
-
// or a paint says it with the soft→solid step instead, which keeps its
|
|
302
|
-
// label readable at both ends.
|
|
303
|
-
look.dim && "opacity-60",
|
|
304
282
|
// The panel is anchored to the edge the row is uncovering, so the label
|
|
305
283
|
// sits where the eye already is rather than across the screen. That edge is
|
|
306
284
|
// physical, while a flex row starts at the INLINE start — so in RTL the two
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cloneElement,
|
|
3
3
|
isValidElement,
|
|
4
|
+
useCallback,
|
|
4
5
|
useEffect,
|
|
5
6
|
useId,
|
|
6
7
|
useLayoutEffect,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type PointerEvent,
|
|
13
14
|
type ReactElement,
|
|
14
15
|
type ReactNode,
|
|
16
|
+
type Ref,
|
|
15
17
|
type RefObject,
|
|
16
18
|
} from "react";
|
|
17
19
|
import { createPortal } from "react-dom";
|
|
@@ -21,6 +23,7 @@ import { useAnchoredRect, type AnchorRect } from "../hooks/use-anchored-rect";
|
|
|
21
23
|
import { viewportSize } from "../hooks/use-anchored-panel";
|
|
22
24
|
import { dirOf, type Direction } from "../lib/direction";
|
|
23
25
|
import { hasClippingAncestor } from "../lib/clipping";
|
|
26
|
+
import { assignRef } from "../lib/refs";
|
|
24
27
|
|
|
25
28
|
/** The attribute that marks an element as a clipping container for {@link Tooltip}'s
|
|
26
29
|
* auto-portal, whatever its computed `overflow` — `<div data-clips>` or
|
|
@@ -129,6 +132,14 @@ export interface TooltipProps extends ComponentPropsWithoutRef<"span"> {
|
|
|
129
132
|
*/
|
|
130
133
|
tap?: TooltipTap;
|
|
131
134
|
children: ReactNode;
|
|
135
|
+
/**
|
|
136
|
+
* The trigger WRAPPER — the `<span>` the rest of the props go to, around the caller's
|
|
137
|
+
* child (round 0.35 R4, as Card and EmptyState took one in 0.34) — merged with its own
|
|
138
|
+
* ref, which the bubble is anchored to and dismissed by. Not the bubble: a portalled
|
|
139
|
+
* one exists only while it is up. On the empty-label branch there is no wrapper, so
|
|
140
|
+
* the ref stays `null` (see the ⚠️ above).
|
|
141
|
+
*/
|
|
142
|
+
ref?: Ref<HTMLSpanElement>;
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
/** What each variant below takes: the resolved `side` and `tap`, and every span attribute
|
|
@@ -630,6 +641,22 @@ function useTooltipTrigger(
|
|
|
630
641
|
return { open, dismissed, handlers };
|
|
631
642
|
}
|
|
632
643
|
|
|
644
|
+
/**
|
|
645
|
+
* The trigger wrapper's own ref, and one callback ref that hands the wrapper to the
|
|
646
|
+
* caller's `ref` as well (round 0.35 R4).
|
|
647
|
+
*/
|
|
648
|
+
function useTriggerRef(ref: Ref<HTMLSpanElement> | undefined) {
|
|
649
|
+
const triggerRef = useRef<HTMLSpanElement | null>(null);
|
|
650
|
+
const setTrigger = useCallback(
|
|
651
|
+
(el: HTMLSpanElement | null) => {
|
|
652
|
+
triggerRef.current = el;
|
|
653
|
+
assignRef(ref, el);
|
|
654
|
+
},
|
|
655
|
+
[ref],
|
|
656
|
+
);
|
|
657
|
+
return { triggerRef, setTrigger };
|
|
658
|
+
}
|
|
659
|
+
|
|
633
660
|
/** The variant that lives next to its trigger, and — when `detect` is on, which is the
|
|
634
661
|
* default — moves its bubble to `<body>` when that turns out to be inside a clipping
|
|
635
662
|
* container (see "Inside a scroll container" on {@link Tooltip}).
|
|
@@ -656,10 +683,11 @@ function InPlaceTooltip({
|
|
|
656
683
|
lazy,
|
|
657
684
|
tap,
|
|
658
685
|
children,
|
|
686
|
+
ref,
|
|
659
687
|
...rest
|
|
660
688
|
}: TooltipVariantProps & { detect: boolean; lazy: boolean }) {
|
|
661
689
|
const id = useId();
|
|
662
|
-
const triggerRef
|
|
690
|
+
const { triggerRef, setTrigger } = useTriggerRef(ref);
|
|
663
691
|
const [clipped, setClipped] = useState(false);
|
|
664
692
|
const [dir, setDir] = useState<Direction>("ltr");
|
|
665
693
|
// The reading direction on every request (the clamp keeps the start edge of a bubble
|
|
@@ -677,7 +705,8 @@ function InPlaceTooltip({
|
|
|
677
705
|
// phantom-scroll bug whether or not anyone opens it.
|
|
678
706
|
useLayoutEffect(() => {
|
|
679
707
|
if (detect) setClipped(hasClippingAncestor(triggerRef.current));
|
|
680
|
-
|
|
708
|
+
// `triggerRef` is the same object for the component's life (useTriggerRef's useRef).
|
|
709
|
+
}, [detect, triggerRef]);
|
|
681
710
|
return (
|
|
682
711
|
// No role for this span: its handlers track whether the bubble is up and activate
|
|
683
712
|
// nothing; the caller's child is the interactive element, keeps its own handlers,
|
|
@@ -687,7 +716,7 @@ function InPlaceTooltip({
|
|
|
687
716
|
// a caller passing an `onFocus` of its own must not replace them (its pointer
|
|
688
717
|
// handlers are called from them).
|
|
689
718
|
{...rest}
|
|
690
|
-
ref={
|
|
719
|
+
ref={setTrigger}
|
|
691
720
|
// Clipped for the one render in which the bubble is measured (see useViewportClamp):
|
|
692
721
|
// that layout must not reach the page's width.
|
|
693
722
|
style={measuring ? { ...rest.style, overflow: "clip" } : rest.style}
|
|
@@ -1041,9 +1070,10 @@ function PortalTooltip({
|
|
|
1041
1070
|
redact,
|
|
1042
1071
|
tap,
|
|
1043
1072
|
children,
|
|
1073
|
+
ref,
|
|
1044
1074
|
...rest
|
|
1045
1075
|
}: TooltipVariantProps) {
|
|
1046
|
-
const triggerRef
|
|
1076
|
+
const { triggerRef, setTrigger } = useTriggerRef(ref);
|
|
1047
1077
|
// The trigger's reading direction, read when the bubble is asked for (an event, not a
|
|
1048
1078
|
// render): it resolves `start` / `end`, and the portalled bubble — which has left the
|
|
1049
1079
|
// subtree it would have inherited `dir` from — carries it too.
|
|
@@ -1062,9 +1092,10 @@ function PortalTooltip({
|
|
|
1062
1092
|
// As in `InPlaceTooltip`: the caller's attributes first, the handlers that run
|
|
1063
1093
|
// this component after them. The BUBBLE is deliberately not given them — it
|
|
1064
1094
|
// is portalled to `<body>`, and an id or a tour anchor duplicated onto a node
|
|
1065
|
-
// that only exists while hovered would match twice or match nothing.
|
|
1095
|
+
// that only exists while hovered would match twice or match nothing. So, too,
|
|
1096
|
+
// the caller's `ref` (round 0.35 R4).
|
|
1066
1097
|
{...rest}
|
|
1067
|
-
ref={
|
|
1098
|
+
ref={setTrigger}
|
|
1068
1099
|
className={cn("relative inline-flex", className)}
|
|
1069
1100
|
{...handlers}
|
|
1070
1101
|
>
|
|
@@ -33,12 +33,13 @@ import { BulkActionBar } from "./bulk-action-bar";
|
|
|
33
33
|
import { Checkbox } from "./checkbox";
|
|
34
34
|
import { Chip } from "./chip";
|
|
35
35
|
import { DataTable } from "./data-table";
|
|
36
|
-
import type { DataTableColumn, DataTableProps, MobileSwipeActions
|
|
36
|
+
import type { DataTableColumn, DataTableProps, MobileSwipeActions } from "./data-table";
|
|
37
37
|
import { Disclosure } from "./disclosure";
|
|
38
38
|
import { SearchField } from "./search-field";
|
|
39
39
|
import { toast } from "./toast";
|
|
40
40
|
import type { SwipeAction } from "./swipeable-row";
|
|
41
41
|
import { ToggleGroup } from "./toggle-group";
|
|
42
|
+
import { useSharedTableView } from "./use-table-state";
|
|
42
43
|
import { TranslationReviewEditor } from "./translation-review-editor";
|
|
43
44
|
import { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from "./translation-review-labels";
|
|
44
45
|
import type { TranslationReviewLabels } from "./translation-review-labels";
|
|
@@ -168,7 +169,8 @@ export interface TranslationReviewPanelProps {
|
|
|
168
169
|
/** Rows per page. Default 50. */
|
|
169
170
|
pageSize?: number;
|
|
170
171
|
/** Persists the table's sort and page size (DataTable's `storageKey`), under the
|
|
171
|
-
* prefix below
|
|
172
|
+
* prefix below — under {@link groupBy}, the one sort and page size every group
|
|
173
|
+
* shares, in the same place (0.35). */
|
|
172
174
|
storageKey?: string;
|
|
173
175
|
/**
|
|
174
176
|
* The `localStorage` prefix in front of `storageKey` (DataTable's `storageKeyPrefix`).
|
|
@@ -257,7 +259,14 @@ export interface TranslationReviewPanelProps {
|
|
|
257
259
|
* reference; a column sort orders the rows INSIDE every group alike. Each group pages
|
|
258
260
|
* on its own, `pageSize` rows at a time — a header then always stands over its own
|
|
259
261
|
* rows, and paging one area does not move another — rather than one pager cutting
|
|
260
|
-
* through the headers.
|
|
262
|
+
* through the headers. The page SIZE is one for all of them, as the sort is: a
|
|
263
|
+
* group's pager changes it everywhere.
|
|
264
|
+
*
|
|
265
|
+
* `storageKey` persists that one view — the sort and the page size — in the place the
|
|
266
|
+
* ungrouped table keeps its own (same key, same prefix, same blob), read on mount and
|
|
267
|
+
* written on a change, so a reviewer switching the grouping on or off keeps both
|
|
268
|
+
* (0.35, Kurvenschmiede; before, it persisted the ungrouped table only). What only
|
|
269
|
+
* the ungrouped table has — column widths, hidden columns — stays in the blob for it.
|
|
261
270
|
*
|
|
262
271
|
* Groups fold (each on its own, remembered while the panel lives), and a folded group's
|
|
263
272
|
* rows are not rendered at all — only its header. Which groups start open is
|
|
@@ -558,8 +567,15 @@ export function TranslationReviewPanel({
|
|
|
558
567
|
// has mounted and taken focus, the group that asked.
|
|
559
568
|
const [confirming, setConfirming] = useState<string | null>(null);
|
|
560
569
|
const confirmFocus = useRef<string | null>(null);
|
|
561
|
-
// One
|
|
562
|
-
|
|
570
|
+
// One view for every group's table — one sort, so a header click orders all of them
|
|
571
|
+
// alike, and one page size — kept under `storageKey` in the single table's own blob, so
|
|
572
|
+
// switching the grouping keeps both (0.35, Kurvenschmiede).
|
|
573
|
+
const groupView = useSharedTableView({
|
|
574
|
+
active: groupBy !== undefined,
|
|
575
|
+
storageKey,
|
|
576
|
+
storageKeyPrefix,
|
|
577
|
+
defaultPageSize: pageSize,
|
|
578
|
+
});
|
|
563
579
|
|
|
564
580
|
const setFilter = (patch: Partial<TranslationReviewFilter>) => {
|
|
565
581
|
const next = { ...filter, ...patch };
|
|
@@ -870,8 +886,10 @@ export function TranslationReviewPanel({
|
|
|
870
886
|
frame: false,
|
|
871
887
|
chrome: "minimal" as const,
|
|
872
888
|
maxBodyHeight: "none",
|
|
873
|
-
sorts:
|
|
874
|
-
onSortsChange:
|
|
889
|
+
sorts: groupView.sorts,
|
|
890
|
+
onSortsChange: groupView.setSorts,
|
|
891
|
+
pageSize: groupView.pageSize,
|
|
892
|
+
onPageSizeChange: groupView.setPageSize,
|
|
875
893
|
labels: { table: group.name },
|
|
876
894
|
}
|
|
877
895
|
: { storageKey, storageKeyPrefix })}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { useLocation, useSearchParams } from "react-router";
|
|
3
|
+
import { useKitTableStorageKeyPrefix } from "../i18n/kit-labels";
|
|
3
4
|
import { readStored, writeStored } from "../lib/safe-storage";
|
|
4
5
|
import {
|
|
5
6
|
decodeFilterValue,
|
|
@@ -45,6 +46,13 @@ function serializePersisted(state: PersistedState): string {
|
|
|
45
46
|
return JSON.stringify(state);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/** A stored page size as a table holds it — `"all"` is `Infinity` — or `fallback`. */
|
|
50
|
+
function storedPageSize(stored: Partial<PersistedState>, fallback: number): number {
|
|
51
|
+
if (stored.pageSize === "all") return Infinity;
|
|
52
|
+
if (typeof stored.pageSize === "number" && stored.pageSize > 0) return stored.pageSize;
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
// ---------- URL sync helpers ----------
|
|
49
57
|
|
|
50
58
|
const URL_FILTER_PREFIX = "f.";
|
|
@@ -251,8 +259,8 @@ function writeUrlState<T>(
|
|
|
251
259
|
* * **Persistence writes on every change, the URL only when asked.** `storageKey` is the
|
|
252
260
|
* per-table identity; `urlSync` is opt-in because only some tables want their view in
|
|
253
261
|
* a shareable address.
|
|
254
|
-
* * **A controlled filter or
|
|
255
|
-
* the note at the reset below.
|
|
262
|
+
* * **A controlled filter, sort or page size that changes sends the table back to page
|
|
263
|
+
* 1.** See the note at the reset below.
|
|
256
264
|
*/
|
|
257
265
|
export function useTableState<T>({
|
|
258
266
|
columns,
|
|
@@ -262,6 +270,7 @@ export function useTableState<T>({
|
|
|
262
270
|
defaultPageSize,
|
|
263
271
|
sortsProp,
|
|
264
272
|
filtersProp,
|
|
273
|
+
pageSizeProp,
|
|
265
274
|
}: {
|
|
266
275
|
columns: DataTableColumn<T>[];
|
|
267
276
|
storageKey?: string;
|
|
@@ -270,6 +279,7 @@ export function useTableState<T>({
|
|
|
270
279
|
defaultPageSize: number;
|
|
271
280
|
sortsProp?: SortState[];
|
|
272
281
|
filtersProp?: FilterState;
|
|
282
|
+
pageSizeProp?: number;
|
|
273
283
|
}) {
|
|
274
284
|
const { enabled: urlSync, prefix: urlPrefix } = resolveTableUrlSync(urlSyncOption);
|
|
275
285
|
const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);
|
|
@@ -312,21 +322,21 @@ export function useTableState<T>({
|
|
|
312
322
|
// is never painted and never written to the URL. Not on mount: a deep link's `p=3`
|
|
313
323
|
// must survive the owner's first render. (In `serverPagination` mode this state is
|
|
314
324
|
// unused — the owner's `page` is the page, and resetting it stays the owner's job.)
|
|
325
|
+
// A controlled page size counts too: the groups of `TranslationReviewPanel` share one,
|
|
326
|
+
// and a group paged deep into its rows starts over when another group's pager changes it.
|
|
315
327
|
const controlledSig =
|
|
316
|
-
sortsProp !== undefined || filtersProp !== undefined
|
|
317
|
-
? JSON.stringify([sortsProp ?? null, filtersProp ?? null])
|
|
328
|
+
sortsProp !== undefined || filtersProp !== undefined || pageSizeProp !== undefined
|
|
329
|
+
? JSON.stringify([sortsProp ?? null, filtersProp ?? null, String(pageSizeProp)])
|
|
318
330
|
: null;
|
|
319
331
|
const [seenControlledSig, setSeenControlledSig] = useState(controlledSig);
|
|
320
332
|
if (seenControlledSig !== controlledSig) {
|
|
321
333
|
setSeenControlledSig(controlledSig);
|
|
322
334
|
setPage(0);
|
|
323
335
|
}
|
|
324
|
-
const [
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return defaultPageSize;
|
|
329
|
-
});
|
|
336
|
+
const [internalPageSize, setPageSize] = useState<number>(() =>
|
|
337
|
+
urlInitial.pageSize !== undefined ? urlInitial.pageSize : storedPageSize(initial, defaultPageSize),
|
|
338
|
+
);
|
|
339
|
+
const pageSize = pageSizeProp ?? internalPageSize;
|
|
330
340
|
const [widths, setWidths] = useState<Record<string, number>>(() => initial.widths ?? {});
|
|
331
341
|
const [hiddenCols, setHiddenCols] = useState<Set<string>>(() => new Set(initial.hidden ?? []));
|
|
332
342
|
const [showSettings, setShowSettings] = useState(false);
|
|
@@ -398,6 +408,77 @@ export function useTableState<T>({
|
|
|
398
408
|
};
|
|
399
409
|
}
|
|
400
410
|
|
|
411
|
+
// ---------- One view over several tables ----------
|
|
412
|
+
|
|
413
|
+
/** What {@link useSharedTableView} keeps. */
|
|
414
|
+
export interface SharedTableView {
|
|
415
|
+
sorts: SortState[];
|
|
416
|
+
setSorts: (next: SortState[]) => void;
|
|
417
|
+
/** `Infinity` for "all", as a table holds it. */
|
|
418
|
+
pageSize: number;
|
|
419
|
+
setPageSize: (next: number) => void;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* One table view — the sort and the page size — for an owner that draws it as SEVERAL
|
|
424
|
+
* tables over one `storageKey`: `TranslationReviewPanel`'s groups, which sort and page
|
|
425
|
+
* alike (0.35, Kurvenschmiede: its grouped review stored no view at all). Pass them to
|
|
426
|
+
* each table as `sorts`/`onSortsChange` and `pageSize`/`onPageSizeChange`.
|
|
427
|
+
*
|
|
428
|
+
* Kept in the very blob a `DataTable` with that `storageKey` keeps, not in a format of
|
|
429
|
+
* its own, so the single table and the groups read each other's view. What the blob
|
|
430
|
+
* holds besides (filters, column widths, hidden columns) is carried over as stored, for
|
|
431
|
+
* the single table to find as it left it. Read when `active` turns on — on mount, or
|
|
432
|
+
* when the owner switches to this view — and written on a change, never on a read. The
|
|
433
|
+
* prefix resolves as DataTable's does: prop > provider > the kit's.
|
|
434
|
+
*/
|
|
435
|
+
export function useSharedTableView({
|
|
436
|
+
active,
|
|
437
|
+
storageKey,
|
|
438
|
+
storageKeyPrefix,
|
|
439
|
+
defaultPageSize,
|
|
440
|
+
}: {
|
|
441
|
+
active: boolean;
|
|
442
|
+
storageKey?: string;
|
|
443
|
+
storageKeyPrefix?: string;
|
|
444
|
+
defaultPageSize: number;
|
|
445
|
+
}): SharedTableView {
|
|
446
|
+
const providerPrefix = useKitTableStorageKeyPrefix();
|
|
447
|
+
const prefix = storageKeyPrefix ?? providerPrefix ?? DEFAULT_PERSIST_PREFIX;
|
|
448
|
+
const read = () => {
|
|
449
|
+
const stored = active ? loadPersisted(storageKey, prefix) : {};
|
|
450
|
+
return { sorts: normalizeSorts(stored.sort), pageSize: storedPageSize(stored, defaultPageSize) };
|
|
451
|
+
};
|
|
452
|
+
const [view, setView] = useState(read);
|
|
453
|
+
// Adjusted during render when `active` turns on, so the first render of the groups
|
|
454
|
+
// already has the view the single table left, not a frame of the default.
|
|
455
|
+
const [wasActive, setWasActive] = useState(active);
|
|
456
|
+
if (active !== wasActive) {
|
|
457
|
+
setWasActive(active);
|
|
458
|
+
if (active) setView(read());
|
|
459
|
+
}
|
|
460
|
+
const change = (patch: Partial<typeof view>) => {
|
|
461
|
+
const next = { ...view, ...patch };
|
|
462
|
+
setView(next);
|
|
463
|
+
if (!active || !storageKey) return;
|
|
464
|
+
const stored = loadPersisted(storageKey, prefix);
|
|
465
|
+
const blob: PersistedState = {
|
|
466
|
+
sort: next.sorts,
|
|
467
|
+
filters: stored.filters ?? {},
|
|
468
|
+
pageSize: next.pageSize === Infinity ? "all" : next.pageSize,
|
|
469
|
+
widths: stored.widths,
|
|
470
|
+
hidden: stored.hidden,
|
|
471
|
+
};
|
|
472
|
+
writeStored(prefix + storageKey, serializePersisted(blob));
|
|
473
|
+
};
|
|
474
|
+
return {
|
|
475
|
+
sorts: view.sorts,
|
|
476
|
+
setSorts: (sorts) => change({ sorts }),
|
|
477
|
+
pageSize: view.pageSize,
|
|
478
|
+
setPageSize: (pageSize) => change({ pageSize }),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
401
482
|
// ---------- Owner-held table state, mirrored to the URL ----------
|
|
402
483
|
|
|
403
484
|
export interface UseTableUrlStateOptions<T> {
|
|
@@ -69,9 +69,9 @@ export interface UserAvatarProps extends Omit<ComponentPropsWithRef<"span">, "ch
|
|
|
69
69
|
* in the surface colour so it reads as its own mark (keksdose account-menu ~385,
|
|
70
70
|
* top-bar :152). Its `label` is read as text where the dot sits — inside a button
|
|
71
71
|
* named by its content it joins that name. In `TopBarActionMenu`'s `trigger` the
|
|
72
|
-
* button is named by the menu's `
|
|
72
|
+
* button is named by the menu's `aria-label` followed by this label: "Account menu
|
|
73
73
|
* 3 unread". The initials stay hidden either way, so the person's name is NOT part
|
|
74
|
-
* of it — put it in `
|
|
74
|
+
* of it — put it in `aria-label` ("Marcel Eifert, account menu") if it should be.
|
|
75
75
|
*/
|
|
76
76
|
badge?: UserAvatarBadge | null;
|
|
77
77
|
}
|
|
@@ -5,16 +5,12 @@ import { cn } from "../lib/cn";
|
|
|
5
5
|
export function WizardStepper({
|
|
6
6
|
steps,
|
|
7
7
|
current,
|
|
8
|
-
"aria-label":
|
|
9
|
-
ariaLabel,
|
|
8
|
+
"aria-label": ariaLabel,
|
|
10
9
|
}: {
|
|
11
10
|
steps: { key: string; label: string }[];
|
|
12
11
|
current: string;
|
|
13
12
|
/** Names the list of steps ("Import steps"). */
|
|
14
13
|
"aria-label"?: string;
|
|
15
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
16
|
-
* Names the steps only when `aria-label` is absent. */
|
|
17
|
-
ariaLabel?: string;
|
|
18
14
|
}) {
|
|
19
15
|
const currentIndex = steps.findIndex((s) => s.key === current);
|
|
20
16
|
return (
|
|
@@ -22,7 +18,7 @@ export function WizardStepper({
|
|
|
22
18
|
// (0.32.1, the 360 px Extra-large sweep; docs/text-size-harmonization.md §4).
|
|
23
19
|
// "Upload — Map columns — Confirm" ran 22 px past a 360 px phone at Extra large.
|
|
24
20
|
// Where the steps fit, they stay one row.
|
|
25
|
-
<ol className="flex flex-wrap items-center gap-1 text-xs" aria-label={
|
|
21
|
+
<ol className="flex flex-wrap items-center gap-1 text-xs" aria-label={ariaLabel}>
|
|
26
22
|
{steps.map((step, i) => (
|
|
27
23
|
<li key={step.key} className="flex items-center gap-1">
|
|
28
24
|
{i > 0 && <span className="w-4 border-t border-subtle" />}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// `@eifi1/ui-kit/currencies` — the currencies the kit's pickers offer, as plain data
|
|
2
|
+
// (0.35, keksdose's 0.34 report).
|
|
3
|
+
//
|
|
4
|
+
// This module imports NOTHING: no React, no stylesheet, no other kit module. That is the
|
|
5
|
+
// entry's whole promise, because an app's BUILD reads it in Node — keksdose's Vite plugin
|
|
6
|
+
// builds its flag-icons subset from these country codes while Vite loads its config. Until
|
|
7
|
+
// 0.35 that plugin parsed the `country` literals out of dist/components/currency-select.js;
|
|
8
|
+
// 0.34 moved the list into the picker's option list and 91 of its test files failed at
|
|
9
|
+
// load. A file inside dist/ is not something the kit promises; an entry is.
|
|
10
|
+
// packaging-contract.test.ts pins the purity, scripts/verify-package.mjs the load in Node.
|
|
11
|
+
//
|
|
12
|
+
// CurrencySelect, AmountInput's currency chip and NumberInput's code detection read this
|
|
13
|
+
// same array (components/currency-option-list re-exports it, and currency-select as it
|
|
14
|
+
// always has), so the list the pickers show and the flags an app ships cannot drift.
|
|
15
|
+
|
|
16
|
+
export interface CurrencyOption {
|
|
17
|
+
code: string;
|
|
18
|
+
name: string;
|
|
19
|
+
symbol: string;
|
|
20
|
+
/** ISO 3166-1 alpha-2 country code (lowercase) used by flag-icons. */
|
|
21
|
+
country: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const CURRENCIES: CurrencyOption[] = [
|
|
25
|
+
{ code: "EUR", name: "Euro", symbol: "€", country: "eu" },
|
|
26
|
+
{ code: "USD", name: "US Dollar", symbol: "$", country: "us" },
|
|
27
|
+
{ code: "GBP", name: "British Pound", symbol: "£", country: "gb" },
|
|
28
|
+
{ code: "CHF", name: "Swiss Franc", symbol: "CHF", country: "ch" },
|
|
29
|
+
{ code: "JPY", name: "Japanese Yen", symbol: "¥", country: "jp" },
|
|
30
|
+
{ code: "CAD", name: "Canadian Dollar", symbol: "CA$", country: "ca" },
|
|
31
|
+
{ code: "AUD", name: "Australian Dollar", symbol: "A$", country: "au" },
|
|
32
|
+
{ code: "NZD", name: "New Zealand Dollar", symbol: "NZ$", country: "nz" },
|
|
33
|
+
{ code: "SEK", name: "Swedish Krona", symbol: "kr", country: "se" },
|
|
34
|
+
{ code: "NOK", name: "Norwegian Krone", symbol: "kr", country: "no" },
|
|
35
|
+
{ code: "DKK", name: "Danish Krone", symbol: "kr", country: "dk" },
|
|
36
|
+
{ code: "PLN", name: "Polish Zloty", symbol: "zł", country: "pl" },
|
|
37
|
+
{ code: "CZK", name: "Czech Koruna", symbol: "Kč", country: "cz" },
|
|
38
|
+
{ code: "HUF", name: "Hungarian Forint", symbol: "Ft", country: "hu" },
|
|
39
|
+
{ code: "RON", name: "Romanian Leu", symbol: "lei", country: "ro" },
|
|
40
|
+
{ code: "BGN", name: "Bulgarian Lev", symbol: "лв", country: "bg" },
|
|
41
|
+
{ code: "TRY", name: "Turkish Lira", symbol: "₺", country: "tr" },
|
|
42
|
+
{ code: "RUB", name: "Russian Ruble", symbol: "₽", country: "ru" },
|
|
43
|
+
{ code: "CNY", name: "Chinese Yuan", symbol: "¥", country: "cn" },
|
|
44
|
+
{ code: "HKD", name: "Hong Kong Dollar", symbol: "HK$", country: "hk" },
|
|
45
|
+
{ code: "SGD", name: "Singapore Dollar", symbol: "S$", country: "sg" },
|
|
46
|
+
{ code: "INR", name: "Indian Rupee", symbol: "₹", country: "in" },
|
|
47
|
+
{ code: "BRL", name: "Brazilian Real", symbol: "R$", country: "br" },
|
|
48
|
+
{ code: "MXN", name: "Mexican Peso", symbol: "MX$", country: "mx" },
|
|
49
|
+
{ code: "ZAR", name: "South African Rand", symbol: "R", country: "za" },
|
|
50
|
+
{ code: "ILS", name: "Israeli Shekel", symbol: "₪", country: "il" },
|
|
51
|
+
{ code: "AED", name: "UAE Dirham", symbol: "د.إ", country: "ae" },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Each currency code → the flag the kit paints for it: the lowercase ISO 3166-1 alpha-2
|
|
56
|
+
* code flag-icons names it by (`fi-ch`, `flags/4x3/ch.svg`). `CHF` → `"ch"`, `EUR` →
|
|
57
|
+
* `"eu"` (flag-icons has the EU's flag under that code).
|
|
58
|
+
*
|
|
59
|
+
* Derived from {@link CURRENCIES}, so a currency the pickers gain is a flag here too. An
|
|
60
|
+
* app that ships only the flags it renders builds its subset from these values:
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* // vite.config.ts — plain Node, no DOM
|
|
64
|
+
* import { CURRENCY_COUNTRIES } from "@eifi1/ui-kit/currencies";
|
|
65
|
+
* const flags = new Set(Object.values(CURRENCY_COUNTRIES));
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export const CURRENCY_COUNTRIES: Record<string, string> = Object.fromEntries(
|
|
69
|
+
CURRENCIES.map((c) => [c.code, c.country]),
|
|
70
|
+
);
|
package/src/demo/demo-banner.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ComponentPropsWithRef } from "react";
|
|
3
3
|
import { ChevronDown, ChevronUp, Timer } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
@@ -30,7 +30,12 @@ function timeText(countdown: DemoCountdown, labels: DemoLabels): string {
|
|
|
30
30
|
: labels.hoursLeft(countdown.hours, countdown.minutes);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* A `div`'s props, `ref` included since 0.35 (round 0.35 R4, the follow-up to API-2). They
|
|
35
|
+
* go on to the {@link AlertBanner} strip, so the ref is the strip's own `<div>` — never a
|
|
36
|
+
* link or a button: the banner takes no `href` or `onClick` of its own.
|
|
37
|
+
*/
|
|
38
|
+
export interface DemoBannerProps extends Omit<ComponentPropsWithRef<"div">, "children" | "onClick"> {
|
|
34
39
|
/** The demo's end: `expires_at` from the `TokenResponse`, or `demo_expires_at` from
|
|
35
40
|
* `/auth/me` (§5.3). */
|
|
36
41
|
expiresAt: DemoExpiry | null | undefined;
|