@viax.io/uxm 4.36.0 → 4.37.1
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/CHANGELOG.md +14 -0
- package/README.md +74 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/previews/index.cjs +3 -0
- package/dist/previews/index.cjs.map +1 -1
- package/dist/previews/index.d.ts +3 -2
- package/dist/previews/index.d.ts.map +1 -1
- package/dist/previews/index.js +2 -0
- package/dist/previews/index.js.map +1 -1
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.ts +2 -2
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/lib/registry/app.cjs +52 -0
- package/dist/studio/lib/registry/app.cjs.map +1 -1
- package/dist/studio/lib/registry/app.d.ts.map +1 -1
- package/dist/studio/lib/registry/app.js +52 -0
- package/dist/studio/lib/registry/app.js.map +1 -1
- package/dist/studio/lib/registry/buttons.cjs +2 -2
- package/dist/studio/lib/registry/buttons.cjs.map +1 -1
- package/dist/studio/lib/registry/buttons.js +2 -2
- package/dist/studio/lib/registry/buttons.js.map +1 -1
- package/dist/studio/lib/registry/composite.cjs +1 -1
- package/dist/studio/lib/registry/composite.cjs.map +1 -1
- package/dist/studio/lib/registry/composite.js +1 -1
- package/dist/studio/lib/registry/composite.js.map +1 -1
- package/dist/studio/lib/registry/diagram.cjs +1 -1
- package/dist/studio/lib/registry/diagram.cjs.map +1 -1
- package/dist/studio/lib/registry/diagram.js +1 -1
- package/dist/studio/lib/registry/diagram.js.map +1 -1
- package/dist/studio/lib/registry/display.cjs +2 -2
- package/dist/studio/lib/registry/display.cjs.map +1 -1
- package/dist/studio/lib/registry/display.js +2 -2
- package/dist/studio/lib/registry/display.js.map +1 -1
- package/dist/studio/lib/registry/feedback.cjs +3 -3
- package/dist/studio/lib/registry/feedback.cjs.map +1 -1
- package/dist/studio/lib/registry/feedback.js +3 -3
- package/dist/studio/lib/registry/feedback.js.map +1 -1
- package/dist/studio/lib/registry/inputs.cjs +12 -12
- package/dist/studio/lib/registry/inputs.cjs.map +1 -1
- package/dist/studio/lib/registry/inputs.js +12 -12
- package/dist/studio/lib/registry/inputs.js.map +1 -1
- package/dist/studio/lib/types.cjs.map +1 -1
- package/dist/studio/persistence/generate-css.cjs +15 -0
- package/dist/studio/persistence/generate-css.cjs.map +1 -1
- package/dist/studio/persistence/generate-css.d.ts.map +1 -1
- package/dist/studio/persistence/generate-css.js +15 -0
- package/dist/studio/persistence/generate-css.js.map +1 -1
- package/dist/studio/shell/canvas.cjs +1 -0
- package/dist/studio/shell/canvas.cjs.map +1 -1
- package/dist/studio/shell/canvas.d.ts.map +1 -1
- package/dist/studio/shell/canvas.js +2 -0
- package/dist/studio/shell/canvas.js.map +1 -1
- package/dist/ui/bulk-action-bar/bulk-action-bar.css +3 -0
- package/dist/ui/calendar/calendar.cjs +3 -1
- package/dist/ui/calendar/calendar.cjs.map +1 -1
- package/dist/ui/calendar/calendar.css +2 -1
- package/dist/ui/calendar/calendar.d.ts +6 -2
- package/dist/ui/calendar/calendar.d.ts.map +1 -1
- package/dist/ui/calendar/calendar.js +3 -1
- package/dist/ui/calendar/calendar.js.map +1 -1
- package/dist/ui/currency-input/currency-input.cjs +3 -1
- package/dist/ui/currency-input/currency-input.cjs.map +1 -1
- package/dist/ui/currency-input/currency-input.d.ts +4 -2
- package/dist/ui/currency-input/currency-input.d.ts.map +1 -1
- package/dist/ui/currency-input/currency-input.js +3 -1
- package/dist/ui/currency-input/currency-input.js.map +1 -1
- package/dist/ui/date-input/date-input.cjs +2 -1
- package/dist/ui/date-input/date-input.cjs.map +1 -1
- package/dist/ui/date-input/date-input.css +1 -1
- package/dist/ui/date-input/date-input.d.ts +11 -1
- package/dist/ui/date-input/date-input.d.ts.map +1 -1
- package/dist/ui/date-input/date-input.js +2 -1
- package/dist/ui/date-input/date-input.js.map +1 -1
- package/dist/ui/editable-cell/editable-cell.cjs +20 -6
- package/dist/ui/editable-cell/editable-cell.cjs.map +1 -1
- package/dist/ui/editable-cell/editable-cell.d.ts +24 -1
- package/dist/ui/editable-cell/editable-cell.d.ts.map +1 -1
- package/dist/ui/editable-cell/editable-cell.js +20 -6
- package/dist/ui/editable-cell/editable-cell.js.map +1 -1
- package/dist/ui/file-upload/file-upload.cjs +36 -9
- package/dist/ui/file-upload/file-upload.cjs.map +1 -1
- package/dist/ui/file-upload/file-upload.d.ts +46 -4
- package/dist/ui/file-upload/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload/file-upload.js +36 -9
- package/dist/ui/file-upload/file-upload.js.map +1 -1
- package/dist/ui/index.cjs +12 -2
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +5 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +7 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input/input.cjs +8 -4
- package/dist/ui/input/input.cjs.map +1 -1
- package/dist/ui/input/input.css +4 -0
- package/dist/ui/input/input.d.ts.map +1 -1
- package/dist/ui/input/input.js +8 -4
- package/dist/ui/input/input.js.map +1 -1
- package/dist/ui/language-switcher/index.cjs +29 -0
- package/dist/ui/language-switcher/index.cjs.map +1 -0
- package/dist/ui/language-switcher/index.d.ts +3 -0
- package/dist/ui/language-switcher/index.d.ts.map +1 -0
- package/dist/ui/language-switcher/index.js +5 -0
- package/dist/ui/language-switcher/index.js.map +1 -0
- package/dist/ui/language-switcher/language-switcher-preview.cjs +137 -0
- package/dist/ui/language-switcher/language-switcher-preview.cjs.map +1 -0
- package/dist/ui/language-switcher/language-switcher-preview.d.ts +3 -0
- package/dist/ui/language-switcher/language-switcher-preview.d.ts.map +1 -0
- package/dist/ui/language-switcher/language-switcher-preview.js +113 -0
- package/dist/ui/language-switcher/language-switcher-preview.js.map +1 -0
- package/dist/ui/language-switcher/language-switcher.cjs +127 -0
- package/dist/ui/language-switcher/language-switcher.cjs.map +1 -0
- package/dist/ui/language-switcher/language-switcher.css +95 -0
- package/dist/ui/language-switcher/language-switcher.d.ts +73 -0
- package/dist/ui/language-switcher/language-switcher.d.ts.map +1 -0
- package/dist/ui/language-switcher/language-switcher.js +103 -0
- package/dist/ui/language-switcher/language-switcher.js.map +1 -0
- package/dist/ui/locale/index.cjs +33 -0
- package/dist/ui/locale/index.cjs.map +1 -0
- package/dist/ui/locale/index.d.ts +3 -0
- package/dist/ui/locale/index.d.ts.map +1 -0
- package/dist/ui/locale/index.js +7 -0
- package/dist/ui/locale/index.js.map +1 -0
- package/dist/ui/locale/locale.cjs +43 -0
- package/dist/ui/locale/locale.cjs.map +1 -0
- package/dist/ui/locale/locale.d.ts +49 -0
- package/dist/ui/locale/locale.d.ts.map +1 -0
- package/dist/ui/locale/locale.js +17 -0
- package/dist/ui/locale/locale.js.map +1 -0
- package/dist/ui/page-shell/page-shell.cjs.map +1 -1
- package/dist/ui/page-shell/page-shell.d.ts +1 -1
- package/dist/ui/page-shell/page-shell.d.ts.map +1 -1
- package/dist/ui/page-shell/page-shell.js.map +1 -1
- package/dist/ui/range-slider/range-slider.cjs +7 -3
- package/dist/ui/range-slider/range-slider.cjs.map +1 -1
- package/dist/ui/range-slider/range-slider.d.ts +17 -1
- package/dist/ui/range-slider/range-slider.d.ts.map +1 -1
- package/dist/ui/range-slider/range-slider.js +7 -3
- package/dist/ui/range-slider/range-slider.js.map +1 -1
- package/dist/ui/stat-card/stat-card.cjs +1 -1
- package/dist/ui/stat-card/stat-card.cjs.map +1 -1
- package/dist/ui/stat-card/stat-card.css +4 -0
- package/dist/ui/stat-card/stat-card.js +1 -1
- package/dist/ui/stat-card/stat-card.js.map +1 -1
- package/dist/ui/styles.css +2 -1
- package/dist/ui/toast/toast.css +3 -1
- package/package.json +4 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/input/input.tsx"],"sourcesContent":["import {\n Children,\n isValidElement,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n type ChangeEvent,\n type HTMLAttributes,\n type InputHTMLAttributes,\n type ReactNode,\n type Ref,\n type SelectHTMLAttributes,\n type TextareaHTMLAttributes,\n} from 'react';\n\nimport { cn, mergeDescribedBy, mergeRefs } from '@/helpers';\n\nimport { ButtonGhost } from '../button';\nimport { FieldError } from '../field-error';\nimport { Icon } from '../icon';\nimport { IconButton } from '../icon-button';\nimport { DEFAULT_MULTI_REQUIRED_MESSAGE, Listbox, MultiListbox } from '../listbox';\n\nimport { clearFieldValue } from './clear-field-value';\n\nexport interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {\n /**\n * When set to a non-empty string, the field renders in its error state:\n * red border (`.uxm-input-text--error`), `aria-invalid`, and the message\n * rendered below the input. Omit (or pass an empty string) for the normal\n * state. The atom owns the message so it works standalone — search bars,\n * inline edits, custom layouts — without requiring a `FormField` wrapper.\n */\n error?: string;\n /**\n * Show a clear (✕) button at the trailing edge when the input has content.\n * **Defaults to `true`** — a free-text field is always safe to clear (you\n * can type/delete anyway), so the affordance is on by default; pass\n * `clearable={false}` to opt out. The ✕ self-clears (resets the field and\n * fires `onChange` with \"\"), so no `onClear` is needed — any controlled\n * `value` + `onChange` usage gets it for free.\n */\n clearable?: boolean;\n /**\n * Optional override for the clear action. By default the field clears\n * itself (and notifies via `onChange`); pass `onClear` only when you need\n * custom reset logic beyond emptying the value.\n */\n onClear?: () => void;\n /** Accessible name for the clear button. Default `\"Clear\"`. */\n clearLabel?: string;\n /**\n * Handle to the underlying `<input>` element, merged with the atom's own\n * internal ref (the self-clear keeps working). Use this to focus, select,\n * measure, or insert text at the caret from a consumer. A plain React `ref`\n * on `<TextInput>` does NOT reach the element — it's consumed by `{...rest}`\n * and overridden by the managed ref — so pass `inputRef` instead.\n */\n inputRef?: Ref<HTMLInputElement>;\n}\n\n\nexport function TextInput({\n className,\n type = 'text',\n error,\n clearable = true,\n onClear,\n clearLabel = 'Clear',\n value,\n defaultValue,\n onChange,\n disabled,\n inputRef,\n ...rest\n}: TextInputProps) {\n const errorId = useId();\n const innerRef = useRef<HTMLInputElement>(null);\n const isControlled = value !== undefined;\n const [hasTextUncontrolled, setHasTextUncontrolled] = useState(\n () => typeof defaultValue === 'string' && defaultValue.length > 0,\n );\n const hasValue = isControlled ? String(value).length > 0 : hasTextUncontrolled;\n const showClear = !!clearable && hasValue && !disabled;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setHasTextUncontrolled(e.target.value.length > 0);\n onChange?.(e);\n };\n const handleClear = () => {\n // Self-clear path (clearFieldValue) already refocuses the field; only the\n // onClear-override path needs an explicit focus. Avoids a double focus().\n if (onClear) {\n onClear();\n innerRef.current?.focus();\n } else {\n clearFieldValue(innerRef.current);\n }\n if (!isControlled) setHasTextUncontrolled(false);\n };\n\n // Merge the consumer's `inputRef` with the atom's `innerRef` (the self-clear\n // relies on `innerRef`), so both get the element. Memoised on `inputRef` so\n // the callback identity is stable and React doesn't detach/reattach.\n const setInputRef = useMemo(() => mergeRefs(innerRef, inputRef), [inputRef]);\n\n const input = (\n // `{...rest}` is spread FIRST so the managed props below always win — a raw\n // consumer `ref` can't clobber the merged ref (which the self-clear relies\n // on); the sanctioned handle is `inputRef`, merged in via `setInputRef`.\n // `defaultValue` is only forwarded when uncontrolled, so value+defaultValue\n // are never both set.\n <input\n {...rest}\n ref={setInputRef}\n type={type}\n value={value}\n defaultValue={isControlled ? undefined : defaultValue}\n onChange={handleChange}\n disabled={disabled}\n className={cn(\n 'uxm-input-text',\n error && 'uxm-input-text--error',\n clearable && 'uxm-input-text--clearable',\n className,\n )}\n aria-invalid={error ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], error ? errorId : undefined)}\n />\n );\n\n return (\n <>\n {clearable ? (\n // Layout-only wrapper so the clear button can sit absolutely at the\n // trailing edge. The visible chrome stays on `.uxm-input-text` (the\n // input itself), so save-emitted `--uxm-input-text-*` vars still land.\n <div className=\"uxm-input-text__wrap\">\n {input}\n {showClear && (\n <IconButton\n onClick={handleClear}\n className=\"uxm-field-clear uxm-input-text__clear\"\n aria-label={clearLabel}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n </div>\n ) : (\n input\n )}\n {error && (\n <FieldError id={errorId} className=\"uxm-input-text__error-message\">\n {error}\n </FieldError>\n )}\n </>\n );\n}\n// Static marker so FormField only forwards its `error` prop into children\n// that accept one (avoids React unknown-prop warnings on non-input children).\nTextInput.hasError = true;\n\nexport interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n /** See `TextInputProps.error`. */\n error?: string;\n /** See `TextInputProps.clearable`. Defaults to `true`; ✕ sits at the top-right corner. */\n clearable?: boolean;\n /** See `TextInputProps.onClear` — optional override; the field self-clears otherwise. */\n onClear?: () => void;\n /** See `TextInputProps.clearLabel`. Default `\"Clear\"`. */\n clearLabel?: string;\n /**\n * Handle to the underlying `<textarea>` element, merged with the atom's own\n * internal ref (the self-clear keeps working). Use it to focus, select, or\n * insert text at the caret (e.g. drop-a-variable-into-a-formula). A plain\n * React `ref` on `<Textarea>` does NOT reach the element — pass\n * `textareaRef` instead.\n */\n textareaRef?: Ref<HTMLTextAreaElement>;\n}\n\nexport function Textarea({\n className,\n error,\n clearable = true,\n onClear,\n clearLabel = 'Clear',\n value,\n defaultValue,\n onChange,\n disabled,\n textareaRef,\n ...rest\n}: TextareaProps) {\n const errorId = useId();\n const innerRef = useRef<HTMLTextAreaElement>(null);\n const isControlled = value !== undefined;\n const [hasTextUncontrolled, setHasTextUncontrolled] = useState(\n () => typeof defaultValue === 'string' && defaultValue.length > 0,\n );\n const hasValue = isControlled ? String(value).length > 0 : hasTextUncontrolled;\n const showClear = !!clearable && hasValue && !disabled;\n\n const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) setHasTextUncontrolled(e.target.value.length > 0);\n onChange?.(e);\n };\n const handleClear = () => {\n // See TextInput: clearFieldValue refocuses; only onClear needs explicit focus.\n if (onClear) {\n onClear();\n innerRef.current?.focus();\n } else {\n clearFieldValue(innerRef.current);\n }\n if (!isControlled) setHasTextUncontrolled(false);\n };\n\n // Merge the consumer's `textareaRef` with the atom's `innerRef` — see TextInput.\n const setTextareaRef = useMemo(() => mergeRefs(innerRef, textareaRef), [textareaRef]);\n\n const textarea = (\n // `{...rest}` first so managed props win; the sanctioned element handle is\n // `textareaRef`, merged in via `setTextareaRef`. `defaultValue` only when\n // uncontrolled — never both value+defaultValue.\n <textarea\n {...rest}\n ref={setTextareaRef}\n value={value}\n defaultValue={isControlled ? undefined : defaultValue}\n onChange={handleChange}\n disabled={disabled}\n className={cn(\n 'uxm-textarea',\n error && 'uxm-textarea--error',\n clearable && 'uxm-textarea--clearable',\n className,\n )}\n aria-invalid={error ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], error ? errorId : undefined)}\n />\n );\n\n return (\n <>\n {clearable ? (\n <div className=\"uxm-textarea__wrap\">\n {textarea}\n {showClear && (\n <IconButton\n onClick={handleClear}\n className=\"uxm-field-clear uxm-textarea__clear\"\n aria-label={clearLabel}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n </div>\n ) : (\n textarea\n )}\n {error && (\n <FieldError id={errorId} className=\"uxm-textarea__error-message\">\n {error}\n </FieldError>\n )}\n </>\n );\n}\nTextarea.hasError = true;\n\n/**\n * Native `<select>` attributes the atom still accepts verbatim. The five\n * omitted keys are the ones `mode` re-shapes: `value` / `defaultValue` /\n * `onChange` differ per mode, `required` gains its own message prop, and\n * `multiple` is replaced by `mode=\"multi\"`. Keeping this base means existing\n * `<Select onBlur={…} autoFocus data-testid={…}>` call sites — and any\n * `Omit<SelectProps, …>` in consumer code — keep compiling.\n */\ntype SelectNativeProps = Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n 'value' | 'defaultValue' | 'onChange' | 'required' | 'multiple'\n>;\n\n/** Props shared by both single- and multi-select modes. */\ninterface SelectCommonProps extends SelectNativeProps {\n /** `<option>` children — parsed into items (and a placeholder from `<option value=\"\" disabled>`). */\n children?: ReactNode;\n /**\n * When set to a non-empty string, the trigger renders in its error\n * state: red border (`.uxm-select-dropdown--error`), `aria-invalid` on\n * the combobox, and the message below the trigger. Omit (or pass an\n * empty string) for the normal state.\n */\n error?: string;\n /**\n * Whether the dropdown panel includes a search box. Forwarded to the\n * internal Listbox: `true`/`false` force it, `\"auto\"` (the default)\n * shows it only once the option count crosses the shared threshold —\n * short lists stay a plain picker, long ones become filterable, with no\n * change to the trigger. (For a search-first combobox with its own\n * trigger chrome — icon glyphs, dial codes — use SearchDropdown.)\n */\n searchable?: boolean | 'auto';\n /**\n * Require a selection. Clearing to empty is still allowed (input-family,\n * live model) but flags the error immediately. An explicit `error` prop\n * takes precedence. Single mode is only clearable with a placeholder\n * option; multi mode uses `clearable`.\n */\n required?: boolean;\n /**\n * Override the default required message. Defaults are per-mode:\n * single → `\"Select an option\"`, multi → `\"Select at least one option\"`\n * (the shared `DEFAULT_MULTI_REQUIRED_MESSAGE`).\n */\n requiredMessage?: string;\n /**\n * Accessible name for the trigger's clear button. Defaults are per-mode:\n * single → `\"Clear selection\"`, multi → `\"Clear all selections\"`.\n */\n clearLabel?: string;\n}\n\nexport interface SelectSingleProps extends SelectCommonProps {\n /** Single-choice (default) — one value, cleared only when a placeholder option exists. */\n mode?: 'single';\n value?: string;\n defaultValue?: string;\n /** Fires a synthesized `ChangeEvent` (native-`<select>`-compatible) so `e.target.value` works. */\n onChange?: (event: ChangeEvent<HTMLSelectElement>) => void;\n}\n\nexport interface SelectMultiProps extends SelectCommonProps {\n /** Multi-choice — an array value with a \"N selected\" trigger, live commit, checkbox rows. */\n mode: 'multi';\n value?: string[];\n defaultValue?: string[];\n /** Fires the full selected-value array on every toggle (live). */\n onChange?: (next: string[]) => void;\n /** Show a clear-all ✕ on the trigger AND a \"Clear all\" in the panel. */\n clearable?: boolean;\n}\n\nexport type SelectProps = SelectSingleProps | SelectMultiProps;\n\ninterface SelectItem {\n value: string;\n label: string;\n disabled: boolean;\n}\n\n/**\n * Parse `<option>` children into Listbox-friendly items. HTML semantics:\n * - `<option value=\"x\">Label</option>` → value \"x\", label \"Label\"\n * - `<option>Label</option>` → value \"Label\", label \"Label\" (label IS the value)\n * - `<option value=\"\" disabled>Placeholder</option>` → reserved as the\n * trigger placeholder text, not added to items (matches the common\n * React Select pattern).\n */\nfunction parseSelectOptions(children: ReactNode): {\n items: SelectItem[];\n placeholder: string | undefined;\n} {\n const items: SelectItem[] = [];\n let placeholder: string | undefined;\n\n Children.forEach(children, (child) => {\n if (\n !isValidElement<{\n value?: string | number;\n children?: ReactNode;\n disabled?: boolean;\n }>(child)\n ) {\n return;\n }\n if (child.type !== 'option') return;\n\n const text = String(child.props.children ?? '');\n const val = child.props.value !== undefined ? String(child.props.value) : text;\n const label = text || val;\n const disabled = child.props.disabled ?? false;\n\n if (val === '' && disabled && placeholder === undefined) {\n placeholder = label;\n return;\n }\n items.push({ value: val, label, disabled });\n });\n\n return { items, placeholder };\n}\n\n/**\n * Select Dropdown — internally a Listbox-backed picker. Its public\n * API stays compatible with the previous native-`<select>` shape:\n * pass `<option>` children, controlled (`value`) or uncontrolled\n * (`defaultValue`) state, and an `onChange(e)` handler that reads\n * `e.target.value`.\n *\n * The visible trigger keeps the same `.uxm-select-dropdown` class\n * (and all its registry-tuned visual chrome — bg, border, radius,\n * padding, font, per-state colors). The OPEN-state UI swaps from the\n * browser's native dropdown to a `<Listbox>` panel so it looks\n * identical across browsers and inherits the shared panel theming\n * from the `listbox` registry entry.\n *\n * `onChange` receives a synthetic `ChangeEvent` shape — most React\n * callers only read `e.target.value`, which is set correctly. For\n * new code, consider using `<Listbox>` directly with its native\n * `(item: T | null) => void` callback.\n */\nfunction SelectSingle({\n className,\n children,\n value,\n defaultValue,\n onChange,\n disabled,\n style,\n name,\n id,\n error,\n searchable = 'auto',\n required = false,\n requiredMessage,\n clearLabel = 'Clear selection',\n 'aria-label': ariaLabel,\n ...rest\n}: SelectSingleProps) {\n const errorId = useId();\n // Required is flagged (not blocked) on clear — input-family live model.\n // An explicit `error` prop wins over the internal required one.\n const [reqError, setReqError] = useState<string | null>(null);\n const shownError = error ?? reqError ?? undefined;\n // Uncontrolled state — only used when `value` is not provided. Matches\n // the native `<select>`'s controlled/uncontrolled duality.\n const [internal, setInternal] = useState<string>(() => String(defaultValue ?? ''));\n const isControlled = value !== undefined;\n const current = String(isControlled ? value : internal);\n\n const { items, placeholder } = useMemo(() => parseSelectOptions(children), [children]);\n const selected = useMemo(() => items.find((o) => o.value === current) ?? null, [items, current]);\n\n const commit = useCallback(\n (nextValue: string) => {\n if (!isControlled) setInternal(nextValue);\n if (required) setReqError(nextValue === '' ? (requiredMessage ?? 'Select an option') : null);\n if (onChange) {\n // Synthesize a ChangeEvent so callers using\n // `onChange={(e) => set(e.target.value)}` keep working. Listbox\n // doesn't fire native events; we fabricate the minimum surface\n // most React callers actually read (`target.value`,\n // `currentTarget.value`, `type`, `name`).\n const target = { value: nextValue, name } as unknown as EventTarget & HTMLSelectElement;\n const event = {\n target,\n currentTarget: target,\n type: 'change',\n bubbles: true,\n cancelable: false,\n defaultPrevented: false,\n preventDefault() {},\n stopPropagation() {},\n isDefaultPrevented: () => false,\n isPropagationStopped: () => false,\n persist() {},\n timeStamp: 0,\n nativeEvent: {} as Event,\n } as unknown as ChangeEvent<HTMLSelectElement>;\n onChange(event);\n }\n },\n [isControlled, onChange, name, required, requiredMessage],\n );\n\n return (\n <>\n <Listbox<SelectItem>\n items={items}\n getKey={(o) => o.value}\n getLabel={(o) => o.label}\n value={selected}\n onChange={(next) => {\n // Listbox's onChange is `(T | null) => void` to support\n // consumer-driven clearing. Select doesn't expose a clear\n // affordance — defensive ignore if null arrives.\n if (next) commit(next.value);\n }}\n isItemDisabled={(o) => o.disabled}\n disabled={disabled}\n searchable={searchable}\n aria-label={ariaLabel}\n // Clear inside the panel (input-family convention: clear on the trigger\n // AND in the dropdown). Shown when a value is picked and the field can be\n // empty (has a placeholder option). Mirrors the trailing ✕ on the trigger.\n footer={selected && placeholder !== undefined && !disabled\n ? ({ close }: { close: () => void }) => (\n <ButtonGhost\n className=\"uxm-listbox__footer-clear-option\"\n onClick={() => { commit(''); close(); }}\n >\n <Icon glyph=\"close\" size={12} />\n Clear\n </ButtonGhost>\n )\n : undefined}\n renderTrigger={({ open, triggerProps }) => (\n // Trigger uses `<div role=\"combobox\">` — gives us a focusable\n // surface that can carry the same `.uxm-select-dropdown` chrome\n // the native `<select>` had. `aria-disabled` drives the disabled\n // visual (CSS targets the attribute since `<div>` doesn't honor\n // `:disabled`).\n // `rest` = the leftover native-select attributes (`onBlur`, `autoFocus`,\n // `form`, `data-*`, …). It goes FIRST so `triggerProps` and the atom's\n // own props always win; the cast only bridges the element generic —\n // the trigger is a `<div>`, not a `<select>`.\n // eslint-disable-next-line jsx-a11y/role-has-required-aria-props -- aria-expanded (always) and aria-controls (while open) arrive via the triggerProps spread; the rule can't see through it\n <div role=\"combobox\"\n {...(rest as HTMLAttributes<HTMLDivElement>)}\n {...triggerProps}\n tabIndex={disabled ? -1 : 0}\n // The trigger is what lands in the tab order, so it needs the name.\n // `aria-label` is destructured out of `rest` above and forwarded to\n // the Listbox, which puts it on the popup panel — naming the popup,\n // not the control. A `<div role=\"combobox\">` also can't be named by a\n // wrapping `<label>` (a div isn't labelable), so without this the\n // control announces as an unnamed combobox.\n aria-label={ariaLabel}\n aria-disabled={disabled || undefined}\n aria-required={required || undefined}\n aria-invalid={shownError ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], shownError ? errorId : undefined)}\n id={id}\n style={style}\n className={cn(\n 'uxm-select-dropdown',\n open && 'uxm-select-dropdown--open',\n shownError && 'uxm-select-dropdown--error',\n className,\n )}\n >\n <span\n className={cn(\n 'uxm-select-dropdown__trigger-label',\n !selected && 'uxm-select-dropdown__trigger-label--empty',\n )}\n >\n {selected ? selected.label : (placeholder ?? '')}\n </span>\n {/* Clear ✕ shows only when the field CAN be empty — i.e. it has a\n placeholder option (`<option value=\"\" disabled>`), which stands\n for the \"no selection\" state. A select without a placeholder is\n mandatory (a value is always picked, native-<select> style), so\n clearing to empty makes no sense and no ✕ is rendered. */}\n {selected && !disabled && placeholder !== undefined && (\n <IconButton\n aria-label={clearLabel}\n onClick={(e) => {\n // Stop click from bubbling to the trigger div (which\n // would toggle the popover back open).\n e.stopPropagation();\n commit('');\n }}\n onMouseDown={(e) => {\n // Stop mousedown too — the atom's Popover wires its\n // click-outside detection to mousedown.\n e.stopPropagation();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.stopPropagation();\n }\n }}\n className=\"uxm-field-clear uxm-select-dropdown__trigger-clear\"\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n <Icon\n glyph=\"chevron-down\"\n size={14}\n className=\"uxm-select-dropdown__trigger-chevron\"\n style={{ transform: open ? 'rotate(180deg)' : 'none' }}\n />\n </div>\n )}\n renderItem={(o) => o.label}\n />\n {shownError && (\n <FieldError id={errorId} className=\"uxm-select-dropdown__error-message\">\n {shownError}\n </FieldError>\n )}\n </>\n );\n}\n\n/**\n * Multi-select mode — same `.uxm-select-dropdown` trigger chrome as single,\n * but backed by MultiListbox: an array value, a \"N selected\" trigger, checkbox\n * rows, live commit, and (input-family) a clear ✕ on the trigger AND in the\n * panel. Required flags an empty selection without blocking.\n */\nfunction SelectMulti({\n className,\n children,\n value,\n defaultValue,\n onChange,\n disabled,\n style,\n id,\n error,\n searchable = 'auto',\n required = false,\n requiredMessage,\n clearable = false,\n clearLabel = 'Clear all selections',\n 'aria-label': ariaLabel,\n ...rest\n}: SelectMultiProps) {\n const errorId = useId();\n const [reqError, setReqError] = useState<string | null>(null);\n const shownError = error ?? reqError ?? undefined;\n const [internal, setInternal] = useState<string[]>(() => defaultValue ?? []);\n const isControlled = value !== undefined;\n const current = isControlled ? value : internal;\n\n const { items, placeholder } = useMemo(() => parseSelectOptions(children), [children]);\n const selectedItems = useMemo(\n () => items.filter((o) => current.includes(o.value)),\n [items, current],\n );\n\n const commit = useCallback(\n (next: string[]) => {\n if (!isControlled) setInternal(next);\n if (required) {\n setReqError(next.length === 0 ? (requiredMessage ?? DEFAULT_MULTI_REQUIRED_MESSAGE) : null);\n }\n onChange?.(next);\n },\n [isControlled, onChange, required, requiredMessage],\n );\n\n return (\n <>\n <MultiListbox<SelectItem>\n items={items}\n getKey={(o) => o.value}\n getLabel={(o) => o.label}\n value={selectedItems}\n onChange={(next) => commit(next.map((o) => o.value))}\n // Relies on MultiListbox's default `commitMode=\"change\"` — the\n // \"N selected\" count has to track each toggle.\n isItemDisabled={(o) => o.disabled}\n disabled={disabled}\n searchable={searchable}\n aria-label={ariaLabel}\n footer={clearable\n ? ({ clear, selected }) =>\n selected.length > 0 ? (\n <ButtonGhost className=\"uxm-listbox__footer-clear-option\" onClick={clear}>\n <Icon glyph=\"close\" size={12} />\n Clear all\n </ButtonGhost>\n ) : null\n : undefined}\n renderTrigger={({ open, triggerProps }) => (\n // `rest` (leftover native-select attributes) goes first so\n // `triggerProps` and the atom's own props win — see SelectSingle.\n // eslint-disable-next-line jsx-a11y/role-has-required-aria-props -- aria-expanded/controls arrive via the triggerProps spread\n <div role=\"combobox\"\n {...(rest as HTMLAttributes<HTMLDivElement>)}\n {...triggerProps}\n tabIndex={disabled ? -1 : 0}\n // Names the control itself, not just the popup — see SelectSingle.\n aria-label={ariaLabel}\n aria-disabled={disabled || undefined}\n aria-required={required || undefined}\n aria-invalid={shownError ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], shownError ? errorId : undefined)}\n id={id}\n style={style}\n className={cn(\n 'uxm-select-dropdown',\n open && 'uxm-select-dropdown--open',\n shownError && 'uxm-select-dropdown--error',\n className,\n )}\n >\n <span\n className={cn(\n 'uxm-select-dropdown__trigger-label',\n current.length === 0 && 'uxm-select-dropdown__trigger-label--empty',\n )}\n >\n {current.length === 0 ? (placeholder ?? '') : `${current.length} selected`}\n </span>\n {clearable && current.length > 0 && !disabled && (\n <IconButton\n aria-label={clearLabel}\n className=\"uxm-field-clear uxm-select-dropdown__trigger-clear\"\n onClick={(e) => { e.stopPropagation(); commit([]); }}\n onMouseDown={(e) => e.stopPropagation()}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') e.stopPropagation(); }}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n <Icon\n glyph=\"chevron-down\"\n size={14}\n className=\"uxm-select-dropdown__trigger-chevron\"\n style={{ transform: open ? 'rotate(180deg)' : 'none' }}\n />\n </div>\n )}\n renderItem={(o) => o.label}\n />\n {shownError && (\n <FieldError id={errorId} className=\"uxm-select-dropdown__error-message\">\n {shownError}\n </FieldError>\n )}\n </>\n );\n}\n\n/**\n * Select dropdown atom. `mode=\"single\"` (default) picks one value;\n * `mode=\"multi\"` picks an array. Both share the same trigger chrome and\n * `<option>` children API — only the value/onChange shape differs.\n */\nexport function Select(props: SelectProps) {\n return props.mode === 'multi' ? <SelectMulti {...props} /> : <SelectSingle {...props} />;\n}\nSelect.hasError = true;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/input/input.tsx"],"sourcesContent":["import {\n Children,\n isValidElement,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n type ChangeEvent,\n type HTMLAttributes,\n type InputHTMLAttributes,\n type ReactNode,\n type Ref,\n type SelectHTMLAttributes,\n type TextareaHTMLAttributes,\n} from 'react';\n\nimport { cn, mergeDescribedBy, mergeRefs } from '@/helpers';\n\nimport { ButtonGhost } from '../button';\nimport { FieldError } from '../field-error';\nimport { Icon } from '../icon';\nimport { IconButton } from '../icon-button';\nimport { DEFAULT_MULTI_REQUIRED_MESSAGE, Listbox, MultiListbox } from '../listbox';\n\nimport { clearFieldValue } from './clear-field-value';\n\nexport interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {\n /**\n * When set to a non-empty string, the field renders in its error state:\n * red border (`.uxm-input-text--error`), `aria-invalid`, and the message\n * rendered below the input. Omit (or pass an empty string) for the normal\n * state. The atom owns the message so it works standalone — search bars,\n * inline edits, custom layouts — without requiring a `FormField` wrapper.\n */\n error?: string;\n /**\n * Show a clear (✕) button at the trailing edge when the input has content.\n * **Defaults to `true`** — a free-text field is always safe to clear (you\n * can type/delete anyway), so the affordance is on by default; pass\n * `clearable={false}` to opt out. The ✕ self-clears (resets the field and\n * fires `onChange` with \"\"), so no `onClear` is needed — any controlled\n * `value` + `onChange` usage gets it for free.\n */\n clearable?: boolean;\n /**\n * Optional override for the clear action. By default the field clears\n * itself (and notifies via `onChange`); pass `onClear` only when you need\n * custom reset logic beyond emptying the value.\n */\n onClear?: () => void;\n /** Accessible name for the clear button. Default `\"Clear\"`. */\n clearLabel?: string;\n /**\n * Handle to the underlying `<input>` element, merged with the atom's own\n * internal ref (the self-clear keeps working). Use this to focus, select,\n * measure, or insert text at the caret from a consumer. A plain React `ref`\n * on `<TextInput>` does NOT reach the element — it's consumed by `{...rest}`\n * and overridden by the managed ref — so pass `inputRef` instead.\n */\n inputRef?: Ref<HTMLInputElement>;\n}\n\n\nexport function TextInput({\n className,\n type = 'text',\n error,\n clearable = true,\n onClear,\n clearLabel = 'Clear',\n value,\n defaultValue,\n onChange,\n disabled,\n inputRef,\n ...rest\n}: TextInputProps) {\n const errorId = useId();\n const innerRef = useRef<HTMLInputElement>(null);\n const isControlled = value !== undefined;\n const [hasTextUncontrolled, setHasTextUncontrolled] = useState(\n () => typeof defaultValue === 'string' && defaultValue.length > 0,\n );\n const hasValue = isControlled ? String(value).length > 0 : hasTextUncontrolled;\n const showClear = !!clearable && hasValue && !disabled;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setHasTextUncontrolled(e.target.value.length > 0);\n onChange?.(e);\n };\n const handleClear = () => {\n // Self-clear path (clearFieldValue) already refocuses the field; only the\n // onClear-override path needs an explicit focus. Avoids a double focus().\n if (onClear) {\n onClear();\n innerRef.current?.focus();\n } else {\n clearFieldValue(innerRef.current);\n }\n if (!isControlled) setHasTextUncontrolled(false);\n };\n\n // Merge the consumer's `inputRef` with the atom's `innerRef` (the self-clear\n // relies on `innerRef`), so both get the element. Memoised on `inputRef` so\n // the callback identity is stable and React doesn't detach/reattach.\n const setInputRef = useMemo(() => mergeRefs(innerRef, inputRef), [inputRef]);\n\n const input = (\n // `{...rest}` is spread FIRST so the managed props below always win — a raw\n // consumer `ref` can't clobber the merged ref (which the self-clear relies\n // on); the sanctioned handle is `inputRef`, merged in via `setInputRef`.\n // `defaultValue` is only forwarded when uncontrolled, so value+defaultValue\n // are never both set.\n <input\n {...rest}\n ref={setInputRef}\n type={type}\n value={value}\n defaultValue={isControlled ? undefined : defaultValue}\n onChange={handleChange}\n disabled={disabled}\n className={cn(\n 'uxm-input-text',\n error && 'uxm-input-text--error',\n clearable && 'uxm-input-text--clearable',\n className,\n )}\n aria-invalid={error ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], error ? errorId : undefined)}\n />\n );\n\n return (\n <>\n {clearable ? (\n // Layout-only wrapper so the clear button can sit absolutely at the\n // trailing edge. The visible chrome stays on `.uxm-input-text` (the\n // input itself), so save-emitted `--uxm-input-text-*` vars still land.\n <div className=\"uxm-input-text__wrap\">\n {input}\n {showClear && (\n <IconButton\n onClick={handleClear}\n className=\"uxm-field-clear uxm-input-text__clear\"\n aria-label={clearLabel}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n </div>\n ) : (\n input\n )}\n {error && (\n <FieldError id={errorId} className=\"uxm-input-text__error-message\">\n {error}\n </FieldError>\n )}\n </>\n );\n}\n// Static marker so FormField only forwards its `error` prop into children\n// that accept one (avoids React unknown-prop warnings on non-input children).\nTextInput.hasError = true;\n\nexport interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {\n /** See `TextInputProps.error`. */\n error?: string;\n /** See `TextInputProps.clearable`. Defaults to `true`; ✕ sits at the top-right corner. */\n clearable?: boolean;\n /** See `TextInputProps.onClear` — optional override; the field self-clears otherwise. */\n onClear?: () => void;\n /** See `TextInputProps.clearLabel`. Default `\"Clear\"`. */\n clearLabel?: string;\n /**\n * Handle to the underlying `<textarea>` element, merged with the atom's own\n * internal ref (the self-clear keeps working). Use it to focus, select, or\n * insert text at the caret (e.g. drop-a-variable-into-a-formula). A plain\n * React `ref` on `<Textarea>` does NOT reach the element — pass\n * `textareaRef` instead.\n */\n textareaRef?: Ref<HTMLTextAreaElement>;\n}\n\nexport function Textarea({\n className,\n error,\n clearable = true,\n onClear,\n clearLabel = 'Clear',\n value,\n defaultValue,\n onChange,\n disabled,\n textareaRef,\n ...rest\n}: TextareaProps) {\n const errorId = useId();\n const innerRef = useRef<HTMLTextAreaElement>(null);\n const isControlled = value !== undefined;\n const [hasTextUncontrolled, setHasTextUncontrolled] = useState(\n () => typeof defaultValue === 'string' && defaultValue.length > 0,\n );\n const hasValue = isControlled ? String(value).length > 0 : hasTextUncontrolled;\n const showClear = !!clearable && hasValue && !disabled;\n\n const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) setHasTextUncontrolled(e.target.value.length > 0);\n onChange?.(e);\n };\n const handleClear = () => {\n // See TextInput: clearFieldValue refocuses; only onClear needs explicit focus.\n if (onClear) {\n onClear();\n innerRef.current?.focus();\n } else {\n clearFieldValue(innerRef.current);\n }\n if (!isControlled) setHasTextUncontrolled(false);\n };\n\n // Merge the consumer's `textareaRef` with the atom's `innerRef` — see TextInput.\n const setTextareaRef = useMemo(() => mergeRefs(innerRef, textareaRef), [textareaRef]);\n\n const textarea = (\n // `{...rest}` first so managed props win; the sanctioned element handle is\n // `textareaRef`, merged in via `setTextareaRef`. `defaultValue` only when\n // uncontrolled — never both value+defaultValue.\n <textarea\n {...rest}\n ref={setTextareaRef}\n value={value}\n defaultValue={isControlled ? undefined : defaultValue}\n onChange={handleChange}\n disabled={disabled}\n className={cn(\n 'uxm-textarea',\n error && 'uxm-textarea--error',\n clearable && 'uxm-textarea--clearable',\n className,\n )}\n aria-invalid={error ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], error ? errorId : undefined)}\n />\n );\n\n return (\n <>\n {clearable ? (\n <div className=\"uxm-textarea__wrap\">\n {textarea}\n {showClear && (\n <IconButton\n onClick={handleClear}\n className=\"uxm-field-clear uxm-textarea__clear\"\n aria-label={clearLabel}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n </div>\n ) : (\n textarea\n )}\n {error && (\n <FieldError id={errorId} className=\"uxm-textarea__error-message\">\n {error}\n </FieldError>\n )}\n </>\n );\n}\nTextarea.hasError = true;\n\n/**\n * Native `<select>` attributes the atom still accepts verbatim. The five\n * omitted keys are the ones `mode` re-shapes: `value` / `defaultValue` /\n * `onChange` differ per mode, `required` gains its own message prop, and\n * `multiple` is replaced by `mode=\"multi\"`. Keeping this base means existing\n * `<Select onBlur={…} autoFocus data-testid={…}>` call sites — and any\n * `Omit<SelectProps, …>` in consumer code — keep compiling.\n */\ntype SelectNativeProps = Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n 'value' | 'defaultValue' | 'onChange' | 'required' | 'multiple'\n>;\n\n/** Props shared by both single- and multi-select modes. */\ninterface SelectCommonProps extends SelectNativeProps {\n /** `<option>` children — parsed into items (and a placeholder from `<option value=\"\" disabled>`). */\n children?: ReactNode;\n /**\n * When set to a non-empty string, the trigger renders in its error\n * state: red border (`.uxm-select-dropdown--error`), `aria-invalid` on\n * the combobox, and the message below the trigger. Omit (or pass an\n * empty string) for the normal state.\n */\n error?: string;\n /**\n * Whether the dropdown panel includes a search box. Forwarded to the\n * internal Listbox: `true`/`false` force it, `\"auto\"` (the default)\n * shows it only once the option count crosses the shared threshold —\n * short lists stay a plain picker, long ones become filterable, with no\n * change to the trigger. (For a search-first combobox with its own\n * trigger chrome — icon glyphs, dial codes — use SearchDropdown.)\n */\n searchable?: boolean | 'auto';\n /**\n * Require a selection. Clearing to empty is still allowed (input-family,\n * live model) but flags the error immediately. An explicit `error` prop\n * takes precedence. Single mode is only clearable with a placeholder\n * option; multi mode uses `clearable`.\n */\n required?: boolean;\n /**\n * Override the default required message. Defaults are per-mode:\n * single → `\"Select an option\"`, multi → `\"Select at least one option\"`\n * (the shared `DEFAULT_MULTI_REQUIRED_MESSAGE`).\n */\n requiredMessage?: string;\n /**\n * Accessible name for the trigger's clear button. Defaults are per-mode:\n * single → `\"Clear selection\"`, multi → `\"Clear all selections\"`.\n */\n clearLabel?: string;\n}\n\nexport interface SelectSingleProps extends SelectCommonProps {\n /** Single-choice (default) — one value, cleared only when a placeholder option exists. */\n mode?: 'single';\n value?: string;\n defaultValue?: string;\n /** Fires a synthesized `ChangeEvent` (native-`<select>`-compatible) so `e.target.value` works. */\n onChange?: (event: ChangeEvent<HTMLSelectElement>) => void;\n}\n\nexport interface SelectMultiProps extends SelectCommonProps {\n /** Multi-choice — an array value with a \"N selected\" trigger, live commit, checkbox rows. */\n mode: 'multi';\n value?: string[];\n defaultValue?: string[];\n /** Fires the full selected-value array on every toggle (live). */\n onChange?: (next: string[]) => void;\n /** Show a clear-all ✕ on the trigger AND a \"Clear all\" in the panel. */\n clearable?: boolean;\n}\n\nexport type SelectProps = SelectSingleProps | SelectMultiProps;\n\ninterface SelectItem {\n value: string;\n label: string;\n disabled: boolean;\n}\n\n/**\n * Parse `<option>` children into Listbox-friendly items. HTML semantics:\n * - `<option value=\"x\">Label</option>` → value \"x\", label \"Label\"\n * - `<option>Label</option>` → value \"Label\", label \"Label\" (label IS the value)\n * - `<option value=\"\" disabled>Placeholder</option>` → reserved as the\n * trigger placeholder text, not added to items (matches the common\n * React Select pattern).\n */\nfunction parseSelectOptions(children: ReactNode): {\n items: SelectItem[];\n placeholder: string | undefined;\n} {\n const items: SelectItem[] = [];\n let placeholder: string | undefined;\n\n Children.forEach(children, (child) => {\n if (\n !isValidElement<{\n value?: string | number;\n children?: ReactNode;\n disabled?: boolean;\n }>(child)\n ) {\n return;\n }\n if (child.type !== 'option') return;\n\n const text = String(child.props.children ?? '');\n const val = child.props.value !== undefined ? String(child.props.value) : text;\n const label = text || val;\n const disabled = child.props.disabled ?? false;\n\n if (val === '' && disabled && placeholder === undefined) {\n placeholder = label;\n return;\n }\n items.push({ value: val, label, disabled });\n });\n\n return { items, placeholder };\n}\n\n/**\n * Select Dropdown — internally a Listbox-backed picker. Its public\n * API stays compatible with the previous native-`<select>` shape:\n * pass `<option>` children, controlled (`value`) or uncontrolled\n * (`defaultValue`) state, and an `onChange(e)` handler that reads\n * `e.target.value`.\n *\n * The visible trigger keeps the same `.uxm-select-dropdown` class\n * (and all its registry-tuned visual chrome — bg, border, radius,\n * padding, font, per-state colors). The OPEN-state UI swaps from the\n * browser's native dropdown to a `<Listbox>` panel so it looks\n * identical across browsers and inherits the shared panel theming\n * from the `listbox` registry entry.\n *\n * `onChange` receives a synthetic `ChangeEvent` shape — most React\n * callers only read `e.target.value`, which is set correctly. For\n * new code, consider using `<Listbox>` directly with its native\n * `(item: T | null) => void` callback.\n */\nfunction SelectSingle({\n className,\n children,\n value,\n defaultValue,\n onChange,\n disabled,\n style,\n name,\n id,\n error,\n searchable = 'auto',\n required = false,\n requiredMessage,\n clearLabel = 'Clear selection',\n 'aria-label': ariaLabel,\n ...rest\n}: SelectSingleProps) {\n const errorId = useId();\n // Required is flagged (not blocked) on clear — input-family live model.\n // An explicit `error` prop wins over the internal required one.\n const [reqError, setReqError] = useState<string | null>(null);\n const shownError = error ?? reqError ?? undefined;\n // Uncontrolled state — only used when `value` is not provided. Matches\n // the native `<select>`'s controlled/uncontrolled duality.\n const [internal, setInternal] = useState<string>(() => String(defaultValue ?? ''));\n const isControlled = value !== undefined;\n const current = String(isControlled ? value : internal);\n\n const { items, placeholder } = useMemo(() => parseSelectOptions(children), [children]);\n const selected = useMemo(() => items.find((o) => o.value === current) ?? null, [items, current]);\n\n const commit = useCallback(\n (nextValue: string) => {\n if (!isControlled) setInternal(nextValue);\n if (required) setReqError(nextValue === '' ? (requiredMessage ?? 'Select an option') : null);\n if (onChange) {\n // Synthesize a ChangeEvent so callers using\n // `onChange={(e) => set(e.target.value)}` keep working. Listbox\n // doesn't fire native events; we fabricate the minimum surface\n // most React callers actually read (`target.value`,\n // `currentTarget.value`, `type`, `name`).\n const target = { value: nextValue, name } as unknown as EventTarget & HTMLSelectElement;\n const event = {\n target,\n currentTarget: target,\n type: 'change',\n bubbles: true,\n cancelable: false,\n defaultPrevented: false,\n preventDefault() {},\n stopPropagation() {},\n isDefaultPrevented: () => false,\n isPropagationStopped: () => false,\n persist() {},\n timeStamp: 0,\n nativeEvent: {} as Event,\n } as unknown as ChangeEvent<HTMLSelectElement>;\n onChange(event);\n }\n },\n [isControlled, onChange, name, required, requiredMessage],\n );\n\n return (\n <>\n <Listbox<SelectItem>\n items={items}\n getKey={(o) => o.value}\n getLabel={(o) => o.label}\n value={selected}\n onChange={(next) => {\n // Listbox's onChange is `(T | null) => void` to support\n // consumer-driven clearing. Select doesn't expose a clear\n // affordance — defensive ignore if null arrives.\n if (next) commit(next.value);\n }}\n isItemDisabled={(o) => o.disabled}\n disabled={disabled}\n searchable={searchable}\n aria-label={ariaLabel}\n // Clear inside the panel (input-family convention: clear on the trigger\n // AND in the dropdown). Shown when a value is picked and the field can be\n // empty (has a placeholder option). Mirrors the trailing ✕ on the trigger.\n footer={selected && placeholder !== undefined && !disabled\n ? ({ close }: { close: () => void }) => (\n <ButtonGhost\n className=\"uxm-listbox__footer-clear-option\"\n onClick={() => { commit(''); close(); }}\n >\n <Icon glyph=\"close\" size={12} />\n Clear\n </ButtonGhost>\n )\n : undefined}\n renderTrigger={({ open, triggerProps }) => (\n // Trigger uses `<div role=\"combobox\">` — gives us a focusable\n // surface that can carry the same `.uxm-select-dropdown` chrome\n // the native `<select>` had. `aria-disabled` drives the disabled\n // visual (CSS targets the attribute since `<div>` doesn't honor\n // `:disabled`).\n // `rest` = the leftover native-select attributes (`onBlur`, `autoFocus`,\n // `form`, `data-*`, …). It goes FIRST so `triggerProps` and the atom's\n // own props always win; the cast only bridges the element generic —\n // the trigger is a `<div>`, not a `<select>`.\n // eslint-disable-next-line jsx-a11y/role-has-required-aria-props -- aria-expanded (always) and aria-controls (while open) arrive via the triggerProps spread; the rule can't see through it\n <div role=\"combobox\"\n {...(rest as HTMLAttributes<HTMLDivElement>)}\n {...triggerProps}\n tabIndex={disabled ? -1 : 0}\n // The trigger is what lands in the tab order, so it needs the name.\n // `aria-label` is destructured out of `rest` above and forwarded to\n // the Listbox, which puts it on the popup panel — naming the popup,\n // not the control. A `<div role=\"combobox\">` also can't be named by a\n // wrapping `<label>` (a div isn't labelable), so without this the\n // control announces as an unnamed combobox.\n aria-label={ariaLabel}\n aria-disabled={disabled || undefined}\n aria-required={required || undefined}\n aria-invalid={shownError ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], shownError ? errorId : undefined)}\n id={id}\n style={style}\n className={cn(\n 'uxm-select-dropdown',\n open && 'uxm-select-dropdown--open',\n shownError && 'uxm-select-dropdown--error',\n className,\n )}\n >\n <span\n className={cn(\n 'uxm-select-dropdown__trigger-label',\n !selected && 'uxm-select-dropdown__trigger-label--empty',\n )}\n >\n {selected ? selected.label : (placeholder ?? '')}\n </span>\n {/* Clear ✕ shows only when the field CAN be empty — i.e. it has a\n placeholder option (`<option value=\"\" disabled>`), which stands\n for the \"no selection\" state. A select without a placeholder is\n mandatory (a value is always picked, native-<select> style), so\n clearing to empty makes no sense and no ✕ is rendered. */}\n {selected && !disabled && placeholder !== undefined && (\n <IconButton\n aria-label={clearLabel}\n onClick={(e) => {\n // Stop click from bubbling to the trigger div (which\n // would toggle the popover back open).\n e.stopPropagation();\n commit('');\n }}\n onMouseDown={(e) => {\n // Stop mousedown too — the atom's Popover wires its\n // click-outside detection to mousedown.\n e.stopPropagation();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.stopPropagation();\n }\n }}\n className=\"uxm-field-clear uxm-select-dropdown__trigger-clear\"\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n <Icon\n glyph=\"chevron-down\"\n size={14}\n className={cn(\n 'uxm-select-dropdown__trigger-chevron',\n open && 'uxm-select-dropdown__trigger-chevron--open',\n )}\n />\n </div>\n )}\n renderItem={(o) => o.label}\n />\n {shownError && (\n <FieldError id={errorId} className=\"uxm-select-dropdown__error-message\">\n {shownError}\n </FieldError>\n )}\n </>\n );\n}\n\n/**\n * Multi-select mode — same `.uxm-select-dropdown` trigger chrome as single,\n * but backed by MultiListbox: an array value, a \"N selected\" trigger, checkbox\n * rows, live commit, and (input-family) a clear ✕ on the trigger AND in the\n * panel. Required flags an empty selection without blocking.\n */\nfunction SelectMulti({\n className,\n children,\n value,\n defaultValue,\n onChange,\n disabled,\n style,\n id,\n error,\n searchable = 'auto',\n required = false,\n requiredMessage,\n clearable = false,\n clearLabel = 'Clear all selections',\n 'aria-label': ariaLabel,\n ...rest\n}: SelectMultiProps) {\n const errorId = useId();\n const [reqError, setReqError] = useState<string | null>(null);\n const shownError = error ?? reqError ?? undefined;\n const [internal, setInternal] = useState<string[]>(() => defaultValue ?? []);\n const isControlled = value !== undefined;\n const current = isControlled ? value : internal;\n\n const { items, placeholder } = useMemo(() => parseSelectOptions(children), [children]);\n const selectedItems = useMemo(\n () => items.filter((o) => current.includes(o.value)),\n [items, current],\n );\n\n const commit = useCallback(\n (next: string[]) => {\n if (!isControlled) setInternal(next);\n if (required) {\n setReqError(next.length === 0 ? (requiredMessage ?? DEFAULT_MULTI_REQUIRED_MESSAGE) : null);\n }\n onChange?.(next);\n },\n [isControlled, onChange, required, requiredMessage],\n );\n\n return (\n <>\n <MultiListbox<SelectItem>\n items={items}\n getKey={(o) => o.value}\n getLabel={(o) => o.label}\n value={selectedItems}\n onChange={(next) => commit(next.map((o) => o.value))}\n // Relies on MultiListbox's default `commitMode=\"change\"` — the\n // \"N selected\" count has to track each toggle.\n isItemDisabled={(o) => o.disabled}\n disabled={disabled}\n searchable={searchable}\n aria-label={ariaLabel}\n footer={clearable\n ? ({ clear, selected }) =>\n selected.length > 0 ? (\n <ButtonGhost className=\"uxm-listbox__footer-clear-option\" onClick={clear}>\n <Icon glyph=\"close\" size={12} />\n Clear all\n </ButtonGhost>\n ) : null\n : undefined}\n renderTrigger={({ open, triggerProps }) => (\n // `rest` (leftover native-select attributes) goes first so\n // `triggerProps` and the atom's own props win — see SelectSingle.\n // eslint-disable-next-line jsx-a11y/role-has-required-aria-props -- aria-expanded/controls arrive via the triggerProps spread\n <div role=\"combobox\"\n {...(rest as HTMLAttributes<HTMLDivElement>)}\n {...triggerProps}\n tabIndex={disabled ? -1 : 0}\n // Names the control itself, not just the popup — see SelectSingle.\n aria-label={ariaLabel}\n aria-disabled={disabled || undefined}\n aria-required={required || undefined}\n aria-invalid={shownError ? true : undefined}\n aria-describedby={mergeDescribedBy(rest['aria-describedby'], shownError ? errorId : undefined)}\n id={id}\n style={style}\n className={cn(\n 'uxm-select-dropdown',\n open && 'uxm-select-dropdown--open',\n shownError && 'uxm-select-dropdown--error',\n className,\n )}\n >\n <span\n className={cn(\n 'uxm-select-dropdown__trigger-label',\n current.length === 0 && 'uxm-select-dropdown__trigger-label--empty',\n )}\n >\n {current.length === 0 ? (placeholder ?? '') : `${current.length} selected`}\n </span>\n {clearable && current.length > 0 && !disabled && (\n <IconButton\n aria-label={clearLabel}\n className=\"uxm-field-clear uxm-select-dropdown__trigger-clear\"\n onClick={(e) => { e.stopPropagation(); commit([]); }}\n onMouseDown={(e) => e.stopPropagation()}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') e.stopPropagation(); }}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n <Icon\n glyph=\"chevron-down\"\n size={14}\n className={cn(\n 'uxm-select-dropdown__trigger-chevron',\n open && 'uxm-select-dropdown__trigger-chevron--open',\n )}\n />\n </div>\n )}\n renderItem={(o) => o.label}\n />\n {shownError && (\n <FieldError id={errorId} className=\"uxm-select-dropdown__error-message\">\n {shownError}\n </FieldError>\n )}\n </>\n );\n}\n\n/**\n * Select dropdown atom. `mode=\"single\"` (default) picks one value;\n * `mode=\"multi\"` picks an array. Both share the same trigger chrome and\n * `<option>` children API — only the value/onChange shape differs.\n */\nexport function Select(props: SelectProps) {\n return props.mode === 'multi' ? <SelectMulti {...props} /> : <SelectSingle {...props} />;\n}\nSelect.hasError = true;\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var language_switcher_exports = {};
|
|
20
|
+
__export(language_switcher_exports, {
|
|
21
|
+
LanguageSwitcher: () => import_language_switcher.LanguageSwitcher
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(language_switcher_exports);
|
|
24
|
+
var import_language_switcher = require("./language-switcher.cjs");
|
|
25
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
26
|
+
0 && (module.exports = {
|
|
27
|
+
LanguageSwitcher
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/language-switcher/index.ts"],"sourcesContent":["export { LanguageSwitcher } from './language-switcher';\nexport type { LanguageSwitcherProps, LanguageSwitcherVariant } from './language-switcher';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAAiC;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/language-switcher/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/language-switcher/index.ts"],"sourcesContent":["export { LanguageSwitcher } from './language-switcher';\nexport type { LanguageSwitcherProps, LanguageSwitcherVariant } from './language-switcher';\n"],"mappings":"AAAA,SAAS,wBAAwB;","names":[]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var language_switcher_preview_exports = {};
|
|
20
|
+
__export(language_switcher_preview_exports, {
|
|
21
|
+
LanguageSwitcherPreview: () => LanguageSwitcherPreview
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(language_switcher_preview_exports);
|
|
24
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
|
+
var import_react = require("react");
|
|
26
|
+
var import_helpers = require("../../helpers/index.cjs");
|
|
27
|
+
var import_ui = require("../../ui/index.cjs");
|
|
28
|
+
const DEMO_LOCALES = ["en-US", "de-DE", "uk-UA", "fr-FR", "ja-JP", "pl-PL"];
|
|
29
|
+
function buildVars(styles) {
|
|
30
|
+
return {
|
|
31
|
+
"--uxm-language-switcher-bg": styles.bg,
|
|
32
|
+
"--uxm-language-switcher-border-color": styles.borderColor,
|
|
33
|
+
"--uxm-language-switcher-radius": `${styles.radius}px`,
|
|
34
|
+
"--uxm-language-switcher-color": styles.color,
|
|
35
|
+
"--uxm-language-switcher-font-size": `${styles.fontSize}px`,
|
|
36
|
+
"--uxm-language-switcher-gap": `${styles.gap}px`,
|
|
37
|
+
"--uxm-language-switcher-padding-x": `${styles.paddingX}px`,
|
|
38
|
+
"--uxm-language-switcher-padding-y": `${styles.paddingY}px`,
|
|
39
|
+
"--uxm-language-switcher-hover-bg": styles.hoverBg,
|
|
40
|
+
"--uxm-language-switcher-hover-border-color": styles.hoverBorderColor,
|
|
41
|
+
"--uxm-language-switcher-open-bg": styles.openBg,
|
|
42
|
+
"--uxm-language-switcher-open-border-color": styles.openBorderColor,
|
|
43
|
+
"--uxm-language-switcher-focus-ring": styles.focusRing,
|
|
44
|
+
"--uxm-language-switcher-caret-color": styles.caretColor,
|
|
45
|
+
"--uxm-language-switcher-disabled-opacity": styles.disabledOpacity != null ? String(styles.disabledOpacity) : void 0
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function StaticShowcase({ state, variant }) {
|
|
49
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uxm-language-switcher", style: { pointerEvents: "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
50
|
+
"button",
|
|
51
|
+
{
|
|
52
|
+
type: "button",
|
|
53
|
+
tabIndex: -1,
|
|
54
|
+
"aria-hidden": "true",
|
|
55
|
+
className: (0, import_helpers.cn)(
|
|
56
|
+
"uxm-language-switcher__trigger",
|
|
57
|
+
`uxm-language-switcher__trigger--${variant}`,
|
|
58
|
+
state !== "default" && `uxm-language-switcher__trigger--state-${state}`
|
|
59
|
+
),
|
|
60
|
+
children: [
|
|
61
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ui.Icon, { glyph: "globe", size: 16 }),
|
|
62
|
+
variant === "full" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-language-switcher__current", children: "Deutsch" }),
|
|
63
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-language-switcher__caret", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
64
|
+
import_ui.Icon,
|
|
65
|
+
{
|
|
66
|
+
glyph: "chevron-down",
|
|
67
|
+
size: 12,
|
|
68
|
+
strokeWidth: 2.2,
|
|
69
|
+
style: { transform: state === "open" ? "rotate(180deg)" : "none" }
|
|
70
|
+
}
|
|
71
|
+
) })
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
) });
|
|
75
|
+
}
|
|
76
|
+
function LanguageSwitcherPreview({ styles, variants }) {
|
|
77
|
+
const variant = variants.variant ?? "full";
|
|
78
|
+
const state = variants.state ?? "default";
|
|
79
|
+
const [locale, setLocale] = (0, import_react.useState)("en-US");
|
|
80
|
+
const cssVars = buildVars(styles);
|
|
81
|
+
const sectionLabel = {
|
|
82
|
+
fontSize: 11,
|
|
83
|
+
color: "var(--color-text-muted)",
|
|
84
|
+
marginBottom: 12,
|
|
85
|
+
letterSpacing: 0.5,
|
|
86
|
+
textTransform: "uppercase"
|
|
87
|
+
};
|
|
88
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 32, ...cssVars }, children: [
|
|
89
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
90
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: sectionLabel, children: [
|
|
91
|
+
variant,
|
|
92
|
+
" \xB7 ",
|
|
93
|
+
state,
|
|
94
|
+
" state"
|
|
95
|
+
] }),
|
|
96
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StaticShowcase, { state, variant })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
99
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: sectionLabel, children: "interactive" }),
|
|
100
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
101
|
+
import_ui.LanguageSwitcher,
|
|
102
|
+
{
|
|
103
|
+
locales: DEMO_LOCALES,
|
|
104
|
+
value: locale,
|
|
105
|
+
onChange: setLocale,
|
|
106
|
+
variant,
|
|
107
|
+
disabled: state === "disabled"
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: sectionLabel, children: "one locale configured \u2192 renders nothing" }),
|
|
113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
style: {
|
|
117
|
+
border: "1px dashed var(--color-border)",
|
|
118
|
+
borderRadius: 8,
|
|
119
|
+
padding: 12,
|
|
120
|
+
fontSize: 12,
|
|
121
|
+
color: "var(--color-text-muted)"
|
|
122
|
+
},
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ui.LanguageSwitcher, { locales: ["en-US"], value: "en-US", onChange: () => {
|
|
125
|
+
} }),
|
|
126
|
+
"(empty \u2014 no wrapper, no disabled control)"
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
] })
|
|
131
|
+
] });
|
|
132
|
+
}
|
|
133
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
134
|
+
0 && (module.exports = {
|
|
135
|
+
LanguageSwitcherPreview
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=language-switcher-preview.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/language-switcher/language-switcher-preview.tsx"],"sourcesContent":["import { useState, type CSSProperties } from 'react';\n\nimport { cn } from '@/helpers';\nimport type { PreviewProps } from '@/previews/types';\nimport { Icon, LanguageSwitcher } from '@/ui';\n\ntype Styles = PreviewProps['styles'];\n\n/** A spread of scripts — Latin, Cyrillic, CJK — so the endonym rendering and\n * the trigger's width behaviour are both visible at a glance. */\nconst DEMO_LOCALES = ['en-US', 'de-DE', 'uk-UA', 'fr-FR', 'ja-JP', 'pl-PL'];\n\nfunction buildVars(styles: Styles): CSSProperties {\n return {\n '--uxm-language-switcher-bg': styles.bg as string,\n '--uxm-language-switcher-border-color': styles.borderColor as string,\n '--uxm-language-switcher-radius': `${styles.radius}px`,\n '--uxm-language-switcher-color': styles.color as string,\n '--uxm-language-switcher-font-size': `${styles.fontSize}px`,\n '--uxm-language-switcher-gap': `${styles.gap}px`,\n '--uxm-language-switcher-padding-x': `${styles.paddingX}px`,\n '--uxm-language-switcher-padding-y': `${styles.paddingY}px`,\n '--uxm-language-switcher-hover-bg': styles.hoverBg as string,\n '--uxm-language-switcher-hover-border-color': styles.hoverBorderColor as string,\n '--uxm-language-switcher-open-bg': styles.openBg as string,\n '--uxm-language-switcher-open-border-color': styles.openBorderColor as string,\n '--uxm-language-switcher-focus-ring': styles.focusRing as string,\n '--uxm-language-switcher-caret-color': styles.caretColor as string,\n '--uxm-language-switcher-disabled-opacity':\n styles.disabledOpacity != null ? String(styles.disabledOpacity) : undefined,\n } as CSSProperties;\n}\n\n/**\n * Static showcase — hand-rendered trigger markup, not the real atom.\n *\n * The atom routes `className` to the Listbox root and exposes no hook onto the\n * trigger, so the `--state-*` forced modifier cannot be projected through it\n * (same reason ButtonGroup's preview hand-renders). Inert: `pointer-events:\n * none` stops the production `:hover` firing on mouse-over and `tabIndex={-1}`\n * keeps it out of the focus order, so a real Tab press cannot override the\n * forced state being demonstrated.\n */\nfunction StaticShowcase({ state, variant }: { state: string; variant: 'full' | 'compact' }) {\n return (\n <div className=\"uxm-language-switcher\" style={{ pointerEvents: 'none' }}>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n className={cn(\n 'uxm-language-switcher__trigger',\n `uxm-language-switcher__trigger--${variant}`,\n state !== 'default' && `uxm-language-switcher__trigger--state-${state}`,\n )}\n >\n <Icon glyph=\"globe\" size={16} />\n {variant === 'full' && <span className=\"uxm-language-switcher__current\">Deutsch</span>}\n <span className=\"uxm-language-switcher__caret\">\n <Icon\n glyph=\"chevron-down\"\n size={12}\n strokeWidth={2.2}\n style={{ transform: state === 'open' ? 'rotate(180deg)' : 'none' }}\n />\n </span>\n </button>\n </div>\n );\n}\n\nexport function LanguageSwitcherPreview({ styles, variants }: PreviewProps) {\n const variant = ((variants.variant as string) ?? 'full') as 'full' | 'compact';\n const state = (variants.state as string) ?? 'default';\n const [locale, setLocale] = useState('en-US');\n const cssVars = buildVars(styles);\n\n const sectionLabel = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 12,\n letterSpacing: 0.5,\n textTransform: 'uppercase' as const,\n };\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 32, ...cssVars } as CSSProperties}>\n <div>\n <div style={sectionLabel}>\n {variant} · {state} state\n </div>\n <StaticShowcase state={state} variant={variant} />\n </div>\n\n {/* Live instance — the real atom, exercising the production :hover /\n :focus-visible / open rules through the same projected vars. */}\n <div>\n <div style={sectionLabel}>interactive</div>\n <LanguageSwitcher\n locales={DEMO_LOCALES}\n value={locale}\n onChange={setLocale}\n variant={variant}\n disabled={state === 'disabled'}\n />\n </div>\n\n {/* The zero-cost-absence rule, shown rather than described: the same atom\n with a single configured locale emits nothing. A realm with one\n language depends on this, and it is easy to regress into a disabled\n control, so it earns a slot. */}\n <div>\n <div style={sectionLabel}>one locale configured → renders nothing</div>\n <div\n style={{\n border: '1px dashed var(--color-border)',\n borderRadius: 8,\n padding: 12,\n fontSize: 12,\n color: 'var(--color-text-muted)',\n }}\n >\n <LanguageSwitcher locales={['en-US']} value=\"en-US\" onChange={() => {}} />\n (empty — no wrapper, no disabled control)\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language-switcher-preview.d.ts","sourceRoot":"","sources":["../../../src/ui/language-switcher/language-switcher-preview.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAoErD,wBAAgB,uBAAuB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,YAAY,2CAyDzE"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { cn } from "../../helpers/index.js";
|
|
4
|
+
import { Icon, LanguageSwitcher } from "../../ui/index.js";
|
|
5
|
+
const DEMO_LOCALES = ["en-US", "de-DE", "uk-UA", "fr-FR", "ja-JP", "pl-PL"];
|
|
6
|
+
function buildVars(styles) {
|
|
7
|
+
return {
|
|
8
|
+
"--uxm-language-switcher-bg": styles.bg,
|
|
9
|
+
"--uxm-language-switcher-border-color": styles.borderColor,
|
|
10
|
+
"--uxm-language-switcher-radius": `${styles.radius}px`,
|
|
11
|
+
"--uxm-language-switcher-color": styles.color,
|
|
12
|
+
"--uxm-language-switcher-font-size": `${styles.fontSize}px`,
|
|
13
|
+
"--uxm-language-switcher-gap": `${styles.gap}px`,
|
|
14
|
+
"--uxm-language-switcher-padding-x": `${styles.paddingX}px`,
|
|
15
|
+
"--uxm-language-switcher-padding-y": `${styles.paddingY}px`,
|
|
16
|
+
"--uxm-language-switcher-hover-bg": styles.hoverBg,
|
|
17
|
+
"--uxm-language-switcher-hover-border-color": styles.hoverBorderColor,
|
|
18
|
+
"--uxm-language-switcher-open-bg": styles.openBg,
|
|
19
|
+
"--uxm-language-switcher-open-border-color": styles.openBorderColor,
|
|
20
|
+
"--uxm-language-switcher-focus-ring": styles.focusRing,
|
|
21
|
+
"--uxm-language-switcher-caret-color": styles.caretColor,
|
|
22
|
+
"--uxm-language-switcher-disabled-opacity": styles.disabledOpacity != null ? String(styles.disabledOpacity) : void 0
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function StaticShowcase({ state, variant }) {
|
|
26
|
+
return /* @__PURE__ */ jsx("div", { className: "uxm-language-switcher", style: { pointerEvents: "none" }, children: /* @__PURE__ */ jsxs(
|
|
27
|
+
"button",
|
|
28
|
+
{
|
|
29
|
+
type: "button",
|
|
30
|
+
tabIndex: -1,
|
|
31
|
+
"aria-hidden": "true",
|
|
32
|
+
className: cn(
|
|
33
|
+
"uxm-language-switcher__trigger",
|
|
34
|
+
`uxm-language-switcher__trigger--${variant}`,
|
|
35
|
+
state !== "default" && `uxm-language-switcher__trigger--state-${state}`
|
|
36
|
+
),
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx(Icon, { glyph: "globe", size: 16 }),
|
|
39
|
+
variant === "full" && /* @__PURE__ */ jsx("span", { className: "uxm-language-switcher__current", children: "Deutsch" }),
|
|
40
|
+
/* @__PURE__ */ jsx("span", { className: "uxm-language-switcher__caret", children: /* @__PURE__ */ jsx(
|
|
41
|
+
Icon,
|
|
42
|
+
{
|
|
43
|
+
glyph: "chevron-down",
|
|
44
|
+
size: 12,
|
|
45
|
+
strokeWidth: 2.2,
|
|
46
|
+
style: { transform: state === "open" ? "rotate(180deg)" : "none" }
|
|
47
|
+
}
|
|
48
|
+
) })
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
) });
|
|
52
|
+
}
|
|
53
|
+
function LanguageSwitcherPreview({ styles, variants }) {
|
|
54
|
+
const variant = variants.variant ?? "full";
|
|
55
|
+
const state = variants.state ?? "default";
|
|
56
|
+
const [locale, setLocale] = useState("en-US");
|
|
57
|
+
const cssVars = buildVars(styles);
|
|
58
|
+
const sectionLabel = {
|
|
59
|
+
fontSize: 11,
|
|
60
|
+
color: "var(--color-text-muted)",
|
|
61
|
+
marginBottom: 12,
|
|
62
|
+
letterSpacing: 0.5,
|
|
63
|
+
textTransform: "uppercase"
|
|
64
|
+
};
|
|
65
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 32, ...cssVars }, children: [
|
|
66
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
67
|
+
/* @__PURE__ */ jsxs("div", { style: sectionLabel, children: [
|
|
68
|
+
variant,
|
|
69
|
+
" \xB7 ",
|
|
70
|
+
state,
|
|
71
|
+
" state"
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ jsx(StaticShowcase, { state, variant })
|
|
74
|
+
] }),
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
76
|
+
/* @__PURE__ */ jsx("div", { style: sectionLabel, children: "interactive" }),
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
LanguageSwitcher,
|
|
79
|
+
{
|
|
80
|
+
locales: DEMO_LOCALES,
|
|
81
|
+
value: locale,
|
|
82
|
+
onChange: setLocale,
|
|
83
|
+
variant,
|
|
84
|
+
disabled: state === "disabled"
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
] }),
|
|
88
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
89
|
+
/* @__PURE__ */ jsx("div", { style: sectionLabel, children: "one locale configured \u2192 renders nothing" }),
|
|
90
|
+
/* @__PURE__ */ jsxs(
|
|
91
|
+
"div",
|
|
92
|
+
{
|
|
93
|
+
style: {
|
|
94
|
+
border: "1px dashed var(--color-border)",
|
|
95
|
+
borderRadius: 8,
|
|
96
|
+
padding: 12,
|
|
97
|
+
fontSize: 12,
|
|
98
|
+
color: "var(--color-text-muted)"
|
|
99
|
+
},
|
|
100
|
+
children: [
|
|
101
|
+
/* @__PURE__ */ jsx(LanguageSwitcher, { locales: ["en-US"], value: "en-US", onChange: () => {
|
|
102
|
+
} }),
|
|
103
|
+
"(empty \u2014 no wrapper, no disabled control)"
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
] })
|
|
108
|
+
] });
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
LanguageSwitcherPreview
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=language-switcher-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/language-switcher/language-switcher-preview.tsx"],"sourcesContent":["import { useState, type CSSProperties } from 'react';\n\nimport { cn } from '@/helpers';\nimport type { PreviewProps } from '@/previews/types';\nimport { Icon, LanguageSwitcher } from '@/ui';\n\ntype Styles = PreviewProps['styles'];\n\n/** A spread of scripts — Latin, Cyrillic, CJK — so the endonym rendering and\n * the trigger's width behaviour are both visible at a glance. */\nconst DEMO_LOCALES = ['en-US', 'de-DE', 'uk-UA', 'fr-FR', 'ja-JP', 'pl-PL'];\n\nfunction buildVars(styles: Styles): CSSProperties {\n return {\n '--uxm-language-switcher-bg': styles.bg as string,\n '--uxm-language-switcher-border-color': styles.borderColor as string,\n '--uxm-language-switcher-radius': `${styles.radius}px`,\n '--uxm-language-switcher-color': styles.color as string,\n '--uxm-language-switcher-font-size': `${styles.fontSize}px`,\n '--uxm-language-switcher-gap': `${styles.gap}px`,\n '--uxm-language-switcher-padding-x': `${styles.paddingX}px`,\n '--uxm-language-switcher-padding-y': `${styles.paddingY}px`,\n '--uxm-language-switcher-hover-bg': styles.hoverBg as string,\n '--uxm-language-switcher-hover-border-color': styles.hoverBorderColor as string,\n '--uxm-language-switcher-open-bg': styles.openBg as string,\n '--uxm-language-switcher-open-border-color': styles.openBorderColor as string,\n '--uxm-language-switcher-focus-ring': styles.focusRing as string,\n '--uxm-language-switcher-caret-color': styles.caretColor as string,\n '--uxm-language-switcher-disabled-opacity':\n styles.disabledOpacity != null ? String(styles.disabledOpacity) : undefined,\n } as CSSProperties;\n}\n\n/**\n * Static showcase — hand-rendered trigger markup, not the real atom.\n *\n * The atom routes `className` to the Listbox root and exposes no hook onto the\n * trigger, so the `--state-*` forced modifier cannot be projected through it\n * (same reason ButtonGroup's preview hand-renders). Inert: `pointer-events:\n * none` stops the production `:hover` firing on mouse-over and `tabIndex={-1}`\n * keeps it out of the focus order, so a real Tab press cannot override the\n * forced state being demonstrated.\n */\nfunction StaticShowcase({ state, variant }: { state: string; variant: 'full' | 'compact' }) {\n return (\n <div className=\"uxm-language-switcher\" style={{ pointerEvents: 'none' }}>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n className={cn(\n 'uxm-language-switcher__trigger',\n `uxm-language-switcher__trigger--${variant}`,\n state !== 'default' && `uxm-language-switcher__trigger--state-${state}`,\n )}\n >\n <Icon glyph=\"globe\" size={16} />\n {variant === 'full' && <span className=\"uxm-language-switcher__current\">Deutsch</span>}\n <span className=\"uxm-language-switcher__caret\">\n <Icon\n glyph=\"chevron-down\"\n size={12}\n strokeWidth={2.2}\n style={{ transform: state === 'open' ? 'rotate(180deg)' : 'none' }}\n />\n </span>\n </button>\n </div>\n );\n}\n\nexport function LanguageSwitcherPreview({ styles, variants }: PreviewProps) {\n const variant = ((variants.variant as string) ?? 'full') as 'full' | 'compact';\n const state = (variants.state as string) ?? 'default';\n const [locale, setLocale] = useState('en-US');\n const cssVars = buildVars(styles);\n\n const sectionLabel = {\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 12,\n letterSpacing: 0.5,\n textTransform: 'uppercase' as const,\n };\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 32, ...cssVars } as CSSProperties}>\n <div>\n <div style={sectionLabel}>\n {variant} · {state} state\n </div>\n <StaticShowcase state={state} variant={variant} />\n </div>\n\n {/* Live instance — the real atom, exercising the production :hover /\n :focus-visible / open rules through the same projected vars. */}\n <div>\n <div style={sectionLabel}>interactive</div>\n <LanguageSwitcher\n locales={DEMO_LOCALES}\n value={locale}\n onChange={setLocale}\n variant={variant}\n disabled={state === 'disabled'}\n />\n </div>\n\n {/* The zero-cost-absence rule, shown rather than described: the same atom\n with a single configured locale emits nothing. A realm with one\n language depends on this, and it is easy to regress into a disabled\n control, so it earns a slot. */}\n <div>\n <div style={sectionLabel}>one locale configured → renders nothing</div>\n <div\n style={{\n border: '1px dashed var(--color-border)',\n borderRadius: 8,\n padding: 12,\n fontSize: 12,\n color: 'var(--color-text-muted)',\n }}\n >\n <LanguageSwitcher locales={['en-US']} value=\"en-US\" onChange={() => {}} />\n (empty — no wrapper, no disabled control)\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|