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

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.
@@ -97,7 +97,7 @@ var ClearAllButton = ({ state }) => {
97
97
  });
98
98
  };
99
99
  var BitkitMultiselect = forwardRef((props, ref) => {
100
- const { children, constrained = false, defaultValue, emptyHelperText, emptyLabel, isLoading, isValueInvalid, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, disablePortal, searchValue, selectProps, size = "lg", state, triggerProps, value, ...fieldProps } = props;
100
+ const { children, constrained = false, defaultValue, emptyHelperText, emptyLabel, 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,
@@ -184,6 +184,7 @@ var BitkitMultiselect = forwardRef((props, ref) => {
184
184
  isLoading,
185
185
  onScrolledToBottom,
186
186
  onSearchChange,
187
+ searchPlaceholder,
187
188
  searchValue,
188
189
  size,
189
190
  children
@@ -1 +1 @@
1
- {"version":3,"file":"BitkitMultiselect.js","names":[],"sources":["../../../lib/components/BitkitMultiselect/BitkitMultiselect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { useControllableState } from '@chakra-ui/react/hooks';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\nimport BitkitMultiselectMenu, {\n type BitkitMultiselectMenuItemProps,\n} from '../BitkitMultiselectMenu/BitkitMultiselectMenu';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport BitkitSelectMenuAction, { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\nimport BitkitTag from '../BitkitTag/BitkitTag';\n\nexport type BitkitMultiselectTriggerProps = SelectTriggerProps;\n\nexport type BitkitMultiselectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n children?: BitkitSelectMenuActionChild;\n constrained?: boolean;\n defaultValue?: Array<string>;\n isLoading?: boolean;\n /** Reddens the offending tags and errors the field, like BitkitTagsInput. Pair with `errorText`. */\n isValueInvalid?: (value: string) => boolean;\n items: Array<BitkitMultiselectMenuItemProps>;\n onValueChange?: (newVal: Array<string>) => void;\n placeholder?: string;\n disablePortal?: boolean;\n selectProps?: Omit<SelectRootProps, 'collection' | 'defaultValue' | 'multiple' | 'onValueChange' | 'value'>;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n triggerProps?: BitkitMultiselectTriggerProps;\n value?: Array<string>;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\ntype MultiselectState = BitkitMultiselectProps['state'];\n\nconst NON_INTERACTIVE_STATES: ReadonlyArray<MultiselectState> = ['disabled', 'readOnly', 'error', 'warning'];\n\n/** Whether the clear-all button should be rendered (and the overlay should reserve extra room for it). */\nconst shouldShowClearButton = (constrained: boolean, selectionCount: number, state: MultiselectState): boolean =>\n !constrained && selectionCount > 0 && !NON_INTERACTIVE_STATES.includes(state);\n\ntype MultiselectOverlayProps = {\n constrained: boolean;\n isValueInvalid?: (value: string) => boolean;\n placeholder?: string;\n size: 'md' | 'lg';\n state?: MultiselectState;\n};\n\nconst MultiselectOverlay = ({ constrained, isValueInvalid, placeholder, size, state }: MultiselectOverlayProps) => {\n const select = useSelectContext();\n const recipe = useSlotRecipe({ key: 'multiselect' });\n const styles = recipe({ constrained, size });\n\n const selectedItems = select.selectedItems as Array<BitkitMultiselectMenuItemProps>;\n const selectedValues = select.value;\n const isDisabled = state === 'disabled';\n const isReadOnly = state === 'readOnly';\n const tagState = isDisabled ? 'disabled' : undefined;\n // Overlay reserves ~96px on the right when the clear-all is visible (to keep the chevron +\n // clear button out of the tag row), otherwise ~48px (chevron only). Using spacing tokens\n // because '80' (the minimum that fits both icons with gaps) isn't a valid token; '96' is\n // the next one up.\n const overlayPaddingEnd = shouldShowClearButton(constrained, selectedValues.length, state) ? '96' : '48';\n\n const resolvedPlaceholder = placeholder ?? (isReadOnly ? '(not selected)' : 'Select options');\n const canRemove = !isDisabled && !isReadOnly;\n\n const hasSelection = selectedValues.length > 0;\n\n const renderTags = () => {\n if (constrained) {\n return (\n <>\n {hasSelection && (\n <TagSlot>\n <BitkitTag\n size=\"sm\"\n colorVariant={selectedValues.some((v) => isValueInvalid?.(v)) ? 'red' : 'neutral'}\n labelText={String(selectedValues.length)}\n state={tagState}\n onRemove={canRemove ? () => select.clearValue() : undefined}\n />\n </TagSlot>\n )}\n <Text as=\"span\" css={styles.placeholderText} data-placeholder={hasSelection ? undefined : ''}>\n {resolvedPlaceholder}\n </Text>\n </>\n );\n }\n\n if (selectedItems.length === 0) {\n return (\n <Text as=\"span\" css={styles.placeholderText} data-placeholder=\"\">\n {resolvedPlaceholder}\n </Text>\n );\n }\n\n return selectedItems.map((item) => (\n <TagSlot key={item.value}>\n <BitkitTag\n size=\"sm\"\n colorVariant={isValueInvalid?.(item.value) ? 'red' : 'neutral'}\n labelText={item.label}\n state={tagState}\n onRemove={canRemove ? () => select.setValue(selectedValues.filter((v) => v !== item.value)) : undefined}\n />\n </TagSlot>\n ));\n };\n\n return (\n <Box css={styles.overlay} pointerEvents=\"none\" paddingInlineEnd={overlayPaddingEnd}>\n <Box css={styles.tagsBlock}>{renderTags()}</Box>\n </Box>\n );\n};\n\n// minWidth=\"0\" keeps the slot shrinkable inside the wrapping tags row: without it the slot holds\n// the tag's min-content width, which is the whole label now that it no longer wraps, and a long\n// selection overflows the control instead of ellipsizing. maxWidth=\"100%\" caps the other direction:\n// a tag alone on its own wrapped line has no sibling to shrink against, so it needs the row as its\n// own ceiling.\nconst TagSlot = ({ children }: { children: ReactNode }) => (\n <Box data-slot=\"tag\" display=\"inline-flex\" minWidth=\"0\" maxWidth=\"100%\" pointerEvents=\"auto\">\n {children}\n </Box>\n);\n\ntype ClearAllButtonProps = {\n state?: MultiselectState;\n};\n\nconst ClearAllButton = ({ state }: ClearAllButtonProps) => {\n const select = useSelectContext();\n\n // Constrained mode has its own inline clear-all on the counter tag; this is the non-constrained trigger-level clear.\n if (!shouldShowClearButton(false, select.value.length, state)) return null;\n\n return (\n <BitkitCloseButton size=\"sm\" colorVariant=\"neutral\" aria-label=\"Clear all\" onClick={() => select.clearValue()} />\n );\n};\n\nconst BitkitMultiselect = forwardRef<HTMLDivElement, BitkitMultiselectProps>((props, ref) => {\n const {\n children,\n constrained = false,\n defaultValue,\n emptyHelperText,\n emptyLabel,\n isLoading,\n isValueInvalid,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n disablePortal,\n searchValue,\n selectProps,\n size = 'lg',\n state,\n triggerProps,\n value,\n ...fieldProps\n } = props;\n\n const { css: triggerCss, ...restTriggerProps } = triggerProps ?? {};\n\n const collection = createListCollection({\n items,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!item.disabled,\n });\n\n const recipe = useSlotRecipe({ key: 'multiselect' });\n const styles = recipe({ constrained, size });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const iconSize = size === 'lg' ? '24' : '16';\n\n const [currentValues, setCurrentValues] = useControllableState<Array<string>>({\n value,\n defaultValue: defaultValue ?? [],\n });\n const isInteractive = state !== 'disabled' && state !== 'readOnly';\n const hasInvalidValue = isInteractive && !!isValueInvalid && currentValues.some(isValueInvalid);\n const effectiveState = hasInvalidValue ? 'error' : state;\n const isInvalid = effectiveState === 'error' || !!fieldProps.errorText;\n\n return (\n <BitkitField ref={ref} state={effectiveState} {...fieldProps}>\n <Select.Root\n collection={collection}\n multiple\n {...selectProps}\n defaultValue={defaultValue}\n disabled={effectiveState === 'disabled'}\n invalid={isInvalid}\n onValueChange={(details) => {\n setCurrentValues(details.value);\n onValueChange?.(details.value);\n }}\n readOnly={effectiveState === 'readOnly'}\n // Zag's default scrollIntoView gates on `isScrollable(contentEl)`. Our content is\n // overflow:hidden + flex column (so the sticky-bottom create row stays visible while\n // the inner itemList Box scrolls). That gate fails, so keyboard highlight never\n // scrolls the items into view. Providing scrollToIndexFn bypasses the gate and lets\n // the browser's native scrollIntoView walk up to the real scroll container (itemList).\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n value={value}\n >\n <Select.Control css={styles.control} className=\"group\">\n <Select.HiddenSelect />\n <Select.Trigger\n css={[styles.trigger, triggerCss]}\n {...restTriggerProps}\n aria-readonly={effectiveState === 'readOnly' || undefined}\n />\n <MultiselectOverlay\n constrained={constrained}\n isValueInvalid={hasInvalidValue ? isValueInvalid : undefined}\n placeholder={placeholder}\n size={size}\n state={effectiveState}\n />\n <Select.IndicatorGroup css={styles.indicatorGroup}>\n {!constrained && <ClearAllButton state={effectiveState} />}\n {effectiveState === 'error' && (\n <Select.Indicator css={styles.indicator}>\n <IconErrorCircleFilled size={iconSize} color=\"icon/negative\" />\n </Select.Indicator>\n )}\n {effectiveState === 'warning' && (\n <Select.Indicator css={styles.indicator}>\n <IconWarningYellow size={iconSize} />\n </Select.Indicator>\n )}\n <Select.Indicator css={styles.indicator} asChild>\n <AssetSelectChevron />\n </Select.Indicator>\n </Select.IndicatorGroup>\n </Select.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Select.Positioner>\n <BitkitMultiselectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n searchValue={searchValue}\n size={size}\n >\n {children}\n </BitkitMultiselectMenu>\n </Select.Positioner>\n </Portal>\n </Select.Root>\n </BitkitField>\n );\n});\n\nBitkitMultiselect.displayName = 'BitkitMultiselect';\n\nexport default withSubComponents(BitkitMultiselect, { Action: BitkitSelectMenuAction });\n"],"mappings":"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"}
1
+ {"version":3,"file":"BitkitMultiselect.js","names":[],"sources":["../../../lib/components/BitkitMultiselect/BitkitMultiselect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { useControllableState } from '@chakra-ui/react/hooks';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents';\nimport BitkitCloseButton from '../BitkitCloseButton/BitkitCloseButton';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField';\nimport BitkitMultiselectMenu, {\n type BitkitMultiselectMenuItemProps,\n} from '../BitkitMultiselectMenu/BitkitMultiselectMenu';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type 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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"}
@@ -57,7 +57,7 @@ var SelectValue = ({ placeholder, state, size }) => {
57
57
  });
58
58
  };
59
59
  var BitkitSelect = forwardRef((props, ref) => {
60
- const { children, defaultValue, emptyHelperText, emptyLabel, isLoading, items, onScrolledToBottom, onSearchChange, onValueChange, placeholder, disablePortal, searchValue, selectProps, size = "lg", state, triggerProps, value, ...fieldProps } = props;
60
+ const { children, defaultValue, emptyHelperText, emptyLabel, 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 || "",
@@ -115,6 +115,7 @@ var BitkitSelect = forwardRef((props, ref) => {
115
115
  isLoading,
116
116
  onScrolledToBottom,
117
117
  onSearchChange,
118
+ searchPlaceholder,
118
119
  searchValue,
119
120
  size,
120
121
  children
@@ -1 +1 @@
1
- {"version":3,"file":"BitkitSelect.js","names":[],"sources":["../../../lib/components/BitkitSelect/BitkitSelect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron.tsx';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents.ts';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField.tsx';\nimport BitkitSelectMenu, {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuItemProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu.tsx';\nimport BitkitSelectMenuAction, {\n type BitkitSelectMenuActionChild,\n} from '../BitkitSelectMenu/BitkitSelectMenuAction.tsx';\nimport { selectSizeMap } from '../BitkitSelectMenu/SelectMenuShell.tsx';\n\nexport type BitkitSelectTriggerProps = SelectTriggerProps;\n\nexport type BitkitSelectProps = Omit<BitkitFieldProps, 'children' | 'onChange' | 'state'> & {\n children?: BitkitSelectMenuActionChild;\n defaultValue?: string;\n isLoading?: boolean;\n items: Array<BitkitSelectMenuItemProps>;\n onValueChange?: (newVal: string) => void;\n placeholder?: string;\n disablePortal?: boolean;\n selectProps?: Omit<SelectRootProps, 'collection' | 'defaultValue' | 'onValueChange' | 'size' | 'value'>;\n size?: 'md' | 'lg';\n state?: 'disabled' | 'error' | 'readOnly' | 'warning';\n triggerProps?: BitkitSelectTriggerProps;\n value?: string;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\ntype SelectValueProps = {\n placeholder?: string;\n size: NonNullable<BitkitSelectProps['size']>;\n state?: BitkitSelectProps['state'];\n};\n\nconst SelectValue = ({ placeholder, state, size }: SelectValueProps) => {\n const select = useSelectContext();\n const items = select.selectedItems as Array<BitkitSelectMenuItemProps>;\n\n const Icon = items[0]?.icon;\n const avatar = items[0]?.avatar;\n const label = items[0]?.label;\n const showAvatar = avatar !== undefined;\n\n const { avatarSize, iconSize } = selectSizeMap[size];\n\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n\n return items[0] ? (\n <Select.ValueText placeholder={placeholder ?? (state === 'readOnly' ? '(not selected)' : 'Select an option')}>\n {showAvatar ? (\n // Same avatar handling as the menu item (see BitkitSelectMenu.tsx). The `data-slot`\n // lets the trigger recipe shrink its vertical padding so the avatar row keeps the\n // 48/40 trigger height (see the `&:has([data-slot=\"avatar\"])` rule in Select.recipe.ts).\n <Box data-slot=\"avatar\" css={styles.avatar}>\n <BitkitAvatar variant=\"image\" src={avatar ?? ''} name={label} size={avatarSize} />\n </Box>\n ) : (\n Icon && <Icon css={styles.valueIcon} size={iconSize} flexShrink={0} />\n )}\n <Text as=\"span\" overflow=\"hidden\" textOverflow=\"ellipsis\" whiteSpace=\"nowrap\" minWidth=\"0\">\n {label}\n </Text>\n </Select.ValueText>\n ) : (\n <Select.ValueText\n placeholder={placeholder ?? (state === 'readOnly' ? '(not selected)' : 'Select an option')}\n display=\"block\"\n overflow=\"hidden\"\n textOverflow=\"ellipsis\"\n whiteSpace=\"nowrap\"\n />\n );\n};\n\nconst BitkitSelect = forwardRef<HTMLDivElement, BitkitSelectProps>((props: BitkitSelectProps, ref) => {\n const {\n children,\n defaultValue,\n emptyHelperText,\n emptyLabel,\n isLoading,\n items,\n onScrolledToBottom,\n onSearchChange,\n onValueChange,\n placeholder,\n disablePortal,\n searchValue,\n selectProps,\n size = 'lg',\n state,\n triggerProps,\n value,\n ...fieldProps\n } = props;\n\n const collection = createListCollection({\n items,\n groupBy: (item) => item.group || '',\n isItemDisabled: (item) => !!item.disabled,\n });\n\n const { disablePortal: disablePortalFromContext } = useContext(BitkitPortalContext);\n const isInvalid = state === 'error' || !!fieldProps.errorText;\n\n return (\n <BitkitField ref={ref} state={state} {...fieldProps}>\n <Select.Root\n collection={collection}\n size={size}\n {...selectProps}\n defaultValue={defaultValue ? [defaultValue] : undefined}\n disabled={state === 'disabled'}\n hasStatusIcon={state === 'error' || state === 'warning'}\n invalid={isInvalid}\n onValueChange={(newVal) => onValueChange?.(newVal.value[0])}\n readOnly={state === 'readOnly'}\n // Bypass Zag's isScrollable(contentEl) gate — our Content is overflow:hidden flex\n // column, so the real scroll container is itemList. See BitkitMultiselect for why.\n scrollToIndexFn={({ getElement }) => getElement()?.scrollIntoView({ block: 'nearest' })}\n // Stay controlled whenever `value` is provided — including `''` (\"nothing selected\"). A\n // truthiness check (`value ? … : undefined`) passes `undefined` for `''`, which flips\n // Select.Root to uncontrolled: it then ignores `value`, so resets don't take and\n // onValueChange stops firing when the same option is re-selected.\n value={value === undefined ? undefined : value ? [value] : []}\n >\n <Select.Control className=\"group\">\n <Select.HiddenSelect />\n <Select.Trigger {...triggerProps} aria-readonly={state === 'readOnly' || undefined}>\n <SelectValue placeholder={placeholder} size={size} state={state} />\n </Select.Trigger>\n <Select.IndicatorGroup>\n {state === 'error' && (\n <Select.Indicator>\n <IconErrorCircleFilled size={size === 'lg' ? '24' : '16'} color=\"icon/negative\" />\n </Select.Indicator>\n )}\n {state === 'warning' && (\n <Select.Indicator>\n <IconWarningYellow size={size === 'lg' ? '24' : '16'} />\n </Select.Indicator>\n )}\n <Select.Indicator asChild>\n <AssetSelectChevron />\n </Select.Indicator>\n </Select.IndicatorGroup>\n </Select.Control>\n <Portal disabled={disablePortal || disablePortalFromContext}>\n <Select.Positioner>\n <BitkitSelectMenu\n collection={collection}\n emptyHelperText={emptyHelperText}\n emptyLabel={emptyLabel}\n isLoading={isLoading}\n onScrolledToBottom={onScrolledToBottom}\n onSearchChange={onSearchChange}\n searchValue={searchValue}\n size={size}\n >\n {children}\n </BitkitSelectMenu>\n </Select.Positioner>\n </Portal>\n </Select.Root>\n </BitkitField>\n );\n});\n\nBitkitSelect.displayName = 'BitkitSelect';\n\nexport default withSubComponents(BitkitSelect, { Action: BitkitSelectMenuAction });\n"],"mappings":"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"}
1
+ {"version":3,"file":"BitkitSelect.js","names":[],"sources":["../../../lib/components/BitkitSelect/BitkitSelect.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { createListCollection } from '@chakra-ui/react/collection';\nimport { Portal } from '@chakra-ui/react/portal';\nimport { Select, type SelectRootProps, type SelectTriggerProps, useSelectContext } from '@chakra-ui/react/select';\nimport { useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, useContext } from 'react';\n\nimport { IconErrorCircleFilled, IconWarningYellow } from '../../icons';\nimport AssetSelectChevron from '../../utilities/AssetSelectChevron.tsx';\nimport BitkitPortalContext from '../../utilities/BitkitPortalContext';\nimport { withSubComponents } from '../../utilities/withSubComponents.ts';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport BitkitField, { type BitkitFieldProps } from '../BitkitField/BitkitField.tsx';\nimport BitkitSelectMenu, {\n type BitkitSelectMenuEmptyStateProps,\n type 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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"}
@@ -25,7 +25,7 @@ var selectSizeMap = {
25
25
  var SelectMenuShell = ({ NS, children, collection, contentProps, contentRef, emptyHelperText, emptyLabel = "No matching options", errorHelperText, errorLabel, iconSize, isLoading = false, onScrolledToBottom, onSearchChange, renderItem, searchPlaceholder, searchValue, spinnerSize, styles }) => {
26
26
  const { css: contentCss, ...restContentProps } = contentProps ?? {};
27
27
  const isEmpty = collection.size === 0;
28
- const hasError = errorLabel !== void 0;
28
+ const hasError = !isLoading && !!errorLabel;
29
29
  const hasAction = isValidElement(children);
30
30
  const wasAtBottom = useRef(false);
31
31
  const handleItemListScroll = (event) => {
@@ -73,7 +73,6 @@ var SelectMenuShell = ({ NS, children, collection, contentProps, contentRef, emp
73
73
  })]
74
74
  }) : /* @__PURE__ */ jsxs(Box, {
75
75
  css: styles.itemList,
76
- "data-slot": "item-list",
77
76
  onScroll: handleItemListScroll,
78
77
  children: [
79
78
  isLoading && /* @__PURE__ */ jsxs(Box, {
@@ -1 +1 @@
1
- {"version":3,"file":"SelectMenuShell.js","names":[],"sources":["../../../lib/components/BitkitSelectMenu/SelectMenuShell.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { type Combobox } from '@chakra-ui/react/combobox';\nimport { type Select, type SelectContentProps } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { chakra, type SystemStyleObject } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { type ChangeEvent, isValidElement, type ReactNode, type Ref, type UIEvent, useRef } from 'react';\n\nimport { IconCross, IconCrossCircle, IconMagnifier } from '../../icons';\nimport BitkitGroupHeading from '../BitkitGroupHeading/BitkitGroupHeading';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuErrorStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from './BitkitSelectMenu';\nimport { type BitkitSelectMenuActionChild, SelectMenuShellContextProvider } from './BitkitSelectMenuAction';\n\nexport const selectSizeMap = {\n lg: { avatarSize: '32', iconSize: '24', spinnerSize: 'lg' },\n md: { avatarSize: '24', iconSize: '16', spinnerSize: 'md' },\n} as const;\n\nexport type SelectSizes = (typeof selectSizeMap)[keyof typeof selectSizeMap];\n\n/**\n * Internal shared shell for BitkitSelectMenu and BitkitMultiselectMenu.\n * Handles Content wrapper, search, loading, empty state, groups, and the action slot —\n * the caller only provides per-item rendering via renderItem and any sub-components\n * (e.g. <BitkitSelectMenu.Action />) via children.\n */\nexport type SelectMenuShellProps<T extends { value: string; group?: string }> = {\n NS: typeof Select | typeof Combobox;\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<T>;\n contentProps?: SelectContentProps;\n contentRef?: Ref<HTMLDivElement>;\n iconSize: SelectSizes['iconSize'];\n isLoading?: boolean;\n renderItem: (item: T) => ReactNode;\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\nexport const SelectMenuShell = <T extends { value: string; group?: string }>({\n NS,\n children,\n collection,\n contentProps,\n contentRef,\n emptyHelperText,\n emptyLabel = 'No matching options',\n errorHelperText,\n errorLabel,\n iconSize,\n isLoading = false,\n onScrolledToBottom,\n onSearchChange,\n renderItem,\n searchPlaceholder,\n searchValue,\n spinnerSize,\n styles,\n}: SelectMenuShellProps<T>) => {\n const { css: contentCss, ...restContentProps } = contentProps ?? {};\n const isEmpty = collection.size === 0;\n const hasError = errorLabel !== undefined;\n const hasAction = isValidElement(children);\n\n const wasAtBottom = useRef(false);\n const handleItemListScroll = (event: UIEvent<HTMLDivElement>) => {\n const list = event.currentTarget;\n const atBottom = list.scrollTop >= list.scrollHeight - list.offsetHeight - 1;\n // Only the transition into \"at the end\", so resting at the bottom doesn't keep asking.\n if (atBottom && !wasAtBottom.current) onScrolledToBottom?.();\n wasAtBottom.current = atBottom;\n };\n\n return (\n <SelectMenuShellContextProvider value={{ iconSize, styles }}>\n <NS.Content css={[styles.content, contentCss]} ref={contentRef} {...restContentProps}>\n {/*\n Without a search input, Zag's getInitialFocus (see @zag-js/dom-query) picks the first\n tabbable descendant as the menu's initial focus target — which would be the action\n slot, visually confusing. A hidden [data-autofocus] span wins over tabbables in\n getInitialFocus's querySelector, so we focus an invisible element instead and Zag's\n aria-activedescendant drives the highlight like a normal Select would.\n */}\n {!onSearchChange && hasAction && <span data-autofocus=\"\" tabIndex={-1} aria-hidden=\"true\" />}\n {onSearchChange && (\n <SelectMenuSearch\n iconSize={iconSize}\n placeholder={searchPlaceholder}\n styles={styles}\n value={searchValue ?? ''}\n onSearchChange={onSearchChange}\n />\n )}\n {hasError ? (\n <Box css={styles.errorState}>\n <IconCrossCircle css={styles.errorIcon} size={iconSize} />\n <Box css={styles.errorTextBlock}>\n <Text css={styles.errorLabel}>{errorLabel}</Text>\n {errorHelperText && <Text css={styles.itemHelperText}>{errorHelperText}</Text>}\n </Box>\n </Box>\n ) : (\n <Box css={styles.itemList} data-slot=\"item-list\" onScroll={handleItemListScroll}>\n {isLoading && (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n )}\n {!isLoading && isEmpty && (\n <Box css={styles.emptyState}>\n <Text css={styles.itemLabel}>{emptyLabel}</Text>\n {emptyHelperText && <Text css={styles.itemHelperText}>{emptyHelperText}</Text>}\n </Box>\n )}\n {!isLoading &&\n !isEmpty &&\n collection.group().map(([type, group]) => (\n <NS.ItemGroup key={type}>\n {type && (\n <NS.ItemGroupLabel asChild>\n <BitkitGroupHeading label={type} paddingBlock=\"12\" paddingInline=\"16\" />\n </NS.ItemGroupLabel>\n )}\n {group.map(renderItem)}\n </NS.ItemGroup>\n ))}\n </Box>\n )}\n {children}\n </NS.Content>\n </SelectMenuShellContextProvider>\n );\n};\n\ntype SelectMenuSearchProps = {\n iconSize: '16' | '24';\n onSearchChange: (searchText: string) => void;\n placeholder?: string;\n styles: Record<string, SystemStyleObject>;\n value: string;\n};\n\nconst SelectMenuSearch = ({\n iconSize,\n onSearchChange,\n placeholder = 'Search...',\n styles,\n value,\n}: SelectMenuSearchProps) => (\n <Box css={styles.searchInputGroup}>\n <IconMagnifier size={iconSize} color=\"icon/tertiary\" />\n <chakra.input\n aria-label=\"Search\"\n css={styles.searchInput}\n placeholder={placeholder}\n value={value}\n onChange={(event: ChangeEvent<HTMLInputElement>) => onSearchChange(event.target.value)}\n onKeyDown={(event) => {\n // Zag's Select.Content keyDown listener fires ITEM.CLICK + preventDefault on Space\n // regardless of which descendant is focused. That would eat the space before the\n // input can type it. Enter is left alone so \"type-then-Enter-to-select\" still works.\n if (event.key === ' ') event.stopPropagation();\n }}\n />\n {value && (\n <chakra.button\n type=\"button\"\n css={styles.searchClear}\n aria-label=\"Clear search\"\n onClick={() => onSearchChange('')}\n >\n <IconCross size={iconSize} />\n </chakra.button>\n )}\n </Box>\n);\n"],"mappings":";;;;;;;;;;;;AAmBA,IAAa,gBAAgB;CAC3B,IAAI;EAAE,YAAY;EAAM,UAAU;EAAM,aAAa;CAAK;CAC1D,IAAI;EAAE,YAAY;EAAM,UAAU;EAAM,aAAa;CAAK;AAC5D;AA0BA,IAAa,mBAAgE,EAC3E,IACA,UACA,YACA,cACA,YACA,iBACA,aAAa,uBACb,iBACA,YACA,UACA,YAAY,OACZ,oBACA,gBACA,YACA,mBACA,aACA,aACA,aAC6B;CAC7B,MAAM,EAAE,KAAK,YAAY,GAAG,qBAAqB,gBAAgB,CAAC;CAClE,MAAM,UAAU,WAAW,SAAS;CACpC,MAAM,WAAW,eAAe,KAAA;CAChC,MAAM,YAAY,eAAe,QAAQ;CAEzC,MAAM,cAAc,OAAO,KAAK;CAChC,MAAM,wBAAwB,UAAmC;EAC/D,MAAM,OAAO,MAAM;EACnB,MAAM,WAAW,KAAK,aAAa,KAAK,eAAe,KAAK,eAAe;EAE3E,IAAI,YAAY,CAAC,YAAY,SAAS,qBAAqB;EAC3D,YAAY,UAAU;CACxB;CAEA,OACE,oBAAC,gCAAD;EAAgC,OAAO;GAAE;GAAU;EAAO;EACxD,UAAA,qBAAC,GAAG,SAAJ;GAAY,KAAK,CAAC,OAAO,SAAS,UAAU;GAAG,KAAK;GAAY,GAAI;GAApE,UAAA;IAQG,CAAC,kBAAkB,aAAa,oBAAC,QAAD;KAAM,kBAAe;KAAG,UAAU;KAAI,eAAY;IAAQ,CAAA;IAC1F,kBACC,oBAAC,kBAAD;KACY;KACV,aAAa;KACL;KACR,OAAO,eAAe;KACN;IACjB,CAAA;IAEF,WACC,qBAAC,KAAD;KAAK,KAAK,OAAO;KAAjB,UAAA,CACE,oBAAC,iBAAD;MAAiB,KAAK,OAAO;MAAW,MAAM;KAAW,CAAA,GACzD,qBAAC,KAAD;MAAK,KAAK,OAAO;MAAjB,UAAA,CACE,oBAAC,MAAD;OAAM,KAAK,OAAO;OAAa,UAAA;MAAiB,CAAA,GAC/C,mBAAmB,oBAAC,MAAD;OAAM,KAAK,OAAO;OAAiB,UAAA;MAAsB,CAAA,CAC1E;KACF,CAAA,CAAA;IAEL,CAAA,IAAA,qBAAC,KAAD;KAAK,KAAK,OAAO;KAAU,aAAU;KAAY,UAAU;KAA3D,UAAA;MACG,aACC,qBAAC,KAAD;OAAK,KAAK,OAAO;OAAjB,UAAA,CACE,oBAAC,SAAD;QAAS,SAAQ;QAAS,MAAM;QAAa,KAAK,OAAO;OAAc,CAAA,GACvE,oBAAC,MAAD;QAAM,KAAK,OAAO;QAAkB,UAAA;OAAgB,CAAA,CACjD;;MAEN,CAAC,aAAa,WACb,qBAAC,KAAD;OAAK,KAAK,OAAO;OAAjB,UAAA,CACE,oBAAC,MAAD;QAAM,KAAK,OAAO;QAAY,UAAA;OAAiB,CAAA,GAC9C,mBAAmB,oBAAC,MAAD;QAAM,KAAK,OAAO;QAAiB,UAAA;OAAsB,CAAA,CAC1E;;MAEN,CAAC,aACA,CAAC,WACD,WAAW,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,WAC7B,qBAAC,GAAG,WAAJ,EAAA,UAAA,CACG,QACC,oBAAC,GAAG,gBAAJ;OAAmB,SAAA;OACjB,UAAA,oBAAC,oBAAD;QAAoB,OAAO;QAAM,cAAa;QAAK,eAAc;OAAM,CAAA;MACtD,CAAA,GAEpB,MAAM,IAAI,UAAU,CACT,EAAA,GAPK,IAOL,CACf;KACA;;IAEN;GACS;;CACkB,CAAA;AAEpC;AAUA,IAAM,oBAAoB,EACxB,UACA,gBACA,cAAc,aACd,QACA,YAEA,qBAAC,KAAD;CAAK,KAAK,OAAO;CAAjB,UAAA;EACE,oBAAC,eAAD;GAAe,MAAM;GAAU,OAAM;EAAiB,CAAA;EACtD,oBAAC,OAAO,OAAR;GACE,cAAW;GACX,KAAK,OAAO;GACC;GACN;GACP,WAAW,UAAyC,eAAe,MAAM,OAAO,KAAK;GACrF,YAAY,UAAU;IAIpB,IAAI,MAAM,QAAQ,KAAK,MAAM,gBAAgB;GAC/C;EACD,CAAA;EACA,SACC,oBAAC,OAAO,QAAR;GACE,MAAK;GACL,KAAK,OAAO;GACZ,cAAW;GACX,eAAe,eAAe,EAAE;GAEhC,UAAA,oBAAC,WAAD,EAAW,MAAM,SAAW,CAAA;EACf,CAAA;CAEd"}
1
+ {"version":3,"file":"SelectMenuShell.js","names":[],"sources":["../../../lib/components/BitkitSelectMenu/SelectMenuShell.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { type Combobox } from '@chakra-ui/react/combobox';\nimport { type Select, type SelectContentProps } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { chakra, type SystemStyleObject } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { type ChangeEvent, isValidElement, type ReactNode, type Ref, type UIEvent, useRef } from 'react';\n\nimport { IconCross, IconCrossCircle, IconMagnifier } from '../../icons';\nimport BitkitGroupHeading from '../BitkitGroupHeading/BitkitGroupHeading';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuErrorStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from './BitkitSelectMenu';\nimport { type BitkitSelectMenuActionChild, SelectMenuShellContextProvider } from './BitkitSelectMenuAction';\n\nexport const selectSizeMap = {\n lg: { avatarSize: '32', iconSize: '24', spinnerSize: 'lg' },\n md: { avatarSize: '24', iconSize: '16', spinnerSize: 'md' },\n} as const;\n\nexport type SelectSizes = (typeof selectSizeMap)[keyof typeof selectSizeMap];\n\n/**\n * Internal shared shell for BitkitSelectMenu and BitkitMultiselectMenu.\n * Handles Content wrapper, search, loading, empty state, groups, and the action slot —\n * the caller only provides per-item rendering via renderItem and any sub-components\n * (e.g. <BitkitSelectMenu.Action />) via children.\n */\nexport type SelectMenuShellProps<T extends { value: string; group?: string }> = {\n NS: typeof Select | typeof Combobox;\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<T>;\n contentProps?: SelectContentProps;\n contentRef?: Ref<HTMLDivElement>;\n iconSize: SelectSizes['iconSize'];\n isLoading?: boolean;\n renderItem: (item: T) => ReactNode;\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\nexport const SelectMenuShell = <T extends { value: string; group?: string }>({\n NS,\n children,\n collection,\n contentProps,\n contentRef,\n emptyHelperText,\n emptyLabel = 'No matching options',\n errorHelperText,\n errorLabel,\n iconSize,\n isLoading = false,\n onScrolledToBottom,\n onSearchChange,\n renderItem,\n searchPlaceholder,\n searchValue,\n spinnerSize,\n styles,\n}: SelectMenuShellProps<T>) => {\n const { css: contentCss, ...restContentProps } = contentProps ?? {};\n const isEmpty = collection.size === 0;\n const hasError = !isLoading && !!errorLabel;\n const hasAction = isValidElement(children);\n\n const wasAtBottom = useRef(false);\n const handleItemListScroll = (event: UIEvent<HTMLDivElement>) => {\n const list = event.currentTarget;\n const atBottom = list.scrollTop >= list.scrollHeight - list.offsetHeight - 1;\n // Only the transition into \"at the end\", so resting at the bottom doesn't keep asking.\n if (atBottom && !wasAtBottom.current) onScrolledToBottom?.();\n wasAtBottom.current = atBottom;\n };\n\n return (\n <SelectMenuShellContextProvider value={{ iconSize, styles }}>\n <NS.Content css={[styles.content, contentCss]} ref={contentRef} {...restContentProps}>\n {/*\n Without a search input, Zag's getInitialFocus (see @zag-js/dom-query) picks the first\n tabbable descendant as the menu's initial focus target — which would be the action\n slot, visually confusing. A hidden [data-autofocus] span wins over tabbables in\n getInitialFocus's querySelector, so we focus an invisible element instead and Zag's\n aria-activedescendant drives the highlight like a normal Select would.\n */}\n {!onSearchChange && hasAction && <span data-autofocus=\"\" tabIndex={-1} aria-hidden=\"true\" />}\n {onSearchChange && (\n <SelectMenuSearch\n iconSize={iconSize}\n placeholder={searchPlaceholder}\n styles={styles}\n value={searchValue ?? ''}\n onSearchChange={onSearchChange}\n />\n )}\n {hasError ? (\n <Box css={styles.errorState}>\n <IconCrossCircle css={styles.errorIcon} size={iconSize} />\n <Box css={styles.errorTextBlock}>\n <Text css={styles.errorLabel}>{errorLabel}</Text>\n {errorHelperText && <Text css={styles.itemHelperText}>{errorHelperText}</Text>}\n </Box>\n </Box>\n ) : (\n <Box css={styles.itemList} onScroll={handleItemListScroll}>\n {isLoading && (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n )}\n {!isLoading && isEmpty && (\n <Box css={styles.emptyState}>\n <Text css={styles.itemLabel}>{emptyLabel}</Text>\n {emptyHelperText && <Text css={styles.itemHelperText}>{emptyHelperText}</Text>}\n </Box>\n )}\n {!isLoading &&\n !isEmpty &&\n collection.group().map(([type, group]) => (\n <NS.ItemGroup key={type}>\n {type && (\n <NS.ItemGroupLabel asChild>\n <BitkitGroupHeading label={type} paddingBlock=\"12\" paddingInline=\"16\" />\n </NS.ItemGroupLabel>\n )}\n {group.map(renderItem)}\n </NS.ItemGroup>\n ))}\n </Box>\n )}\n {children}\n </NS.Content>\n </SelectMenuShellContextProvider>\n );\n};\n\ntype SelectMenuSearchProps = {\n iconSize: '16' | '24';\n onSearchChange: (searchText: string) => void;\n placeholder?: string;\n styles: Record<string, SystemStyleObject>;\n value: string;\n};\n\nconst SelectMenuSearch = ({\n iconSize,\n onSearchChange,\n placeholder = 'Search...',\n styles,\n value,\n}: SelectMenuSearchProps) => (\n <Box css={styles.searchInputGroup}>\n <IconMagnifier size={iconSize} color=\"icon/tertiary\" />\n <chakra.input\n aria-label=\"Search\"\n css={styles.searchInput}\n placeholder={placeholder}\n value={value}\n onChange={(event: ChangeEvent<HTMLInputElement>) => onSearchChange(event.target.value)}\n onKeyDown={(event) => {\n // Zag's Select.Content keyDown listener fires ITEM.CLICK + preventDefault on Space\n // regardless of which descendant is focused. That would eat the space before the\n // input can type it. Enter is left alone so \"type-then-Enter-to-select\" still works.\n if (event.key === ' ') event.stopPropagation();\n }}\n />\n {value && (\n <chakra.button\n type=\"button\"\n css={styles.searchClear}\n aria-label=\"Clear search\"\n onClick={() => onSearchChange('')}\n >\n <IconCross size={iconSize} />\n </chakra.button>\n )}\n </Box>\n);\n"],"mappings":"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"}
@@ -11,7 +11,7 @@ import UISideNavWorkspaceSelector from "./UISideNavWorkspaceSelector.js";
11
11
  import { uiSideNavRailWidth } from "../../theme/slot-recipes/UISideNav.recipe.js";
12
12
  import { Box } from "@chakra-ui/react/box";
13
13
  import { useSlotRecipe } from "@chakra-ui/react/styled-system";
14
- import { forwardRef, useRef, useState } from "react";
14
+ import { forwardRef, useMemo, useRef, useState } from "react";
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
16
  import { Portal } from "@chakra-ui/react/portal";
17
17
  import { Drawer } from "@chakra-ui/react/drawer";
@@ -59,6 +59,11 @@ var BitkitUISideNav = forwardRef((props, ref) => {
59
59
  const menuContainerRef = useRef(null);
60
60
  const styles = useSlotRecipe({ key: "uiSideNav" })({ layout: isMobile ? "drawer" : "rail" });
61
61
  const expanded = isMobile || (expandedProp ?? (pointerInside || focusInside || menuOpen));
62
+ const tracksPointer = !isMobile && expandedProp === void 0;
63
+ const handleFocus = (event) => {
64
+ if (!event.target.matches(":focus-visible")) return;
65
+ setFocusInside(true);
66
+ };
62
67
  const handleBlur = (event) => {
63
68
  if (event.currentTarget.contains(event.relatedTarget)) return;
64
69
  setFocusInside(false);
@@ -70,19 +75,23 @@ var BitkitUISideNav = forwardRef((props, ref) => {
70
75
  "data-expanded": expanded ? "" : void 0,
71
76
  ...rest,
72
77
  css: [styles.root, css],
73
- onBlur: isMobile ? void 0 : handleBlur,
74
- onFocus: isMobile ? void 0 : () => setFocusInside(true),
75
- onPointerEnter: isMobile ? void 0 : () => setPointerInside(true),
76
- onPointerLeave: isMobile ? void 0 : () => setPointerInside(false),
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,
77
82
  children: /* @__PURE__ */ jsx("nav", { children })
78
83
  });
79
- const context = {
84
+ const context = useMemo(() => ({
80
85
  expanded,
81
86
  isMobile,
82
87
  menuContainerRef,
83
88
  setMenuOpen,
84
89
  styles
85
- };
90
+ }), [
91
+ expanded,
92
+ isMobile,
93
+ styles
94
+ ]);
86
95
  if (!isMobile) return /* @__PURE__ */ jsx(UISideNavContextProvider, {
87
96
  value: context,
88
97
  children: nav
@@ -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, 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 // The drawer has no collapsed state to be in, so mobile is always expanded. On desktop an open\n // menu counts alongside the pointer: letting the pointer alone decide would collapse the rail\n // out from under an open workspace list.\n const expanded = isMobile || (expandedProp ?? (pointerInside || focusInside || menuOpen));\n\n const handleBlur = (event: FocusEvent<HTMLElement>) => {\n // `blur` also fires when focus moves between two rows. Only a target outside the nav counts.\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={isMobile ? undefined : handleBlur}\n onFocus={isMobile ? undefined : () => setFocusInside(true)}\n onPointerEnter={isMobile ? undefined : () => setPointerInside(true)}\n onPointerLeave={isMobile ? undefined : () => setPointerInside(false)}\n >\n <nav>{children}</nav>\n </Box>\n );\n\n const context = { expanded, isMobile, menuContainerRef, setMenuOpen, styles };\n\n if (!isMobile) {\n return <UISideNavContextProvider value={context}>{nav}</UISideNavContextProvider>;\n }\n\n return (\n <UISideNavContextProvider value={context}>\n {/*\n No `placement`: the drawer recipe carries no placement variant, so Chakra does not accept\n the prop. Which edge the panel comes from is settled by the positioner and the slide\n animations in this component's own recipe.\n */}\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 {/*\n Visually beside the panel, but still a child of the content so the dialog's focus\n trap includes it — a close button the keyboard cannot reach is not a close button.\n\n A plain button rather than `Drawer.CloseTrigger`: the trigger carries the drawer\n recipe's own `closeTrigger` slot, which pins itself inside the panel and paints the\n icon dark. The drawer is controlled anyway, so closing it is one call.\n */}\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;CAK9D,MAAM,WAAW,aAAa,iBAAiB,iBAAiB,eAAe;CAE/E,MAAM,cAAc,UAAmC;EAErD,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,WAAW,KAAA,IAAY;EAC/B,SAAS,WAAW,KAAA,UAAkB,eAAe,IAAI;EACzD,gBAAgB,WAAW,KAAA,UAAkB,iBAAiB,IAAI;EAClE,gBAAgB,WAAW,KAAA,UAAkB,iBAAiB,KAAK;EAEnE,UAAA,oBAAC,OAAD,EAAM,SAAc,CAAA;CACjB,CAAA;CAGP,MAAM,UAAU;EAAE;EAAU;EAAU;EAAkB;EAAa;CAAO;CAE5E,IAAI,CAAC,UACH,OAAO,oBAAC,0BAAD;EAA0B,OAAO;EAAU,UAAA;CAA8B,CAAA;CAGlF,OACE,oBAAC,0BAAD;EAA0B,OAAO;EAM/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,KASD,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 { 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 +1 @@
1
- {"version":3,"file":"UISideNavItem.js","names":[],"sources":["../../../lib/components/BitkitUISideNav/UISideNavItem.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { chakra, type HTMLChakraProps } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef } from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport { useUISideNavContext } from './UISideNavContext';\n\nexport interface BitkitUISideNavItemProps extends Omit<HTMLChakraProps<'a'>, 'children'> {\n /**\n * The destination. Always an anchor: the platform navigation moves between server-rendered\n * product areas, so every row is a real link rather than a button with a handler.\n */\n href: string;\n /**\n * The row's label. A string rather than `ReactNode` because the collapsed rail clips it and\n * the expanded rail truncates it, neither of which survives arbitrary markup.\n */\n children: string;\n /** Shown at rest. In the collapsed rail this is the only thing visible. */\n icon: BitkitIconComponent;\n /** Marks the row as the current product area. The consumer decides what counts as current. */\n selected?: boolean;\n /**\n * Swapped in for `icon` while selected. The design pairs an outline icon with its filled\n * counterpart, e.g. `IconDashboard` with `IconDashboardFilled`. Without it the outline icon\n * stays and only the row's colour and marker change.\n */\n selectedIcon?: BitkitIconComponent;\n}\n\nconst UISideNavItem = forwardRef<HTMLAnchorElement, BitkitUISideNavItemProps>((props, ref) => {\n const { children, css, icon: Icon, selected, selectedIcon: SelectedIcon, ...rest } = props;\n\n const { styles } = useUISideNavContext();\n\n const RowIcon = selected && SelectedIcon ? SelectedIcon : Icon;\n\n return (\n <chakra.a\n ref={ref}\n aria-current={selected ? 'page' : undefined}\n data-selected={selected ? '' : undefined}\n {...rest}\n css={[styles.item, css]}\n >\n <RowIcon css={styles.itemIcon} />\n <Text as=\"span\" css={styles.itemLabel}>\n {children}\n </Text>\n {selected && <Box css={styles.itemIndicator} />}\n </chakra.a>\n );\n});\n\nUISideNavItem.displayName = 'BitkitUISideNav.Item';\n\nexport default UISideNavItem;\n"],"mappings":";;;;;;;AA+BA,IAAM,gBAAgB,YAAyD,OAAO,QAAQ;CAC5F,MAAM,EAAE,UAAU,KAAK,MAAM,MAAM,UAAU,cAAc,cAAc,GAAG,SAAS;CAErF,MAAM,EAAE,WAAW,oBAAoB;CAEvC,MAAM,UAAU,YAAY,eAAe,eAAe;CAE1D,OACE,qBAAC,OAAO,GAAR;EACO;EACL,gBAAc,WAAW,SAAS,KAAA;EAClC,iBAAe,WAAW,KAAK,KAAA;EAC/B,GAAI;EACJ,KAAK,CAAC,OAAO,MAAM,GAAG;EALxB,UAAA;GAOE,oBAAC,SAAD,EAAS,KAAK,OAAO,SAAW,CAAA;GAChC,oBAAC,MAAD;IAAM,IAAG;IAAO,KAAK,OAAO;IACzB;GACG,CAAA;GACL,YAAY,oBAAC,KAAD,EAAK,KAAK,OAAO,cAAgB,CAAA;EACtC;;AAEd,CAAC;AAED,cAAc,cAAc"}
1
+ {"version":3,"file":"UISideNavItem.js","names":[],"sources":["../../../lib/components/BitkitUISideNav/UISideNavItem.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { chakra, type HTMLChakraProps } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef } from 'react';\n\nimport { type BitkitIconComponent } from '../../icons';\nimport { useUISideNavContext } from './UISideNavContext';\n\nexport interface BitkitUISideNavItemProps extends Omit<HTMLChakraProps<'a'>, 'children'> {\n /**\n * The destination. Always an anchor: the platform navigation moves between server-rendered\n * product areas, so every row is a real link rather than a button with a handler.\n */\n href: string;\n /**\n * The row's label. A string rather than `ReactNode` because the collapsed rail clips it and\n * the expanded rail truncates it, neither of which survives arbitrary markup.\n */\n children: string;\n /** Shown at rest. In the collapsed rail this is the only thing visible. */\n icon: BitkitIconComponent;\n /** Marks the row as the current product area. The consumer decides what counts as current. */\n selected?: boolean;\n /**\n * Swapped in for `icon` while selected. The design pairs an outline icon with its filled\n * counterpart, e.g. `IconDashboard` with `IconDashboardFilled`. Without it the outline icon\n * stays and only the row's colour and marker change.\n */\n selectedIcon?: BitkitIconComponent;\n}\n\nconst UISideNavItem = forwardRef<HTMLAnchorElement, BitkitUISideNavItemProps>((props, ref) => {\n const { children, css, icon: Icon, selected, selectedIcon: SelectedIcon, ...rest } = props;\n\n const { styles } = useUISideNavContext();\n\n const RowIcon = selected && SelectedIcon ? SelectedIcon : Icon;\n\n // `chakra.a` rather than `Link`: the row is styled entirely by the nav's own `item` slot, and\n // `Link` would layer its own recipe over it.\n return (\n <chakra.a\n ref={ref}\n aria-current={selected ? 'page' : undefined}\n data-selected={selected ? '' : undefined}\n {...rest}\n css={[styles.item, css]}\n >\n <RowIcon css={styles.itemIcon} />\n <Text as=\"span\" css={styles.itemLabel}>\n {children}\n </Text>\n {selected && <Box css={styles.itemIndicator} />}\n </chakra.a>\n );\n});\n\nUISideNavItem.displayName = 'BitkitUISideNav.Item';\n\nexport default UISideNavItem;\n"],"mappings":";;;;;;;AA+BA,IAAM,gBAAgB,YAAyD,OAAO,QAAQ;CAC5F,MAAM,EAAE,UAAU,KAAK,MAAM,MAAM,UAAU,cAAc,cAAc,GAAG,SAAS;CAErF,MAAM,EAAE,WAAW,oBAAoB;CAEvC,MAAM,UAAU,YAAY,eAAe,eAAe;CAI1D,OACE,qBAAC,OAAO,GAAR;EACO;EACL,gBAAc,WAAW,SAAS,KAAA;EAClC,iBAAe,WAAW,KAAK,KAAA;EAC/B,GAAI;EACJ,KAAK,CAAC,OAAO,MAAM,GAAG;EALxB,UAAA;GAOE,oBAAC,SAAD,EAAS,KAAK,OAAO,SAAW,CAAA;GAChC,oBAAC,MAAD;IAAM,IAAG;IAAO,KAAK,OAAO;IACzB;GACG,CAAA;GACL,YAAY,oBAAC,KAAD,EAAK,KAAK,OAAO,cAAgB,CAAA;EACtC;;AAEd,CAAC;AAED,cAAc,cAAc"}
@@ -7,7 +7,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { Text } from "@chakra-ui/react/text";
8
8
  //#region lib/components/BitkitUISideNav/UISideNavLink.tsx
9
9
  var UISideNavLink = forwardRef((props, ref) => {
10
- const { children, icon: Icon, secondaryText, status } = props;
10
+ const { children, css, href, icon: Icon, isExternal, rel, secondaryText, status, target, ...rest } = props;
11
11
  const { styles } = useUISideNavContext();
12
12
  const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
13
13
  /* @__PURE__ */ jsxs(Box, {
@@ -29,27 +29,25 @@ var UISideNavLink = forwardRef((props, ref) => {
29
29
  children: secondaryText
30
30
  })]
31
31
  }),
32
- props.isExternal && /* @__PURE__ */ jsx(IconArrowNortheast, { css: styles.linkIcon })
32
+ isExternal && /* @__PURE__ */ jsx(IconArrowNortheast, { css: styles.linkIcon })
33
33
  ] });
34
- if (props.href === void 0) {
35
- const { children: _children, css, href: _href, icon: _icon, isExternal: _isExternal, secondaryText: _secondaryText, status: _status, ...rest } = props;
36
- return /* @__PURE__ */ jsx(chakra.button, {
37
- ref,
38
- "data-with-secondary": secondaryText ? "" : void 0,
39
- type: "button",
40
- ...rest,
41
- css: [styles.link, css],
42
- children: content
43
- });
44
- }
45
- const { children: _children, css, icon: _icon, isExternal, secondaryText: _secondaryText, status: _status, ...rest } = props;
46
- return /* @__PURE__ */ jsx(chakra.a, {
47
- ref,
34
+ const rootProps = {
48
35
  "data-with-secondary": secondaryText ? "" : void 0,
49
- rel: isExternal ? "noopener noreferrer" : void 0,
50
- target: isExternal ? "_blank" : void 0,
51
36
  ...rest,
52
- css: [styles.link, css],
37
+ css: [styles.link, css]
38
+ };
39
+ if (href === void 0) return /* @__PURE__ */ jsx(chakra.button, {
40
+ ref,
41
+ type: "button",
42
+ ...rootProps,
43
+ children: content
44
+ });
45
+ return /* @__PURE__ */ jsx(chakra.a, {
46
+ ref,
47
+ href,
48
+ rel: isExternal ? rel ? `${rel} noreferrer noopener` : "noreferrer noopener" : rel,
49
+ target: isExternal ? "_blank" : target,
50
+ ...rootProps,
53
51
  children: content
54
52
  });
55
53
  });
@@ -1 +1 @@
1
- {"version":3,"file":"UISideNavLink.js","names":[],"sources":["../../../lib/components/BitkitUISideNav/UISideNavLink.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { chakra, type HTMLChakraProps } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type Ref } from 'react';\n\nimport { type BitkitIconComponent, IconArrowNortheast } from '../../icons';\nimport { useUISideNavContext } from './UISideNavContext';\n\ninterface UISideNavLinkCommonProps {\n children: string;\n icon: BitkitIconComponent;\n /**\n * A second line under the label, for a row that reports something rather than just naming a\n * destination. It steps the row up to 56px and the label down to the smaller semibold size.\n */\n secondaryText?: string;\n /**\n * A coloured dot on the icon's trailing bottom corner. Use it with `secondaryText`, which is\n * what spells the state out; the dot alone only carries colour.\n */\n status?: 'critical' | 'neutral' | 'success';\n}\n\nexport interface BitkitUISideNavLinkAsAnchorProps\n extends UISideNavLinkCommonProps, Omit<HTMLChakraProps<'a'>, 'children'> {\n href: string;\n /** Adds the north-east arrow and the usual `target`/`rel` pair for a link leaving the app. */\n isExternal?: boolean;\n}\n\nexport interface BitkitUISideNavLinkAsButtonProps\n extends UISideNavLinkCommonProps, Omit<HTMLChakraProps<'button'>, 'children'> {\n href?: undefined;\n isExternal?: undefined;\n /** Makes the row a button instead of a link — for one that opens a dialog rather than navigating. */\n onClick: NonNullable<HTMLChakraProps<'button'>['onClick']>;\n}\n\nexport type BitkitUISideNavLinkProps = BitkitUISideNavLinkAsAnchorProps | BitkitUISideNavLinkAsButtonProps;\n\nconst UISideNavLink = forwardRef<HTMLAnchorElement | HTMLButtonElement, BitkitUISideNavLinkProps>((props, ref) => {\n const { children, icon: Icon, secondaryText, status } = props;\n\n const { styles } = useUISideNavContext();\n\n const content = (\n <>\n <Box css={styles.linkIconBlock}>\n <Icon css={styles.linkIcon} />\n {status && <Box css={styles.linkStatusDot} data-status={status} />}\n </Box>\n <Box css={styles.linkTextBlock}>\n <Text as=\"span\" css={styles.linkLabel}>\n {children}\n </Text>\n {secondaryText && (\n <Text as=\"span\" css={styles.linkSecondaryText}>\n {secondaryText}\n </Text>\n )}\n </Box>\n {props.isExternal && <IconArrowNortheast css={styles.linkIcon} />}\n </>\n );\n\n if (props.href === undefined) {\n const {\n children: _children,\n css,\n href: _href,\n icon: _icon,\n isExternal: _isExternal,\n secondaryText: _secondaryText,\n status: _status,\n ...rest\n } = props;\n\n return (\n <chakra.button\n ref={ref as Ref<HTMLButtonElement>}\n data-with-secondary={secondaryText ? '' : undefined}\n type=\"button\"\n {...rest}\n css={[styles.link, css]}\n >\n {content}\n </chakra.button>\n );\n }\n\n const {\n children: _children,\n css,\n icon: _icon,\n isExternal,\n secondaryText: _secondaryText,\n status: _status,\n ...rest\n } = props;\n\n return (\n <chakra.a\n ref={ref as Ref<HTMLAnchorElement>}\n data-with-secondary={secondaryText ? '' : undefined}\n rel={isExternal ? 'noopener noreferrer' : undefined}\n target={isExternal ? '_blank' : undefined}\n {...rest}\n css={[styles.link, css]}\n >\n {content}\n </chakra.a>\n );\n});\n\nUISideNavLink.displayName = 'BitkitUISideNav.Link';\n\nexport default UISideNavLink;\n"],"mappings":";;;;;;;;AAwCA,IAAM,gBAAgB,YAA6E,OAAO,QAAQ;CAChH,MAAM,EAAE,UAAU,MAAM,MAAM,eAAe,WAAW;CAExD,MAAM,EAAE,WAAW,oBAAoB;CAEvC,MAAM,UACJ,qBAAA,YAAA,EAAA,UAAA;EACE,qBAAC,KAAD;GAAK,KAAK,OAAO;GAAjB,UAAA,CACE,oBAAC,MAAD,EAAM,KAAK,OAAO,SAAW,CAAA,GAC5B,UAAU,oBAAC,KAAD;IAAK,KAAK,OAAO;IAAe,eAAa;GAAS,CAAA,CAC9D;;EACL,qBAAC,KAAD;GAAK,KAAK,OAAO;GAAjB,UAAA,CACE,oBAAC,MAAD;IAAM,IAAG;IAAO,KAAK,OAAO;IACzB;GACG,CAAA,GACL,iBACC,oBAAC,MAAD;IAAM,IAAG;IAAO,KAAK,OAAO;IACzB,UAAA;GACG,CAAA,CAEL;;EACJ,MAAM,cAAc,oBAAC,oBAAD,EAAoB,KAAK,OAAO,SAAW,CAAA;CAChE,EAAA,CAAA;CAGJ,IAAI,MAAM,SAAS,KAAA,GAAW;EAC5B,MAAM,EACJ,UAAU,WACV,KACA,MAAM,OACN,MAAM,OACN,YAAY,aACZ,eAAe,gBACf,QAAQ,SACR,GAAG,SACD;EAEJ,OACE,oBAAC,OAAO,QAAR;GACO;GACL,uBAAqB,gBAAgB,KAAK,KAAA;GAC1C,MAAK;GACL,GAAI;GACJ,KAAK,CAAC,OAAO,MAAM,GAAG;GAErB,UAAA;EACY,CAAA;CAEnB;CAEA,MAAM,EACJ,UAAU,WACV,KACA,MAAM,OACN,YACA,eAAe,gBACf,QAAQ,SACR,GAAG,SACD;CAEJ,OACE,oBAAC,OAAO,GAAR;EACO;EACL,uBAAqB,gBAAgB,KAAK,KAAA;EAC1C,KAAK,aAAa,wBAAwB,KAAA;EAC1C,QAAQ,aAAa,WAAW,KAAA;EAChC,GAAI;EACJ,KAAK,CAAC,OAAO,MAAM,GAAG;EAErB,UAAA;CACO,CAAA;AAEd,CAAC;AAED,cAAc,cAAc"}
1
+ {"version":3,"file":"UISideNavLink.js","names":[],"sources":["../../../lib/components/BitkitUISideNav/UISideNavLink.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { chakra, type HTMLChakraProps } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type Ref } from 'react';\n\nimport { type BitkitIconComponent, IconArrowNortheast } from '../../icons';\nimport { useUISideNavContext } from './UISideNavContext';\n\ninterface UISideNavLinkCommonProps {\n children: string;\n icon: BitkitIconComponent;\n /**\n * A second line under the label, for a row that reports something rather than just naming a\n * destination. It steps the row up to 56px and the label down to the smaller semibold size.\n */\n secondaryText?: string;\n /**\n * A coloured dot on the icon's trailing bottom corner. Use it with `secondaryText`, which is\n * what spells the state out; the dot alone only carries colour.\n */\n status?: 'critical' | 'neutral' | 'success';\n}\n\nexport interface BitkitUISideNavLinkAsAnchorProps\n extends UISideNavLinkCommonProps, Omit<HTMLChakraProps<'a'>, 'children'> {\n href: string;\n /** Adds the north-east arrow and the usual `target`/`rel` pair for a link leaving the app. */\n isExternal?: boolean;\n}\n\nexport interface BitkitUISideNavLinkAsButtonProps\n extends UISideNavLinkCommonProps, Omit<HTMLChakraProps<'button'>, 'children'> {\n href?: undefined;\n isExternal?: undefined;\n /** Makes the row a button instead of a link — for one that opens a dialog rather than navigating. */\n onClick: NonNullable<HTMLChakraProps<'button'>['onClick']>;\n}\n\nexport type BitkitUISideNavLinkProps = BitkitUISideNavLinkAsAnchorProps | BitkitUISideNavLinkAsButtonProps;\n\nconst UISideNavLink = forwardRef<HTMLAnchorElement | HTMLButtonElement, BitkitUISideNavLinkProps>((props, ref) => {\n const {\n children,\n css,\n href,\n icon: Icon,\n isExternal,\n rel,\n secondaryText,\n status,\n target,\n ...rest\n } = props as BitkitUISideNavLinkAsAnchorProps;\n\n const { styles } = useUISideNavContext();\n\n const content = (\n <>\n <Box css={styles.linkIconBlock}>\n <Icon css={styles.linkIcon} />\n {status && <Box css={styles.linkStatusDot} data-status={status} />}\n </Box>\n <Box css={styles.linkTextBlock}>\n <Text as=\"span\" css={styles.linkLabel}>\n {children}\n </Text>\n {secondaryText && (\n <Text as=\"span\" css={styles.linkSecondaryText}>\n {secondaryText}\n </Text>\n )}\n </Box>\n {isExternal && <IconArrowNortheast css={styles.linkIcon} />}\n </>\n );\n\n const rootProps = {\n 'data-with-secondary': secondaryText ? '' : undefined,\n ...rest,\n css: [styles.link, css],\n };\n\n // `chakra.button` / `chakra.a` rather than `Button` / `Link`: the row is styled entirely by the\n // nav's own `link` slot, and either named primitive would layer its own recipe over it.\n if (href === undefined) {\n return (\n <chakra.button ref={ref as Ref<HTMLButtonElement>} type=\"button\" {...(rootProps as HTMLChakraProps<'button'>)}>\n {content}\n </chakra.button>\n );\n }\n\n return (\n <chakra.a\n ref={ref as Ref<HTMLAnchorElement>}\n href={href}\n rel={isExternal ? (rel ? `${rel} noreferrer noopener` : 'noreferrer noopener') : rel}\n target={isExternal ? '_blank' : target}\n {...rootProps}\n >\n {content}\n </chakra.a>\n );\n});\n\nUISideNavLink.displayName = 'BitkitUISideNav.Link';\n\nexport default UISideNavLink;\n"],"mappings":";;;;;;;;AAwCA,IAAM,gBAAgB,YAA6E,OAAO,QAAQ;CAChH,MAAM,EACJ,UACA,KACA,MACA,MAAM,MACN,YACA,KACA,eACA,QACA,QACA,GAAG,SACD;CAEJ,MAAM,EAAE,WAAW,oBAAoB;CAEvC,MAAM,UACJ,qBAAA,YAAA,EAAA,UAAA;EACE,qBAAC,KAAD;GAAK,KAAK,OAAO;GAAjB,UAAA,CACE,oBAAC,MAAD,EAAM,KAAK,OAAO,SAAW,CAAA,GAC5B,UAAU,oBAAC,KAAD;IAAK,KAAK,OAAO;IAAe,eAAa;GAAS,CAAA,CAC9D;;EACL,qBAAC,KAAD;GAAK,KAAK,OAAO;GAAjB,UAAA,CACE,oBAAC,MAAD;IAAM,IAAG;IAAO,KAAK,OAAO;IACzB;GACG,CAAA,GACL,iBACC,oBAAC,MAAD;IAAM,IAAG;IAAO,KAAK,OAAO;IACzB,UAAA;GACG,CAAA,CAEL;;EACJ,cAAc,oBAAC,oBAAD,EAAoB,KAAK,OAAO,SAAW,CAAA;CAC1D,EAAA,CAAA;CAGJ,MAAM,YAAY;EAChB,uBAAuB,gBAAgB,KAAK,KAAA;EAC5C,GAAG;EACH,KAAK,CAAC,OAAO,MAAM,GAAG;CACxB;CAIA,IAAI,SAAS,KAAA,GACX,OACE,oBAAC,OAAO,QAAR;EAAoB;EAA+B,MAAK;EAAS,GAAK;EACnE,UAAA;CACY,CAAA;CAInB,OACE,oBAAC,OAAO,GAAR;EACO;EACC;EACN,KAAK,aAAc,MAAM,GAAG,IAAI,wBAAwB,wBAAyB;EACjF,QAAQ,aAAa,WAAW;EAChC,GAAI;EAEH,UAAA;CACO,CAAA;AAEd,CAAC;AAED,cAAc,cAAc"}
@@ -24,7 +24,7 @@ export type BitkitUISideNavWorkspaceSelectorProps = Omit<SelectTriggerProps, 'ch
24
24
  /** The small uppercase caption above the workspace name. */
25
25
  label?: string;
26
26
  onValueChange?: (value: string) => void;
27
- /** The selected workspace's `value`. */
27
+ /** The selected workspace's `value`. Leave it off to let the selector track the choice itself. */
28
28
  value?: string;
29
29
  workspaces: Array<BitkitUISideNavWorkspaceProps>;
30
30
  } & Omit<BitkitSelectMenuSearchProps, 'searchPlaceholder'> & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
@@ -38,7 +38,7 @@ declare const UISideNavWorkspaceSelector: import('react').ForwardRefExoticCompon
38
38
  /** The small uppercase caption above the workspace name. */
39
39
  label?: string;
40
40
  onValueChange?: (value: string) => void;
41
- /** The selected workspace's `value`. */
41
+ /** The selected workspace's `value`. Leave it off to let the selector track the choice itself. */
42
42
  value?: string;
43
43
  workspaces: Array<BitkitUISideNavWorkspaceProps>;
44
44
  } & Omit<BitkitSelectMenuSearchProps, "searchPlaceholder"> & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLButtonElement>>;
@@ -3,25 +3,41 @@ import BitkitAvatar from "../BitkitAvatar/BitkitAvatar.js";
3
3
  import BitkitSelectMenu from "../BitkitSelectMenu/BitkitSelectMenu.js";
4
4
  import { useUISideNavContext } from "./UISideNavContext.js";
5
5
  import { Box } from "@chakra-ui/react/box";
6
- import { forwardRef, useState } from "react";
6
+ import { forwardRef, useMemo, useRef, useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Portal } from "@chakra-ui/react/portal";
9
9
  import { Text } from "@chakra-ui/react/text";
10
+ import { useControllableState } from "@chakra-ui/react/hooks";
10
11
  import { createListCollection } from "@chakra-ui/react/collection";
11
12
  import { Select } from "@chakra-ui/react/select";
13
+ import { useFilter } from "@chakra-ui/react/locale";
12
14
  var UISideNavWorkspaceSelector = forwardRef((props, ref) => {
13
15
  const { children, css, emptyHelperText, emptyLabel, errorHelperText, errorLabel, isLoading, label = "Workspace", onScrolledToBottom, onSearchChange, onValueChange, searchValue, value, workspaces, ...rest } = props;
14
16
  const { isMobile, menuContainerRef, setMenuOpen, styles } = useUISideNavContext();
17
+ const { contains } = useFilter({ sensitivity: "base" });
15
18
  const [ownQuery, setOwnQuery] = useState("");
19
+ const [currentValue, setCurrentValue] = useControllableState({
20
+ defaultValue: void 0,
21
+ value
22
+ });
16
23
  const isSearchDelegated = onSearchChange !== void 0;
17
24
  const query = isSearchDelegated ? searchValue : ownQuery;
18
25
  const hasSearch = isSearchDelegated || workspaces.length >= 6;
19
- const items = (isSearchDelegated || !ownQuery ? workspaces : workspaces.filter((workspace) => workspace.label.toLowerCase().includes(ownQuery.toLowerCase()))).map((workspace) => ({
20
- ...workspace,
21
- avatar: workspace.avatar ?? null
22
- }));
23
- const collection = createListCollection({ items });
24
- const selected = workspaces.find((workspace) => workspace.value === value);
26
+ const collection = useMemo(() => {
27
+ const items = (ownQuery ? workspaces.filter((workspace) => contains(workspace.label, ownQuery)) : workspaces).map((workspace) => ({
28
+ ...workspace,
29
+ avatar: workspace.avatar ?? null
30
+ }));
31
+ return createListCollection({ items });
32
+ }, [
33
+ contains,
34
+ ownQuery,
35
+ workspaces
36
+ ]);
37
+ const lastSelected = useRef(void 0);
38
+ const matched = workspaces.find((workspace) => workspace.value === currentValue);
39
+ if (matched) lastSelected.current = matched;
40
+ const selected = matched ?? (lastSelected.current?.value === currentValue ? lastSelected.current : void 0);
25
41
  return /* @__PURE__ */ jsxs(Select.Root, {
26
42
  collection,
27
43
  css: styles.workspaceRoot,
@@ -29,7 +45,10 @@ var UISideNavWorkspaceSelector = forwardRef((props, ref) => {
29
45
  setMenuOpen(details.open);
30
46
  if (!details.open) setOwnQuery("");
31
47
  },
32
- onValueChange: (details) => onValueChange?.(details.value[0]),
48
+ onValueChange: (details) => {
49
+ setCurrentValue(details.value[0]);
50
+ onValueChange?.(details.value[0]);
51
+ },
33
52
  positioning: isMobile ? {
34
53
  gutter: 0,
35
54
  offset: { crossAxis: 16 },
@@ -40,27 +59,22 @@ var UISideNavWorkspaceSelector = forwardRef((props, ref) => {
40
59
  placement: "bottom-start"
41
60
  },
42
61
  size: "lg",
43
- value: value === void 0 ? void 0 : [value],
62
+ value: currentValue === void 0 ? [] : [currentValue],
44
63
  children: [/* @__PURE__ */ jsxs(Select.Control, {
45
64
  css: styles.workspaceControl,
46
65
  children: [/* @__PURE__ */ jsx(Select.HiddenSelect, {}), /* @__PURE__ */ jsxs(Select.Trigger, {
47
66
  ref,
67
+ unstyled: true,
48
68
  ...rest,
49
69
  css: [styles.workspace, css],
50
70
  children: [
51
- selected?.avatar ? /* @__PURE__ */ jsx(BitkitAvatar, {
52
- borderColor: "border/minimal",
53
- borderWidth: "1",
54
- name: selected.label,
55
- size: "40",
56
- src: selected.avatar,
57
- variant: "image"
58
- }) : /* @__PURE__ */ jsx(BitkitAvatar, {
71
+ /* @__PURE__ */ jsx(BitkitAvatar, {
59
72
  borderColor: "border/minimal",
60
73
  borderWidth: "1",
61
74
  name: selected?.label ?? "",
62
75
  size: "40",
63
- variant: "letters"
76
+ src: selected?.avatar ?? "",
77
+ variant: "image"
64
78
  }),
65
79
  /* @__PURE__ */ jsxs(Box, {
66
80
  css: styles.workspaceTextBlock,