@eifi1/ui-kit 0.35.0 → 0.36.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 +35 -5
- package/dist/admin/role-select.d.ts +3 -2
- package/dist/admin/role-select.js +2 -0
- package/dist/admin/role-select.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +2 -2
- package/dist/components/autocomplete.js +2 -1
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-look.d.ts +1 -1
- package/dist/components/button-look.js +9 -4
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/checkbox-group.d.ts +5 -2
- package/dist/components/checkbox-group.js +15 -4
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/column-mapper.d.ts +5 -3
- package/dist/components/column-mapper.js +2 -0
- package/dist/components/column-mapper.js.map +1 -1
- package/dist/components/combobox.d.ts +2 -2
- package/dist/components/combobox.js +2 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/country-select.d.ts +3 -3
- package/dist/components/country-select.js +3 -0
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-select.d.ts +3 -2
- package/dist/components/currency-select.js +10 -1
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/date-field.d.ts +2 -1
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.js +3 -0
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.js +3 -0
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +2 -2
- package/dist/components/entity-combobox.js +2 -1
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-parts.d.ts +66 -11
- package/dist/components/field-parts.js +32 -10
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +3 -2
- package/dist/components/field-strip.js +2 -1
- package/dist/components/field-strip.js.map +1 -1
- package/dist/components/field.d.ts +3 -2
- package/dist/components/field.js +24 -15
- package/dist/components/field.js.map +1 -1
- package/dist/components/help-popover.d.ts +9 -7
- package/dist/components/help-popover.js.map +1 -1
- package/dist/components/icon-picker.d.ts +3 -2
- package/dist/components/icon-picker.js +3 -0
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-entity-combobox.d.ts +2 -2
- package/dist/components/inline-entity-combobox.js +2 -1
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.d.ts +2 -2
- package/dist/components/input.js +2 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/month-picker.d.ts +2 -1
- package/dist/components/month-picker.js +8 -1
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +2 -2
- package/dist/components/multi-entity-combobox.js +2 -1
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +2 -2
- package/dist/components/multi-select.js +3 -0
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.d.ts +2 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numeric-field.d.ts +5 -2
- package/dist/components/numeric-field.js +4 -1
- package/dist/components/numeric-field.js.map +1 -1
- package/dist/components/one-time-code-input.d.ts +2 -1
- package/dist/components/one-time-code-input.js +2 -1
- package/dist/components/one-time-code-input.js.map +1 -1
- package/dist/components/phone-input.d.ts +1 -1
- package/dist/components/phone-input.js +4 -0
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/popover.js +1 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/select.d.ts +4 -3
- package/dist/components/select.js +2 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/slider.d.ts +3 -2
- package/dist/components/slider.js +7 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/stat-tile.js +2 -11
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swatch-picker.d.ts +3 -2
- package/dist/components/swatch-picker.js +3 -0
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/text-link.js +3 -1
- package/dist/components/text-link.js.map +1 -1
- package/dist/components/textarea.d.ts +2 -1
- package/dist/components/textarea.js +9 -3
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/toggle-group.d.ts +2 -2
- package/dist/components/toggle-group.js +7 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/i18n/defaults.js +5 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +97 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -0
- package/dist/i18n/kit-labels.js +21 -1
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +103 -2
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +95 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +96 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +95 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +96 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +95 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +13 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +3 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/device-settings.d.ts +72 -0
- package/dist/lib/device-settings.js +64 -0
- package/dist/lib/device-settings.js.map +1 -0
- package/dist/lib/plain-text.d.ts +11 -0
- package/dist/lib/plain-text.js +16 -0
- package/dist/lib/plain-text.js.map +1 -0
- package/dist/lib/safe-storage.d.ts +6 -0
- package/dist/lib/safe-storage.js +11 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/notifications/notification-parts.d.ts +39 -0
- package/dist/notifications/notification-parts.js +47 -0
- package/dist/notifications/notification-parts.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +62 -0
- package/dist/notifications/notification-types-setting.js +192 -0
- package/dist/notifications/notification-types-setting.js.map +1 -0
- package/dist/notifications/notification-types.d.ts +75 -0
- package/dist/notifications/notification-types.js +2 -0
- package/dist/notifications/notification-types.js.map +1 -0
- package/dist/notifications/notifications-labels.d.ts +131 -0
- package/dist/notifications/notifications-labels.js +83 -0
- package/dist/notifications/notifications-labels.js.map +1 -0
- package/dist/notifications/push-device-setting.d.ts +37 -0
- package/dist/notifications/push-device-setting.js +164 -0
- package/dist/notifications/push-device-setting.js.map +1 -0
- package/dist/notifications/push-devices-setting.d.ts +44 -0
- package/dist/notifications/push-devices-setting.js +203 -0
- package/dist/notifications/push-devices-setting.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +47 -0
- package/dist/notifications/quiet-hours-setting.js +131 -0
- package/dist/notifications/quiet-hours-setting.js.map +1 -0
- package/dist/notifications/time-zone-select.d.ts +50 -0
- package/dist/notifications/time-zone-select.js +76 -0
- package/dist/notifications/time-zone-select.js.map +1 -0
- package/dist/notifications/unsubscribe-panel.d.ts +53 -0
- package/dist/notifications/unsubscribe-panel.js +139 -0
- package/dist/notifications/unsubscribe-panel.js.map +1 -0
- package/dist/notifications/use-push-device.d.ts +122 -0
- package/dist/notifications/use-push-device.js +230 -0
- package/dist/notifications/use-push-device.js.map +1 -0
- package/dist/rhf/fields.d.ts +26 -19
- package/dist/rhf/fields.js +51 -22
- package/dist/rhf/fields.js.map +1 -1
- package/dist/settings/device-reset-setting-labels.d.ts +3 -0
- package/dist/settings/device-reset-setting-labels.js +15 -0
- package/dist/settings/device-reset-setting-labels.js.map +1 -0
- package/dist/settings/device-reset-setting.d.ts +58 -0
- package/dist/settings/device-reset-setting.js +51 -0
- package/dist/settings/device-reset-setting.js.map +1 -0
- package/dist/shell/app-shell.js +5 -0
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/theme/contrast-tokens.d.ts +42 -4
- package/dist/theme/contrast-tokens.js +56 -9
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +4 -2
- package/dist/theme/contrast.js +13 -0
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/palette-derive.d.ts +15 -1
- package/dist/theme/palette-derive.js +37 -8
- package/dist/theme/palette-derive.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +2 -1
- package/dist/theme/palette-presets.js +5 -5
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +6 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js +6 -0
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +6 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/role-select.tsx +4 -1
- package/src/components/amount-input.tsx +2 -1
- package/src/components/autocomplete.tsx +5 -2
- package/src/components/button-look.ts +15 -5
- package/src/components/checkbox-group.tsx +22 -4
- package/src/components/column-mapper.tsx +6 -2
- package/src/components/combobox.tsx +5 -2
- package/src/components/country-select.tsx +6 -1
- package/src/components/currency-select.tsx +12 -3
- package/src/components/date-field.tsx +2 -2
- package/src/components/date-picker.tsx +3 -0
- package/src/components/date-range-picker.tsx +3 -0
- package/src/components/entity-combobox.tsx +5 -2
- package/src/components/field-parts.tsx +107 -19
- package/src/components/field-strip.tsx +4 -3
- package/src/components/field.tsx +28 -15
- package/src/components/help-popover.tsx +9 -7
- package/src/components/icon-picker.tsx +8 -3
- package/src/components/inline-entity-combobox.tsx +5 -2
- package/src/components/input.tsx +4 -2
- package/src/components/month-picker.tsx +10 -3
- package/src/components/multi-entity-combobox.tsx +5 -2
- package/src/components/multi-select.tsx +6 -1
- package/src/components/number-input.tsx +2 -2
- package/src/components/numeric-field.ts +10 -2
- package/src/components/one-time-code-input.tsx +5 -3
- package/src/components/phone-input.tsx +4 -0
- package/src/components/popover.tsx +3 -1
- package/src/components/select.tsx +6 -3
- package/src/components/slider.tsx +10 -3
- package/src/components/stat-tile.tsx +3 -18
- package/src/components/swatch-picker.tsx +9 -4
- package/src/components/text-link.tsx +3 -1
- package/src/components/textarea.tsx +11 -4
- package/src/components/toggle-group.tsx +21 -5
- package/src/i18n/defaults.ts +4 -0
- package/src/i18n/german.ts +113 -0
- package/src/i18n/kit-labels.tsx +51 -1
- package/src/i18n/locales/en.ts +6 -0
- package/src/i18n/locales/es.ts +106 -0
- package/src/i18n/locales/fr.ts +106 -0
- package/src/i18n/locales/hu.ts +104 -0
- package/src/i18n/locales/it.ts +105 -0
- package/src/i18n/locales/zh.ts +96 -0
- package/src/i18n/review.ts +13 -0
- package/src/i18n/shared-labels.ts +2 -0
- package/src/index.ts +50 -0
- package/src/lib/device-settings.ts +146 -0
- package/src/lib/plain-text.ts +22 -0
- package/src/lib/safe-storage.ts +16 -0
- package/src/notifications/notification-parts.tsx +83 -0
- package/src/notifications/notification-types-setting.tsx +309 -0
- package/src/notifications/notification-types.ts +80 -0
- package/src/notifications/notifications-labels.ts +238 -0
- package/src/notifications/push-device-setting.tsx +224 -0
- package/src/notifications/push-devices-setting.tsx +306 -0
- package/src/notifications/quiet-hours-setting.tsx +185 -0
- package/src/notifications/time-zone-select.tsx +126 -0
- package/src/notifications/unsubscribe-panel.tsx +203 -0
- package/src/notifications/use-push-device.ts +392 -0
- package/src/rhf/fields.tsx +77 -29
- package/src/settings/device-reset-setting-labels.ts +20 -0
- package/src/settings/device-reset-setting.tsx +119 -0
- package/src/shell/app-shell.tsx +8 -0
- package/src/theme/contrast-tokens.ts +106 -12
- package/src/theme/contrast.ts +30 -4
- package/src/theme/palette-derive.ts +68 -7
- package/src/theme/palette-presets.ts +9 -6
- package/src/theme/palette-store.ts +12 -0
- package/src/theme/text-size.ts +8 -0
- package/src/theme/theme-store.ts +10 -0
- package/tokens.css +38 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/theme/theme-store.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { logger } from \"../lib/logger\";\n\nexport type ThemeMode = \"light\" | \"dark\";\n/** What the user chose; \"system\" tracks the OS `prefers-color-scheme`. */\nexport type ThemePreference = \"light\" | \"dark\" | \"system\";\n\nexport interface ThemeState {\n /** The user's choice (persisted). */\n preference: ThemePreference;\n /** The RESOLVED light/dark actually in effect (system → OS). Read this for\n * anything that needs the concrete theme (e.g. token selection, toasts). */\n mode: ThemeMode;\n setPreference: (preference: ThemePreference) => void;\n /** Set an explicit light/dark choice (preference follows). */\n setMode: (mode: ThemeMode) => void;\n /** Flip the effective theme to the opposite explicit choice. */\n toggle: () => void;\n}\n\nfunction systemPreference(): ThemeMode {\n try {\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n } catch {\n return \"light\";\n }\n}\n\nfunction resolve(preference: ThemePreference): ThemeMode {\n return preference === \"system\" ? systemPreference() : preference;\n}\n\n/** Normalize a persisted theme-state blob to a valid preference. Older blobs\n * persisted the concrete `mode` (light/dark); carry that over as an explicit\n * preference so the choice survives. Shared by the store's `migrate` and the\n * pre-hydration {@link applyPersistedTheme}. */\nfunction preferenceFromState(\n s: { mode?: string; preference?: string } | null | undefined,\n): ThemePreference {\n if (s?.preference === \"light\" || s?.preference === \"dark\" || s?.preference === \"system\") {\n return s.preference;\n }\n if (s?.mode === \"light\" || s?.mode === \"dark\") return s.mode;\n return \"system\";\n}\n\n/** Toggle the `.dark` class on <html> for a resolved light/dark mode. */\nfunction applyThemeClass(mode: ThemeMode): void {\n const root = document.documentElement;\n if (mode === \"dark\") root.classList.add(\"dark\");\n else root.classList.remove(\"dark\");\n}\n\n/**\n * Pre-hydration: read the theme PREFERENCE persisted under `storageKey` straight\n * from localStorage, resolve it to a concrete light/dark mode, and toggle the\n * `.dark` class on <html> — BEFORE React and the store mount, so the first paint\n * already has the right theme (no flash). Returns the resolved mode so the caller\n * can chain {@link applyPersistedPalette}. Encapsulates the zustand-persist\n * envelope + the legacy `mode`→`preference` fallback so apps never re-type the\n * storage shape in their entrypoint.\n */\nexport function applyPersistedTheme(storageKey: string): ThemeMode {\n let preference: ThemePreference = \"system\";\n try {\n const blob = JSON.parse(localStorage.getItem(storageKey) ?? \"{}\") as {\n state?: { mode?: string; preference?: string };\n };\n preference = preferenceFromState(blob.state);\n } catch {\n /* malformed storage → system */\n }\n const mode = resolve(preference);\n applyThemeClass(mode);\n return mode;\n}\n\n/**\n * Build a theme store bound to a consumer-supplied localStorage key. Tracks a\n * light/dark/system PREFERENCE (feedback #330) and exposes the resolved `mode`.\n * Returns the `useTheme` hook plus a `useApplyTheme` effect that toggles the\n * `.dark` class on <html> and, while on \"system\", follows OS theme changes live.\n */\nexport function createThemeStore(storageKey: string) {\n const useTheme = create<ThemeState>()(\n logger(\n persist(\n (set, get) => ({\n preference: \"system\" as ThemePreference,\n mode: systemPreference(),\n setPreference: (preference) => set({ preference, mode: resolve(preference) }),\n setMode: (mode) => set({ preference: mode, mode }),\n toggle: () => {\n const next: ThemeMode = get().mode === \"dark\" ? \"light\" : \"dark\";\n set({ preference: next, mode: next });\n },\n }),\n {\n name: storageKey,\n version: 2,\n // Only the preference is persisted; `mode` is derived on load.\n partialize: (s) => ({ preference: s.preference }),\n migrate: (persistedState): { preference: ThemePreference } => ({\n preference: preferenceFromState(\n persistedState as { mode?: string; preference?: string } | null,\n ),\n }),\n onRehydrateStorage: () => (state) => {\n // The persisted preference decides the concrete mode at load.\n if (state) state.mode = resolve(state.preference);\n },\n },\n ),\n \"theme\",\n ),\n );\n\n function useApplyTheme(): void {\n const mode = useTheme((s) => s.mode);\n const preference = useTheme((s) => s.preference);\n useEffect(() => {\n applyThemeClass(mode);\n }, [mode]);\n // While tracking the system, follow OS theme changes live.\n useEffect(() => {\n if (preference !== \"system\") return;\n let mq: MediaQueryList;\n try {\n mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n } catch {\n return;\n }\n const onChange = () => useTheme.setState({ mode: mq.matches ? \"dark\" : \"light\" });\n onChange();\n mq.addEventListener?.(\"change\", onChange);\n return () => mq.removeEventListener?.(\"change\", onChange);\n }, [preference]);\n }\n\n return { useTheme, useApplyTheme };\n}\n\nexport type ThemeStore = ReturnType<typeof createThemeStore>;\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,cAAc;
|
|
1
|
+
{"version":3,"sources":["../../src/theme/theme-store.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { logger } from \"../lib/logger\";\nimport { registerDeviceSetting } from \"../lib/device-settings\";\n\nexport type ThemeMode = \"light\" | \"dark\";\n/** What the user chose; \"system\" tracks the OS `prefers-color-scheme`. */\nexport type ThemePreference = \"light\" | \"dark\" | \"system\";\n\nexport interface ThemeState {\n /** The user's choice (persisted). */\n preference: ThemePreference;\n /** The RESOLVED light/dark actually in effect (system → OS). Read this for\n * anything that needs the concrete theme (e.g. token selection, toasts). */\n mode: ThemeMode;\n setPreference: (preference: ThemePreference) => void;\n /** Set an explicit light/dark choice (preference follows). */\n setMode: (mode: ThemeMode) => void;\n /** Flip the effective theme to the opposite explicit choice. */\n toggle: () => void;\n}\n\nfunction systemPreference(): ThemeMode {\n try {\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n } catch {\n return \"light\";\n }\n}\n\nfunction resolve(preference: ThemePreference): ThemeMode {\n return preference === \"system\" ? systemPreference() : preference;\n}\n\n/** Normalize a persisted theme-state blob to a valid preference. Older blobs\n * persisted the concrete `mode` (light/dark); carry that over as an explicit\n * preference so the choice survives. Shared by the store's `migrate` and the\n * pre-hydration {@link applyPersistedTheme}. */\nfunction preferenceFromState(\n s: { mode?: string; preference?: string } | null | undefined,\n): ThemePreference {\n if (s?.preference === \"light\" || s?.preference === \"dark\" || s?.preference === \"system\") {\n return s.preference;\n }\n if (s?.mode === \"light\" || s?.mode === \"dark\") return s.mode;\n return \"system\";\n}\n\n/** Toggle the `.dark` class on <html> for a resolved light/dark mode. */\nfunction applyThemeClass(mode: ThemeMode): void {\n const root = document.documentElement;\n if (mode === \"dark\") root.classList.add(\"dark\");\n else root.classList.remove(\"dark\");\n}\n\n/**\n * Pre-hydration: read the theme PREFERENCE persisted under `storageKey` straight\n * from localStorage, resolve it to a concrete light/dark mode, and toggle the\n * `.dark` class on <html> — BEFORE React and the store mount, so the first paint\n * already has the right theme (no flash). Returns the resolved mode so the caller\n * can chain {@link applyPersistedPalette}. Encapsulates the zustand-persist\n * envelope + the legacy `mode`→`preference` fallback so apps never re-type the\n * storage shape in their entrypoint.\n */\nexport function applyPersistedTheme(storageKey: string): ThemeMode {\n let preference: ThemePreference = \"system\";\n try {\n const blob = JSON.parse(localStorage.getItem(storageKey) ?? \"{}\") as {\n state?: { mode?: string; preference?: string };\n };\n preference = preferenceFromState(blob.state);\n } catch {\n /* malformed storage → system */\n }\n const mode = resolve(preference);\n applyThemeClass(mode);\n return mode;\n}\n\n/**\n * Build a theme store bound to a consumer-supplied localStorage key. Tracks a\n * light/dark/system PREFERENCE (feedback #330) and exposes the resolved `mode`.\n * Returns the `useTheme` hook plus a `useApplyTheme` effect that toggles the\n * `.dark` class on <html> and, while on \"system\", follows OS theme changes live.\n */\nexport function createThemeStore(storageKey: string) {\n const useTheme = create<ThemeState>()(\n logger(\n persist(\n (set, get) => ({\n preference: \"system\" as ThemePreference,\n mode: systemPreference(),\n setPreference: (preference) => set({ preference, mode: resolve(preference) }),\n setMode: (mode) => set({ preference: mode, mode }),\n toggle: () => {\n const next: ThemeMode = get().mode === \"dark\" ? \"light\" : \"dark\";\n set({ preference: next, mode: next });\n },\n }),\n {\n name: storageKey,\n version: 2,\n // Only the preference is persisted; `mode` is derived on load.\n partialize: (s) => ({ preference: s.preference }),\n migrate: (persistedState): { preference: ThemePreference } => ({\n preference: preferenceFromState(\n persistedState as { mode?: string; preference?: string } | null,\n ),\n }),\n onRehydrateStorage: () => (state) => {\n // The persisted preference decides the concrete mode at load.\n if (state) state.mode = resolve(state.preference);\n },\n },\n ),\n \"theme\",\n ),\n );\n // Reset this device's settings (docs/settings-harmonization.md §11.4): back to\n // \"System\" through the setter, which re-resolves `mode`; `useApplyTheme` puts the\n // class back and follows the OS again, and the persisted blob is what the pre-paint\n // `applyPersistedTheme` reads next load.\n registerDeviceSetting({\n key: storageKey,\n kind: \"appearance\",\n reset: () => useTheme.getState().setPreference(\"system\"),\n });\n\n function useApplyTheme(): void {\n const mode = useTheme((s) => s.mode);\n const preference = useTheme((s) => s.preference);\n useEffect(() => {\n applyThemeClass(mode);\n }, [mode]);\n // While tracking the system, follow OS theme changes live.\n useEffect(() => {\n if (preference !== \"system\") return;\n let mq: MediaQueryList;\n try {\n mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\n } catch {\n return;\n }\n const onChange = () => useTheme.setState({ mode: mq.matches ? \"dark\" : \"light\" });\n onChange();\n mq.addEventListener?.(\"change\", onChange);\n return () => mq.removeEventListener?.(\"change\", onChange);\n }, [preference]);\n }\n\n return { useTheme, useApplyTheme };\n}\n\nexport type ThemeStore = ReturnType<typeof createThemeStore>;\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,6BAA6B;AAmBtC,SAAS,mBAA8B;AACrC,MAAI;AACF,WAAO,OAAO,WAAW,8BAA8B,EAAE,UAAU,SAAS;AAAA,EAC9E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,QAAQ,YAAwC;AACvD,SAAO,eAAe,WAAW,iBAAiB,IAAI;AACxD;AAMA,SAAS,oBACP,GACiB;AACjB,MAAI,GAAG,eAAe,WAAW,GAAG,eAAe,UAAU,GAAG,eAAe,UAAU;AACvF,WAAO,EAAE;AAAA,EACX;AACA,MAAI,GAAG,SAAS,WAAW,GAAG,SAAS,OAAQ,QAAO,EAAE;AACxD,SAAO;AACT;AAGA,SAAS,gBAAgB,MAAuB;AAC9C,QAAM,OAAO,SAAS;AACtB,MAAI,SAAS,OAAQ,MAAK,UAAU,IAAI,MAAM;AAAA,MACzC,MAAK,UAAU,OAAO,MAAM;AACnC;AAWO,SAAS,oBAAoB,YAA+B;AACjE,MAAI,aAA8B;AAClC,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,aAAa,QAAQ,UAAU,KAAK,IAAI;AAGhE,iBAAa,oBAAoB,KAAK,KAAK;AAAA,EAC7C,QAAQ;AAAA,EAER;AACA,QAAM,OAAO,QAAQ,UAAU;AAC/B,kBAAgB,IAAI;AACpB,SAAO;AACT;AAQO,SAAS,iBAAiB,YAAoB;AACnD,QAAM,WAAW,OAAmB;AAAA,IAClC;AAAA,MACE;AAAA,QACE,CAAC,KAAK,SAAS;AAAA,UACb,YAAY;AAAA,UACZ,MAAM,iBAAiB;AAAA,UACvB,eAAe,CAAC,eAAe,IAAI,EAAE,YAAY,MAAM,QAAQ,UAAU,EAAE,CAAC;AAAA,UAC5E,SAAS,CAAC,SAAS,IAAI,EAAE,YAAY,MAAM,KAAK,CAAC;AAAA,UACjD,QAAQ,MAAM;AACZ,kBAAM,OAAkB,IAAI,EAAE,SAAS,SAAS,UAAU;AAC1D,gBAAI,EAAE,YAAY,MAAM,MAAM,KAAK,CAAC;AAAA,UACtC;AAAA,QACF;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA;AAAA,UAET,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW;AAAA,UAC/C,SAAS,CAAC,oBAAqD;AAAA,YAC7D,YAAY;AAAA,cACV;AAAA,YACF;AAAA,UACF;AAAA,UACA,oBAAoB,MAAM,CAAC,UAAU;AAEnC,gBAAI,MAAO,OAAM,OAAO,QAAQ,MAAM,UAAU;AAAA,UAClD;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAKA,wBAAsB;AAAA,IACpB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO,MAAM,SAAS,SAAS,EAAE,cAAc,QAAQ;AAAA,EACzD,CAAC;AAED,WAAS,gBAAsB;AAC7B,UAAM,OAAO,SAAS,CAAC,MAAM,EAAE,IAAI;AACnC,UAAM,aAAa,SAAS,CAAC,MAAM,EAAE,UAAU;AAC/C,cAAU,MAAM;AACd,sBAAgB,IAAI;AAAA,IACtB,GAAG,CAAC,IAAI,CAAC;AAET,cAAU,MAAM;AACd,UAAI,eAAe,SAAU;AAC7B,UAAI;AACJ,UAAI;AACF,aAAK,OAAO,WAAW,8BAA8B;AAAA,MACvD,QAAQ;AACN;AAAA,MACF;AACA,YAAM,WAAW,MAAM,SAAS,SAAS,EAAE,MAAM,GAAG,UAAU,SAAS,QAAQ,CAAC;AAChF,eAAS;AACT,SAAG,mBAAmB,UAAU,QAAQ;AACxC,aAAO,MAAM,GAAG,sBAAsB,UAAU,QAAQ;AAAA,IAC1D,GAAG,CAAC,UAAU,CAAC;AAAA,EACjB;AAEA,SAAO,EAAE,UAAU,cAAc;AACnC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
|
@@ -7,6 +7,7 @@ import type { CheckboxGroupOption } from "../components/checkbox-group";
|
|
|
7
7
|
import { Select } from "../components/select";
|
|
8
8
|
import type { SelectProps } from "../components/select";
|
|
9
9
|
import type { CommitScope } from "../components/write-lock";
|
|
10
|
+
import type { FieldHelpProps } from "../components/field-parts";
|
|
10
11
|
import { DEFAULT_ROLE_SELECT_LABELS } from "./role-select-labels";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -185,7 +186,7 @@ export function RoleSelect<R extends string = string>({
|
|
|
185
186
|
|
|
186
187
|
/* ── RolesEditor ────────────────────────────────────────────────────────────── */
|
|
187
188
|
|
|
188
|
-
export interface RolesEditorProps<R extends string = string> {
|
|
189
|
+
export interface RolesEditorProps<R extends string = string> extends FieldHelpProps {
|
|
189
190
|
/** The roles ticked — the draft the app holds while its dialog is open. */
|
|
190
191
|
value: readonly R[];
|
|
191
192
|
roles: RoleVocabulary<R>;
|
|
@@ -236,6 +237,7 @@ export function RolesEditor<R extends string = string>({
|
|
|
236
237
|
legendVisibility,
|
|
237
238
|
required,
|
|
238
239
|
hint,
|
|
240
|
+
help,
|
|
239
241
|
error,
|
|
240
242
|
columns,
|
|
241
243
|
commit,
|
|
@@ -267,6 +269,7 @@ export function RolesEditor<R extends string = string>({
|
|
|
267
269
|
onChange={onChange}
|
|
268
270
|
required={required}
|
|
269
271
|
hint={hint}
|
|
272
|
+
help={help}
|
|
270
273
|
error={error}
|
|
271
274
|
columns={columns}
|
|
272
275
|
commit={commit}
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
LockedReason,
|
|
37
37
|
mergeRefs,
|
|
38
38
|
type DisabledReasonDisplay,
|
|
39
|
+
type FieldHelpProps,
|
|
39
40
|
type LockReason,
|
|
40
41
|
} from "./field-parts";
|
|
41
42
|
import { useEndWidth, useNumericField } from "./numeric-field";
|
|
@@ -43,7 +44,7 @@ import type { CommitScope } from "./write-lock";
|
|
|
43
44
|
|
|
44
45
|
/** {@link AmountInput}'s props — exported since 0.34 (refactor audit 2026-10, API-9): a
|
|
45
46
|
* wrapper typed itself off `ComponentProps<typeof AmountInput>` until then. */
|
|
46
|
-
export interface AmountInputProps {
|
|
47
|
+
export interface AmountInputProps extends FieldHelpProps {
|
|
47
48
|
value: string;
|
|
48
49
|
onChange: (value: string) => void;
|
|
49
50
|
currency?: string;
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
useFieldHint,
|
|
33
33
|
useLockReason,
|
|
34
34
|
type DisabledReasonDisplay,
|
|
35
|
+
type FieldHelpProps,
|
|
35
36
|
} from "./field-parts";
|
|
36
37
|
import type { CommitScope } from "./write-lock";
|
|
37
38
|
|
|
@@ -44,7 +45,8 @@ export interface AutocompleteProps<V extends string | number = string>
|
|
|
44
45
|
extends Omit<
|
|
45
46
|
ComponentPropsWithoutRef<"input">,
|
|
46
47
|
"value" | "defaultValue" | "onChange" | "onSelect" | "children" | "type" | "size"
|
|
47
|
-
|
|
48
|
+
>,
|
|
49
|
+
FieldHelpProps {
|
|
48
50
|
/** What is in the field. Controlled, and never reset by the component: opening,
|
|
49
51
|
* closing and a failed lookup all leave it exactly as typed. */
|
|
50
52
|
value: string;
|
|
@@ -220,6 +222,7 @@ export function Autocomplete<V extends string | number = string>(props: Autocomp
|
|
|
220
222
|
invalid,
|
|
221
223
|
error,
|
|
222
224
|
hint,
|
|
225
|
+
help,
|
|
223
226
|
status,
|
|
224
227
|
emptyLabel,
|
|
225
228
|
loadErrorLabel,
|
|
@@ -252,7 +255,7 @@ export function Autocomplete<V extends string | number = string>(props: Autocomp
|
|
|
252
255
|
noResults: emptyLabel,
|
|
253
256
|
loadError: loadErrorLabel,
|
|
254
257
|
});
|
|
255
|
-
const hintParts = useFieldHint(hint, ariaDescribedBy);
|
|
258
|
+
const hintParts = useFieldHint(hint, ariaDescribedBy, { help, name: label ?? ariaLabel });
|
|
256
259
|
// A form library's `aria-invalid` paints as well as announces (DUP-8: it only
|
|
257
260
|
// announced, through the spread below).
|
|
258
261
|
const field = useFieldError(error, invalid, hintParts.describedBy, rest["aria-invalid"]);
|
|
@@ -100,8 +100,13 @@ export const buttonVariantClasses: Record<ButtonVariant, string> = {
|
|
|
100
100
|
// the base's focus ring stays: those copies had `outline-none` and no ring, so
|
|
101
101
|
// a keyboard user tabbing onto them saw nothing at all. `rounded-sm` keeps that
|
|
102
102
|
// ring hugging the word instead of drawing a pill round it.
|
|
103
|
+
//
|
|
104
|
+
// Under More contrast the word reads `--brand-muted`, the brand AS TEXT (0.36,
|
|
105
|
+
// colour-roles §13.3 decision 8): `--brand` is solved as a mark (3:1), and on More
|
|
106
|
+
// contrast's darker light well Moss's read 3.68:1; the derived dark brands sit at 3.0
|
|
107
|
+
// as text too. Both modes, since `contrast:` does not split them.
|
|
103
108
|
link:
|
|
104
|
-
"rounded-sm p-0 bg-transparent text-brand underline-offset-4 hover:underline focus-visible:ring-brand",
|
|
109
|
+
"rounded-sm p-0 bg-transparent text-brand underline-offset-4 hover:underline focus-visible:ring-brand contrast:text-brand-muted",
|
|
105
110
|
};
|
|
106
111
|
|
|
107
112
|
/**
|
|
@@ -132,11 +137,14 @@ export const buttonVariantClasses: Record<ButtonVariant, string> = {
|
|
|
132
137
|
*/
|
|
133
138
|
export type ButtonTone = "default" | "muted" | "danger";
|
|
134
139
|
|
|
140
|
+
// `contrast:text-secondary` restates the tone's own colour under the `contrast:` variant,
|
|
141
|
+
// so tailwind-merge drops the link's `contrast:text-brand-muted` (0.36): a quiet link
|
|
142
|
+
// stays quiet under More contrast. On `ghost` it reads the same either way.
|
|
135
143
|
const BUTTON_TONES: Record<Exclude<ButtonTone, "default">, string> = {
|
|
136
144
|
muted:
|
|
137
|
-
"text-secondary hover:text-primary disabled:hover:text-secondary",
|
|
145
|
+
"text-secondary hover:text-primary disabled:hover:text-secondary contrast:text-secondary",
|
|
138
146
|
danger:
|
|
139
|
-
"text-secondary hover:text-danger focus-visible:ring-danger-strong disabled:hover:text-secondary",
|
|
147
|
+
"text-secondary hover:text-danger focus-visible:ring-danger-strong disabled:hover:text-secondary contrast:text-secondary",
|
|
140
148
|
};
|
|
141
149
|
|
|
142
150
|
/** Only the two transparent variants take every tone; see {@link ButtonTone}. */
|
|
@@ -158,9 +166,11 @@ const BUTTON_BOXED_DANGER: Partial<Record<ButtonVariant, string>> = {
|
|
|
158
166
|
|
|
159
167
|
// `pressed` on a `link`: the brand colour and a heavier weight, which is exactly what
|
|
160
168
|
// lenkbank's "All speeds" toggle paints by hand (gear/hysteresis-charts.tsx:482). After
|
|
161
|
-
// the tone, so a pressed muted link is brand, and an unpressed one is quiet grey.
|
|
169
|
+
// the tone, so a pressed muted link is brand, and an unpressed one is quiet grey. Under
|
|
170
|
+
// More contrast the brand as text, `--brand-muted`, at rest and hovered (0.36, as the
|
|
171
|
+
// `link` variant), whatever the tone.
|
|
162
172
|
export const BUTTON_LINK_PRESSED =
|
|
163
|
-
"font-medium text-brand hover:text-brand disabled:hover:text-brand";
|
|
173
|
+
"font-medium text-brand hover:text-brand disabled:hover:text-brand contrast:text-brand-muted contrast:hover:text-brand-muted contrast:disabled:hover:text-brand-muted";
|
|
164
174
|
|
|
165
175
|
/** The second argument of {@link buttonClasses} in its options form. */
|
|
166
176
|
export interface ButtonClassesOptions {
|
|
@@ -4,7 +4,14 @@ import { cn } from "../lib/cn";
|
|
|
4
4
|
import { Checkbox } from "./checkbox";
|
|
5
5
|
import { hasContent } from "../lib/presence";
|
|
6
6
|
import { useCommitReason, type CommitScope } from "./write-lock";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
FIELD_ERROR_CLASS,
|
|
9
|
+
fieldHelpMark,
|
|
10
|
+
mergeDescribedBy,
|
|
11
|
+
RequiredStarOnLegend,
|
|
12
|
+
useInlineHint,
|
|
13
|
+
type FieldHelpProps,
|
|
14
|
+
} from "./field-parts";
|
|
8
15
|
|
|
9
16
|
/** One box of a {@link CheckboxGroup}. */
|
|
10
17
|
export interface CheckboxGroupOption<T extends string = string> {
|
|
@@ -22,7 +29,7 @@ export interface CheckboxGroupOption<T extends string = string> {
|
|
|
22
29
|
* reader where the dialog's title already says it to the eye. */
|
|
23
30
|
export type CheckboxGroupLegendVisibility = "visible" | "sr-only";
|
|
24
31
|
|
|
25
|
-
export interface CheckboxGroupProps<T extends string = string> {
|
|
32
|
+
export interface CheckboxGroupProps<T extends string = string> extends FieldHelpProps {
|
|
26
33
|
/** The question the boxes answer — the fieldset's `<legend>`, which names the group.
|
|
27
34
|
* Without one, pass `aria-label`. */
|
|
28
35
|
legend?: ReactNode;
|
|
@@ -64,6 +71,8 @@ export interface CheckboxGroupProps<T extends string = string> {
|
|
|
64
71
|
/**
|
|
65
72
|
* Standing advice for the group, under the boxes, attached to the fieldset with
|
|
66
73
|
* `aria-describedby` before the error — the field anatomy every kit field keeps.
|
|
74
|
+
* A `help` beside it (0.36) is a "?" after the legend — with a visible legend only —
|
|
75
|
+
* and names nothing: the fieldset is then named by the legend's words alone.
|
|
67
76
|
*/
|
|
68
77
|
hint?: ReactNode;
|
|
69
78
|
/** What is wrong with the set, in the caller's words. Under the boxes, on the
|
|
@@ -128,6 +137,7 @@ export function CheckboxGroup<T extends string = string>({
|
|
|
128
137
|
disabled,
|
|
129
138
|
required,
|
|
130
139
|
hint,
|
|
140
|
+
help,
|
|
131
141
|
error,
|
|
132
142
|
invalid,
|
|
133
143
|
commit,
|
|
@@ -142,7 +152,11 @@ export function CheckboxGroup<T extends string = string>({
|
|
|
142
152
|
const errorId = `${generated}-error`;
|
|
143
153
|
// §4: a FieldHint under the group is its words at Large and on a touch screen, not a
|
|
144
154
|
// lone "?" (DUP-2, refactor audit 2026-10). A HelpPopover stays a "?".
|
|
145
|
-
|
|
155
|
+
// A `help` "?" after a VISIBLE legend (0.36): the label line a group has. Its FieldHint
|
|
156
|
+
// hint is then its words at every size, as everywhere a help stands beside one.
|
|
157
|
+
const helpMark = fieldHelpMark(legend !== undefined && legendVisibility === "visible" ? help : undefined, legend);
|
|
158
|
+
const legendTextId = `${generated}-legend`;
|
|
159
|
+
const shownHint = useInlineHint(hint, helpMark !== undefined);
|
|
146
160
|
const showHint = hasContent(shownHint);
|
|
147
161
|
const showError = hasContent(error);
|
|
148
162
|
const isInvalid = Boolean(invalid) || showError;
|
|
@@ -169,6 +183,9 @@ export function CheckboxGroup<T extends string = string>({
|
|
|
169
183
|
<fieldset
|
|
170
184
|
id={id}
|
|
171
185
|
aria-label={ariaLabel}
|
|
186
|
+
// With a help "?" inside the legend, the legend's WORDS name the group: a legend
|
|
187
|
+
// names its fieldset by its whole content, the "?"'s "About …" included.
|
|
188
|
+
aria-labelledby={helpMark !== undefined && ariaLabel === undefined ? legendTextId : undefined}
|
|
172
189
|
aria-describedby={mergeDescribedBy(ariaDescribedBy, showHint && hintId, showError && errorId)}
|
|
173
190
|
disabled={disabled && !locked}
|
|
174
191
|
// `relative` holds an `sr-only` legend (sr-only-containment.test); `min-w-0`
|
|
@@ -184,12 +201,13 @@ export function CheckboxGroup<T extends string = string>({
|
|
|
184
201
|
: cn("mb-2 p-0 text-sm font-medium text-primary", (disabled || locked) && "opacity-60")
|
|
185
202
|
}
|
|
186
203
|
>
|
|
187
|
-
{legend}
|
|
204
|
+
{helpMark === undefined ? legend : <span id={legendTextId}>{legend}</span>}
|
|
188
205
|
{required && (
|
|
189
206
|
<span aria-hidden className="ms-0.5 text-danger">
|
|
190
207
|
*
|
|
191
208
|
</span>
|
|
192
209
|
)}
|
|
210
|
+
{helpMark !== undefined && <span className="ms-1 inline-flex align-middle font-normal">{helpMark}</span>}
|
|
193
211
|
</legend>
|
|
194
212
|
)}
|
|
195
213
|
<RequiredStarOnLegend.Provider value={true}>
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
TableHeaderCell,
|
|
32
32
|
TableRow,
|
|
33
33
|
} from "./table";
|
|
34
|
+
import type { FieldHelpProps } from "./field-parts";
|
|
34
35
|
import { Select } from "./select";
|
|
35
36
|
import { Textarea } from "./textarea";
|
|
36
37
|
import { DEFAULT_COLUMN_MAPPER_LABELS, markedShort } from "./column-mapper-labels";
|
|
@@ -425,7 +426,7 @@ export function ColumnRoleTable<
|
|
|
425
426
|
|
|
426
427
|
// ── ColumnMapper ──────────────────────────────────────────────────────────────
|
|
427
428
|
|
|
428
|
-
export interface ColumnMapperProps<R extends string> {
|
|
429
|
+
export interface ColumnMapperProps<R extends string> extends FieldHelpProps {
|
|
429
430
|
/** The parts the columns play — translated labels, which are required, which must
|
|
430
431
|
* be numbers. See {@link ColumnRole}. */
|
|
431
432
|
roles: readonly ColumnRole<R>[];
|
|
@@ -440,7 +441,8 @@ export interface ColumnMapperProps<R extends string> {
|
|
|
440
441
|
onChange: (result: ColumnMapperResult<R> | null) => void;
|
|
441
442
|
/** The paste box's label. Default: `labels.paste`. */
|
|
442
443
|
label?: ReactNode;
|
|
443
|
-
/** The caption under the paste box. Default: `labels.pasteHint`.
|
|
444
|
+
/** The caption under the paste box. Default: `labels.pasteHint`. The paste box's
|
|
445
|
+
* `help` (0.36) is `help`. */
|
|
444
446
|
hint?: ReactNode;
|
|
445
447
|
/** What the file dialog offers, and what a drop is checked against. Default:
|
|
446
448
|
* `.csv,.tsv,.txt,.dat,.asc`. */
|
|
@@ -503,6 +505,7 @@ export function ColumnMapper<R extends string>({
|
|
|
503
505
|
onChange,
|
|
504
506
|
label,
|
|
505
507
|
hint,
|
|
508
|
+
help,
|
|
506
509
|
accept = DEFAULT_ACCEPT,
|
|
507
510
|
maxSize,
|
|
508
511
|
defaultText = "",
|
|
@@ -605,6 +608,7 @@ export function ColumnMapper<R extends string>({
|
|
|
605
608
|
<Textarea
|
|
606
609
|
label={label ?? labels.paste}
|
|
607
610
|
hint={hint ?? labels.pasteHint}
|
|
611
|
+
help={help}
|
|
608
612
|
rows={5}
|
|
609
613
|
wrap="off"
|
|
610
614
|
spellCheck={false}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
useFieldHint,
|
|
23
23
|
useLockReason,
|
|
24
24
|
type DisabledReasonDisplay,
|
|
25
|
+
type FieldHelpProps,
|
|
25
26
|
} from "./field-parts";
|
|
26
27
|
import type { CommitScope } from "./write-lock";
|
|
27
28
|
import { lockedKeyDown, rowClass, SuggestionList, usePrimaryPressOnly } from "./combobox-suggestions";
|
|
@@ -38,7 +39,8 @@ export type { InlineEntityComboboxProps } from "./inline-entity-combobox";
|
|
|
38
39
|
* the DOM events of the wrapper. Everything else a wrapper needs reaches the root.
|
|
39
40
|
*/
|
|
40
41
|
export interface ComboboxProps
|
|
41
|
-
extends Omit<ComponentPropsWithoutRef<"div">, "id" | "onChange" | "onBlur" | "onSubmit"
|
|
42
|
+
extends Omit<ComponentPropsWithoutRef<"div">, "id" | "onChange" | "onBlur" | "onSubmit">,
|
|
43
|
+
FieldHelpProps {
|
|
42
44
|
value: string;
|
|
43
45
|
onChange: (v: string) => void;
|
|
44
46
|
/** Suggestion pool (e.g. existing payee names). */
|
|
@@ -186,6 +188,7 @@ export function Combobox(props: ComboboxProps) {
|
|
|
186
188
|
invalid,
|
|
187
189
|
error,
|
|
188
190
|
hint,
|
|
191
|
+
help,
|
|
189
192
|
disabled,
|
|
190
193
|
disabledReason,
|
|
191
194
|
disabledReasonDisplay,
|
|
@@ -204,7 +207,7 @@ export function Combobox(props: ComboboxProps) {
|
|
|
204
207
|
"aria-required": ariaRequired,
|
|
205
208
|
...rest
|
|
206
209
|
} = props;
|
|
207
|
-
const hintParts = useFieldHint(hint, ariaDescribedBy);
|
|
210
|
+
const hintParts = useFieldHint(hint, ariaDescribedBy, { help, name: label ?? ariaLabel });
|
|
208
211
|
const field = useFieldError(error, invalid, hintParts.describedBy, ariaInvalid);
|
|
209
212
|
const generated = useId();
|
|
210
213
|
const fieldId = id ?? generated;
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
useFieldMessages,
|
|
20
20
|
useLockReason,
|
|
21
21
|
type DisabledReasonDisplay,
|
|
22
|
+
type FieldHelpProps,
|
|
22
23
|
} from "./field-parts";
|
|
23
24
|
import { CurrencyFlag } from "./currency-select";
|
|
24
25
|
import { ComboboxPanel, useComboboxCore, type ComboOption } from "./combobox-core";
|
|
@@ -66,7 +67,8 @@ export { DEFAULT_COUNTRY_SELECT_LABELS };
|
|
|
66
67
|
* `null` of a clear cannot reach a handler that was never told about it.
|
|
67
68
|
*/
|
|
68
69
|
export interface CountrySelectProps<Clearable extends boolean = false>
|
|
69
|
-
extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "defaultValue"
|
|
70
|
+
extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "defaultValue">,
|
|
71
|
+
FieldHelpProps {
|
|
70
72
|
/** The chosen country as an ISO 3166-1 alpha-2 code ("CH"), in either case;
|
|
71
73
|
* `null`, `undefined` or `""` when none is. A code outside the list still shows
|
|
72
74
|
* its name — the list restricts what can be PICKED, not what can be shown. */
|
|
@@ -249,6 +251,7 @@ export function CountrySelect<Clearable extends boolean = false>({
|
|
|
249
251
|
flags = false,
|
|
250
252
|
label,
|
|
251
253
|
hint,
|
|
254
|
+
help,
|
|
252
255
|
error,
|
|
253
256
|
invalid,
|
|
254
257
|
placeholder,
|
|
@@ -359,6 +362,8 @@ export function CountrySelect<Clearable extends boolean = false>({
|
|
|
359
362
|
// the error — Select's order. Through useFieldMessages since 0.34 (DUP-2, DUP-8).
|
|
360
363
|
const field = useFieldMessages({
|
|
361
364
|
hint,
|
|
365
|
+
help,
|
|
366
|
+
name: label ?? ariaLabel,
|
|
362
367
|
error,
|
|
363
368
|
invalid,
|
|
364
369
|
describedBy: ariaDescribedBy,
|
|
@@ -2,7 +2,7 @@ import { useCallback, useId } from "react";
|
|
|
2
2
|
import type { ComponentPropsWithoutRef, ReactNode, Ref } from "react";
|
|
3
3
|
import { FieldChevron, FIELD_TRIGGER, FIELD_INVALID, FIELD_FLOATING_PAD } from "./field-base";
|
|
4
4
|
import { FieldLabel } from "./field-parts";
|
|
5
|
-
import { assignRef, EndHintRow, useFieldMessages } from "./field-parts";
|
|
5
|
+
import { assignRef, EndHintRow, useFieldMessages, type FieldHelpProps } from "./field-parts";
|
|
6
6
|
import { cn } from "../lib/cn";
|
|
7
7
|
import { useComboboxCore } from "./combobox-core";
|
|
8
8
|
import {
|
|
@@ -29,7 +29,7 @@ export type { CurrencyFlagProps, CurrencyOption };
|
|
|
29
29
|
|
|
30
30
|
/** `onChange` is the kit's — "a currency was picked", carrying the code — rather
|
|
31
31
|
* than the div's form event. */
|
|
32
|
-
export interface CurrencySelectProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange"
|
|
32
|
+
export interface CurrencySelectProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange">, FieldHelpProps {
|
|
33
33
|
value: string;
|
|
34
34
|
onChange: (code: string) => void;
|
|
35
35
|
placeholder?: string;
|
|
@@ -106,6 +106,7 @@ export function CurrencySelect({
|
|
|
106
106
|
invalid,
|
|
107
107
|
error,
|
|
108
108
|
hint,
|
|
109
|
+
help,
|
|
109
110
|
labels,
|
|
110
111
|
currencyNames,
|
|
111
112
|
"aria-label": ariaLabel,
|
|
@@ -143,7 +144,15 @@ export function CurrencySelect({
|
|
|
143
144
|
// on the wrapper `<div>` through the spread, where nothing reads it. The caller's ids
|
|
144
145
|
// first, then the caption, then the error — Select's order, through useFieldMessages
|
|
145
146
|
// since 0.34 (DUP-2, DUP-8).
|
|
146
|
-
const field = useFieldMessages({
|
|
147
|
+
const field = useFieldMessages({
|
|
148
|
+
hint,
|
|
149
|
+
help,
|
|
150
|
+
name: label ?? ariaLabel,
|
|
151
|
+
error,
|
|
152
|
+
invalid,
|
|
153
|
+
describedBy: ariaDescribedBy,
|
|
154
|
+
ariaInvalid,
|
|
155
|
+
});
|
|
147
156
|
const { labelHint, describedBy, isInvalid } = field;
|
|
148
157
|
|
|
149
158
|
// The trigger's NAME. A combobox does not take its name from its content, and the
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { splitTriggerAria } from "./trigger-aria";
|
|
20
20
|
import type { TriggerAria } from "./trigger-aria";
|
|
21
21
|
import { FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from "./field-base";
|
|
22
|
-
import { assignRef, FieldLabelLine, LABEL_ABOVE_BOX } from "./field-parts";
|
|
22
|
+
import { assignRef, FieldLabelLine, LABEL_ABOVE_BOX, type FieldHelpProps } from "./field-parts";
|
|
23
23
|
import { FullBleedDialog } from "./full-bleed-dialog";
|
|
24
24
|
import type { RemLength } from "../hooks/use-breakpoint";
|
|
25
25
|
import type { MiniCalendarProps } from "./mini-calendar";
|
|
@@ -34,7 +34,7 @@ import { Popover } from "./popover";
|
|
|
34
34
|
* `FormEvent`, while both pickers answer with ISO date strings. Each declares its own
|
|
35
35
|
* (date-picker.tsx, date-range-picker.tsx).
|
|
36
36
|
*/
|
|
37
|
-
export interface DatePickerBaseProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange"
|
|
37
|
+
export interface DatePickerBaseProps extends Omit<ComponentPropsWithoutRef<"div">, "onChange">, FieldHelpProps {
|
|
38
38
|
/** BCP 47 tag for the trigger's date and the calendar. Falls back to
|
|
39
39
|
* `<UiKitProvider locale>`, then to the runtime's default. */
|
|
40
40
|
locale?: string;
|
|
@@ -170,6 +170,7 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
170
170
|
disabled,
|
|
171
171
|
invalid,
|
|
172
172
|
hint,
|
|
173
|
+
help,
|
|
173
174
|
error,
|
|
174
175
|
// Named so it reaches the field (and so its trigger) when a step/today row is the
|
|
175
176
|
// root and takes the rest of the caller's props.
|
|
@@ -185,6 +186,8 @@ export function DatePicker(props: DatePickerProps) {
|
|
|
185
186
|
// (the element a screen reader is on) after any description of the caller's own.
|
|
186
187
|
const messages = useFieldMessages({
|
|
187
188
|
hint,
|
|
189
|
+
help,
|
|
190
|
+
name: label ?? rest["aria-label"],
|
|
188
191
|
error,
|
|
189
192
|
invalid,
|
|
190
193
|
describedBy: rest["aria-describedby"],
|
|
@@ -394,6 +394,7 @@ export function DateRangePicker(props: DateRangePickerProps) {
|
|
|
394
394
|
disabled,
|
|
395
395
|
invalid,
|
|
396
396
|
hint,
|
|
397
|
+
help,
|
|
397
398
|
error,
|
|
398
399
|
// The trigger's (API-3).
|
|
399
400
|
ref,
|
|
@@ -404,6 +405,8 @@ export function DateRangePicker(props: DateRangePickerProps) {
|
|
|
404
405
|
const render = useTriggerFormat("dateRangePicker", locale, formatOptions, formatValue);
|
|
405
406
|
const messages = useFieldMessages({
|
|
406
407
|
hint,
|
|
408
|
+
help,
|
|
409
|
+
name: label ?? rest["aria-label"],
|
|
407
410
|
error,
|
|
408
411
|
invalid,
|
|
409
412
|
describedBy: rest["aria-describedby"],
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
useFieldHint,
|
|
26
26
|
useLockReason,
|
|
27
27
|
type DisabledReasonDisplay,
|
|
28
|
+
type FieldHelpProps,
|
|
28
29
|
} from "./field-parts";
|
|
29
30
|
import { useCommitReason, type CommitScope } from "./write-lock";
|
|
30
31
|
|
|
@@ -33,7 +34,8 @@ export type { ComboClearValue, ComboOption } from "./combobox-core";
|
|
|
33
34
|
/** `onChange` is the kit's — "a selection was made", carrying values — rather
|
|
34
35
|
* than the div's form event, so the DOM's spelling of it is omitted. */
|
|
35
36
|
export interface EntityComboboxProps<V extends string | number, C extends ComboClearValue = null>
|
|
36
|
-
extends Omit<ComponentPropsWithoutRef<"div">, "onChange"
|
|
37
|
+
extends Omit<ComponentPropsWithoutRef<"div">, "onChange">,
|
|
38
|
+
FieldHelpProps {
|
|
37
39
|
/** Selected id; `null`, `undefined` or `clearValue` when nothing is selected. */
|
|
38
40
|
value: V | C | null | undefined;
|
|
39
41
|
/** Selecting an option emits its value; the clear button emits `clearValue`. */
|
|
@@ -182,6 +184,7 @@ export function EntityCombobox<V extends string | number, C extends ComboClearVa
|
|
|
182
184
|
invalid,
|
|
183
185
|
error,
|
|
184
186
|
hint,
|
|
187
|
+
help,
|
|
185
188
|
filter,
|
|
186
189
|
minChars,
|
|
187
190
|
debounceMs,
|
|
@@ -206,7 +209,7 @@ export function EntityCombobox<V extends string | number, C extends ComboClearVa
|
|
|
206
209
|
minChars,
|
|
207
210
|
debounceMs,
|
|
208
211
|
});
|
|
209
|
-
const hintParts = useFieldHint(hint, ariaDescribedBy);
|
|
212
|
+
const hintParts = useFieldHint(hint, ariaDescribedBy, { help, name: label ?? ariaLabel });
|
|
210
213
|
const field = useFieldError(error, invalid, hintParts.describedBy, ariaInvalid);
|
|
211
214
|
// The props are the per-instance overrides, the provider the app-wide ones; a
|
|
212
215
|
// prop left `undefined` falls through to the provider rather than masking it.
|