@bitrise/bitkit-v2 0.3.459 → 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.
- package/dist/components/BitkitMultiselect/BitkitMultiselect.js +2 -1
- package/dist/components/BitkitMultiselect/BitkitMultiselect.js.map +1 -1
- package/dist/components/BitkitMultiselectMenu/BitkitMultiselectMenu.d.ts +3 -3
- package/dist/components/BitkitMultiselectMenu/BitkitMultiselectMenu.js.map +1 -1
- package/dist/components/BitkitSelect/BitkitSelect.js +2 -1
- package/dist/components/BitkitSelect/BitkitSelect.js.map +1 -1
- package/dist/components/BitkitSelectMenu/BitkitSelectMenu.d.ts +7 -2
- package/dist/components/BitkitSelectMenu/BitkitSelectMenu.js.map +1 -1
- package/dist/components/BitkitSelectMenu/SelectMenuShell.d.ts +3 -3
- package/dist/components/BitkitSelectMenu/SelectMenuShell.js +22 -4
- package/dist/components/BitkitSelectMenu/SelectMenuShell.js.map +1 -1
- package/dist/components/BitkitUISideNav/BitkitUISideNav.d.ts +37 -0
- package/dist/components/BitkitUISideNav/BitkitUISideNav.js +135 -0
- package/dist/components/BitkitUISideNav/BitkitUISideNav.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavBody.d.ts +7 -0
- package/dist/components/BitkitUISideNav/UISideNavBody.js +20 -0
- package/dist/components/BitkitUISideNav/UISideNavBody.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavContext.d.ts +32 -0
- package/dist/components/BitkitUISideNav/UISideNavContext.js +28 -0
- package/dist/components/BitkitUISideNav/UISideNavContext.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavFooter.d.ts +16 -0
- package/dist/components/BitkitUISideNav/UISideNavFooter.js +28 -0
- package/dist/components/BitkitUISideNav/UISideNavFooter.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavFooterHeading.d.ts +12 -0
- package/dist/components/BitkitUISideNav/UISideNavFooterHeading.js +26 -0
- package/dist/components/BitkitUISideNav/UISideNavFooterHeading.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavItem.d.ts +26 -0
- package/dist/components/BitkitUISideNav/UISideNavItem.js +33 -0
- package/dist/components/BitkitUISideNav/UISideNavItem.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavLink.d.ts +30 -0
- package/dist/components/BitkitUISideNav/UISideNavLink.js +58 -0
- package/dist/components/BitkitUISideNav/UISideNavLink.js.map +1 -0
- package/dist/components/BitkitUISideNav/UISideNavWorkspaceSelector.d.ts +45 -0
- package/dist/components/BitkitUISideNav/UISideNavWorkspaceSelector.js +119 -0
- package/dist/components/BitkitUISideNav/UISideNavWorkspaceSelector.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/main.js +4 -1
- package/dist/theme/semantic-tokens/index.d.ts +17 -2
- package/dist/theme/semantic-tokens/semanticColors.d.ts +17 -2
- package/dist/theme/semantic-tokens/semanticColors.js +5 -2
- package/dist/theme/semantic-tokens/semanticColors.js.map +1 -1
- package/dist/theme/slot-recipes/DatePickerSelect.recipe.d.ts +14 -2
- package/dist/theme/slot-recipes/Select.recipe.d.ts +23 -5
- package/dist/theme/slot-recipes/Select.recipe.js +37 -3
- package/dist/theme/slot-recipes/Select.recipe.js.map +1 -1
- package/dist/theme/slot-recipes/UISideNav.recipe.d.ts +114 -0
- package/dist/theme/slot-recipes/UISideNav.recipe.js +413 -0
- package/dist/theme/slot-recipes/UISideNav.recipe.js.map +1 -0
- package/dist/theme/slot-recipes/index.d.ts +145 -7
- package/dist/theme/slot-recipes/index.js +3 -1
- package/dist/theme/slot-recipes/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ListCollection } from '@chakra-ui/react/collection';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
3
|
+
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuErrorStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from '../BitkitSelectMenu/BitkitSelectMenu';
|
|
4
4
|
import { BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';
|
|
5
5
|
export type BitkitMultiselectMenuItemProps = {
|
|
6
6
|
value: string;
|
|
@@ -15,11 +15,11 @@ export type BitkitMultiselectMenuProps = {
|
|
|
15
15
|
collection: ListCollection<BitkitMultiselectMenuItemProps>;
|
|
16
16
|
isLoading?: boolean;
|
|
17
17
|
size?: 'md' | 'lg';
|
|
18
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
18
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
19
19
|
declare const BitkitMultiselectMenu: import('react').ForwardRefExoticComponent<{
|
|
20
20
|
children?: BitkitSelectMenuActionChild;
|
|
21
21
|
collection: ListCollection<BitkitMultiselectMenuItemProps>;
|
|
22
22
|
isLoading?: boolean;
|
|
23
23
|
size?: "md" | "lg";
|
|
24
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
24
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
25
25
|
export default BitkitMultiselectMenu;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitMultiselectMenu.js","names":[],"sources":["../../../lib/components/BitkitMultiselectMenu/BitkitMultiselectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Select } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode } from 'react';\n\nimport { IconCheck } from '../../icons';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from '../BitkitSelectMenu/SelectMenuShell';\n\nexport type BitkitMultiselectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n helperText?: ReactNode;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitMultiselectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitMultiselectMenuItemProps>;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\nconst BitkitMultiselectMenu = forwardRef<HTMLDivElement, BitkitMultiselectMenuProps>((props, ref) => {\n const { children, collection, size, ...shellProps } = props;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={Select}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <MultiselectMenuItem\n key={item.value}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitMultiselectMenu.displayName = 'BitkitMultiselectMenu';\n\ntype MultiselectMenuItemRenderProps = {\n item: BitkitMultiselectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst MultiselectMenuItem = ({ item, iconSize, spinnerSize, styles }: MultiselectMenuItemRenderProps) => {\n if (item.loading) {\n // Rendered as a plain Box, not Select.Item — Zag's state machine won't track it\n // as an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <Select.Item css={styles.item} item={item}>\n <Box css={styles.checkbox} data-slot=\"checkbox\">\n <IconCheck size={iconSize} css={styles.checkmark} data-slot=\"checkmark\" />\n </Box>\n <Box css={styles.itemContent}>\n <Text css={styles.itemLabel}>{item.label}</Text>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n </Select.Item>\n );\n};\n\nexport default BitkitMultiselectMenu;\n"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"BitkitMultiselectMenu.js","names":[],"sources":["../../../lib/components/BitkitMultiselectMenu/BitkitMultiselectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Select } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { forwardRef, type ReactNode } from 'react';\n\nimport { IconCheck } from '../../icons';\nimport {\n type BitkitSelectMenuEmptyStateProps,\n type BitkitSelectMenuErrorStateProps,\n type BitkitSelectMenuScrollProps,\n type BitkitSelectMenuSearchProps,\n} from '../BitkitSelectMenu/BitkitSelectMenu';\nimport { type BitkitSelectMenuActionChild } from '../BitkitSelectMenu/BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from '../BitkitSelectMenu/SelectMenuShell';\n\nexport type BitkitMultiselectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n helperText?: ReactNode;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitMultiselectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitMultiselectMenuItemProps>;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\nconst BitkitMultiselectMenu = forwardRef<HTMLDivElement, BitkitMultiselectMenuProps>((props, ref) => {\n const { children, collection, size, ...shellProps } = props;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={Select}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <MultiselectMenuItem\n key={item.value}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitMultiselectMenu.displayName = 'BitkitMultiselectMenu';\n\ntype MultiselectMenuItemRenderProps = {\n item: BitkitMultiselectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst MultiselectMenuItem = ({ item, iconSize, spinnerSize, styles }: MultiselectMenuItemRenderProps) => {\n if (item.loading) {\n // Rendered as a plain Box, not Select.Item — Zag's state machine won't track it\n // as an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <Select.Item css={styles.item} item={item}>\n <Box css={styles.checkbox} data-slot=\"checkbox\">\n <IconCheck size={iconSize} css={styles.checkmark} data-slot=\"checkmark\" />\n </Box>\n <Box css={styles.itemContent}>\n <Text css={styles.itemLabel}>{item.label}</Text>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n </Select.Item>\n );\n};\n\nexport default BitkitMultiselectMenu;\n"],"mappings":";;;;;;;;;;AAqCA,IAAM,wBAAwB,YAAwD,OAAO,QAAQ;CACnG,MAAM,EAAE,UAAU,YAAY,MAAM,GAAG,eAAe;CAEtD,MAAM,SADS,cAAc,EAAE,KAAK,SAAS,CAC9B,CAAA,CAAO,EAAE,KAAK,CAAC;CAC9B,MAAM,EAAE,UAAU,gBAAgB,cAAc,QAAQ;CAExD,OACE,oBAAC,iBAAD;EACE,IAAI;EACQ;EACZ,YAAY;EACF;EACV,aAAa,SACX,oBAAC,qBAAD;GAEQ;GACI;GACG;GACL;EACT,GALM,KAAK,KAKX;EAEU;EACL;EACR,GAAI;EAEH;CACc,CAAA;AAErB,CAAC;AAED,sBAAsB,cAAc;AASpC,IAAM,uBAAuB,EAAE,MAAM,UAAU,aAAa,aAA6C;CACvG,IAAI,KAAK,SAGP,OACE,qBAAC,KAAD;EAAK,KAAK,OAAO;EAAjB,UAAA,CACE,oBAAC,SAAD;GAAS,SAAQ;GAAS,MAAM;GAAa,KAAK,OAAO;EAAc,CAAA,GACvE,oBAAC,MAAD;GAAM,KAAK,OAAO;GAAkB,UAAA;EAAgB,CAAA,CACjD;;CAIT,OACE,qBAAC,OAAO,MAAR;EAAa,KAAK,OAAO;EAAY;EAArC,UAAA,CACE,oBAAC,KAAD;GAAK,KAAK,OAAO;GAAU,aAAU;GACnC,UAAA,oBAAC,WAAD;IAAW,MAAM;IAAU,KAAK,OAAO;IAAW,aAAU;GAAa,CAAA;EACtE,CAAA,GACL,qBAAC,KAAD;GAAK,KAAK,OAAO;GAAjB,UAAA,CACE,oBAAC,MAAD;IAAM,KAAK,OAAO;IAAY,UAAA,KAAK;GAAY,CAAA,GAC9C,KAAK,cAAc,oBAAC,MAAD;IAAM,KAAK,OAAO;IAAiB,UAAA,KAAK;GAAiB,CAAA,CAC1E;EACM,CAAA,CAAA;;AAEjB"}
|
|
@@ -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":";;;;;;;;;;;;;;;;;;;AAkDA,IAAM,eAAe,EAAE,aAAa,OAAO,WAA6B;CAEtE,MAAM,QADS,iBACD,CAAA,CAAO;CAErB,MAAM,OAAO,MAAM,EAAE,EAAE;CACvB,MAAM,SAAS,MAAM,EAAE,EAAE;CACzB,MAAM,QAAQ,MAAM,EAAE,EAAE;CACxB,MAAM,aAAa,WAAW,KAAA;CAE9B,MAAM,EAAE,YAAY,aAAa,cAAc;CAG/C,MAAM,SADS,cAAc,EAAE,KAAK,SAAS,CAC9B,CAAA,CAAO,EAAE,KAAK,CAAC;CAE9B,OAAO,MAAM,KACX,qBAAC,OAAO,WAAR;EAAkB,aAAa,gBAAgB,UAAU,aAAa,mBAAmB;EAAzF,UAAA,CACG,aAIC,oBAAC,KAAD;GAAK,aAAU;GAAS,KAAK,OAAO;GAClC,UAAA,oBAAC,cAAD;IAAc,SAAQ;IAAQ,KAAK,UAAU;IAAI,MAAM;IAAO,MAAM;GAAa,CAAA;EAC9E,CAAA,IAEL,QAAQ,oBAAC,MAAD;GAAM,KAAK,OAAO;GAAW,MAAM;GAAU,YAAY;EAAI,CAAA,GAEvE,oBAAC,MAAD;GAAM,IAAG;GAAO,UAAS;GAAS,cAAa;GAAW,YAAW;GAAS,UAAS;GACpF,UAAA;EACG,CAAA,CACU;CAElB,CAAA,IAAA,oBAAC,OAAO,WAAR;EACE,aAAa,gBAAgB,UAAU,aAAa,mBAAmB;EACvE,SAAQ;EACR,UAAS;EACT,cAAa;EACb,YAAW;CACZ,CAAA;AAEL;AAEA,IAAM,eAAe,YAA+C,OAA0B,QAAQ;CACpG,MAAM,EACJ,UACA,cACA,iBACA,YACA,WACA,OACA,oBACA,gBACA,eACA,aACA,eACA,aACA,aACA,OAAO,MACP,OACA,cACA,OACA,GAAG,eACD;CAEJ,MAAM,aAAa,qBAAqB;EACtC;EACA,UAAU,SAAS,KAAK,SAAS;EACjC,iBAAiB,SAAS,CAAC,CAAC,KAAK;CACnC,CAAC;CAED,MAAM,EAAE,eAAe,6BAA6B,WAAW,mBAAmB;CAClF,MAAM,YAAY,UAAU,WAAW,CAAC,CAAC,WAAW;CAEpD,OACE,oBAAC,aAAD;EAAkB;EAAY;EAAO,GAAI;EACvC,UAAA,qBAAC,OAAO,MAAR;GACc;GACN;GACN,GAAI;GACJ,cAAc,eAAe,CAAC,YAAY,IAAI,KAAA;GAC9C,UAAU,UAAU;GACpB,eAAe,UAAU,WAAW,UAAU;GAC9C,SAAS;GACT,gBAAgB,WAAW,gBAAgB,OAAO,MAAM,EAAE;GAC1D,UAAU,UAAU;GAGpB,kBAAkB,EAAE,iBAAiB,WAAW,CAAC,EAAE,eAAe,EAAE,OAAO,UAAU,CAAC;GAKtF,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,QAAQ,CAAC,KAAK,IAAI,CAAC;GAjB9D,UAAA,CAmBE,qBAAC,OAAO,SAAR;IAAgB,WAAU;IAA1B,UAAA;KACE,oBAAC,OAAO,cAAR,CAAsB,CAAA;KACtB,oBAAC,OAAO,SAAR;MAAgB,GAAI;MAAc,iBAAe,UAAU,cAAc,KAAA;MACvE,UAAA,oBAAC,aAAD;OAA0B;OAAmB;OAAa;MAAQ,CAAA;KACpD,CAAA;KAChB,qBAAC,OAAO,gBAAR,EAAA,UAAA;MACG,UAAU,WACT,oBAAC,OAAO,WAAR,EAAA,UACE,oBAAC,uBAAD;OAAuB,MAAM,SAAS,OAAO,OAAO;OAAM,OAAM;MAAiB,CAAA,EACjE,CAAA;MAEnB,UAAU,aACT,oBAAC,OAAO,WAAR,EAAA,UACE,oBAAC,mBAAD,EAAmB,MAAM,SAAS,OAAO,OAAO,KAAO,CAAA,EACvC,CAAA;MAEpB,oBAAC,OAAO,WAAR;OAAkB,SAAA;OAChB,UAAA,oBAAC,oBAAD,CAAqB,CAAA;MACL,CAAA;KACG,EAAA,CAAA;IACT;GAChB,CAAA,GAAA,oBAAC,QAAD;IAAQ,UAAU,iBAAiB;IACjC,UAAA,oBAAC,OAAO,YAAR,EAAA,UACE,oBAAC,kBAAD;KACc;KACK;KACL;KACD;KACS;KACJ;KACH;KACP;KAEL;IACe,CAAA,EACD,CAAA;GACb,CAAA,CACG;;CACF,CAAA;AAEjB,CAAC;AAED,aAAa,cAAc;AAE3B,IAAA,uBAAe,kBAAkB,cAAc,EAAE,QAAQ,uBAAuB,CAAC"}
|
|
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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"}
|
|
@@ -8,6 +8,7 @@ export { type BitkitSelectMenuActionProps } from './BitkitSelectMenuAction';
|
|
|
8
8
|
export type BitkitSelectMenuSearchProps = {
|
|
9
9
|
/** When provided, a search input is rendered above the menu items. `searchValue` is required alongside. */
|
|
10
10
|
onSearchChange?: (searchText: string) => void;
|
|
11
|
+
searchPlaceholder?: string;
|
|
11
12
|
searchValue?: string;
|
|
12
13
|
};
|
|
13
14
|
export type BitkitSelectMenuScrollProps = {
|
|
@@ -33,6 +34,10 @@ export type BitkitSelectMenuEmptyStateProps = {
|
|
|
33
34
|
emptyLabel?: string;
|
|
34
35
|
emptyHelperText?: string;
|
|
35
36
|
};
|
|
37
|
+
export type BitkitSelectMenuErrorStateProps = {
|
|
38
|
+
errorLabel?: string;
|
|
39
|
+
errorHelperText?: string;
|
|
40
|
+
};
|
|
36
41
|
export type BitkitSelectMenuProps = {
|
|
37
42
|
children?: BitkitSelectMenuActionChild;
|
|
38
43
|
collection: ListCollection<BitkitSelectMenuItemProps>;
|
|
@@ -43,7 +48,7 @@ export type BitkitSelectMenuProps = {
|
|
|
43
48
|
isLoading?: boolean;
|
|
44
49
|
size?: 'md' | 'lg';
|
|
45
50
|
variant?: 'select' | 'combobox';
|
|
46
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
51
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
47
52
|
declare const BitkitSelectMenu: import('react').ForwardRefExoticComponent<{
|
|
48
53
|
children?: BitkitSelectMenuActionChild;
|
|
49
54
|
collection: ListCollection<BitkitSelectMenuItemProps>;
|
|
@@ -54,5 +59,5 @@ declare const BitkitSelectMenu: import('react').ForwardRefExoticComponent<{
|
|
|
54
59
|
isLoading?: boolean;
|
|
55
60
|
size?: "md" | "lg";
|
|
56
61
|
variant?: "select" | "combobox";
|
|
57
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
62
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
58
63
|
export default BitkitSelectMenu;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BitkitSelectMenu.js","names":[],"sources":["../../../lib/components/BitkitSelectMenu/BitkitSelectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Combobox } from '@chakra-ui/react/combobox';\nimport { Select, type SelectContentProps } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { cloneElement, forwardRef, type ReactElement, type ReactNode } from 'react';\n\nimport { type BitkitIconComponent, IconCheck } from '../../icons';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport { type BitkitBadgeProps } from '../BitkitBadge/BitkitBadge';\nimport { type BitkitSelectMenuActionChild } from './BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from './SelectMenuShell';\n\nexport { type BitkitSelectMenuActionProps } from './BitkitSelectMenuAction';\n\nexport type BitkitSelectMenuSearchProps = {\n /** When provided, a search input is rendered above the menu items. `searchValue` is required alongside. */\n onSearchChange?: (searchText: string) => void;\n searchValue?: string;\n};\n\nexport type BitkitSelectMenuScrollProps = {\n /** Fires when the item list reaches its end. Position only: keep your own loading and end-of-list guards. */\n onScrolledToBottom?: () => void;\n};\n\nexport type BitkitSelectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n icon?: BitkitIconComponent;\n /** The item's avatar. A URL renders that image; `null` renders the avatar with the initials\n * of `label`; omitting the prop renders no avatar and leaves the row's leading slot to\n * `icon`. */\n avatar?: string | null;\n helperText?: ReactNode;\n /** A `BitkitBadge` after the label. Forced to `colorVariant=\"neutral\"` on a disabled item. */\n badge?: ReactElement<BitkitBadgeProps>;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitSelectMenuEmptyStateProps = {\n emptyLabel?: string;\n emptyHelperText?: string;\n};\n\nexport type BitkitSelectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitSelectMenuItemProps>;\n /** Forwarded to the underlying Select/Combobox.Content. Kept for internal callers\n * (e.g. BitkitCalendar's in-grid selects that need to tweak max-height / width). The\n * public components (BitkitSelect, BitkitCombobox) do not expose this escape hatch. */\n contentProps?: SelectContentProps;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n variant?: 'select' | 'combobox';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps;\n\nconst BitkitSelectMenu = forwardRef<HTMLDivElement, BitkitSelectMenuProps>((props, ref) => {\n const { children, collection, variant = 'select', size, ...shellProps } = props;\n const NS = variant === 'combobox' ? Combobox : Select;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { avatarSize, iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={NS}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <SelectMenuItem\n key={item.value}\n NS={NS}\n avatarSize={avatarSize}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitSelectMenu.displayName = 'BitkitSelectMenu';\n\ntype SelectMenuItemRenderProps = {\n NS: typeof Select | typeof Combobox;\n avatarSize: SelectSizes['avatarSize'];\n item: BitkitSelectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst SelectMenuItem = ({ NS, avatarSize, item, iconSize, spinnerSize, styles }: SelectMenuItemRenderProps) => {\n const Icon = item.icon;\n const showAvatar = item.avatar !== undefined;\n\n if (item.loading) {\n // Rendered as a plain Box, not NS.Item — Zag's state machine won't track it as\n // an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <NS.Item css={styles.item} item={item}>\n {showAvatar && (\n <Box data-slot=\"avatar\" css={styles.avatar}>\n <BitkitAvatar variant=\"image\" src={item.avatar ?? ''} name={item.label} size={avatarSize} />\n </Box>\n )}\n <Box css={styles.itemContent}>\n <Box display=\"flex\" alignItems=\"center\" gap=\"8\">\n {Icon && !showAvatar && <Icon css={styles.itemIcon} size={iconSize} />}\n <Text css={styles.itemLabel}>{item.label}</Text>\n </Box>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n {item.badge && (\n <Box css={styles.itemBadge}>\n {item.disabled ? cloneElement(item.badge, { colorVariant: 'neutral' }) : item.badge}\n </Box>\n )}\n <NS.ItemIndicator asChild>\n <IconCheck size={iconSize} css={styles.itemIndicator} />\n </NS.ItemIndicator>\n </NS.Item>\n );\n};\n\nexport default BitkitSelectMenu;\n"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"BitkitSelectMenu.js","names":[],"sources":["../../../lib/components/BitkitSelectMenu/BitkitSelectMenu.tsx"],"sourcesContent":["import { Box } from '@chakra-ui/react/box';\nimport { type ListCollection } from '@chakra-ui/react/collection';\nimport { Combobox } from '@chakra-ui/react/combobox';\nimport { Select, type SelectContentProps } from '@chakra-ui/react/select';\nimport { Spinner } from '@chakra-ui/react/spinner';\nimport { type SystemStyleObject, useSlotRecipe } from '@chakra-ui/react/styled-system';\nimport { Text } from '@chakra-ui/react/text';\nimport { cloneElement, forwardRef, type ReactElement, type ReactNode } from 'react';\n\nimport { type BitkitIconComponent, IconCheck } from '../../icons';\nimport BitkitAvatar from '../BitkitAvatar/BitkitAvatar';\nimport { type BitkitBadgeProps } from '../BitkitBadge/BitkitBadge';\nimport { type BitkitSelectMenuActionChild } from './BitkitSelectMenuAction';\nimport { SelectMenuShell, selectSizeMap, type SelectSizes } from './SelectMenuShell';\n\nexport { type BitkitSelectMenuActionProps } from './BitkitSelectMenuAction';\n\nexport type BitkitSelectMenuSearchProps = {\n /** When provided, a search input is rendered above the menu items. `searchValue` is required alongside. */\n onSearchChange?: (searchText: string) => void;\n searchPlaceholder?: string;\n searchValue?: string;\n};\n\nexport type BitkitSelectMenuScrollProps = {\n /** Fires when the item list reaches its end. Position only: keep your own loading and end-of-list guards. */\n onScrolledToBottom?: () => void;\n};\n\nexport type BitkitSelectMenuItemProps = {\n value: string;\n label: string;\n group?: string;\n icon?: BitkitIconComponent;\n /** The item's avatar. A URL renders that image; `null` renders the avatar with the initials\n * of `label`; omitting the prop renders no avatar and leaves the row's leading slot to\n * `icon`. */\n avatar?: string | null;\n helperText?: ReactNode;\n /** A `BitkitBadge` after the label. Forced to `colorVariant=\"neutral\"` on a disabled item. */\n badge?: ReactElement<BitkitBadgeProps>;\n disabled?: boolean;\n loading?: boolean;\n};\n\nexport type BitkitSelectMenuEmptyStateProps = {\n emptyLabel?: string;\n emptyHelperText?: string;\n};\n\nexport type BitkitSelectMenuErrorStateProps = {\n errorLabel?: string;\n errorHelperText?: string;\n};\n\nexport type BitkitSelectMenuProps = {\n children?: BitkitSelectMenuActionChild;\n collection: ListCollection<BitkitSelectMenuItemProps>;\n /** Forwarded to the underlying Select/Combobox.Content. Kept for internal callers\n * (e.g. BitkitCalendar's in-grid selects that need to tweak max-height / width). The\n * public components (BitkitSelect, BitkitCombobox) do not expose this escape hatch. */\n contentProps?: SelectContentProps;\n isLoading?: boolean;\n size?: 'md' | 'lg';\n variant?: 'select' | 'combobox';\n} & BitkitSelectMenuSearchProps &\n BitkitSelectMenuScrollProps &\n BitkitSelectMenuEmptyStateProps &\n BitkitSelectMenuErrorStateProps;\n\nconst BitkitSelectMenu = forwardRef<HTMLDivElement, BitkitSelectMenuProps>((props, ref) => {\n const { children, collection, variant = 'select', size, ...shellProps } = props;\n const NS = variant === 'combobox' ? Combobox : Select;\n const recipe = useSlotRecipe({ key: 'select' });\n const styles = recipe({ size });\n const { avatarSize, iconSize, spinnerSize } = selectSizeMap[size ?? 'lg'];\n\n return (\n <SelectMenuShell\n NS={NS}\n collection={collection}\n contentRef={ref}\n iconSize={iconSize}\n renderItem={(item) => (\n <SelectMenuItem\n key={item.value}\n NS={NS}\n avatarSize={avatarSize}\n item={item}\n iconSize={iconSize}\n spinnerSize={spinnerSize}\n styles={styles}\n />\n )}\n spinnerSize={spinnerSize}\n styles={styles}\n {...shellProps}\n >\n {children}\n </SelectMenuShell>\n );\n});\n\nBitkitSelectMenu.displayName = 'BitkitSelectMenu';\n\ntype SelectMenuItemRenderProps = {\n NS: typeof Select | typeof Combobox;\n avatarSize: SelectSizes['avatarSize'];\n item: BitkitSelectMenuItemProps;\n iconSize: SelectSizes['iconSize'];\n spinnerSize: SelectSizes['spinnerSize'];\n styles: Record<string, SystemStyleObject>;\n};\n\nconst SelectMenuItem = ({ NS, avatarSize, item, iconSize, spinnerSize, styles }: SelectMenuItemRenderProps) => {\n const Icon = item.icon;\n const showAvatar = item.avatar !== undefined;\n\n if (item.loading) {\n // Rendered as a plain Box, not NS.Item — Zag's state machine won't track it as\n // an option, so keyboard nav skips it and it can't be selected.\n return (\n <Box css={styles.item}>\n <Spinner variant=\"purple\" size={spinnerSize} css={styles.itemLoading} />\n <Text css={styles.itemLoadingLabel}>Loading...</Text>\n </Box>\n );\n }\n\n return (\n <NS.Item css={styles.item} item={item}>\n {showAvatar && (\n <Box data-slot=\"avatar\" css={styles.avatar}>\n <BitkitAvatar variant=\"image\" src={item.avatar ?? ''} name={item.label} size={avatarSize} />\n </Box>\n )}\n <Box css={styles.itemContent}>\n <Box display=\"flex\" alignItems=\"center\" gap=\"8\">\n {Icon && !showAvatar && <Icon css={styles.itemIcon} size={iconSize} />}\n <Text css={styles.itemLabel}>{item.label}</Text>\n </Box>\n {item.helperText && <Text css={styles.itemHelperText}>{item.helperText}</Text>}\n </Box>\n {item.badge && (\n <Box css={styles.itemBadge}>\n {item.disabled ? cloneElement(item.badge, { colorVariant: 'neutral' }) : item.badge}\n </Box>\n )}\n <NS.ItemIndicator asChild>\n <IconCheck size={iconSize} css={styles.itemIndicator} />\n </NS.ItemIndicator>\n </NS.Item>\n );\n};\n\nexport default BitkitSelectMenu;\n"],"mappings":";;;;;;;;;;;;AAsEA,IAAM,mBAAmB,YAAmD,OAAO,QAAQ;CACzF,MAAM,EAAE,UAAU,YAAY,UAAU,UAAU,MAAM,GAAG,eAAe;CAC1E,MAAM,KAAK,YAAY,aAAa,WAAW;CAE/C,MAAM,SADS,cAAc,EAAE,KAAK,SAAS,CAC9B,CAAA,CAAO,EAAE,KAAK,CAAC;CAC9B,MAAM,EAAE,YAAY,UAAU,gBAAgB,cAAc,QAAQ;CAEpE,OACE,oBAAC,iBAAD;EACM;EACQ;EACZ,YAAY;EACF;EACV,aAAa,SACX,oBAAC,gBAAD;GAEM;GACQ;GACN;GACI;GACG;GACL;EACT,GAPM,KAAK,KAOX;EAEU;EACL;EACR,GAAI;EAEH;CACc,CAAA;AAErB,CAAC;AAED,iBAAiB,cAAc;AAW/B,IAAM,kBAAkB,EAAE,IAAI,YAAY,MAAM,UAAU,aAAa,aAAwC;CAC7G,MAAM,OAAO,KAAK;CAClB,MAAM,aAAa,KAAK,WAAW,KAAA;CAEnC,IAAI,KAAK,SAGP,OACE,qBAAC,KAAD;EAAK,KAAK,OAAO;EAAjB,UAAA,CACE,oBAAC,SAAD;GAAS,SAAQ;GAAS,MAAM;GAAa,KAAK,OAAO;EAAc,CAAA,GACvE,oBAAC,MAAD;GAAM,KAAK,OAAO;GAAkB,UAAA;EAAgB,CAAA,CACjD;;CAIT,OACE,qBAAC,GAAG,MAAJ;EAAS,KAAK,OAAO;EAAY;EAAjC,UAAA;GACG,cACC,oBAAC,KAAD;IAAK,aAAU;IAAS,KAAK,OAAO;IAClC,UAAA,oBAAC,cAAD;KAAc,SAAQ;KAAQ,KAAK,KAAK,UAAU;KAAI,MAAM,KAAK;KAAO,MAAM;IAAa,CAAA;GACxF,CAAA;GAEP,qBAAC,KAAD;IAAK,KAAK,OAAO;IAAjB,UAAA,CACE,qBAAC,KAAD;KAAK,SAAQ;KAAO,YAAW;KAAS,KAAI;KAA5C,UAAA,CACG,QAAQ,CAAC,cAAc,oBAAC,MAAD;MAAM,KAAK,OAAO;MAAU,MAAM;KAAW,CAAA,GACrE,oBAAC,MAAD;MAAM,KAAK,OAAO;MAAY,UAAA,KAAK;KAAY,CAAA,CAC5C;IACJ,CAAA,GAAA,KAAK,cAAc,oBAAC,MAAD;KAAM,KAAK,OAAO;KAAiB,UAAA,KAAK;IAAiB,CAAA,CAC1E;;GACJ,KAAK,SACJ,oBAAC,KAAD;IAAK,KAAK,OAAO;IACd,UAAA,KAAK,WAAW,aAAa,KAAK,OAAO,EAAE,cAAc,UAAU,CAAC,IAAI,KAAK;GAC3E,CAAA;GAEP,oBAAC,GAAG,eAAJ;IAAkB,SAAA;IAChB,UAAA,oBAAC,WAAD;KAAW,MAAM;KAAU,KAAK,OAAO;IAAgB,CAAA;GACvC,CAAA;EACX;;AAEb"}
|
|
@@ -3,7 +3,7 @@ import { Combobox } from '@chakra-ui/react/combobox';
|
|
|
3
3
|
import { Select, SelectContentProps } from '@chakra-ui/react/select';
|
|
4
4
|
import { SystemStyleObject } from '@chakra-ui/react/styled-system';
|
|
5
5
|
import { ReactNode, Ref } from 'react';
|
|
6
|
-
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from './BitkitSelectMenu';
|
|
6
|
+
import { BitkitSelectMenuEmptyStateProps, BitkitSelectMenuErrorStateProps, BitkitSelectMenuScrollProps, BitkitSelectMenuSearchProps } from './BitkitSelectMenu';
|
|
7
7
|
import { BitkitSelectMenuActionChild } from './BitkitSelectMenuAction';
|
|
8
8
|
export declare const selectSizeMap: {
|
|
9
9
|
readonly lg: {
|
|
@@ -38,8 +38,8 @@ export type SelectMenuShellProps<T extends {
|
|
|
38
38
|
renderItem: (item: T) => ReactNode;
|
|
39
39
|
spinnerSize: SelectSizes['spinnerSize'];
|
|
40
40
|
styles: Record<string, SystemStyleObject>;
|
|
41
|
-
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps;
|
|
41
|
+
} & BitkitSelectMenuSearchProps & BitkitSelectMenuScrollProps & BitkitSelectMenuEmptyStateProps & BitkitSelectMenuErrorStateProps;
|
|
42
42
|
export declare const SelectMenuShell: <T extends {
|
|
43
43
|
value: string;
|
|
44
44
|
group?: string;
|
|
45
|
-
}>({ NS, children, collection, contentProps, contentRef, emptyHelperText, emptyLabel, iconSize, isLoading, onScrolledToBottom, onSearchChange, renderItem, searchValue, spinnerSize, styles, }: SelectMenuShellProps<T>) => import('react').JSX.Element;
|
|
45
|
+
}>({ NS, children, collection, contentProps, contentRef, emptyHelperText, emptyLabel, errorHelperText, errorLabel, iconSize, isLoading, onScrolledToBottom, onSearchChange, renderItem, searchPlaceholder, searchValue, spinnerSize, styles, }: SelectMenuShellProps<T>) => import('react').JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import IconCross from "../../icons/IconCross.js";
|
|
2
|
+
import IconCrossCircle from "../../icons/IconCrossCircle.js";
|
|
2
3
|
import IconMagnifier from "../../icons/IconMagnifier.js";
|
|
3
4
|
import BitkitGroupHeading from "../BitkitGroupHeading/BitkitGroupHeading.js";
|
|
4
5
|
import { SelectMenuShellContextProvider } from "./BitkitSelectMenuAction.js";
|
|
@@ -21,9 +22,10 @@ var selectSizeMap = {
|
|
|
21
22
|
spinnerSize: "md"
|
|
22
23
|
}
|
|
23
24
|
};
|
|
24
|
-
var SelectMenuShell = ({ NS, children, collection, contentProps, contentRef, emptyHelperText, emptyLabel = "No matching options", iconSize, isLoading = false, onScrolledToBottom, onSearchChange, renderItem, searchValue, spinnerSize, styles }) => {
|
|
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 }) => {
|
|
25
26
|
const { css: contentCss, ...restContentProps } = contentProps ?? {};
|
|
26
27
|
const isEmpty = collection.size === 0;
|
|
28
|
+
const hasError = !isLoading && !!errorLabel;
|
|
27
29
|
const hasAction = isValidElement(children);
|
|
28
30
|
const wasAtBottom = useRef(false);
|
|
29
31
|
const handleItemListScroll = (event) => {
|
|
@@ -49,11 +51,27 @@ var SelectMenuShell = ({ NS, children, collection, contentProps, contentRef, emp
|
|
|
49
51
|
}),
|
|
50
52
|
onSearchChange && /* @__PURE__ */ jsx(SelectMenuSearch, {
|
|
51
53
|
iconSize,
|
|
54
|
+
placeholder: searchPlaceholder,
|
|
52
55
|
styles,
|
|
53
56
|
value: searchValue ?? "",
|
|
54
57
|
onSearchChange
|
|
55
58
|
}),
|
|
56
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
59
|
+
hasError ? /* @__PURE__ */ jsxs(Box, {
|
|
60
|
+
css: styles.errorState,
|
|
61
|
+
children: [/* @__PURE__ */ jsx(IconCrossCircle, {
|
|
62
|
+
css: styles.errorIcon,
|
|
63
|
+
size: iconSize
|
|
64
|
+
}), /* @__PURE__ */ jsxs(Box, {
|
|
65
|
+
css: styles.errorTextBlock,
|
|
66
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
67
|
+
css: styles.errorLabel,
|
|
68
|
+
children: errorLabel
|
|
69
|
+
}), errorHelperText && /* @__PURE__ */ jsx(Text, {
|
|
70
|
+
css: styles.itemHelperText,
|
|
71
|
+
children: errorHelperText
|
|
72
|
+
})]
|
|
73
|
+
})]
|
|
74
|
+
}) : /* @__PURE__ */ jsxs(Box, {
|
|
57
75
|
css: styles.itemList,
|
|
58
76
|
onScroll: handleItemListScroll,
|
|
59
77
|
children: [
|
|
@@ -93,7 +111,7 @@ var SelectMenuShell = ({ NS, children, collection, contentProps, contentRef, emp
|
|
|
93
111
|
})
|
|
94
112
|
});
|
|
95
113
|
};
|
|
96
|
-
var SelectMenuSearch = ({ iconSize, onSearchChange, styles, value }) => /* @__PURE__ */ jsxs(Box, {
|
|
114
|
+
var SelectMenuSearch = ({ iconSize, onSearchChange, placeholder = "Search...", styles, value }) => /* @__PURE__ */ jsxs(Box, {
|
|
97
115
|
css: styles.searchInputGroup,
|
|
98
116
|
children: [
|
|
99
117
|
/* @__PURE__ */ jsx(IconMagnifier, {
|
|
@@ -103,7 +121,7 @@ var SelectMenuSearch = ({ iconSize, onSearchChange, styles, value }) => /* @__PU
|
|
|
103
121
|
/* @__PURE__ */ jsx(chakra.input, {
|
|
104
122
|
"aria-label": "Search",
|
|
105
123
|
css: styles.searchInput,
|
|
106
|
-
placeholder
|
|
124
|
+
placeholder,
|
|
107
125
|
value,
|
|
108
126
|
onChange: (event) => onSearchChange(event.target.value),
|
|
109
127
|
onKeyDown: (event) => {
|
|
@@ -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, IconMagnifier } from '../../icons';\nimport BitkitGroupHeading from '../BitkitGroupHeading/BitkitGroupHeading';\nimport {\n type BitkitSelectMenuEmptyStateProps,\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\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 iconSize,\n isLoading = false,\n onScrolledToBottom,\n onSearchChange,\n renderItem,\n searchValue,\n spinnerSize,\n styles,\n}: SelectMenuShellProps<T>) => {\n const { css: contentCss, ...restContentProps } = contentProps ?? {};\n const isEmpty = collection.size === 0;\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 styles={styles}\n value={searchValue ?? ''}\n onSearchChange={onSearchChange}\n />\n )}\n <Box css={styles.itemList} onScroll={handleItemListScroll}>\n
|
|
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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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BoxProps } from '@chakra-ui/react/box';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
export interface BitkitUISideNavProps extends Omit<BoxProps, 'children'> {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* Forces the desktop rail open or shut, overriding the pointer and focus behaviour. Leave it
|
|
7
|
+
* unset for the normal rail. Set it to `true` while something the rail does not know about is
|
|
8
|
+
* anchored to it, such as a popover opened from the footer.
|
|
9
|
+
*/
|
|
10
|
+
expanded?: boolean;
|
|
11
|
+
/** Mobile drawer visibility. Controlled, because the header's menu button is what opens it. */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
onOpenChange?: (open: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
export { uiSideNavRailWidth } from '../../theme/slot-recipes/UISideNav.recipe';
|
|
16
|
+
export { type BitkitUISideNavBodyProps } from './UISideNavBody';
|
|
17
|
+
export { type BitkitUISideNavFooterProps } from './UISideNavFooter';
|
|
18
|
+
export { type BitkitUISideNavFooterHeadingProps } from './UISideNavFooterHeading';
|
|
19
|
+
export { type BitkitUISideNavItemProps } from './UISideNavItem';
|
|
20
|
+
export { type BitkitUISideNavLinkAsAnchorProps, type BitkitUISideNavLinkAsButtonProps, type BitkitUISideNavLinkProps, } from './UISideNavLink';
|
|
21
|
+
export { type BitkitUISideNavWorkspaceProps, type BitkitUISideNavWorkspaceSelectorProps, } from './UISideNavWorkspaceSelector';
|
|
22
|
+
declare const _default: import('react').ForwardRefExoticComponent<BitkitUISideNavProps & import('react').RefAttributes<HTMLElement>> & {
|
|
23
|
+
Body: import('react').ForwardRefExoticComponent<import('./UISideNavBody').BitkitUISideNavBodyProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
24
|
+
Footer: import('react').ForwardRefExoticComponent<import('./UISideNavFooter').BitkitUISideNavFooterProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
25
|
+
FooterHeading: import('react').ForwardRefExoticComponent<import('./UISideNavFooterHeading').BitkitUISideNavFooterHeadingProps & import('react').RefAttributes<HTMLParagraphElement>>;
|
|
26
|
+
Item: import('react').ForwardRefExoticComponent<import('./UISideNavItem').BitkitUISideNavItemProps & import('react').RefAttributes<HTMLAnchorElement>>;
|
|
27
|
+
Link: import('react').ForwardRefExoticComponent<import('./UISideNavLink').BitkitUISideNavLinkProps & import('react').RefAttributes<HTMLAnchorElement | HTMLButtonElement>>;
|
|
28
|
+
WorkspaceSelector: import('react').ForwardRefExoticComponent<Omit<import('@chakra-ui/react').SelectTriggerProps, "children" | "onChange"> & {
|
|
29
|
+
children?: import('../BitkitSelectMenu/BitkitSelectMenuAction').BitkitSelectMenuActionChild;
|
|
30
|
+
isLoading?: boolean;
|
|
31
|
+
label?: string;
|
|
32
|
+
onValueChange?: (value: string) => void;
|
|
33
|
+
value?: string;
|
|
34
|
+
workspaces: Array<import('./UISideNavWorkspaceSelector').BitkitUISideNavWorkspaceProps>;
|
|
35
|
+
} & Omit<import('../BitkitSelectMenu/BitkitSelectMenu').BitkitSelectMenuSearchProps, "searchPlaceholder"> & import('../BitkitSelectMenu/BitkitSelectMenu').BitkitSelectMenuScrollProps & import('../BitkitSelectMenu/BitkitSelectMenu').BitkitSelectMenuEmptyStateProps & import('../BitkitSelectMenu/BitkitSelectMenu').BitkitSelectMenuErrorStateProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
36
|
+
};
|
|
37
|
+
export default _default;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { withSubComponents } from "../../utilities/withSubComponents.js";
|
|
2
|
+
import BitkitCloseButton from "../BitkitCloseButton/BitkitCloseButton.js";
|
|
3
|
+
import useResponsive from "../../hooks/useResponsive.js";
|
|
4
|
+
import { UISideNavContextProvider } from "./UISideNavContext.js";
|
|
5
|
+
import UISideNavBody from "./UISideNavBody.js";
|
|
6
|
+
import UISideNavFooter from "./UISideNavFooter.js";
|
|
7
|
+
import UISideNavFooterHeading from "./UISideNavFooterHeading.js";
|
|
8
|
+
import UISideNavItem from "./UISideNavItem.js";
|
|
9
|
+
import UISideNavLink from "./UISideNavLink.js";
|
|
10
|
+
import UISideNavWorkspaceSelector from "./UISideNavWorkspaceSelector.js";
|
|
11
|
+
import { uiSideNavRailWidth } from "../../theme/slot-recipes/UISideNav.recipe.js";
|
|
12
|
+
import { Box } from "@chakra-ui/react/box";
|
|
13
|
+
import { useSlotRecipe } from "@chakra-ui/react/styled-system";
|
|
14
|
+
import { forwardRef, useMemo, useRef, useState } from "react";
|
|
15
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
import { Portal } from "@chakra-ui/react/portal";
|
|
17
|
+
import { Drawer } from "@chakra-ui/react/drawer";
|
|
18
|
+
//#region lib/components/BitkitUISideNav/BitkitUISideNav.tsx
|
|
19
|
+
/**
|
|
20
|
+
* The platform's primary navigation. It expects a workspace selector, a body of navigation
|
|
21
|
+
* items, and an optional footer:
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <BitkitUISideNav open={open} onOpenChange={setOpen}>
|
|
25
|
+
* <BitkitUISideNav.WorkspaceSelector
|
|
26
|
+
* workspaces={workspaces}
|
|
27
|
+
* value={workspace}
|
|
28
|
+
* onValueChange={setWorkspace}
|
|
29
|
+
* />
|
|
30
|
+
* <BitkitUISideNav.Body>
|
|
31
|
+
* <BitkitUISideNav.Item href="/dashboard" icon={IconDashboard} selectedIcon={IconDashboardFilled} selected>
|
|
32
|
+
* Dashboard
|
|
33
|
+
* </BitkitUISideNav.Item>
|
|
34
|
+
* </BitkitUISideNav.Body>
|
|
35
|
+
* <BitkitUISideNav.Footer>…</BitkitUISideNav.Footer>
|
|
36
|
+
* </BitkitUISideNav>
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* On desktop it is a 72px rail that widens to 256px under the pointer and while anything inside
|
|
40
|
+
* it holds keyboard focus. The rows are always laid out at the full width and clipped, so the
|
|
41
|
+
* labels appear without any text reflowing. It will not collapse while the workspace menu is
|
|
42
|
+
* open. There is no pin: the rail is the resting state.
|
|
43
|
+
*
|
|
44
|
+
* Below the `tablet` breakpoint it becomes an overlay drawer instead, opened by the header rather
|
|
45
|
+
* than by hovering. Pass `open` and `onOpenChange`; they are ignored on desktop.
|
|
46
|
+
*
|
|
47
|
+
* It does not position itself. The consumer decides where it sits and how tall it is, which is
|
|
48
|
+
* what lets a page slot it under a header of any height. The expanded rail overlays the page
|
|
49
|
+
* rather than pushing it, so give it a `position` of your own and reserve 72px beside it.
|
|
50
|
+
*
|
|
51
|
+
* Descendants can read the current state with `useUISideNav()`.
|
|
52
|
+
*/
|
|
53
|
+
var BitkitUISideNav = forwardRef((props, ref) => {
|
|
54
|
+
const { children, css, expanded: expandedProp, onOpenChange, open = false, ...rest } = props;
|
|
55
|
+
const { isMobile } = useResponsive();
|
|
56
|
+
const [pointerInside, setPointerInside] = useState(false);
|
|
57
|
+
const [focusInside, setFocusInside] = useState(false);
|
|
58
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
59
|
+
const menuContainerRef = useRef(null);
|
|
60
|
+
const styles = useSlotRecipe({ key: "uiSideNav" })({ layout: isMobile ? "drawer" : "rail" });
|
|
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
|
+
};
|
|
67
|
+
const handleBlur = (event) => {
|
|
68
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
69
|
+
setFocusInside(false);
|
|
70
|
+
};
|
|
71
|
+
const nav = /* @__PURE__ */ jsx(Box, {
|
|
72
|
+
ref,
|
|
73
|
+
"aria-label": "Platform navigation",
|
|
74
|
+
asChild: true,
|
|
75
|
+
"data-expanded": expanded ? "" : void 0,
|
|
76
|
+
...rest,
|
|
77
|
+
css: [styles.root, css],
|
|
78
|
+
onBlur: tracksPointer ? handleBlur : void 0,
|
|
79
|
+
onFocus: tracksPointer ? handleFocus : void 0,
|
|
80
|
+
onPointerEnter: tracksPointer ? () => setPointerInside(true) : void 0,
|
|
81
|
+
onPointerLeave: tracksPointer ? () => setPointerInside(false) : void 0,
|
|
82
|
+
children: /* @__PURE__ */ jsx("nav", { children })
|
|
83
|
+
});
|
|
84
|
+
const context = useMemo(() => ({
|
|
85
|
+
expanded,
|
|
86
|
+
isMobile,
|
|
87
|
+
menuContainerRef,
|
|
88
|
+
setMenuOpen,
|
|
89
|
+
styles
|
|
90
|
+
}), [
|
|
91
|
+
expanded,
|
|
92
|
+
isMobile,
|
|
93
|
+
styles
|
|
94
|
+
]);
|
|
95
|
+
if (!isMobile) return /* @__PURE__ */ jsx(UISideNavContextProvider, {
|
|
96
|
+
value: context,
|
|
97
|
+
children: nav
|
|
98
|
+
});
|
|
99
|
+
return /* @__PURE__ */ jsx(UISideNavContextProvider, {
|
|
100
|
+
value: context,
|
|
101
|
+
children: /* @__PURE__ */ jsx(Drawer.Root, {
|
|
102
|
+
onOpenChange: (details) => onOpenChange?.(details.open),
|
|
103
|
+
open,
|
|
104
|
+
children: /* @__PURE__ */ jsxs(Portal, { children: [/* @__PURE__ */ jsx(Drawer.Backdrop, { css: styles.backdrop }), /* @__PURE__ */ jsx(Drawer.Positioner, {
|
|
105
|
+
css: styles.positioner,
|
|
106
|
+
children: /* @__PURE__ */ jsxs(Drawer.Content, {
|
|
107
|
+
ref: menuContainerRef,
|
|
108
|
+
css: styles.content,
|
|
109
|
+
children: [nav, /* @__PURE__ */ jsx(Box, {
|
|
110
|
+
css: styles.closeButton,
|
|
111
|
+
children: /* @__PURE__ */ jsx(BitkitCloseButton, {
|
|
112
|
+
"aria-label": "Close navigation",
|
|
113
|
+
colorVariant: "white",
|
|
114
|
+
onClick: () => onOpenChange?.(false),
|
|
115
|
+
size: "md"
|
|
116
|
+
})
|
|
117
|
+
})]
|
|
118
|
+
})
|
|
119
|
+
})] })
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
BitkitUISideNav.displayName = "BitkitUISideNav";
|
|
124
|
+
var BitkitUISideNav_default = withSubComponents(BitkitUISideNav, {
|
|
125
|
+
Body: UISideNavBody,
|
|
126
|
+
Footer: UISideNavFooter,
|
|
127
|
+
FooterHeading: UISideNavFooterHeading,
|
|
128
|
+
Item: UISideNavItem,
|
|
129
|
+
Link: UISideNavLink,
|
|
130
|
+
WorkspaceSelector: UISideNavWorkspaceSelector
|
|
131
|
+
});
|
|
132
|
+
//#endregion
|
|
133
|
+
export { BitkitUISideNav_default as default, uiSideNavRailWidth };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=BitkitUISideNav.js.map
|