@bitrise/bitkit-v2 0.3.469 → 0.3.470
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/BitkitCombobox/BitkitCombobox.d.ts +3 -3
- package/dist/components/BitkitCombobox/BitkitCombobox.js +6 -2
- package/dist/components/BitkitCombobox/BitkitCombobox.js.map +1 -1
- package/dist/components/BitkitMultiselect/BitkitMultiselect.d.ts +3 -3
- package/dist/components/BitkitMultiselect/BitkitMultiselect.js +4 -1
- package/dist/components/BitkitMultiselect/BitkitMultiselect.js.map +1 -1
- package/dist/components/BitkitMultiselectMenu/BitkitMultiselectMenu.d.ts +3 -3
- package/dist/components/BitkitMultiselectMenu/BitkitMultiselectMenu.js.map +1 -1
- package/dist/components/BitkitSelect/BitkitSelect.d.ts +3 -3
- package/dist/components/BitkitSelect/BitkitSelect.js +4 -1
- package/dist/components/BitkitSelect/BitkitSelect.js.map +1 -1
- package/dist/components/BitkitSelectMenu/BitkitSelectMenu.d.ts +7 -2
- package/dist/components/BitkitSelectMenu/BitkitSelectMenu.js.map +1 -1
- package/dist/components/BitkitSelectMenu/SelectMenuShell.d.ts +3 -3
- package/dist/components/BitkitSelectMenu/SelectMenuShell.js +59 -29
- package/dist/components/BitkitSelectMenu/SelectMenuShell.js.map +1 -1
- package/dist/components/BitkitUISideNav/BitkitUISideNav.d.ts +38 -0
- package/dist/components/BitkitUISideNav/BitkitUISideNav.js +167 -0
- package/dist/components/BitkitUISideNav/BitkitUISideNav.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavBody.d.ts +7 -0
- package/dist/components/BitkitUISideNav/UISideNavBody.js +20 -0
- package/dist/components/BitkitUISideNav/UISideNavBody.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavContext.d.ts +32 -0
- package/dist/components/BitkitUISideNav/UISideNavContext.js +28 -0
- package/dist/components/BitkitUISideNav/UISideNavContext.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavFooter.d.ts +16 -0
- package/dist/components/BitkitUISideNav/UISideNavFooter.js +28 -0
- package/dist/components/BitkitUISideNav/UISideNavFooter.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavFooterHeading.d.ts +12 -0
- package/dist/components/BitkitUISideNav/UISideNavFooterHeading.js +26 -0
- package/dist/components/BitkitUISideNav/UISideNavFooterHeading.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavItem.d.ts +26 -0
- package/dist/components/BitkitUISideNav/UISideNavItem.js +33 -0
- package/dist/components/BitkitUISideNav/UISideNavItem.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavLink.d.ts +30 -0
- package/dist/components/BitkitUISideNav/UISideNavLink.js +58 -0
- package/dist/components/BitkitUISideNav/UISideNavLink.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavWorkspaceSelector.d.ts +55 -0
- package/dist/components/BitkitUISideNav/UISideNavWorkspaceSelector.js +127 -0
- package/dist/components/BitkitUISideNav/UISideNavWorkspaceSelector.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/main.js +4 -1
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.d.ts +14 -2
- package/dist/theme/slot-recipes/Select.recipe.d.ts +23 -5
- package/dist/theme/slot-recipes/Select.recipe.js +37 -3
- package/dist/theme/slot-recipes/Select.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/UISideNav.recipe.d.ts +119 -0
- package/dist/theme/slot-recipes/UISideNav.recipe.js +416 -0
- package/dist/theme/slot-recipes/UISideNav.recipe.js.map +1 -0
- package/dist/theme/slot-recipes/index.d.ts +150 -7
- package/dist/theme/slot-recipes/index.js +3 -1
- package/dist/theme/slot-recipes/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComboboxRootProps } from '@chakra-ui/react/combobox';
|
|
2
2
|
import { BitkitFieldProps } from '../BitkitField/BitkitField';
|
|
3
|
-
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuItemProps, BitkitSelectMenuScrollProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
3
|
+
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuErrorStateProps, BitkitSelectMenuItemProps, BitkitSelectMenuScrollProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
4
4
|
import { BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';
|
|
5
5
|
export type BitkitComboboxProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {
|
|
6
6
|
/**
|
|
@@ -28,7 +28,7 @@ export type BitkitComboboxProps = Omit<BitkitFieldProps, 'children' | 'onChange'
|
|
|
28
28
|
size?: 'md' | 'lg';
|
|
29
29
|
state?: 'disabled' | 'error' | 'readOnly' | 'warning';
|
|
30
30
|
value?: string;
|
|
31
|
-
} & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
31
|
+
} & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
32
32
|
declare const _default: import('react').ForwardRefExoticComponent<Omit<BitkitFieldProps, "children" | "onChange" | "state"> & {
|
|
33
33
|
/**
|
|
34
34
|
* Accept text that matches no option. `onValueChange` then fires with the typed text when the
|
|
@@ -55,7 +55,7 @@ declare const _default: import('react').ForwardRefExoticComponent<Omit<BitkitFie
|
|
|
55
55
|
size?: "md" | "lg";
|
|
56
56
|
state?: "disabled" | "error" | "readOnly" | "warning";
|
|
57
57
|
value?: string;
|
|
58
|
-
} & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
58
|
+
} & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
59
59
|
Action: {
|
|
60
60
|
(props: import('..').BitkitSelectMenuActionProps): import('react').JSX.Element;
|
|
61
61
|
displayName: string;
|
|
@@ -22,7 +22,7 @@ import { useFilter } from "@chakra-ui/react/locale";
|
|
|
22
22
|
* Pass `onSearchChange` to do the filtering yourself, for example against a server-side search.
|
|
23
23
|
*/
|
|
24
24
|
var BitkitCombobox = forwardRef((props, ref) => {
|
|
25
|
-
const { allowCustomValue, children, comboboxProps, defaultValue, disablePortal, emptyHelperText, emptyLabel, isLoading, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder = "Enter value or select", size = "lg", state, value, ...fieldProps } = props;
|
|
25
|
+
const { allowCustomValue, children, comboboxProps, defaultValue, disablePortal, emptyHelperText, emptyLabel, errorHelperText, errorLabel, isLoading, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder = "Enter value or select", size = "lg", state, value, ...fieldProps } = props;
|
|
26
26
|
return /* @__PURE__ */ jsx(BitkitField, {
|
|
27
27
|
ref,
|
|
28
28
|
state,
|
|
@@ -34,6 +34,8 @@ var BitkitCombobox = forwardRef((props, ref) => {
|
|
|
34
34
|
disablePortal,
|
|
35
35
|
emptyHelperText: emptyHelperText ?? (allowCustomValue ? "Enter custom value." : void 0),
|
|
36
36
|
emptyLabel,
|
|
37
|
+
errorHelperText,
|
|
38
|
+
errorLabel,
|
|
37
39
|
hasWarning: state === "warning" || !!fieldProps.warningText,
|
|
38
40
|
isInvalid: state === "error" || !!fieldProps.errorText,
|
|
39
41
|
isLoading,
|
|
@@ -51,7 +53,7 @@ var BitkitCombobox = forwardRef((props, ref) => {
|
|
|
51
53
|
});
|
|
52
54
|
BitkitCombobox.displayName = "BitkitCombobox";
|
|
53
55
|
/** Rendered inside BitkitField so it can read the field's ids and wire them to the input. */
|
|
54
|
-
var ComboboxControl = ({ allowCustomValue, children, comboboxProps, defaultValue, disablePortal, emptyHelperText, emptyLabel, hasWarning, isInvalid, isLoading, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, size, state, value }) => {
|
|
56
|
+
var ComboboxControl = ({ allowCustomValue, children, comboboxProps, defaultValue, disablePortal, emptyHelperText, emptyLabel, errorHelperText, errorLabel, hasWarning, isInvalid, isLoading, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, size, state, value }) => {
|
|
55
57
|
const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);
|
|
56
58
|
const field = useFieldContext();
|
|
57
59
|
const { contains } = useFilter({ sensitivity: "base" });
|
|
@@ -200,6 +202,8 @@ var ComboboxControl = ({ allowCustomValue, children, comboboxProps, defaultValue
|
|
|
200
202
|
collection,
|
|
201
203
|
emptyHelperText,
|
|
202
204
|
emptyLabel,
|
|
205
|
+
errorHelperText,
|
|
206
|
+
errorLabel,
|
|
203
207
|
isLoading,
|
|
204
208
|
onScrolledToBottom,
|
|
205
209
|
size,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitCombobox.js","names":[],"sources":["../../../lib/components/BitkitCombobox/BitkitCombobox.tsx"],"sourcesContent":["import { createListCollection } from '@chakra-ui/react/collection';\nimport { Combobox, type ComboboxRootProps } from '@chakra-ui/react/combobox';\nimport { useFieldContext } from '@chakra-ui/react/field';\nimport { useFilter } from '@chakra-ui/react/locale';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { forwardRef, useContext, useEffect, useRef, useState } from 'react';\n\nimport IconErrorCircleFilled from '../../icons/IconErrorCircleFilled';\nimport IconWarningYellow from '../../icons/IconWarningYellow';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\nimport BitkitSelectMenu, {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuItemProps,\n type BitkitSelectMenuScrollProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport BitkitSelectMenuAction, { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\n\nexport type BitkitComboboxProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n /**\n * Accept text that matches no option. `onValueChange` then fires with the typed text when the\n * user commits it (closing the menu or leaving the field), and with the option's `value` when\n * they pick one from the list.\n */\n allowCustomValue?: boolean;\n children?: BitkitSelectMenuActionChild;\n comboboxProps?: Omit<\n ComboboxRootProps,\n | 'allowCustomValue'\n | 'collection'\n | 'defaultInputValue'\n | 'defaultValue'\n | 'ids'\n | 'inputValue'\n | 'onInputValueChange'\n | 'onOpenChange'\n | 'onValueChange'\n | 'open'\n | 'openOnClick'\n | 'value'\n >;\n defaultValue?: string;\n disablePortal?: boolean;\n isLoading?: boolean;\n items: Array<BitkitSelectMenuItemProps>;\n /**\n * Fires with the text the user types, and with `''` once the input is cleared or filled in from a\n * selection. When provided, the combobox stops filtering `items` itself: pass only the options\n * that match, for example the results of a server-side search. Keep the selected option in\n * `items`, since its label is looked up there. With `allowCustomValue`, text committed before its\n * results arrive is matched only against the current `items`.\n */\n onSearchChange?: (searchText: string) => void;\n onValueChange?: (newVal: string | undefined) => void;\n placeholder?: string;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n value?: string;\n} & BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\n/**\n * A text input that filters a list of options as you type. The menu opens on focus with every\n * option, narrows to the ones matching the input, and shows an empty state when nothing matches.\n * Unlike BitkitSelect's menu it has no separate search field — the input itself is the filter.\n * Pass `onSearchChange` to do the filtering yourself, for example against a server-side search.\n */\nconst BitkitCombobox = forwardRef<HTMLDivElement, BitkitComboboxProps>((props: BitkitComboboxProps, ref) => {\n const {\n allowCustomValue,\n children,\n comboboxProps,\n defaultValue,\n disablePortal,\n emptyHelperText,\n emptyLabel,\n isLoading,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder = 'Enter value or select',\n size = 'lg',\n state,\n value,\n ...fieldProps\n } = props;\n\n return (\n <BitkitField ref={ref} state={state} {...fieldProps}>\n <ComboboxControl\n allowCustomValue={allowCustomValue}\n comboboxProps={comboboxProps}\n defaultValue={defaultValue}\n disablePortal={disablePortal}\n emptyHelperText={emptyHelperText ?? (allowCustomValue ? 'Enter custom value.' : undefined)}\n emptyLabel={emptyLabel}\n hasWarning={state === 'warning' || !!fieldProps.warningText}\n isInvalid={state === 'error' || !!fieldProps.errorText}\n isLoading={isLoading}\n items={items}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n onValueChange={onValueChange}\n placeholder={placeholder}\n size={size}\n state={state}\n value={value}\n >\n {children}\n </ComboboxControl>\n </BitkitField>\n );\n});\n\nBitkitCombobox.displayName = 'BitkitCombobox';\n\ntype ComboboxControlProps = Pick<\n BitkitComboboxProps,\n | 'allowCustomValue'\n | 'children'\n | 'comboboxProps'\n | 'defaultValue'\n | 'disablePortal'\n | 'emptyHelperText'\n | 'emptyLabel'\n | 'isLoading'\n | 'items'\n | 'onScrolledToBottom'\n | 'onSearchChange'\n | 'onValueChange'\n | 'state'\n | 'value'\n> &\n Required<Pick<BitkitComboboxProps, 'placeholder' | 'size'>> & {\n hasWarning: boolean;\n isInvalid: boolean;\n };\n\n/** Rendered inside BitkitField so it can read the field's ids and wire them to the input. */\nconst ComboboxControl = ({\n allowCustomValue,\n children,\n comboboxProps,\n defaultValue,\n disablePortal,\n emptyHelperText,\n emptyLabel,\n hasWarning,\n isInvalid,\n isLoading,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n size,\n state,\n value,\n}: ComboboxControlProps) => {\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const field = useFieldContext();\n\n const { contains } = useFilter({ sensitivity: 'base' });\n\n // Derive the collection on every render rather than using useListCollection, which snapshots\n // `items` into state as `initialItems` and never syncs it again, so changing `items` actually reaches the menu.\n // Deriving also keeps callers that pass an inline array literal safe, since nothing here is keyed on items identity.\n const [filterText, setFilterText] = useState('');\n const filtersItself = !onSearchChange;\n const visibleItems = filterText && filtersItself ? items.filter((item) => contains(item.label, filterText)) : items;\n // The menu hides the options behind its spinner while loading, so disable them too: otherwise arrow\n // keys and Enter can still pick one the user never saw.\n const collection = createListCollection<BitkitSelectMenuItemProps>({\n items: visibleItems,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!isLoading || !!item.disabled,\n });\n\n // A new set of items invalidates whatever the old ones were filtered by — otherwise leftover\n // filter text can hide every new item and leave the menu empty. Keyed on item values, not array\n // identity, so an inline `items={[…]}` literal doesn't wipe the filter on every keystroke; an\n // append (the next page of results) keeps it, so paging doesn't drop what the user typed.\n const itemValues = items.map((item) => item.value);\n const itemsKey = JSON.stringify(itemValues);\n const [prevItemsKey, setPrevItemsKey] = useState(itemsKey);\n if (prevItemsKey !== itemsKey) {\n const prevValues = JSON.parse(prevItemsKey) as Array<string>;\n const isAppend = itemValues.length > prevValues.length && prevValues.every((v, i) => v === itemValues[i]);\n setPrevItemsKey(itemsKey);\n if (!isAppend && filtersItself) {\n setFilterText('');\n }\n }\n\n // Zag only reports the input text once it changes, so mirror what it starts with — otherwise the\n // clear button stays hidden next to a pre-selected value.\n const [initialInputValue] = useState(() => {\n const initialValue = value ?? defaultValue;\n if (!initialValue) return '';\n return items.find((item) => item.value === initialValue)?.label ?? (allowCustomValue ? initialValue : '');\n });\n const [inputValue, setInputValue] = useState(initialInputValue);\n const [isOpen, setIsOpen] = useState(false);\n // A controlled `value` that no option carries can only live in the input, so follow it there.\n const [prevValue, setPrevValue] = useState(value);\n if (value !== prevValue) {\n setPrevValue(value);\n if (allowCustomValue) {\n setInputValue(value ? (items.find((item) => item.value === value)?.label ?? value) : '');\n setFilterText('');\n }\n }\n const lastEmittedValue = useRef<string | null>(null);\n const lastReportedSearch = useRef('');\n const reportSearch = (searchText: string) => {\n if (searchText === lastReportedSearch.current) return;\n lastReportedSearch.current = searchText;\n onSearchChange?.(searchText);\n };\n // The input handlers report right away, so the consumer's results follow the input in the same\n // render. This catches what changes the search during render instead, like a controlled reset.\n useEffect(() => {\n reportSearch(filterText);\n });\n // Clicking the chevron focuses the input first, so without this the focus handler would open the\n // menu just before the trigger's own click closes it again.\n const skipFocusOpen = useRef(false);\n\n const isInteractive = state !== 'disabled' && state !== 'readOnly';\n\n const commitCustomValue = () => {\n const text = inputValue.trim();\n if (!allowCustomValue || !text) return;\n // Match the filter's case-insensitivity: typing \"react\" means the React option, not a custom\n // value that happens to read the same.\n const match = items.find((item) => item.label.toLowerCase() === text.toLowerCase());\n const committed = match ? match.value : text;\n if (lastEmittedValue.current === committed) return;\n lastEmittedValue.current = committed;\n onValueChange?.(committed);\n };\n\n const iconSize = size === 'md' ? '16' : '24';\n\n let statusIcon;\n if (isInvalid) {\n statusIcon = <IconErrorCircleFilled size={iconSize} color=\"icon/negative\" />;\n } else if (hasWarning) {\n statusIcon = <IconWarningYellow size={iconSize} />;\n }\n\n return (\n <Combobox.Root\n {...comboboxProps}\n allowCustomValue={allowCustomValue}\n collection={collection}\n defaultInputValue={initialInputValue}\n defaultValue={defaultValue ? [defaultValue] : undefined}\n disabled={state === 'disabled'}\n hasStatusIcon={!!statusIcon}\n // Chakra's Combobox doesn't take its input id from the surrounding Field, so the field\n // label's `htmlFor` would point at an element that doesn't exist. Hand it the field's ids\n // instead, and the label both labels and focuses the input.\n ids={field ? { input: field.ids.control, label: field.ids.label } : undefined}\n invalid={isInvalid}\n // Custom values live only in the input: Zag rewrites it from the collection on every value\n // change, which would blank text no option carries, so we own it and keep Zag off it.\n inputValue={allowCustomValue ? inputValue : undefined}\n onInputValueChange={({ inputValue: nextInputValue, reason }) => {\n setInputValue(nextInputValue);\n // The clear button empties `value` too, but a custom value never reached it, so Zag has\n // nothing to report — tell the consumer the field is empty now.\n if (reason === 'clear-trigger' && lastEmittedValue.current !== null) {\n lastEmittedValue.current = null;\n onValueChange?.(undefined);\n }\n // Only what the user types filters the list. Zag also writes the selected item's label\n // into the input, which would otherwise narrow the menu to that one item.\n const nextFilterText = reason === 'input-change' ? nextInputValue : '';\n setFilterText(nextFilterText);\n reportSearch(nextFilterText);\n }}\n onOpenChange={({ open }) => {\n setIsOpen(open);\n if (!open) commitCustomValue();\n }}\n onValueChange={(details) => {\n const next = details.value[0];\n // `selectionBehavior=\"preserve\"` leaves the input alone, so put the option's label there and\n // end the search, as Zag's own input rewrite does without it.\n if (allowCustomValue) {\n setInputValue(next ? (items.find((item) => item.value === next)?.label ?? next) : '');\n setFilterText('');\n reportSearch('');\n }\n // The clear path above already reported the empty field.\n if (next === undefined && lastEmittedValue.current === null) return;\n lastEmittedValue.current = next ?? null;\n onValueChange?.(next);\n }}\n open={isOpen}\n openOnClick\n readOnly={state === 'readOnly'}\n // Bypass Zag's isScrollable(contentEl) gate — our Content is overflow:hidden flex\n // column, so the real scroll container is itemList. See BitkitMultiselect for why.\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n selectionBehavior={allowCustomValue ? 'preserve' : undefined}\n size={size}\n // Stay controlled whenever `value` is provided — including `''` (\"nothing selected\"). A\n // truthiness check (`value ? … : undefined`) passes `undefined` for `''`, which flips\n // Combobox.Root to uncontrolled: it then ignores `value`, so resets don't take and\n // onValueChange stops firing when the same option is re-selected.\n value={value === undefined ? undefined : value ? [value] : []}\n >\n <Combobox.Control className=\"group\">\n <Combobox.Input\n onBlur={commitCustomValue}\n onKeyDown={(event) => {\n // Zag's Enter selects the highlighted item and does nothing when there is none, so\n // free text would just sit in an open menu. Commit it and close.\n if (!allowCustomValue || event.key !== 'Enter') return;\n if (event.currentTarget.getAttribute('aria-activedescendant')) return;\n commitCustomValue();\n setIsOpen(false);\n }}\n onFocus={() => {\n if (skipFocusOpen.current || !isInteractive) return;\n setIsOpen(true);\n }}\n placeholder={placeholder}\n />\n <Combobox.IndicatorGroup>\n {/* Zag hides the clear button until an option is selected; the design shows it for\n anything the field holds, including text that matches nothing yet. */}\n <Combobox.ClearTrigger asChild hidden={!isInteractive || (!inputValue && !value)}>\n <BitkitCloseButton size=\"sm\" />\n </Combobox.ClearTrigger>\n {statusIcon}\n <Combobox.Trigger\n asChild\n onPointerDown={() => {\n skipFocusOpen.current = true;\n requestAnimationFrame(() => {\n skipFocusOpen.current = false;\n });\n }}\n >\n <AssetSelectChevron />\n </Combobox.Trigger>\n </Combobox.IndicatorGroup>\n </Combobox.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Combobox.Positioner>\n <BitkitSelectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n size={size}\n variant=\"combobox\"\n >\n {children}\n </BitkitSelectMenu>\n </Combobox.Positioner>\n </Portal>\n </Combobox.Root>\n );\n};\n\nexport default withSubComponents(BitkitCombobox, { Action: BitkitSelectMenuAction });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BitkitCombobox.js","names":[],"sources":["../../../lib/components/BitkitCombobox/BitkitCombobox.tsx"],"sourcesContent":["import { createListCollection } from '@chakra-ui/react/collection';\nimport { Combobox, type ComboboxRootProps } from '@chakra-ui/react/combobox';\nimport { useFieldContext } from '@chakra-ui/react/field';\nimport { useFilter } from '@chakra-ui/react/locale';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { forwardRef, useContext, useEffect, useRef, useState } from 'react';\n\nimport IconErrorCircleFilled from '../../icons/IconErrorCircleFilled';\nimport IconWarningYellow from '../../icons/IconWarningYellow';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\nimport BitkitSelectMenu, {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuErrorStateProps,\n type BitkitSelectMenuItemProps,\n type BitkitSelectMenuScrollProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport BitkitSelectMenuAction, { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\n\nexport type BitkitComboboxProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n /**\n * Accept text that matches no option. `onValueChange` then fires with the typed text when the\n * user commits it (closing the menu or leaving the field), and with the option's `value` when\n * they pick one from the list.\n */\n allowCustomValue?: boolean;\n children?: BitkitSelectMenuActionChild;\n comboboxProps?: Omit<\n ComboboxRootProps,\n | 'allowCustomValue'\n | 'collection'\n | 'defaultInputValue'\n | 'defaultValue'\n | 'ids'\n | 'inputValue'\n | 'onInputValueChange'\n | 'onOpenChange'\n | 'onValueChange'\n | 'open'\n | 'openOnClick'\n | 'value'\n >;\n defaultValue?: string;\n disablePortal?: boolean;\n isLoading?: boolean;\n items: Array<BitkitSelectMenuItemProps>;\n /**\n * Fires with the text the user types, and with `''` once the input is cleared or filled in from a\n * selection. When provided, the combobox stops filtering `items` itself: pass only the options\n * that match, for example the results of a server-side search. Keep the selected option in\n * `items`, since its label is looked up there. With `allowCustomValue`, text committed before its\n * results arrive is matched only against the current `items`.\n */\n onSearchChange?: (searchText: string) => void;\n onValueChange?: (newVal: string | undefined) => void;\n placeholder?: string;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n value?: string;\n} & BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\n/**\n * A text input that filters a list of options as you type. The menu opens on focus with every\n * option, narrows to the ones matching the input, and shows an empty state when nothing matches.\n * Unlike BitkitSelect's menu it has no separate search field — the input itself is the filter.\n * Pass `onSearchChange` to do the filtering yourself, for example against a server-side search.\n */\nconst BitkitCombobox = forwardRef<HTMLDivElement, BitkitComboboxProps>((props: BitkitComboboxProps, ref) => {\n const {\n allowCustomValue,\n children,\n comboboxProps,\n defaultValue,\n disablePortal,\n emptyHelperText,\n emptyLabel,\n errorHelperText,\n errorLabel,\n isLoading,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder = 'Enter value or select',\n size = 'lg',\n state,\n value,\n ...fieldProps\n } = props;\n\n return (\n <BitkitField ref={ref} state={state} {...fieldProps}>\n <ComboboxControl\n allowCustomValue={allowCustomValue}\n comboboxProps={comboboxProps}\n defaultValue={defaultValue}\n disablePortal={disablePortal}\n emptyHelperText={emptyHelperText ?? (allowCustomValue ? 'Enter custom value.' : undefined)}\n emptyLabel={emptyLabel}\n errorHelperText={errorHelperText}\n errorLabel={errorLabel}\n hasWarning={state === 'warning' || !!fieldProps.warningText}\n isInvalid={state === 'error' || !!fieldProps.errorText}\n isLoading={isLoading}\n items={items}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n onValueChange={onValueChange}\n placeholder={placeholder}\n size={size}\n state={state}\n value={value}\n >\n {children}\n </ComboboxControl>\n </BitkitField>\n );\n});\n\nBitkitCombobox.displayName = 'BitkitCombobox';\n\ntype ComboboxControlProps = Pick<\n BitkitComboboxProps,\n | 'allowCustomValue'\n | 'children'\n | 'comboboxProps'\n | 'defaultValue'\n | 'disablePortal'\n | 'emptyHelperText'\n | 'emptyLabel'\n | 'errorHelperText'\n | 'errorLabel'\n | 'isLoading'\n | 'items'\n | 'onScrolledToBottom'\n | 'onSearchChange'\n | 'onValueChange'\n | 'state'\n | 'value'\n> &\n Required<Pick<BitkitComboboxProps, 'placeholder' | 'size'>> & {\n hasWarning: boolean;\n isInvalid: boolean;\n };\n\n/** Rendered inside BitkitField so it can read the field's ids and wire them to the input. */\nconst ComboboxControl = ({\n allowCustomValue,\n children,\n comboboxProps,\n defaultValue,\n disablePortal,\n emptyHelperText,\n emptyLabel,\n errorHelperText,\n errorLabel,\n hasWarning,\n isInvalid,\n isLoading,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n size,\n state,\n value,\n}: ComboboxControlProps) => {\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const field = useFieldContext();\n\n const { contains } = useFilter({ sensitivity: 'base' });\n\n // Derive the collection on every render rather than using useListCollection, which snapshots\n // `items` into state as `initialItems` and never syncs it again, so changing `items` actually reaches the menu.\n // Deriving also keeps callers that pass an inline array literal safe, since nothing here is keyed on items identity.\n const [filterText, setFilterText] = useState('');\n const filtersItself = !onSearchChange;\n const visibleItems = filterText && filtersItself ? items.filter((item) => contains(item.label, filterText)) : items;\n // The menu hides the options behind its spinner while loading, so disable them too: otherwise arrow\n // keys and Enter can still pick one the user never saw.\n const collection = createListCollection<BitkitSelectMenuItemProps>({\n items: visibleItems,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!isLoading || !!item.disabled,\n });\n\n // A new set of items invalidates whatever the old ones were filtered by — otherwise leftover\n // filter text can hide every new item and leave the menu empty. Keyed on item values, not array\n // identity, so an inline `items={[…]}` literal doesn't wipe the filter on every keystroke; an\n // append (the next page of results) keeps it, so paging doesn't drop what the user typed.\n const itemValues = items.map((item) => item.value);\n const itemsKey = JSON.stringify(itemValues);\n const [prevItemsKey, setPrevItemsKey] = useState(itemsKey);\n if (prevItemsKey !== itemsKey) {\n const prevValues = JSON.parse(prevItemsKey) as Array<string>;\n const isAppend = itemValues.length > prevValues.length && prevValues.every((v, i) => v === itemValues[i]);\n setPrevItemsKey(itemsKey);\n if (!isAppend && filtersItself) {\n setFilterText('');\n }\n }\n\n // Zag only reports the input text once it changes, so mirror what it starts with — otherwise the\n // clear button stays hidden next to a pre-selected value.\n const [initialInputValue] = useState(() => {\n const initialValue = value ?? defaultValue;\n if (!initialValue) return '';\n return items.find((item) => item.value === initialValue)?.label ?? (allowCustomValue ? initialValue : '');\n });\n const [inputValue, setInputValue] = useState(initialInputValue);\n const [isOpen, setIsOpen] = useState(false);\n // A controlled `value` that no option carries can only live in the input, so follow it there.\n const [prevValue, setPrevValue] = useState(value);\n if (value !== prevValue) {\n setPrevValue(value);\n if (allowCustomValue) {\n setInputValue(value ? (items.find((item) => item.value === value)?.label ?? value) : '');\n setFilterText('');\n }\n }\n const lastEmittedValue = useRef<string | null>(null);\n const lastReportedSearch = useRef('');\n const reportSearch = (searchText: string) => {\n if (searchText === lastReportedSearch.current) return;\n lastReportedSearch.current = searchText;\n onSearchChange?.(searchText);\n };\n // The input handlers report right away, so the consumer's results follow the input in the same\n // render. This catches what changes the search during render instead, like a controlled reset.\n useEffect(() => {\n reportSearch(filterText);\n });\n // Clicking the chevron focuses the input first, so without this the focus handler would open the\n // menu just before the trigger's own click closes it again.\n const skipFocusOpen = useRef(false);\n\n const isInteractive = state !== 'disabled' && state !== 'readOnly';\n\n const commitCustomValue = () => {\n const text = inputValue.trim();\n if (!allowCustomValue || !text) return;\n // Match the filter's case-insensitivity: typing \"react\" means the React option, not a custom\n // value that happens to read the same.\n const match = items.find((item) => item.label.toLowerCase() === text.toLowerCase());\n const committed = match ? match.value : text;\n if (lastEmittedValue.current === committed) return;\n lastEmittedValue.current = committed;\n onValueChange?.(committed);\n };\n\n const iconSize = size === 'md' ? '16' : '24';\n\n let statusIcon;\n if (isInvalid) {\n statusIcon = <IconErrorCircleFilled size={iconSize} color=\"icon/negative\" />;\n } else if (hasWarning) {\n statusIcon = <IconWarningYellow size={iconSize} />;\n }\n\n return (\n <Combobox.Root\n {...comboboxProps}\n allowCustomValue={allowCustomValue}\n collection={collection}\n defaultInputValue={initialInputValue}\n defaultValue={defaultValue ? [defaultValue] : undefined}\n disabled={state === 'disabled'}\n hasStatusIcon={!!statusIcon}\n // Chakra's Combobox doesn't take its input id from the surrounding Field, so the field\n // label's `htmlFor` would point at an element that doesn't exist. Hand it the field's ids\n // instead, and the label both labels and focuses the input.\n ids={field ? { input: field.ids.control, label: field.ids.label } : undefined}\n invalid={isInvalid}\n // Custom values live only in the input: Zag rewrites it from the collection on every value\n // change, which would blank text no option carries, so we own it and keep Zag off it.\n inputValue={allowCustomValue ? inputValue : undefined}\n onInputValueChange={({ inputValue: nextInputValue, reason }) => {\n setInputValue(nextInputValue);\n // The clear button empties `value` too, but a custom value never reached it, so Zag has\n // nothing to report — tell the consumer the field is empty now.\n if (reason === 'clear-trigger' && lastEmittedValue.current !== null) {\n lastEmittedValue.current = null;\n onValueChange?.(undefined);\n }\n // Only what the user types filters the list. Zag also writes the selected item's label\n // into the input, which would otherwise narrow the menu to that one item.\n const nextFilterText = reason === 'input-change' ? nextInputValue : '';\n setFilterText(nextFilterText);\n reportSearch(nextFilterText);\n }}\n onOpenChange={({ open }) => {\n setIsOpen(open);\n if (!open) commitCustomValue();\n }}\n onValueChange={(details) => {\n const next = details.value[0];\n // `selectionBehavior=\"preserve\"` leaves the input alone, so put the option's label there and\n // end the search, as Zag's own input rewrite does without it.\n if (allowCustomValue) {\n setInputValue(next ? (items.find((item) => item.value === next)?.label ?? next) : '');\n setFilterText('');\n reportSearch('');\n }\n // The clear path above already reported the empty field.\n if (next === undefined && lastEmittedValue.current === null) return;\n lastEmittedValue.current = next ?? null;\n onValueChange?.(next);\n }}\n open={isOpen}\n openOnClick\n readOnly={state === 'readOnly'}\n // Bypass Zag's isScrollable(contentEl) gate — our Content is overflow:hidden flex\n // column, so the real scroll container is itemList. See BitkitMultiselect for why.\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n selectionBehavior={allowCustomValue ? 'preserve' : undefined}\n size={size}\n // Stay controlled whenever `value` is provided — including `''` (\"nothing selected\"). A\n // truthiness check (`value ? … : undefined`) passes `undefined` for `''`, which flips\n // Combobox.Root to uncontrolled: it then ignores `value`, so resets don't take and\n // onValueChange stops firing when the same option is re-selected.\n value={value === undefined ? undefined : value ? [value] : []}\n >\n <Combobox.Control className=\"group\">\n <Combobox.Input\n onBlur={commitCustomValue}\n onKeyDown={(event) => {\n // Zag's Enter selects the highlighted item and does nothing when there is none, so\n // free text would just sit in an open menu. Commit it and close.\n if (!allowCustomValue || event.key !== 'Enter') return;\n if (event.currentTarget.getAttribute('aria-activedescendant')) return;\n commitCustomValue();\n setIsOpen(false);\n }}\n onFocus={() => {\n if (skipFocusOpen.current || !isInteractive) return;\n setIsOpen(true);\n }}\n placeholder={placeholder}\n />\n <Combobox.IndicatorGroup>\n {/* Zag hides the clear button until an option is selected; the design shows it for\n anything the field holds, including text that matches nothing yet. */}\n <Combobox.ClearTrigger asChild hidden={!isInteractive || (!inputValue && !value)}>\n <BitkitCloseButton size=\"sm\" />\n </Combobox.ClearTrigger>\n {statusIcon}\n <Combobox.Trigger\n asChild\n onPointerDown={() => {\n skipFocusOpen.current = true;\n requestAnimationFrame(() => {\n skipFocusOpen.current = false;\n });\n }}\n >\n <AssetSelectChevron />\n </Combobox.Trigger>\n </Combobox.IndicatorGroup>\n </Combobox.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Combobox.Positioner>\n <BitkitSelectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n errorHelperText={errorHelperText}\n errorLabel={errorLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n size={size}\n variant=\"combobox\"\n >\n {children}\n </BitkitSelectMenu>\n </Combobox.Positioner>\n </Portal>\n </Combobox.Root>\n );\n};\n\nexport default withSubComponents(BitkitCombobox, { Action: BitkitSelectMenuAction });\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwEA,IAAM,iBAAiB,YAAiD,OAA4B,QAAQ;CAC1G,MAAM,EACJ,kBACA,UACA,eACA,cACA,eACA,iBACA,YACA,iBACA,YACA,WACA,OACA,oBACA,gBACA,eACA,cAAc,yBACd,OAAO,MACP,OACA,OACA,GAAG,eACD;CAEJ,OACE,oBAAC,aAAD;EAAkB;EAAY;EAAO,GAAI;EACvC,UAAA,oBAAC,iBAAD;GACoB;GACH;GACD;GACC;GACf,iBAAiB,oBAAoB,mBAAmB,wBAAwB,KAAA;GACpE;GACK;GACL;GACZ,YAAY,UAAU,aAAa,CAAC,CAAC,WAAW;GAChD,WAAW,UAAU,WAAW,CAAC,CAAC,WAAW;GAClC;GACJ;GACa;GACJ;GACD;GACF;GACP;GACC;GACA;GAEN;EACc,CAAA;CACN,CAAA;AAEjB,CAAC;AAED,eAAe,cAAc;;AA2B7B,IAAM,mBAAmB,EACvB,kBACA,UACA,eACA,cACA,eACA,iBACA,YACA,iBACA,YACA,YACA,WACA,WACA,OACA,oBACA,gBACA,eACA,aACA,MACA,OACA,YAC0B;CAC1B,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAClF,MAAM,QAAQ,gBAAgB;CAE9B,MAAM,EAAE,aAAa,UAAU,EAAE,aAAa,OAAO,CAAC;CAKtD,MAAM,CAAC,YAAY,iBAAiB,SAAS,EAAE;CAC/C,MAAM,gBAAgB,CAAC;CACvB,MAAM,eAAe,cAAc,gBAAgB,MAAM,QAAQ,SAAS,SAAS,KAAK,OAAO,UAAU,CAAC,IAAI;CAG9G,MAAM,aAAa,qBAAgD;EACjE,OAAO;EACP,UAAU,SAAS,KAAK,SAAS;EACjC,iBAAiB,SAAS,CAAC,CAAC,aAAa,CAAC,CAAC,KAAK;CAClD,CAAC;CAMD,MAAM,aAAa,MAAM,KAAK,SAAS,KAAK,KAAK;CACjD,MAAM,WAAW,KAAK,UAAU,UAAU;CAC1C,MAAM,CAAC,cAAc,mBAAmB,SAAS,QAAQ;CACzD,IAAI,iBAAiB,UAAU;EAC7B,MAAM,aAAa,KAAK,MAAM,YAAY;EAC1C,MAAM,WAAW,WAAW,SAAS,WAAW,UAAU,WAAW,OAAO,GAAG,MAAM,MAAM,WAAW,EAAE;EACxG,gBAAgB,QAAQ;EACxB,IAAI,CAAC,YAAY,eACf,cAAc,EAAE;CAEpB;CAIA,MAAM,CAAC,qBAAqB,eAAe;EACzC,MAAM,eAAe,SAAS;EAC9B,IAAI,CAAC,cAAc,OAAO;EAC1B,OAAO,MAAM,MAAM,SAAS,KAAK,UAAU,YAAY,CAAC,EAAE,UAAU,mBAAmB,eAAe;CACxG,CAAC;CACD,MAAM,CAAC,YAAY,iBAAiB,SAAS,iBAAiB;CAC9D,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAE1C,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,IAAI,UAAU,WAAW;EACvB,aAAa,KAAK;EAClB,IAAI,kBAAkB;GACpB,cAAc,QAAS,MAAM,MAAM,SAAS,KAAK,UAAU,KAAK,CAAC,EAAE,SAAS,QAAS,EAAE;GACvF,cAAc,EAAE;EAClB;CACF;CACA,MAAM,mBAAmB,OAAsB,IAAI;CACnD,MAAM,qBAAqB,OAAO,EAAE;CACpC,MAAM,gBAAgB,eAAuB;EAC3C,IAAI,eAAe,mBAAmB,SAAS;EAC/C,mBAAmB,UAAU;EAC7B,iBAAiB,UAAU;CAC7B;CAGA,gBAAgB;EACd,aAAa,UAAU;CACzB,CAAC;CAGD,MAAM,gBAAgB,OAAO,KAAK;CAElC,MAAM,gBAAgB,UAAU,cAAc,UAAU;CAExD,MAAM,0BAA0B;EAC9B,MAAM,OAAO,WAAW,KAAK;EAC7B,IAAI,CAAC,oBAAoB,CAAC,MAAM;EAGhC,MAAM,QAAQ,MAAM,MAAM,SAAS,KAAK,MAAM,YAAY,MAAM,KAAK,YAAY,CAAC;EAClF,MAAM,YAAY,QAAQ,MAAM,QAAQ;EACxC,IAAI,iBAAiB,YAAY,WAAW;EAC5C,iBAAiB,UAAU;EAC3B,gBAAgB,SAAS;CAC3B;CAEA,MAAM,WAAW,SAAS,OAAO,OAAO;CAExC,IAAI;CACJ,IAAI,WACF,aAAa,oBAAC,uBAAD;EAAuB,MAAM;EAAU,OAAM;CAAiB,CAAA;MACtE,IAAI,YACT,aAAa,oBAAC,mBAAD,EAAmB,MAAM,SAAW,CAAA;CAGnD,OACE,qBAAC,SAAS,MAAV;EACE,GAAI;EACc;EACN;EACZ,mBAAmB;EACnB,cAAc,eAAe,CAAC,YAAY,IAAI,KAAA;EAC9C,UAAU,UAAU;EACpB,eAAe,CAAC,CAAC;EAIjB,KAAK,QAAQ;GAAE,OAAO,MAAM,IAAI;GAAS,OAAO,MAAM,IAAI;EAAM,IAAI,KAAA;EACpE,SAAS;EAGT,YAAY,mBAAmB,aAAa,KAAA;EAC5C,qBAAqB,EAAE,YAAY,gBAAgB,aAAa;GAC9D,cAAc,cAAc;GAG5B,IAAI,WAAW,mBAAmB,iBAAiB,YAAY,MAAM;IACnE,iBAAiB,UAAU;IAC3B,gBAAgB,KAAA,CAAS;GAC3B;GAGA,MAAM,iBAAiB,WAAW,iBAAiB,iBAAiB;GACpE,cAAc,cAAc;GAC5B,aAAa,cAAc;EAC7B;EACA,eAAe,EAAE,WAAW;GAC1B,UAAU,IAAI;GACd,IAAI,CAAC,MAAM,kBAAkB;EAC/B;EACA,gBAAgB,YAAY;GAC1B,MAAM,OAAO,QAAQ,MAAM;GAG3B,IAAI,kBAAkB;IACpB,cAAc,OAAQ,MAAM,MAAM,SAAS,KAAK,UAAU,IAAI,CAAC,EAAE,SAAS,OAAQ,EAAE;IACpF,cAAc,EAAE;IAChB,aAAa,EAAE;GACjB;GAEA,IAAI,SAAS,KAAA,KAAa,iBAAiB,YAAY,MAAM;GAC7D,iBAAiB,UAAU,QAAQ;GACnC,gBAAgB,IAAI;EACtB;EACA,MAAM;EACN,aAAA;EACA,UAAU,UAAU;EAGpB,kBAAkB,EAAE,iBAAiB,WAAW,CAAC,EAAE,eAAe,EAAE,OAAO,UAAU,CAAC;EACtF,mBAAmB,mBAAmB,aAAa,KAAA;EAC7C;EAKN,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,QAAQ,CAAC,KAAK,IAAI,CAAC;EA5D9D,UAAA,CA8DE,qBAAC,SAAS,SAAV;GAAkB,WAAU;GAA5B,UAAA,CACE,oBAAC,SAAS,OAAV;IACE,QAAQ;IACR,YAAY,UAAU;KAGpB,IAAI,CAAC,oBAAoB,MAAM,QAAQ,SAAS;KAChD,IAAI,MAAM,cAAc,aAAa,uBAAuB,GAAG;KAC/D,kBAAkB;KAClB,UAAU,KAAK;IACjB;IACA,eAAe;KACb,IAAI,cAAc,WAAW,CAAC,eAAe;KAC7C,UAAU,IAAI;IAChB;IACa;GACd,CAAA,GACD,qBAAC,SAAS,gBAAV,EAAA,UAAA;IAGE,oBAAC,SAAS,cAAV;KAAuB,SAAA;KAAQ,QAAQ,CAAC,iBAAkB,CAAC,cAAc,CAAC;KACxE,UAAA,oBAAC,mBAAD,EAAmB,MAAK,KAAM,CAAA;IACT,CAAA;IACtB;IACD,oBAAC,SAAS,SAAV;KACE,SAAA;KACA,qBAAqB;MACnB,cAAc,UAAU;MACxB,4BAA4B;OAC1B,cAAc,UAAU;MAC1B,CAAC;KACH;KAEA,UAAA,oBAAC,oBAAD,CAAqB,CAAA;IACL,CAAA;GACK,EAAA,CAAA,CACT;EAClB,CAAA,GAAA,oBAAC,QAAD;GAAQ,UAAU,iBAAiB;GACjC,UAAA,oBAAC,SAAS,YAAV,EAAA,UACE,oBAAC,kBAAD;IACc;IACK;IACL;IACK;IACL;IACD;IACS;IACd;IACN,SAAQ;IAEP;GACe,CAAA,EACC,CAAA;EACf,CAAA,CACK;;AAEnB;AAEA,IAAA,yBAAe,kBAAkB,gBAAgB,EAAE,QAAQ,uBAAuB,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SelectRootProps, SelectTriggerProps } from '@chakra-ui/react/select';
|
|
2
2
|
import { BitkitFieldProps } from '../BitkitField/BitkitField';
|
|
3
3
|
import { BitkitMultiselectMenuItemProps } from '../BitkitMultiselectMenu/BitkitMultiselectMenu';
|
|
4
|
-
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
4
|
+
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuErrorStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
5
5
|
import { BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';
|
|
6
6
|
export type BitkitMultiselectTriggerProps = SelectTriggerProps;
|
|
7
7
|
export type BitkitMultiselectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {
|
|
@@ -20,7 +20,7 @@ export type BitkitMultiselectProps = Omit<BitkitFieldProps, 'children' | 'onChan
|
|
|
20
20
|
state?: 'disabled' | 'error' | 'readOnly' | 'warning';
|
|
21
21
|
triggerProps?: BitkitMultiselectTriggerProps;
|
|
22
22
|
value?: Array<string>;
|
|
23
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
23
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
24
24
|
declare const _default: import('react').ForwardRefExoticComponent<Omit<BitkitFieldProps, "children" | "onChange" | "state"> & {
|
|
25
25
|
children?: BitkitSelectMenuActionChild;
|
|
26
26
|
constrained?: boolean;
|
|
@@ -37,7 +37,7 @@ declare const _default: import('react').ForwardRefExoticComponent<Omit<BitkitFie
|
|
|
37
37
|
state?: "disabled" | "error" | "readOnly" | "warning";
|
|
38
38
|
triggerProps?: BitkitMultiselectTriggerProps;
|
|
39
39
|
value?: Array<string>;
|
|
40
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
40
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
41
41
|
Action: {
|
|
42
42
|
(props: import('..').BitkitSelectMenuActionProps): import('react').JSX.Element;
|
|
43
43
|
displayName: string;
|
|
@@ -97,7 +97,7 @@ var ClearAllButton = ({ state }) => {
|
|
|
97
97
|
});
|
|
98
98
|
};
|
|
99
99
|
var BitkitMultiselect = forwardRef((props, ref) => {
|
|
100
|
-
const { children, constrained = false, defaultValue, emptyHelperText, emptyLabel, isLoading, isValueInvalid, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, disablePortal, searchValue, selectProps, size = "lg", state, triggerProps, value, ...fieldProps } = props;
|
|
100
|
+
const { children, constrained = false, defaultValue, emptyHelperText, emptyLabel, errorHelperText, errorLabel, isLoading, isValueInvalid, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, disablePortal, searchPlaceholder, searchValue, selectProps, size = "lg", state, triggerProps, value, ...fieldProps } = props;
|
|
101
101
|
const { css: triggerCss, ...restTriggerProps } = triggerProps ?? {};
|
|
102
102
|
const collection = createListCollection({
|
|
103
103
|
items,
|
|
@@ -181,9 +181,12 @@ var BitkitMultiselect = forwardRef((props, ref) => {
|
|
|
181
181
|
collection,
|
|
182
182
|
emptyHelperText,
|
|
183
183
|
emptyLabel,
|
|
184
|
+
errorHelperText,
|
|
185
|
+
errorLabel,
|
|
184
186
|
isLoading,
|
|
185
187
|
onScrolledToBottom,
|
|
186
188
|
onSearchChange,
|
|
189
|
+
searchPlaceholder,
|
|
187
190
|
searchValue,
|
|
188
191
|
size,
|
|
189
192
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitMultiselect.js","names":[],"sources":["../../../lib/components/BitkitMultiselect/BitkitMultiselect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { useControllableState } from '@chakra-ui/react/hooks';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\nimport BitkitMultiselectMenu, {\n type BitkitMultiselectMenuItemProps,\n} from '../BitkitMultiselectMenu/BitkitMultiselectMenu';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport BitkitSelectMenuAction, { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\nimport BitkitTag from '../BitkitTag/BitkitTag';\n\nexport type BitkitMultiselectTriggerProps = SelectTriggerProps;\n\nexport type BitkitMultiselectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n children?: BitkitSelectMenuActionChild;\n constrained?: boolean;\n defaultValue?: Array<string>;\n isLoading?: boolean;\n /** Reddens the offending tags and errors the field, like BitkitTagsInput. Pair with `errorText`. */\n isValueInvalid?: (value: string) => boolean;\n items: Array<BitkitMultiselectMenuItemProps>;\n onValueChange?: (newVal: Array<string>) => void;\n placeholder?: string;\n disablePortal?: boolean;\n selectProps?: Omit<SelectRootProps, 'collection' | 'defaultValue' | 'multiple' | 'onValueChange' | 'value'>;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n triggerProps?: BitkitMultiselectTriggerProps;\n value?: Array<string>;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\ntype MultiselectState = BitkitMultiselectProps['state'];\n\nconst NON_INTERACTIVE_STATES: ReadonlyArray<MultiselectState> = ['disabled', 'readOnly', 'error', 'warning'];\n\n/** Whether the clear-all button should be rendered (and the overlay should reserve extra room for it). */\nconst shouldShowClearButton = (constrained: boolean, selectionCount: number, state: MultiselectState): boolean =>\n !constrained && selectionCount > 0 && !NON_INTERACTIVE_STATES.includes(state);\n\ntype MultiselectOverlayProps = {\n constrained: boolean;\n isValueInvalid?: (value: string) => boolean;\n placeholder?: string;\n size: 'md' | 'lg';\n state?: MultiselectState;\n};\n\nconst MultiselectOverlay = ({ constrained, isValueInvalid, placeholder, size, state }: MultiselectOverlayProps) => {\n const select = useSelectContext();\n const recipe = useSlotRecipe({ key: 'multiselect' });\n const styles = recipe({ constrained, size });\n\n const selectedItems = select.selectedItems as Array<BitkitMultiselectMenuItemProps>;\n const selectedValues = select.value;\n const isDisabled = state === 'disabled';\n const isReadOnly = state === 'readOnly';\n const tagState = isDisabled ? 'disabled' : undefined;\n // Overlay reserves ~96px on the right when the clear-all is visible (to keep the chevron +\n // clear button out of the tag row), otherwise ~48px (chevron only). Using spacing tokens\n // because '80' (the minimum that fits both icons with gaps) isn't a valid token; '96' is\n // the next one up.\n const overlayPaddingEnd = shouldShowClearButton(constrained, selectedValues.length, state) ? '96' : '48';\n\n const resolvedPlaceholder = placeholder ?? (isReadOnly ? '(not selected)' : 'Select options');\n const canRemove = !isDisabled && !isReadOnly;\n\n const hasSelection = selectedValues.length > 0;\n\n const renderTags = () => {\n if (constrained) {\n return (\n <>\n {hasSelection && (\n <TagSlot>\n <BitkitTag\n size=\"sm\"\n colorVariant={selectedValues.some((v) => isValueInvalid?.(v)) ? 'red' : 'neutral'}\n labelText={String(selectedValues.length)}\n state={tagState}\n onRemove={canRemove ? () => select.clearValue() : undefined}\n />\n </TagSlot>\n )}\n <Text as=\"span\" css={styles.placeholderText} data-placeholder={hasSelection ? undefined : ''}>\n {resolvedPlaceholder}\n </Text>\n </>\n );\n }\n\n if (selectedItems.length === 0) {\n return (\n <Text as=\"span\" css={styles.placeholderText} data-placeholder=\"\">\n {resolvedPlaceholder}\n </Text>\n );\n }\n\n return selectedItems.map((item) => (\n <TagSlot key={item.value}>\n <BitkitTag\n size=\"sm\"\n colorVariant={isValueInvalid?.(item.value) ? 'red' : 'neutral'}\n labelText={item.label}\n state={tagState}\n onRemove={canRemove ? () => select.setValue(selectedValues.filter((v) => v !== item.value)) : undefined}\n />\n </TagSlot>\n ));\n };\n\n return (\n <Box css={styles.overlay} pointerEvents=\"none\" paddingInlineEnd={overlayPaddingEnd}>\n <Box css={styles.tagsBlock}>{renderTags()}</Box>\n </Box>\n );\n};\n\n// minWidth=\"0\" keeps the slot shrinkable inside the wrapping tags row: without it the slot holds\n// the tag's min-content width, which is the whole label now that it no longer wraps, and a long\n// selection overflows the control instead of ellipsizing. maxWidth=\"100%\" caps the other direction:\n// a tag alone on its own wrapped line has no sibling to shrink against, so it needs the row as its\n// own ceiling.\nconst TagSlot = ({ children }: { children: ReactNode }) => (\n <Box data-slot=\"tag\" display=\"inline-flex\" minWidth=\"0\" maxWidth=\"100%\" pointerEvents=\"auto\">\n {children}\n </Box>\n);\n\ntype ClearAllButtonProps = {\n state?: MultiselectState;\n};\n\nconst ClearAllButton = ({ state }: ClearAllButtonProps) => {\n const select = useSelectContext();\n\n // Constrained mode has its own inline clear-all on the counter tag; this is the non-constrained trigger-level clear.\n if (!shouldShowClearButton(false, select.value.length, state)) return null;\n\n return (\n <BitkitCloseButton size=\"sm\" colorVariant=\"neutral\" aria-label=\"Clear all\" onClick={() => select.clearValue()} />\n );\n};\n\nconst BitkitMultiselect = forwardRef<HTMLDivElement, BitkitMultiselectProps>((props, ref) => {\n const {\n children,\n constrained = false,\n defaultValue,\n emptyHelperText,\n emptyLabel,\n isLoading,\n isValueInvalid,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n disablePortal,\n searchValue,\n selectProps,\n size = 'lg',\n state,\n triggerProps,\n value,\n ...fieldProps\n } = props;\n\n const { css: triggerCss, ...restTriggerProps } = triggerProps ?? {};\n\n const collection = createListCollection({\n items,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!item.disabled,\n });\n\n const recipe = useSlotRecipe({ key: 'multiselect' });\n const styles = recipe({ constrained, size });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const iconSize = size === 'lg' ? '24' : '16';\n\n const [currentValues, setCurrentValues] = useControllableState<Array<string>>({\n value,\n defaultValue: defaultValue ?? [],\n });\n const isInteractive = state !== 'disabled' && state !== 'readOnly';\n const hasInvalidValue = isInteractive && !!isValueInvalid && currentValues.some(isValueInvalid);\n const effectiveState = hasInvalidValue ? 'error' : state;\n const isInvalid = effectiveState === 'error' || !!fieldProps.errorText;\n\n return (\n <BitkitField ref={ref} state={effectiveState} {...fieldProps}>\n <Select.Root\n collection={collection}\n multiple\n {...selectProps}\n defaultValue={defaultValue}\n disabled={effectiveState === 'disabled'}\n invalid={isInvalid}\n onValueChange={(details) => {\n setCurrentValues(details.value);\n onValueChange?.(details.value);\n }}\n readOnly={effectiveState === 'readOnly'}\n // Zag's default scrollIntoView gates on `isScrollable(contentEl)`. Our content is\n // overflow:hidden + flex column (so the sticky-bottom create row stays visible while\n // the inner itemList Box scrolls). That gate fails, so keyboard highlight never\n // scrolls the items into view. Providing scrollToIndexFn bypasses the gate and lets\n // the browser's native scrollIntoView walk up to the real scroll container (itemList).\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n value={value}\n >\n <Select.Control css={styles.control} className=\"group\">\n <Select.HiddenSelect />\n <Select.Trigger\n css={[styles.trigger, triggerCss]}\n {...restTriggerProps}\n aria-readonly={effectiveState === 'readOnly' || undefined}\n />\n <MultiselectOverlay\n constrained={constrained}\n isValueInvalid={hasInvalidValue ? isValueInvalid : undefined}\n placeholder={placeholder}\n size={size}\n state={effectiveState}\n />\n <Select.IndicatorGroup css={styles.indicatorGroup}>\n {!constrained && <ClearAllButton state={effectiveState} />}\n {effectiveState === 'error' && (\n <Select.Indicator css={styles.indicator}>\n <IconErrorCircleFilled size={iconSize} color=\"icon/negative\" />\n </Select.Indicator>\n )}\n {effectiveState === 'warning' && (\n <Select.Indicator css={styles.indicator}>\n <IconWarningYellow size={iconSize} />\n </Select.Indicator>\n )}\n <Select.Indicator css={styles.indicator} asChild>\n <AssetSelectChevron />\n </Select.Indicator>\n </Select.IndicatorGroup>\n </Select.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Select.Positioner>\n <BitkitMultiselectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n searchValue={searchValue}\n size={size}\n >\n {children}\n </BitkitMultiselectMenu>\n </Select.Positioner>\n </Portal>\n </Select.Root>\n </BitkitField>\n );\n});\n\nBitkitMultiselect.displayName = 'BitkitMultiselect';\n\nexport default withSubComponents(BitkitMultiselect, { Action: BitkitSelectMenuAction });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BitkitMultiselect.js","names":[],"sources":["../../../lib/components/BitkitMultiselect/BitkitMultiselect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { useControllableState } from '@chakra-ui/react/hooks';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\nimport BitkitMultiselectMenu, {\n type BitkitMultiselectMenuItemProps,\n} from '../BitkitMultiselectMenu/BitkitMultiselectMenu';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuErrorStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport BitkitSelectMenuAction, { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\nimport BitkitTag from '../BitkitTag/BitkitTag';\n\nexport type BitkitMultiselectTriggerProps = SelectTriggerProps;\n\nexport type BitkitMultiselectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n children?: BitkitSelectMenuActionChild;\n constrained?: boolean;\n defaultValue?: Array<string>;\n isLoading?: boolean;\n /** Reddens the offending tags and errors the field, like BitkitTagsInput. Pair with `errorText`. */\n isValueInvalid?: (value: string) => boolean;\n items: Array<BitkitMultiselectMenuItemProps>;\n onValueChange?: (newVal: Array<string>) => void;\n placeholder?: string;\n disablePortal?: boolean;\n selectProps?: Omit<SelectRootProps, 'collection' | 'defaultValue' | 'multiple' | 'onValueChange' | 'value'>;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n triggerProps?: BitkitMultiselectTriggerProps;\n value?: Array<string>;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\ntype MultiselectState = BitkitMultiselectProps['state'];\n\nconst NON_INTERACTIVE_STATES: ReadonlyArray<MultiselectState> = ['disabled', 'readOnly', 'error', 'warning'];\n\n/** Whether the clear-all button should be rendered (and the overlay should reserve extra room for it). */\nconst shouldShowClearButton = (constrained: boolean, selectionCount: number, state: MultiselectState): boolean =>\n !constrained && selectionCount > 0 && !NON_INTERACTIVE_STATES.includes(state);\n\ntype MultiselectOverlayProps = {\n constrained: boolean;\n isValueInvalid?: (value: string) => boolean;\n placeholder?: string;\n size: 'md' | 'lg';\n state?: MultiselectState;\n};\n\nconst MultiselectOverlay = ({ constrained, isValueInvalid, placeholder, size, state }: MultiselectOverlayProps) => {\n const select = useSelectContext();\n const recipe = useSlotRecipe({ key: 'multiselect' });\n const styles = recipe({ constrained, size });\n\n const selectedItems = select.selectedItems as Array<BitkitMultiselectMenuItemProps>;\n const selectedValues = select.value;\n const isDisabled = state === 'disabled';\n const isReadOnly = state === 'readOnly';\n const tagState = isDisabled ? 'disabled' : undefined;\n // Overlay reserves ~96px on the right when the clear-all is visible (to keep the chevron +\n // clear button out of the tag row), otherwise ~48px (chevron only). Using spacing tokens\n // because '80' (the minimum that fits both icons with gaps) isn't a valid token; '96' is\n // the next one up.\n const overlayPaddingEnd = shouldShowClearButton(constrained, selectedValues.length, state) ? '96' : '48';\n\n const resolvedPlaceholder = placeholder ?? (isReadOnly ? '(not selected)' : 'Select options');\n const canRemove = !isDisabled && !isReadOnly;\n\n const hasSelection = selectedValues.length > 0;\n\n const renderTags = () => {\n if (constrained) {\n return (\n <>\n {hasSelection && (\n <TagSlot>\n <BitkitTag\n size=\"sm\"\n colorVariant={selectedValues.some((v) => isValueInvalid?.(v)) ? 'red' : 'neutral'}\n labelText={String(selectedValues.length)}\n state={tagState}\n onRemove={canRemove ? () => select.clearValue() : undefined}\n />\n </TagSlot>\n )}\n <Text as=\"span\" css={styles.placeholderText} data-placeholder={hasSelection ? undefined : ''}>\n {resolvedPlaceholder}\n </Text>\n </>\n );\n }\n\n if (selectedItems.length === 0) {\n return (\n <Text as=\"span\" css={styles.placeholderText} data-placeholder=\"\">\n {resolvedPlaceholder}\n </Text>\n );\n }\n\n return selectedItems.map((item) => (\n <TagSlot key={item.value}>\n <BitkitTag\n size=\"sm\"\n colorVariant={isValueInvalid?.(item.value) ? 'red' : 'neutral'}\n labelText={item.label}\n state={tagState}\n onRemove={canRemove ? () => select.setValue(selectedValues.filter((v) => v !== item.value)) : undefined}\n />\n </TagSlot>\n ));\n };\n\n return (\n <Box css={styles.overlay} pointerEvents=\"none\" paddingInlineEnd={overlayPaddingEnd}>\n <Box css={styles.tagsBlock}>{renderTags()}</Box>\n </Box>\n );\n};\n\n// minWidth=\"0\" keeps the slot shrinkable inside the wrapping tags row: without it the slot holds\n// the tag's min-content width, which is the whole label now that it no longer wraps, and a long\n// selection overflows the control instead of ellipsizing. maxWidth=\"100%\" caps the other direction:\n// a tag alone on its own wrapped line has no sibling to shrink against, so it needs the row as its\n// own ceiling.\nconst TagSlot = ({ children }: { children: ReactNode }) => (\n <Box data-slot=\"tag\" display=\"inline-flex\" minWidth=\"0\" maxWidth=\"100%\" pointerEvents=\"auto\">\n {children}\n </Box>\n);\n\ntype ClearAllButtonProps = {\n state?: MultiselectState;\n};\n\nconst ClearAllButton = ({ state }: ClearAllButtonProps) => {\n const select = useSelectContext();\n\n // Constrained mode has its own inline clear-all on the counter tag; this is the non-constrained trigger-level clear.\n if (!shouldShowClearButton(false, select.value.length, state)) return null;\n\n return (\n <BitkitCloseButton size=\"sm\" colorVariant=\"neutral\" aria-label=\"Clear all\" onClick={() => select.clearValue()} />\n );\n};\n\nconst BitkitMultiselect = forwardRef<HTMLDivElement, BitkitMultiselectProps>((props, ref) => {\n const {\n children,\n constrained = false,\n defaultValue,\n emptyHelperText,\n emptyLabel,\n errorHelperText,\n errorLabel,\n isLoading,\n isValueInvalid,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n disablePortal,\n searchPlaceholder,\n searchValue,\n selectProps,\n size = 'lg',\n state,\n triggerProps,\n value,\n ...fieldProps\n } = props;\n\n const { css: triggerCss, ...restTriggerProps } = triggerProps ?? {};\n\n const collection = createListCollection({\n items,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!item.disabled,\n });\n\n const recipe = useSlotRecipe({ key: 'multiselect' });\n const styles = recipe({ constrained, size });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const iconSize = size === 'lg' ? '24' : '16';\n\n const [currentValues, setCurrentValues] = useControllableState<Array<string>>({\n value,\n defaultValue: defaultValue ?? [],\n });\n const isInteractive = state !== 'disabled' && state !== 'readOnly';\n const hasInvalidValue = isInteractive && !!isValueInvalid && currentValues.some(isValueInvalid);\n const effectiveState = hasInvalidValue ? 'error' : state;\n const isInvalid = effectiveState === 'error' || !!fieldProps.errorText;\n\n return (\n <BitkitField ref={ref} state={effectiveState} {...fieldProps}>\n <Select.Root\n collection={collection}\n multiple\n {...selectProps}\n defaultValue={defaultValue}\n disabled={effectiveState === 'disabled'}\n invalid={isInvalid}\n onValueChange={(details) => {\n setCurrentValues(details.value);\n onValueChange?.(details.value);\n }}\n readOnly={effectiveState === 'readOnly'}\n // Zag's default scrollIntoView gates on `isScrollable(contentEl)`. Our content is\n // overflow:hidden + flex column (so the sticky-bottom create row stays visible while\n // the inner itemList Box scrolls). That gate fails, so keyboard highlight never\n // scrolls the items into view. Providing scrollToIndexFn bypasses the gate and lets\n // the browser's native scrollIntoView walk up to the real scroll container (itemList).\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n value={value}\n >\n <Select.Control css={styles.control} className=\"group\">\n <Select.HiddenSelect />\n <Select.Trigger\n css={[styles.trigger, triggerCss]}\n {...restTriggerProps}\n aria-readonly={effectiveState === 'readOnly' || undefined}\n />\n <MultiselectOverlay\n constrained={constrained}\n isValueInvalid={hasInvalidValue ? isValueInvalid : undefined}\n placeholder={placeholder}\n size={size}\n state={effectiveState}\n />\n <Select.IndicatorGroup css={styles.indicatorGroup}>\n {!constrained && <ClearAllButton state={effectiveState} />}\n {effectiveState === 'error' && (\n <Select.Indicator css={styles.indicator}>\n <IconErrorCircleFilled size={iconSize} color=\"icon/negative\" />\n </Select.Indicator>\n )}\n {effectiveState === 'warning' && (\n <Select.Indicator css={styles.indicator}>\n <IconWarningYellow size={iconSize} />\n </Select.Indicator>\n )}\n <Select.Indicator css={styles.indicator} asChild>\n <AssetSelectChevron />\n </Select.Indicator>\n </Select.IndicatorGroup>\n </Select.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Select.Positioner>\n <BitkitMultiselectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n errorHelperText={errorHelperText}\n errorLabel={errorLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n searchPlaceholder={searchPlaceholder}\n searchValue={searchValue}\n size={size}\n >\n {children}\n </BitkitMultiselectMenu>\n </Select.Positioner>\n </Portal>\n </Select.Root>\n </BitkitField>\n );\n});\n\nBitkitMultiselect.displayName = 'BitkitMultiselect';\n\nexport default withSubComponents(BitkitMultiselect, { Action: BitkitSelectMenuAction });\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoDA,IAAM,yBAA0D;CAAC;CAAY;CAAY;CAAS;AAAS;;AAG3G,IAAM,yBAAyB,aAAsB,gBAAwB,UAC3E,CAAC,eAAe,iBAAiB,KAAK,CAAC,uBAAuB,SAAS,KAAK;AAU9E,IAAM,sBAAsB,EAAE,aAAa,gBAAgB,aAAa,MAAM,YAAqC;CACjH,MAAM,SAAS,iBAAiB;CAEhC,MAAM,SADS,cAAc,EAAE,KAAK,cAAc,CACnC,CAAA,CAAO;EAAE;EAAa;CAAK,CAAC;CAE3C,MAAM,gBAAgB,OAAO;CAC7B,MAAM,iBAAiB,OAAO;CAC9B,MAAM,aAAa,UAAU;CAC7B,MAAM,aAAa,UAAU;CAC7B,MAAM,WAAW,aAAa,aAAa,KAAA;CAK3C,MAAM,oBAAoB,sBAAsB,aAAa,eAAe,QAAQ,KAAK,IAAI,OAAO;CAEpG,MAAM,sBAAsB,gBAAgB,aAAa,mBAAmB;CAC5E,MAAM,YAAY,CAAC,cAAc,CAAC;CAElC,MAAM,eAAe,eAAe,SAAS;CAE7C,MAAM,mBAAmB;EACvB,IAAI,aACF,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,gBACC,oBAAC,SAAD,EAAA,UACE,oBAAC,WAAD;GACE,MAAK;GACL,cAAc,eAAe,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,QAAQ;GACxE,WAAW,OAAO,eAAe,MAAM;GACvC,OAAO;GACP,UAAU,kBAAkB,OAAO,WAAW,IAAI,KAAA;EACnD,CAAA,EACM,CAAA,GAEX,oBAAC,MAAD;GAAM,IAAG;GAAO,KAAK,OAAO;GAAiB,oBAAkB,eAAe,KAAA,IAAY;GACvF,UAAA;EACG,CAAA,CACN,EAAA,CAAA;EAIN,IAAI,cAAc,WAAW,GAC3B,OACE,oBAAC,MAAD;GAAM,IAAG;GAAO,KAAK,OAAO;GAAiB,oBAAiB;GAC3D,UAAA;EACG,CAAA;EAIV,OAAO,cAAc,KAAK,SACxB,oBAAC,SAAD,EAAA,UACE,oBAAC,WAAD;GACE,MAAK;GACL,cAAc,iBAAiB,KAAK,KAAK,IAAI,QAAQ;GACrD,WAAW,KAAK;GAChB,OAAO;GACP,UAAU,kBAAkB,OAAO,SAAS,eAAe,QAAQ,MAAM,MAAM,KAAK,KAAK,CAAC,IAAI,KAAA;EAC/F,CAAA,EACM,GARK,KAAK,KAQV,CACV;CACH;CAEA,OACE,oBAAC,KAAD;EAAK,KAAK,OAAO;EAAS,eAAc;EAAO,kBAAkB;EAC/D,UAAA,oBAAC,KAAD;GAAK,KAAK,OAAO;GAAY,UAAA,WAAW;EAAO,CAAA;CAC5C,CAAA;AAET;AAOA,IAAM,WAAW,EAAE,eACjB,oBAAC,KAAD;CAAK,aAAU;CAAM,SAAQ;CAAc,UAAS;CAAI,UAAS;CAAO,eAAc;CACnF;AACE,CAAA;AAOP,IAAM,kBAAkB,EAAE,YAAiC;CACzD,MAAM,SAAS,iBAAiB;CAGhC,IAAI,CAAC,sBAAsB,OAAO,OAAO,MAAM,QAAQ,KAAK,GAAG,OAAO;CAEtE,OACE,oBAAC,mBAAD;EAAmB,MAAK;EAAK,cAAa;EAAU,cAAW;EAAY,eAAe,OAAO,WAAW;CAAI,CAAA;AAEpH;AAEA,IAAM,oBAAoB,YAAoD,OAAO,QAAQ;CAC3F,MAAM,EACJ,UACA,cAAc,OACd,cACA,iBACA,YACA,iBACA,YACA,WACA,gBACA,OACA,oBACA,gBACA,eACA,aACA,eACA,mBACA,aACA,aACA,OAAO,MACP,OACA,cACA,OACA,GAAG,eACD;CAEJ,MAAM,EAAE,KAAK,YAAY,GAAG,qBAAqB,gBAAgB,CAAC;CAElE,MAAM,aAAa,qBAAqB;EACtC;EACA,UAAU,SAAS,KAAK,SAAS;EACjC,iBAAiB,SAAS,CAAC,CAAC,KAAK;CACnC,CAAC;CAGD,MAAM,SADS,cAAc,EAAE,KAAK,cAAc,CACnC,CAAA,CAAO;EAAE;EAAa;CAAK,CAAC;CAE3C,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAClF,MAAM,WAAW,SAAS,OAAO,OAAO;CAExC,MAAM,CAAC,eAAe,oBAAoB,qBAAoC;EAC5E;EACA,cAAc,gBAAgB,CAAC;CACjC,CAAC;CAED,MAAM,kBADgB,UAAU,cAAc,UAAU,cACf,CAAC,CAAC,kBAAkB,cAAc,KAAK,cAAc;CAC9F,MAAM,iBAAiB,kBAAkB,UAAU;CACnD,MAAM,YAAY,mBAAmB,WAAW,CAAC,CAAC,WAAW;CAE7D,OACE,oBAAC,aAAD;EAAkB;EAAK,OAAO;EAAgB,GAAI;EAChD,UAAA,qBAAC,OAAO,MAAR;GACc;GACZ,UAAA;GACA,GAAI;GACU;GACd,UAAU,mBAAmB;GAC7B,SAAS;GACT,gBAAgB,YAAY;IAC1B,iBAAiB,QAAQ,KAAK;IAC9B,gBAAgB,QAAQ,KAAK;GAC/B;GACA,UAAU,mBAAmB;GAM7B,kBAAkB,EAAE,iBAAiB,WAAW,CAAC,EAAE,eAAe,EAAE,OAAO,UAAU,CAAC;GAC/E;GAlBT,UAAA,CAoBE,qBAAC,OAAO,SAAR;IAAgB,KAAK,OAAO;IAAS,WAAU;IAA/C,UAAA;KACE,oBAAC,OAAO,cAAR,CAAsB,CAAA;KACtB,oBAAC,OAAO,SAAR;MACE,KAAK,CAAC,OAAO,SAAS,UAAU;MAChC,GAAI;MACJ,iBAAe,mBAAmB,cAAc,KAAA;KACjD,CAAA;KACD,oBAAC,oBAAD;MACe;MACb,gBAAgB,kBAAkB,iBAAiB,KAAA;MACtC;MACP;MACN,OAAO;KACR,CAAA;KACD,qBAAC,OAAO,gBAAR;MAAuB,KAAK,OAAO;MAAnC,UAAA;OACG,CAAC,eAAe,oBAAC,gBAAD,EAAgB,OAAO,eAAiB,CAAA;OACxD,mBAAmB,WAClB,oBAAC,OAAO,WAAR;QAAkB,KAAK,OAAO;QAC5B,UAAA,oBAAC,uBAAD;SAAuB,MAAM;SAAU,OAAM;QAAiB,CAAA;OAC9C,CAAA;OAEnB,mBAAmB,aAClB,oBAAC,OAAO,WAAR;QAAkB,KAAK,OAAO;QAC5B,UAAA,oBAAC,mBAAD,EAAmB,MAAM,SAAW,CAAA;OACpB,CAAA;OAEpB,oBAAC,OAAO,WAAR;QAAkB,KAAK,OAAO;QAAW,SAAA;QACvC,UAAA,oBAAC,oBAAD,CAAqB,CAAA;OACL,CAAA;MACG;;IACT;GAChB,CAAA,GAAA,oBAAC,QAAD;IAAQ,UAAU,iBAAiB;IACjC,UAAA,oBAAC,OAAO,YAAR,EAAA,UACE,oBAAC,uBAAD;KACc;KACK;KACL;KACK;KACL;KACD;KACS;KACJ;KACG;KACN;KACP;KAEL;IACoB,CAAA,EACN,CAAA;GACb,CAAA,CACG;;CACF,CAAA;AAEjB,CAAC;AAED,kBAAkB,cAAc;AAEhC,IAAA,4BAAe,kBAAkB,mBAAmB,EAAE,QAAQ,uBAAuB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ListCollection } from '@chakra-ui/react/collection';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
3
|
+
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuErrorStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
4
4
|
import { BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';
|
|
5
5
|
export type BitkitMultiselectMenuItemProps = {
|
|
6
6
|
value: string;
|
|
@@ -15,11 +15,11 @@ export type BitkitMultiselectMenuProps = {
|
|
|
15
15
|
collection: ListCollection<BitkitMultiselectMenuItemProps>;
|
|
16
16
|
isLoading?: boolean;
|
|
17
17
|
size?: 'md' | 'lg';
|
|
18
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
18
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
19
19
|
declare const BitkitMultiselectMenu: import('react').ForwardRefExoticComponent<{
|
|
20
20
|
children?: BitkitSelectMenuActionChild;
|
|
21
21
|
collection: ListCollection<BitkitMultiselectMenuItemProps>;
|
|
22
22
|
isLoading?: boolean;
|
|
23
23
|
size?: "md" | "lg";
|
|
24
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
24
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
25
25
|
export default BitkitMultiselectMenu;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitMultiselectMenu.js","names":[],"sources":["../../../lib/components/BitkitMultiselectMenu/BitkitMultiselectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Select } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode } from 'react';\n\nimport { IconCheck } from '../../icons';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from '../BitkitSelectMenu/SelectMenuShell';\n\nexport type BitkitMultiselectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n helperText?: ReactNode;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitMultiselectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitMultiselectMenuItemProps>;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\nconst BitkitMultiselectMenu = forwardRef<HTMLDivElement, BitkitMultiselectMenuProps>((props, ref) => {\n const { children, collection, size, ...shellProps } = props;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={Select}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <MultiselectMenuItem\n key={item.value}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitMultiselectMenu.displayName = 'BitkitMultiselectMenu';\n\ntype MultiselectMenuItemRenderProps = {\n item: BitkitMultiselectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst MultiselectMenuItem = ({ item, iconSize, spinnerSize, styles }: MultiselectMenuItemRenderProps) => {\n if (item.loading) {\n // Rendered as a plain Box, not Select.Item — Zag's state machine won't track it\n // as an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <Select.Item css={styles.item} item={item}>\n <Box css={styles.checkbox} data-slot=\"checkbox\">\n <IconCheck size={iconSize} css={styles.checkmark} data-slot=\"checkmark\" />\n </Box>\n <Box css={styles.itemContent}>\n <Text css={styles.itemLabel}>{item.label}</Text>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n </Select.Item>\n );\n};\n\nexport default BitkitMultiselectMenu;\n"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"BitkitMultiselectMenu.js","names":[],"sources":["../../../lib/components/BitkitMultiselectMenu/BitkitMultiselectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Select } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode } from 'react';\n\nimport { IconCheck } from '../../icons';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuErrorStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from '../BitkitSelectMenu/SelectMenuShell';\n\nexport type BitkitMultiselectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n helperText?: ReactNode;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitMultiselectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitMultiselectMenuItemProps>;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\nconst BitkitMultiselectMenu = forwardRef<HTMLDivElement, BitkitMultiselectMenuProps>((props, ref) => {\n const { children, collection, size, ...shellProps } = props;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={Select}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <MultiselectMenuItem\n key={item.value}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitMultiselectMenu.displayName = 'BitkitMultiselectMenu';\n\ntype MultiselectMenuItemRenderProps = {\n item: BitkitMultiselectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst MultiselectMenuItem = ({ item, iconSize, spinnerSize, styles }: MultiselectMenuItemRenderProps) => {\n if (item.loading) {\n // Rendered as a plain Box, not Select.Item — Zag's state machine won't track it\n // as an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <Select.Item css={styles.item} item={item}>\n <Box css={styles.checkbox} data-slot=\"checkbox\">\n <IconCheck size={iconSize} css={styles.checkmark} data-slot=\"checkmark\" />\n </Box>\n <Box css={styles.itemContent}>\n <Text css={styles.itemLabel}>{item.label}</Text>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n </Select.Item>\n );\n};\n\nexport default BitkitMultiselectMenu;\n"],"mappings":";;;;;;;;;;AAqCA,IAAM,wBAAwB,YAAwD,OAAO,QAAQ;CACnG,MAAM,EAAE,UAAU,YAAY,MAAM,GAAG,eAAe;CAEtD,MAAM,SADS,cAAc,EAAE,KAAK,SAAS,CAC9B,CAAA,CAAO,EAAE,KAAK,CAAC;CAC9B,MAAM,EAAE,UAAU,gBAAgB,cAAc,QAAQ;CAExD,OACE,oBAAC,iBAAD;EACE,IAAI;EACQ;EACZ,YAAY;EACF;EACV,aAAa,SACX,oBAAC,qBAAD;GAEQ;GACI;GACG;GACL;EACT,GALM,KAAK,KAKX;EAEU;EACL;EACR,GAAI;EAEH;CACc,CAAA;AAErB,CAAC;AAED,sBAAsB,cAAc;AASpC,IAAM,uBAAuB,EAAE,MAAM,UAAU,aAAa,aAA6C;CACvG,IAAI,KAAK,SAGP,OACE,qBAAC,KAAD;EAAK,KAAK,OAAO;EAAjB,UAAA,CACE,oBAAC,SAAD;GAAS,SAAQ;GAAS,MAAM;GAAa,KAAK,OAAO;EAAc,CAAA,GACvE,oBAAC,MAAD;GAAM,KAAK,OAAO;GAAkB,UAAA;EAAgB,CAAA,CACjD;;CAIT,OACE,qBAAC,OAAO,MAAR;EAAa,KAAK,OAAO;EAAY;EAArC,UAAA,CACE,oBAAC,KAAD;GAAK,KAAK,OAAO;GAAU,aAAU;GACnC,UAAA,oBAAC,WAAD;IAAW,MAAM;IAAU,KAAK,OAAO;IAAW,aAAU;GAAa,CAAA;EACtE,CAAA,GACL,qBAAC,KAAD;GAAK,KAAK,OAAO;GAAjB,UAAA,CACE,oBAAC,MAAD;IAAM,KAAK,OAAO;IAAY,UAAA,KAAK;GAAY,CAAA,GAC9C,KAAK,cAAc,oBAAC,MAAD;IAAM,KAAK,OAAO;IAAiB,UAAA,KAAK;GAAiB,CAAA,CAC1E;EACM,CAAA,CAAA;;AAEjB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SelectRootProps, SelectTriggerProps } from '@chakra-ui/react/select';
|
|
2
2
|
import { BitkitFieldProps } from '../BitkitField/BitkitField.tsx';
|
|
3
|
-
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuItemProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu.tsx';
|
|
3
|
+
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuErrorStateProps, BitkitSelectMenuItemProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu.tsx';
|
|
4
4
|
import { BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction.tsx';
|
|
5
5
|
export type BitkitSelectTriggerProps = SelectTriggerProps;
|
|
6
6
|
export type BitkitSelectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {
|
|
@@ -16,7 +16,7 @@ export type BitkitSelectProps = Omit<BitkitFieldProps, 'children' | 'onChange' |
|
|
|
16
16
|
state?: 'disabled' | 'error' | 'readOnly' | 'warning';
|
|
17
17
|
triggerProps?: BitkitSelectTriggerProps;
|
|
18
18
|
value?: string;
|
|
19
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
19
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
20
20
|
declare const _default: import('react').ForwardRefExoticComponent<Omit<BitkitFieldProps, "children" | "onChange" | "state"> & {
|
|
21
21
|
children?: BitkitSelectMenuActionChild;
|
|
22
22
|
defaultValue?: string;
|
|
@@ -30,7 +30,7 @@ declare const _default: import('react').ForwardRefExoticComponent<Omit<BitkitFie
|
|
|
30
30
|
state?: "disabled" | "error" | "readOnly" | "warning";
|
|
31
31
|
triggerProps?: BitkitSelectTriggerProps;
|
|
32
32
|
value?: string;
|
|
33
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
33
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLDivElement>> & {
|
|
34
34
|
Action: {
|
|
35
35
|
(props: import('../index.ts').BitkitSelectMenuActionProps): import('react').JSX.Element;
|
|
36
36
|
displayName: string;
|
|
@@ -57,7 +57,7 @@ var SelectValue = ({ placeholder, state, size }) => {
|
|
|
57
57
|
});
|
|
58
58
|
};
|
|
59
59
|
var BitkitSelect = forwardRef((props, ref) => {
|
|
60
|
-
const { children, defaultValue, emptyHelperText, emptyLabel, isLoading, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, disablePortal, searchValue, selectProps, size = "lg", state, triggerProps, value, ...fieldProps } = props;
|
|
60
|
+
const { children, defaultValue, emptyHelperText, emptyLabel, errorHelperText, errorLabel, isLoading, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, disablePortal, searchPlaceholder, searchValue, selectProps, size = "lg", state, triggerProps, value, ...fieldProps } = props;
|
|
61
61
|
const collection = createListCollection({
|
|
62
62
|
items,
|
|
63
63
|
groupBy: (item) => item.group || "",
|
|
@@ -112,9 +112,12 @@ var BitkitSelect = forwardRef((props, ref) => {
|
|
|
112
112
|
collection,
|
|
113
113
|
emptyHelperText,
|
|
114
114
|
emptyLabel,
|
|
115
|
+
errorHelperText,
|
|
116
|
+
errorLabel,
|
|
115
117
|
isLoading,
|
|
116
118
|
onScrolledToBottom,
|
|
117
119
|
onSearchChange,
|
|
120
|
+
searchPlaceholder,
|
|
118
121
|
searchValue,
|
|
119
122
|
size,
|
|
120
123
|
children
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitSelect.js","names":[],"sources":["../../../lib/components/BitkitSelect/BitkitSelect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron.tsx';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents.ts';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField.tsx';\nimport BitkitSelectMenu, {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuItemProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu.tsx';\nimport BitkitSelectMenuAction, {\n type BitkitSelectMenuActionChild,\n} from '../BitkitSelectMenu/BitkitSelectMenuAction.tsx';\nimport { selectSizeMap } from '../BitkitSelectMenu/SelectMenuShell.tsx';\n\nexport type BitkitSelectTriggerProps = SelectTriggerProps;\n\nexport type BitkitSelectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n children?: BitkitSelectMenuActionChild;\n defaultValue?: string;\n isLoading?: boolean;\n items: Array<BitkitSelectMenuItemProps>;\n onValueChange?: (newVal: string) => void;\n placeholder?: string;\n disablePortal?: boolean;\n selectProps?: Omit<SelectRootProps, 'collection' | 'defaultValue' | 'onValueChange' | 'size' | 'value'>;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n triggerProps?: BitkitSelectTriggerProps;\n value?: string;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\ntype SelectValueProps = {\n placeholder?: string;\n size: NonNullable<BitkitSelectProps['size']>;\n state?: BitkitSelectProps['state'];\n};\n\nconst SelectValue = ({ placeholder, state, size }: SelectValueProps) => {\n const select = useSelectContext();\n const items = select.selectedItems as Array<BitkitSelectMenuItemProps>;\n\n const Icon = items[0]?.icon;\n const avatar = items[0]?.avatar;\n const label = items[0]?.label;\n const showAvatar = avatar !== undefined;\n\n const { avatarSize, iconSize } = selectSizeMap[size];\n\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n\n return items[0] ? (\n <Select.ValueText placeholder={placeholder ?? (state === 'readOnly' ? '(not selected)' : 'Select an option')}>\n {showAvatar ? (\n // Same avatar handling as the menu item (see BitkitSelectMenu.tsx). The `data-slot`\n // lets the trigger recipe shrink its vertical padding so the avatar row keeps the\n // 48/40 trigger height (see the `&:has([data-slot=\"avatar\"])` rule in Select.recipe.ts).\n <Box data-slot=\"avatar\" css={styles.avatar}>\n <BitkitAvatar variant=\"image\" src={avatar ?? ''} name={label} size={avatarSize} />\n </Box>\n ) : (\n Icon && <Icon css={styles.valueIcon} size={iconSize} flexShrink={0} />\n )}\n <Text as=\"span\" overflow=\"hidden\" textOverflow=\"ellipsis\" whiteSpace=\"nowrap\" minWidth=\"0\">\n {label}\n </Text>\n </Select.ValueText>\n ) : (\n <Select.ValueText\n placeholder={placeholder ?? (state === 'readOnly' ? '(not selected)' : 'Select an option')}\n display=\"block\"\n overflow=\"hidden\"\n textOverflow=\"ellipsis\"\n whiteSpace=\"nowrap\"\n />\n );\n};\n\nconst BitkitSelect = forwardRef<HTMLDivElement, BitkitSelectProps>((props: BitkitSelectProps, ref) => {\n const {\n children,\n defaultValue,\n emptyHelperText,\n emptyLabel,\n isLoading,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n disablePortal,\n searchValue,\n selectProps,\n size = 'lg',\n state,\n triggerProps,\n value,\n ...fieldProps\n } = props;\n\n const collection = createListCollection({\n items,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!item.disabled,\n });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const isInvalid = state === 'error' || !!fieldProps.errorText;\n\n return (\n <BitkitField ref={ref} state={state} {...fieldProps}>\n <Select.Root\n collection={collection}\n size={size}\n {...selectProps}\n defaultValue={defaultValue ? [defaultValue] : undefined}\n disabled={state === 'disabled'}\n hasStatusIcon={state === 'error' || state === 'warning'}\n invalid={isInvalid}\n onValueChange={(newVal) => onValueChange?.(newVal.value[0])}\n readOnly={state === 'readOnly'}\n // Bypass Zag's isScrollable(contentEl) gate — our Content is overflow:hidden flex\n // column, so the real scroll container is itemList. See BitkitMultiselect for why.\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n // Stay controlled whenever `value` is provided — including `''` (\"nothing selected\"). A\n // truthiness check (`value ? … : undefined`) passes `undefined` for `''`, which flips\n // Select.Root to uncontrolled: it then ignores `value`, so resets don't take and\n // onValueChange stops firing when the same option is re-selected.\n value={value === undefined ? undefined : value ? [value] : []}\n >\n <Select.Control className=\"group\">\n <Select.HiddenSelect />\n <Select.Trigger {...triggerProps} aria-readonly={state === 'readOnly' || undefined}>\n <SelectValue placeholder={placeholder} size={size} state={state} />\n </Select.Trigger>\n <Select.IndicatorGroup>\n {state === 'error' && (\n <Select.Indicator>\n <IconErrorCircleFilled size={size === 'lg' ? '24' : '16'} color=\"icon/negative\" />\n </Select.Indicator>\n )}\n {state === 'warning' && (\n <Select.Indicator>\n <IconWarningYellow size={size === 'lg' ? '24' : '16'} />\n </Select.Indicator>\n )}\n <Select.Indicator asChild>\n <AssetSelectChevron />\n </Select.Indicator>\n </Select.IndicatorGroup>\n </Select.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Select.Positioner>\n <BitkitSelectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n searchValue={searchValue}\n size={size}\n >\n {children}\n </BitkitSelectMenu>\n </Select.Positioner>\n </Portal>\n </Select.Root>\n </BitkitField>\n );\n});\n\nBitkitSelect.displayName = 'BitkitSelect';\n\nexport default withSubComponents(BitkitSelect, { Action: BitkitSelectMenuAction });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BitkitSelect.js","names":[],"sources":["../../../lib/components/BitkitSelect/BitkitSelect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron.tsx';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents.ts';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField.tsx';\nimport BitkitSelectMenu, {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuErrorStateProps,\n type BitkitSelectMenuItemProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu.tsx';\nimport BitkitSelectMenuAction, {\n type BitkitSelectMenuActionChild,\n} from '../BitkitSelectMenu/BitkitSelectMenuAction.tsx';\nimport { selectSizeMap } from '../BitkitSelectMenu/SelectMenuShell.tsx';\n\nexport type BitkitSelectTriggerProps = SelectTriggerProps;\n\nexport type BitkitSelectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n children?: BitkitSelectMenuActionChild;\n defaultValue?: string;\n isLoading?: boolean;\n items: Array<BitkitSelectMenuItemProps>;\n onValueChange?: (newVal: string) => void;\n placeholder?: string;\n disablePortal?: boolean;\n selectProps?: Omit<SelectRootProps, 'collection' | 'defaultValue' | 'onValueChange' | 'size' | 'value'>;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n triggerProps?: BitkitSelectTriggerProps;\n value?: string;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\ntype SelectValueProps = {\n placeholder?: string;\n size: NonNullable<BitkitSelectProps['size']>;\n state?: BitkitSelectProps['state'];\n};\n\nconst SelectValue = ({ placeholder, state, size }: SelectValueProps) => {\n const select = useSelectContext();\n const items = select.selectedItems as Array<BitkitSelectMenuItemProps>;\n\n const Icon = items[0]?.icon;\n const avatar = items[0]?.avatar;\n const label = items[0]?.label;\n const showAvatar = avatar !== undefined;\n\n const { avatarSize, iconSize } = selectSizeMap[size];\n\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n\n return items[0] ? (\n <Select.ValueText placeholder={placeholder ?? (state === 'readOnly' ? '(not selected)' : 'Select an option')}>\n {showAvatar ? (\n // Same avatar handling as the menu item (see BitkitSelectMenu.tsx). The `data-slot`\n // lets the trigger recipe shrink its vertical padding so the avatar row keeps the\n // 48/40 trigger height (see the `&:has([data-slot=\"avatar\"])` rule in Select.recipe.ts).\n <Box data-slot=\"avatar\" css={styles.avatar}>\n <BitkitAvatar variant=\"image\" src={avatar ?? ''} name={label} size={avatarSize} />\n </Box>\n ) : (\n Icon && <Icon css={styles.valueIcon} size={iconSize} flexShrink={0} />\n )}\n <Text as=\"span\" overflow=\"hidden\" textOverflow=\"ellipsis\" whiteSpace=\"nowrap\" minWidth=\"0\">\n {label}\n </Text>\n </Select.ValueText>\n ) : (\n <Select.ValueText\n placeholder={placeholder ?? (state === 'readOnly' ? '(not selected)' : 'Select an option')}\n display=\"block\"\n overflow=\"hidden\"\n textOverflow=\"ellipsis\"\n whiteSpace=\"nowrap\"\n />\n );\n};\n\nconst BitkitSelect = forwardRef<HTMLDivElement, BitkitSelectProps>((props: BitkitSelectProps, ref) => {\n const {\n children,\n defaultValue,\n emptyHelperText,\n emptyLabel,\n errorHelperText,\n errorLabel,\n isLoading,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n disablePortal,\n searchPlaceholder,\n searchValue,\n selectProps,\n size = 'lg',\n state,\n triggerProps,\n value,\n ...fieldProps\n } = props;\n\n const collection = createListCollection({\n items,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!item.disabled,\n });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const isInvalid = state === 'error' || !!fieldProps.errorText;\n\n return (\n <BitkitField ref={ref} state={state} {...fieldProps}>\n <Select.Root\n collection={collection}\n size={size}\n {...selectProps}\n defaultValue={defaultValue ? [defaultValue] : undefined}\n disabled={state === 'disabled'}\n hasStatusIcon={state === 'error' || state === 'warning'}\n invalid={isInvalid}\n onValueChange={(newVal) => onValueChange?.(newVal.value[0])}\n readOnly={state === 'readOnly'}\n // Bypass Zag's isScrollable(contentEl) gate — our Content is overflow:hidden flex\n // column, so the real scroll container is itemList. See BitkitMultiselect for why.\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n // Stay controlled whenever `value` is provided — including `''` (\"nothing selected\"). A\n // truthiness check (`value ? … : undefined`) passes `undefined` for `''`, which flips\n // Select.Root to uncontrolled: it then ignores `value`, so resets don't take and\n // onValueChange stops firing when the same option is re-selected.\n value={value === undefined ? undefined : value ? [value] : []}\n >\n <Select.Control className=\"group\">\n <Select.HiddenSelect />\n <Select.Trigger {...triggerProps} aria-readonly={state === 'readOnly' || undefined}>\n <SelectValue placeholder={placeholder} size={size} state={state} />\n </Select.Trigger>\n <Select.IndicatorGroup>\n {state === 'error' && (\n <Select.Indicator>\n <IconErrorCircleFilled size={size === 'lg' ? '24' : '16'} color=\"icon/negative\" />\n </Select.Indicator>\n )}\n {state === 'warning' && (\n <Select.Indicator>\n <IconWarningYellow size={size === 'lg' ? '24' : '16'} />\n </Select.Indicator>\n )}\n <Select.Indicator asChild>\n <AssetSelectChevron />\n </Select.Indicator>\n </Select.IndicatorGroup>\n </Select.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Select.Positioner>\n <BitkitSelectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n errorHelperText={errorHelperText}\n errorLabel={errorLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n searchPlaceholder={searchPlaceholder}\n searchValue={searchValue}\n size={size}\n >\n {children}\n </BitkitSelectMenu>\n </Select.Positioner>\n </Portal>\n </Select.Root>\n </BitkitField>\n );\n});\n\nBitkitSelect.displayName = 'BitkitSelect';\n\nexport default withSubComponents(BitkitSelect, { Action: BitkitSelectMenuAction });\n"],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ export { type BitkitSelectMenuActionProps } from './BitkitSelectMenuAction';
|
|
|
8
8
|
export type BitkitSelectMenuSearchProps = {
|
|
9
9
|
/** When provided, a search input is rendered above the menu items. `searchValue` is required alongside. */
|
|
10
10
|
onSearchChange?: (searchText: string) => void;
|
|
11
|
+
searchPlaceholder?: string;
|
|
11
12
|
searchValue?: string;
|
|
12
13
|
};
|
|
13
14
|
export type BitkitSelectMenuScrollProps = {
|
|
@@ -33,6 +34,10 @@ export type BitkitSelectMenuEmptyStateProps = {
|
|
|
33
34
|
emptyLabel?: string;
|
|
34
35
|
emptyHelperText?: string;
|
|
35
36
|
};
|
|
37
|
+
export type BitkitSelectMenuErrorStateProps = {
|
|
38
|
+
errorLabel?: string;
|
|
39
|
+
errorHelperText?: string;
|
|
40
|
+
};
|
|
36
41
|
export type BitkitSelectMenuProps = {
|
|
37
42
|
children?: BitkitSelectMenuActionChild;
|
|
38
43
|
collection: ListCollection<BitkitSelectMenuItemProps>;
|
|
@@ -43,7 +48,7 @@ export type BitkitSelectMenuProps = {
|
|
|
43
48
|
isLoading?: boolean;
|
|
44
49
|
size?: 'md' | 'lg';
|
|
45
50
|
variant?: 'select' | 'combobox';
|
|
46
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
51
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
47
52
|
declare const BitkitSelectMenu: import('react').ForwardRefExoticComponent<{
|
|
48
53
|
children?: BitkitSelectMenuActionChild;
|
|
49
54
|
collection: ListCollection<BitkitSelectMenuItemProps>;
|
|
@@ -54,5 +59,5 @@ declare const BitkitSelectMenu: import('react').ForwardRefExoticComponent<{
|
|
|
54
59
|
isLoading?: boolean;
|
|
55
60
|
size?: "md" | "lg";
|
|
56
61
|
variant?: "select" | "combobox";
|
|
57
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
62
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
58
63
|
export default BitkitSelectMenu;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitSelectMenu.js","names":[],"sources":["../../../lib/components/BitkitSelectMenu/BitkitSelectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Combobox } from '@chakra-ui/react/combobox';\nimport { Select, type SelectContentProps } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { cloneElement, forwardRef, type ReactElement, type ReactNode } from 'react';\n\nimport { type BitkitIconComponent, IconCheck } from '../../icons';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport { type BitkitBadgeProps } from '../BitkitBadge/BitkitBadge';\nimport { type BitkitSelectMenuActionChild } from './BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from './SelectMenuShell';\n\nexport { type BitkitSelectMenuActionProps } from './BitkitSelectMenuAction';\n\nexport type BitkitSelectMenuSearchProps = {\n /** When provided, a search input is rendered above the menu items. `searchValue` is required alongside. */\n onSearchChange?: (searchText: string) => void;\n searchValue?: string;\n};\n\nexport type BitkitSelectMenuScrollProps = {\n /** Fires when the item list reaches its end. Position only: keep your own loading and end-of-list guards. */\n onScrolledToBottom?: () => void;\n};\n\nexport type BitkitSelectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n icon?: BitkitIconComponent;\n /** The item's avatar. A URL renders that image; `null` renders the avatar with the initials\n * of `label`; omitting the prop renders no avatar and leaves the row's leading slot to\n * `icon`. */\n avatar?: string | null;\n helperText?: ReactNode;\n /** A `BitkitBadge` after the label. Forced to `colorVariant=\"neutral\"` on a disabled item. */\n badge?: ReactElement<BitkitBadgeProps>;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitSelectMenuEmptyStateProps = {\n emptyLabel?: string;\n emptyHelperText?: string;\n};\n\nexport type BitkitSelectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitSelectMenuItemProps>;\n /** Forwarded to the underlying Select/Combobox.Content. Kept for internal callers\n * (e.g. BitkitCalendar's in-grid selects that need to tweak max-height / width). The\n * public components (BitkitSelect, BitkitCombobox) do not expose this escape hatch. */\n contentProps?: SelectContentProps;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n variant?: 'select' | 'combobox';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\nconst BitkitSelectMenu = forwardRef<HTMLDivElement, BitkitSelectMenuProps>((props, ref) => {\n const { children, collection, variant = 'select', size, ...shellProps } = props;\n const NS = variant === 'combobox' ? Combobox : Select;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { avatarSize, iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={NS}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <SelectMenuItem\n key={item.value}\n NS={NS}\n avatarSize={avatarSize}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitSelectMenu.displayName = 'BitkitSelectMenu';\n\ntype SelectMenuItemRenderProps = {\n NS: typeof Select | typeof Combobox;\n avatarSize: SelectSizes['avatarSize'];\n item: BitkitSelectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst SelectMenuItem = ({ NS, avatarSize, item, iconSize, spinnerSize, styles }: SelectMenuItemRenderProps) => {\n const Icon = item.icon;\n const showAvatar = item.avatar !== undefined;\n\n if (item.loading) {\n // Rendered as a plain Box, not NS.Item — Zag's state machine won't track it as\n // an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <NS.Item css={styles.item} item={item}>\n {showAvatar && (\n <Box data-slot=\"avatar\" css={styles.avatar}>\n <BitkitAvatar variant=\"image\" src={item.avatar ?? ''} name={item.label} size={avatarSize} />\n </Box>\n )}\n <Box css={styles.itemContent}>\n <Box display=\"flex\" alignItems=\"center\" gap=\"8\">\n {Icon && !showAvatar && <Icon css={styles.itemIcon} size={iconSize} />}\n <Text css={styles.itemLabel}>{item.label}</Text>\n </Box>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n {item.badge && (\n <Box css={styles.itemBadge}>\n {item.disabled ? cloneElement(item.badge, { colorVariant: 'neutral' }) : item.badge}\n </Box>\n )}\n <NS.ItemIndicator asChild>\n <IconCheck size={iconSize} css={styles.itemIndicator} />\n </NS.ItemIndicator>\n </NS.Item>\n );\n};\n\nexport default BitkitSelectMenu;\n"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"BitkitSelectMenu.js","names":[],"sources":["../../../lib/components/BitkitSelectMenu/BitkitSelectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Combobox } from '@chakra-ui/react/combobox';\nimport { Select, type SelectContentProps } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { cloneElement, forwardRef, type ReactElement, type ReactNode } from 'react';\n\nimport { type BitkitIconComponent, IconCheck } from '../../icons';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport { type BitkitBadgeProps } from '../BitkitBadge/BitkitBadge';\nimport { type BitkitSelectMenuActionChild } from './BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from './SelectMenuShell';\n\nexport { type BitkitSelectMenuActionProps } from './BitkitSelectMenuAction';\n\nexport type BitkitSelectMenuSearchProps = {\n /** When provided, a search input is rendered above the menu items. `searchValue` is required alongside. */\n onSearchChange?: (searchText: string) => void;\n searchPlaceholder?: string;\n searchValue?: string;\n};\n\nexport type BitkitSelectMenuScrollProps = {\n /** Fires when the item list reaches its end. Position only: keep your own loading and end-of-list guards. */\n onScrolledToBottom?: () => void;\n};\n\nexport type BitkitSelectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n icon?: BitkitIconComponent;\n /** The item's avatar. A URL renders that image; `null` renders the avatar with the initials\n * of `label`; omitting the prop renders no avatar and leaves the row's leading slot to\n * `icon`. */\n avatar?: string | null;\n helperText?: ReactNode;\n /** A `BitkitBadge` after the label. Forced to `colorVariant=\"neutral\"` on a disabled item. */\n badge?: ReactElement<BitkitBadgeProps>;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitSelectMenuEmptyStateProps = {\n emptyLabel?: string;\n emptyHelperText?: string;\n};\n\nexport type BitkitSelectMenuErrorStateProps = {\n errorLabel?: string;\n errorHelperText?: string;\n};\n\nexport type BitkitSelectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitSelectMenuItemProps>;\n /** Forwarded to the underlying Select/Combobox.Content. Kept for internal callers\n * (e.g. BitkitCalendar's in-grid selects that need to tweak max-height / width). The\n * public components (BitkitSelect, BitkitCombobox) do not expose this escape hatch. */\n contentProps?: SelectContentProps;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n variant?: 'select' | 'combobox';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\nconst BitkitSelectMenu = forwardRef<HTMLDivElement, BitkitSelectMenuProps>((props, ref) => {\n const { children, collection, variant = 'select', size, ...shellProps } = props;\n const NS = variant === 'combobox' ? Combobox : Select;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { avatarSize, iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={NS}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <SelectMenuItem\n key={item.value}\n NS={NS}\n avatarSize={avatarSize}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitSelectMenu.displayName = 'BitkitSelectMenu';\n\ntype SelectMenuItemRenderProps = {\n NS: typeof Select | typeof Combobox;\n avatarSize: SelectSizes['avatarSize'];\n item: BitkitSelectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst SelectMenuItem = ({ NS, avatarSize, item, iconSize, spinnerSize, styles }: SelectMenuItemRenderProps) => {\n const Icon = item.icon;\n const showAvatar = item.avatar !== undefined;\n\n if (item.loading) {\n // Rendered as a plain Box, not NS.Item — Zag's state machine won't track it as\n // an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <NS.Item css={styles.item} item={item}>\n {showAvatar && (\n <Box data-slot=\"avatar\" css={styles.avatar}>\n <BitkitAvatar variant=\"image\" src={item.avatar ?? ''} name={item.label} size={avatarSize} />\n </Box>\n )}\n <Box css={styles.itemContent}>\n <Box display=\"flex\" alignItems=\"center\" gap=\"8\">\n {Icon && !showAvatar && <Icon css={styles.itemIcon} size={iconSize} />}\n <Text css={styles.itemLabel}>{item.label}</Text>\n </Box>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n {item.badge && (\n <Box css={styles.itemBadge}>\n {item.disabled ? cloneElement(item.badge, { colorVariant: 'neutral' }) : item.badge}\n </Box>\n )}\n <NS.ItemIndicator asChild>\n <IconCheck size={iconSize} css={styles.itemIndicator} />\n </NS.ItemIndicator>\n </NS.Item>\n );\n};\n\nexport default BitkitSelectMenu;\n"],"mappings":";;;;;;;;;;;;AAsEA,IAAM,mBAAmB,YAAmD,OAAO,QAAQ;CACzF,MAAM,EAAE,UAAU,YAAY,UAAU,UAAU,MAAM,GAAG,eAAe;CAC1E,MAAM,KAAK,YAAY,aAAa,WAAW;CAE/C,MAAM,SADS,cAAc,EAAE,KAAK,SAAS,CAC9B,CAAA,CAAO,EAAE,KAAK,CAAC;CAC9B,MAAM,EAAE,YAAY,UAAU,gBAAgB,cAAc,QAAQ;CAEpE,OACE,oBAAC,iBAAD;EACM;EACQ;EACZ,YAAY;EACF;EACV,aAAa,SACX,oBAAC,gBAAD;GAEM;GACQ;GACN;GACI;GACG;GACL;EACT,GAPM,KAAK,KAOX;EAEU;EACL;EACR,GAAI;EAEH;CACc,CAAA;AAErB,CAAC;AAED,iBAAiB,cAAc;AAW/B,IAAM,kBAAkB,EAAE,IAAI,YAAY,MAAM,UAAU,aAAa,aAAwC;CAC7G,MAAM,OAAO,KAAK;CAClB,MAAM,aAAa,KAAK,WAAW,KAAA;CAEnC,IAAI,KAAK,SAGP,OACE,qBAAC,KAAD;EAAK,KAAK,OAAO;EAAjB,UAAA,CACE,oBAAC,SAAD;GAAS,SAAQ;GAAS,MAAM;GAAa,KAAK,OAAO;EAAc,CAAA,GACvE,oBAAC,MAAD;GAAM,KAAK,OAAO;GAAkB,UAAA;EAAgB,CAAA,CACjD;;CAIT,OACE,qBAAC,GAAG,MAAJ;EAAS,KAAK,OAAO;EAAY;EAAjC,UAAA;GACG,cACC,oBAAC,KAAD;IAAK,aAAU;IAAS,KAAK,OAAO;IAClC,UAAA,oBAAC,cAAD;KAAc,SAAQ;KAAQ,KAAK,KAAK,UAAU;KAAI,MAAM,KAAK;KAAO,MAAM;IAAa,CAAA;GACxF,CAAA;GAEP,qBAAC,KAAD;IAAK,KAAK,OAAO;IAAjB,UAAA,CACE,qBAAC,KAAD;KAAK,SAAQ;KAAO,YAAW;KAAS,KAAI;KAA5C,UAAA,CACG,QAAQ,CAAC,cAAc,oBAAC,MAAD;MAAM,KAAK,OAAO;MAAU,MAAM;KAAW,CAAA,GACrE,oBAAC,MAAD;MAAM,KAAK,OAAO;MAAY,UAAA,KAAK;KAAY,CAAA,CAC5C;IACJ,CAAA,GAAA,KAAK,cAAc,oBAAC,MAAD;KAAM,KAAK,OAAO;KAAiB,UAAA,KAAK;IAAiB,CAAA,CAC1E;;GACJ,KAAK,SACJ,oBAAC,KAAD;IAAK,KAAK,OAAO;IACd,UAAA,KAAK,WAAW,aAAa,KAAK,OAAO,EAAE,cAAc,UAAU,CAAC,IAAI,KAAK;GAC3E,CAAA;GAEP,oBAAC,GAAG,eAAJ;IAAkB,SAAA;IAChB,UAAA,oBAAC,WAAD;KAAW,MAAM;KAAU,KAAK,OAAO;IAAgB,CAAA;GACvC,CAAA;EACX;;AAEb"}
|
|
@@ -3,7 +3,7 @@ import { Combobox } from '@chakra-ui/react/combobox';
|
|
|
3
3
|
import { Select, SelectContentProps } from '@chakra-ui/react/select';
|
|
4
4
|
import { SystemStyleObject } from '@chakra-ui/react/styled-system';
|
|
5
5
|
import { ReactNode, Ref } from 'react';
|
|
6
|
-
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from './BitkitSelectMenu';
|
|
6
|
+
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuErrorStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from './BitkitSelectMenu';
|
|
7
7
|
import { BitkitSelectMenuActionChild } from './BitkitSelectMenuAction';
|
|
8
8
|
export declare const selectSizeMap: {
|
|
9
9
|
readonly lg: {
|
|
@@ -38,8 +38,8 @@ export type SelectMenuShellProps<T extends {
|
|
|
38
38
|
renderItem: (item: T) => ReactNode;
|
|
39
39
|
spinnerSize: SelectSizes['spinnerSize'];
|
|
40
40
|
styles: Record<string, SystemStyleObject>;
|
|
41
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
41
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
42
42
|
export declare const SelectMenuShell: <T extends {
|
|
43
43
|
value: string;
|
|
44
44
|
group?: string;
|
|
45
|
-
}>({ NS, children, collection, contentProps, contentRef, emptyHelperText, emptyLabel, iconSize, isLoading, onScrolledToBottom, onSearchChange, renderItem, searchValue, spinnerSize, styles, }: SelectMenuShellProps<T>) => import('react').JSX.Element;
|
|
45
|
+
}>({ NS, children, collection, contentProps, contentRef, emptyHelperText, emptyLabel, errorHelperText, errorLabel, iconSize, isLoading, onScrolledToBottom, onSearchChange, renderItem, searchPlaceholder, searchValue, spinnerSize, styles, }: SelectMenuShellProps<T>) => import('react').JSX.Element;
|