@bitrise/bitkit-v2 0.3.460-beta.2905 → 0.3.460-beta.2908

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.
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;AAsEA,IAAM,iBAAiB,YAAiD,OAA4B,QAAQ;CAC1G,MAAM,EACJ,kBACA,UACA,eACA,cACA,eACA,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;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;;AAyB7B,IAAM,mBAAmB,EACvB,kBACA,UACA,eACA,cACA,eACA,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;IACD;IACS;IACd;IACN,SAAQ;IAEP;GACe,CAAA,EACC,CAAA;EACf,CAAA,CACK;;AAEnB;AAEA,IAAA,yBAAe,kBAAkB,gBAAgB,EAAE,QAAQ,uBAAuB,CAAC"}
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":"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"}
@@ -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, searchPlaceholder, 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,6 +181,8 @@ 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,
@@ -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 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 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":"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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":"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"}
@@ -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, searchPlaceholder, 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,6 +112,8 @@ 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,
@@ -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 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 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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"}
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":";;;;;;;;;;;;;;;;;;;AAoDA,IAAM,eAAe,EAAE,aAAa,OAAO,WAA6B;CAEtE,MAAM,QADS,iBACD,CAAA,CAAO;CAErB,MAAM,OAAO,MAAM,EAAE,EAAE;CACvB,MAAM,SAAS,MAAM,EAAE,EAAE;CACzB,MAAM,QAAQ,MAAM,EAAE,EAAE;CACxB,MAAM,aAAa,WAAW,KAAA;CAE9B,MAAM,EAAE,YAAY,aAAa,cAAc;CAG/C,MAAM,SADS,cAAc,EAAE,KAAK,SAAS,CAC9B,CAAA,CAAO,EAAE,KAAK,CAAC;CAE9B,OAAO,MAAM,KACX,qBAAC,OAAO,WAAR;EAAkB,aAAa,gBAAgB,UAAU,aAAa,mBAAmB;EAAzF,UAAA,CACG,aAIC,oBAAC,KAAD;GAAK,aAAU;GAAS,KAAK,OAAO;GAClC,UAAA,oBAAC,cAAD;IAAc,SAAQ;IAAQ,KAAK,UAAU;IAAI,MAAM;IAAO,MAAM;GAAa,CAAA;EAC9E,CAAA,IAEL,QAAQ,oBAAC,MAAD;GAAM,KAAK,OAAO;GAAW,MAAM;GAAU,YAAY;EAAI,CAAA,GAEvE,oBAAC,MAAD;GAAM,IAAG;GAAO,UAAS;GAAS,cAAa;GAAW,YAAW;GAAS,UAAS;GACpF,UAAA;EACG,CAAA,CACU;CAElB,CAAA,IAAA,oBAAC,OAAO,WAAR;EACE,aAAa,gBAAgB,UAAU,aAAa,mBAAmB;EACvE,SAAQ;EACR,UAAS;EACT,cAAa;EACb,YAAW;CACZ,CAAA;AAEL;AAEA,IAAM,eAAe,YAA+C,OAA0B,QAAQ;CACpG,MAAM,EACJ,UACA,cACA,iBACA,YACA,iBACA,YACA,WACA,OACA,oBACA,gBACA,eACA,aACA,eACA,mBACA,aACA,aACA,OAAO,MACP,OACA,cACA,OACA,GAAG,eACD;CAEJ,MAAM,aAAa,qBAAqB;EACtC;EACA,UAAU,SAAS,KAAK,SAAS;EACjC,iBAAiB,SAAS,CAAC,CAAC,KAAK;CACnC,CAAC;CAED,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAClF,MAAM,YAAY,UAAU,WAAW,CAAC,CAAC,WAAW;CAEpD,OACE,oBAAC,aAAD;EAAkB;EAAY;EAAO,GAAI;EACvC,UAAA,qBAAC,OAAO,MAAR;GACc;GACN;GACN,GAAI;GACJ,cAAc,eAAe,CAAC,YAAY,IAAI,KAAA;GAC9C,UAAU,UAAU;GACpB,eAAe,UAAU,WAAW,UAAU;GAC9C,SAAS;GACT,gBAAgB,WAAW,gBAAgB,OAAO,MAAM,EAAE;GAC1D,UAAU,UAAU;GAGpB,kBAAkB,EAAE,iBAAiB,WAAW,CAAC,EAAE,eAAe,EAAE,OAAO,UAAU,CAAC;GAKtF,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,QAAQ,CAAC,KAAK,IAAI,CAAC;GAjB9D,UAAA,CAmBE,qBAAC,OAAO,SAAR;IAAgB,WAAU;IAA1B,UAAA;KACE,oBAAC,OAAO,cAAR,CAAsB,CAAA;KACtB,oBAAC,OAAO,SAAR;MAAgB,GAAI;MAAc,iBAAe,UAAU,cAAc,KAAA;MACvE,UAAA,oBAAC,aAAD;OAA0B;OAAmB;OAAa;MAAQ,CAAA;KACpD,CAAA;KAChB,qBAAC,OAAO,gBAAR,EAAA,UAAA;MACG,UAAU,WACT,oBAAC,OAAO,WAAR,EAAA,UACE,oBAAC,uBAAD;OAAuB,MAAM,SAAS,OAAO,OAAO;OAAM,OAAM;MAAiB,CAAA,EACjE,CAAA;MAEnB,UAAU,aACT,oBAAC,OAAO,WAAR,EAAA,UACE,oBAAC,mBAAD,EAAmB,MAAM,SAAS,OAAO,OAAO,KAAO,CAAA,EACvC,CAAA;MAEpB,oBAAC,OAAO,WAAR;OAAkB,SAAA;OAChB,UAAA,oBAAC,oBAAD,CAAqB,CAAA;MACL,CAAA;KACG,EAAA,CAAA;IACT;GAChB,CAAA,GAAA,oBAAC,QAAD;IAAQ,UAAU,iBAAiB;IACjC,UAAA,oBAAC,OAAO,YAAR,EAAA,UACE,oBAAC,kBAAD;KACc;KACK;KACL;KACK;KACL;KACD;KACS;KACJ;KACG;KACN;KACP;KAEL;IACe,CAAA,EACD,CAAA;GACb,CAAA,CACG;;CACF,CAAA;AAEjB,CAAC;AAED,aAAa,cAAc;AAE3B,IAAA,uBAAe,kBAAkB,cAAc,EAAE,QAAQ,uBAAuB,CAAC"}
@@ -51,7 +51,7 @@ import { Drawer } from "@chakra-ui/react/drawer";
51
51
  * Descendants can read the current state with `useUISideNav()`.
52
52
  */
53
53
  var BitkitUISideNav = forwardRef((props, ref) => {
54
- const { children, css, expanded: expandedProp, onOpenChange, open = false, ...rest } = props;
54
+ const { children, css, expanded: expandedProp, onBlur, onFocus, onOpenChange, onPointerEnter, onPointerLeave, open = false, ...rest } = props;
55
55
  const { isMobile } = useResponsive();
56
56
  const [pointerInside, setPointerInside] = useState(false);
57
57
  const [focusInside, setFocusInside] = useState(false);
@@ -61,13 +61,23 @@ var BitkitUISideNav = forwardRef((props, ref) => {
61
61
  const expanded = isMobile || (expandedProp ?? (pointerInside || focusInside || menuOpen));
62
62
  const tracksPointer = !isMobile && expandedProp === void 0;
63
63
  const handleFocus = (event) => {
64
- if (!event.target.matches(":focus-visible")) return;
64
+ onFocus?.(event);
65
+ if (!tracksPointer || !event.target.matches(":focus-visible")) return;
65
66
  setFocusInside(true);
66
67
  };
67
68
  const handleBlur = (event) => {
68
- if (event.currentTarget.contains(event.relatedTarget)) return;
69
+ onBlur?.(event);
70
+ if (!tracksPointer || event.currentTarget.contains(event.relatedTarget)) return;
69
71
  setFocusInside(false);
70
72
  };
73
+ const handlePointerEnter = (event) => {
74
+ onPointerEnter?.(event);
75
+ if (tracksPointer) setPointerInside(true);
76
+ };
77
+ const handlePointerLeave = (event) => {
78
+ onPointerLeave?.(event);
79
+ if (tracksPointer) setPointerInside(false);
80
+ };
71
81
  const nav = /* @__PURE__ */ jsx(Box, {
72
82
  ref,
73
83
  "aria-label": "Platform navigation",
@@ -75,10 +85,10 @@ var BitkitUISideNav = forwardRef((props, ref) => {
75
85
  "data-expanded": expanded ? "" : void 0,
76
86
  ...rest,
77
87
  css: [styles.root, css],
78
- onBlur: tracksPointer ? handleBlur : void 0,
79
- onFocus: tracksPointer ? handleFocus : void 0,
80
- onPointerEnter: tracksPointer ? () => setPointerInside(true) : void 0,
81
- onPointerLeave: tracksPointer ? () => setPointerInside(false) : void 0,
88
+ onBlur: handleBlur,
89
+ onFocus: handleFocus,
90
+ onPointerEnter: handlePointerEnter,
91
+ onPointerLeave: handlePointerLeave,
82
92
  children: /* @__PURE__ */ jsx("nav", { children })
83
93
  });
84
94
  const context = useMemo(() => ({
@@ -105,6 +115,7 @@ var BitkitUISideNav = forwardRef((props, ref) => {
105
115
  css: styles.positioner,
106
116
  children: /* @__PURE__ */ jsxs(Drawer.Content, {
107
117
  ref: menuContainerRef,
118
+ "aria-label": "Navigation",
108
119
  css: styles.content,
109
120
  children: [nav, /* @__PURE__ */ jsx(Box, {
110
121
  css: styles.closeButton,
@@ -1 +1 @@
1
- {"version":3,"file":"BitkitUISideNav.js","names":[],"sources":["../../../lib/components/BitkitUISideNav/BitkitUISideNav.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { Drawer } from '@chakra-ui/react/drawer';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { type FocusEvent, forwardRef, type ReactNode, useMemo, useRef, useState } from 'react';\n\nimport useResponsive from '../../hooks/useResponsive';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport UISideNavBody from './UISideNavBody';\nimport { UISideNavContextProvider } from './UISideNavContext';\nimport UISideNavFooter from './UISideNavFooter';\nimport UISideNavFooterHeading from './UISideNavFooterHeading';\nimport UISideNavItem from './UISideNavItem';\nimport UISideNavLink from './UISideNavLink';\nimport UISideNavWorkspaceSelector from './UISideNavWorkspaceSelector';\n\nexport interface BitkitUISideNavProps extends Omit<BoxProps, 'children'> {\n children: ReactNode;\n /**\n * Forces the desktop rail open or shut, overriding the pointer and focus behaviour. Leave it\n * unset for the normal rail. Set it to `true` while something the rail does not know about is\n * anchored to it, such as a popover opened from the footer.\n */\n expanded?: boolean;\n /** Mobile drawer visibility. Controlled, because the header's menu button is what opens it. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\n/**\n * The platform's primary navigation. It expects a workspace selector, a body of navigation\n * items, and an optional footer:\n *\n * ```tsx\n * <BitkitUISideNav open={open} onOpenChange={setOpen}>\n * <BitkitUISideNav.WorkspaceSelector\n * workspaces={workspaces}\n * value={workspace}\n * onValueChange={setWorkspace}\n * />\n * <BitkitUISideNav.Body>\n * <BitkitUISideNav.Item href=\"/dashboard\" icon={IconDashboard} selectedIcon={IconDashboardFilled} selected>\n * Dashboard\n * </BitkitUISideNav.Item>\n * </BitkitUISideNav.Body>\n * <BitkitUISideNav.Footer>…</BitkitUISideNav.Footer>\n * </BitkitUISideNav>\n * ```\n *\n * On desktop it is a 72px rail that widens to 256px under the pointer and while anything inside\n * it holds keyboard focus. The rows are always laid out at the full width and clipped, so the\n * labels appear without any text reflowing. It will not collapse while the workspace menu is\n * open. There is no pin: the rail is the resting state.\n *\n * Below the `tablet` breakpoint it becomes an overlay drawer instead, opened by the header rather\n * than by hovering. Pass `open` and `onOpenChange`; they are ignored on desktop.\n *\n * It does not position itself. The consumer decides where it sits and how tall it is, which is\n * what lets a page slot it under a header of any height. The expanded rail overlays the page\n * rather than pushing it, so give it a `position` of your own and reserve 72px beside it.\n *\n * Descendants can read the current state with `useUISideNav()`.\n */\nconst BitkitUISideNav = forwardRef<HTMLElement, BitkitUISideNavProps>((props, ref) => {\n const { children, css, expanded: expandedProp, onOpenChange, open = false, ...rest } = props;\n\n const { isMobile } = useResponsive();\n\n const [pointerInside, setPointerInside] = useState(false);\n const [focusInside, setFocusInside] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const menuContainerRef = useRef<HTMLDivElement>(null);\n\n const recipe = useSlotRecipe({ key: 'uiSideNav' });\n const styles = recipe({ layout: isMobile ? 'drawer' : 'rail' });\n\n const expanded = isMobile || (expandedProp ?? (pointerInside || focusInside || menuOpen));\n const tracksPointer = !isMobile && expandedProp === undefined;\n\n const handleFocus = (event: FocusEvent<HTMLElement>) => {\n if (!event.target.matches(':focus-visible')) return;\n setFocusInside(true);\n };\n\n const handleBlur = (event: FocusEvent<HTMLElement>) => {\n if (event.currentTarget.contains(event.relatedTarget)) return;\n setFocusInside(false);\n };\n\n const nav = (\n <Box\n ref={ref}\n aria-label=\"Platform navigation\"\n asChild\n data-expanded={expanded ? '' : undefined}\n {...rest}\n css={[styles.root, css]}\n onBlur={tracksPointer ? handleBlur : undefined}\n onFocus={tracksPointer ? handleFocus : undefined}\n onPointerEnter={tracksPointer ? () => setPointerInside(true) : undefined}\n onPointerLeave={tracksPointer ? () => setPointerInside(false) : undefined}\n >\n <nav>{children}</nav>\n </Box>\n );\n\n const context = useMemo(\n () => ({ expanded, isMobile, menuContainerRef, setMenuOpen, styles }),\n [expanded, isMobile, styles],\n );\n\n if (!isMobile) {\n return <UISideNavContextProvider value={context}>{nav}</UISideNavContextProvider>;\n }\n\n return (\n <UISideNavContextProvider value={context}>\n <Drawer.Root onOpenChange={(details) => onOpenChange?.(details.open)} open={open}>\n <Portal>\n <Drawer.Backdrop css={styles.backdrop} />\n <Drawer.Positioner css={styles.positioner}>\n <Drawer.Content ref={menuContainerRef} css={styles.content}>\n {nav}\n <Box css={styles.closeButton}>\n <BitkitCloseButton\n aria-label=\"Close navigation\"\n colorVariant=\"white\"\n onClick={() => onOpenChange?.(false)}\n size=\"md\"\n />\n </Box>\n </Drawer.Content>\n </Drawer.Positioner>\n </Portal>\n </Drawer.Root>\n </UISideNavContextProvider>\n );\n});\n\nexport { uiSideNavRailWidth } from '../../theme/slot-recipes/UISideNav.recipe';\nexport { type BitkitUISideNavBodyProps } from './UISideNavBody';\nexport { type BitkitUISideNavFooterProps } from './UISideNavFooter';\nexport { type BitkitUISideNavFooterHeadingProps } from './UISideNavFooterHeading';\nexport { type BitkitUISideNavItemProps } from './UISideNavItem';\nexport {\n type BitkitUISideNavLinkAsAnchorProps,\n type BitkitUISideNavLinkAsButtonProps,\n type BitkitUISideNavLinkProps,\n} from './UISideNavLink';\nexport {\n type BitkitUISideNavWorkspaceProps,\n type BitkitUISideNavWorkspaceSelectorProps,\n} from './UISideNavWorkspaceSelector';\n\nBitkitUISideNav.displayName = 'BitkitUISideNav';\n\nexport default withSubComponents(BitkitUISideNav, {\n Body: UISideNavBody,\n Footer: UISideNavFooter,\n FooterHeading: UISideNavFooterHeading,\n Item: UISideNavItem,\n Link: UISideNavLink,\n WorkspaceSelector: UISideNavWorkspaceSelector,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAM,kBAAkB,YAA+C,OAAO,QAAQ;CACpF,MAAM,EAAE,UAAU,KAAK,UAAU,cAAc,cAAc,OAAO,OAAO,GAAG,SAAS;CAEvF,MAAM,EAAE,aAAa,cAAc;CAEnC,MAAM,CAAC,eAAe,oBAAoB,SAAS,KAAK;CACxD,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,mBAAmB,OAAuB,IAAI;CAGpD,MAAM,SADS,cAAc,EAAE,KAAK,YAAY,CACjC,CAAA,CAAO,EAAE,QAAQ,WAAW,WAAW,OAAO,CAAC;CAE9D,MAAM,WAAW,aAAa,iBAAiB,iBAAiB,eAAe;CAC/E,MAAM,gBAAgB,CAAC,YAAY,iBAAiB,KAAA;CAEpD,MAAM,eAAe,UAAmC;EACtD,IAAI,CAAC,MAAM,OAAO,QAAQ,gBAAgB,GAAG;EAC7C,eAAe,IAAI;CACrB;CAEA,MAAM,cAAc,UAAmC;EACrD,IAAI,MAAM,cAAc,SAAS,MAAM,aAAa,GAAG;EACvD,eAAe,KAAK;CACtB;CAEA,MAAM,MACJ,oBAAC,KAAD;EACO;EACL,cAAW;EACX,SAAA;EACA,iBAAe,WAAW,KAAK,KAAA;EAC/B,GAAI;EACJ,KAAK,CAAC,OAAO,MAAM,GAAG;EACtB,QAAQ,gBAAgB,aAAa,KAAA;EACrC,SAAS,gBAAgB,cAAc,KAAA;EACvC,gBAAgB,sBAAsB,iBAAiB,IAAI,IAAI,KAAA;EAC/D,gBAAgB,sBAAsB,iBAAiB,KAAK,IAAI,KAAA;EAEhE,UAAA,oBAAC,OAAD,EAAM,SAAc,CAAA;CACjB,CAAA;CAGP,MAAM,UAAU,eACP;EAAE;EAAU;EAAU;EAAkB;EAAa;CAAO,IACnE;EAAC;EAAU;EAAU;CAAM,CAC7B;CAEA,IAAI,CAAC,UACH,OAAO,oBAAC,0BAAD;EAA0B,OAAO;EAAU,UAAA;CAA8B,CAAA;CAGlF,OACE,oBAAC,0BAAD;EAA0B,OAAO;EAC/B,UAAA,oBAAC,OAAO,MAAR;GAAa,eAAe,YAAY,eAAe,QAAQ,IAAI;GAAS;GAC1E,UAAA,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,OAAO,UAAR,EAAiB,KAAK,OAAO,SAAW,CAAA,GACxC,oBAAC,OAAO,YAAR;IAAmB,KAAK,OAAO;IAC7B,UAAA,qBAAC,OAAO,SAAR;KAAgB,KAAK;KAAkB,KAAK,OAAO;KAAnD,UAAA,CACG,KACD,oBAAC,KAAD;MAAK,KAAK,OAAO;MACf,UAAA,oBAAC,mBAAD;OACE,cAAW;OACX,cAAa;OACb,eAAe,eAAe,KAAK;OACnC,MAAK;MACN,CAAA;KACE,CAAA,CACS;;GACC,CAAA,CACb,EAAA,CAAA;EACG,CAAA;CACW,CAAA;AAE9B,CAAC;AAiBD,gBAAgB,cAAc;AAE9B,IAAA,0BAAe,kBAAkB,iBAAiB;CAChD,MAAM;CACN,QAAQ;CACR,eAAe;CACf,MAAM;CACN,MAAM;CACN,mBAAmB;AACrB,CAAC"}
1
+ {"version":3,"file":"BitkitUISideNav.js","names":[],"sources":["../../../lib/components/BitkitUISideNav/BitkitUISideNav.tsx"],"sourcesContent":["import { Box, type BoxProps } from '@chakra-ui/react/box';\nimport { Drawer } from '@chakra-ui/react/drawer';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { forwardRef, type ReactNode, useMemo, useRef, useState } from 'react';\n\nimport useResponsive from '../../hooks/useResponsive';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport UISideNavBody from './UISideNavBody';\nimport { UISideNavContextProvider } from './UISideNavContext';\nimport UISideNavFooter from './UISideNavFooter';\nimport UISideNavFooterHeading from './UISideNavFooterHeading';\nimport UISideNavItem from './UISideNavItem';\nimport UISideNavLink from './UISideNavLink';\nimport UISideNavWorkspaceSelector from './UISideNavWorkspaceSelector';\n\nexport interface BitkitUISideNavProps extends Omit<BoxProps, 'children'> {\n children: ReactNode;\n /**\n * Forces the desktop rail open or shut, overriding the pointer and focus behaviour. Leave it\n * unset for the normal rail. Set it to `true` while something the rail does not know about is\n * anchored to it, such as a popover opened from the footer.\n */\n expanded?: boolean;\n /** Mobile drawer visibility. Controlled, because the header's menu button is what opens it. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\n/**\n * The platform's primary navigation. It expects a workspace selector, a body of navigation\n * items, and an optional footer:\n *\n * ```tsx\n * <BitkitUISideNav open={open} onOpenChange={setOpen}>\n * <BitkitUISideNav.WorkspaceSelector\n * workspaces={workspaces}\n * value={workspace}\n * onValueChange={setWorkspace}\n * />\n * <BitkitUISideNav.Body>\n * <BitkitUISideNav.Item href=\"/dashboard\" icon={IconDashboard} selectedIcon={IconDashboardFilled} selected>\n * Dashboard\n * </BitkitUISideNav.Item>\n * </BitkitUISideNav.Body>\n * <BitkitUISideNav.Footer>…</BitkitUISideNav.Footer>\n * </BitkitUISideNav>\n * ```\n *\n * On desktop it is a 72px rail that widens to 256px under the pointer and while anything inside\n * it holds keyboard focus. The rows are always laid out at the full width and clipped, so the\n * labels appear without any text reflowing. It will not collapse while the workspace menu is\n * open. There is no pin: the rail is the resting state.\n *\n * Below the `tablet` breakpoint it becomes an overlay drawer instead, opened by the header rather\n * than by hovering. Pass `open` and `onOpenChange`; they are ignored on desktop.\n *\n * It does not position itself. The consumer decides where it sits and how tall it is, which is\n * what lets a page slot it under a header of any height. The expanded rail overlays the page\n * rather than pushing it, so give it a `position` of your own and reserve 72px beside it.\n *\n * Descendants can read the current state with `useUISideNav()`.\n */\nconst BitkitUISideNav = forwardRef<HTMLElement, BitkitUISideNavProps>((props, ref) => {\n const {\n children,\n css,\n expanded: expandedProp,\n onBlur,\n onFocus,\n onOpenChange,\n onPointerEnter,\n onPointerLeave,\n open = false,\n ...rest\n } = props;\n\n const { isMobile } = useResponsive();\n\n const [pointerInside, setPointerInside] = useState(false);\n const [focusInside, setFocusInside] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const menuContainerRef = useRef<HTMLDivElement>(null);\n\n const recipe = useSlotRecipe({ key: 'uiSideNav' });\n const styles = recipe({ layout: isMobile ? 'drawer' : 'rail' });\n\n const expanded = isMobile || (expandedProp ?? (pointerInside || focusInside || menuOpen));\n const tracksPointer = !isMobile && expandedProp === undefined;\n\n // The consumer's handlers run first and unconditionally: the rail listens on the same events,\n // it does not own them.\n const handleFocus: NonNullable<BitkitUISideNavProps['onFocus']> = (event) => {\n onFocus?.(event);\n if (!tracksPointer || !event.target.matches(':focus-visible')) return;\n setFocusInside(true);\n };\n\n const handleBlur: NonNullable<BitkitUISideNavProps['onBlur']> = (event) => {\n onBlur?.(event);\n // `blur` also fires when focus moves between two rows. Only a target outside the nav counts.\n if (!tracksPointer || event.currentTarget.contains(event.relatedTarget)) return;\n setFocusInside(false);\n };\n\n const handlePointerEnter: NonNullable<BitkitUISideNavProps['onPointerEnter']> = (event) => {\n onPointerEnter?.(event);\n if (tracksPointer) setPointerInside(true);\n };\n\n const handlePointerLeave: NonNullable<BitkitUISideNavProps['onPointerLeave']> = (event) => {\n onPointerLeave?.(event);\n if (tracksPointer) setPointerInside(false);\n };\n\n const nav = (\n <Box\n ref={ref}\n aria-label=\"Platform navigation\"\n asChild\n data-expanded={expanded ? '' : undefined}\n {...rest}\n css={[styles.root, css]}\n onBlur={handleBlur}\n onFocus={handleFocus}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={handlePointerLeave}\n >\n <nav>{children}</nav>\n </Box>\n );\n\n const context = useMemo(\n () => ({ expanded, isMobile, menuContainerRef, setMenuOpen, styles }),\n [expanded, isMobile, styles],\n );\n\n if (!isMobile) {\n return <UISideNavContextProvider value={context}>{nav}</UISideNavContextProvider>;\n }\n\n return (\n <UISideNavContextProvider value={context}>\n <Drawer.Root onOpenChange={(details) => onOpenChange?.(details.open)} open={open}>\n <Portal>\n <Drawer.Backdrop css={styles.backdrop} />\n <Drawer.Positioner css={styles.positioner}>\n <Drawer.Content ref={menuContainerRef} aria-label=\"Navigation\" css={styles.content}>\n {nav}\n <Box css={styles.closeButton}>\n <BitkitCloseButton\n aria-label=\"Close navigation\"\n colorVariant=\"white\"\n onClick={() => onOpenChange?.(false)}\n size=\"md\"\n />\n </Box>\n </Drawer.Content>\n </Drawer.Positioner>\n </Portal>\n </Drawer.Root>\n </UISideNavContextProvider>\n );\n});\n\nexport { uiSideNavRailWidth } from '../../theme/slot-recipes/UISideNav.recipe';\nexport { type BitkitUISideNavBodyProps } from './UISideNavBody';\nexport { type BitkitUISideNavFooterProps } from './UISideNavFooter';\nexport { type BitkitUISideNavFooterHeadingProps } from './UISideNavFooterHeading';\nexport { type BitkitUISideNavItemProps } from './UISideNavItem';\nexport {\n type BitkitUISideNavLinkAsAnchorProps,\n type BitkitUISideNavLinkAsButtonProps,\n type BitkitUISideNavLinkProps,\n} from './UISideNavLink';\nexport {\n type BitkitUISideNavWorkspaceProps,\n type BitkitUISideNavWorkspaceSelectorProps,\n} from './UISideNavWorkspaceSelector';\n\nBitkitUISideNav.displayName = 'BitkitUISideNav';\n\nexport default withSubComponents(BitkitUISideNav, {\n Body: UISideNavBody,\n Footer: UISideNavFooter,\n FooterHeading: UISideNavFooterHeading,\n Item: UISideNavItem,\n Link: UISideNavLink,\n WorkspaceSelector: UISideNavWorkspaceSelector,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAM,kBAAkB,YAA+C,OAAO,QAAQ;CACpF,MAAM,EACJ,UACA,KACA,UAAU,cACV,QACA,SACA,cACA,gBACA,gBACA,OAAO,OACP,GAAG,SACD;CAEJ,MAAM,EAAE,aAAa,cAAc;CAEnC,MAAM,CAAC,eAAe,oBAAoB,SAAS,KAAK;CACxD,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,mBAAmB,OAAuB,IAAI;CAGpD,MAAM,SADS,cAAc,EAAE,KAAK,YAAY,CACjC,CAAA,CAAO,EAAE,QAAQ,WAAW,WAAW,OAAO,CAAC;CAE9D,MAAM,WAAW,aAAa,iBAAiB,iBAAiB,eAAe;CAC/E,MAAM,gBAAgB,CAAC,YAAY,iBAAiB,KAAA;CAIpD,MAAM,eAA6D,UAAU;EAC3E,UAAU,KAAK;EACf,IAAI,CAAC,iBAAiB,CAAC,MAAM,OAAO,QAAQ,gBAAgB,GAAG;EAC/D,eAAe,IAAI;CACrB;CAEA,MAAM,cAA2D,UAAU;EACzE,SAAS,KAAK;EAEd,IAAI,CAAC,iBAAiB,MAAM,cAAc,SAAS,MAAM,aAAa,GAAG;EACzE,eAAe,KAAK;CACtB;CAEA,MAAM,sBAA2E,UAAU;EACzF,iBAAiB,KAAK;EACtB,IAAI,eAAe,iBAAiB,IAAI;CAC1C;CAEA,MAAM,sBAA2E,UAAU;EACzF,iBAAiB,KAAK;EACtB,IAAI,eAAe,iBAAiB,KAAK;CAC3C;CAEA,MAAM,MACJ,oBAAC,KAAD;EACO;EACL,cAAW;EACX,SAAA;EACA,iBAAe,WAAW,KAAK,KAAA;EAC/B,GAAI;EACJ,KAAK,CAAC,OAAO,MAAM,GAAG;EACtB,QAAQ;EACR,SAAS;EACT,gBAAgB;EAChB,gBAAgB;EAEhB,UAAA,oBAAC,OAAD,EAAM,SAAc,CAAA;CACjB,CAAA;CAGP,MAAM,UAAU,eACP;EAAE;EAAU;EAAU;EAAkB;EAAa;CAAO,IACnE;EAAC;EAAU;EAAU;CAAM,CAC7B;CAEA,IAAI,CAAC,UACH,OAAO,oBAAC,0BAAD;EAA0B,OAAO;EAAU,UAAA;CAA8B,CAAA;CAGlF,OACE,oBAAC,0BAAD;EAA0B,OAAO;EAC/B,UAAA,oBAAC,OAAO,MAAR;GAAa,eAAe,YAAY,eAAe,QAAQ,IAAI;GAAS;GAC1E,UAAA,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,OAAO,UAAR,EAAiB,KAAK,OAAO,SAAW,CAAA,GACxC,oBAAC,OAAO,YAAR;IAAmB,KAAK,OAAO;IAC7B,UAAA,qBAAC,OAAO,SAAR;KAAgB,KAAK;KAAkB,cAAW;KAAa,KAAK,OAAO;KAA3E,UAAA,CACG,KACD,oBAAC,KAAD;MAAK,KAAK,OAAO;MACf,UAAA,oBAAC,mBAAD;OACE,cAAW;OACX,cAAa;OACb,eAAe,eAAe,KAAK;OACnC,MAAK;MACN,CAAA;KACE,CAAA,CACS;;GACC,CAAA,CACb,EAAA,CAAA;EACG,CAAA;CACW,CAAA;AAE9B,CAAC;AAiBD,gBAAgB,cAAc;AAE9B,IAAA,0BAAe,kBAAkB,iBAAiB;CAChD,MAAM;CACN,QAAQ;CACR,eAAe;CACf,MAAM;CACN,MAAM;CACN,mBAAmB;AACrB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitrise/bitkit-v2",
3
3
  "private": false,
4
- "version": "0.3.460-beta.2905",
4
+ "version": "0.3.460-beta.2908",
5
5
  "description": "Bitrise Design System Components built with Chakra UI V3",
6
6
  "keywords": [
7
7
  "react",