@adecore/ui 0.14.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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/AccentSwatches.d.ts +15 -0
- package/dist/AccentSwatches.js +21 -0
- package/dist/Banner.d.ts +12 -0
- package/dist/Banner.js +18 -0
- package/dist/Button.d.ts +9 -0
- package/dist/Button.js +28 -0
- package/dist/ButtonGroup.d.ts +3 -0
- package/dist/ButtonGroup.js +11 -0
- package/dist/Checkbox.d.ts +11 -0
- package/dist/Checkbox.js +9 -0
- package/dist/ChoiceCards.d.ts +23 -0
- package/dist/ChoiceCards.js +21 -0
- package/dist/CloseButton.d.ts +15 -0
- package/dist/CloseButton.js +11 -0
- package/dist/ColorSwatch.d.ts +14 -0
- package/dist/ColorSwatch.js +28 -0
- package/dist/ColumnResizeHandle.d.ts +9 -0
- package/dist/ColumnResizeHandle.js +12 -0
- package/dist/DisabledReason.d.ts +6 -0
- package/dist/DisabledReason.js +8 -0
- package/dist/DockShell.d.ts +9 -0
- package/dist/DockShell.js +49 -0
- package/dist/EmptyState.d.ts +16 -0
- package/dist/EmptyState.js +11 -0
- package/dist/ErrorBoundary.d.ts +24 -0
- package/dist/ErrorBoundary.js +47 -0
- package/dist/Field.d.ts +17 -0
- package/dist/Field.js +48 -0
- package/dist/FileIcon.d.ts +8 -0
- package/dist/FileIcon.js +15 -0
- package/dist/Icon.d.ts +9 -0
- package/dist/Icon.js +12 -0
- package/dist/IconButton.d.ts +23 -0
- package/dist/IconButton.js +25 -0
- package/dist/IconPicker.d.ts +17 -0
- package/dist/IconPicker.js +93 -0
- package/dist/Input.d.ts +14 -0
- package/dist/Input.js +25 -0
- package/dist/Kbd.d.ts +23 -0
- package/dist/Kbd.js +20 -0
- package/dist/ListRow.d.ts +10 -0
- package/dist/ListRow.js +20 -0
- package/dist/Meter.d.ts +12 -0
- package/dist/Meter.js +7 -0
- package/dist/PanelEmpty.d.ts +16 -0
- package/dist/PanelEmpty.js +7 -0
- package/dist/PanelHeader.d.ts +6 -0
- package/dist/PanelHeader.js +20 -0
- package/dist/Pill.d.ts +27 -0
- package/dist/Pill.js +25 -0
- package/dist/ProjectSwitcher.d.ts +30 -0
- package/dist/ProjectSwitcher.js +18 -0
- package/dist/PromptDialog.d.ts +40 -0
- package/dist/PromptDialog.js +47 -0
- package/dist/SectionLabel.d.ts +3 -0
- package/dist/SectionLabel.js +16 -0
- package/dist/SegmentBar.d.ts +18 -0
- package/dist/SegmentBar.js +44 -0
- package/dist/Segmented.d.ts +17 -0
- package/dist/Segmented.js +7 -0
- package/dist/Select.d.ts +27 -0
- package/dist/Select.js +34 -0
- package/dist/Separator.d.ts +7 -0
- package/dist/Separator.js +8 -0
- package/dist/ShortcutHints.d.ts +1 -0
- package/dist/ShortcutHints.js +94 -0
- package/dist/Skeleton.d.ts +6 -0
- package/dist/Skeleton.js +6 -0
- package/dist/SlidingColumn.d.ts +19 -0
- package/dist/SlidingColumn.js +41 -0
- package/dist/Spinner.d.ts +6 -0
- package/dist/Spinner.js +7 -0
- package/dist/Stepper.d.ts +14 -0
- package/dist/Stepper.js +18 -0
- package/dist/Surface.d.ts +3 -0
- package/dist/Surface.js +16 -0
- package/dist/Switch.d.ts +10 -0
- package/dist/Switch.js +7 -0
- package/dist/TextMenu.d.ts +5 -0
- package/dist/TextMenu.js +31 -0
- package/dist/Tile.d.ts +11 -0
- package/dist/Tile.js +10 -0
- package/dist/Toasts.d.ts +9 -0
- package/dist/Toasts.js +58 -0
- package/dist/Tooltip.d.ts +16 -0
- package/dist/Tooltip.js +21 -0
- package/dist/UIProvider.d.ts +9 -0
- package/dist/UIProvider.js +26 -0
- package/dist/Waveform.d.ts +16 -0
- package/dist/Waveform.js +123 -0
- package/dist/Wipe.d.ts +12 -0
- package/dist/Wipe.js +47 -0
- package/dist/ZoomControls.d.ts +30 -0
- package/dist/ZoomControls.js +16 -0
- package/dist/class-name.d.ts +2 -0
- package/dist/class-name.js +3 -0
- package/dist/clipboard.d.ts +2 -0
- package/dist/clipboard.js +16 -0
- package/dist/context-menu/index.parts.d.ts +1 -0
- package/dist/context-menu/index.parts.js +2 -0
- package/dist/dialog/index.parts.d.ts +1 -0
- package/dist/dialog/index.parts.js +1 -0
- package/dist/dialog/parts.d.ts +36 -0
- package/dist/dialog/parts.js +55 -0
- package/dist/dialog-layer.d.ts +1 -0
- package/dist/dialog-layer.js +27 -0
- package/dist/error-boundary.d.ts +4 -0
- package/dist/error-boundary.js +34 -0
- package/dist/error-message.d.ts +1 -0
- package/dist/error-message.js +4 -0
- package/dist/field-context.d.ts +7 -0
- package/dist/field-context.js +4 -0
- package/dist/file-icon.d.ts +4 -0
- package/dist/file-icon.js +46 -0
- package/dist/floating.d.ts +5 -0
- package/dist/floating.js +6 -0
- package/dist/format/datetime.d.ts +11 -0
- package/dist/format/datetime.js +50 -0
- package/dist/format/duration.d.ts +5 -0
- package/dist/format/duration.js +76 -0
- package/dist/format/index.d.ts +6 -0
- package/dist/format/index.js +7 -0
- package/dist/format/locale.d.ts +17 -0
- package/dist/format/locale.js +105 -0
- package/dist/format/number.d.ts +8 -0
- package/dist/format/number.js +72 -0
- package/dist/format/regions.d.ts +6 -0
- package/dist/format/regions.js +15 -0
- package/dist/format/time-zone.d.ts +1 -0
- package/dist/format/time-zone.js +2 -0
- package/dist/icon-button-size.d.ts +3 -0
- package/dist/icon-button-size.js +3 -0
- package/dist/icon-picker.d.ts +23 -0
- package/dist/icon-picker.js +80 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +74 -0
- package/dist/lazy.d.ts +12 -0
- package/dist/lazy.js +104 -0
- package/dist/locales/en.json +59 -0
- package/dist/locales/nl.json +59 -0
- package/dist/locales.d.ts +4 -0
- package/dist/locales.js +17 -0
- package/dist/menu/index.parts.d.ts +1 -0
- package/dist/menu/index.parts.js +1 -0
- package/dist/menu/parts.d.ts +67 -0
- package/dist/menu/parts.js +113 -0
- package/dist/merge-refs.d.ts +2 -0
- package/dist/merge-refs.js +11 -0
- package/dist/modality.d.ts +1 -0
- package/dist/modality.js +29 -0
- package/dist/platform.d.ts +2 -0
- package/dist/platform.js +9 -0
- package/dist/popover/index.parts.d.ts +1 -0
- package/dist/popover/index.parts.js +1 -0
- package/dist/popover/parts.d.ts +26 -0
- package/dist/popover/parts.js +28 -0
- package/dist/prefetch.d.ts +21 -0
- package/dist/prefetch.js +99 -0
- package/dist/preview-card/index.parts.d.ts +1 -0
- package/dist/preview-card/index.parts.js +1 -0
- package/dist/project-switcher-row.d.ts +6 -0
- package/dist/project-switcher-row.js +16 -0
- package/dist/selection.d.ts +2 -0
- package/dist/selection.js +22 -0
- package/dist/seti-icons.d.ts +5 -0
- package/dist/seti-icons.js +445 -0
- package/dist/settings/ConfirmDialog.d.ts +9 -0
- package/dist/settings/ConfirmDialog.js +9 -0
- package/dist/settings/DetailHeader.d.ts +10 -0
- package/dist/settings/DetailHeader.js +6 -0
- package/dist/settings/MasterDetail.d.ts +24 -0
- package/dist/settings/MasterDetail.js +16 -0
- package/dist/settings/SettingsDialog.d.ts +46 -0
- package/dist/settings/SettingsDialog.js +96 -0
- package/dist/settings/SettingsRow.d.ts +22 -0
- package/dist/settings/SettingsRow.js +30 -0
- package/dist/settings/SettingsSection.d.ts +14 -0
- package/dist/settings/SettingsSection.js +8 -0
- package/dist/settings/index.d.ts +7 -0
- package/dist/settings/index.js +8 -0
- package/dist/settings/target.d.ts +7 -0
- package/dist/settings/target.js +3 -0
- package/dist/shortcut-hints.d.ts +38 -0
- package/dist/shortcut-hints.js +81 -0
- package/dist/shortcut.d.ts +32 -0
- package/dist/shortcut.js +137 -0
- package/dist/testing/dedupe.d.ts +1 -0
- package/dist/testing/dedupe.js +73 -0
- package/dist/testing/fake-source.d.ts +8 -0
- package/dist/testing/fake-source.js +15 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/theme.css +562 -0
- package/dist/toast-store.d.ts +38 -0
- package/dist/toast-store.js +61 -0
- package/dist/useAsyncAction.d.ts +8 -0
- package/dist/useAsyncAction.js +23 -0
- package/dist/useColumnResize.d.ts +14 -0
- package/dist/useColumnResize.js +49 -0
- package/dist/useContentSize.d.ts +5 -0
- package/dist/useContentSize.js +21 -0
- package/dist/useMeasuredWidth.d.ts +1 -0
- package/dist/useMeasuredWidth.js +6 -0
- package/dist/useNow.d.ts +3 -0
- package/dist/useNow.js +41 -0
- package/dist/wipe-split.d.ts +7 -0
- package/dist/wipe-split.js +20 -0
- package/dist/zoom.d.ts +3 -0
- package/dist/zoom.js +6 -0
- package/package.json +92 -0
- package/src/AccentSwatches.tsx +73 -0
- package/src/Banner.tsx +43 -0
- package/src/Button.tsx +55 -0
- package/src/ButtonGroup.tsx +14 -0
- package/src/Checkbox.tsx +44 -0
- package/src/ChoiceCards.tsx +108 -0
- package/src/CloseButton.tsx +21 -0
- package/src/ColorSwatch.tsx +41 -0
- package/src/ColumnResizeHandle.tsx +23 -0
- package/src/DisabledReason.tsx +23 -0
- package/src/DockShell.tsx +76 -0
- package/src/EmptyState.tsx +48 -0
- package/src/ErrorBoundary.tsx +116 -0
- package/src/Field.tsx +116 -0
- package/src/FileIcon.tsx +27 -0
- package/src/Icon.tsx +22 -0
- package/src/IconButton.tsx +84 -0
- package/src/IconPicker.tsx +257 -0
- package/src/Input.tsx +78 -0
- package/src/Kbd.tsx +67 -0
- package/src/ListRow.tsx +26 -0
- package/src/Meter.tsx +41 -0
- package/src/PanelEmpty.tsx +37 -0
- package/src/PanelHeader.tsx +31 -0
- package/src/Pill.tsx +61 -0
- package/src/ProjectSwitcher.tsx +127 -0
- package/src/PromptDialog.tsx +168 -0
- package/src/SectionLabel.tsx +20 -0
- package/src/SegmentBar.tsx +128 -0
- package/src/Segmented.tsx +51 -0
- package/src/Select.tsx +135 -0
- package/src/Separator.tsx +15 -0
- package/src/ShortcutHints.tsx +118 -0
- package/src/Skeleton.tsx +13 -0
- package/src/SlidingColumn.tsx +91 -0
- package/src/Spinner.tsx +32 -0
- package/src/Stepper.tsx +67 -0
- package/src/Surface.tsx +20 -0
- package/src/Switch.tsx +32 -0
- package/src/TextMenu.tsx +59 -0
- package/src/Tile.tsx +49 -0
- package/src/Toasts.tsx +151 -0
- package/src/Tooltip.tsx +70 -0
- package/src/UIProvider.tsx +41 -0
- package/src/Waveform.tsx +199 -0
- package/src/Wipe.tsx +99 -0
- package/src/ZoomControls.tsx +99 -0
- package/src/class-name.ts +8 -0
- package/src/clipboard.ts +17 -0
- package/src/context-menu/index.parts.ts +21 -0
- package/src/dialog/index.parts.ts +10 -0
- package/src/dialog/parts.tsx +112 -0
- package/src/dialog-layer.ts +31 -0
- package/src/error-boundary.ts +40 -0
- package/src/error-message.ts +5 -0
- package/src/field-context.ts +12 -0
- package/src/file-icon.ts +52 -0
- package/src/floating.ts +9 -0
- package/src/format/datetime.ts +63 -0
- package/src/format/duration.ts +83 -0
- package/src/format/index.ts +20 -0
- package/src/format/locale.ts +153 -0
- package/src/format/number.ts +87 -0
- package/src/format/regions.ts +20 -0
- package/src/format/time-zone.ts +2 -0
- package/src/icon-button-size.ts +6 -0
- package/src/icon-picker.ts +112 -0
- package/src/index.ts +103 -0
- package/src/lazy.tsx +124 -0
- package/src/locales/en.json +59 -0
- package/src/locales/nl.json +59 -0
- package/src/locales.ts +21 -0
- package/src/menu/index.parts.ts +20 -0
- package/src/menu/parts.tsx +296 -0
- package/src/merge-refs.ts +14 -0
- package/src/modality.ts +37 -0
- package/src/platform.ts +10 -0
- package/src/popover/index.parts.ts +8 -0
- package/src/popover/parts.tsx +119 -0
- package/src/prefetch.ts +113 -0
- package/src/preview-card/index.parts.ts +1 -0
- package/src/project-switcher-row.tsx +51 -0
- package/src/selection.ts +24 -0
- package/src/seti-icons.ts +451 -0
- package/src/settings/ConfirmDialog.tsx +30 -0
- package/src/settings/DetailHeader.tsx +25 -0
- package/src/settings/MasterDetail.tsx +67 -0
- package/src/settings/SettingsDialog.tsx +342 -0
- package/src/settings/SettingsRow.tsx +89 -0
- package/src/settings/SettingsSection.tsx +46 -0
- package/src/settings/index.ts +16 -0
- package/src/settings/target.ts +12 -0
- package/src/shortcut-hints.ts +117 -0
- package/src/shortcut.ts +170 -0
- package/src/testing/dedupe.ts +93 -0
- package/src/testing/fake-source.ts +21 -0
- package/src/testing/index.ts +3 -0
- package/src/theme.css +562 -0
- package/src/toast-store.ts +120 -0
- package/src/useAsyncAction.ts +32 -0
- package/src/useColumnResize.ts +75 -0
- package/src/useContentSize.ts +27 -0
- package/src/useMeasuredWidth.ts +7 -0
- package/src/useNow.ts +49 -0
- package/src/wipe-split.ts +24 -0
- package/src/zoom.ts +9 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { numberFormatter } from './locale.ts';
|
|
2
|
+
|
|
3
|
+
const WHOLE: Intl.NumberFormatOptions = { maximumFractionDigits: 0 };
|
|
4
|
+
const ONE_DECIMAL: Intl.NumberFormatOptions = { maximumFractionDigits: 1 };
|
|
5
|
+
|
|
6
|
+
/* A count as a region groups one: `1,234,567` there, `1.234.567` here. */
|
|
7
|
+
export const formatNumber = (value: number): string => numberFormatter(WHOLE).format(Math.round(value));
|
|
8
|
+
|
|
9
|
+
/* The same, to one decimal, where the fraction is the part that means something. */
|
|
10
|
+
export const formatDecimal = (value: number): string => numberFormatter(ONE_DECIMAL).format(value);
|
|
11
|
+
|
|
12
|
+
const fixedSpecs = new Map<number, Intl.NumberFormatOptions>();
|
|
13
|
+
|
|
14
|
+
/* Always `decimals` places, so values that follow each other keep one width: `1,0` and `1,5`, never `1` and `1,5`. */
|
|
15
|
+
export const formatFixed = (value: number, decimals: number): string => {
|
|
16
|
+
let spec = fixedSpecs.get(decimals);
|
|
17
|
+
if (spec === undefined) {
|
|
18
|
+
spec = { minimumFractionDigits: decimals, maximumFractionDigits: decimals };
|
|
19
|
+
fixedSpecs.set(decimals, spec);
|
|
20
|
+
}
|
|
21
|
+
return numberFormatter(spec).format(value);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/* Percent as Activity Monitor writes it: a decimal under ten, where the difference still shows. */
|
|
25
|
+
export const formatPercent = (value: number): string => `${value < 10 ? formatDecimal(value) : formatNumber(value)}%`;
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* The option objects a money formatter is cached on, one per currency and precision. The cache is
|
|
29
|
+
* keyed on the object itself, so building a fresh one per call would build a fresh formatter too.
|
|
30
|
+
*/
|
|
31
|
+
const moneySpecs = new Map<string, Intl.NumberFormatOptions>();
|
|
32
|
+
|
|
33
|
+
const moneySpec = (currency: string, maximumFractionDigits: number | undefined): Intl.NumberFormatOptions => {
|
|
34
|
+
const key = `${currency}|${maximumFractionDigits ?? ''}`;
|
|
35
|
+
const known = moneySpecs.get(key);
|
|
36
|
+
if (known !== undefined) {
|
|
37
|
+
return known;
|
|
38
|
+
}
|
|
39
|
+
// Narrow, or a Dutch region writes a dollar amount as `US$ 5.480,96`, which is a currency lesson
|
|
40
|
+
// nobody asked for in a table of prices.
|
|
41
|
+
const spec: Intl.NumberFormatOptions = {
|
|
42
|
+
style: 'currency',
|
|
43
|
+
currency,
|
|
44
|
+
currencyDisplay: 'narrowSymbol',
|
|
45
|
+
...(maximumFractionDigits ? { maximumFractionDigits } : {})
|
|
46
|
+
};
|
|
47
|
+
moneySpecs.set(key, spec);
|
|
48
|
+
return spec;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/* An amount in the currency it is already in, as a bank statement would print it. */
|
|
52
|
+
export const formatMoney = (value: number, currency: string, maximumFractionDigits?: number): string =>
|
|
53
|
+
numberFormatter(moneySpec(currency, maximumFractionDigits)).format(value);
|
|
54
|
+
|
|
55
|
+
const SIGNIFICANT_MONEY: Intl.NumberFormatOptions = { style: 'currency', currency: 'USD', currencyDisplay: 'narrowSymbol', maximumSignificantDigits: 3 };
|
|
56
|
+
|
|
57
|
+
/* Dollars to three significant digits, for amounts that run from a fraction of a cent to tens of dollars in one list. */
|
|
58
|
+
export const formatUsdSignificant = (value: number): string => numberFormatter(SIGNIFICANT_MONEY).format(value);
|
|
59
|
+
|
|
60
|
+
/* `1.2M`, `412K`, `640`: a token count is read as a size, not counted. */
|
|
61
|
+
export const formatTokens = (value: number): string => {
|
|
62
|
+
const rounded = Math.round(value);
|
|
63
|
+
if (rounded >= 1_000_000) {
|
|
64
|
+
return `${formatDecimal(rounded >= 10_000_000 ? Math.round(rounded / 1_000_000) : rounded / 1_000_000)}M`;
|
|
65
|
+
}
|
|
66
|
+
if (rounded >= 1_000) {
|
|
67
|
+
return `${formatDecimal(rounded >= 10_000 ? Math.round(rounded / 1_000) : rounded / 1_000)}K`;
|
|
68
|
+
}
|
|
69
|
+
return formatNumber(rounded);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* A size the way a person says it, with the number in the region's own notation: whole bytes up to
|
|
76
|
+
* a kilobyte, one decimal above that. `whole` is for a column that only has room for a round number.
|
|
77
|
+
*/
|
|
78
|
+
export const formatBytes = (bytes: number, whole = false): string => {
|
|
79
|
+
let value = Math.max(0, bytes);
|
|
80
|
+
let unit = 0;
|
|
81
|
+
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
82
|
+
value /= 1024;
|
|
83
|
+
unit++;
|
|
84
|
+
}
|
|
85
|
+
const decimals = unit > 0 && !whole && value < 10;
|
|
86
|
+
return `${decimals ? formatDecimal(value) : formatNumber(value)} ${UNITS[unit]}`;
|
|
87
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The regions a person can read this app in, beside the one its language comes with and the one the
|
|
3
|
+
* operating system was set to. The list is short on purpose. It is here for a machine whose region
|
|
4
|
+
* reads nothing like the person in front of it, not as a country picker.
|
|
5
|
+
*/
|
|
6
|
+
export const FORMAT_LANGUAGE = 'language';
|
|
7
|
+
export const FORMAT_SYSTEM = 'system';
|
|
8
|
+
|
|
9
|
+
/* Only the tags. A country names itself in the language the interface is in (`Intl.DisplayNames`),
|
|
10
|
+
so no translation file ever carries a list of countries. */
|
|
11
|
+
export const FORMAT_REGIONS = ['nl-NL', 'en-US', 'en-GB', 'de-DE', 'fr-FR', 'es-ES', 'sv-SE', 'ja-JP'] as const;
|
|
12
|
+
|
|
13
|
+
export const FORMAT_REGION_CHOICES: readonly string[] = [FORMAT_LANGUAGE, FORMAT_SYSTEM, ...FORMAT_REGIONS];
|
|
14
|
+
|
|
15
|
+
/* The country of a region tag, named in `language`: `nl-NL` in English is "Netherlands". */
|
|
16
|
+
export const regionName = (region: string, language: string): string =>
|
|
17
|
+
new Intl.DisplayNames([language], { type: 'region' }).of(region.slice(region.indexOf('-') + 1)) ?? region;
|
|
18
|
+
|
|
19
|
+
/* A stored region, or the language's own for anything this version does not offer. */
|
|
20
|
+
export const formatRegionFrom = (stored: unknown): string => FORMAT_REGION_CHOICES.find((region) => region === stored) ?? FORMAT_LANGUAGE;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type IconButtonSize = 'md' | 'sm' | 'xs' | '2xs';
|
|
2
|
+
|
|
3
|
+
/* The size decides the square and the radius (`.icon-btn` in `theme.css`) and the icon inside: 16 in 32, 14 in 28, 12 below. */
|
|
4
|
+
export const ICON_BUTTON_CLASS: Record<IconButtonSize, string | undefined> = { md: undefined, sm: 'icon-btn-sm', xs: 'icon-btn-xs', '2xs': 'icon-btn-2xs' };
|
|
5
|
+
|
|
6
|
+
export const ICON_BUTTON_ICON_SIZE: Record<IconButtonSize, number> = { md: 16, sm: 14, xs: 12, '2xs': 12 };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { LucideIcon } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
/* A heading over part of the icons. The label arrives in the reader's language; the picker never translates it. */
|
|
4
|
+
export interface IconPickerGroup {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
icons: Readonly<Record<string, LucideIcon>>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type IconPickerIcons = Readonly<Record<string, LucideIcon>> | readonly IconPickerGroup[];
|
|
11
|
+
|
|
12
|
+
/* A group as the grid draws it: a flat set of icons is one group without a heading. */
|
|
13
|
+
export interface IconSection {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string | null;
|
|
16
|
+
icons: readonly (readonly [name: string, glyph: LucideIcon])[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Where a cell sits on the screen, in the units of `getBoundingClientRect`. */
|
|
20
|
+
export interface IconCellBox {
|
|
21
|
+
left: number;
|
|
22
|
+
top: number;
|
|
23
|
+
width: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const isGrouped = (icons: IconPickerIcons): icons is readonly IconPickerGroup[] => Array.isArray(icons);
|
|
27
|
+
|
|
28
|
+
export const sectionsOf = (icons: IconPickerIcons): IconSection[] =>
|
|
29
|
+
isGrouped(icons)
|
|
30
|
+
? icons.map((group) => ({ id: group.id, label: group.label, icons: Object.entries(group.icons) }))
|
|
31
|
+
: [{ id: '', label: null, icons: Object.entries(icons) }];
|
|
32
|
+
|
|
33
|
+
/* The icons whose name, keywords or group label hold the query, case-insensitive. A group without one is left out. */
|
|
34
|
+
export const filterSections = (sections: readonly IconSection[], query: string, keywords: Readonly<Record<string, readonly string[]>> = {}): IconSection[] => {
|
|
35
|
+
const needle = query.trim().toLowerCase();
|
|
36
|
+
if (needle === '') {
|
|
37
|
+
return [...sections];
|
|
38
|
+
}
|
|
39
|
+
const holds = (text: string): boolean => text.toLowerCase().includes(needle);
|
|
40
|
+
return sections.flatMap((section) => {
|
|
41
|
+
if (section.label !== null && holds(section.label)) {
|
|
42
|
+
return [section];
|
|
43
|
+
}
|
|
44
|
+
const icons = section.icons.filter(([name]) => holds(name) || (keywords[name] ?? []).some(holds));
|
|
45
|
+
return icons.length > 0 ? [{ ...section, icons }] : [];
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/* The one icon the grid's tab stop sits on: the one last focused, else the chosen one, else the first, as long as the search shows it. */
|
|
50
|
+
export const tabStopOf = (visible: readonly string[], focused: string | null, value: string | null): string | null => {
|
|
51
|
+
if (focused !== null && visible.includes(focused)) {
|
|
52
|
+
return focused;
|
|
53
|
+
}
|
|
54
|
+
if (value !== null && visible.includes(value)) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
return visible[0] ?? null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/* Rows lie a few pixels apart at least, so a rounding difference between two cells of one row is no new row. */
|
|
61
|
+
const SAME_ROW = 2;
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* The cell a key moves the focus to, or null for a key the grid leaves alone. Up and down go by what
|
|
65
|
+
* the eye sees: the nearest cell in the next row drawn, which may belong to another group. At an edge
|
|
66
|
+
* the focus stays, so the key never scrolls the grid instead.
|
|
67
|
+
*/
|
|
68
|
+
export const cellForKey = (key: string, boxes: readonly IconCellBox[], from: number): number | null => {
|
|
69
|
+
const last = boxes.length - 1;
|
|
70
|
+
switch (key) {
|
|
71
|
+
case 'ArrowRight':
|
|
72
|
+
return Math.min(from + 1, last);
|
|
73
|
+
case 'ArrowLeft':
|
|
74
|
+
return Math.max(from - 1, 0);
|
|
75
|
+
case 'Home':
|
|
76
|
+
return 0;
|
|
77
|
+
case 'End':
|
|
78
|
+
return last;
|
|
79
|
+
case 'ArrowDown':
|
|
80
|
+
case 'ArrowUp':
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const current = boxes[from];
|
|
86
|
+
if (current === undefined) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const down = key === 'ArrowDown';
|
|
90
|
+
const tops = boxes.map((box) => box.top).filter((top) => (down ? top > current.top + SAME_ROW : top < current.top - SAME_ROW));
|
|
91
|
+
if (tops.length === 0) {
|
|
92
|
+
return from;
|
|
93
|
+
}
|
|
94
|
+
const row = down ? Math.min(...tops) : Math.max(...tops);
|
|
95
|
+
const center = current.left + current.width / 2;
|
|
96
|
+
let nearest = from;
|
|
97
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
98
|
+
boxes.forEach((box, index) => {
|
|
99
|
+
if (Math.abs(box.top - row) > SAME_ROW) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const gap = Math.abs(box.left + box.width / 2 - center);
|
|
103
|
+
if (gap < distance) {
|
|
104
|
+
nearest = index;
|
|
105
|
+
distance = gap;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return nearest;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/* Escape empties a search that holds text and goes no further; on an empty one it is left to whatever else hears it, such as a dialog. */
|
|
112
|
+
export const escapeClearsSearch = (key: string, query: string): boolean => key === 'Escape' && query !== '';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The components, hooks and helpers of the library. Every name here is public API; the snapshot in
|
|
3
|
+
* `exports.test.ts` turns any change to this list into a diff someone has to accept.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * as ContextMenu from './context-menu/index.parts.ts';
|
|
7
|
+
export * as Dialog from './dialog/index.parts.ts';
|
|
8
|
+
export * as Menu from './menu/index.parts.ts';
|
|
9
|
+
export * as Popover from './popover/index.parts.ts';
|
|
10
|
+
export * as PreviewCard from './preview-card/index.parts.ts';
|
|
11
|
+
|
|
12
|
+
export type { DialogDescriptionProps, DialogFooterProps, DialogPopupProps, DialogTextProps, DialogTitleProps } from './dialog/parts.tsx';
|
|
13
|
+
export type {
|
|
14
|
+
MenuCheckProps,
|
|
15
|
+
MenuHintProps,
|
|
16
|
+
MenuLabelProps,
|
|
17
|
+
MenuPopupProps,
|
|
18
|
+
MenuRowActionProps,
|
|
19
|
+
MenuRowProps,
|
|
20
|
+
MenuRowSubmenuTriggerProps
|
|
21
|
+
} from './menu/parts.tsx';
|
|
22
|
+
export type { PopoverPopupProps, PopupVariant, PreviewCardPopupProps } from './popover/parts.tsx';
|
|
23
|
+
|
|
24
|
+
export { AccentSwatches, type AccentSwatchesProps } from './AccentSwatches.tsx';
|
|
25
|
+
export { Banner, type BannerProps, type BannerTone } from './Banner.tsx';
|
|
26
|
+
export { Button, type ButtonProps, type ButtonSize, type ButtonVariant } from './Button.tsx';
|
|
27
|
+
export { ButtonGroup, type ButtonGroupProps } from './ButtonGroup.tsx';
|
|
28
|
+
export { Checkbox, type CheckboxProps } from './Checkbox.tsx';
|
|
29
|
+
export { ChoiceCards, type Choice, type ChoiceCardsProps } from './ChoiceCards.tsx';
|
|
30
|
+
export { CloseButton, type CloseButtonProps } from './CloseButton.tsx';
|
|
31
|
+
export { ColorSwatch, type ColorSwatchProps } from './ColorSwatch.tsx';
|
|
32
|
+
export { ColumnResizeHandle, type ColumnResizeHandleProps } from './ColumnResizeHandle.tsx';
|
|
33
|
+
export { DisabledReason, type DisabledReasonProps } from './DisabledReason.tsx';
|
|
34
|
+
export { DockShell, type DockShellProps } from './DockShell.tsx';
|
|
35
|
+
export { EmptyState, type EmptyStateProps } from './EmptyState.tsx';
|
|
36
|
+
export { ErrorBoundary, type ErrorBoundaryProps } from './ErrorBoundary.tsx';
|
|
37
|
+
export { Field, FieldHint, FormError, type FieldHintProps, type FieldProps, type FormErrorProps } from './Field.tsx';
|
|
38
|
+
export { FileIcon, type FileIconProps } from './FileIcon.tsx';
|
|
39
|
+
export { Icon, type IconProps } from './Icon.tsx';
|
|
40
|
+
export { IconButton, type IconButtonProps, type IconButtonSize } from './IconButton.tsx';
|
|
41
|
+
export { IconPicker, type IconPickerGroup, type IconPickerProps } from './IconPicker.tsx';
|
|
42
|
+
export { Input, TextArea, type InputProps, type TextAreaProps } from './Input.tsx';
|
|
43
|
+
export { Kbd, KeyCap, Keys, type KbdProps, type KeyCapProps, type KeysProps } from './Kbd.tsx';
|
|
44
|
+
export { ListRow, type ListRowProps } from './ListRow.tsx';
|
|
45
|
+
export { Meter, type MeterProps } from './Meter.tsx';
|
|
46
|
+
export { PanelEmpty, type PanelEmptyProps } from './PanelEmpty.tsx';
|
|
47
|
+
export { PanelHeader, type PanelHeaderProps } from './PanelHeader.tsx';
|
|
48
|
+
export { Pill, type PillProps } from './Pill.tsx';
|
|
49
|
+
export { PromptDialog, type PromptDialogProps } from './PromptDialog.tsx';
|
|
50
|
+
export { ProjectSwitcher, type ProjectSwitcherItem, type ProjectSwitcherProps } from './ProjectSwitcher.tsx';
|
|
51
|
+
export { SectionLabel, type SectionLabelProps } from './SectionLabel.tsx';
|
|
52
|
+
export { SegmentBar, type SegmentBarPart, type SegmentBarProps } from './SegmentBar.tsx';
|
|
53
|
+
export { Segmented, type SegmentedOption, type SegmentedProps } from './Segmented.tsx';
|
|
54
|
+
export { Select, type SelectGroup, type SelectItem, type SelectProps } from './Select.tsx';
|
|
55
|
+
export { Separator, type SeparatorProps } from './Separator.tsx';
|
|
56
|
+
export { ShortcutHints } from './ShortcutHints.tsx';
|
|
57
|
+
export { Skeleton, type SkeletonProps } from './Skeleton.tsx';
|
|
58
|
+
export { SlidingColumn, type SlidingColumnProps } from './SlidingColumn.tsx';
|
|
59
|
+
export { Spinner, type SpinnerProps } from './Spinner.tsx';
|
|
60
|
+
export { Stepper, type StepperProps } from './Stepper.tsx';
|
|
61
|
+
export { Surface, type SurfaceProps } from './Surface.tsx';
|
|
62
|
+
export { Switch, type SwitchProps } from './Switch.tsx';
|
|
63
|
+
export { TextMenu, type TextMenuProps } from './TextMenu.tsx';
|
|
64
|
+
export { Tile, type TileProps } from './Tile.tsx';
|
|
65
|
+
export { Toasts, type ToastsProps } from './Toasts.tsx';
|
|
66
|
+
export { Tooltip, TooltipProvider, type TooltipProps, type TooltipSide } from './Tooltip.tsx';
|
|
67
|
+
export { UIProvider, type UIProviderProps } from './UIProvider.tsx';
|
|
68
|
+
export { Waveform, type WaveformProps } from './Waveform.tsx';
|
|
69
|
+
export { Wipe, type WipeProps } from './Wipe.tsx';
|
|
70
|
+
export { ZoomControls, type ZoomControlsProps, type ZoomLabels, type ZoomSelection } from './ZoomControls.tsx';
|
|
71
|
+
|
|
72
|
+
export { copyText, readClipboardText } from './clipboard.ts';
|
|
73
|
+
export { messageOf } from './error-message.ts';
|
|
74
|
+
export type { ResetKeys } from './error-boundary.ts';
|
|
75
|
+
export { FILE_TREE_ICONS } from './file-icon.ts';
|
|
76
|
+
export { cameThroughPortal, isInFloatingLayer } from './floating.ts';
|
|
77
|
+
export { LoadedComponent, lazyDialog, lazyNamed, onLazyOpenError } from './lazy.tsx';
|
|
78
|
+
export { UI_NAMESPACE, UI_RESOURCES, addUiResources } from './locales.ts';
|
|
79
|
+
export { startInputModality } from './modality.ts';
|
|
80
|
+
export { isApplePlatform } from './platform.ts';
|
|
81
|
+
export { Prefetcher, prefetcher, type Loader, type WhenIdle } from './prefetch.ts';
|
|
82
|
+
export { selectAllWithin, selectionWithin } from './selection.ts';
|
|
83
|
+
export { EDIT_SHORTCUTS, KEY_SHORTCUTS, formatShortcut, isModHeld, matchesShortcut, shortcut, shortcutParts, type KeyLike, type Shortcut } from './shortcut.ts';
|
|
84
|
+
export {
|
|
85
|
+
SUCCESS_MS,
|
|
86
|
+
UNDO_MS,
|
|
87
|
+
createToastStore,
|
|
88
|
+
elapsedOf,
|
|
89
|
+
type Toast,
|
|
90
|
+
type ToastAction,
|
|
91
|
+
type ToastDeadline,
|
|
92
|
+
type ToastInput,
|
|
93
|
+
type ToastKind,
|
|
94
|
+
type ToastPatch,
|
|
95
|
+
type ToastStore,
|
|
96
|
+
type ToastStoreHook
|
|
97
|
+
} from './toast-store.ts';
|
|
98
|
+
export { useAsyncAction, type AsyncAction } from './useAsyncAction.ts';
|
|
99
|
+
export { clampColumnSize, useColumnResize, type ColumnEdge, type ColumnResize, type ColumnResizeOptions } from './useColumnResize.ts';
|
|
100
|
+
export { useContentSize, type ContentSize } from './useContentSize.ts';
|
|
101
|
+
export { useMeasuredWidth } from './useMeasuredWidth.ts';
|
|
102
|
+
export { useNow, useTickingText } from './useNow.ts';
|
|
103
|
+
export { ZOOM_PRESETS } from './zoom.ts';
|
package/src/lazy.tsx
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Suspense, lazy, useState, useSyncExternalStore, type ComponentProps, type ComponentType } from 'react';
|
|
2
|
+
import { prefetcher } from './prefetch.ts';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* A module's component, loaded once and kept. `React.lazy` suspends for a tick even on a module the
|
|
6
|
+
* prefetcher already has, which shows the fallback and mounts a dialog already open; a component
|
|
7
|
+
* that is here is drawn straight away instead.
|
|
8
|
+
*/
|
|
9
|
+
export class LoadedComponent<Component> {
|
|
10
|
+
current: Component | null = null;
|
|
11
|
+
private readonly listeners = new Set<() => void>();
|
|
12
|
+
private readonly loader: () => Promise<Component>;
|
|
13
|
+
|
|
14
|
+
constructor(loader: () => Promise<Component>) {
|
|
15
|
+
this.loader = loader;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async load(): Promise<Component> {
|
|
19
|
+
const component = await this.loader();
|
|
20
|
+
if (this.current === null) {
|
|
21
|
+
this.current = component;
|
|
22
|
+
for (const listener of this.listeners) {
|
|
23
|
+
listener();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return component;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
subscribe(listener: () => void): () => void {
|
|
30
|
+
this.listeners.add(listener);
|
|
31
|
+
return () => {
|
|
32
|
+
this.listeners.delete(listener);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const openErrorListeners = new Set<(error: unknown) => void>();
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* Tells `listener` when a module that `lazyNamed` or `lazyDialog` loads for a render fails to load,
|
|
41
|
+
* which is a load a person waits on. A failed prefetch never reaches it, not even one of the same
|
|
42
|
+
* module at the same moment, so an app can answer the one without being woken by the other.
|
|
43
|
+
*/
|
|
44
|
+
export function onLazyOpenError(listener: (error: unknown) => void): () => void {
|
|
45
|
+
openErrorListeners.add(listener);
|
|
46
|
+
return () => {
|
|
47
|
+
openErrorListeners.delete(listener);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Only a render loads through here; the prefetcher calls `loaded.load()` itself, so its failures report nothing. */
|
|
52
|
+
async function open<Component>(loaded: LoadedComponent<Component>): Promise<{ default: Component }> {
|
|
53
|
+
try {
|
|
54
|
+
return { default: await loaded.load() };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
for (const listener of openErrorListeners) {
|
|
57
|
+
listener(error);
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* `React.lazy` for a module that exports its component by name, `default` included. Every module
|
|
65
|
+
* loaded through here, or through `lazyDialog`, registers with `prefetcher`, which loads it ahead of
|
|
66
|
+
* its first opening once the app calls `prefetcher.prefetchEverything()`.
|
|
67
|
+
*/
|
|
68
|
+
export function lazyNamed<Module extends Record<Name, ComponentType<any>>, Name extends keyof Module>(
|
|
69
|
+
load: () => Promise<Module>,
|
|
70
|
+
name: Name
|
|
71
|
+
): ComponentType<ComponentProps<Module[Name]>> {
|
|
72
|
+
const loaded = new LoadedComponent(async () => (await load())[name]);
|
|
73
|
+
prefetcher.register(() => loaded.load());
|
|
74
|
+
const Lazy = lazy(() => open(loaded));
|
|
75
|
+
// Once `loaded` has it, `Lazy` never committed anything, so switching over remounts nothing.
|
|
76
|
+
const LazyNamed = (props: ComponentProps<Module[Name]>) => {
|
|
77
|
+
const Component: ComponentType<any> = loaded.current ?? Lazy;
|
|
78
|
+
return <Component {...props} />;
|
|
79
|
+
};
|
|
80
|
+
return LazyNamed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Stays true after the first opening, so every close after it still has something mounted to animate. */
|
|
84
|
+
function useOpenedOnce(open: boolean): boolean {
|
|
85
|
+
const [opened, setOpened] = useState(open);
|
|
86
|
+
if (open && !opened) {
|
|
87
|
+
setOpened(true);
|
|
88
|
+
}
|
|
89
|
+
return opened || open;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
* A dialog that keeps its own open state. Once its module is here, prefetched or opened, it stays
|
|
94
|
+
* mounted closed, so its first opening animates like every other; before that, opening loads it.
|
|
95
|
+
*/
|
|
96
|
+
export function lazyDialog<Module extends Record<Name, ComponentType>, Name extends keyof Module, State>(
|
|
97
|
+
load: () => Promise<Module>,
|
|
98
|
+
name: Name,
|
|
99
|
+
useStore: (select: (state: State) => boolean) => boolean,
|
|
100
|
+
isOpen: (state: State) => boolean
|
|
101
|
+
): ComponentType {
|
|
102
|
+
const loaded = new LoadedComponent(async (): Promise<ComponentType> => (await load())[name]);
|
|
103
|
+
prefetcher.register(() => loaded.load());
|
|
104
|
+
const Dialog = lazy(() => open(loaded));
|
|
105
|
+
const subscribe = (listener: () => void) => loaded.subscribe(listener);
|
|
106
|
+
const isLoaded = () => loaded.current !== null;
|
|
107
|
+
const LazyDialog = () => {
|
|
108
|
+
const opened = useOpenedOnce(useStore(isOpen));
|
|
109
|
+
useSyncExternalStore(subscribe, isLoaded);
|
|
110
|
+
const Loaded: ComponentType | null = loaded.current;
|
|
111
|
+
if (Loaded) {
|
|
112
|
+
return <Loaded />;
|
|
113
|
+
}
|
|
114
|
+
if (!opened) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return (
|
|
118
|
+
<Suspense fallback={null}>
|
|
119
|
+
<Dialog />
|
|
120
|
+
</Suspense>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
return LazyDialog;
|
|
124
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"action": {
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"copy": "Copy",
|
|
5
|
+
"copyError": "Copy error",
|
|
6
|
+
"dismiss": "Dismiss",
|
|
7
|
+
"reload": "Reload",
|
|
8
|
+
"retry": "Try again",
|
|
9
|
+
"select": "Select",
|
|
10
|
+
"selectAll": "Select all"
|
|
11
|
+
},
|
|
12
|
+
"accent": {
|
|
13
|
+
"more": "More colors"
|
|
14
|
+
},
|
|
15
|
+
"settings": {
|
|
16
|
+
"title": "Settings",
|
|
17
|
+
"section": "Settings section",
|
|
18
|
+
"sections": "Settings sections",
|
|
19
|
+
"close": "Close settings",
|
|
20
|
+
"failed": "This settings page failed to render",
|
|
21
|
+
"search": {
|
|
22
|
+
"placeholder": "Search settings",
|
|
23
|
+
"clear": "Clear search",
|
|
24
|
+
"none": "Nothing matches \"{{query}}\".",
|
|
25
|
+
"results": "Search results",
|
|
26
|
+
"pane": "Section"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"controls": {
|
|
30
|
+
"stepper": {
|
|
31
|
+
"smaller": "Smaller",
|
|
32
|
+
"larger": "Larger",
|
|
33
|
+
"smallerFor": "{{label}}: smaller",
|
|
34
|
+
"largerFor": "{{label}}: larger"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"iconPicker": {
|
|
38
|
+
"label": "Icon",
|
|
39
|
+
"none": "No icon",
|
|
40
|
+
"search": "Find an icon",
|
|
41
|
+
"clear": "Clear search",
|
|
42
|
+
"noMatch": "No icon matches “{{query}}”"
|
|
43
|
+
},
|
|
44
|
+
"zoom": {
|
|
45
|
+
"out": "Zoom out",
|
|
46
|
+
"in": "Zoom in",
|
|
47
|
+
"presets": "Zoom presets",
|
|
48
|
+
"fit": "Zoom to fit",
|
|
49
|
+
"fitEverything": "Fit everything"
|
|
50
|
+
},
|
|
51
|
+
"waveform": {
|
|
52
|
+
"position": "{{time}} of {{duration}}"
|
|
53
|
+
},
|
|
54
|
+
"projectSwitcher": {
|
|
55
|
+
"label": "Projects",
|
|
56
|
+
"recent": "Recent projects",
|
|
57
|
+
"actionsFor": "Actions for {{name}}"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"action": {
|
|
3
|
+
"cancel": "Annuleren",
|
|
4
|
+
"copy": "Kopiëren",
|
|
5
|
+
"copyError": "Fout kopiëren",
|
|
6
|
+
"dismiss": "Sluiten",
|
|
7
|
+
"reload": "Herladen",
|
|
8
|
+
"retry": "Opnieuw proberen",
|
|
9
|
+
"select": "Selecteren",
|
|
10
|
+
"selectAll": "Alles selecteren"
|
|
11
|
+
},
|
|
12
|
+
"accent": {
|
|
13
|
+
"more": "Meer kleuren"
|
|
14
|
+
},
|
|
15
|
+
"settings": {
|
|
16
|
+
"title": "Instellingen",
|
|
17
|
+
"section": "Onderdeel van de instellingen",
|
|
18
|
+
"sections": "Onderdelen van de instellingen",
|
|
19
|
+
"close": "Instellingen sluiten",
|
|
20
|
+
"failed": "Deze instellingenpagina kon niet worden getekend",
|
|
21
|
+
"search": {
|
|
22
|
+
"placeholder": "Instellingen zoeken",
|
|
23
|
+
"clear": "Zoekopdracht wissen",
|
|
24
|
+
"none": "Niets komt overeen met \"{{query}}\".",
|
|
25
|
+
"results": "Zoekresultaten",
|
|
26
|
+
"pane": "Onderdeel"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"controls": {
|
|
30
|
+
"stepper": {
|
|
31
|
+
"smaller": "Kleiner",
|
|
32
|
+
"larger": "Groter",
|
|
33
|
+
"smallerFor": "{{label}}: kleiner",
|
|
34
|
+
"largerFor": "{{label}}: groter"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"iconPicker": {
|
|
38
|
+
"label": "Icoon",
|
|
39
|
+
"none": "Geen icoon",
|
|
40
|
+
"search": "Zoek een icoon",
|
|
41
|
+
"clear": "Zoekopdracht wissen",
|
|
42
|
+
"noMatch": "Geen icoon past bij “{{query}}”"
|
|
43
|
+
},
|
|
44
|
+
"zoom": {
|
|
45
|
+
"out": "Uitzoomen",
|
|
46
|
+
"in": "Inzoomen",
|
|
47
|
+
"presets": "Zoomstanden",
|
|
48
|
+
"fit": "Zoomen tot alles past",
|
|
49
|
+
"fitEverything": "Alles laten passen"
|
|
50
|
+
},
|
|
51
|
+
"waveform": {
|
|
52
|
+
"position": "{{time}} van {{duration}}"
|
|
53
|
+
},
|
|
54
|
+
"projectSwitcher": {
|
|
55
|
+
"label": "Projecten",
|
|
56
|
+
"recent": "Recente projecten",
|
|
57
|
+
"actionsFor": "Acties voor {{name}}"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/locales.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { i18n as I18n } from 'i18next';
|
|
2
|
+
import en from './locales/en.json' with { type: 'json' };
|
|
3
|
+
import nl from './locales/nl.json' with { type: 'json' };
|
|
4
|
+
|
|
5
|
+
/* The words of this library live in a namespace of their own, which the app adds to its i18next beside its own namespaces. */
|
|
6
|
+
export const UI_NAMESPACE = 'ui';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Every language the library speaks, by language code. Small enough to ship whole, which is what lets
|
|
10
|
+
* `addUiResources` run synchronously before the first render, so no word ever shows as its key.
|
|
11
|
+
*/
|
|
12
|
+
export const UI_RESOURCES: Readonly<Record<string, Record<string, unknown>>> = { en, nl };
|
|
13
|
+
|
|
14
|
+
/* Adds the `ui` namespace in every language to an i18next instance. A language the app already filled keeps its words. */
|
|
15
|
+
export const addUiResources = (i18n: I18n): void => {
|
|
16
|
+
for (const [language, words] of Object.entries(UI_RESOURCES)) {
|
|
17
|
+
if (!i18n.hasResourceBundle(language, UI_NAMESPACE)) {
|
|
18
|
+
i18n.addResourceBundle(language, UI_NAMESPACE, words, true, false);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
MenuCheck as Check,
|
|
3
|
+
MenuCheckboxItem as CheckboxItem,
|
|
4
|
+
MenuGroup as Group,
|
|
5
|
+
MenuGroupLabel as GroupLabel,
|
|
6
|
+
MenuHint as Hint,
|
|
7
|
+
MenuItem as Item,
|
|
8
|
+
MenuLabel as Label,
|
|
9
|
+
MenuPopup as Popup,
|
|
10
|
+
MenuRadioGroup as RadioGroup,
|
|
11
|
+
MenuRadioItem as RadioItem,
|
|
12
|
+
MenuRoot as Root,
|
|
13
|
+
MenuRow as Row,
|
|
14
|
+
MenuRowAction as RowAction,
|
|
15
|
+
MenuRowSubmenuTrigger as RowSubmenuTrigger,
|
|
16
|
+
MenuSeparator as Separator,
|
|
17
|
+
MenuSubmenuRoot as SubmenuRoot,
|
|
18
|
+
MenuSubmenuTrigger as SubmenuTrigger,
|
|
19
|
+
MenuTrigger as Trigger
|
|
20
|
+
} from './parts.tsx';
|