@rebasepro/ui 0.25.1 → 0.27.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/dist/components/BooleanSwitchWithLabel.es.js +1 -1
- package/dist/components/BooleanSwitchWithLabel.es.js.map +1 -1
- package/dist/components/DateTimeField.es.js +1 -1
- package/dist/components/DateTimeField.es.js.map +1 -1
- package/dist/components/Dialog.d.ts +61 -12
- package/dist/components/Dialog.es.js +56 -13
- package/dist/components/Dialog.es.js.map +1 -1
- package/dist/components/ExpandablePanel.es.js +1 -1
- package/dist/components/ExpandablePanel.es.js.map +1 -1
- package/dist/components/SearchBar.es.js +1 -2
- package/dist/components/SearchBar.es.js.map +1 -1
- package/dist/components/Select.es.js +1 -1
- package/dist/components/Select.es.js.map +1 -1
- package/dist/icons/index.d.ts +1 -1
- package/dist/icons/index.es.js +2 -2
- package/dist/index.es.js +2 -2
- package/dist/src/theme.css +14 -7
- package/dist/styles.d.ts +14 -10
- package/dist/styles.es.js +14 -10
- package/dist/styles.es.js.map +1 -1
- package/dist/theme.css +14 -7
- package/dist/views/CollectionView/CollectionCardView.es.js +1 -1
- package/dist/views/CollectionView/CollectionCardView.es.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ var BooleanSwitchWithLabel = function BooleanSwitchWithLabel({ value, position =
|
|
|
24
24
|
ref,
|
|
25
25
|
onFocus,
|
|
26
26
|
onBlur,
|
|
27
|
-
className: cls(!invisible && "bg-surface-field border border-
|
|
27
|
+
className: cls(!invisible && "bg-surface-field border border-transparent", !invisible && (disabled ? "bg-surface-field/60" : "transition-colors duration-150 hover:bg-surface-field-hover"), disabled ? "cursor-default" : "cursor-pointer", "rounded-lg max-w-full justify-between box-border relative inline-flex items-center", !invisible && focus && !disabled ? focusedClasses : "", error ? "text-red-500 dark:text-red-600" : focus && !disabled ? "text-primary" : !disabled ? "text-text-primary dark:text-text-primary-dark" : "text-text-secondary dark:text-text-secondary-dark", controlHeightMixin[size], size === "small" || size === "smallest" ? "pl-2" : "pl-4", size === "small" || size === "smallest" ? "pr-4" : "pr-6", position === "end" ? "flex-row-reverse" : "flex-row", fullWidth ? "w-full" : "", className),
|
|
28
28
|
onClick: disabled ? void 0 : (e) => {
|
|
29
29
|
if (props.allowIndeterminate) {
|
|
30
30
|
const onChange = onValueChange;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BooleanSwitchWithLabel.es.js","names":[],"sources":["../../src/components/BooleanSwitchWithLabel.tsx"],"sourcesContent":["\"use client\";\nimport React from \"react\";\nimport {\n controlHeightMixin,\n fieldBackgroundDisabledMixin,\n fieldBackgroundHoverMixin,\n fieldBackgroundMixin,\n focusedClasses,\n focusedDisabled\n} from \"../styles\";\nimport { BooleanSwitch, BooleanSwitchProps } from \"./BooleanSwitch\";\nimport { cls } from \"../util\";\n\nexport type BooleanSwitchWithLabelProps = BooleanSwitchProps & {\n position?: \"start\" | \"end\",\n invisible?: boolean,\n label?: React.ReactNode,\n error?: boolean,\n autoFocus?: boolean,\n fullWidth?: boolean,\n className?: string,\n inputClassName?: string,\n};\n\n/**\n * Simple boolean switch.\n *\n */\nexport const BooleanSwitchWithLabel = function BooleanSwitchWithLabel({\n value,\n position = \"end\",\n size = \"medium\",\n invisible,\n onValueChange,\n error,\n label,\n autoFocus,\n disabled,\n className,\n fullWidth = true,\n inputClassName,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n }: BooleanSwitchWithLabelProps) {\n\n const ref = React.useRef<HTMLDivElement | null>(null);\n const refInput = React.useRef<HTMLButtonElement | null>(null);\n const switchLabelId = React.useId();\n const [_, setFocused] = React.useState(autoFocus)\n const onFocus = () => setFocused(true);\n const onBlur = () => setFocused(false);\n\n React.useEffect(() => {\n if (autoFocus) refInput.current?.focus();\n // Mount only, like the `autoFocus` attribute it stands in for.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const focus = document.activeElement === refInput?.current || document.activeElement === ref?.current\n\n return (\n // The row is a click target only. It used to be a second\n // `role=\"switch\"` carrying the label, wrapped around the real switch\n // (the one that takes focus), which had no name: a screen reader\n // tabbing in heard \"switch, off\". The label now names the switch.\n <div\n ref={ref}\n onFocus={onFocus}\n onBlur={onBlur}\n className={cls(\n !invisible && fieldBackgroundMixin,\n !invisible && (disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin),\n disabled ? \"cursor-default\" : \"cursor-pointer\",\n \"rounded-lg max-w-full justify-between box-border relative inline-flex items-center\",\n !invisible && focus && !disabled ? focusedClasses : \"\",\n error ? \"text-red-500 dark:text-red-600\" : (focus && !disabled ? \"text-primary\" : (!disabled ? \"text-text-primary dark:text-text-primary-dark\" : \"text-text-secondary dark:text-text-secondary-dark\")),\n // The one control size scale — 28/32/40/48. This component still\n // carried its own map (…/44/64), so a switch at `large` stood\n // 16px taller than the text field beside it, which read as a\n // giant empty box once the label moved out of the control.\n controlHeightMixin[size],\n size === \"small\" || size === \"smallest\" ? \"pl-2\" : \"pl-4\",\n size === \"small\" || size === \"smallest\" ? \"pr-4\" : \"pr-6\",\n position === \"end\" ? \"flex-row-reverse\" : \"flex-row\",\n fullWidth ? \"w-full\" : \"\",\n className\n )}\n onClick={disabled ? undefined : (e) => {\n if (props.allowIndeterminate) {\n const onChange = onValueChange as ((newValue: boolean | null) => void) | undefined;\n if (value === null || value === undefined) onChange?.(true)\n else if (value) onChange?.(false)\n else onChange?.(null);\n } else {\n onValueChange?.(!value);\n }\n }}\n >\n\n <BooleanSwitch\n value={value}\n ref={refInput}\n size={size}\n aria-labelledby={ariaLabelledBy ?? (label ? switchLabelId : undefined)}\n aria-label={ariaLabel}\n // The row draws the focus ring around the whole field; a second\n // ring on the switch inside it would be noise. Invisible rows\n // have no ring of their own, so the switch keeps it.\n className={cls(invisible ? (focus ? focusedClasses : \"\") : focusedDisabled, inputClassName)}\n disabled={disabled}\n {...props}\n />\n\n <div id={switchLabelId} className={cls(\n \"grow\",\n position === \"end\" ? \"mr-4\" : \"ml-4\",\n size === \"small\" || size === \"smallest\" ? \"text-sm\" : \"text-base\"\n )}>\n {label}\n </div>\n\n </div>\n\n );\n};\n"],"mappings":";;;;;;;;;;;AA4BA,IAAa,yBAAyB,SAAS,uBAAuB,EACI,OACA,WAAW,OACX,OAAO,UACP,WACA,eACA,OACA,OACA,WACA,UACA,WACA,YAAY,MACZ,gBACA,cAAc,WACd,mBAAmB,gBACnB,GAAG,SACyB;CAElG,MAAM,MAAM,MAAM,OAA8B,IAAI;CACpD,MAAM,WAAW,MAAM,OAAiC,IAAI;CAC5D,MAAM,gBAAgB,MAAM,MAAM;CAClC,MAAM,CAAC,GAAG,cAAc,MAAM,SAAS,SAAS;CAChD,MAAM,gBAAgB,WAAW,IAAI;CACrC,MAAM,eAAe,WAAW,KAAK;CAErC,MAAM,gBAAgB;EAClB,IAAI,WAAW,SAAS,SAAS,MAAM;CAG3C,GAAG,CAAC,CAAC;CAEL,MAAM,QAAQ,SAAS,kBAAkB,UAAU,WAAW,SAAS,kBAAkB,KAAK;CAE9F,OAKI,qBAAC,OAAD;EACS;EACI;EACD;EACR,WAAW,IACP,CAAC,aAAA,
|
|
1
|
+
{"version":3,"file":"BooleanSwitchWithLabel.es.js","names":[],"sources":["../../src/components/BooleanSwitchWithLabel.tsx"],"sourcesContent":["\"use client\";\nimport React from \"react\";\nimport {\n controlHeightMixin,\n fieldBackgroundDisabledMixin,\n fieldBackgroundHoverMixin,\n fieldBackgroundMixin,\n focusedClasses,\n focusedDisabled\n} from \"../styles\";\nimport { BooleanSwitch, BooleanSwitchProps } from \"./BooleanSwitch\";\nimport { cls } from \"../util\";\n\nexport type BooleanSwitchWithLabelProps = BooleanSwitchProps & {\n position?: \"start\" | \"end\",\n invisible?: boolean,\n label?: React.ReactNode,\n error?: boolean,\n autoFocus?: boolean,\n fullWidth?: boolean,\n className?: string,\n inputClassName?: string,\n};\n\n/**\n * Simple boolean switch.\n *\n */\nexport const BooleanSwitchWithLabel = function BooleanSwitchWithLabel({\n value,\n position = \"end\",\n size = \"medium\",\n invisible,\n onValueChange,\n error,\n label,\n autoFocus,\n disabled,\n className,\n fullWidth = true,\n inputClassName,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n }: BooleanSwitchWithLabelProps) {\n\n const ref = React.useRef<HTMLDivElement | null>(null);\n const refInput = React.useRef<HTMLButtonElement | null>(null);\n const switchLabelId = React.useId();\n const [_, setFocused] = React.useState(autoFocus)\n const onFocus = () => setFocused(true);\n const onBlur = () => setFocused(false);\n\n React.useEffect(() => {\n if (autoFocus) refInput.current?.focus();\n // Mount only, like the `autoFocus` attribute it stands in for.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const focus = document.activeElement === refInput?.current || document.activeElement === ref?.current\n\n return (\n // The row is a click target only. It used to be a second\n // `role=\"switch\"` carrying the label, wrapped around the real switch\n // (the one that takes focus), which had no name: a screen reader\n // tabbing in heard \"switch, off\". The label now names the switch.\n <div\n ref={ref}\n onFocus={onFocus}\n onBlur={onBlur}\n className={cls(\n !invisible && fieldBackgroundMixin,\n !invisible && (disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin),\n disabled ? \"cursor-default\" : \"cursor-pointer\",\n \"rounded-lg max-w-full justify-between box-border relative inline-flex items-center\",\n !invisible && focus && !disabled ? focusedClasses : \"\",\n error ? \"text-red-500 dark:text-red-600\" : (focus && !disabled ? \"text-primary\" : (!disabled ? \"text-text-primary dark:text-text-primary-dark\" : \"text-text-secondary dark:text-text-secondary-dark\")),\n // The one control size scale — 28/32/40/48. This component still\n // carried its own map (…/44/64), so a switch at `large` stood\n // 16px taller than the text field beside it, which read as a\n // giant empty box once the label moved out of the control.\n controlHeightMixin[size],\n size === \"small\" || size === \"smallest\" ? \"pl-2\" : \"pl-4\",\n size === \"small\" || size === \"smallest\" ? \"pr-4\" : \"pr-6\",\n position === \"end\" ? \"flex-row-reverse\" : \"flex-row\",\n fullWidth ? \"w-full\" : \"\",\n className\n )}\n onClick={disabled ? undefined : (e) => {\n if (props.allowIndeterminate) {\n const onChange = onValueChange as ((newValue: boolean | null) => void) | undefined;\n if (value === null || value === undefined) onChange?.(true)\n else if (value) onChange?.(false)\n else onChange?.(null);\n } else {\n onValueChange?.(!value);\n }\n }}\n >\n\n <BooleanSwitch\n value={value}\n ref={refInput}\n size={size}\n aria-labelledby={ariaLabelledBy ?? (label ? switchLabelId : undefined)}\n aria-label={ariaLabel}\n // The row draws the focus ring around the whole field; a second\n // ring on the switch inside it would be noise. Invisible rows\n // have no ring of their own, so the switch keeps it.\n className={cls(invisible ? (focus ? focusedClasses : \"\") : focusedDisabled, inputClassName)}\n disabled={disabled}\n {...props}\n />\n\n <div id={switchLabelId} className={cls(\n \"grow\",\n position === \"end\" ? \"mr-4\" : \"ml-4\",\n size === \"small\" || size === \"smallest\" ? \"text-sm\" : \"text-base\"\n )}>\n {label}\n </div>\n\n </div>\n\n );\n};\n"],"mappings":";;;;;;;;;;;AA4BA,IAAa,yBAAyB,SAAS,uBAAuB,EACI,OACA,WAAW,OACX,OAAO,UACP,WACA,eACA,OACA,OACA,WACA,UACA,WACA,YAAY,MACZ,gBACA,cAAc,WACd,mBAAmB,gBACnB,GAAG,SACyB;CAElG,MAAM,MAAM,MAAM,OAA8B,IAAI;CACpD,MAAM,WAAW,MAAM,OAAiC,IAAI;CAC5D,MAAM,gBAAgB,MAAM,MAAM;CAClC,MAAM,CAAC,GAAG,cAAc,MAAM,SAAS,SAAS;CAChD,MAAM,gBAAgB,WAAW,IAAI;CACrC,MAAM,eAAe,WAAW,KAAK;CAErC,MAAM,gBAAgB;EAClB,IAAI,WAAW,SAAS,SAAS,MAAM;CAG3C,GAAG,CAAC,CAAC;CAEL,MAAM,QAAQ,SAAS,kBAAkB,UAAU,WAAW,SAAS,kBAAkB,KAAK;CAE9F,OAKI,qBAAC,OAAD;EACS;EACI;EACD;EACR,WAAW,IACP,CAAC,aAAA,8CACD,CAAC,cAAc,WAAA,wBAAA,gEACf,WAAW,mBAAmB,kBAC9B,sFACA,CAAC,aAAa,SAAS,CAAC,WAAW,iBAAiB,IACpD,QAAQ,mCAAoC,SAAS,CAAC,WAAW,iBAAkB,CAAC,WAAW,kDAAkD,qDAKjJ,mBAAmB,OACnB,SAAS,WAAW,SAAS,aAAa,SAAS,QACnD,SAAS,WAAW,SAAS,aAAa,SAAS,QACnD,aAAa,QAAQ,qBAAqB,YAC1C,YAAY,WAAW,IACvB,SACJ;EACA,SAAS,WAAW,KAAA,KAAa,MAAM;GACnC,IAAI,MAAM,oBAAoB;IAC1B,MAAM,WAAW;IACjB,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,WAAW,IAAI;SACrD,IAAI,OAAO,WAAW,KAAK;SAC3B,WAAW,IAAI;GACxB,OACI,gBAAgB,CAAC,KAAK;EAE9B;YA/BJ,CAkCI,oBAAC,eAAD;GACW;GACP,KAAK;GACC;GACN,mBAAiB,mBAAmB,QAAQ,gBAAgB,KAAA;GAC5D,cAAY;GAIZ,WAAW,IAAI,YAAa,QAAQ,iBAAiB,KAAM,iBAAiB,cAAc;GAChF;GACV,GAAI;EACP,CAAA,GAED,oBAAC,OAAD;GAAK,IAAI;GAAe,WAAW,IAC/B,QACA,aAAa,QAAQ,SAAS,QAC9B,SAAS,WAAW,SAAS,aAAa,YAAY,WAC1D;aACK;EACA,CAAA,CAEJ;;AAGb"}
|
|
@@ -150,7 +150,7 @@ var DateTimeField = ({ value, label, onChange, disabled, clearable, mode = "date
|
|
|
150
150
|
setIsTyping(false);
|
|
151
151
|
};
|
|
152
152
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
153
|
-
className: cls("rounded-lg relative max-w-full", !invisible && "bg-surface-field border border-
|
|
153
|
+
className: cls("rounded-lg relative max-w-full", !invisible && "bg-surface-field border border-transparent", disabled ? fieldBackgroundDisabledMixin : invisible ? "" : fieldBackgroundHoverMixin, {
|
|
154
154
|
"min-h-[28px]": size === "smallest",
|
|
155
155
|
"min-h-[32px]": size === "small",
|
|
156
156
|
"min-h-[40px]": size === "medium",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateTimeField.es.js","names":[],"sources":["../../src/components/DateTimeField.tsx"],"sourcesContent":["\n\"use client\";\n\nimport React, { useCallback, useId, useRef, useState } from \"react\";\nimport { CalendarIcon, ClockIcon, XIcon, AlertCircleIcon } from \"lucide-react\";\nimport { iconSize } from \"../icons/Icon\";\nimport { IconButton } from \"./IconButton\";\nimport { fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundMixin } from \"../styles\";\nimport { InputLabel } from \"./InputLabel\";\nimport { Typography } from \"./Typography\";\nimport { cls } from \"../util\";\nimport { useInjectStyles } from \"../hooks\";\nimport { useUiLabels } from \"../hooks/UiLabelsContext\";\n\n/**\n * What the field picks: a day, a day and a time, or a time of day alone. A\n * time of day is a `Date` on 1970-01-01 — how a `time` column travels — read\n * and written in `timezone`.\n */\nexport type DateTimeFieldMode = \"date\" | \"date_time\" | \"time\";\n\nexport type DateTimeFieldProps = {\n value?: Date | null;\n onChange?: (date: Date | null) => void;\n mode?: DateTimeFieldMode;\n disabled?: boolean;\n clearable?: boolean;\n error?: boolean;\n size?: \"smallest\" | \"small\" | \"medium\" | \"large\";\n label?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n inputClassName?: string;\n invisible?: boolean;\n /**\n * Whether the field draws its own calendar button. A table cell turns it\n * off: the cell draws one opener for every kind of editor, in the same\n * place, and opens the picker through `inputRef`.\n */\n pickerButton?: boolean;\n /** The native input, for a caller that opens the picker itself (`showPicker()`). */\n inputRef?: React.Ref<HTMLInputElement>;\n /**\n * BCP 47 locale, set as the input's `lang`. The browser still draws a\n * native date input in its own UI language; this is the hint it may use.\n */\n locale?: string;\n /**\n * IANA timezone string (e.g., \"America/New_York\", \"Europe/London\").\n * Used to display and input dates in the specified timezone.\n * The value passed to onChange will always be in UTC.\n * If not provided, uses the user's local timezone.\n */\n timezone?: string;\n /**\n * Names the control when there is no `label` — e.g. when the surrounding\n * form draws the label itself and passes `label={undefined}`.\n */\n \"aria-label\"?: string;\n};\n\n/** The parts `<input type=\"date\">` / `\"datetime-local\"` values are built from. */\nconst INPUT_PARTS: Intl.DateTimeFormatOptions = {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false\n};\n\n/**\n * Formatters for named time zones, keyed by the IANA name (\"UTC\" included).\n *\n * Building an `Intl.DateTimeFormat` costs about ten times what formatting\n * with one does, and this field needs one per render and three per keystroke\n * when it has a `timezone`. A named zone's rules cannot change during a\n * session, so its formatter is built once; the map holds one entry per zone\n * a field has used. The local zone (no `timezone`) is NOT cached: a formatter\n * fixes the zone it resolved when it was built, while the parse side reads the\n * machine's zone live through `Date`, and a laptop can change zones with the\n * tab open.\n */\nconst zoneFormatters = new Map<string, Intl.DateTimeFormat>();\n\nfunction inputPartsFormatter(timeZone: string | undefined): Intl.DateTimeFormat {\n if (timeZone === undefined) return new Intl.DateTimeFormat(\"en-CA\", INPUT_PARTS);\n let formatter = zoneFormatters.get(timeZone);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(\"en-CA\", {\n ...INPUT_PARTS,\n timeZone\n });\n zoneFormatters.set(timeZone, formatter);\n }\n return formatter;\n}\n\n/** A UTC `Date` as the input's value string, in `timezone` (undefined = local). */\nfunction valueAsInputValue(\n dateValue: Date | null,\n mode: DateTimeFieldMode,\n timezone: string | undefined\n): string {\n if (!dateValue || !(dateValue instanceof Date) || isNaN(dateValue.getTime())) {\n return \"\";\n }\n const pad = (n: number) => n.toString().padStart(2, \"0\");\n\n // The date parts in the target timezone (undefined = local timezone)\n const parts = inputPartsFormatter(timezone).formatToParts(dateValue);\n\n const getPart = (type: string) => parts.find(p => p.type === type)?.value ?? \"\";\n\n const year = getPart(\"year\");\n const month = getPart(\"month\");\n const day = getPart(\"day\");\n\n if (mode === \"time\") {\n return `${getPart(\"hour\")}:${getPart(\"minute\")}`;\n }\n if (mode === \"date\") {\n return `${year}-${month}-${day}`;\n } else {\n const hours = getPart(\"hour\");\n const minutes = getPart(\"minute\");\n return `${year}-${month}-${day}T${hours}:${minutes}`;\n }\n}\n\nexport const DateTimeField: React.FC<DateTimeFieldProps> = ({\n value,\n label,\n onChange,\n disabled,\n clearable,\n mode = \"date\",\n error,\n size = \"large\",\n className,\n style,\n inputClassName,\n invisible,\n pickerButton = true,\n inputRef: inputRefProp,\n timezone,\n locale,\n \"aria-label\": ariaLabel\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n const labels = useUiLabels();\n const setInputRef = useCallback((node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof inputRefProp === \"function\") inputRefProp(node);\n else if (inputRefProp) inputRefProp.current = node;\n }, [inputRefProp]);\n const [focused, setFocused] = useState(false);\n // The label was decorative: an InputLabel with no `htmlFor` beside an input\n // with no `id`, so a date field announced itself by its value even when it\n // visibly had a label. Wired the way TextField already does it.\n const inputId = useId();\n const labelId = `${inputId}-label`;\n const [internalValue, setInternalValue] = useState<string>(\"\");\n const [isTyping, setIsTyping] = useState(false);\n const invalidValue = value !== undefined && value !== null && (!(value instanceof Date) || isNaN(value.getTime()));\n\n // The calendar/clear adornments are IconButtons. Left at their default\n // (`medium`, 40px) they floored the whole field at 40px, so smallest/small\n // rendered identically to medium. Keep them a step below the field.\n const adornmentSize = size === \"large\" ? \"medium\" : size === \"medium\" ? \"small\" : \"smallest\";\n\n useInjectStyles(\"DateTimeField\", inputStyles);\n\n const handleClear = (e: React.MouseEvent) => {\n e.preventDefault();\n // A disabled field is read-only, its adornments included.\n if (disabled) return;\n setInternalValue(\"\");\n setIsTyping(false);\n onChange?.(null);\n };\n\n // Get the UTC offset for a specific date in the target timezone (in minutes)\n const getTimezoneOffsetMinutes = (date: Date, tz?: string): number => {\n if (!tz) {\n // Local timezone: use built-in getTimezoneOffset (returns offset in minutes, inverted sign)\n return -date.getTimezoneOffset();\n }\n // For named timezones, calculate the offset by comparing formatted times\n const utcFormatter = inputPartsFormatter(\"UTC\");\n const tzFormatter = inputPartsFormatter(tz);\n\n const utcParts = utcFormatter.formatToParts(date);\n const tzParts = tzFormatter.formatToParts(date);\n\n const getPart = (parts: Intl.DateTimeFormatPart[], type: string) =>\n parseInt(parts.find(p => p.type === type)?.value ?? \"0\", 10);\n\n const utcDate = new Date(Date.UTC(\n getPart(utcParts, \"year\"),\n getPart(utcParts, \"month\") - 1,\n getPart(utcParts, \"day\"),\n getPart(utcParts, \"hour\"),\n getPart(utcParts, \"minute\")\n ));\n\n const tzDate = new Date(Date.UTC(\n getPart(tzParts, \"year\"),\n getPart(tzParts, \"month\") - 1,\n getPart(tzParts, \"day\"),\n getPart(tzParts, \"hour\"),\n getPart(tzParts, \"minute\")\n ));\n\n return (tzDate.getTime() - utcDate.getTime()) / 60000;\n };\n\n // Handle input value change - convert from display timezone to UTC\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const inputValue = e.target.value;\n setInternalValue(inputValue);\n setIsTyping(true);\n\n if (!inputValue) {\n onChange?.(null);\n return;\n }\n\n try {\n let year: number, month: number, day: number, hours = 0, minutes = 0;\n\n if (mode === \"time\") {\n // Parse time-only input: \"HH:MM\" (or \"HH:MM:SS\"), on the\n // day a time of day travels on.\n [year, month, day] = [1970, 1, 1];\n [hours, minutes] = inputValue.split(\":\").map(Number);\n } else if (mode === \"date\") {\n // Parse date-only input: \"YYYY-MM-DD\"\n [year, month, day] = inputValue.split(\"-\").map(Number);\n } else {\n // Parse datetime-local input: \"YYYY-MM-DDTHH:MM\"\n const [datePart, timePart] = inputValue.split(\"T\");\n [year, month, day] = datePart.split(\"-\").map(Number);\n [hours, minutes] = timePart.split(\":\").map(Number);\n }\n\n // Built field by field: `new Date(year, …)` and `Date.UTC(year, …)`\n // read a year of 0–99 as 1900–1999, so a year typed digit by digit\n // (0002, 0020) came out as 1902 and 1920.\n let resultDate: Date;\n\n if (!timezone) {\n // No timezone specified: interpret input as local time (backward compatible)\n resultDate = new Date(0);\n resultDate.setFullYear(year, month - 1, day);\n resultDate.setHours(hours, minutes, 0, 0);\n } else {\n // Timezone specified: interpret input as that timezone and convert to UTC.\n // If the user entered 00:00 in Mexico (UTC-6, offset=-360), the\n // wall-clock time read as UTC is 00:00Z, and subtracting -360\n // minutes gives 06:00 UTC.\n const wallClock = new Date(0);\n wallClock.setUTCFullYear(year, month - 1, day);\n wallClock.setUTCHours(hours, minutes, 0, 0);\n const wallClockAsUtc = wallClock.getTime();\n\n // The offset belongs to the instant being entered, not to the\n // wall-clock time read as UTC: within hours of a DST switch those\n // two sit on opposite sides of it. So the offset at the wall\n // clock is only a first guess, and the offset at the instant it\n // points to is the one that applies.\n const firstGuess = getTimezoneOffsetMinutes(new Date(wallClockAsUtc), timezone);\n const offsetMinutes = getTimezoneOffsetMinutes(new Date(wallClockAsUtc - firstGuess * 60000), timezone);\n resultDate = new Date(wallClockAsUtc - offsetMinutes * 60000);\n\n // A time the spring switch skips (02:30 in New York) has no\n // instant of its own; it moves forward across the gap.\n if (getTimezoneOffsetMinutes(resultDate, timezone) !== offsetMinutes) {\n resultDate = new Date(wallClockAsUtc - firstGuess * 60000);\n }\n }\n\n if (isNaN(resultDate.getTime())) {\n throw new Error(\"Invalid date\");\n }\n\n onChange?.(resultDate);\n } catch (e) {\n // Don't call onChange with null while typing\n return;\n }\n };\n\n // Formatted when the value, mode or zone changes, not on every render.\n // Focusing formats afresh (below), so typing always starts from the\n // current wall-clock text.\n const valueTime = value instanceof Date ? value.getTime() : undefined;\n const displayValue = React.useMemo(\n () => valueAsInputValue(valueTime === undefined ? null : new Date(valueTime), mode, timezone),\n [valueTime, mode, timezone]\n );\n\n const handleFocus = () => {\n setFocused(true);\n setIsTyping(true);\n setInternalValue(valueAsInputValue(value ?? null, mode, timezone));\n };\n\n const handleBlur = () => {\n setFocused(false);\n setIsTyping(false);\n };\n\n return (\n <>\n <div\n className={cls(\n \"rounded-lg relative max-w-full\",\n !invisible && fieldBackgroundMixin,\n // An invisible field has no hover fill of its own: whatever frames it (a table cell) carries the hover.\n disabled ? fieldBackgroundDisabledMixin : (invisible ? \"\" : fieldBackgroundHoverMixin),\n {\n \"min-h-[28px]\": size === \"smallest\",\n \"min-h-[32px]\": size === \"small\",\n \"min-h-[40px]\": size === \"medium\",\n \"min-h-[48px]\": size === \"large\"\n },\n className\n )}\n style={style}\n onClick={() => {\n if (!disabled) {\n inputRef.current?.focus();\n }\n }}\n >\n {label && (\n <InputLabel\n id={labelId}\n htmlFor={inputId}\n className={cls(\n \"absolute top-1 pointer-events-none\",\n !error\n ? focused\n ? \"text-primary\"\n : \"text-text-secondary dark:text-text-secondary-dark\"\n : \"text-red-600 dark:text-red-500\",\n disabled ? \"opacity-50\" : \"\"\n )}\n shrink={true}\n // shrink={hasValue || focused}\n >\n {label}\n </InputLabel>\n )}\n\n <input\n ref={setInputRef}\n id={inputId}\n aria-labelledby={label ? labelId : undefined}\n aria-label={label ? undefined : ariaLabel}\n type={mode === \"date_time\" ? \"datetime-local\" : mode === \"time\" ? \"time\" : \"date\"}\n lang={locale}\n aria-invalid={error || undefined}\n value={isTyping ? internalValue : displayValue}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={disabled}\n className={cls(\n \"w-full outline-hidden bg-transparent leading-normal text-sm px-3\",\n // Room for the adornments drawn over the input's right end.\n pickerButton ? (clearable ? \"pr-14\" : \"pr-12\") : (clearable ? \"pr-12\" : \"\"),\n \"rounded-lg\",\n {\n \"min-h-[28px]\": size === \"smallest\",\n \"min-h-[32px]\": size === \"small\",\n \"min-h-[40px]\": size === \"medium\",\n \"min-h-[48px]\": size === \"large\"\n },\n // A flat py-2 (16px) plus the line box floored\n // the field at 40px, so smallest/small/medium all\n // rendered the same height. Scale it with `size`.\n label\n ? \"pt-8 pb-2\"\n : { smallest: \"py-0.5\", small: \"py-1\", medium: \"py-2\", large: \"py-3\" }[size],\n inputClassName,\n disabled &&\n \"border border-transparent outline-hidden opacity-50 dark:opacity-50 text-surface-accent-600 dark:text-surface-accent-500\"\n )}\n />\n {pickerButton && <IconButton\n size={adornmentSize}\n aria-label={mode === \"time\" ? labels.openTimePicker : \"Open calendar\"}\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation();\n try {\n inputRef.current?.showPicker();\n } catch {\n // The browser refuses (a disabled or read-only input, or\n // no user activation): there is no picker to open, and\n // the field still takes typing.\n }\n }}\n className=\"absolute right-3 top-1/2 transform -translate-y-1/2 text-surface-accent-500!\"\n >\n {mode === \"time\" ? <ClockIcon/> : <CalendarIcon/>}\n </IconButton>}\n {clearable && value && (\n <IconButton\n size={adornmentSize}\n aria-label=\"Clear date\"\n disabled={disabled}\n onClick={handleClear}\n className=\"absolute right-14 top-1/2 transform -translate-y-1/2 text-surface-accent-400 \"\n >\n <XIcon/>\n </IconButton>\n )}\n </div>\n {invalidValue && (\n <div className=\"flex items-center m-2\">\n <AlertCircleIcon size={iconSize.small} color={\"error\"}/>\n <div className=\"pl-2\">\n <Typography variant={\"body2\"}>\n Invalid date value for this field\n </Typography>\n <Typography variant={\"body2\"}>\n {`The provided value is: ${JSON.stringify(value)}`}\n </Typography>\n </div>\n </div>\n )}\n </>\n );\n};\n\nconst inputStyles = `\n /* Hide the default calendar icon in Chrome, Safari, Edge, Opera */\n input[type=\"date\"]::-webkit-calendar-picker-indicator,\n input[type=\"datetime-local\"]::-webkit-calendar-picker-indicator,\n input[type=\"time\"]::-webkit-calendar-picker-indicator {\n display: none;\n -webkit-appearance: none;\n }\n /* Hide default calendar icon in Firefox */\n input[type=\"date\"],\n input[type=\"datetime-local\"],\n input[type=\"time\"] {\n -moz-appearance:textfield;\n }\n `;\n"],"mappings":";;;;;;;;;;;;;;AA8DA,IAAM,cAA0C;CAC5C,MAAM;CACN,OAAO;CACP,KAAK;CACL,MAAM;CACN,QAAQ;CACR,QAAQ;AACZ;;;;;;;;;;;;;AAcA,IAAM,iCAAiB,IAAI,IAAiC;AAE5D,SAAS,oBAAoB,UAAmD;CAC5E,IAAI,aAAa,KAAA,GAAW,OAAO,IAAI,KAAK,eAAe,SAAS,WAAW;CAC/E,IAAI,YAAY,eAAe,IAAI,QAAQ;CAC3C,IAAI,CAAC,WAAW;EACZ,YAAY,IAAI,KAAK,eAAe,SAAS;GACzC,GAAG;GACH;EACJ,CAAC;EACD,eAAe,IAAI,UAAU,SAAS;CAC1C;CACA,OAAO;AACX;;AAGA,SAAS,kBACL,WACA,MACA,UACM;CACN,IAAI,CAAC,aAAa,EAAE,qBAAqB,SAAS,MAAM,UAAU,QAAQ,CAAC,GACvE,OAAO;CAKX,MAAM,QAAQ,oBAAoB,QAAQ,CAAC,CAAC,cAAc,SAAS;CAEnE,MAAM,WAAW,SAAiB,MAAM,MAAK,MAAK,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS;CAE7E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,QAAQ,QAAQ,OAAO;CAC7B,MAAM,MAAM,QAAQ,KAAK;CAEzB,IAAI,SAAS,QACT,OAAO,GAAG,QAAQ,MAAM,EAAE,GAAG,QAAQ,QAAQ;CAEjD,IAAI,SAAS,QACT,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG;MAI3B,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,GAFjB,QAAQ,MAEY,EAAM,GADxB,QAAQ,QACmB;AAEnD;AAEA,IAAa,iBAA+C,EACxD,OACA,OACA,UACA,UACA,WACA,OAAO,QACP,OACA,OAAO,SACP,WACA,OACA,gBACA,WACA,eAAe,MACf,UAAU,cACV,UACA,QACA,cAAc,gBACZ;CACF,MAAM,WAAW,OAAyB,IAAI;CAC9C,MAAM,SAAS,YAAY;CAC3B,MAAM,cAAc,aAAa,SAAkC;EAC/D,SAAS,UAAU;EACnB,IAAI,OAAO,iBAAiB,YAAY,aAAa,IAAI;OACpD,IAAI,cAAc,aAAa,UAAU;CAClD,GAAG,CAAC,YAAY,CAAC;CACjB,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAI5C,MAAM,UAAU,MAAM;CACtB,MAAM,UAAU,GAAG,QAAQ;CAC3B,MAAM,CAAC,eAAe,oBAAoB,SAAiB,EAAE;CAC7D,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,eAAe,UAAU,KAAA,KAAa,UAAU,SAAS,EAAE,iBAAiB,SAAS,MAAM,MAAM,QAAQ,CAAC;CAKhH,MAAM,gBAAgB,SAAS,UAAU,WAAW,SAAS,WAAW,UAAU;CAElF,gBAAgB,iBAAiB,WAAW;CAE5C,MAAM,eAAe,MAAwB;EACzC,EAAE,eAAe;EAEjB,IAAI,UAAU;EACd,iBAAiB,EAAE;EACnB,YAAY,KAAK;EACjB,WAAW,IAAI;CACnB;CAGA,MAAM,4BAA4B,MAAY,OAAwB;EAClE,IAAI,CAAC,IAED,OAAO,CAAC,KAAK,kBAAkB;EAGnC,MAAM,eAAe,oBAAoB,KAAK;EAC9C,MAAM,cAAc,oBAAoB,EAAE;EAE1C,MAAM,WAAW,aAAa,cAAc,IAAI;EAChD,MAAM,UAAU,YAAY,cAAc,IAAI;EAE9C,MAAM,WAAW,OAAkC,SAC/C,SAAS,MAAM,MAAK,MAAK,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,KAAK,EAAE;EAE/D,MAAM,UAAU,IAAI,KAAK,KAAK,IAC1B,QAAQ,UAAU,MAAM,GACxB,QAAQ,UAAU,OAAO,IAAI,GAC7B,QAAQ,UAAU,KAAK,GACvB,QAAQ,UAAU,MAAM,GACxB,QAAQ,UAAU,QAAQ,CAC9B,CAAC;EAUD,QAAQ,IARW,KAAK,KAAK,IACzB,QAAQ,SAAS,MAAM,GACvB,QAAQ,SAAS,OAAO,IAAI,GAC5B,QAAQ,SAAS,KAAK,GACtB,QAAQ,SAAS,MAAM,GACvB,QAAQ,SAAS,QAAQ,CAC7B,CAEQ,CAAA,CAAO,QAAQ,IAAI,QAAQ,QAAQ,KAAK;CACpD;CAGA,MAAM,qBAAqB,MAA2C;EAClE,MAAM,aAAa,EAAE,OAAO;EAC5B,iBAAiB,UAAU;EAC3B,YAAY,IAAI;EAEhB,IAAI,CAAC,YAAY;GACb,WAAW,IAAI;GACf;EACJ;EAEA,IAAI;GACA,IAAI,MAAc,OAAe,KAAa,QAAQ,GAAG,UAAU;GAEnE,IAAI,SAAS,QAAQ;IAGjB,CAAC,MAAM,OAAO,OAAO;KAAC;KAAM;KAAG;IAAC;IAChC,CAAC,OAAO,WAAW,WAAW,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;GACvD,OAAO,IAAI,SAAS,QAEhB,CAAC,MAAM,OAAO,OAAO,WAAW,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;QAClD;IAEH,MAAM,CAAC,UAAU,YAAY,WAAW,MAAM,GAAG;IACjD,CAAC,MAAM,OAAO,OAAO,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;IACnD,CAAC,OAAO,WAAW,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;GACrD;GAKA,IAAI;GAEJ,IAAI,CAAC,UAAU;IAEX,6BAAa,IAAI,KAAK,CAAC;IACvB,WAAW,YAAY,MAAM,QAAQ,GAAG,GAAG;IAC3C,WAAW,SAAS,OAAO,SAAS,GAAG,CAAC;GAC5C,OAAO;IAKH,MAAM,4BAAY,IAAI,KAAK,CAAC;IAC5B,UAAU,eAAe,MAAM,QAAQ,GAAG,GAAG;IAC7C,UAAU,YAAY,OAAO,SAAS,GAAG,CAAC;IAC1C,MAAM,iBAAiB,UAAU,QAAQ;IAOzC,MAAM,aAAa,yBAAyB,IAAI,KAAK,cAAc,GAAG,QAAQ;IAC9E,MAAM,gBAAgB,yCAAyB,IAAI,KAAK,iBAAiB,aAAa,GAAK,GAAG,QAAQ;IACtG,6BAAa,IAAI,KAAK,iBAAiB,gBAAgB,GAAK;IAI5D,IAAI,yBAAyB,YAAY,QAAQ,MAAM,eACnD,6BAAa,IAAI,KAAK,iBAAiB,aAAa,GAAK;GAEjE;GAEA,IAAI,MAAM,WAAW,QAAQ,CAAC,GAC1B,MAAM,IAAI,MAAM,cAAc;GAGlC,WAAW,UAAU;EACzB,SAAS,GAAG;GAER;EACJ;CACJ;CAKA,MAAM,YAAY,iBAAiB,OAAO,MAAM,QAAQ,IAAI,KAAA;CAC5D,MAAM,eAAe,MAAM,cACjB,kBAAkB,cAAc,KAAA,IAAY,OAAO,IAAI,KAAK,SAAS,GAAG,MAAM,QAAQ,GAC5F;EAAC;EAAW;EAAM;CAAQ,CAC9B;CAEA,MAAM,oBAAoB;EACtB,WAAW,IAAI;EACf,YAAY,IAAI;EAChB,iBAAiB,kBAAkB,SAAS,MAAM,MAAM,QAAQ,CAAC;CACrE;CAEA,MAAM,mBAAmB;EACrB,WAAW,KAAK;EAChB,YAAY,KAAK;CACrB;CAEA,OACI,qBAAA,UAAA,EAAA,UAAA,CACI,qBAAC,OAAD;EACI,WAAW,IACP,kCACA,CAAC,aAAA,2CAEL,WAAW,+BAAgC,YAAY,KAAK,2BACxD;GACI,gBAAgB,SAAS;GACzB,gBAAgB,SAAS;GACzB,gBAAgB,SAAS;GACzB,gBAAgB,SAAS;EAC7B,GACA,SACJ;EACO;EACP,eAAe;GACX,IAAI,CAAC,UACD,SAAS,SAAS,MAAM;EAEhC;YAnBJ;GAqBK,SACG,oBAAC,YAAD;IACI,IAAI;IACJ,SAAS;IACT,WAAW,IACP,sCACA,CAAC,QACK,UACI,iBACA,sDACJ,kCACN,WAAW,eAAe,EAC9B;IACA,QAAQ;cAGP;GACO,CAAA;GAGhB,oBAAC,SAAD;IACI,KAAK;IACL,IAAI;IACJ,mBAAiB,QAAQ,UAAU,KAAA;IACnC,cAAY,QAAQ,KAAA,IAAY;IAChC,MAAM,SAAS,cAAc,mBAAmB,SAAS,SAAS,SAAS;IAC3E,MAAM;IACN,gBAAc,SAAS,KAAA;IACvB,OAAO,WAAW,gBAAgB;IAClC,UAAU;IACV,SAAS;IACT,QAAQ;IACE;IACV,WAAW,IACP,oEAEA,eAAgB,YAAY,UAAU,UAAY,YAAY,UAAU,IACxE,cACA;KACI,gBAAgB,SAAS;KACzB,gBAAgB,SAAS;KACzB,gBAAgB,SAAS;KACzB,gBAAgB,SAAS;IAC7B,GAIA,QACM,cACA;KAAE,UAAU;KAAU,OAAO;KAAQ,QAAQ;KAAQ,OAAO;IAAO,EAAE,OAC3E,gBACA,YACA,0HACJ;GACH,CAAA;GACA,gBAAgB,oBAAC,YAAD;IACb,MAAM;IACN,cAAY,SAAS,SAAS,OAAO,iBAAiB;IAC5C;IACV,UAAU,MAAM;KACZ,EAAE,gBAAgB;KAClB,IAAI;MACA,SAAS,SAAS,WAAW;KACjC,QAAQ,CAIR;IACJ;IACA,WAAU;cAET,SAAS,SAAS,oBAAC,WAAD,CAAW,CAAA,IAAI,oBAAC,cAAD,CAAc,CAAA;GACxC,CAAA;GACX,aAAa,SACV,oBAAC,YAAD;IACI,MAAM;IACN,cAAW;IACD;IACV,SAAS;IACT,WAAU;cAEV,oBAAC,OAAD,CAAO,CAAA;GACC,CAAA;EAEf;KACJ,gBACG,qBAAC,OAAD;EAAK,WAAU;YAAf,CACI,oBAAC,iBAAD;GAAiB,MAAM,SAAS;GAAO,OAAO;EAAS,CAAA,GACvD,qBAAC,OAAD;GAAK,WAAU;aAAf,CACI,oBAAC,YAAD;IAAY,SAAS;cAAS;GAElB,CAAA,GACZ,oBAAC,YAAD;IAAY,SAAS;cAChB,0BAA0B,KAAK,UAAU,KAAK;GACvC,CAAA,CACX;IACJ;GAEX,EAAA,CAAA;AAEV;AAEA,IAAM,cAAc"}
|
|
1
|
+
{"version":3,"file":"DateTimeField.es.js","names":[],"sources":["../../src/components/DateTimeField.tsx"],"sourcesContent":["\n\"use client\";\n\nimport React, { useCallback, useId, useRef, useState } from \"react\";\nimport { CalendarIcon, ClockIcon, XIcon, AlertCircleIcon } from \"lucide-react\";\nimport { iconSize } from \"../icons/Icon\";\nimport { IconButton } from \"./IconButton\";\nimport { fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundMixin } from \"../styles\";\nimport { InputLabel } from \"./InputLabel\";\nimport { Typography } from \"./Typography\";\nimport { cls } from \"../util\";\nimport { useInjectStyles } from \"../hooks\";\nimport { useUiLabels } from \"../hooks/UiLabelsContext\";\n\n/**\n * What the field picks: a day, a day and a time, or a time of day alone. A\n * time of day is a `Date` on 1970-01-01 — how a `time` column travels — read\n * and written in `timezone`.\n */\nexport type DateTimeFieldMode = \"date\" | \"date_time\" | \"time\";\n\nexport type DateTimeFieldProps = {\n value?: Date | null;\n onChange?: (date: Date | null) => void;\n mode?: DateTimeFieldMode;\n disabled?: boolean;\n clearable?: boolean;\n error?: boolean;\n size?: \"smallest\" | \"small\" | \"medium\" | \"large\";\n label?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n inputClassName?: string;\n invisible?: boolean;\n /**\n * Whether the field draws its own calendar button. A table cell turns it\n * off: the cell draws one opener for every kind of editor, in the same\n * place, and opens the picker through `inputRef`.\n */\n pickerButton?: boolean;\n /** The native input, for a caller that opens the picker itself (`showPicker()`). */\n inputRef?: React.Ref<HTMLInputElement>;\n /**\n * BCP 47 locale, set as the input's `lang`. The browser still draws a\n * native date input in its own UI language; this is the hint it may use.\n */\n locale?: string;\n /**\n * IANA timezone string (e.g., \"America/New_York\", \"Europe/London\").\n * Used to display and input dates in the specified timezone.\n * The value passed to onChange will always be in UTC.\n * If not provided, uses the user's local timezone.\n */\n timezone?: string;\n /**\n * Names the control when there is no `label` — e.g. when the surrounding\n * form draws the label itself and passes `label={undefined}`.\n */\n \"aria-label\"?: string;\n};\n\n/** The parts `<input type=\"date\">` / `\"datetime-local\"` values are built from. */\nconst INPUT_PARTS: Intl.DateTimeFormatOptions = {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false\n};\n\n/**\n * Formatters for named time zones, keyed by the IANA name (\"UTC\" included).\n *\n * Building an `Intl.DateTimeFormat` costs about ten times what formatting\n * with one does, and this field needs one per render and three per keystroke\n * when it has a `timezone`. A named zone's rules cannot change during a\n * session, so its formatter is built once; the map holds one entry per zone\n * a field has used. The local zone (no `timezone`) is NOT cached: a formatter\n * fixes the zone it resolved when it was built, while the parse side reads the\n * machine's zone live through `Date`, and a laptop can change zones with the\n * tab open.\n */\nconst zoneFormatters = new Map<string, Intl.DateTimeFormat>();\n\nfunction inputPartsFormatter(timeZone: string | undefined): Intl.DateTimeFormat {\n if (timeZone === undefined) return new Intl.DateTimeFormat(\"en-CA\", INPUT_PARTS);\n let formatter = zoneFormatters.get(timeZone);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(\"en-CA\", {\n ...INPUT_PARTS,\n timeZone\n });\n zoneFormatters.set(timeZone, formatter);\n }\n return formatter;\n}\n\n/** A UTC `Date` as the input's value string, in `timezone` (undefined = local). */\nfunction valueAsInputValue(\n dateValue: Date | null,\n mode: DateTimeFieldMode,\n timezone: string | undefined\n): string {\n if (!dateValue || !(dateValue instanceof Date) || isNaN(dateValue.getTime())) {\n return \"\";\n }\n const pad = (n: number) => n.toString().padStart(2, \"0\");\n\n // The date parts in the target timezone (undefined = local timezone)\n const parts = inputPartsFormatter(timezone).formatToParts(dateValue);\n\n const getPart = (type: string) => parts.find(p => p.type === type)?.value ?? \"\";\n\n const year = getPart(\"year\");\n const month = getPart(\"month\");\n const day = getPart(\"day\");\n\n if (mode === \"time\") {\n return `${getPart(\"hour\")}:${getPart(\"minute\")}`;\n }\n if (mode === \"date\") {\n return `${year}-${month}-${day}`;\n } else {\n const hours = getPart(\"hour\");\n const minutes = getPart(\"minute\");\n return `${year}-${month}-${day}T${hours}:${minutes}`;\n }\n}\n\nexport const DateTimeField: React.FC<DateTimeFieldProps> = ({\n value,\n label,\n onChange,\n disabled,\n clearable,\n mode = \"date\",\n error,\n size = \"large\",\n className,\n style,\n inputClassName,\n invisible,\n pickerButton = true,\n inputRef: inputRefProp,\n timezone,\n locale,\n \"aria-label\": ariaLabel\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n const labels = useUiLabels();\n const setInputRef = useCallback((node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof inputRefProp === \"function\") inputRefProp(node);\n else if (inputRefProp) inputRefProp.current = node;\n }, [inputRefProp]);\n const [focused, setFocused] = useState(false);\n // The label was decorative: an InputLabel with no `htmlFor` beside an input\n // with no `id`, so a date field announced itself by its value even when it\n // visibly had a label. Wired the way TextField already does it.\n const inputId = useId();\n const labelId = `${inputId}-label`;\n const [internalValue, setInternalValue] = useState<string>(\"\");\n const [isTyping, setIsTyping] = useState(false);\n const invalidValue = value !== undefined && value !== null && (!(value instanceof Date) || isNaN(value.getTime()));\n\n // The calendar/clear adornments are IconButtons. Left at their default\n // (`medium`, 40px) they floored the whole field at 40px, so smallest/small\n // rendered identically to medium. Keep them a step below the field.\n const adornmentSize = size === \"large\" ? \"medium\" : size === \"medium\" ? \"small\" : \"smallest\";\n\n useInjectStyles(\"DateTimeField\", inputStyles);\n\n const handleClear = (e: React.MouseEvent) => {\n e.preventDefault();\n // A disabled field is read-only, its adornments included.\n if (disabled) return;\n setInternalValue(\"\");\n setIsTyping(false);\n onChange?.(null);\n };\n\n // Get the UTC offset for a specific date in the target timezone (in minutes)\n const getTimezoneOffsetMinutes = (date: Date, tz?: string): number => {\n if (!tz) {\n // Local timezone: use built-in getTimezoneOffset (returns offset in minutes, inverted sign)\n return -date.getTimezoneOffset();\n }\n // For named timezones, calculate the offset by comparing formatted times\n const utcFormatter = inputPartsFormatter(\"UTC\");\n const tzFormatter = inputPartsFormatter(tz);\n\n const utcParts = utcFormatter.formatToParts(date);\n const tzParts = tzFormatter.formatToParts(date);\n\n const getPart = (parts: Intl.DateTimeFormatPart[], type: string) =>\n parseInt(parts.find(p => p.type === type)?.value ?? \"0\", 10);\n\n const utcDate = new Date(Date.UTC(\n getPart(utcParts, \"year\"),\n getPart(utcParts, \"month\") - 1,\n getPart(utcParts, \"day\"),\n getPart(utcParts, \"hour\"),\n getPart(utcParts, \"minute\")\n ));\n\n const tzDate = new Date(Date.UTC(\n getPart(tzParts, \"year\"),\n getPart(tzParts, \"month\") - 1,\n getPart(tzParts, \"day\"),\n getPart(tzParts, \"hour\"),\n getPart(tzParts, \"minute\")\n ));\n\n return (tzDate.getTime() - utcDate.getTime()) / 60000;\n };\n\n // Handle input value change - convert from display timezone to UTC\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const inputValue = e.target.value;\n setInternalValue(inputValue);\n setIsTyping(true);\n\n if (!inputValue) {\n onChange?.(null);\n return;\n }\n\n try {\n let year: number, month: number, day: number, hours = 0, minutes = 0;\n\n if (mode === \"time\") {\n // Parse time-only input: \"HH:MM\" (or \"HH:MM:SS\"), on the\n // day a time of day travels on.\n [year, month, day] = [1970, 1, 1];\n [hours, minutes] = inputValue.split(\":\").map(Number);\n } else if (mode === \"date\") {\n // Parse date-only input: \"YYYY-MM-DD\"\n [year, month, day] = inputValue.split(\"-\").map(Number);\n } else {\n // Parse datetime-local input: \"YYYY-MM-DDTHH:MM\"\n const [datePart, timePart] = inputValue.split(\"T\");\n [year, month, day] = datePart.split(\"-\").map(Number);\n [hours, minutes] = timePart.split(\":\").map(Number);\n }\n\n // Built field by field: `new Date(year, …)` and `Date.UTC(year, …)`\n // read a year of 0–99 as 1900–1999, so a year typed digit by digit\n // (0002, 0020) came out as 1902 and 1920.\n let resultDate: Date;\n\n if (!timezone) {\n // No timezone specified: interpret input as local time (backward compatible)\n resultDate = new Date(0);\n resultDate.setFullYear(year, month - 1, day);\n resultDate.setHours(hours, minutes, 0, 0);\n } else {\n // Timezone specified: interpret input as that timezone and convert to UTC.\n // If the user entered 00:00 in Mexico (UTC-6, offset=-360), the\n // wall-clock time read as UTC is 00:00Z, and subtracting -360\n // minutes gives 06:00 UTC.\n const wallClock = new Date(0);\n wallClock.setUTCFullYear(year, month - 1, day);\n wallClock.setUTCHours(hours, minutes, 0, 0);\n const wallClockAsUtc = wallClock.getTime();\n\n // The offset belongs to the instant being entered, not to the\n // wall-clock time read as UTC: within hours of a DST switch those\n // two sit on opposite sides of it. So the offset at the wall\n // clock is only a first guess, and the offset at the instant it\n // points to is the one that applies.\n const firstGuess = getTimezoneOffsetMinutes(new Date(wallClockAsUtc), timezone);\n const offsetMinutes = getTimezoneOffsetMinutes(new Date(wallClockAsUtc - firstGuess * 60000), timezone);\n resultDate = new Date(wallClockAsUtc - offsetMinutes * 60000);\n\n // A time the spring switch skips (02:30 in New York) has no\n // instant of its own; it moves forward across the gap.\n if (getTimezoneOffsetMinutes(resultDate, timezone) !== offsetMinutes) {\n resultDate = new Date(wallClockAsUtc - firstGuess * 60000);\n }\n }\n\n if (isNaN(resultDate.getTime())) {\n throw new Error(\"Invalid date\");\n }\n\n onChange?.(resultDate);\n } catch (e) {\n // Don't call onChange with null while typing\n return;\n }\n };\n\n // Formatted when the value, mode or zone changes, not on every render.\n // Focusing formats afresh (below), so typing always starts from the\n // current wall-clock text.\n const valueTime = value instanceof Date ? value.getTime() : undefined;\n const displayValue = React.useMemo(\n () => valueAsInputValue(valueTime === undefined ? null : new Date(valueTime), mode, timezone),\n [valueTime, mode, timezone]\n );\n\n const handleFocus = () => {\n setFocused(true);\n setIsTyping(true);\n setInternalValue(valueAsInputValue(value ?? null, mode, timezone));\n };\n\n const handleBlur = () => {\n setFocused(false);\n setIsTyping(false);\n };\n\n return (\n <>\n <div\n className={cls(\n \"rounded-lg relative max-w-full\",\n !invisible && fieldBackgroundMixin,\n // An invisible field has no hover fill of its own: whatever frames it (a table cell) carries the hover.\n disabled ? fieldBackgroundDisabledMixin : (invisible ? \"\" : fieldBackgroundHoverMixin),\n {\n \"min-h-[28px]\": size === \"smallest\",\n \"min-h-[32px]\": size === \"small\",\n \"min-h-[40px]\": size === \"medium\",\n \"min-h-[48px]\": size === \"large\"\n },\n className\n )}\n style={style}\n onClick={() => {\n if (!disabled) {\n inputRef.current?.focus();\n }\n }}\n >\n {label && (\n <InputLabel\n id={labelId}\n htmlFor={inputId}\n className={cls(\n \"absolute top-1 pointer-events-none\",\n !error\n ? focused\n ? \"text-primary\"\n : \"text-text-secondary dark:text-text-secondary-dark\"\n : \"text-red-600 dark:text-red-500\",\n disabled ? \"opacity-50\" : \"\"\n )}\n shrink={true}\n // shrink={hasValue || focused}\n >\n {label}\n </InputLabel>\n )}\n\n <input\n ref={setInputRef}\n id={inputId}\n aria-labelledby={label ? labelId : undefined}\n aria-label={label ? undefined : ariaLabel}\n type={mode === \"date_time\" ? \"datetime-local\" : mode === \"time\" ? \"time\" : \"date\"}\n lang={locale}\n aria-invalid={error || undefined}\n value={isTyping ? internalValue : displayValue}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n disabled={disabled}\n className={cls(\n \"w-full outline-hidden bg-transparent leading-normal text-sm px-3\",\n // Room for the adornments drawn over the input's right end.\n pickerButton ? (clearable ? \"pr-14\" : \"pr-12\") : (clearable ? \"pr-12\" : \"\"),\n \"rounded-lg\",\n {\n \"min-h-[28px]\": size === \"smallest\",\n \"min-h-[32px]\": size === \"small\",\n \"min-h-[40px]\": size === \"medium\",\n \"min-h-[48px]\": size === \"large\"\n },\n // A flat py-2 (16px) plus the line box floored\n // the field at 40px, so smallest/small/medium all\n // rendered the same height. Scale it with `size`.\n label\n ? \"pt-8 pb-2\"\n : { smallest: \"py-0.5\", small: \"py-1\", medium: \"py-2\", large: \"py-3\" }[size],\n inputClassName,\n disabled &&\n \"border border-transparent outline-hidden opacity-50 dark:opacity-50 text-surface-accent-600 dark:text-surface-accent-500\"\n )}\n />\n {pickerButton && <IconButton\n size={adornmentSize}\n aria-label={mode === \"time\" ? labels.openTimePicker : \"Open calendar\"}\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation();\n try {\n inputRef.current?.showPicker();\n } catch {\n // The browser refuses (a disabled or read-only input, or\n // no user activation): there is no picker to open, and\n // the field still takes typing.\n }\n }}\n className=\"absolute right-3 top-1/2 transform -translate-y-1/2 text-surface-accent-500!\"\n >\n {mode === \"time\" ? <ClockIcon/> : <CalendarIcon/>}\n </IconButton>}\n {clearable && value && (\n <IconButton\n size={adornmentSize}\n aria-label=\"Clear date\"\n disabled={disabled}\n onClick={handleClear}\n className=\"absolute right-14 top-1/2 transform -translate-y-1/2 text-surface-accent-400 \"\n >\n <XIcon/>\n </IconButton>\n )}\n </div>\n {invalidValue && (\n <div className=\"flex items-center m-2\">\n <AlertCircleIcon size={iconSize.small} color={\"error\"}/>\n <div className=\"pl-2\">\n <Typography variant={\"body2\"}>\n Invalid date value for this field\n </Typography>\n <Typography variant={\"body2\"}>\n {`The provided value is: ${JSON.stringify(value)}`}\n </Typography>\n </div>\n </div>\n )}\n </>\n );\n};\n\nconst inputStyles = `\n /* Hide the default calendar icon in Chrome, Safari, Edge, Opera */\n input[type=\"date\"]::-webkit-calendar-picker-indicator,\n input[type=\"datetime-local\"]::-webkit-calendar-picker-indicator,\n input[type=\"time\"]::-webkit-calendar-picker-indicator {\n display: none;\n -webkit-appearance: none;\n }\n /* Hide default calendar icon in Firefox */\n input[type=\"date\"],\n input[type=\"datetime-local\"],\n input[type=\"time\"] {\n -moz-appearance:textfield;\n }\n `;\n"],"mappings":"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"}
|
|
@@ -5,10 +5,16 @@ export type DialogProps = {
|
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
className?: string;
|
|
7
7
|
containerClassName?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Size the paper to most of the viewport's width (`w-11/12`), capped at
|
|
10
|
+
* `maxWidth`. On by default. Off, the paper takes `maxWidth`'s width, still
|
|
11
|
+
* capped at that share of the viewport, so it never runs off a phone.
|
|
12
|
+
*/
|
|
8
13
|
fullWidth?: boolean;
|
|
9
14
|
fullHeight?: boolean;
|
|
10
15
|
fullScreen?: boolean;
|
|
11
16
|
scrollable?: boolean;
|
|
17
|
+
/** The widest the paper gets (default `"lg"`). Ignored when `fullScreen`. */
|
|
12
18
|
maxWidth?: keyof typeof widthClasses;
|
|
13
19
|
modal?: boolean;
|
|
14
20
|
onOpenAutoFocus?: (e: Event) => void;
|
|
@@ -43,19 +49,62 @@ export type DialogProps = {
|
|
|
43
49
|
portalContainer?: HTMLElement | null;
|
|
44
50
|
"aria-describedby"?: string;
|
|
45
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Each size as the paper's cap (`max`) and as its width when it does not fill
|
|
54
|
+
* a share of the viewport (`width`). The two are never applied together with
|
|
55
|
+
* another width: the class merge keeps the last of two widths, and that is how
|
|
56
|
+
* `w-lg` used to drop `w-11/12` and make every default dialog 512px wide on a
|
|
57
|
+
* 375px phone.
|
|
58
|
+
*/
|
|
46
59
|
declare const widthClasses: {
|
|
47
|
-
xs:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
xs: {
|
|
61
|
+
max: string;
|
|
62
|
+
width: string;
|
|
63
|
+
};
|
|
64
|
+
sm: {
|
|
65
|
+
max: string;
|
|
66
|
+
width: string;
|
|
67
|
+
};
|
|
68
|
+
md: {
|
|
69
|
+
max: string;
|
|
70
|
+
width: string;
|
|
71
|
+
};
|
|
72
|
+
lg: {
|
|
73
|
+
max: string;
|
|
74
|
+
width: string;
|
|
75
|
+
};
|
|
76
|
+
xl: {
|
|
77
|
+
max: string;
|
|
78
|
+
width: string;
|
|
79
|
+
};
|
|
80
|
+
"2xl": {
|
|
81
|
+
max: string;
|
|
82
|
+
width: string;
|
|
83
|
+
};
|
|
84
|
+
"3xl": {
|
|
85
|
+
max: string;
|
|
86
|
+
width: string;
|
|
87
|
+
};
|
|
88
|
+
"4xl": {
|
|
89
|
+
max: string;
|
|
90
|
+
width: string;
|
|
91
|
+
};
|
|
92
|
+
"5xl": {
|
|
93
|
+
max: string;
|
|
94
|
+
width: string;
|
|
95
|
+
};
|
|
96
|
+
"6xl": {
|
|
97
|
+
max: string;
|
|
98
|
+
width: string;
|
|
99
|
+
};
|
|
100
|
+
"7xl": {
|
|
101
|
+
max: string;
|
|
102
|
+
width: string;
|
|
103
|
+
};
|
|
104
|
+
full: {
|
|
105
|
+
max: string;
|
|
106
|
+
width: string;
|
|
107
|
+
};
|
|
59
108
|
};
|
|
60
109
|
export declare const Dialog: ({ open, onOpenChange, children, className, containerClassName, fullWidth, fullHeight, fullScreen, scrollable, maxWidth, modal, onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, onPointerDownOutside, onInteractOutside, dismissOnBackdrop, initialFocus, disableInitialFocus, portalContainer, "aria-describedby": ariaDescribedby }: DialogProps) => React.JSX.Element;
|
|
61
110
|
export {};
|
|
@@ -8,19 +8,62 @@ import { useEffect, useState } from "react";
|
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
10
10
|
//#region src/components/Dialog.tsx
|
|
11
|
+
/**
|
|
12
|
+
* Each size as the paper's cap (`max`) and as its width when it does not fill
|
|
13
|
+
* a share of the viewport (`width`). The two are never applied together with
|
|
14
|
+
* another width: the class merge keeps the last of two widths, and that is how
|
|
15
|
+
* `w-lg` used to drop `w-11/12` and make every default dialog 512px wide on a
|
|
16
|
+
* 375px phone.
|
|
17
|
+
*/
|
|
11
18
|
var widthClasses = {
|
|
12
|
-
xs:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
xs: {
|
|
20
|
+
max: "max-w-xs",
|
|
21
|
+
width: "w-xs"
|
|
22
|
+
},
|
|
23
|
+
sm: {
|
|
24
|
+
max: "max-w-sm",
|
|
25
|
+
width: "w-sm"
|
|
26
|
+
},
|
|
27
|
+
md: {
|
|
28
|
+
max: "max-w-md",
|
|
29
|
+
width: "w-md"
|
|
30
|
+
},
|
|
31
|
+
lg: {
|
|
32
|
+
max: "max-w-lg",
|
|
33
|
+
width: "w-lg"
|
|
34
|
+
},
|
|
35
|
+
xl: {
|
|
36
|
+
max: "max-w-xl",
|
|
37
|
+
width: "w-xl"
|
|
38
|
+
},
|
|
39
|
+
"2xl": {
|
|
40
|
+
max: "max-w-2xl",
|
|
41
|
+
width: "w-2xl"
|
|
42
|
+
},
|
|
43
|
+
"3xl": {
|
|
44
|
+
max: "max-w-3xl",
|
|
45
|
+
width: "w-3xl"
|
|
46
|
+
},
|
|
47
|
+
"4xl": {
|
|
48
|
+
max: "max-w-4xl",
|
|
49
|
+
width: "w-4xl"
|
|
50
|
+
},
|
|
51
|
+
"5xl": {
|
|
52
|
+
max: "max-w-5xl",
|
|
53
|
+
width: "w-5xl"
|
|
54
|
+
},
|
|
55
|
+
"6xl": {
|
|
56
|
+
max: "max-w-6xl",
|
|
57
|
+
width: "w-6xl"
|
|
58
|
+
},
|
|
59
|
+
"7xl": {
|
|
60
|
+
max: "max-w-7xl",
|
|
61
|
+
width: "w-7xl"
|
|
62
|
+
},
|
|
63
|
+
full: {
|
|
64
|
+
max: "max-w-full",
|
|
65
|
+
width: "w-full"
|
|
66
|
+
}
|
|
24
67
|
};
|
|
25
68
|
var Dialog = ({ open, onOpenChange, children, className, containerClassName, fullWidth = true, fullHeight, fullScreen, scrollable = true, maxWidth = "lg", modal = true, onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, onPointerDownOutside, onInteractOutside, dismissOnBackdrop = false, initialFocus, disableInitialFocus = true, portalContainer, "aria-describedby": ariaDescribedby }) => {
|
|
26
69
|
const [displayed, setDisplayed] = useState(false);
|
|
@@ -76,7 +119,7 @@ var Dialog = ({ open, onOpenChange, children, className, containerClassName, ful
|
|
|
76
119
|
onFocusOutside: (e) => e.preventDefault(),
|
|
77
120
|
onInteractOutside,
|
|
78
121
|
"aria-describedby": ariaDescribedby,
|
|
79
|
-
className: cls("relative z-60 outline-none", focusedDisabled, fullWidth && !fullScreen ? "w-11/12" : void 0, fullHeight && !fullScreen ? "h-[90vh]" : void 0, fullScreen ? "h-screen w-screen" : void 0, maxWidth && !fullScreen ? widthClasses[maxWidth] : void 0),
|
|
122
|
+
className: cls("relative z-60 outline-none", focusedDisabled, fullWidth && !fullScreen ? "w-11/12" : void 0, fullHeight && !fullScreen ? "h-[90vh]" : void 0, fullScreen ? "h-screen w-screen" : void 0, maxWidth && !fullScreen ? fullWidth ? widthClasses[maxWidth].max : [widthClasses[maxWidth].width, "max-w-11/12"] : void 0, !maxWidth && !fullWidth && !fullScreen ? "max-w-11/12" : void 0),
|
|
80
123
|
children: [/* @__PURE__ */ jsx("div", {
|
|
81
124
|
className: cls(paperMixin, "rounded-2xl", "z-60", "relative", "w-full", "overflow-hidden", "outline-none focus:outline-none", fullHeight || fullScreen ? "h-full" : void 0, "text-text-primary dark:text-text-primary-dark", "justify-center items-center", fullScreen ? void 0 : "max-h-[90vh] shadow-lg", "ease-in-out duration-200 motion-reduce:transition-none", scrollable && "overflow-y-auto", displayed && open ? "opacity-100 scale-100" : "opacity-0 scale-[0.97]", className),
|
|
82
125
|
children: /* @__PURE__ */ jsx(PortalContainerProvider, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.es.js","names":[],"sources":["../../src/components/Dialog.tsx"],"sourcesContent":["\"use client\";\nimport React, { useEffect, useState } from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { focusedDisabled, paperMixin } from \"../styles\";\nimport { cls } from \"../util\";\nimport { PortalContainerProvider, usePortalContainer } from \"../hooks/PortalContainerContext\";\nimport { useRestoreInterruptedFocus } from \"../hooks/useRestoreInterruptedFocus\";\nimport { useModalFocus } from \"../hooks/useModalFocus\";\n\nexport type DialogProps = {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n children: React.ReactNode;\n className?: string;\n containerClassName?: string;\n fullWidth?: boolean;\n fullHeight?: boolean;\n fullScreen?: boolean;\n scrollable?: boolean;\n maxWidth?: keyof typeof widthClasses;\n modal?: boolean;\n onOpenAutoFocus?: (e: Event) => void;\n /**\n * Called when focus is about to return after the dialog closes. By default\n * it goes back to the element that had it when the dialog opened; call\n * `preventDefault()` to put it somewhere else.\n */\n onCloseAutoFocus?: (e: Event) => void;\n onEscapeKeyDown?: (e: KeyboardEvent) => void;\n onPointerDownOutside?: (e: Event) => void;\n onInteractOutside?: (e: Event) => void;\n /**\n * Whether a click on the dimmed backdrop closes the dialog. Off by default:\n * a record or a form dialog holds work that one stray click must not throw\n * away. Turn it on for a picker, where closing loses nothing. Escape closes\n * either kind.\n */\n dismissOnBackdrop?: boolean;\n /**\n * The element to focus when the dialog opens, for a dialog whose purpose is\n * one field. Without it the dialog focuses itself, so a screen reader\n * announces it by its title and a phone does not raise its keyboard.\n */\n initialFocus?: React.RefObject<HTMLElement | null>;\n /**\n * If `true` (the default), the dialog will not focus the first focusable\n * element when opened: it focuses its own container instead. `false` lets\n * the first focusable element take focus.\n */\n disableInitialFocus?: boolean;\n portalContainer?: HTMLElement | null;\n \"aria-describedby\"?: string;\n};\n\nconst widthClasses = {\n xs: \"max-w-xs w-xs\",\n sm: \"max-w-sm w-sm\",\n md: \"max-w-md w-md\",\n lg: \"max-w-lg w-lg\",\n xl: \"max-w-xl w-xl\",\n \"2xl\": \"max-w-2xl w-2xl\",\n \"3xl\": \"max-w-3xl w-3xl\",\n \"4xl\": \"max-w-4xl w-4xl\",\n \"5xl\": \"max-w-5xl w-5xl\",\n \"6xl\": \"max-w-6xl w-6xl\",\n \"7xl\": \"max-w-7xl w-7xl\",\n full: \"max-w-full w-full\"\n};\n\nexport const Dialog = ({\n open,\n onOpenChange,\n children,\n className,\n containerClassName,\n fullWidth = true,\n fullHeight,\n fullScreen,\n scrollable = true,\n maxWidth = \"lg\",\n modal = true,\n onOpenAutoFocus,\n onCloseAutoFocus,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n dismissOnBackdrop = false,\n initialFocus,\n disableInitialFocus = true,\n portalContainer,\n \"aria-describedby\": ariaDescribedby\n }: DialogProps) => {\n const [displayed, setDisplayed] = useState(false);\n // Where popups rendered *inside* this dialog get portaled to. A modal\n // dialog locks scrolling everywhere but its own content, so a popup\n // portaled to `document.body` renders fine and then refuses to scroll:\n // `react-remove-scroll` cancels every wheel event outside the lock.\n // Handing descendants this host keeps them inside it.\n const [popupHost, setPopupHost] = useState<HTMLDivElement | null>(null);\n\n // The focus trap's container. Tab between two fields used to land here\n // whenever the first field's tooltip closed on the way out.\n const [contentEl, setContentEl] = useState<HTMLDivElement | null>(null);\n useRestoreInterruptedFocus(contentEl);\n\n // The two surfaces outside the paper: a pointer-down on either is a\n // click on the backdrop. Anything else outside the paper — a toast, a\n // popup some other layer portaled to the body — is not.\n const [overlayEl, setOverlayEl] = useState<HTMLDivElement | null>(null);\n const [layerEl, setLayerEl] = useState<HTMLDivElement | null>(null);\n\n const focus = useModalFocus({\n open: Boolean(open),\n initialFocus,\n focusContainer: disableInitialFocus,\n onOpenAutoFocus,\n onCloseAutoFocus\n });\n\n // Get the portal container from context\n const contextContainer = usePortalContainer();\n\n // Prioritize manual prop, fallback to context container\n const finalContainer = (portalContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;\n\n useEffect(() => {\n if (!open) {\n const timeout = setTimeout(() => {\n setDisplayed(false);\n }, 100);\n return () => clearTimeout(timeout);\n } else {\n setDisplayed(true);\n return () => {\n };\n }\n }, [open]);\n\n return (\n <DialogPrimitive.Root open={displayed || open}\n modal={modal}\n onOpenChange={onOpenChange}>\n <DialogPrimitive.Portal container={finalContainer}>\n\n <div ref={setLayerEl}\n className={cls(\"fixed inset-0 z-50 flex justify-center items-center\", containerClassName)}>\n\n <DialogPrimitive.Overlay\n ref={setOverlayEl}\n className={cls(\"fixed inset-0 transition-opacity ease-in-out duration-200 motion-reduce:transition-none bg-surface-scrim backdrop-blur-sm\",\n displayed && open ? \"opacity-100\" : \"opacity-0\",\n \"z-50 fixed top-0 left-0 w-full h-full\"\n )}\n style={{\n pointerEvents: displayed ? \"auto\" : \"none\"\n }}\n />\n\n {/* The content IS the paper's box: what is outside it is the\n backdrop, and what assistive technology and a test runner\n measure as the dialog is what the user sees. It used to fill\n the viewport, so the dimmed area was part of the dialog and\n the outside-click handlers could never fire. The paper itself\n is a child because its open animation scales it, and a\n transform would make it the containing block of every popup\n portaled into the host beside it. */}\n <DialogPrimitive.Content\n ref={setContentEl}\n onEscapeKeyDown={onEscapeKeyDown}\n onOpenAutoFocus={focus.onOpenAutoFocus}\n onCloseAutoFocus={focus.onCloseAutoFocus}\n onPointerDownOutside={(e) => {\n onPointerDownOutside?.(e);\n if (e.defaultPrevented) return;\n const target = e.target;\n const onBackdrop = target === overlayEl || target === layerEl;\n if (!dismissOnBackdrop || !onBackdrop) e.preventDefault();\n }}\n // Focus leaving the paper is not a request to close it. A modal\n // dialog already ignores it; a non-modal one would dismiss.\n onFocusOutside={(e) => e.preventDefault()}\n onInteractOutside={onInteractOutside}\n aria-describedby={ariaDescribedby}\n className={cls(\n \"relative z-60 outline-none\",\n // Focused on open so a screen reader announces it; it is\n // a container, not a control, so it draws no ring.\n focusedDisabled,\n fullWidth && !fullScreen ? \"w-11/12\" : undefined,\n fullHeight && !fullScreen ? \"h-[90vh]\" : undefined,\n fullScreen ? \"h-screen w-screen\" : undefined,\n maxWidth && !fullScreen ? widthClasses[maxWidth] : undefined\n )}\n >\n <div\n className={cls(paperMixin,\n \"rounded-2xl\",\n \"z-60\",\n \"relative\",\n \"w-full\",\n \"overflow-hidden\",\n \"outline-none focus:outline-none\",\n fullHeight || fullScreen ? \"h-full\" : undefined,\n \"text-text-primary dark:text-text-primary-dark\",\n \"justify-center items-center\",\n fullScreen ? undefined : \"max-h-[90vh] shadow-lg\",\n \"ease-in-out duration-200 motion-reduce:transition-none\",\n scrollable && \"overflow-y-auto\",\n displayed && open ? \"opacity-100 scale-100\" : \"opacity-0 scale-[0.97]\",\n className\n )}>\n <PortalContainerProvider container={popupHost}>\n {children}\n </PortalContainerProvider>\n </div>\n\n {/* Sized to nothing so it changes no layout, but positioned and\n stacked above the paper so popups portaled here are not\n painted behind it. */}\n <div ref={setPopupHost} className={\"relative z-70 w-0 h-0\"}/>\n\n </DialogPrimitive.Content>\n </div>\n\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n};\n"],"mappings":";;;;;;;;;;AAsDA,IAAM,eAAe;CACjB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,MAAM;AACV;AAEA,IAAa,UAAU,EACI,MACA,cACA,UACA,WACA,oBACA,YAAY,MACZ,YACA,YACA,aAAa,MACb,WAAW,MACX,QAAQ,MACR,iBACA,kBACA,iBACA,sBACA,mBACA,oBAAoB,OACpB,cACA,sBAAsB,MACtB,iBACA,oBAAoB,sBACL;CACtC,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAMhD,MAAM,CAAC,WAAW,gBAAgB,SAAgC,IAAI;CAItE,MAAM,CAAC,WAAW,gBAAgB,SAAgC,IAAI;CACtE,2BAA2B,SAAS;CAKpC,MAAM,CAAC,WAAW,gBAAgB,SAAgC,IAAI;CACtE,MAAM,CAAC,SAAS,cAAc,SAAgC,IAAI;CAElE,MAAM,QAAQ,cAAc;EACxB,MAAM,QAAQ,IAAI;EAClB;EACA,gBAAgB;EAChB;EACA;CACJ,CAAC;CAGD,MAAM,mBAAmB,mBAAmB;CAG5C,MAAM,iBAAkB,mBAAmB,oBAAoB,KAAA;CAE/D,gBAAgB;EACZ,IAAI,CAAC,MAAM;GACP,MAAM,UAAU,iBAAiB;IAC7B,aAAa,KAAK;GACtB,GAAG,GAAG;GACN,aAAa,aAAa,OAAO;EACrC,OAAO;GACH,aAAa,IAAI;GACjB,aAAa,CACb;EACJ;CACJ,GAAG,CAAC,IAAI,CAAC;CAET,OACI,oBAAC,gBAAgB,MAAjB;EAAsB,MAAM,aAAa;EACZ;EACO;YAChC,oBAAC,gBAAgB,QAAjB;GAAwB,WAAW;aAE/B,qBAAC,OAAD;IAAK,KAAK;IACL,WAAW,IAAI,uDAAuD,kBAAkB;cAD7F,CAGI,oBAAC,gBAAgB,SAAjB;KACI,KAAK;KACL,WAAW,IAAI,6HACX,aAAa,OAAO,gBAAgB,aACpC,uCACJ;KACA,OAAO,EACH,eAAe,YAAY,SAAS,OACxC;IACH,CAAA,GAUD,qBAAC,gBAAgB,SAAjB;KACI,KAAK;KACY;KACjB,iBAAiB,MAAM;KACvB,kBAAkB,MAAM;KACxB,uBAAuB,MAAM;MACzB,uBAAuB,CAAC;MACxB,IAAI,EAAE,kBAAkB;MACxB,MAAM,SAAS,EAAE;MAEjB,IAAI,CAAC,qBAAqB,EADP,WAAW,aAAa,WAAW,UACf,EAAE,eAAe;KAC5D;KAGA,iBAAiB,MAAM,EAAE,eAAe;KACrB;KACnB,oBAAkB;KAClB,WAAW,IACP,8BAGA,iBACA,aAAa,CAAC,aAAa,YAAY,KAAA,GACvC,cAAc,CAAC,aAAa,aAAa,KAAA,GACzC,aAAa,sBAAsB,KAAA,GACnC,YAAY,CAAC,aAAa,aAAa,YAAY,KAAA,CACvD;eA1BJ,CA4BI,oBAAC,OAAD;MACI,WAAW,IAAI,YACX,eACA,QACA,YACA,UACA,mBACA,mCACA,cAAc,aAAa,WAAW,KAAA,GACtC,iDACA,+BACA,aAAa,KAAA,IAAY,0BACzB,0DACA,cAAc,mBACd,aAAa,OAAO,0BAA0B,0BAC9C,SACJ;gBACA,oBAAC,yBAAD;OAAyB,WAAW;OAC/B;MACoB,CAAA;KACxB,CAAA,GAKL,oBAAC,OAAD;MAAK,KAAK;MAAc,WAAW;KAAyB,CAAA,CAEvC;MACxB;;EAEe,CAAA;CACN,CAAA;AAE9B"}
|
|
1
|
+
{"version":3,"file":"Dialog.es.js","names":[],"sources":["../../src/components/Dialog.tsx"],"sourcesContent":["\"use client\";\nimport React, { useEffect, useState } from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { focusedDisabled, paperMixin } from \"../styles\";\nimport { cls } from \"../util\";\nimport { PortalContainerProvider, usePortalContainer } from \"../hooks/PortalContainerContext\";\nimport { useRestoreInterruptedFocus } from \"../hooks/useRestoreInterruptedFocus\";\nimport { useModalFocus } from \"../hooks/useModalFocus\";\n\nexport type DialogProps = {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n children: React.ReactNode;\n className?: string;\n containerClassName?: string;\n /**\n * Size the paper to most of the viewport's width (`w-11/12`), capped at\n * `maxWidth`. On by default. Off, the paper takes `maxWidth`'s width, still\n * capped at that share of the viewport, so it never runs off a phone.\n */\n fullWidth?: boolean;\n fullHeight?: boolean;\n fullScreen?: boolean;\n scrollable?: boolean;\n /** The widest the paper gets (default `\"lg\"`). Ignored when `fullScreen`. */\n maxWidth?: keyof typeof widthClasses;\n modal?: boolean;\n onOpenAutoFocus?: (e: Event) => void;\n /**\n * Called when focus is about to return after the dialog closes. By default\n * it goes back to the element that had it when the dialog opened; call\n * `preventDefault()` to put it somewhere else.\n */\n onCloseAutoFocus?: (e: Event) => void;\n onEscapeKeyDown?: (e: KeyboardEvent) => void;\n onPointerDownOutside?: (e: Event) => void;\n onInteractOutside?: (e: Event) => void;\n /**\n * Whether a click on the dimmed backdrop closes the dialog. Off by default:\n * a record or a form dialog holds work that one stray click must not throw\n * away. Turn it on for a picker, where closing loses nothing. Escape closes\n * either kind.\n */\n dismissOnBackdrop?: boolean;\n /**\n * The element to focus when the dialog opens, for a dialog whose purpose is\n * one field. Without it the dialog focuses itself, so a screen reader\n * announces it by its title and a phone does not raise its keyboard.\n */\n initialFocus?: React.RefObject<HTMLElement | null>;\n /**\n * If `true` (the default), the dialog will not focus the first focusable\n * element when opened: it focuses its own container instead. `false` lets\n * the first focusable element take focus.\n */\n disableInitialFocus?: boolean;\n portalContainer?: HTMLElement | null;\n \"aria-describedby\"?: string;\n};\n\n/**\n * Each size as the paper's cap (`max`) and as its width when it does not fill\n * a share of the viewport (`width`). The two are never applied together with\n * another width: the class merge keeps the last of two widths, and that is how\n * `w-lg` used to drop `w-11/12` and make every default dialog 512px wide on a\n * 375px phone.\n */\nconst widthClasses = {\n xs: { max: \"max-w-xs\", width: \"w-xs\" },\n sm: { max: \"max-w-sm\", width: \"w-sm\" },\n md: { max: \"max-w-md\", width: \"w-md\" },\n lg: { max: \"max-w-lg\", width: \"w-lg\" },\n xl: { max: \"max-w-xl\", width: \"w-xl\" },\n \"2xl\": { max: \"max-w-2xl\", width: \"w-2xl\" },\n \"3xl\": { max: \"max-w-3xl\", width: \"w-3xl\" },\n \"4xl\": { max: \"max-w-4xl\", width: \"w-4xl\" },\n \"5xl\": { max: \"max-w-5xl\", width: \"w-5xl\" },\n \"6xl\": { max: \"max-w-6xl\", width: \"w-6xl\" },\n \"7xl\": { max: \"max-w-7xl\", width: \"w-7xl\" },\n full: { max: \"max-w-full\", width: \"w-full\" }\n};\n\nexport const Dialog = ({\n open,\n onOpenChange,\n children,\n className,\n containerClassName,\n fullWidth = true,\n fullHeight,\n fullScreen,\n scrollable = true,\n maxWidth = \"lg\",\n modal = true,\n onOpenAutoFocus,\n onCloseAutoFocus,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n dismissOnBackdrop = false,\n initialFocus,\n disableInitialFocus = true,\n portalContainer,\n \"aria-describedby\": ariaDescribedby\n }: DialogProps) => {\n const [displayed, setDisplayed] = useState(false);\n // Where popups rendered *inside* this dialog get portaled to. A modal\n // dialog locks scrolling everywhere but its own content, so a popup\n // portaled to `document.body` renders fine and then refuses to scroll:\n // `react-remove-scroll` cancels every wheel event outside the lock.\n // Handing descendants this host keeps them inside it.\n const [popupHost, setPopupHost] = useState<HTMLDivElement | null>(null);\n\n // The focus trap's container. Tab between two fields used to land here\n // whenever the first field's tooltip closed on the way out.\n const [contentEl, setContentEl] = useState<HTMLDivElement | null>(null);\n useRestoreInterruptedFocus(contentEl);\n\n // The two surfaces outside the paper: a pointer-down on either is a\n // click on the backdrop. Anything else outside the paper — a toast, a\n // popup some other layer portaled to the body — is not.\n const [overlayEl, setOverlayEl] = useState<HTMLDivElement | null>(null);\n const [layerEl, setLayerEl] = useState<HTMLDivElement | null>(null);\n\n const focus = useModalFocus({\n open: Boolean(open),\n initialFocus,\n focusContainer: disableInitialFocus,\n onOpenAutoFocus,\n onCloseAutoFocus\n });\n\n // Get the portal container from context\n const contextContainer = usePortalContainer();\n\n // Prioritize manual prop, fallback to context container\n const finalContainer = (portalContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;\n\n useEffect(() => {\n if (!open) {\n const timeout = setTimeout(() => {\n setDisplayed(false);\n }, 100);\n return () => clearTimeout(timeout);\n } else {\n setDisplayed(true);\n return () => {\n };\n }\n }, [open]);\n\n return (\n <DialogPrimitive.Root open={displayed || open}\n modal={modal}\n onOpenChange={onOpenChange}>\n <DialogPrimitive.Portal container={finalContainer}>\n\n <div ref={setLayerEl}\n className={cls(\"fixed inset-0 z-50 flex justify-center items-center\", containerClassName)}>\n\n <DialogPrimitive.Overlay\n ref={setOverlayEl}\n className={cls(\"fixed inset-0 transition-opacity ease-in-out duration-200 motion-reduce:transition-none bg-surface-scrim backdrop-blur-sm\",\n displayed && open ? \"opacity-100\" : \"opacity-0\",\n \"z-50 fixed top-0 left-0 w-full h-full\"\n )}\n style={{\n pointerEvents: displayed ? \"auto\" : \"none\"\n }}\n />\n\n {/* The content IS the paper's box: what is outside it is the\n backdrop, and what assistive technology and a test runner\n measure as the dialog is what the user sees. It used to fill\n the viewport, so the dimmed area was part of the dialog and\n the outside-click handlers could never fire. The paper itself\n is a child because its open animation scales it, and a\n transform would make it the containing block of every popup\n portaled into the host beside it. */}\n <DialogPrimitive.Content\n ref={setContentEl}\n onEscapeKeyDown={onEscapeKeyDown}\n onOpenAutoFocus={focus.onOpenAutoFocus}\n onCloseAutoFocus={focus.onCloseAutoFocus}\n onPointerDownOutside={(e) => {\n onPointerDownOutside?.(e);\n if (e.defaultPrevented) return;\n const target = e.target;\n const onBackdrop = target === overlayEl || target === layerEl;\n if (!dismissOnBackdrop || !onBackdrop) e.preventDefault();\n }}\n // Focus leaving the paper is not a request to close it. A modal\n // dialog already ignores it; a non-modal one would dismiss.\n onFocusOutside={(e) => e.preventDefault()}\n onInteractOutside={onInteractOutside}\n aria-describedby={ariaDescribedby}\n className={cls(\n \"relative z-60 outline-none\",\n // Focused on open so a screen reader announces it; it is\n // a container, not a control, so it draws no ring.\n focusedDisabled,\n // Never wider than the screen: a share of the viewport\n // capped at `maxWidth`, or `maxWidth`'s width capped at\n // that share.\n fullWidth && !fullScreen ? \"w-11/12\" : undefined,\n fullHeight && !fullScreen ? \"h-[90vh]\" : undefined,\n fullScreen ? \"h-screen w-screen\" : undefined,\n maxWidth && !fullScreen\n ? (fullWidth ? widthClasses[maxWidth].max : [widthClasses[maxWidth].width, \"max-w-11/12\"])\n : undefined,\n !maxWidth && !fullWidth && !fullScreen ? \"max-w-11/12\" : undefined\n )}\n >\n <div\n className={cls(paperMixin,\n \"rounded-2xl\",\n \"z-60\",\n \"relative\",\n \"w-full\",\n \"overflow-hidden\",\n \"outline-none focus:outline-none\",\n fullHeight || fullScreen ? \"h-full\" : undefined,\n \"text-text-primary dark:text-text-primary-dark\",\n \"justify-center items-center\",\n fullScreen ? undefined : \"max-h-[90vh] shadow-lg\",\n \"ease-in-out duration-200 motion-reduce:transition-none\",\n scrollable && \"overflow-y-auto\",\n displayed && open ? \"opacity-100 scale-100\" : \"opacity-0 scale-[0.97]\",\n className\n )}>\n <PortalContainerProvider container={popupHost}>\n {children}\n </PortalContainerProvider>\n </div>\n\n {/* Sized to nothing so it changes no layout, but positioned and\n stacked above the paper so popups portaled here are not\n painted behind it. */}\n <div ref={setPopupHost} className={\"relative z-70 w-0 h-0\"}/>\n\n </DialogPrimitive.Content>\n </div>\n\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n};\n"],"mappings":"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"}
|
|
@@ -63,7 +63,7 @@ function ExpandablePanel({ title, children, invisible = false, expanded, onExpan
|
|
|
63
63
|
children: [/* @__PURE__ */ jsx(Collapsible.Trigger, {
|
|
64
64
|
asChild: true,
|
|
65
65
|
children: /* @__PURE__ */ jsxs("div", {
|
|
66
|
-
className: cls("rounded-t-lg flex items-center justify-between w-full min-h-[52px]", "hover:bg-surface-hover active:bg-surface-active", invisible ? "border-b px-2" : "p-4", open ? "py-6" : "py-4", "transition-all duration-200 motion-reduce:transition-none", invisible && "border-hairline", asField && "bg-surface-field border border-
|
|
66
|
+
className: cls("rounded-t-lg flex items-center justify-between w-full min-h-[52px]", "hover:bg-surface-hover active:bg-surface-active", invisible ? "border-b px-2" : "p-4", open ? "py-6" : "py-4", "transition-all duration-200 motion-reduce:transition-none", invisible && "border-hairline", asField && "bg-surface-field border border-transparent", titleClassName, "cursor-pointer"),
|
|
67
67
|
role: "button",
|
|
68
68
|
tabIndex: 0,
|
|
69
69
|
"aria-expanded": open,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpandablePanel.es.js","names":[],"sources":["../../src/components/ExpandablePanel.tsx"],"sourcesContent":["\n\"use client\";\nimport React, { PropsWithChildren, useEffect, useState } from \"react\";\n\nimport * as Collapsible from \"@radix-ui/react-collapsible\";\nimport { defaultBorderMixin, fieldBackgroundMixin } from \"../styles\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport { cls } from \"../util\";\nimport { useInjectStyles } from \"../hooks\";\n\nexport function ExpandablePanel({\n title,\n children,\n invisible = false,\n expanded,\n onExpandedChange,\n initiallyExpanded = true,\n titleClassName,\n asField,\n className,\n innerClassName\n }: PropsWithChildren<{\n title: React.ReactNode,\n invisible?: boolean,\n initiallyExpanded?: boolean;\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void,\n titleClassName?: string,\n asField?: boolean,\n className?: string,\n innerClassName?: string\n}>) {\n\n useInjectStyles(\"ExpandablePanel\", `\n.CollapsibleContent {\n overflow: hidden;\n}\n.CollapsibleContent[data-state='open'] {\n animation: expandablePanelSlideDown 220ms ease-out\n}\n.CollapsibleContent[data-state='closed'] {\n animation: expandablePanelSlideUp 220ms ease-out;\n}\n\n@keyframes expandablePanelSlideDown {\n from {\n height: 0;\n }\n to {\n height: var(--radix-collapsible-content-height);\n }\n}\n\n@keyframes expandablePanelSlideUp {\n from {\n height: var(--radix-collapsible-content-height);\n }\n to {\n height: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .CollapsibleContent[data-state] {\n animation: none;\n }\n}`);\n\n const [open, setOpen] = useState(expanded !== undefined ? expanded : initiallyExpanded);\n const [allowOverflow, setAllowOverflow] = useState(open);\n\n useEffect(() => {\n if (open) {\n setTimeout(() => {\n setAllowOverflow(true);\n }, 220);\n } else {\n setAllowOverflow(false);\n }\n }, [open]);\n\n useEffect(() => {\n if (expanded !== undefined)\n setOpen(expanded);\n }, [expanded]);\n\n return (<>\n <Collapsible.Root\n className={cls(\n !invisible && defaultBorderMixin + \" border\",\n \"rounded-lg\",\n \"w-full\",\n className\n )}\n open={open}\n onOpenChange={(updatedOpen: boolean) => {\n onExpandedChange?.(updatedOpen);\n setOpen(updatedOpen);\n }}>\n\n <Collapsible.Trigger asChild>\n <div\n className={cls(\n \"rounded-t-lg flex items-center justify-between w-full min-h-[52px]\",\n \"hover:bg-surface-hover active:bg-surface-active\",\n invisible ? \"border-b px-2\" : \"p-4\",\n open ? \"py-6\" : \"py-4\",\n \"transition-all duration-200 motion-reduce:transition-none\",\n invisible && defaultBorderMixin,\n asField && fieldBackgroundMixin,\n titleClassName,\n \"cursor-pointer\"\n )}\n role=\"button\"\n tabIndex={0}\n aria-expanded={open}\n // A role=\"button\" has to answer Enter and Space itself:\n // Radix's trigger listens for click only, and a div does\n // not turn a key into a click, so the header took focus and\n // then ignored the keyboard. Only for keys pressed on the\n // header — a control inside the title keeps its own keys.\n onKeyDown={(event) => {\n if (event.target !== event.currentTarget) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n event.preventDefault(); // Space would scroll the page\n event.currentTarget.click();\n }}\n >\n {title}\n <ChevronDownIcon className={cls(\"transition\", open ? \"rotate-180\" : \"\")}/>\n </div>\n </Collapsible.Trigger>\n\n <Collapsible.Content\n className={cls(\"CollapsibleContent\")}\n style={{\n overflow: allowOverflow ? \"visible\" : \"hidden\"\n }}\n >\n <div className={innerClassName}>\n {children}\n </div>\n </Collapsible.Content>\n </Collapsible.Root>\n </>\n )\n}\n"],"mappings":";;;;;;;;;AAUA,SAAgB,gBAAgB,EACI,OACA,UACA,YAAY,OACZ,UACA,kBACA,oBAAoB,MACpB,gBACA,SACA,WACA,kBAWhC;CAEA,gBAAgB,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCrC;CAEE,MAAM,CAAC,MAAM,WAAW,SAAS,aAAa,KAAA,IAAY,WAAW,iBAAiB;CACtF,MAAM,CAAC,eAAe,oBAAoB,SAAS,IAAI;CAEvD,gBAAgB;EACZ,IAAI,MACA,iBAAiB;GACb,iBAAiB,IAAI;EACzB,GAAG,GAAG;OAEN,iBAAiB,KAAK;CAE9B,GAAG,CAAC,IAAI,CAAC;CAET,gBAAgB;EACZ,IAAI,aAAa,KAAA,GACb,QAAQ,QAAQ;CACxB,GAAG,CAAC,QAAQ,CAAC;CAEb,OAAQ,oBAAA,UAAA,EAAA,UACA,qBAAC,YAAY,MAAb;EACI,WAAW,IACP,CAAC,aAAA,0BACD,cACA,UACA,SACJ;EACM;EACN,eAAe,gBAAyB;GACpC,mBAAmB,WAAW;GAC9B,QAAQ,WAAW;EACvB;YAXJ,CAaI,oBAAC,YAAY,SAAb;GAAqB,SAAA;aACjB,qBAAC,OAAD;IACI,WAAW,IACP,sEACA,mDACA,YAAY,kBAAkB,OAC9B,OAAO,SAAS,QAChB,6DACA,aAAA,mBACA,WAAA,
|
|
1
|
+
{"version":3,"file":"ExpandablePanel.es.js","names":[],"sources":["../../src/components/ExpandablePanel.tsx"],"sourcesContent":["\n\"use client\";\nimport React, { PropsWithChildren, useEffect, useState } from \"react\";\n\nimport * as Collapsible from \"@radix-ui/react-collapsible\";\nimport { defaultBorderMixin, fieldBackgroundMixin } from \"../styles\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport { cls } from \"../util\";\nimport { useInjectStyles } from \"../hooks\";\n\nexport function ExpandablePanel({\n title,\n children,\n invisible = false,\n expanded,\n onExpandedChange,\n initiallyExpanded = true,\n titleClassName,\n asField,\n className,\n innerClassName\n }: PropsWithChildren<{\n title: React.ReactNode,\n invisible?: boolean,\n initiallyExpanded?: boolean;\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void,\n titleClassName?: string,\n asField?: boolean,\n className?: string,\n innerClassName?: string\n}>) {\n\n useInjectStyles(\"ExpandablePanel\", `\n.CollapsibleContent {\n overflow: hidden;\n}\n.CollapsibleContent[data-state='open'] {\n animation: expandablePanelSlideDown 220ms ease-out\n}\n.CollapsibleContent[data-state='closed'] {\n animation: expandablePanelSlideUp 220ms ease-out;\n}\n\n@keyframes expandablePanelSlideDown {\n from {\n height: 0;\n }\n to {\n height: var(--radix-collapsible-content-height);\n }\n}\n\n@keyframes expandablePanelSlideUp {\n from {\n height: var(--radix-collapsible-content-height);\n }\n to {\n height: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .CollapsibleContent[data-state] {\n animation: none;\n }\n}`);\n\n const [open, setOpen] = useState(expanded !== undefined ? expanded : initiallyExpanded);\n const [allowOverflow, setAllowOverflow] = useState(open);\n\n useEffect(() => {\n if (open) {\n setTimeout(() => {\n setAllowOverflow(true);\n }, 220);\n } else {\n setAllowOverflow(false);\n }\n }, [open]);\n\n useEffect(() => {\n if (expanded !== undefined)\n setOpen(expanded);\n }, [expanded]);\n\n return (<>\n <Collapsible.Root\n className={cls(\n !invisible && defaultBorderMixin + \" border\",\n \"rounded-lg\",\n \"w-full\",\n className\n )}\n open={open}\n onOpenChange={(updatedOpen: boolean) => {\n onExpandedChange?.(updatedOpen);\n setOpen(updatedOpen);\n }}>\n\n <Collapsible.Trigger asChild>\n <div\n className={cls(\n \"rounded-t-lg flex items-center justify-between w-full min-h-[52px]\",\n \"hover:bg-surface-hover active:bg-surface-active\",\n invisible ? \"border-b px-2\" : \"p-4\",\n open ? \"py-6\" : \"py-4\",\n \"transition-all duration-200 motion-reduce:transition-none\",\n invisible && defaultBorderMixin,\n asField && fieldBackgroundMixin,\n titleClassName,\n \"cursor-pointer\"\n )}\n role=\"button\"\n tabIndex={0}\n aria-expanded={open}\n // A role=\"button\" has to answer Enter and Space itself:\n // Radix's trigger listens for click only, and a div does\n // not turn a key into a click, so the header took focus and\n // then ignored the keyboard. Only for keys pressed on the\n // header — a control inside the title keeps its own keys.\n onKeyDown={(event) => {\n if (event.target !== event.currentTarget) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n event.preventDefault(); // Space would scroll the page\n event.currentTarget.click();\n }}\n >\n {title}\n <ChevronDownIcon className={cls(\"transition\", open ? \"rotate-180\" : \"\")}/>\n </div>\n </Collapsible.Trigger>\n\n <Collapsible.Content\n className={cls(\"CollapsibleContent\")}\n style={{\n overflow: allowOverflow ? \"visible\" : \"hidden\"\n }}\n >\n <div className={innerClassName}>\n {children}\n </div>\n </Collapsible.Content>\n </Collapsible.Root>\n </>\n )\n}\n"],"mappings":";;;;;;;;;AAUA,SAAgB,gBAAgB,EACI,OACA,UACA,YAAY,OACZ,UACA,kBACA,oBAAoB,MACpB,gBACA,SACA,WACA,kBAWhC;CAEA,gBAAgB,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCrC;CAEE,MAAM,CAAC,MAAM,WAAW,SAAS,aAAa,KAAA,IAAY,WAAW,iBAAiB;CACtF,MAAM,CAAC,eAAe,oBAAoB,SAAS,IAAI;CAEvD,gBAAgB;EACZ,IAAI,MACA,iBAAiB;GACb,iBAAiB,IAAI;EACzB,GAAG,GAAG;OAEN,iBAAiB,KAAK;CAE9B,GAAG,CAAC,IAAI,CAAC;CAET,gBAAgB;EACZ,IAAI,aAAa,KAAA,GACb,QAAQ,QAAQ;CACxB,GAAG,CAAC,QAAQ,CAAC;CAEb,OAAQ,oBAAA,UAAA,EAAA,UACA,qBAAC,YAAY,MAAb;EACI,WAAW,IACP,CAAC,aAAA,0BACD,cACA,UACA,SACJ;EACM;EACN,eAAe,gBAAyB;GACpC,mBAAmB,WAAW;GAC9B,QAAQ,WAAW;EACvB;YAXJ,CAaI,oBAAC,YAAY,SAAb;GAAqB,SAAA;aACjB,qBAAC,OAAD;IACI,WAAW,IACP,sEACA,mDACA,YAAY,kBAAkB,OAC9B,OAAO,SAAS,QAChB,6DACA,aAAA,mBACA,WAAA,8CACA,gBACA,gBACJ;IACA,MAAK;IACL,UAAU;IACV,iBAAe;IAMf,YAAY,UAAU;KAClB,IAAI,MAAM,WAAW,MAAM,eAAe;KAC1C,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;KAChD,MAAM,eAAe;KACrB,MAAM,cAAc,MAAM;IAC9B;cAzBJ,CA2BK,OACD,oBAAC,iBAAD,EAAiB,WAAW,IAAI,cAAc,OAAO,eAAe,EAAE,EAAG,CAAA,CACxE;;EACY,CAAA,GAErB,oBAAC,YAAY,SAAb;GACI,WAAW,IAAI,oBAAoB;GACnC,OAAO,EACH,UAAU,gBAAgB,YAAY,SAC1C;aAEA,oBAAC,OAAD;IAAK,WAAW;IACX;GACA,CAAA;EACY,CAAA,CACP;IACpB,CAAA;AAEV"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { defaultBorderMixin } from "../styles.es.js";
|
|
3
2
|
import { cls } from "../util/cls.es.js";
|
|
4
3
|
import { useDebounceValue } from "../hooks/useDebounceValue.es.js";
|
|
5
4
|
import { iconSize } from "../icons/Icon.es.js";
|
|
@@ -41,7 +40,7 @@ function SearchBar({ onClick, onTextSearch, placeholder, expandable = false, siz
|
|
|
41
40
|
role: "search",
|
|
42
41
|
"aria-label": labels.search,
|
|
43
42
|
onClick,
|
|
44
|
-
className: cls("relative transition-all", heightClass, "bg-surface-field border",
|
|
43
|
+
className: cls("relative transition-all", heightClass, "bg-surface-field border border-transparent", "focus-within:ring-1 focus-within:ring-primary/60 focus-within:border-primary focus-within:shadow-[0_0_0_3px_rgba(0,112,244,0.1)]", "rounded-full overflow-hidden", className),
|
|
45
44
|
children: [
|
|
46
45
|
/* @__PURE__ */ jsx("div", {
|
|
47
46
|
className: cls("absolute p-0 h-full pointer-events-none flex items-center justify-center top-0", iconPaddingClass),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchBar.es.js","names":[],"sources":["../../src/components/SearchBar.tsx"],"sourcesContent":["\n\"use client\";\nimport { SearchIcon, XIcon } from \"lucide-react\";\nimport { iconSize } from \"../icons/Icon\";\nimport React, { useCallback, useState } from \"react\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"SearchBar.es.js","names":[],"sources":["../../src/components/SearchBar.tsx"],"sourcesContent":["\n\"use client\";\nimport { SearchIcon, XIcon } from \"lucide-react\";\nimport { iconSize } from \"../icons/Icon\";\nimport React, { useCallback, useState } from \"react\";\n\nimport { CircularProgress } from \"./CircularProgress\";\nimport { IconButton } from \"./IconButton\";\n\nimport { cls } from \"../util\";\nimport { useDebounceValue } from \"../hooks\";\nimport { useUiLabels } from \"../hooks/UiLabelsContext\";\n\ninterface SearchBarProps {\n onClick?: () => void;\n onTextSearch?: (searchString?: string) => void;\n placeholder?: string;\n expandable?: boolean;\n /**\n * Size of the search bar.\n * - \"small\": 32px height (matches TextField small)\n * - \"medium\": 44px height (matches TextField medium)\n * @default \"medium\"\n */\n size?: \"smallest\" | \"small\" | \"medium\" | \"large\";\n innerClassName?: string;\n className?: string;\n autoFocus?: boolean;\n disabled?: boolean;\n loading?: boolean;\n inputRef?: React.Ref<HTMLInputElement>;\n /**\n * Optional initial value for the search input, e.g. from URL params.\n */\n initialValue?: string;\n}\n\nexport function SearchBar({\n onClick,\n onTextSearch,\n placeholder,\n expandable = false,\n size = \"medium\",\n innerClassName,\n className,\n autoFocus,\n disabled,\n loading,\n inputRef,\n initialValue\n}: SearchBarProps) {\n\n const labels = useUiLabels();\n const placeholderText = placeholder ?? labels.search;\n\n const [searchText, setSearchText] = useState<string>(initialValue ?? \"\");\n const [active, setActive] = useState<boolean>(false);\n\n const deferredValues = useDebounceValue(searchText, 200);\n\n /**\n * Debounce on SearchIcon text update\n */\n React.useEffect(() => {\n if (!onTextSearch) return;\n if (deferredValues) {\n onTextSearch(deferredValues);\n } else {\n onTextSearch(undefined);\n }\n }, [deferredValues]);\n\n const clearText = useCallback(() => {\n if (!onTextSearch) return;\n setSearchText(\"\");\n onTextSearch(undefined);\n }, [onTextSearch]);\n\n // Height classes matching TextField sizes\n // Heights come from the shared control scale (styles.ts) so a SearchBar\n // lines up with the Button and Select beside it in a toolbar.\n const heightClass = { smallest: \"h-[28px]\", small: \"h-[32px]\", medium: \"h-[40px]\", large: \"h-[48px]\" }[size];\n const iconPaddingClass = size === \"smallest\" || size === \"small\" ? \"px-2\" : size === \"medium\" ? \"px-3\" : \"px-4\";\n const inputPaddingClass = size === \"smallest\" || size === \"small\" ? \"pl-8\" : size === \"medium\" ? \"pl-10\" : \"pl-12\";\n\n return (\n <div\n role=\"search\"\n aria-label={labels.search}\n onClick={onClick}\n className={cls(\"relative transition-all\",\n heightClass,\n // A field like any other: filled, with its border kept but\n // transparent so focus can colour it without moving anything.\n \"bg-surface-field border border-transparent\",\n \"focus-within:ring-1 focus-within:ring-primary/60 focus-within:border-primary focus-within:shadow-[0_0_0_3px_rgba(0,112,244,0.1)]\",\n \"rounded-full overflow-hidden\",\n className)}>\n <div\n className={cls(\"absolute p-0 h-full pointer-events-none flex items-center justify-center top-0\", iconPaddingClass)}>\n {loading ? <CircularProgress size={\"smallest\"}/> : <SearchIcon className={\"text-text-disabled dark:text-text-disabled-dark\"} size={size === \"smallest\" ? 14 : size === \"small\" ? 18 : 20}/>}\n </div>\n <input\n value={searchText ?? \"\"}\n ref={inputRef}\n onClick={onClick}\n placeholder={placeholderText}\n aria-label={placeholderText}\n readOnly={!onTextSearch}\n disabled={disabled}\n onChange={onTextSearch\n ? (event) => {\n setSearchText(event.target.value);\n }\n : undefined}\n autoFocus={autoFocus}\n onFocus={() => setActive(true)}\n onBlur={() => setActive(false)}\n className={cls(\n (disabled || loading) && \"pointer-events-none\",\n \"placeholder-text-disabled dark:placeholder-text-disabled-dark\",\n \"relative flex items-center transition-all bg-transparent outline-none focus:outline-none focus:ring-0 appearance-none border-none focus:border-transparent\",\n inputPaddingClass, \"h-full w-full text-current\",\n size === \"small\" ? \"text-sm\" : \"\",\n expandable ? (active ? \"w-[220px]\" : \"w-[180px]\") : \"\",\n innerClassName\n )}\n />\n {searchText\n ? <IconButton\n className={`${size === \"small\" ? \"mr-0 top-0\" : \"mr-1 top-0\"} absolute right-0 z-10`}\n size={\"small\"}\n aria-label={labels.clearSearch}\n onClick={clearText}>\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n : <div style={{ width: 26 }}/>\n }\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAqCA,SAAgB,UAAU,EACtB,SACA,cACA,aACA,aAAa,OACb,OAAO,UACP,gBACA,WACA,WACA,UACA,SACA,UACA,gBACe;CAEf,MAAM,SAAS,YAAY;CAC3B,MAAM,kBAAkB,eAAe,OAAO;CAE9C,MAAM,CAAC,YAAY,iBAAiB,SAAiB,gBAAgB,EAAE;CACvE,MAAM,CAAC,QAAQ,aAAa,SAAkB,KAAK;CAEnD,MAAM,iBAAiB,iBAAiB,YAAY,GAAG;;;;CAKvD,MAAM,gBAAgB;EAClB,IAAI,CAAC,cAAc;EACnB,IAAI,gBACA,aAAa,cAAc;OAE3B,aAAa,KAAA,CAAS;CAE9B,GAAG,CAAC,cAAc,CAAC;CAEnB,MAAM,YAAY,kBAAkB;EAChC,IAAI,CAAC,cAAc;EACnB,cAAc,EAAE;EAChB,aAAa,KAAA,CAAS;CAC1B,GAAG,CAAC,YAAY,CAAC;CAKjB,MAAM,cAAc;EAAE,UAAU;EAAY,OAAO;EAAY,QAAQ;EAAY,OAAO;CAAW,EAAE;CACvG,MAAM,mBAAmB,SAAS,cAAc,SAAS,UAAU,SAAS,SAAS,WAAW,SAAS;CACzG,MAAM,oBAAoB,SAAS,cAAc,SAAS,UAAU,SAAS,SAAS,WAAW,UAAU;CAE3G,OACI,qBAAC,OAAD;EACI,MAAK;EACL,cAAY,OAAO;EACV;EACT,WAAW,IAAI,2BACX,aAGA,8CACA,oIACA,gCACA,SAAS;YAXjB;GAYI,oBAAC,OAAD;IACI,WAAW,IAAI,kFAAkF,gBAAgB;cAChH,UAAU,oBAAC,kBAAD,EAAkB,MAAM,WAAY,CAAA,IAAI,oBAAC,YAAD;KAAY,WAAW;KAAmD,MAAM,SAAS,aAAa,KAAK,SAAS,UAAU,KAAK;IAAI,CAAA;GACzL,CAAA;GACL,oBAAC,SAAD;IACI,OAAO,cAAc;IACrB,KAAK;IACI;IACT,aAAa;IACb,cAAY;IACZ,UAAU,CAAC;IACD;IACV,UAAU,gBACH,UAAU;KACT,cAAc,MAAM,OAAO,KAAK;IACpC,IACE,KAAA;IACK;IACX,eAAe,UAAU,IAAI;IAC7B,cAAc,UAAU,KAAK;IAC7B,WAAW,KACN,YAAY,YAAY,uBACzB,iEACA,8JACA,mBAAmB,8BACnB,SAAS,UAAU,YAAY,IAC/B,aAAc,SAAS,cAAc,cAAe,IACpD,cACJ;GACH,CAAA;GACA,aACK,oBAAC,YAAD;IACE,WAAW,GAAG,SAAS,UAAU,eAAe,aAAa;IAC7D,MAAM;IACN,cAAY,OAAO;IACnB,SAAS;cACT,oBAAC,OAAD,EAAO,MAAM,SAAS,SAAU,CAAA;GACxB,CAAA,IACV,oBAAC,OAAD,EAAK,OAAO,EAAE,OAAO,GAAG,EAAG,CAAA;EAEhC;;AAEb"}
|
|
@@ -199,7 +199,7 @@ var Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenCh
|
|
|
199
199
|
children: label
|
|
200
200
|
}) : label,
|
|
201
201
|
/* @__PURE__ */ jsxs("div", {
|
|
202
|
-
className: cls("select-none", !bare && "rounded-lg text-sm", !bare && (invisible ? "bg-transparent" : "bg-surface-field border border-
|
|
202
|
+
className: cls("select-none", !bare && "rounded-lg text-sm", !bare && (invisible ? "bg-transparent" : "bg-surface-field border border-transparent"), !bare && (disabled ? "bg-surface-field/60" : invisible ? "" : "transition-colors duration-150 hover:bg-surface-field-hover"), "relative flex items-center", className, !bare && controlHeight, {
|
|
203
203
|
"w-fit": !fullWidth,
|
|
204
204
|
"w-full": fullWidth
|
|
205
205
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.es.js","names":[],"sources":["../../src/components/Select.tsx"],"sourcesContent":["\n\"use client\";\nimport { CheckIcon, ChevronDownIcon } from \"lucide-react\";\nimport { iconSize } from \"../icons/Icon\";\nimport React, { ChangeEvent, Children, forwardRef, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport {\n defaultBorderMixin,\n fieldBackgroundDisabledMixin,\n fieldBackgroundHoverMixin,\n fieldBackgroundInvisibleMixin,\n fieldBackgroundMixin,\n focusedDisabled\n} from \"../styles\";\n\nimport { cls } from \"../util\";\nimport { SelectInputLabel } from \"./common/SelectInputLabel\";\nimport { usePortalContainer } from \"../hooks/PortalContainerContext\";\n\nexport type SelectValue = string | number | boolean;\n\n/**\n * Above this many options a closed select renders only the one it shows.\n *\n * Radix renders a closed select's items anyway — into a detached fragment, so\n * that a letter typed on the focused trigger can pick one, and inside a form\n * into the hidden native `<select>` as well. An enum field paid for its whole\n * list on every render of the form around it: 500 options were 100 ms to mount\n * and 17 ms per re-render in jsdom, linear in the count. Small lists keep\n * Radix's own behaviour untouched; long ones answer typed letters here\n * ({@link findNextOption}) and render their items when opened.\n */\nconst LAZY_OPTIONS_THRESHOLD = 50;\n\n/** How long typed letters accumulate into one search — Radix's own window. */\nconst TYPEAHEAD_RESET_MS = 1000;\n\ntype SelectOption = {\n element: React.ReactElement<SelectItemProps<SelectValue>>,\n value: string,\n disabled: boolean\n};\n\n/**\n * The `SelectItem`s among `children`, in document order: through fragments,\n * `SelectGroup`s and any other element wrapping them. An item rendered by a\n * component of the caller's own is not seen, so such a list never counts as\n * long and keeps the default behaviour.\n */\nfunction collectOptions(children: React.ReactNode, into: SelectOption[] = []): SelectOption[] {\n Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return;\n if (child.type === SelectItem) {\n const props = child.props as SelectItemProps<SelectValue>;\n into.push({\n element: child as React.ReactElement<SelectItemProps<SelectValue>>,\n value: String(props.value),\n disabled: Boolean(props.disabled)\n });\n return;\n }\n const nested = (child.props as { children?: React.ReactNode }).children;\n if (nested !== undefined) collectOptions(nested, into);\n });\n return into;\n}\n\n/** The text a node would render, as far as its elements' own children say. */\nfunction textOf(node: React.ReactNode): string {\n if (node === null || node === undefined || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\" || typeof node === \"number\") return String(node);\n if (Array.isArray(node)) return node.map(textOf).join(\"\");\n if (React.isValidElement(node)) return textOf((node.props as { children?: React.ReactNode }).children);\n return \"\";\n}\n\n/**\n * What a typed letter matches an option against: its `textValue`, else the\n * text of its children, else its value. Radix reads the rendered text, which\n * an unrendered item does not have — an item whose label is drawn by a\n * component (a chip) passes `textValue`.\n */\nfunction optionLabel(option: SelectOption): string {\n const { textValue, children } = option.element.props;\n if (textValue !== undefined) return textValue;\n return textOf(children).trim() || option.value;\n}\n\n/**\n * Radix's `findNextItem`, over options that are not rendered: the next option\n * after the current one whose label starts with the search; the same letter\n * typed again cycles through the options it starts.\n */\nfunction findNextOption(options: SelectOption[], search: string, current: SelectOption | undefined): SelectOption | undefined {\n if (options.length === 0) return undefined;\n const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);\n const normalizedSearch = (isRepeated ? search[0] : search).toLowerCase();\n const start = Math.max(current ? options.indexOf(current) : -1, 0);\n let wrapped = options.map((_, index) => options[(start + index) % options.length]);\n if (normalizedSearch.length === 1) wrapped = wrapped.filter((option) => option !== current);\n const next = wrapped.find((option) => optionLabel(option).toLowerCase().startsWith(normalizedSearch));\n return next !== current ? next : undefined;\n}\n\nexport type SelectProps<T extends SelectValue = string> = {\n open?: boolean,\n name?: string,\n fullWidth?: boolean,\n id?: string,\n onOpenChange?: (open: boolean) => void,\n value?: T,\n className?: string,\n inputClassName?: string,\n viewportClassName?: string,\n onChange?: React.EventHandler<ChangeEvent<HTMLSelectElement>>,\n onValueChange?: (updatedValue: T) => void,\n placeholder?: React.ReactNode,\n renderValue?: (value: T) => React.ReactNode,\n size?: \"smallest\" | \"small\" | \"medium\" | \"large\",\n label?: React.ReactNode | string,\n disabled?: boolean,\n error?: boolean,\n position?: \"item-aligned\" | \"popper\",\n endAdornment?: React.ReactNode,\n inputRef?: React.Ref<HTMLButtonElement>,\n padding?: boolean,\n invisible?: boolean,\n /**\n * Whether the trigger draws its own chevron. A table cell turns it off:\n * the cell draws one opener for every kind of editor, in the same place.\n */\n chevron?: boolean,\n /**\n * Replaces everything the trigger draws — the value, the box, its padding,\n * its height and the chevron — with this node. For a caller that frames\n * the select itself: a table cell passes the preview it shows at rest, so\n * selecting the cell moves nothing.\n */\n triggerContent?: React.ReactNode,\n children?: React.ReactNode;\n dataType?: \"string\" | \"number\" | \"boolean\";\n portalContainer?: HTMLElement | null; // Explicitly added to props type if missing\n /**\n * Accessible name for the trigger, for a select whose `label` is a node\n * rather than a string — or which shows no label at all.\n *\n * Without it such a select announces the literal fallback \"Select an\n * option\", which is indistinguishable from every other unlabelled select\n * on the screen.\n */\n \"aria-label\"?: string;\n};\n\nexport const Select = forwardRef<HTMLDivElement, SelectProps>(({\n inputRef,\n open,\n name,\n fullWidth = false,\n id,\n onOpenChange,\n value,\n onChange,\n onValueChange,\n className,\n inputClassName,\n viewportClassName,\n placeholder,\n renderValue,\n label,\n size = \"large\",\n error,\n disabled,\n padding = true,\n position = \"item-aligned\",\n endAdornment,\n invisible,\n chevron = true,\n triggerContent,\n children,\n dataType = \"string\",\n portalContainer: manualContainer, // Rename to avoid confusion\n \"aria-label\": ariaLabel,\n ...props\n}, ref) => {\n\n const [openInternal, setOpenInternal] = useState(open ?? false);\n\n useEffect(() => {\n setOpenInternal(open ?? false);\n }, [open]);\n\n // Get the portal container from context\n const contextContainer = usePortalContainer();\n\n // Resolve final container (Manual Prop > Context Container > Undefined/Body)\n const finalContainer = (manualContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;\n\n const onValueChangeInternal = useCallback((newValue: string) => {\n let typedValue: SelectValue = newValue;\n if (dataType === \"boolean\") {\n if (newValue === \"true\") typedValue = true;\n else if (newValue === \"false\") typedValue = false;\n } else if (dataType === \"number\") {\n if (!isNaN(Number(newValue)) && newValue.trim() !== \"\") typedValue = Number(newValue);\n }\n\n onValueChange?.(typedValue as SelectValue & typeof value);\n if (onChange) {\n const event = {\n target: {\n name,\n value: typedValue\n }\n } as ChangeEvent<HTMLSelectElement>;\n onChange(event);\n }\n }, [onChange, onValueChange, name, dataType]);\n\n const hasValue = Array.isArray(value) ? value.length > 0 : (value != null && value !== \"\" && value !== undefined);\n const stringValue = value !== undefined ? String(value) : undefined;\n\n // A long list renders its items only while open; closed, it renders the\n // one it shows, which keeps that option in the native `<select>` a form\n // submits. See LAZY_OPTIONS_THRESHOLD.\n const options = useMemo(() => collectOptions(children), [children]);\n const lazyOptions = options.length > LAZY_OPTIONS_THRESHOLD;\n const renderedOptions = !lazyOptions || openInternal\n ? children\n : (options.find((option) => option.value === stringValue)?.element ?? null);\n\n // Type-ahead on the closed trigger of a long list, where Radix has no\n // rendered items to search. Radix's own handler still runs after this one\n // and finds nothing to change; it keeps its own buffer in step, so a space\n // typed mid-search does not open the list, as before.\n const typeahead = useRef<{ search: string, timer?: ReturnType<typeof setTimeout> }>({ search: \"\" });\n useEffect(() => () => clearTimeout(typeahead.current.timer), []);\n const resetTypeahead = useCallback(() => {\n clearTimeout(typeahead.current.timer);\n typeahead.current.search = \"\";\n }, []);\n const onTriggerKeyDown = useCallback((event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (!lazyOptions || disabled) return;\n if (event.ctrlKey || event.altKey || event.metaKey || event.key.length !== 1) return;\n const state = typeahead.current;\n // A space starts nothing: it opens the list.\n if (event.key === \" \" && state.search === \"\") return;\n const search = state.search + event.key;\n state.search = search;\n clearTimeout(state.timer);\n state.timer = setTimeout(() => {\n state.search = \"\";\n }, TYPEAHEAD_RESET_MS);\n const enabled = options.filter((option) => !option.disabled);\n const current = enabled.find((option) => option.value === stringValue);\n const next = findNextOption(enabled, search, current);\n if (next) onValueChangeInternal(next.value);\n }, [lazyOptions, disabled, options, stringValue, onValueChangeInternal]);\n\n // Inside a form Radix mirrors the value into a hidden native `<select>` and\n // reports whatever that select then holds. A closed long list has only its\n // shown option in there, so when the value moves to another option — typed,\n // or set by the form — the select has no match for one commit and reports\n // \"\", a change nobody made, which would clear the field. A real \"\" can only\n // come from an option whose value is \"\".\n const hasEmptyOption = useMemo(() => options.some((option) => option.value === \"\"), [options]);\n const onRootValueChange = useCallback((newValue: string) => {\n if (lazyOptions && newValue === \"\" && !hasEmptyOption) return;\n onValueChangeInternal(newValue);\n }, [lazyOptions, hasEmptyOption, onValueChangeInternal]);\n\n const displayChildren = useMemo(() => {\n if (!hasValue || renderValue) return null;\n\n // Find the child that matches the current value to display its content\n let found: React.ReactNode = null;\n Children.forEach(children, (child) => {\n if (React.isValidElement<SelectItemProps>(child) && String(child.props.value) === String(value)) {\n found = child.props.children;\n }\n });\n return found;\n }, [children, hasValue, renderValue, value]);\n\n // Radix reads a value of \"\" as nothing selected and shows the placeholder\n // slot, whatever the Value's children say. So an item whose value really is\n // \"\" (an \"inherit the default\" choice) is shown through that slot, or the\n // trigger stays blank while it is the selected one.\n const emptyItemDisplay = useMemo(() => {\n if (stringValue !== \"\") return null;\n const emptyItem = Children.toArray(children).find((child): child is React.ReactElement<SelectItemProps> =>\n React.isValidElement<SelectItemProps>(child) && String(child.props.value) === \"\");\n if (!emptyItem) return null;\n return value !== undefined && renderValue ? renderValue(value) : emptyItem.props.children;\n }, [children, renderValue, stringValue, value]);\n\n const bare = triggerContent !== undefined;\n // Radix's value slot is an inline `span` that drops the class it is\n // given, so a chip inside it sits on a text baseline and the slot stands\n // a pixel taller than the chip. A block of its own takes it off the line.\n const bareContent = bare ? <div className={\"flex w-full\"}>{triggerContent}</div> : undefined;\n\n const controlHeight = {\n \"min-h-[28px]\": size === \"smallest\",\n \"min-h-[32px]\": size === \"small\",\n \"min-h-[40px]\": size === \"medium\",\n \"min-h-[48px]\": size === \"large\"\n };\n\n return (\n <SelectPrimitive.Root\n name={name}\n value={stringValue}\n open={openInternal}\n disabled={disabled}\n onValueChange={onRootValueChange}\n onOpenChange={(open) => {\n if (open) resetTypeahead();\n onOpenChange?.(open);\n setOpenInternal(open);\n }}\n {...props}>\n {typeof label === \"string\" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}\n <div className={cls(\n \"select-none\",\n !bare && \"rounded-lg text-sm\",\n !bare && (invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin),\n // An invisible field has no hover fill of its own: whatever frames it (a table cell) carries the hover.\n !bare && (disabled ? fieldBackgroundDisabledMixin : (invisible ? \"\" : fieldBackgroundHoverMixin)),\n \"relative flex items-center\",\n className,\n !bare && controlHeight,\n {\n \"w-fit\": !fullWidth,\n \"w-full\": fullWidth\n }\n )}>\n <SelectPrimitive.Trigger\n ref={inputRef}\n id={id}\n asChild={false}\n type=\"button\"\n onKeyDown={onTriggerKeyDown}\n // A `label` that is an element — which every entity form\n // passes, because the label carries the property's type\n // icon — fell through to a hard-coded English literal, so\n // every enum field in every form announced itself as\n // \"Select an option\". Callers pass `aria-label` now; the\n // literal stays as the last resort, and is unreachable for\n // anything that names itself.\n aria-label={ariaLabel ?? (typeof label === \"string\" ? label : (typeof renderValue === \"string\" ? renderValue : \"Select an option\"))}\n aria-invalid={error || undefined}\n className={cls(\n \"h-full\",\n padding && !bare ? {\n \"px-4\": size === \"large\",\n \"px-3\": size === \"medium\",\n \"px-2\": size === \"small\" || size === \"smallest\"\n } : \"\",\n \"outline-hidden focus:outline-hidden\",\n \"outline-none focus:outline-none\",\n \"select-none\",\n !bare && [\n \"rounded-lg text-sm\",\n error ? \"text-red-500 dark:text-red-600\" : \"focus:text-text-primary dark:focus:text-text-primary-dark\",\n error ? \"border border-red-500 dark:border-red-600\" : \"\",\n disabled ? \"text-surface-accent-600 dark:text-surface-accent-400\" : \"text-surface-accent-800 dark:text-white\",\n controlHeight\n ],\n \"relative flex flex-row items-center\",\n {\n \"w-full\": fullWidth,\n \"w-fit\": !fullWidth\n },\n inputClassName\n )}\n >\n <div\n ref={ref}\n className={cls(\n \"flex-grow max-w-full flex flex-row gap-2 items-center\",\n \"overflow-visible\",\n !bare && controlHeight\n )}>\n <SelectPrimitive.Value\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n placeholder={bareContent ?? emptyItemDisplay ?? placeholder}\n className={\"w-full\"}>\n\n {bareContent ?? (hasValue && value !== undefined && renderValue\n ? renderValue(value)\n : (displayChildren || placeholder)\n )}\n\n </SelectPrimitive.Value>\n </div>\n\n {/* Spaced with a margin, never padding: under border-box,\n padding on the svg comes out of its width and draws the\n chevron smaller and fainter than the relation field's. */}\n {chevron && !bare && <SelectPrimitive.Icon asChild>\n <ChevronDownIcon size={size === \"large\" ? iconSize.medium : iconSize.small}\n className={cls(\"shrink-0 transition\", open ? \"rotate-180\" : \"\", {\n \"ml-2\": size === \"large\",\n \"ml-1\": size === \"medium\" || size === \"small\"\n })}/>\n </SelectPrimitive.Icon>}\n </SelectPrimitive.Trigger>\n\n {endAdornment && (\n <div\n className={cls(\"h-full flex items-center absolute right-0 pr-12\")}\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}>\n {endAdornment}\n </div>\n )}\n </div>\n\n {/* Pass the calculated finalContainer */}\n <SelectPrimitive.Portal container={finalContainer}>\n <SelectPrimitive.Content position={position}\n className={cls(focusedDisabled, \"z-50 relative overflow-hidden border bg-surface-card p-2 rounded-lg shadow-lg\", defaultBorderMixin)}>\n <SelectPrimitive.Viewport className={cls(\"p-1\", viewportClassName)}\n style={{ maxHeight: \"var(--radix-select-content-available-height)\" }}>\n {renderedOptions}\n </SelectPrimitive.Viewport>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n );\n});\n\nSelect.displayName = \"Select\";\n\nexport type SelectItemProps<T extends SelectValue = string> = {\n value: T,\n children?: React.ReactNode,\n disabled?: boolean,\n className?: string,\n /**\n * The text a letter typed on the focused select matches this option by.\n * Defaults to the option's text. Set it when the option is drawn by a\n * component (a chip, an icon with a name inside it): a closed select with\n * more than 50 options does not render them, so their text cannot be read.\n */\n textValue?: string,\n};\n\nexport const SelectItem = React.memo(function SelectItem<T extends SelectValue = string>({\n value,\n children,\n disabled,\n className,\n textValue\n}: SelectItemProps<T>) {\n // Convert value to string for Radix UI\n const stringValue = String(value);\n\n return <SelectPrimitive.Item\n key={stringValue}\n value={stringValue}\n disabled={disabled}\n textValue={textValue}\n className={cls(\n \"w-full\",\n \"relative flex items-center p-2 rounded-md text-sm text-surface-accent-700 dark:text-surface-accent-300\",\n \"focus:z-10\",\n \"data-[state=checked]:bg-surface-active focus:bg-surface-hover\",\n \"data-[state=checked]:focus:bg-surface-active\",\n disabled ? \"opacity-50 cursor-not-allowed\" : \"cursor-pointer\",\n \"[&>*]:w-full\",\n \"overflow-visible\",\n className\n )}>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n <div\n className=\"absolute left-1 data-[state=checked]:block hidden\">\n <CheckIcon size={iconSize.smallest}/>\n </div>\n </SelectPrimitive.Item>;\n});\n\nexport type SelectGroupProps = {\n label: React.ReactNode,\n children: React.ReactNode,\n className?: string\n};\n\nexport const SelectGroup = React.memo(function SelectGroup({\n label,\n children,\n className\n}: SelectGroupProps) {\n // The items go inside the group and the label is Radix's own, so the\n // `role=\"group\"` holds its options and is named by the label.\n return <SelectPrimitive.Group className=\"mt-6 first:mt-2\">\n <SelectPrimitive.Label\n className={cls(\n \"text-xs text-surface-accent-900 dark:text-white uppercase tracking-wider font-semibold\",\n \"px-2 py-2\",\n className\n )}>\n {label}\n </SelectPrimitive.Label>\n {children}\n </SelectPrimitive.Group>;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Select.es.js","names":[],"sources":["../../src/components/Select.tsx"],"sourcesContent":["\n\"use client\";\nimport { CheckIcon, ChevronDownIcon } from \"lucide-react\";\nimport { iconSize } from \"../icons/Icon\";\nimport React, { ChangeEvent, Children, forwardRef, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport {\n defaultBorderMixin,\n fieldBackgroundDisabledMixin,\n fieldBackgroundHoverMixin,\n fieldBackgroundInvisibleMixin,\n fieldBackgroundMixin,\n focusedDisabled\n} from \"../styles\";\n\nimport { cls } from \"../util\";\nimport { SelectInputLabel } from \"./common/SelectInputLabel\";\nimport { usePortalContainer } from \"../hooks/PortalContainerContext\";\n\nexport type SelectValue = string | number | boolean;\n\n/**\n * Above this many options a closed select renders only the one it shows.\n *\n * Radix renders a closed select's items anyway — into a detached fragment, so\n * that a letter typed on the focused trigger can pick one, and inside a form\n * into the hidden native `<select>` as well. An enum field paid for its whole\n * list on every render of the form around it: 500 options were 100 ms to mount\n * and 17 ms per re-render in jsdom, linear in the count. Small lists keep\n * Radix's own behaviour untouched; long ones answer typed letters here\n * ({@link findNextOption}) and render their items when opened.\n */\nconst LAZY_OPTIONS_THRESHOLD = 50;\n\n/** How long typed letters accumulate into one search — Radix's own window. */\nconst TYPEAHEAD_RESET_MS = 1000;\n\ntype SelectOption = {\n element: React.ReactElement<SelectItemProps<SelectValue>>,\n value: string,\n disabled: boolean\n};\n\n/**\n * The `SelectItem`s among `children`, in document order: through fragments,\n * `SelectGroup`s and any other element wrapping them. An item rendered by a\n * component of the caller's own is not seen, so such a list never counts as\n * long and keeps the default behaviour.\n */\nfunction collectOptions(children: React.ReactNode, into: SelectOption[] = []): SelectOption[] {\n Children.forEach(children, (child) => {\n if (!React.isValidElement(child)) return;\n if (child.type === SelectItem) {\n const props = child.props as SelectItemProps<SelectValue>;\n into.push({\n element: child as React.ReactElement<SelectItemProps<SelectValue>>,\n value: String(props.value),\n disabled: Boolean(props.disabled)\n });\n return;\n }\n const nested = (child.props as { children?: React.ReactNode }).children;\n if (nested !== undefined) collectOptions(nested, into);\n });\n return into;\n}\n\n/** The text a node would render, as far as its elements' own children say. */\nfunction textOf(node: React.ReactNode): string {\n if (node === null || node === undefined || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\" || typeof node === \"number\") return String(node);\n if (Array.isArray(node)) return node.map(textOf).join(\"\");\n if (React.isValidElement(node)) return textOf((node.props as { children?: React.ReactNode }).children);\n return \"\";\n}\n\n/**\n * What a typed letter matches an option against: its `textValue`, else the\n * text of its children, else its value. Radix reads the rendered text, which\n * an unrendered item does not have — an item whose label is drawn by a\n * component (a chip) passes `textValue`.\n */\nfunction optionLabel(option: SelectOption): string {\n const { textValue, children } = option.element.props;\n if (textValue !== undefined) return textValue;\n return textOf(children).trim() || option.value;\n}\n\n/**\n * Radix's `findNextItem`, over options that are not rendered: the next option\n * after the current one whose label starts with the search; the same letter\n * typed again cycles through the options it starts.\n */\nfunction findNextOption(options: SelectOption[], search: string, current: SelectOption | undefined): SelectOption | undefined {\n if (options.length === 0) return undefined;\n const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);\n const normalizedSearch = (isRepeated ? search[0] : search).toLowerCase();\n const start = Math.max(current ? options.indexOf(current) : -1, 0);\n let wrapped = options.map((_, index) => options[(start + index) % options.length]);\n if (normalizedSearch.length === 1) wrapped = wrapped.filter((option) => option !== current);\n const next = wrapped.find((option) => optionLabel(option).toLowerCase().startsWith(normalizedSearch));\n return next !== current ? next : undefined;\n}\n\nexport type SelectProps<T extends SelectValue = string> = {\n open?: boolean,\n name?: string,\n fullWidth?: boolean,\n id?: string,\n onOpenChange?: (open: boolean) => void,\n value?: T,\n className?: string,\n inputClassName?: string,\n viewportClassName?: string,\n onChange?: React.EventHandler<ChangeEvent<HTMLSelectElement>>,\n onValueChange?: (updatedValue: T) => void,\n placeholder?: React.ReactNode,\n renderValue?: (value: T) => React.ReactNode,\n size?: \"smallest\" | \"small\" | \"medium\" | \"large\",\n label?: React.ReactNode | string,\n disabled?: boolean,\n error?: boolean,\n position?: \"item-aligned\" | \"popper\",\n endAdornment?: React.ReactNode,\n inputRef?: React.Ref<HTMLButtonElement>,\n padding?: boolean,\n invisible?: boolean,\n /**\n * Whether the trigger draws its own chevron. A table cell turns it off:\n * the cell draws one opener for every kind of editor, in the same place.\n */\n chevron?: boolean,\n /**\n * Replaces everything the trigger draws — the value, the box, its padding,\n * its height and the chevron — with this node. For a caller that frames\n * the select itself: a table cell passes the preview it shows at rest, so\n * selecting the cell moves nothing.\n */\n triggerContent?: React.ReactNode,\n children?: React.ReactNode;\n dataType?: \"string\" | \"number\" | \"boolean\";\n portalContainer?: HTMLElement | null; // Explicitly added to props type if missing\n /**\n * Accessible name for the trigger, for a select whose `label` is a node\n * rather than a string — or which shows no label at all.\n *\n * Without it such a select announces the literal fallback \"Select an\n * option\", which is indistinguishable from every other unlabelled select\n * on the screen.\n */\n \"aria-label\"?: string;\n};\n\nexport const Select = forwardRef<HTMLDivElement, SelectProps>(({\n inputRef,\n open,\n name,\n fullWidth = false,\n id,\n onOpenChange,\n value,\n onChange,\n onValueChange,\n className,\n inputClassName,\n viewportClassName,\n placeholder,\n renderValue,\n label,\n size = \"large\",\n error,\n disabled,\n padding = true,\n position = \"item-aligned\",\n endAdornment,\n invisible,\n chevron = true,\n triggerContent,\n children,\n dataType = \"string\",\n portalContainer: manualContainer, // Rename to avoid confusion\n \"aria-label\": ariaLabel,\n ...props\n}, ref) => {\n\n const [openInternal, setOpenInternal] = useState(open ?? false);\n\n useEffect(() => {\n setOpenInternal(open ?? false);\n }, [open]);\n\n // Get the portal container from context\n const contextContainer = usePortalContainer();\n\n // Resolve final container (Manual Prop > Context Container > Undefined/Body)\n const finalContainer = (manualContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;\n\n const onValueChangeInternal = useCallback((newValue: string) => {\n let typedValue: SelectValue = newValue;\n if (dataType === \"boolean\") {\n if (newValue === \"true\") typedValue = true;\n else if (newValue === \"false\") typedValue = false;\n } else if (dataType === \"number\") {\n if (!isNaN(Number(newValue)) && newValue.trim() !== \"\") typedValue = Number(newValue);\n }\n\n onValueChange?.(typedValue as SelectValue & typeof value);\n if (onChange) {\n const event = {\n target: {\n name,\n value: typedValue\n }\n } as ChangeEvent<HTMLSelectElement>;\n onChange(event);\n }\n }, [onChange, onValueChange, name, dataType]);\n\n const hasValue = Array.isArray(value) ? value.length > 0 : (value != null && value !== \"\" && value !== undefined);\n const stringValue = value !== undefined ? String(value) : undefined;\n\n // A long list renders its items only while open; closed, it renders the\n // one it shows, which keeps that option in the native `<select>` a form\n // submits. See LAZY_OPTIONS_THRESHOLD.\n const options = useMemo(() => collectOptions(children), [children]);\n const lazyOptions = options.length > LAZY_OPTIONS_THRESHOLD;\n const renderedOptions = !lazyOptions || openInternal\n ? children\n : (options.find((option) => option.value === stringValue)?.element ?? null);\n\n // Type-ahead on the closed trigger of a long list, where Radix has no\n // rendered items to search. Radix's own handler still runs after this one\n // and finds nothing to change; it keeps its own buffer in step, so a space\n // typed mid-search does not open the list, as before.\n const typeahead = useRef<{ search: string, timer?: ReturnType<typeof setTimeout> }>({ search: \"\" });\n useEffect(() => () => clearTimeout(typeahead.current.timer), []);\n const resetTypeahead = useCallback(() => {\n clearTimeout(typeahead.current.timer);\n typeahead.current.search = \"\";\n }, []);\n const onTriggerKeyDown = useCallback((event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (!lazyOptions || disabled) return;\n if (event.ctrlKey || event.altKey || event.metaKey || event.key.length !== 1) return;\n const state = typeahead.current;\n // A space starts nothing: it opens the list.\n if (event.key === \" \" && state.search === \"\") return;\n const search = state.search + event.key;\n state.search = search;\n clearTimeout(state.timer);\n state.timer = setTimeout(() => {\n state.search = \"\";\n }, TYPEAHEAD_RESET_MS);\n const enabled = options.filter((option) => !option.disabled);\n const current = enabled.find((option) => option.value === stringValue);\n const next = findNextOption(enabled, search, current);\n if (next) onValueChangeInternal(next.value);\n }, [lazyOptions, disabled, options, stringValue, onValueChangeInternal]);\n\n // Inside a form Radix mirrors the value into a hidden native `<select>` and\n // reports whatever that select then holds. A closed long list has only its\n // shown option in there, so when the value moves to another option — typed,\n // or set by the form — the select has no match for one commit and reports\n // \"\", a change nobody made, which would clear the field. A real \"\" can only\n // come from an option whose value is \"\".\n const hasEmptyOption = useMemo(() => options.some((option) => option.value === \"\"), [options]);\n const onRootValueChange = useCallback((newValue: string) => {\n if (lazyOptions && newValue === \"\" && !hasEmptyOption) return;\n onValueChangeInternal(newValue);\n }, [lazyOptions, hasEmptyOption, onValueChangeInternal]);\n\n const displayChildren = useMemo(() => {\n if (!hasValue || renderValue) return null;\n\n // Find the child that matches the current value to display its content\n let found: React.ReactNode = null;\n Children.forEach(children, (child) => {\n if (React.isValidElement<SelectItemProps>(child) && String(child.props.value) === String(value)) {\n found = child.props.children;\n }\n });\n return found;\n }, [children, hasValue, renderValue, value]);\n\n // Radix reads a value of \"\" as nothing selected and shows the placeholder\n // slot, whatever the Value's children say. So an item whose value really is\n // \"\" (an \"inherit the default\" choice) is shown through that slot, or the\n // trigger stays blank while it is the selected one.\n const emptyItemDisplay = useMemo(() => {\n if (stringValue !== \"\") return null;\n const emptyItem = Children.toArray(children).find((child): child is React.ReactElement<SelectItemProps> =>\n React.isValidElement<SelectItemProps>(child) && String(child.props.value) === \"\");\n if (!emptyItem) return null;\n return value !== undefined && renderValue ? renderValue(value) : emptyItem.props.children;\n }, [children, renderValue, stringValue, value]);\n\n const bare = triggerContent !== undefined;\n // Radix's value slot is an inline `span` that drops the class it is\n // given, so a chip inside it sits on a text baseline and the slot stands\n // a pixel taller than the chip. A block of its own takes it off the line.\n const bareContent = bare ? <div className={\"flex w-full\"}>{triggerContent}</div> : undefined;\n\n const controlHeight = {\n \"min-h-[28px]\": size === \"smallest\",\n \"min-h-[32px]\": size === \"small\",\n \"min-h-[40px]\": size === \"medium\",\n \"min-h-[48px]\": size === \"large\"\n };\n\n return (\n <SelectPrimitive.Root\n name={name}\n value={stringValue}\n open={openInternal}\n disabled={disabled}\n onValueChange={onRootValueChange}\n onOpenChange={(open) => {\n if (open) resetTypeahead();\n onOpenChange?.(open);\n setOpenInternal(open);\n }}\n {...props}>\n {typeof label === \"string\" ? <SelectInputLabel error={error}>{label}</SelectInputLabel> : label}\n <div className={cls(\n \"select-none\",\n !bare && \"rounded-lg text-sm\",\n !bare && (invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin),\n // An invisible field has no hover fill of its own: whatever frames it (a table cell) carries the hover.\n !bare && (disabled ? fieldBackgroundDisabledMixin : (invisible ? \"\" : fieldBackgroundHoverMixin)),\n \"relative flex items-center\",\n className,\n !bare && controlHeight,\n {\n \"w-fit\": !fullWidth,\n \"w-full\": fullWidth\n }\n )}>\n <SelectPrimitive.Trigger\n ref={inputRef}\n id={id}\n asChild={false}\n type=\"button\"\n onKeyDown={onTriggerKeyDown}\n // A `label` that is an element — which every entity form\n // passes, because the label carries the property's type\n // icon — fell through to a hard-coded English literal, so\n // every enum field in every form announced itself as\n // \"Select an option\". Callers pass `aria-label` now; the\n // literal stays as the last resort, and is unreachable for\n // anything that names itself.\n aria-label={ariaLabel ?? (typeof label === \"string\" ? label : (typeof renderValue === \"string\" ? renderValue : \"Select an option\"))}\n aria-invalid={error || undefined}\n className={cls(\n \"h-full\",\n padding && !bare ? {\n \"px-4\": size === \"large\",\n \"px-3\": size === \"medium\",\n \"px-2\": size === \"small\" || size === \"smallest\"\n } : \"\",\n \"outline-hidden focus:outline-hidden\",\n \"outline-none focus:outline-none\",\n \"select-none\",\n !bare && [\n \"rounded-lg text-sm\",\n error ? \"text-red-500 dark:text-red-600\" : \"focus:text-text-primary dark:focus:text-text-primary-dark\",\n error ? \"border border-red-500 dark:border-red-600\" : \"\",\n disabled ? \"text-surface-accent-600 dark:text-surface-accent-400\" : \"text-surface-accent-800 dark:text-white\",\n controlHeight\n ],\n \"relative flex flex-row items-center\",\n {\n \"w-full\": fullWidth,\n \"w-fit\": !fullWidth\n },\n inputClassName\n )}\n >\n <div\n ref={ref}\n className={cls(\n \"flex-grow max-w-full flex flex-row gap-2 items-center\",\n \"overflow-visible\",\n !bare && controlHeight\n )}>\n <SelectPrimitive.Value\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n placeholder={bareContent ?? emptyItemDisplay ?? placeholder}\n className={\"w-full\"}>\n\n {bareContent ?? (hasValue && value !== undefined && renderValue\n ? renderValue(value)\n : (displayChildren || placeholder)\n )}\n\n </SelectPrimitive.Value>\n </div>\n\n {/* Spaced with a margin, never padding: under border-box,\n padding on the svg comes out of its width and draws the\n chevron smaller and fainter than the relation field's. */}\n {chevron && !bare && <SelectPrimitive.Icon asChild>\n <ChevronDownIcon size={size === \"large\" ? iconSize.medium : iconSize.small}\n className={cls(\"shrink-0 transition\", open ? \"rotate-180\" : \"\", {\n \"ml-2\": size === \"large\",\n \"ml-1\": size === \"medium\" || size === \"small\"\n })}/>\n </SelectPrimitive.Icon>}\n </SelectPrimitive.Trigger>\n\n {endAdornment && (\n <div\n className={cls(\"h-full flex items-center absolute right-0 pr-12\")}\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}>\n {endAdornment}\n </div>\n )}\n </div>\n\n {/* Pass the calculated finalContainer */}\n <SelectPrimitive.Portal container={finalContainer}>\n <SelectPrimitive.Content position={position}\n className={cls(focusedDisabled, \"z-50 relative overflow-hidden border bg-surface-card p-2 rounded-lg shadow-lg\", defaultBorderMixin)}>\n <SelectPrimitive.Viewport className={cls(\"p-1\", viewportClassName)}\n style={{ maxHeight: \"var(--radix-select-content-available-height)\" }}>\n {renderedOptions}\n </SelectPrimitive.Viewport>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n );\n});\n\nSelect.displayName = \"Select\";\n\nexport type SelectItemProps<T extends SelectValue = string> = {\n value: T,\n children?: React.ReactNode,\n disabled?: boolean,\n className?: string,\n /**\n * The text a letter typed on the focused select matches this option by.\n * Defaults to the option's text. Set it when the option is drawn by a\n * component (a chip, an icon with a name inside it): a closed select with\n * more than 50 options does not render them, so their text cannot be read.\n */\n textValue?: string,\n};\n\nexport const SelectItem = React.memo(function SelectItem<T extends SelectValue = string>({\n value,\n children,\n disabled,\n className,\n textValue\n}: SelectItemProps<T>) {\n // Convert value to string for Radix UI\n const stringValue = String(value);\n\n return <SelectPrimitive.Item\n key={stringValue}\n value={stringValue}\n disabled={disabled}\n textValue={textValue}\n className={cls(\n \"w-full\",\n \"relative flex items-center p-2 rounded-md text-sm text-surface-accent-700 dark:text-surface-accent-300\",\n \"focus:z-10\",\n \"data-[state=checked]:bg-surface-active focus:bg-surface-hover\",\n \"data-[state=checked]:focus:bg-surface-active\",\n disabled ? \"opacity-50 cursor-not-allowed\" : \"cursor-pointer\",\n \"[&>*]:w-full\",\n \"overflow-visible\",\n className\n )}>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n <div\n className=\"absolute left-1 data-[state=checked]:block hidden\">\n <CheckIcon size={iconSize.smallest}/>\n </div>\n </SelectPrimitive.Item>;\n});\n\nexport type SelectGroupProps = {\n label: React.ReactNode,\n children: React.ReactNode,\n className?: string\n};\n\nexport const SelectGroup = React.memo(function SelectGroup({\n label,\n children,\n className\n}: SelectGroupProps) {\n // The items go inside the group and the label is Radix's own, so the\n // `role=\"group\"` holds its options and is named by the label.\n return <SelectPrimitive.Group className=\"mt-6 first:mt-2\">\n <SelectPrimitive.Label\n className={cls(\n \"text-xs text-surface-accent-900 dark:text-white uppercase tracking-wider font-semibold\",\n \"px-2 py-2\",\n className\n )}>\n {label}\n </SelectPrimitive.Label>\n {children}\n </SelectPrimitive.Group>;\n});\n"],"mappings":"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"}
|
package/dist/icons/index.d.ts
CHANGED
|
@@ -33,4 +33,4 @@ export * from "./LucideIconByName.js";
|
|
|
33
33
|
* @see loadLucideIcons
|
|
34
34
|
*/
|
|
35
35
|
export { icons as lucideIcons } from "lucide-react";
|
|
36
|
-
export { AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, LinkIcon, Link2Icon, Unlink2Icon, ListIcon, LockIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
|
|
36
|
+
export { AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitMergeIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, LinkIcon, Link2Icon, Unlink2Icon, ListIcon, LockIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon } from "lucide-react";
|
package/dist/icons/index.es.js
CHANGED
|
@@ -4,5 +4,5 @@ import { colorClassesMapping, iconSize } from "./Icon.es.js";
|
|
|
4
4
|
import { GitHubIcon } from "./GitHubIcon.es.js";
|
|
5
5
|
import { HandleIcon } from "./HandleIcon.es.js";
|
|
6
6
|
import { LucideIconByName, getLoadedLucideIcons, loadLucideIcons, resolveLucideIcon, useLucideIcons } from "./LucideIconByName.es.js";
|
|
7
|
-
import { AlertCircleIcon as AlertCircleIcon$1, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon as ArrowLeftIcon$1, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon as ArrowUpIcon$1, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon as CalendarIcon$1, CheckCircle2Icon, CheckCircleIcon, CheckIcon as CheckIcon$1, CheckSquareIcon, ChevronDownIcon as ChevronDownIcon$1, ChevronLeftIcon as ChevronLeftIcon$1, ChevronRightIcon as ChevronRightIcon$1, ChevronUpIcon as ChevronUpIcon$1, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon as FilterIcon$1, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon as ImageIcon$1, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LockIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon as MinusIcon$1, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon as RefreshCwIcon$1, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon as SearchIcon$1, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon as XIcon$1, icons as lucideIcons } from "lucide-react";
|
|
8
|
-
export { AlertCircleIcon$1 as AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon$1 as ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon$1 as ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon$1 as CalendarIcon, CheckCircle2Icon, CheckCircleIcon, CheckIcon$1 as CheckIcon, CheckSquareIcon, ChevronDownIcon$1 as ChevronDownIcon, ChevronLeftIcon$1 as ChevronLeftIcon, ChevronRightIcon$1 as ChevronRightIcon, ChevronUpIcon$1 as ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon$1 as FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitHubIcon, GlobeIcon, HandleIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon$1 as ImageIcon, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LockIcon, LogOutIcon, LucideIconByName, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon$1 as MinusIcon, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon$1 as RefreshCwIcon, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon$1 as SearchIcon, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon$1 as XIcon, colorClassesMapping, coolIconKeys, getLoadedLucideIcons, iconKeys, iconSize, loadLucideIcons, lucideIcons, resolveLucideIcon, useLucideIcons };
|
|
7
|
+
import { AlertCircleIcon as AlertCircleIcon$1, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon as ArrowLeftIcon$1, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon as ArrowUpIcon$1, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon as CalendarIcon$1, CheckCircle2Icon, CheckCircleIcon, CheckIcon as CheckIcon$1, CheckSquareIcon, ChevronDownIcon as ChevronDownIcon$1, ChevronLeftIcon as ChevronLeftIcon$1, ChevronRightIcon as ChevronRightIcon$1, ChevronUpIcon as ChevronUpIcon$1, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon as FilterIcon$1, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitMergeIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon as ImageIcon$1, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LockIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon as MinusIcon$1, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon as RefreshCwIcon$1, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon as SearchIcon$1, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon as XIcon$1, icons as lucideIcons } from "lucide-react";
|
|
8
|
+
export { AlertCircleIcon$1 as AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon$1 as ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon$1 as ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon$1 as CalendarIcon, CheckCircle2Icon, CheckCircleIcon, CheckIcon$1 as CheckIcon, CheckSquareIcon, ChevronDownIcon$1 as ChevronDownIcon, ChevronLeftIcon$1 as ChevronLeftIcon, ChevronRightIcon$1 as ChevronRightIcon, ChevronUpIcon$1 as ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon$1 as FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitHubIcon, GitMergeIcon, GlobeIcon, HandleIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon$1 as ImageIcon, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LockIcon, LogOutIcon, LucideIconByName, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon$1 as MinusIcon, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon$1 as RefreshCwIcon, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon$1 as SearchIcon, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon$1 as XIcon, colorClassesMapping, coolIconKeys, getLoadedLucideIcons, iconKeys, iconSize, loadLucideIcons, lucideIcons, resolveLucideIcon, useLucideIcons };
|
package/dist/index.es.js
CHANGED
|
@@ -18,7 +18,7 @@ import { colorClassesMapping, iconSize } from "./icons/Icon.es.js";
|
|
|
18
18
|
import { GitHubIcon } from "./icons/GitHubIcon.es.js";
|
|
19
19
|
import { HandleIcon } from "./icons/HandleIcon.es.js";
|
|
20
20
|
import { LucideIconByName, getLoadedLucideIcons, loadLucideIcons, resolveLucideIcon, useLucideIcons } from "./icons/LucideIconByName.es.js";
|
|
21
|
-
import { AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LockIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon, lucideIcons } from "./icons/index.es.js";
|
|
21
|
+
import { AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, BoldIcon, BookOpenIcon, CalendarIcon, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, CircleDotIcon, CircleIcon, CircleUserIcon, CodeIcon, ColumnsIcon, CopyIcon, DatabaseIcon, DollarSignIcon, DownloadIcon, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitMergeIcon, GlobeIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, ImageIcon, ImageOffIcon, InfoIcon, ItalicIcon, KanbanIcon, KeyIcon, KeyRoundIcon, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, LoaderIcon, LockIcon, LogOutIcon, MailIcon, Maximize2Icon, MenuIcon, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, QuoteIcon, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, Rows3Icon, SaveIcon, SearchIcon, SendIcon, SettingsIcon, ShieldIcon, ShoppingCartIcon, SlidersHorizontalIcon, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, TableIcon, TagIcon, TerminalIcon, TextIcon, Trash2Icon, TrendingUpIcon, TypeIcon, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon, lucideIcons } from "./icons/index.es.js";
|
|
22
22
|
import { Alert } from "./components/Alert.es.js";
|
|
23
23
|
import { Avatar } from "./components/Avatar.es.js";
|
|
24
24
|
import { BooleanSwitch } from "./components/BooleanSwitch.es.js";
|
|
@@ -85,4 +85,4 @@ import { CardView } from "./views/CardView.es.js";
|
|
|
85
85
|
import { KanbanView } from "./views/KanbanView.es.js";
|
|
86
86
|
import { CollectionView } from "./views/CollectionView/CollectionView.es.js";
|
|
87
87
|
import { isKeyHandled } from "./util/keys.es.js";
|
|
88
|
-
export { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, Autocomplete, AutocompleteItem, Avatar, Badge, BoldIcon, BookOpenIcon, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_HUES, CHIP_SEED_KEYS, CONTROL_HEIGHT, CalendarIcon, Card, CardView, CenteredView, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleDotIcon, CircleIcon, CircleUserIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, CollectionView, ColorPicker, ColumnsIcon, Container, CopyIcon, DatabaseIcon, DateTimeField, DebouncedTextField, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitHubIcon, GlobeIcon, HandleIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, IconButton, ImageIcon, ImageOffIcon, InfoIcon, InfoLabel, InputLabel, ItalicIcon, KanbanIcon, KanbanView, KeyIcon, KeyRoundIcon, Label, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, ListView, LoaderIcon, LoadingButton, LockIcon, LogOutIcon, LucideIconByName, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarItemIndicator, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarSubTriggerIndicator, MenubarTrigger, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectContext, MultiSelectItem, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, Paper, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, Popover, PopoverPrimitive, Portal, PortalContainerProvider, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SaveIcon, SearchBar, SearchIcon, Select, SelectGroup, SelectItem, SendIcon, Separator, SettingsIcon, Sheet, ShieldIcon, ShoppingCartIcon, Skeleton, Slider, SlidersHorizontalIcon, Slot, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TagIcon, TerminalIcon, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, TooltipProvider, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UiLabelsProvider, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableNumberInput, VirtualTableSelect, VirtualTableSelectionProvider, VirtualTableSwitch, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon, accentTextMixin, cardClickableMixin, cardMixin, cardSelectedMixin, cls, codeSurfaceMixin, colorClassesMapping, controlHeightMixin, controlPaddingMixin, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, defaultUiLabels, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedClasses, focusedDisabled, focusedInvisibleMixin, getColorSchemeForKey, getColorSchemeForSeed, getLoadedLucideIcons, iconKeys, iconSize, isChunkLoadError, isKeyHandled, lazyChunk, loadChunk, loadLucideIcons, lucideIcons, paperMixin, resolveLucideIcon, surfaceActiveMixin, surfaceHoverMixin, useAutoComplete, useDebounceCallback, useDebounceValue, useDebouncedCallback, useInjectStyles, useIsDarkMode, useLucideIcons, useOutsideAlerter, usePortalContainer, useUiLabels, useVirtualTableCellSelected, useVirtualTableSelection };
|
|
88
|
+
export { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, AppWindow, ArrowDownIcon, ArrowDownToLineIcon, ArrowLeftIcon, ArrowRightFromLineIcon, ArrowRightIcon, ArrowRightLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, ArrowUpToLineIcon, Autocomplete, AutocompleteItem, Avatar, Badge, BoldIcon, BookOpenIcon, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_HUES, CHIP_SEED_KEYS, CONTROL_HEIGHT, CalendarIcon, Card, CardView, CenteredView, CheckCircle2Icon, CheckCircleIcon, CheckIcon, CheckSquareIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleDotIcon, CircleIcon, CircleUserIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, CollectionView, ColorPicker, ColumnsIcon, Container, CopyIcon, DatabaseIcon, DateTimeField, DebouncedTextField, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileIcon, FileSearchIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderIcon, FolderKanbanIcon, FolderPlusIcon, FolderUpIcon, FunctionSquareIcon, GitBranchIcon, GitHubIcon, GitMergeIcon, GlobeIcon, HandleIcon, HashIcon, Heading1Icon, Heading2Icon, Heading3Icon, HeartIcon, HelpCircleIcon, HistoryIcon, HomeIcon, IconButton, ImageIcon, ImageOffIcon, InfoIcon, InfoLabel, InputLabel, ItalicIcon, KanbanIcon, KanbanView, KeyIcon, KeyRoundIcon, Label, LanguagesIcon, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListPlusIcon, ListTodoIcon, ListView, LoaderIcon, LoadingButton, LockIcon, LogOutIcon, LucideIconByName, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarItemIndicator, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarSubTriggerIndicator, MenubarTrigger, MessageCircleIcon, MinusCircleIcon, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectContext, MultiSelectItem, Music2Icon, PanelLeftCloseIcon, PanelLeftIcon, PanelLeftOpenIcon, Paper, PauseIcon, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlayIcon, PlusIcon, Popover, PopoverPrimitive, Portal, PortalContainerProvider, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCcwIcon, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SaveIcon, SearchBar, SearchIcon, Select, SelectGroup, SelectItem, SendIcon, Separator, SettingsIcon, Sheet, ShieldIcon, ShoppingCartIcon, Skeleton, Slider, SlidersHorizontalIcon, Slot, SquareIcon, StarIcon, StickyNoteIcon, StrikethroughIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TagIcon, TerminalIcon, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, TooltipProvider, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UiLabelsProvider, UnderlineIcon, UndoIcon, Unlink2Icon, UploadCloudIcon, UploadIcon, UserCheckIcon, UserIcon, UserPlus, UsersIcon, VideoIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableNumberInput, VirtualTableSelect, VirtualTableSelectionProvider, VirtualTableSwitch, VoteIcon, Wand2Icon, WrenchIcon, XCircleIcon, XIcon, accentTextMixin, cardClickableMixin, cardMixin, cardSelectedMixin, cls, codeSurfaceMixin, colorClassesMapping, controlHeightMixin, controlPaddingMixin, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, defaultUiLabels, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedClasses, focusedDisabled, focusedInvisibleMixin, getColorSchemeForKey, getColorSchemeForSeed, getLoadedLucideIcons, iconKeys, iconSize, isChunkLoadError, isKeyHandled, lazyChunk, loadChunk, loadLucideIcons, lucideIcons, paperMixin, resolveLucideIcon, surfaceActiveMixin, surfaceHoverMixin, useAutoComplete, useDebounceCallback, useDebounceValue, useDebouncedCallback, useInjectStyles, useIsDarkMode, useLucideIcons, useOutsideAlerter, usePortalContainer, useUiLabels, useVirtualTableCellSelected, useVirtualTableSelection };
|
package/dist/src/theme.css
CHANGED
|
@@ -290,10 +290,11 @@
|
|
|
290
290
|
* Lifted means lighter on both themes; the light frame is the greyest
|
|
291
291
|
* thing on the page. Fields are the one exception — a field is inset, so
|
|
292
292
|
* it is an alpha fill that goes the other way.
|
|
293
|
-
* 2. A hairline marks an object, not a region. Cards on the sheet
|
|
294
|
-
* surfaces
|
|
293
|
+
* 2. A hairline marks an object, not a region. Cards on the sheet and
|
|
294
|
+
* floating surfaces get a line. The sheet edge, tiles inside a card,
|
|
295
295
|
* rows, tracks, buttons and chips do not; the lightness step separates
|
|
296
|
-
* them.
|
|
296
|
+
* them. Fields do not either: a field is a filled box, and its fill is
|
|
297
|
+
* set strong enough to be the whole edge (see `--surface-field` below).
|
|
297
298
|
*
|
|
298
299
|
* These are plain custom properties, deliberately outside `@theme`, so they
|
|
299
300
|
* can switch on the theme selector. The `@theme inline` entries above alias
|
|
@@ -323,8 +324,11 @@
|
|
|
323
324
|
--surface-raised: #eaecef;
|
|
324
325
|
--surface-raised-hover: #e1e4e7;
|
|
325
326
|
--surface-lifted: #ffffff;
|
|
326
|
-
|
|
327
|
-
|
|
327
|
+
/* A field has no edge, so the fill alone has to read as a box on white
|
|
328
|
+
and on the sheet. 3.5% did that only with a hairline round it; 5% is
|
|
329
|
+
the FireCMS field (slate-200 at half strength), on the graphite hue. */
|
|
330
|
+
--surface-field: rgb(0 0 0 / 0.05);
|
|
331
|
+
--surface-field-hover: rgb(0 0 0 / 0.075);
|
|
328
332
|
--surface-hover: rgb(0 0 0 / 0.04);
|
|
329
333
|
--surface-active: rgb(0 0 0 / 0.07);
|
|
330
334
|
--surface-scrim: rgb(0 0 0 / 0.4);
|
|
@@ -344,8 +348,11 @@
|
|
|
344
348
|
--surface-raised: #212429;
|
|
345
349
|
--surface-raised-hover: #292c32;
|
|
346
350
|
--surface-lifted: #292c32;
|
|
347
|
-
|
|
348
|
-
|
|
351
|
+
/* 4% was tuned to sit inside a hairline. Without one it read as a soft
|
|
352
|
+
slab on a card; 6.5% lifts a field about as far off `surface-card` as
|
|
353
|
+
the FireCMS field sits off its own card. */
|
|
354
|
+
--surface-field: rgb(255 255 255 / 0.065);
|
|
355
|
+
--surface-field-hover: rgb(255 255 255 / 0.095);
|
|
349
356
|
--surface-hover: rgb(255 255 255 / 0.06);
|
|
350
357
|
--surface-active: rgb(255 255 255 / 0.09);
|
|
351
358
|
--surface-scrim: rgb(0 0 0 / 0.6);
|
package/dist/styles.d.ts
CHANGED
|
@@ -69,20 +69,24 @@ export declare const focusedClasses = "z-30 outline-none ring-2 ring-primary/60
|
|
|
69
69
|
*
|
|
70
70
|
* A field is the one surface that goes the other way: it is INSET into
|
|
71
71
|
* whatever holds it, so `surface-field` is an alpha fill rather than a solid,
|
|
72
|
-
* and it composes over a card, the sheet or a dialog alike.
|
|
73
|
-
* the hairline: a field is an object (DESIGN.md rule 2), and without the line
|
|
74
|
-
* a 4% fill on a card reads as a soft slab rather than a control — measured on
|
|
75
|
-
* the product form, eight of them in a row looked like a wall. The reference
|
|
76
|
-
* draws every input with its 1px edge, on both themes.
|
|
72
|
+
* and it composes over a card, the sheet or a dialog alike.
|
|
77
73
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* the
|
|
74
|
+
* It is a filled box with no visible edge — the FireCMS field. From 0.20 it
|
|
75
|
+
* carried a hairline, because a 4% fill on a card read as a soft slab rather
|
|
76
|
+
* than a control; the fill is now strong enough to be the edge on its own
|
|
77
|
+
* (`--surface-field` in theme.css), and a column of fields reads as a set of
|
|
78
|
+
* boxes rather than a ruled table.
|
|
79
|
+
*
|
|
80
|
+
* The border is still there, transparent, and that is load-bearing. Every
|
|
81
|
+
* height in the kit was measured with a 1px border inside the box (MultiSelect
|
|
82
|
+
* and the read-only field compensate for it explicitly), and the error state
|
|
83
|
+
* recolours it (`border-red-500`) rather than adding one, so a field that
|
|
84
|
+
* turns invalid does not grow by 2px and shove the form down.
|
|
81
85
|
*
|
|
82
86
|
* Exactly one element carries this mixin per field — the box, never the
|
|
83
87
|
* input inside it — so the alpha fill and its hover composite once.
|
|
84
88
|
*/
|
|
85
|
-
export declare const fieldBackgroundMixin = "bg-surface-field border border-
|
|
89
|
+
export declare const fieldBackgroundMixin = "bg-surface-field border border-transparent";
|
|
86
90
|
export declare const fieldBackgroundInvisibleMixin = "bg-transparent";
|
|
87
91
|
export declare const fieldBackgroundDisabledMixin = "bg-surface-field/60";
|
|
88
92
|
/**
|
|
@@ -94,7 +98,7 @@ export declare const fieldBackgroundHoverMixin = "transition-colors duration-150
|
|
|
94
98
|
/**
|
|
95
99
|
* The hairline. One value per theme (`--hairline` in theme.css), and where it
|
|
96
100
|
* goes matters more than what it is: a line marks an OBJECT — a card on the
|
|
97
|
-
* sheet, a
|
|
101
|
+
* sheet, a floating surface. It does not mark a region: a tile inside
|
|
98
102
|
* a card, a row, a segmented track, a button, a chip are fills, and the
|
|
99
103
|
* lightness step between them is the whole separation. One exception, and it
|
|
100
104
|
* is the sheet's own edge against the frame: that step is about 3 L* on both
|
package/dist/styles.es.js
CHANGED
|
@@ -68,20 +68,24 @@ var focusedClasses = "z-30 outline-none ring-2 ring-primary/60 ring-offset-0 rin
|
|
|
68
68
|
*
|
|
69
69
|
* A field is the one surface that goes the other way: it is INSET into
|
|
70
70
|
* whatever holds it, so `surface-field` is an alpha fill rather than a solid,
|
|
71
|
-
* and it composes over a card, the sheet or a dialog alike.
|
|
72
|
-
* the hairline: a field is an object (DESIGN.md rule 2), and without the line
|
|
73
|
-
* a 4% fill on a card reads as a soft slab rather than a control — measured on
|
|
74
|
-
* the product form, eight of them in a row looked like a wall. The reference
|
|
75
|
-
* draws every input with its 1px edge, on both themes.
|
|
71
|
+
* and it composes over a card, the sheet or a dialog alike.
|
|
76
72
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* the
|
|
73
|
+
* It is a filled box with no visible edge — the FireCMS field. From 0.20 it
|
|
74
|
+
* carried a hairline, because a 4% fill on a card read as a soft slab rather
|
|
75
|
+
* than a control; the fill is now strong enough to be the edge on its own
|
|
76
|
+
* (`--surface-field` in theme.css), and a column of fields reads as a set of
|
|
77
|
+
* boxes rather than a ruled table.
|
|
78
|
+
*
|
|
79
|
+
* The border is still there, transparent, and that is load-bearing. Every
|
|
80
|
+
* height in the kit was measured with a 1px border inside the box (MultiSelect
|
|
81
|
+
* and the read-only field compensate for it explicitly), and the error state
|
|
82
|
+
* recolours it (`border-red-500`) rather than adding one, so a field that
|
|
83
|
+
* turns invalid does not grow by 2px and shove the form down.
|
|
80
84
|
*
|
|
81
85
|
* Exactly one element carries this mixin per field — the box, never the
|
|
82
86
|
* input inside it — so the alpha fill and its hover composite once.
|
|
83
87
|
*/
|
|
84
|
-
var fieldBackgroundMixin = "bg-surface-field border border-
|
|
88
|
+
var fieldBackgroundMixin = "bg-surface-field border border-transparent";
|
|
85
89
|
var fieldBackgroundInvisibleMixin = "bg-transparent";
|
|
86
90
|
var fieldBackgroundDisabledMixin = "bg-surface-field/60";
|
|
87
91
|
/**
|
|
@@ -93,7 +97,7 @@ var fieldBackgroundHoverMixin = "transition-colors duration-150 hover:bg-surface
|
|
|
93
97
|
/**
|
|
94
98
|
* The hairline. One value per theme (`--hairline` in theme.css), and where it
|
|
95
99
|
* goes matters more than what it is: a line marks an OBJECT — a card on the
|
|
96
|
-
* sheet, a
|
|
100
|
+
* sheet, a floating surface. It does not mark a region: a tile inside
|
|
97
101
|
* a card, a row, a segmented track, a button, a chip are fills, and the
|
|
98
102
|
* lightness step between them is the whole separation. One exception, and it
|
|
99
103
|
* is the sheet's own edge against the frame: that step is about 3 L* on both
|
package/dist/styles.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.es.js","names":[],"sources":["../src/styles.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Control size scale — the single source of truth for how tall an inline\n// control is.\n//\n// Every control that sits on a line with another control (buttons, text\n// fields, selects, search bars, date fields, icon buttons, checkboxes) resolves\n// its height from this map, so a Button and a TextField at the same `size` are\n// pixel-identical and share a baseline. Before this existed each component\n// carried its own map and `size=\"large\"` meant 42px on a Button and 64px on a\n// TextField.\n//\n// The scale is 28/32/40/48 — the rhythm IconButton and Checkbox already used.\n// `xl`/`2xl` continue above it and are button-only.\n// ---------------------------------------------------------------------------\n\nexport type ControlSize = \"smallest\" | \"small\" | \"medium\" | \"large\";\nexport type ButtonSize = ControlSize | \"xl\" | \"2xl\";\n\nexport const CONTROL_HEIGHT = {\n smallest: 28,\n small: 32,\n medium: 40,\n large: 48,\n xl: 56,\n \"2xl\": 64\n} as const satisfies Record<ButtonSize, number>;\n\n/** Tailwind min-height class per control size. Keep in sync with CONTROL_HEIGHT. */\nexport const controlHeightMixin = {\n smallest: \"min-h-[28px]\",\n small: \"min-h-[32px]\",\n medium: \"min-h-[40px]\",\n large: \"min-h-[48px]\",\n xl: \"min-h-[56px]\",\n \"2xl\": \"min-h-[64px]\"\n} as const satisfies Record<ButtonSize, string>;\n\n/** Horizontal padding paired with each size, so gutters scale with height. */\nexport const controlPaddingMixin = {\n smallest: \"px-2\",\n small: \"px-2\",\n medium: \"px-3\",\n large: \"px-4\",\n xl: \"px-6\",\n \"2xl\": \"px-10\"\n} as const satisfies Record<ButtonSize, string>;\n\n/**\n * Suppress the focus ring on a surface that is focusable but is not a control —\n * a popover panel, a menu container, a bare cell input inside a table that\n * indicates focus by other means.\n *\n * Still expressed as `ring-0`, and that is why the default ring in\n * `index.css` overrides `--tw-ring-shadow` rather than switching to\n * `inset-ring-*`: `inset-ring` is a different variable, which this would not\n * have silenced, and every surface that had opted out would have got a ring\n * back.\n */\nexport const focusedDisabled = \"focus-visible:ring-0 focus-visible:ring-offset-0\";\nexport const focusedInvisibleMixin = \"focus:bg-surface-field-hover\";\n\n/**\n * The focus ring, drawn OUTSIDE the element, for a control that opts into it.\n *\n * The product's default ring is the `:focus-visible` rule in `index.css`, and\n * that one is drawn *inside* the border box: an outset ring is paint beyond the\n * box, and any ancestor that clips — a scroller, a truncating cell, a\n * max-height collapse — cuts it. This one is still outset because its only\n * caller is a whole field row sitting inside `p-4`/`p-6` of card padding, where\n * there is room around it and nothing clipping within 16px. Reach for it only\n * where that is true; anywhere else, let the default apply.\n *\n * Three dead declarations were removed rather than reshuffled:\n * `outline-hidden` + `outline-none` — the same instruction twice.\n * `ring-primary` + `ring-primary/50` — the first could never win.\n * `ring-opacity-50` — Tailwind v3 syntax. v4 expresses\n * opacity with the `/50` slash form, so\n * this generated nothing at all.\n *\n * What is left is the ring that was actually rendering, at 60% rather than 50%:\n * on `surface-900` the old value sat close enough to the field's own border to\n * be missed at a glance, which is the one thing a focus ring may not be.\n * `ring-offset-0` + a transparent offset stay — without them the ring draws a\n * white halo on dark surfaces.\n */\nexport const focusedClasses = \"z-30 outline-none ring-2 ring-primary/60 ring-offset-0 ring-offset-transparent\";\n\n/**\n * Field surfaces.\n *\n * A field is the one surface that goes the other way: it is INSET into\n * whatever holds it, so `surface-field` is an alpha fill rather than a solid,\n * and it composes over a card, the sheet or a dialog alike. It also carries\n * the hairline: a field is an object (DESIGN.md rule 2), and without the line\n * a 4% fill on a card reads as a soft slab rather than a control — measured on\n * the product form, eight of them in a row looked like a wall. The reference\n * draws every input with its 1px edge, on both themes.\n *\n * The 1px inset top highlight that used to sit here was the recessed-edge\n * device from before the field had a border; with a real hairline above it\n * the two stacked into a doubled top edge. The border does its work now.\n *\n * Exactly one element carries this mixin per field — the box, never the\n * input inside it — so the alpha fill and its hover composite once.\n */\nexport const fieldBackgroundMixin = \"bg-surface-field border border-hairline\";\nexport const fieldBackgroundInvisibleMixin = \"bg-transparent\";\nexport const fieldBackgroundDisabledMixin = \"bg-surface-field/60\";\n\n/**\n * `transition-colors` here, because every other interactive surface in the kit\n * has it and fields did not — `cardClickableMixin` eases, a TextField snapped.\n * On a form of eight fields that difference is the whole feel of the screen.\n */\nexport const fieldBackgroundHoverMixin = \"transition-colors duration-150 hover:bg-surface-field-hover\";\n\n/**\n * The hairline. One value per theme (`--hairline` in theme.css), and where it\n * goes matters more than what it is: a line marks an OBJECT — a card on the\n * sheet, a field, a floating surface. It does not mark a region: a tile inside\n * a card, a row, a segmented track, a button, a chip are fills, and the\n * lightness step between them is the whole separation. One exception, and it\n * is the sheet's own edge against the frame: that step is about 3 L* on both\n * themes and did not read on a real screen, so the Scaffold draws this line\n * there. Measured before this existed, the panel drew the same line on every\n * card and every tile as well, and with only two surface values available the\n * line was doing the work a step should have done.\n */\nexport const defaultBorderMixin = \"border-hairline\";\n\n// ---------------------------------------------------------------------------\n// Surfaces are named by ROLE (see theme.css) and the theme decides the value.\n// Every nested surface is one step lifted from the surface it sits on, on both\n// themes. The rules are in packages/ui/DESIGN.md (Surfaces); the measurements\n// behind the ladder are in docs/design/instatic-distilled.html.\n//\n// Two kinds of card, and the difference is what the border is for.\n//\n// A **floating** surface — a menu, a dialog, a popover — sits OVER the page. It\n// has to be legible against whatever happens to be underneath it, so its edge\n// is definite: `hairline-strong`. That is `paperMixin`.\n//\n// A **page** surface — a card in the document flow — sits ON the sheet, and it\n// is already one step lighter than the sheet. It keeps a plain hairline, which\n// is all a card on the sheet needs; once the step is there the line is close\n// to optional, and a stronger one reads as a box drawn around content.\n// ---------------------------------------------------------------------------\nexport const paperMixin = \"bg-surface-card rounded-lg border border-hairline-strong\";\nexport const cardMixin = \"bg-surface-card rounded-xl border border-hairline\";\n\n/**\n * An inset well: code, a query, a log tail, a connection string.\n *\n * It must read as *recessed into* the surface holding it, which means darker\n * than that surface in dark mode. Cards now sit at `surface-card` (#16181c),\n * so #090a0c is already thirteen units below and reads as a well; #000 was\n * tuned for the old #0a card and is now harsher than it needs to be. That value\n * is the `surface-well` role (theme.css), the one surface that goes DOWN the\n * ladder on purpose. Pair with `font-mono`; this mixin carries the surface\n * only.\n */\nexport const codeSurfaceMixin = \"bg-surface-well rounded-md\";\n\n/**\n * The accent, used as TEXT.\n *\n * `--color-primary` (#0070F4) is tuned to be a fill — white text on it, it on\n * white. Read as text on our dark surfaces it lands at **4.36:1 on a\n * `surface-900` card** (measured), which is below AA for body-sized text, and\n * every accent link in the product sits on exactly that surface. On the page's\n * `surface-950` it only reaches 4.62:1, so the margin was never real.\n *\n * `--color-primary-light` is the same hue lifted 0.15 in OKLCH lightness and\n * measures **7.34:1** on the same card. It is indistinguishable as \"the blue\"\n * and comfortably legible, so dark mode swaps to it for text.\n *\n * Use this for links, accent labels and any accent-coloured type. It is NOT for\n * fills — a filled button keeps `bg-primary`, where the contrast question runs\n * the other way and #0070F4 is already correct.\n */\nexport const accentTextMixin = \"text-primary dark:text-primary-light\";\n\n/**\n * Hover on a SOLID surface goes one step lighter, and it stays neutral. The\n * previous `hover:bg-primary/5` tinted every clickable card blue, which spent\n * the brand colour on a state that carries no meaning; on the dark ladder it\n * also read as a hue shift rather than a lift.\n */\nexport const cardClickableMixin = \"hover:bg-surface-card-hover cursor-pointer transition-colors duration-150\";\nexport const cardSelectedMixin = \"bg-primary-bg/30 dark:bg-primary-bg/10 ring-1 ring-primary/75\";\n\n/**\n * Hover on a TRANSPARENT ground — a nav item, a menu item, a list row, a tree\n * row. `surface-hover` is an alpha fill, so it lifts whatever it lands on by\n * the same amount, and `surface-active` is the same idea one step further for\n * the selected row when no hue is wanted. Do not use these on a solid card:\n * an alpha fill REPLACES a solid background rather than tinting it.\n */\nexport const surfaceHoverMixin = \"hover:bg-surface-hover transition-colors duration-150\";\nexport const surfaceActiveMixin = \"bg-surface-active\";\n"],"mappings":";AAkBA,IAAa,iBAAiB;CAC1B,UAAU;CACV,OAAO;CACP,QAAQ;CACR,OAAO;CACP,IAAI;CACJ,OAAO;AACX;;AAGA,IAAa,qBAAqB;CAC9B,UAAU;CACV,OAAO;CACP,QAAQ;CACR,OAAO;CACP,IAAI;CACJ,OAAO;AACX;;AAGA,IAAa,sBAAsB;CAC/B,UAAU;CACV,OAAO;CACP,QAAQ;CACR,OAAO;CACP,IAAI;CACJ,OAAO;AACX;;;;;;;;;;;;AAaA,IAAa,kBAAkB;AAC/B,IAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BrC,IAAa,iBAAiB;;;;;;;;;;;;;;;;;;;AAoB9B,IAAa,uBAAuB;AACpC,IAAa,gCAAgC;AAC7C,IAAa,+BAA+B;;;;;;AAO5C,IAAa,4BAA4B;;;;;;;;;;;;;AAczC,IAAa,qBAAqB;AAmBlC,IAAa,aAAa;AAC1B,IAAa,YAAY;;;;;;;;;;;;AAazB,IAAa,mBAAmB;;;;;;;;;;;;;;;;;;AAmBhC,IAAa,kBAAkB;;;;;;;AAQ/B,IAAa,qBAAqB;AAClC,IAAa,oBAAoB;;;;;;;;AASjC,IAAa,oBAAoB;AACjC,IAAa,qBAAqB"}
|
|
1
|
+
{"version":3,"file":"styles.es.js","names":[],"sources":["../src/styles.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Control size scale — the single source of truth for how tall an inline\n// control is.\n//\n// Every control that sits on a line with another control (buttons, text\n// fields, selects, search bars, date fields, icon buttons, checkboxes) resolves\n// its height from this map, so a Button and a TextField at the same `size` are\n// pixel-identical and share a baseline. Before this existed each component\n// carried its own map and `size=\"large\"` meant 42px on a Button and 64px on a\n// TextField.\n//\n// The scale is 28/32/40/48 — the rhythm IconButton and Checkbox already used.\n// `xl`/`2xl` continue above it and are button-only.\n// ---------------------------------------------------------------------------\n\nexport type ControlSize = \"smallest\" | \"small\" | \"medium\" | \"large\";\nexport type ButtonSize = ControlSize | \"xl\" | \"2xl\";\n\nexport const CONTROL_HEIGHT = {\n smallest: 28,\n small: 32,\n medium: 40,\n large: 48,\n xl: 56,\n \"2xl\": 64\n} as const satisfies Record<ButtonSize, number>;\n\n/** Tailwind min-height class per control size. Keep in sync with CONTROL_HEIGHT. */\nexport const controlHeightMixin = {\n smallest: \"min-h-[28px]\",\n small: \"min-h-[32px]\",\n medium: \"min-h-[40px]\",\n large: \"min-h-[48px]\",\n xl: \"min-h-[56px]\",\n \"2xl\": \"min-h-[64px]\"\n} as const satisfies Record<ButtonSize, string>;\n\n/** Horizontal padding paired with each size, so gutters scale with height. */\nexport const controlPaddingMixin = {\n smallest: \"px-2\",\n small: \"px-2\",\n medium: \"px-3\",\n large: \"px-4\",\n xl: \"px-6\",\n \"2xl\": \"px-10\"\n} as const satisfies Record<ButtonSize, string>;\n\n/**\n * Suppress the focus ring on a surface that is focusable but is not a control —\n * a popover panel, a menu container, a bare cell input inside a table that\n * indicates focus by other means.\n *\n * Still expressed as `ring-0`, and that is why the default ring in\n * `index.css` overrides `--tw-ring-shadow` rather than switching to\n * `inset-ring-*`: `inset-ring` is a different variable, which this would not\n * have silenced, and every surface that had opted out would have got a ring\n * back.\n */\nexport const focusedDisabled = \"focus-visible:ring-0 focus-visible:ring-offset-0\";\nexport const focusedInvisibleMixin = \"focus:bg-surface-field-hover\";\n\n/**\n * The focus ring, drawn OUTSIDE the element, for a control that opts into it.\n *\n * The product's default ring is the `:focus-visible` rule in `index.css`, and\n * that one is drawn *inside* the border box: an outset ring is paint beyond the\n * box, and any ancestor that clips — a scroller, a truncating cell, a\n * max-height collapse — cuts it. This one is still outset because its only\n * caller is a whole field row sitting inside `p-4`/`p-6` of card padding, where\n * there is room around it and nothing clipping within 16px. Reach for it only\n * where that is true; anywhere else, let the default apply.\n *\n * Three dead declarations were removed rather than reshuffled:\n * `outline-hidden` + `outline-none` — the same instruction twice.\n * `ring-primary` + `ring-primary/50` — the first could never win.\n * `ring-opacity-50` — Tailwind v3 syntax. v4 expresses\n * opacity with the `/50` slash form, so\n * this generated nothing at all.\n *\n * What is left is the ring that was actually rendering, at 60% rather than 50%:\n * on `surface-900` the old value sat close enough to the field's own border to\n * be missed at a glance, which is the one thing a focus ring may not be.\n * `ring-offset-0` + a transparent offset stay — without them the ring draws a\n * white halo on dark surfaces.\n */\nexport const focusedClasses = \"z-30 outline-none ring-2 ring-primary/60 ring-offset-0 ring-offset-transparent\";\n\n/**\n * Field surfaces.\n *\n * A field is the one surface that goes the other way: it is INSET into\n * whatever holds it, so `surface-field` is an alpha fill rather than a solid,\n * and it composes over a card, the sheet or a dialog alike.\n *\n * It is a filled box with no visible edge — the FireCMS field. From 0.20 it\n * carried a hairline, because a 4% fill on a card read as a soft slab rather\n * than a control; the fill is now strong enough to be the edge on its own\n * (`--surface-field` in theme.css), and a column of fields reads as a set of\n * boxes rather than a ruled table.\n *\n * The border is still there, transparent, and that is load-bearing. Every\n * height in the kit was measured with a 1px border inside the box (MultiSelect\n * and the read-only field compensate for it explicitly), and the error state\n * recolours it (`border-red-500`) rather than adding one, so a field that\n * turns invalid does not grow by 2px and shove the form down.\n *\n * Exactly one element carries this mixin per field — the box, never the\n * input inside it — so the alpha fill and its hover composite once.\n */\nexport const fieldBackgroundMixin = \"bg-surface-field border border-transparent\";\nexport const fieldBackgroundInvisibleMixin = \"bg-transparent\";\nexport const fieldBackgroundDisabledMixin = \"bg-surface-field/60\";\n\n/**\n * `transition-colors` here, because every other interactive surface in the kit\n * has it and fields did not — `cardClickableMixin` eases, a TextField snapped.\n * On a form of eight fields that difference is the whole feel of the screen.\n */\nexport const fieldBackgroundHoverMixin = \"transition-colors duration-150 hover:bg-surface-field-hover\";\n\n/**\n * The hairline. One value per theme (`--hairline` in theme.css), and where it\n * goes matters more than what it is: a line marks an OBJECT — a card on the\n * sheet, a floating surface. It does not mark a region: a tile inside\n * a card, a row, a segmented track, a button, a chip are fills, and the\n * lightness step between them is the whole separation. One exception, and it\n * is the sheet's own edge against the frame: that step is about 3 L* on both\n * themes and did not read on a real screen, so the Scaffold draws this line\n * there. Measured before this existed, the panel drew the same line on every\n * card and every tile as well, and with only two surface values available the\n * line was doing the work a step should have done.\n */\nexport const defaultBorderMixin = \"border-hairline\";\n\n// ---------------------------------------------------------------------------\n// Surfaces are named by ROLE (see theme.css) and the theme decides the value.\n// Every nested surface is one step lifted from the surface it sits on, on both\n// themes. The rules are in packages/ui/DESIGN.md (Surfaces); the measurements\n// behind the ladder are in docs/design/instatic-distilled.html.\n//\n// Two kinds of card, and the difference is what the border is for.\n//\n// A **floating** surface — a menu, a dialog, a popover — sits OVER the page. It\n// has to be legible against whatever happens to be underneath it, so its edge\n// is definite: `hairline-strong`. That is `paperMixin`.\n//\n// A **page** surface — a card in the document flow — sits ON the sheet, and it\n// is already one step lighter than the sheet. It keeps a plain hairline, which\n// is all a card on the sheet needs; once the step is there the line is close\n// to optional, and a stronger one reads as a box drawn around content.\n// ---------------------------------------------------------------------------\nexport const paperMixin = \"bg-surface-card rounded-lg border border-hairline-strong\";\nexport const cardMixin = \"bg-surface-card rounded-xl border border-hairline\";\n\n/**\n * An inset well: code, a query, a log tail, a connection string.\n *\n * It must read as *recessed into* the surface holding it, which means darker\n * than that surface in dark mode. Cards now sit at `surface-card` (#16181c),\n * so #090a0c is already thirteen units below and reads as a well; #000 was\n * tuned for the old #0a card and is now harsher than it needs to be. That value\n * is the `surface-well` role (theme.css), the one surface that goes DOWN the\n * ladder on purpose. Pair with `font-mono`; this mixin carries the surface\n * only.\n */\nexport const codeSurfaceMixin = \"bg-surface-well rounded-md\";\n\n/**\n * The accent, used as TEXT.\n *\n * `--color-primary` (#0070F4) is tuned to be a fill — white text on it, it on\n * white. Read as text on our dark surfaces it lands at **4.36:1 on a\n * `surface-900` card** (measured), which is below AA for body-sized text, and\n * every accent link in the product sits on exactly that surface. On the page's\n * `surface-950` it only reaches 4.62:1, so the margin was never real.\n *\n * `--color-primary-light` is the same hue lifted 0.15 in OKLCH lightness and\n * measures **7.34:1** on the same card. It is indistinguishable as \"the blue\"\n * and comfortably legible, so dark mode swaps to it for text.\n *\n * Use this for links, accent labels and any accent-coloured type. It is NOT for\n * fills — a filled button keeps `bg-primary`, where the contrast question runs\n * the other way and #0070F4 is already correct.\n */\nexport const accentTextMixin = \"text-primary dark:text-primary-light\";\n\n/**\n * Hover on a SOLID surface goes one step lighter, and it stays neutral. The\n * previous `hover:bg-primary/5` tinted every clickable card blue, which spent\n * the brand colour on a state that carries no meaning; on the dark ladder it\n * also read as a hue shift rather than a lift.\n */\nexport const cardClickableMixin = \"hover:bg-surface-card-hover cursor-pointer transition-colors duration-150\";\nexport const cardSelectedMixin = \"bg-primary-bg/30 dark:bg-primary-bg/10 ring-1 ring-primary/75\";\n\n/**\n * Hover on a TRANSPARENT ground — a nav item, a menu item, a list row, a tree\n * row. `surface-hover` is an alpha fill, so it lifts whatever it lands on by\n * the same amount, and `surface-active` is the same idea one step further for\n * the selected row when no hue is wanted. Do not use these on a solid card:\n * an alpha fill REPLACES a solid background rather than tinting it.\n */\nexport const surfaceHoverMixin = \"hover:bg-surface-hover transition-colors duration-150\";\nexport const surfaceActiveMixin = \"bg-surface-active\";\n"],"mappings":";AAkBA,IAAa,iBAAiB;CAC1B,UAAU;CACV,OAAO;CACP,QAAQ;CACR,OAAO;CACP,IAAI;CACJ,OAAO;AACX;;AAGA,IAAa,qBAAqB;CAC9B,UAAU;CACV,OAAO;CACP,QAAQ;CACR,OAAO;CACP,IAAI;CACJ,OAAO;AACX;;AAGA,IAAa,sBAAsB;CAC/B,UAAU;CACV,OAAO;CACP,QAAQ;CACR,OAAO;CACP,IAAI;CACJ,OAAO;AACX;;;;;;;;;;;;AAaA,IAAa,kBAAkB;AAC/B,IAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BrC,IAAa,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;AAwB9B,IAAa,uBAAuB;AACpC,IAAa,gCAAgC;AAC7C,IAAa,+BAA+B;;;;;;AAO5C,IAAa,4BAA4B;;;;;;;;;;;;;AAczC,IAAa,qBAAqB;AAmBlC,IAAa,aAAa;AAC1B,IAAa,YAAY;;;;;;;;;;;;AAazB,IAAa,mBAAmB;;;;;;;;;;;;;;;;;;AAmBhC,IAAa,kBAAkB;;;;;;;AAQ/B,IAAa,qBAAqB;AAClC,IAAa,oBAAoB;;;;;;;;AASjC,IAAa,oBAAoB;AACjC,IAAa,qBAAqB"}
|
package/dist/theme.css
CHANGED
|
@@ -290,10 +290,11 @@
|
|
|
290
290
|
* Lifted means lighter on both themes; the light frame is the greyest
|
|
291
291
|
* thing on the page. Fields are the one exception — a field is inset, so
|
|
292
292
|
* it is an alpha fill that goes the other way.
|
|
293
|
-
* 2. A hairline marks an object, not a region. Cards on the sheet
|
|
294
|
-
* surfaces
|
|
293
|
+
* 2. A hairline marks an object, not a region. Cards on the sheet and
|
|
294
|
+
* floating surfaces get a line. The sheet edge, tiles inside a card,
|
|
295
295
|
* rows, tracks, buttons and chips do not; the lightness step separates
|
|
296
|
-
* them.
|
|
296
|
+
* them. Fields do not either: a field is a filled box, and its fill is
|
|
297
|
+
* set strong enough to be the whole edge (see `--surface-field` below).
|
|
297
298
|
*
|
|
298
299
|
* These are plain custom properties, deliberately outside `@theme`, so they
|
|
299
300
|
* can switch on the theme selector. The `@theme inline` entries above alias
|
|
@@ -323,8 +324,11 @@
|
|
|
323
324
|
--surface-raised: #eaecef;
|
|
324
325
|
--surface-raised-hover: #e1e4e7;
|
|
325
326
|
--surface-lifted: #ffffff;
|
|
326
|
-
|
|
327
|
-
|
|
327
|
+
/* A field has no edge, so the fill alone has to read as a box on white
|
|
328
|
+
and on the sheet. 3.5% did that only with a hairline round it; 5% is
|
|
329
|
+
the FireCMS field (slate-200 at half strength), on the graphite hue. */
|
|
330
|
+
--surface-field: rgb(0 0 0 / 0.05);
|
|
331
|
+
--surface-field-hover: rgb(0 0 0 / 0.075);
|
|
328
332
|
--surface-hover: rgb(0 0 0 / 0.04);
|
|
329
333
|
--surface-active: rgb(0 0 0 / 0.07);
|
|
330
334
|
--surface-scrim: rgb(0 0 0 / 0.4);
|
|
@@ -344,8 +348,11 @@
|
|
|
344
348
|
--surface-raised: #212429;
|
|
345
349
|
--surface-raised-hover: #292c32;
|
|
346
350
|
--surface-lifted: #292c32;
|
|
347
|
-
|
|
348
|
-
|
|
351
|
+
/* 4% was tuned to sit inside a hairline. Without one it read as a soft
|
|
352
|
+
slab on a card; 6.5% lifts a field about as far off `surface-card` as
|
|
353
|
+
the FireCMS field sits off its own card. */
|
|
354
|
+
--surface-field: rgb(255 255 255 / 0.065);
|
|
355
|
+
--surface-field-hover: rgb(255 255 255 / 0.095);
|
|
349
356
|
--surface-hover: rgb(255 255 255 / 0.06);
|
|
350
357
|
--surface-active: rgb(255 255 255 / 0.09);
|
|
351
358
|
--surface-scrim: rgb(0 0 0 / 0.6);
|
|
@@ -97,7 +97,7 @@ function CollectionCardView({ dataController, properties, propertiesOrder, idPro
|
|
|
97
97
|
src: imageValue,
|
|
98
98
|
alt: "",
|
|
99
99
|
className: "w-full h-32 object-cover rounded-t-lg"
|
|
100
|
-
}) : /* @__PURE__ */ jsx("div", { className: cls("h-24 rounded-t-lg", "bg-surface-field border border-
|
|
100
|
+
}) : /* @__PURE__ */ jsx("div", { className: cls("h-24 rounded-t-lg", "bg-surface-field border border-transparent") })),
|
|
101
101
|
selectionEnabled && /* @__PURE__ */ jsx("div", {
|
|
102
102
|
className: "absolute top-2 right-2",
|
|
103
103
|
onClick: (e) => e.stopPropagation(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollectionCardView.es.js","names":[],"sources":["../../../src/views/CollectionView/CollectionCardView.tsx"],"sourcesContent":["\"use client\";\nimport React, { useCallback, useMemo } from \"react\";\nimport { CardView } from \"../CardView\";\nimport { Card } from \"../../components/Card\";\nimport { Typography } from \"../../components/Typography\";\nimport { Checkbox } from \"../../components/Checkbox\";\nimport { DefaultCellRenderer } from \"./DefaultCellRenderer\";\nimport { cls } from \"../../util\";\nimport { useUiLabels } from \"../../hooks/UiLabelsContext\";\nimport { cardSelectedMixin, fieldBackgroundMixin } from \"../../styles\";\nimport type {\n CollectionPropertyConfig,\n CollectionDataController,\n CellRendererOverride,\n CollectionViewSize,\n CollectionSelectionController,\n} from \"./CollectionViewTypes\";\nimport { resolveTitlePropertyKey } from \"./resolveTitleProperty\";\n\nimport { getValueInPath } from \"./utils\";\n\n/**\n * Props for the {@link CollectionCardView} component.\n * @group Collection View\n */\nexport type CollectionCardViewProps<T = Record<string, unknown>> = {\n dataController: CollectionDataController<T>;\n properties: Record<string, CollectionPropertyConfig>;\n propertiesOrder?: string[];\n idProperty?: string;\n titleProperty?: string;\n onRowClick?: (row: T) => void;\n cellRenderer?: CellRendererOverride<T>;\n size?: CollectionViewSize;\n selectionEnabled?: boolean;\n selectionController?: CollectionSelectionController<T>;\n highlightedItems?: T[];\n emptyComponent?: React.ReactNode;\n className?: string;\n};\n\n/**\n * Headless card grid view that wraps {@link CardView}.\n *\n * Renders each item as a card with an optional image, title,\n * preview properties, and selection support.\n *\n * @group Collection View\n */\nexport function CollectionCardView<T extends Record<string, unknown> = Record<string, unknown>>({\n dataController,\n properties,\n propertiesOrder,\n idProperty = \"id\",\n titleProperty,\n onRowClick,\n cellRenderer,\n size = \"m\",\n selectionEnabled,\n selectionController,\n highlightedItems,\n emptyComponent,\n className,\n}: CollectionCardViewProps<T>) {\n const labels = useUiLabels();\n const order = useMemo(\n () => propertiesOrder ?? Object.keys(properties),\n [propertiesOrder, properties]\n );\n\n // ── Resolve title property ───────────────────────────────────────\n const titlePropertyKey = useMemo(\n () => resolveTitlePropertyKey(order, properties, idProperty, titleProperty),\n [titleProperty, order, properties, idProperty]\n );\n\n // ── Resolve image property ───────────────────────────────────────\n const imageProperty = useMemo(() => {\n for (const key of order) {\n const prop = properties[key];\n if (prop && prop.type === \"string\" && prop.storage) {\n return key;\n }\n }\n return undefined;\n }, [order, properties]);\n\n // ── Preview properties (up to 3) ─────────────────────────────────\n const previewProperties = useMemo(() => {\n const result: { key: string; property: CollectionPropertyConfig }[] = [];\n for (const key of order) {\n if (key === titlePropertyKey || key === imageProperty) continue;\n const prop = properties[key];\n if (prop && !prop.hideFromCollection) {\n result.push({ key, property: prop });\n if (result.length >= 3) break;\n }\n }\n return result;\n }, [order, properties, titlePropertyKey, imageProperty]);\n\n // ── Selection IDs ────────────────────────────────────────────────\n const selectedIds = useMemo(() => {\n if (!selectionController?.selectedItems) return undefined;\n const ids = new Set<string | number>();\n for (const item of selectionController.selectedItems) {\n const id = getValueInPath(item as Record<string, unknown>, idProperty);\n if (id !== undefined && id !== null) {\n ids.add(id as string | number);\n }\n }\n return ids;\n }, [selectionController?.selectedItems, idProperty]);\n\n // ── Highlighted IDs ──────────────────────────────────────────────\n const highlightedIds = useMemo(() => {\n if (!highlightedItems) return undefined;\n const ids = new Set<string | number>();\n for (const item of highlightedItems) {\n const id = getValueInPath(item as Record<string, unknown>, idProperty);\n if (id !== undefined && id !== null) {\n ids.add(id as string | number);\n }\n }\n return ids;\n }, [highlightedItems, idProperty]);\n\n // ── Selection handler ────────────────────────────────────────────\n const handleSelectionChange = useCallback(\n (item: T, selected: boolean) => {\n if (!selectionController) return;\n selectionController.setSelectedItems((prev) => {\n if (selected) {\n return [...prev, item];\n }\n const itemId = getValueInPath(item as Record<string, unknown>, idProperty);\n return prev.filter((existing) => {\n const existingId = getValueInPath(existing as Record<string, unknown>, idProperty);\n return existingId !== itemId;\n });\n });\n },\n [selectionController, idProperty]\n );\n\n // ── Render card ──────────────────────────────────────────────────\n const renderCard = useCallback(\n (\n item: T,\n extra: {\n selected: boolean;\n highlighted: boolean;\n onSelectionChange: (selected: boolean) => void;\n onClick: (e: React.MouseEvent) => void;\n }\n ): React.ReactNode => {\n const titleValue = getValueInPath(item as Record<string, unknown>, titlePropertyKey);\n const imageValue = imageProperty\n ? getValueInPath(item as Record<string, unknown>, imageProperty)\n : undefined;\n const hasImage = typeof imageValue === \"string\" && imageValue.length > 0;\n\n return (\n <Card\n onClick={(e) => { if (e) extra.onClick(e); }}\n className={cls(\n \"overflow-hidden relative\",\n extra.selected && cardSelectedMixin,\n extra.highlighted && !extra.selected && \"ring-1 ring-primary/50\"\n )}\n >\n {/* Image */}\n {imageProperty && (\n hasImage ? (\n <img\n src={imageValue as string}\n alt=\"\"\n className=\"w-full h-32 object-cover rounded-t-lg\"\n />\n ) : (\n <div className={cls(\"h-24 rounded-t-lg\", fieldBackgroundMixin)} />\n )\n )}\n\n {/* Selection checkbox */}\n {selectionEnabled && (\n <div\n className=\"absolute top-2 right-2\"\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox\n aria-label={labels.select}\n checked={extra.selected}\n onCheckedChange={extra.onSelectionChange}\n size=\"small\"\n />\n </div>\n )}\n\n {/* Title */}\n <Typography variant=\"subtitle2\" noWrap className=\"px-3 pt-2 pb-1\">\n {String(titleValue ?? \"\")}\n </Typography>\n\n {/* Preview properties */}\n {previewProperties.map(({ key, property }) => {\n const value = getValueInPath(item as Record<string, unknown>, key);\n\n if (cellRenderer) {\n const custom = cellRenderer({\n row: item,\n propertyKey: key,\n property,\n value,\n size: \"small\",\n });\n if (custom !== undefined) {\n return (\n <div key={key} className=\"px-3 py-0.5\">\n {custom}\n </div>\n );\n }\n }\n\n return (\n <div key={key} className=\"px-3 py-0.5\">\n <DefaultCellRenderer\n row={item}\n propertyKey={key}\n property={property}\n value={value}\n size=\"small\"\n />\n </div>\n );\n })}\n\n <div className=\"pb-2\" />\n </Card>\n );\n },\n [titlePropertyKey, imageProperty, previewProperties, cellRenderer, selectionEnabled, labels]\n );\n\n return (\n <div className={cls(\"w-full h-full overflow-auto\", className)}>\n <CardView<T>\n data={dataController.data}\n dataLoading={dataController.loading}\n noMoreToLoad={dataController.noMoreToLoad}\n dataLoadingError={dataController.error}\n itemCount={dataController.itemCount}\n setItemCount={dataController.setItemCount}\n pageSize={dataController.pageSize}\n paginationEnabled={dataController.paginationEnabled}\n onItemClick={onRowClick}\n selectedIds={selectedIds}\n highlightedIds={highlightedIds}\n selectionEnabled={!!selectionEnabled}\n onSelectionChange={handleSelectionChange}\n size={size}\n renderCard={renderCard}\n emptyComponent={emptyComponent}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CollectionCardView.es.js","names":[],"sources":["../../../src/views/CollectionView/CollectionCardView.tsx"],"sourcesContent":["\"use client\";\nimport React, { useCallback, useMemo } from \"react\";\nimport { CardView } from \"../CardView\";\nimport { Card } from \"../../components/Card\";\nimport { Typography } from \"../../components/Typography\";\nimport { Checkbox } from \"../../components/Checkbox\";\nimport { DefaultCellRenderer } from \"./DefaultCellRenderer\";\nimport { cls } from \"../../util\";\nimport { useUiLabels } from \"../../hooks/UiLabelsContext\";\nimport { cardSelectedMixin, fieldBackgroundMixin } from \"../../styles\";\nimport type {\n CollectionPropertyConfig,\n CollectionDataController,\n CellRendererOverride,\n CollectionViewSize,\n CollectionSelectionController,\n} from \"./CollectionViewTypes\";\nimport { resolveTitlePropertyKey } from \"./resolveTitleProperty\";\n\nimport { getValueInPath } from \"./utils\";\n\n/**\n * Props for the {@link CollectionCardView} component.\n * @group Collection View\n */\nexport type CollectionCardViewProps<T = Record<string, unknown>> = {\n dataController: CollectionDataController<T>;\n properties: Record<string, CollectionPropertyConfig>;\n propertiesOrder?: string[];\n idProperty?: string;\n titleProperty?: string;\n onRowClick?: (row: T) => void;\n cellRenderer?: CellRendererOverride<T>;\n size?: CollectionViewSize;\n selectionEnabled?: boolean;\n selectionController?: CollectionSelectionController<T>;\n highlightedItems?: T[];\n emptyComponent?: React.ReactNode;\n className?: string;\n};\n\n/**\n * Headless card grid view that wraps {@link CardView}.\n *\n * Renders each item as a card with an optional image, title,\n * preview properties, and selection support.\n *\n * @group Collection View\n */\nexport function CollectionCardView<T extends Record<string, unknown> = Record<string, unknown>>({\n dataController,\n properties,\n propertiesOrder,\n idProperty = \"id\",\n titleProperty,\n onRowClick,\n cellRenderer,\n size = \"m\",\n selectionEnabled,\n selectionController,\n highlightedItems,\n emptyComponent,\n className,\n}: CollectionCardViewProps<T>) {\n const labels = useUiLabels();\n const order = useMemo(\n () => propertiesOrder ?? Object.keys(properties),\n [propertiesOrder, properties]\n );\n\n // ── Resolve title property ───────────────────────────────────────\n const titlePropertyKey = useMemo(\n () => resolveTitlePropertyKey(order, properties, idProperty, titleProperty),\n [titleProperty, order, properties, idProperty]\n );\n\n // ── Resolve image property ───────────────────────────────────────\n const imageProperty = useMemo(() => {\n for (const key of order) {\n const prop = properties[key];\n if (prop && prop.type === \"string\" && prop.storage) {\n return key;\n }\n }\n return undefined;\n }, [order, properties]);\n\n // ── Preview properties (up to 3) ─────────────────────────────────\n const previewProperties = useMemo(() => {\n const result: { key: string; property: CollectionPropertyConfig }[] = [];\n for (const key of order) {\n if (key === titlePropertyKey || key === imageProperty) continue;\n const prop = properties[key];\n if (prop && !prop.hideFromCollection) {\n result.push({ key, property: prop });\n if (result.length >= 3) break;\n }\n }\n return result;\n }, [order, properties, titlePropertyKey, imageProperty]);\n\n // ── Selection IDs ────────────────────────────────────────────────\n const selectedIds = useMemo(() => {\n if (!selectionController?.selectedItems) return undefined;\n const ids = new Set<string | number>();\n for (const item of selectionController.selectedItems) {\n const id = getValueInPath(item as Record<string, unknown>, idProperty);\n if (id !== undefined && id !== null) {\n ids.add(id as string | number);\n }\n }\n return ids;\n }, [selectionController?.selectedItems, idProperty]);\n\n // ── Highlighted IDs ──────────────────────────────────────────────\n const highlightedIds = useMemo(() => {\n if (!highlightedItems) return undefined;\n const ids = new Set<string | number>();\n for (const item of highlightedItems) {\n const id = getValueInPath(item as Record<string, unknown>, idProperty);\n if (id !== undefined && id !== null) {\n ids.add(id as string | number);\n }\n }\n return ids;\n }, [highlightedItems, idProperty]);\n\n // ── Selection handler ────────────────────────────────────────────\n const handleSelectionChange = useCallback(\n (item: T, selected: boolean) => {\n if (!selectionController) return;\n selectionController.setSelectedItems((prev) => {\n if (selected) {\n return [...prev, item];\n }\n const itemId = getValueInPath(item as Record<string, unknown>, idProperty);\n return prev.filter((existing) => {\n const existingId = getValueInPath(existing as Record<string, unknown>, idProperty);\n return existingId !== itemId;\n });\n });\n },\n [selectionController, idProperty]\n );\n\n // ── Render card ──────────────────────────────────────────────────\n const renderCard = useCallback(\n (\n item: T,\n extra: {\n selected: boolean;\n highlighted: boolean;\n onSelectionChange: (selected: boolean) => void;\n onClick: (e: React.MouseEvent) => void;\n }\n ): React.ReactNode => {\n const titleValue = getValueInPath(item as Record<string, unknown>, titlePropertyKey);\n const imageValue = imageProperty\n ? getValueInPath(item as Record<string, unknown>, imageProperty)\n : undefined;\n const hasImage = typeof imageValue === \"string\" && imageValue.length > 0;\n\n return (\n <Card\n onClick={(e) => { if (e) extra.onClick(e); }}\n className={cls(\n \"overflow-hidden relative\",\n extra.selected && cardSelectedMixin,\n extra.highlighted && !extra.selected && \"ring-1 ring-primary/50\"\n )}\n >\n {/* Image */}\n {imageProperty && (\n hasImage ? (\n <img\n src={imageValue as string}\n alt=\"\"\n className=\"w-full h-32 object-cover rounded-t-lg\"\n />\n ) : (\n <div className={cls(\"h-24 rounded-t-lg\", fieldBackgroundMixin)} />\n )\n )}\n\n {/* Selection checkbox */}\n {selectionEnabled && (\n <div\n className=\"absolute top-2 right-2\"\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox\n aria-label={labels.select}\n checked={extra.selected}\n onCheckedChange={extra.onSelectionChange}\n size=\"small\"\n />\n </div>\n )}\n\n {/* Title */}\n <Typography variant=\"subtitle2\" noWrap className=\"px-3 pt-2 pb-1\">\n {String(titleValue ?? \"\")}\n </Typography>\n\n {/* Preview properties */}\n {previewProperties.map(({ key, property }) => {\n const value = getValueInPath(item as Record<string, unknown>, key);\n\n if (cellRenderer) {\n const custom = cellRenderer({\n row: item,\n propertyKey: key,\n property,\n value,\n size: \"small\",\n });\n if (custom !== undefined) {\n return (\n <div key={key} className=\"px-3 py-0.5\">\n {custom}\n </div>\n );\n }\n }\n\n return (\n <div key={key} className=\"px-3 py-0.5\">\n <DefaultCellRenderer\n row={item}\n propertyKey={key}\n property={property}\n value={value}\n size=\"small\"\n />\n </div>\n );\n })}\n\n <div className=\"pb-2\" />\n </Card>\n );\n },\n [titlePropertyKey, imageProperty, previewProperties, cellRenderer, selectionEnabled, labels]\n );\n\n return (\n <div className={cls(\"w-full h-full overflow-auto\", className)}>\n <CardView<T>\n data={dataController.data}\n dataLoading={dataController.loading}\n noMoreToLoad={dataController.noMoreToLoad}\n dataLoadingError={dataController.error}\n itemCount={dataController.itemCount}\n setItemCount={dataController.setItemCount}\n pageSize={dataController.pageSize}\n paginationEnabled={dataController.paginationEnabled}\n onItemClick={onRowClick}\n selectedIds={selectedIds}\n highlightedIds={highlightedIds}\n selectionEnabled={!!selectionEnabled}\n onSelectionChange={handleSelectionChange}\n size={size}\n renderCard={renderCard}\n emptyComponent={emptyComponent}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|