diff --git a/package.json b/package.json index 76fec80f..f22c983f 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "@loomhq/loom-embed": "1.5.0", "@markdoc/markdoc": "0.4.0", "@monaco-editor/react": "4.6.0", - "@react-aria/utils": "3.22.0", - "@react-hooks-library/core": "0.5.1", + "@react-aria/utils": "3.23.0", + "@react-hooks-library/core": "0.6.0", "@react-spring/web": "^9.7.3", "@react-stately/utils": "3.9.0", "@react-types/shared": "3.22.0", @@ -54,11 +54,11 @@ "moment": "2.29.4", "prop-types": "15.8.1", "react-animate-height": "3.2.3", - "react-aria": "3.31.0", + "react-aria": "3.31.1", "react-embed": "3.7.0", "react-markdown": "9.0.1", "react-merge-refs": "2.1.1", - "react-stately": "3.29.0", + "react-stately": "3.29.1", "react-use-measure": "2.1.1", "rehype-raw": "7.0.0", "resize-observer-polyfill": "1.5.1", diff --git a/src/components/Card.tsx b/src/components/Card.tsx index b8aaf527..ae7cec09 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,10 +1,10 @@ import chroma from 'chroma-js' import { Div, type DivProps } from 'honorable' import { forwardRef } from 'react' -import { type DefaultTheme, useTheme } from 'styled-components' +import styled, { type DefaultTheme } from 'styled-components' import { memoize } from 'lodash-es' -import { type SeverityExt, sanitizeSeverity } from '../types' +import { type Severity, type SeverityExt, sanitizeSeverity } from '../types' import { type FillLevel, @@ -31,10 +31,13 @@ type CardHue = (typeof HUES)[number] type CardSeverity = Extract type BaseCardProps = { - hue?: CardHue // Deprecated, prefer fillLevel + /** @deprecated Colors set by `FillLevelContext`. If you need to override context, use `fillLevel` */ + hue?: CardHue + /** Used to override a fill level set by `FillLevelContext` */ fillLevel?: FillLevel cornerSize?: CornerSize clickable?: boolean + disabled?: boolean selected?: boolean severity?: SeverityExt } @@ -78,12 +81,7 @@ const fillToNeutralSelectedBgC: Record< 3: 'fill-three-selected', } -const cornerSizeToBorderRadius: Record = { - medium: 'medium', - large: 'large', -} - -function useDecideFillLevel({ +export function useDecideFillLevel({ hue, fillLevel, }: { @@ -101,7 +99,7 @@ function useDecideFillLevel({ : (toFillLevel(parentFillLevel + 1) as CardFillLevel) } -const getFillToLightBgC = memoize( +export const getFillToLightBgC = memoize( ( theme: DefaultTheme ): Record> => ({ @@ -184,21 +182,70 @@ const getBgColor = ({ return fillToLightBgC[severity][fillLevel] } +const CardSC = styled(Div)<{ + $fillLevel: CardFillLevel + $cornerSize: CornerSize + $severity: Severity + $selected: boolean + $clickable: boolean + disabled: boolean +}>( + ({ + theme, + $fillLevel: fillLevel, + $cornerSize: cornerSize, + $severity: severity, + $selected: selected, + $clickable: clickable, + disabled, + }) => ({ + ...theme.partials.reset.button, + border: `1px solid ${getBorderColor({ + theme, + fillLevel, + severity, + })}`, + borderRadius: theme.borderRadiuses[cornerSize], + backgroundColor: selected + ? fillToNeutralSelectedBgC[fillLevel] + : getBgColor({ theme, fillLevel, severity }), + '&:focus, &:focus-visible': { + outline: 'none', + }, + '&:focus-visible': { + borderColor: theme.colors['border-outline-focused'], + }, + ...(clickable && + !disabled && { + cursor: 'pointer', + }), + ...(clickable && + !disabled && + !selected && + severity === 'neutral' && { + ':hover': { + backgroundColor: theme.colors[fillToNeutralHoverBgC[fillLevel]], + }, + }), + ...theme.partials.scrollBar({ fillLevel }), + }) +) + const Card = forwardRef( ( { - cornerSize: size = 'large', + cornerSize = 'large', hue, // Deprecated, prefer fillLevel severity = 'neutral', fillLevel, selected = false, clickable = false, + disabled = false, ...props }: CardProps, ref ) => { fillLevel = useDecideFillLevel({ hue, fillLevel }) - const theme = useTheme() const cardSeverity = sanitizeSeverity(severity, { allowList: CARD_SEVERITIES, default: 'neutral', @@ -206,40 +253,19 @@ const Card = forwardRef( return ( -
@@ -248,4 +274,4 @@ const Card = forwardRef( ) export default Card -export type { BaseCardProps, CardProps, CornerSize as CardSize, CardHue } +export type { BaseCardProps, CardProps, CornerSize, CardHue, CardFillLevel } diff --git a/src/components/Chip.tsx b/src/components/Chip.tsx index 41d1f5a1..8848b274 100644 --- a/src/components/Chip.tsx +++ b/src/components/Chip.tsx @@ -1,58 +1,52 @@ import { type FlexProps } from 'honorable' -import PropTypes from 'prop-types' import { type ComponentProps, type ReactElement, type Ref, forwardRef, } from 'react' -import styled, { type DefaultTheme, useTheme } from 'styled-components' +import styled, { + type DefaultTheme, + type StyledComponent, + useTheme, +} from 'styled-components' -import { SEVERITIES } from '../types' +import chroma from 'chroma-js' + +import { type SEVERITIES } from '../types' import { Spinner } from './Spinner' -import Card, { type BaseCardProps } from './Card' -import { type FillLevel, useFillLevel } from './contexts/FillLevelContext' +import Card, { + type BaseCardProps, + type CardFillLevel, + getFillToLightBgC, + useDecideFillLevel, +} from './Card' import CloseIcon from './icons/CloseIcon' +import Tooltip from './Tooltip' -const HUES = ['default', 'lighter', 'lightest'] as const +export const CHIP_CLOSE_ATTR_KEY = 'data-close-button' as const const SIZES = ['small', 'medium', 'large'] as const -type ChipHue = (typeof HUES)[number] type ChipSize = (typeof SIZES)[number] type ChipSeverity = (typeof SEVERITIES)[number] export type ChipProps = Omit & BaseCardProps & { size?: ChipSize + condensed?: boolean severity?: ChipSeverity icon?: ReactElement loading?: boolean closeButton?: boolean + closeButtonProps?: ComponentProps> clickable?: boolean + truncateWidth?: number + truncateEdge?: 'start' | 'end' + tooltip?: boolean | ComponentProps['label'] + tooltipProps?: ComponentProps [x: string]: unknown - } - -const propTypes = { - size: PropTypes.oneOf(SIZES), - severity: PropTypes.oneOf(SEVERITIES), - hue: PropTypes.oneOf(HUES), - icon: PropTypes.element, - loading: PropTypes.bool, -} as const - -const parentFillLevelToHue = { - 0: 'default', - 1: 'lighter', - 2: 'lightest', - 3: 'lightest', -} as const satisfies Record - -const hueToFillLevel: Record = { - default: 1, - lighter: 2, - lightest: 3, -} + } & ({ severity?: ChipSeverity } | { severity: 'error' }) const severityToColor = { neutral: 'text', @@ -61,7 +55,7 @@ const severityToColor = { warning: 'text-warning-light', danger: 'text-danger-light', critical: 'text-danger', - // deprecated + // @ts-expect-error deprecated, should match 'danger' error: 'text-danger-light', } as const satisfies Record @@ -72,7 +66,7 @@ const severityToIconColor = { warning: 'icon-warning', danger: 'icon-danger', critical: 'icon-danger-critical', - // deprecated + // @ts-expect-error deprecated, should match 'danger' error: 'icon-danger', } as const satisfies Record @@ -82,31 +76,35 @@ const sizeToCloseHeight = { large: 12, } as const satisfies Record -const ChipCardSC = styled(Card)<{ $size: ChipSize; $severity: ChipSeverity }>(({ - $size, - $severity, - theme, -}) => { +const ChipCardSC = styled(Card)<{ + $size: ChipSize + $severity: ChipSeverity + $truncateWidth?: number + $truncateEdge?: 'start' | 'end' + $condensed?: boolean +}>(({ $size, $severity, $truncateWidth, $truncateEdge, $condensed, theme }) => { const textColor = theme.mode === 'light' ? theme.colors['text-light'] : theme.colors[severityToColor[$severity]] || theme.colors.text return { - '.closeIcon': { - color: theme.colors['text-light'], - paddingLeft: theme.spacing.xsmall, - }, - '&:hover': { - '.closeIcon': { - color: theme.colors.text, - }, - }, - '.spinner': { - marginRight: theme.spacing.xsmall, - }, - '.icon': { - marginRight: theme.spacing.xsmall, + '&&': { + padding: `${$size === 'large' ? 6 : theme.spacing.xxxsmall}px ${ + $size === 'large' && $condensed + ? 6 + : $size === 'small' + ? $condensed + ? 6 + : theme.spacing.xsmall + : $condensed + ? theme.spacing.xsmall + : theme.spacing.small + }px`, + alignItems: 'center', + display: 'inline-flex', + textDecoration: 'none', + gap: $condensed ? 6 : theme.spacing.xsmall, }, '.children': { display: 'flex', @@ -115,7 +113,63 @@ const ChipCardSC = styled(Card)<{ $size: ChipSize; $severity: ChipSeverity }>(({ fontSize: $size === 'small' ? 12 : 14, fontWeight: $size === 'small' ? 400 : 600, lineHeight: $size === 'small' ? '16px' : '20px', - gap: 4, + gap: theme.spacing.xxsmall, + ...($condensed + ? { + letterSpacing: $condensed ? '-0.025em' : undefined, + } + : {}), + ...($truncateWidth + ? { + display: 'block', + maxWidth: $truncateWidth, + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + ...($truncateEdge === 'start' + ? { direction: 'rtl', textAlign: 'left' } + : {}), + } + : {}), + }, + } +}) + +const CloseButtonSC = styled.button<{ + $severity: ChipSeverity + $fillLevel: CardFillLevel +}>(({ theme, $fillLevel, $severity }) => { + const lightBg = chroma(getFillToLightBgC(theme)[$severity][$fillLevel]) + const lightBgHover = `${lightBg.alpha(lightBg.alpha() + 0.15)}` + + return { + ...theme.partials.reset.button, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: theme.borderRadiuses.medium, + padding: theme.spacing.xsmall - theme.spacing.xxsmall, + margin: -(theme.spacing.xsmall - theme.spacing.xxsmall), + '.closeIcon': { + color: theme.colors['text-light'], + }, + '&:focus-visible': { + ...theme.partials.focus.outline, + }, + '&:not(:disabled)': { + '&:focus-visible, &:hover, [data-clickable=true]:hover > &': { + backgroundColor: + theme.mode === 'light' && $severity !== 'neutral' + ? lightBgHover + : theme.colors[ + `fill-${ + $fillLevel === 3 ? 'three' : $fillLevel === 2 ? 'two' : 'one' + }-hover` + ], + '.closeIcon': { + color: theme.colors.text, + }, + }, }, } }) @@ -124,38 +178,43 @@ function ChipRef( { children, size = 'medium', + condensed = false, severity = 'neutral', + truncateWidth, + truncateEdge, hue, + fillLevel, loading = false, icon, closeButton, + closeButtonProps, clickable, + disabled, + tooltip, + tooltipProps, as, ...props }: ChipProps & { as?: ComponentProps['forwardedAs'] }, ref: Ref ) { - const parentFillLevel = useFillLevel() + fillLevel = useDecideFillLevel({ hue, fillLevel }) const theme = useTheme() - hue = hue || parentFillLevelToHue[parentFillLevel] - const iconCol = severityToIconColor[severity] || 'icon-default' - return ( + let content = ( @@ -175,17 +234,42 @@ function ChipRef( )}
{children}
{closeButton && ( - + + + )}
) + + if (tooltip) { + content = ( + + {content} + + ) + } + + return content } const Chip = forwardRef(ChipRef) -Chip.propTypes = propTypes - export default Chip diff --git a/src/components/ComboBox.tsx b/src/components/ComboBox.tsx index 901dc6de..5ff86c2b 100644 --- a/src/components/ComboBox.tsx +++ b/src/components/ComboBox.tsx @@ -1,9 +1,11 @@ -import { mergeTheme } from 'honorable' import { omitBy } from 'lodash' -import { isUndefined, omit, pick } from 'lodash-es' +import { isEmpty, isUndefined, omit, pick } from 'lodash-es' import { + type ComponentProps, type HTMLAttributes, type Key, + type KeyboardEvent, + type KeyboardEventHandler, type MouseEventHandler, type ReactElement, type ReactNode, @@ -27,7 +29,7 @@ import { useFloatingDropdown } from '../hooks/useFloatingDropdown' import DropdownArrowIcon from './icons/DropdownArrowIcon' import SearchIcon from './icons/SearchIcon' -import Input, { type InputProps } from './Input' +import { type InputProps } from './Input' import { Spinner } from './Spinner' import { type ListBoxItemBaseProps } from './ListBoxItem' @@ -37,8 +39,11 @@ import { setNextFocusedKey, useSelectComboStateProps, } from './SelectComboShared' +import Input2 from './Input2' +import Chip, { CHIP_CLOSE_ATTR_KEY } from './Chip' type Placement = 'left' | 'right' +const CHIP_ATTR_KEY = 'data-chip-key' as const type ComboBoxProps = Exclude & { children: @@ -58,6 +63,10 @@ type ComboBoxProps = Exclude & { filter?: ComboBoxStateOptions['defaultFilter'] loading?: boolean titleContent?: ReactNode + chips?: ComponentProps[] + onDeleteChip?: (key: string) => void + inputContent?: ComponentProps['inputContent'] + onDeleteInputContent?: ComponentProps['onDeleteInputContent'] } & Pick & Omit< ComboBoxStateOptions, @@ -89,51 +98,60 @@ type ComboBoxInputProps = { buttonRef?: RefObject buttonProps?: AriaButtonProps loading?: boolean + hasChips?: boolean } -const OpenButton = styled( - ({ - isOpen: _isOpen, - buttonRef, - buttonProps, - ...props - }: HTMLAttributes & { - isOpen?: boolean - buttonRef: RefObject - buttonProps: AriaButtonProps - }) => { - const { buttonProps: useButtonProps } = useButton( - { ...buttonProps, elementType: 'div' }, - buttonRef - ) - - return ( -
- -
- ) - } -)(({ theme, isOpen }) => ({ +const OpenButtonSC = styled.div(({ theme }) => ({ cursor: 'pointer', display: 'flex', alignItems: 'center', + alignSelf: 'stretch', paddingLeft: theme.spacing.medium, paddingRight: theme.spacing.medium, - height: '100%', - ...theme.partials.dropdown.arrowTransition({ isOpen }), - '&:focus': { - outline: 'none', - }, borderRadius: theme.borderRadiuses.medium - theme.borderWidths.default, - '&:focus-visible': { - ...theme.partials.focus.outline, + ...theme.partials.dropdown.arrowTransition({ isOpen: false }), + '&[aria-expanded=true]': { + ...theme.partials.dropdown.arrowTransition({ isOpen: true }), + }, + '&:focus, &:focus-visible': { + outline: 'none', }, })) +function OpenButton({ + buttonRef, + buttonProps, + ...props +}: HTMLAttributes & { + isOpen?: boolean + buttonRef: RefObject + buttonProps: AriaButtonProps +}) { + const { buttonProps: useButtonProps } = useButton( + { ...buttonProps, elementType: 'div' }, + buttonRef + ) + + return ( + + + + ) +} + +const InputChipList = styled.div(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing.xxsmall, +})) +const onChipClick = (e: Event) => { + e.stopPropagation() +} + const honorableInputPropNames = [ 'onChange', 'onFocus', @@ -151,6 +169,7 @@ function ComboBoxInput({ buttonRef, buttonProps, showArrow = true, + hasChips = false, isOpen, onInputClick, loading, @@ -163,6 +182,7 @@ function ComboBoxInput({ 'onChange' | 'onFocus' | 'onBlur' | 'onKeyDown' | 'onKeyUp' >), } + const theme = useTheme() // Need to filter out undefined properties so they won't override // outerInputProps for honorable component @@ -171,33 +191,12 @@ function ComboBoxInput({ [inputProps] ) - let themeExtension: any = {} - - if (showArrow) { - themeExtension = mergeTheme(themeExtension, { - Input: { - Root: [{ paddingRight: 0 }], - InputBase: [{ paddingRight: 0 }], - EndIcon: [ - { - alignSelf: 'stretch', - paddingLeft: 0, - paddingRight: 0, - marginLeft: 0, - marginRight: 0, - }, - ], - }, - }) - } - return ( - : startIcon } - endIcon={ + dropdownButton={ showArrow ? ( (null) @@ -378,17 +383,142 @@ function ComboBox({ placement, }) - outerInputProps = { - ...outerInputProps, - ...(outerInputProps.ref - ? { ref: mergeRefs([outerInputProps.ref, triggerRef]) } - : { ref: triggerRef }), - } + const chipListRef = useRef(null) + + const onDeleteChip = useCallback( + (key: string) => { + const elt = chipListRef?.current?.querySelector( + `[${CHIP_ATTR_KEY}="${CSS.escape(key)}"]` + ) + const prevChipClose = elt?.previousElementSibling?.querySelector( + '[data-close-button]' + ) + + if (prevChipClose instanceof HTMLElement) { + prevChipClose.focus() + } else { + const nextChipClose = elt?.nextElementSibling?.querySelector( + '[data-close-button]' + ) + + if (nextChipClose instanceof HTMLElement) { + nextChipClose.focus?.() + } else { + inputRef.current?.querySelector('input')?.focus?.() + } + } + + onDeleteChipProp?.(key) + }, + [onDeleteChipProp] + ) + const handleKeyDown = useCallback((e) => { + const elt = e.currentTarget + + const dir: 0 | 1 | -1 = + e.code === 'ArrowLeft' ? -1 : e.code === 'ArrowRight' ? 1 : 0 + + if (dir === 0) return + + if (elt instanceof HTMLInputElement) { + if (elt.selectionStart !== 0 || dir !== -1) { + return + } + + const lastChipClose = chipListRef.current?.querySelector( + `:last-of-type[${CHIP_ATTR_KEY}] [${CHIP_CLOSE_ATTR_KEY}]` + ) + + if (lastChipClose instanceof HTMLElement) { + lastChipClose.focus() + } + } else if ( + elt instanceof HTMLElement && + elt.contains(document.activeElement) + ) { + const chip = document.activeElement?.closest(`[${CHIP_ATTR_KEY}]`) + + if (dir === 1) { + if (!chip.nextElementSibling) { + inputRef.current?.querySelector('input')?.focus() + } else { + chip?.nextElementSibling + ?.querySelector(`[${CHIP_CLOSE_ATTR_KEY}]`) + // @ts-ignore + ?.focus?.() + } + } else if (dir === -1) { + chip.previousElementSibling + ?.querySelector(`[${CHIP_CLOSE_ATTR_KEY}]`) + // @ts-ignore + ?.focus?.() + } + } + }, []) + + outerInputProps = useMemo( + () => ({ + ...(!isEmpty(chips) + ? { + inputContent: ( + + {chips.map((chipProps) => ( + { + onDeleteChip?.(chipProps?.key) + }, + 'aria-label': `Remove ${chipProps.key}`, + }} + {...{ [CHIP_ATTR_KEY]: chipProps?.key }} + {...chipProps} + /> + ))} + + ), + } + : {}), + ...(onDeleteChipProp + ? { + onDeleteInputContent: () => + onDeleteChipProp?.(chips?.[chips.length - 1]?.key), + } + : {}), + ...outerInputProps, + ...(outerInputProps.ref + ? { ref: mergeRefs([outerInputProps.ref, triggerRef]) } + : { ref: triggerRef }), + }), + [ + chips, + handleKeyDown, + onDeleteChip, + onDeleteChipProp, + outerInputProps, + triggerRef, + ] + ) return ( ) => { + handleKeyDown(e) + inputProps?.onKeyDown?.(e) + }, + }} buttonRef={buttonRef} buttonProps={buttonProps} showArrow={showArrow} @@ -401,6 +531,7 @@ function ComboBox({ startIcon={startIcon} outerInputProps={outerInputProps} loading={loading} + hasChips={!!chips} onInputClick={() => { setIsOpen(true) // Need to also manually open with state to override diff --git a/src/components/IconFrame.tsx b/src/components/IconFrame.tsx index cc87429b..736aac57 100644 --- a/src/components/IconFrame.tsx +++ b/src/components/IconFrame.tsx @@ -87,6 +87,7 @@ const sizeToFrameSize: Record = { type IconFrameProps = Omit & { clickable?: boolean + disabled?: boolean textValue?: string icon: ReactElement size?: Size @@ -102,7 +103,9 @@ const IconFrameSC = styled(Flex)<{ $selected: boolean $size: Size }>(({ theme, $type, $clickable, $selected, $size }) => ({ + display: 'flex', alignItems: 'center', + alignContent: 'center', justifyContent: 'center', width: sizeToFrameSize[$size], height: sizeToFrameSize[$size], @@ -111,7 +114,6 @@ const IconFrameSC = styled(Flex)<{ : typeToBG(theme)[$type], border: typeToBorder(theme)[$type], borderRadius: theme.borderRadiuses.medium, - '&:focus,&:focus-visible': { outline: 'none' }, '&:focus-visible,&:hover:focus-visible': { ...theme.partials.focus.default, @@ -127,7 +129,10 @@ const IconFrameSC = styled(Flex)<{ ...($clickable ? { cursor: 'pointer', - '&:hover': { + '&[disabled]': { + cursor: 'default', + }, + '&:hover:not(:disabled)': { backgroundColor: $selected ? typeToSelectedBG(theme)[$type] : $clickable && typeToHoverBG(theme)[$type], @@ -154,6 +159,7 @@ const IconFrame = forwardRef< size = 'medium', textValue = '', clickable = false, + disabled = false, selected = false, tooltip, tooltipProps, @@ -176,8 +182,8 @@ const IconFrame = forwardRef< $type={type} $size={size} ref={ref} - flex={false} aria-label={textValue} + disabled={(clickable && disabled) || undefined} {...(forwardedAs ? { forwardedAs } : {})} {...(clickable && { tabIndex: 0, diff --git a/src/components/Input2.tsx b/src/components/Input2.tsx new file mode 100644 index 00000000..8fe7e1e3 --- /dev/null +++ b/src/components/Input2.tsx @@ -0,0 +1,389 @@ +import { + type ComponentProps, + type ComponentPropsWithRef, + type KeyboardEventHandler, + type MouseEventHandler, + type ReactNode, + forwardRef, + useCallback, + useRef, +} from 'react' +import styled, { type DefaultTheme } from 'styled-components' +import { mergeRefs } from 'react-merge-refs' +import { mergeProps } from 'react-aria' + +import { simulateInputChange } from '../utils/simulateInputChange' +import { useRefResizeObserver } from '../hooks/useRefResizeObserver' + +import { useFillLevel } from './contexts/FillLevelContext' +import { TitleContent } from './Select' +import Tooltip from './Tooltip' +import IconFrame from './IconFrame' +import CloseIcon from './icons/CloseIcon' + +import { useFormField } from './FormField' + +export type InputProps = { + suffix?: ReactNode + prefix?: ReactNode + titleContent?: ReactNode + startContent?: ReactNode[] + showClearButton?: boolean + startIcon?: ReactNode + endIcon?: ReactNode + dropdownButton?: ReactNode + inputContent?: ReactNode + inputProps?: ComponentProps + /** + * @deprecated use `size` + */ + small?: boolean + /** + * @deprecated use `size` + */ + medium?: boolean + /** + * @deprecated use `size` + */ + large?: boolean + size?: 'small' | 'medium' | 'large' + error?: boolean + onEnter?: KeyboardEventHandler + onDeleteInputContent?: KeyboardEventHandler + onClick?: MouseEventHandler +} +export type InputPropsFull = InputProps & { className?: string } & Pick< + // eslint-disable-next-line @typescript-eslint/ban-types + ComponentPropsWithRef<'input'>, + | 'value' + | 'disabled' + | 'defaultValue' + | 'placeholder' + | 'onChange' + | 'onFocus' + | 'onBlur' + | 'onKeyDown' + > + +const PrefixSuffix = styled.div(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + alignSelf: 'stretch', + paddingLeft: theme.spacing.small, + paddingRight: theme.spacing.small, + backgroundColor: + theme.mode === 'light' + ? theme.colors['fill-three'] + : theme.colors['fill-two'], +})) + +const ClearButtonSC = styled.div(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + alignSelf: 'stretch', + paddingRight: theme.spacing.xsmall, +})) + +function ClearButton({ + className, + ...props +}: Omit, 'clickable' | 'icon' | 'size'>) { + return ( + + + } + size="small" + {...props} + /> + + + ) +} + +const InputTitleContent = styled(TitleContent)((_) => ({ + alignSelf: 'stretch', +})) + +const InputRootSC = styled.div<{ + $error: boolean + $size: InputProps['size'] +}>(({ theme, $error, $size }) => ({ + ...($size === 'small' + ? theme.partials.text.caption + : theme.partials.text.body2), + display: 'flex', + overflow: 'hidden', + justifyContent: 'space-between', + alignItems: 'center', + height: 'auto', + minHeight: $size === 'large' ? 48 : $size === 'small' ? 32 : 40, + width: 'auto', + padding: 0, + border: theme.borders.input, + borderColor: $error + ? theme.colors['border-danger'] + : theme.colors['border-input'], + borderRadius: theme.borderRadiuses.normal, + '&:focus-within': { + borderColor: theme.colors['border-outline-focused'], + }, + '&[aria-disabled=true]': { + borderColor: theme.colors['border-disabled'], + }, + '&[aria-disabled=true], &[aria-disabled=true] *': { + color: theme.colors['text-input-disabled'], + }, +})) +const InputBaseSC = styled.input<{ + $padStart: 'xsmall' | 'small' | 'medium' | undefined | null + $padEnd: 'xsmall' | 'small' | 'medium' | undefined | null +}>(({ theme, $padStart, $padEnd }) => ({ + ...theme.partials.reset.input, + width: '100%', + flex: '1 0', + alignSelf: 'stretch', + minHeight: 22, + lineHeight: '22px', + color: theme.colors.text, + ...($padStart ? { paddingLeft: theme.spacing[$padStart] } : {}), + ...($padEnd ? { paddingRight: theme.spacing[$padEnd] } : {}), + '&::placeholder': { + color: theme.colors['text-xlight'], + }, + '&[disabled]': { + '&, &::placeholder': { + color: theme.colors['text-disabled'], + }, + }, +})) + +const BaseIcon = styled.div((_) => ({ + alignSelf: 'stretch', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: 'small', + margin: 0, + padding: 0, +})) +const StartIcon = styled(BaseIcon)<{ $hasStartContent: boolean }>( + ({ theme, $hasStartContent }) => ({ + paddingLeft: $hasStartContent ? theme.spacing.small : theme.spacing.medium, + paddingRight: $hasStartContent + ? theme.spacing.xsmall + : theme.spacing.medium, + zIndex: 1, + }) +) +const EndIcon = styled(BaseIcon)<{ + $hasEndContent: boolean + $hasDropdownButton: boolean +}>(({ theme, $hasEndContent, $hasDropdownButton }) => ({ + paddingRight: $hasEndContent + ? theme.spacing.small + : $hasDropdownButton + ? 0 + : theme.spacing.medium, + paddingLeft: $hasEndContent ? theme.spacing.xsmall : theme.spacing.medium, +})) + +const InputAreaSC = styled.div((_) => ({ + display: 'flex', + alignSelf: 'stretch', + flex: '1 1', + overflowX: 'auto', +})) +const InputContentSC = styled.div<{ $padStart: keyof DefaultTheme['spacing'] }>( + ({ theme, $padStart }) => ({ + display: 'flex', + alignSelf: 'stretch', + paddingLeft: theme.spacing[$padStart], + }) +) + +const Input2 = forwardRef( + ( + { + startIcon, + endIcon, + dropdownButton, + suffix, + prefix, + showClearButton, + titleContent, + size, + small, + large, + onEnter, + onDeleteInputContent, + inputContent, + inputProps, + // Input props + disabled, + value, + error, + placeholder, + onChange, + onFocus, + onBlur, + onKeyDown, + ...props + }, + ref + ) => { + const inputRef = useRef(null) + const inputAreaRef = useRef(null) + const inputContentRef = useRef(null) + const inputContentWidthRef = useRef(0) + const onInputContentResize = useCallback< + Parameters[1] + >((entry) => { + const prevWidth = inputContentWidthRef.current + + inputContentWidthRef.current = entry.contentRect.width + if (entry.contentRect.width <= prevWidth) { + return + } + const scrollDiff = + (inputAreaRef.current?.scrollWidth ?? 0) - + (inputAreaRef.current?.getBoundingClientRect().width ?? 0) + + if (scrollDiff > 0) { + inputAreaRef.current.scrollTo({ + left: scrollDiff + 1, + behavior: 'smooth', + }) + } + }, []) + const inputContentRefCb = useRefResizeObserver( + inputContentRef, + onInputContentResize + ) + + inputProps = { + ...(inputProps ?? {}), + ref: mergeRefs([inputRef, ...(inputProps?.ref ? [inputProps.ref] : [])]), + } + + const parentFillLevel = useFillLevel() + + size = size || (large ? 'large' : small ? 'small' : 'medium') + + inputProps = mergeProps(useFormField()?.fieldProps ?? {}, inputProps) + + const hasEndContent = !!suffix + const hasStartContent = !!prefix || !!titleContent + const hasClearButton = showClearButton && value + const inputPadStart = startIcon + ? null + : hasStartContent + ? 'small' + : 'medium' + const inputPadEnd = endIcon ? null : hasEndContent ? 'small' : 'medium' + + const wrappedOnChange: InputPropsFull['onChange'] = useCallback( + (e) => { + onChange?.(e) + }, + [onChange] + ) + + const wrappedOnKeyDown: InputPropsFull['onKeyDown'] = useCallback( + (e) => { + if (e.key === 'Enter' && typeof onEnter === 'function') { + onEnter?.(e) + } + if (e.key === 'Backspace' && inputRef?.current?.selectionStart === 0) { + onDeleteInputContent?.(e) + } + if (typeof onKeyDown === 'function') { + onKeyDown?.(e) + } + }, + [onDeleteInputContent, onEnter, onKeyDown] + ) + + const outerOnClick: InputPropsFull['onClick'] = useCallback((e) => { + e.preventDefault() + inputRef?.current?.focus() + }, []) + + return ( + + {(titleContent && ( + + {titleContent} + + )) || + (prefix && {prefix})} + + {startIcon && ( + {startIcon} + )} + + {inputContent && ( + + {inputContent} + + )} + + + {hasClearButton && ( + { + const input = inputRef?.current + + if (input) { + simulateInputChange(input, '') + input.focus() + } + }} + /> + )} + {!!endIcon && ( + + {endIcon} + + )} + {!!suffix && {suffix}} + {dropdownButton} + + ) + } +) + +export default Input2 diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index f05eda04..f317cb48 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -7,10 +7,7 @@ import { type Extends } from '../utils/ts-utils' import Banner from './Banner' import Layer, { type LayerPositionType } from './Layer' -export type ToastSeverity = Extends< - Severity, - 'info' | 'success' | 'danger' | 'error' -> +export type ToastSeverity = Extends type ToastProps = { position?: LayerPositionType diff --git a/src/hooks/useRefResizeObserver.tsx b/src/hooks/useRefResizeObserver.tsx new file mode 100644 index 00000000..2ae52acf --- /dev/null +++ b/src/hooks/useRefResizeObserver.tsx @@ -0,0 +1,43 @@ +import { + type MutableRefObject, + type RefCallback, + useCallback, + useRef, +} from 'react' + +export function useRefResizeObserver( + ref: MutableRefObject, + callback: (entry: ResizeObserverEntry, observer: ResizeObserver) => void, + options: ResizeObserverOptions = {} +) { + const observerRef = useRef(null) + const handleResize: ResizeObserverCallback = useCallback( + (entries, observer) => { + if (!Array.isArray(entries)) { + return + } + + const entry = entries[0] + + if (callback) { + callback(entry, observer) + } + }, + [callback] + ) + + return useCallback>( + (node) => { + observerRef.current?.disconnect?.() + ref.current = node + if (!node) { + observerRef.current = null + + return + } + observerRef.current = new ResizeObserver(handleResize) + observerRef.current.observe(node, options) + }, + [ref, handleResize, options] + ) +} diff --git a/src/stories/Card.stories.tsx b/src/stories/Card.stories.tsx index 99d6e2db..b2a96b33 100644 --- a/src/stories/Card.stories.tsx +++ b/src/stories/Card.stories.tsx @@ -28,6 +28,7 @@ const cornerSizes: ComponentProps['cornerSize'][] = [ function Template({ clickable, selected, + disabled, width, height, severity, @@ -46,6 +47,7 @@ function Template({ ['size'][] = [ 'large', ] -const severities: ComponentProps['severity'][] = [ - 'neutral', - 'info', - 'success', - 'warning', - 'error', - 'critical', -] +const severities = SEVERITIES const versionsArgs = [{}, { loading: true }, { icon: }] @@ -240,8 +235,11 @@ function Template({ onFillLevel, asLink, ...args }: any) { export const Default = Template.bind({}) Default.args = { - closeButton: false, - clickable: false, + closeButton: true, + clickable: true, + disabled: false, asLink: false, onFillLevel: 0, + tooltip: false, + condensed: false, } diff --git a/src/stories/ChipList.stories.tsx b/src/stories/ChipList.stories.tsx index 57ece1ab..e55a08e1 100644 --- a/src/stories/ChipList.stories.tsx +++ b/src/stories/ChipList.stories.tsx @@ -4,6 +4,7 @@ import type Chip from '../components/Chip' import Card from '../components/Card' import ChipList from '../components/ChipList' import WrapWithIf from '../components/WrapWithIf' +import { SEVERITIES } from '../types' const sizes: ComponentProps['size'][] = [ 'small', @@ -11,14 +12,7 @@ const sizes: ComponentProps['size'][] = [ 'large', ] -const severities: ComponentProps['severity'][] = [ - 'neutral', - 'info', - 'success', - 'warning', - 'error', - 'critical', -] +const severities: ComponentProps['severity'][] = [...SEVERITIES] export default { title: 'ChipList', diff --git a/src/stories/ClusterTagsTemplate.tsx b/src/stories/ClusterTagsTemplate.tsx new file mode 100644 index 00000000..60cde859 --- /dev/null +++ b/src/stories/ClusterTagsTemplate.tsx @@ -0,0 +1,182 @@ +import { Flex } from 'honorable' +import { type ComponentProps, type Key, useMemo, useState } from 'react' +import Fuse from 'fuse.js' + +import { isEqual, uniqWith } from 'lodash-es' + +import styled from 'styled-components' + +import { Card, Chip, ComboBox, ListBoxItem, TagIcon, WrapWithIf } from '..' + +import { isNonNullable } from '../utils/isNonNullable' + +const TagPicker = styled.div(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing.small, +})) + +type Tag = { + name: string + value: string +} + +const TAGS: Tag[] = [ + { name: 'local', value: 'true' }, + { name: 'local', value: 'false' }, + { name: 'stage', value: 'dev' }, + { name: 'stage', value: 'prod' }, + { name: 'stage', value: 'canary' }, + { name: 'route', value: 'some-very-very-long-tag-value' }, + { name: 'route', value: 'short-name' }, + { name: 'local2', value: 'true' }, + { name: 'local2', value: 'false' }, + { name: 'stage2', value: 'dev' }, + { name: 'stage2', value: 'prod' }, + { name: 'stage2', value: 'canary' }, + { name: 'route2', value: 'some-very-very-long-tag-value' }, + { name: 'route2', value: 'short-name' }, +] +const tags = uniqWith(TAGS, isEqual) + +function tagToKey(tag: Tag) { + return `${tag.name}:${tag.value}` +} + +export function ClusterTagsTemplate({ + onFillLevel, + withTitleContent, + ...args +}: { + onFillLevel: any + withTitleContent: boolean +}) { + const [selectedTagKeys, setSelectedTagKeys] = useState(new Set()) + const selectedTagArr = useMemo(() => [...selectedTagKeys], [selectedTagKeys]) + const [inputValue, setInputValue] = useState('') + const [isOpen, setIsOpen] = useState(false) + + const fuse = useMemo( + () => + new Fuse(tags, { + includeScore: true, + shouldSort: true, + threshold: 0.3, + keys: ['name', 'value'], + }), + [] + ) + + const searchResults = useMemo(() => { + let ret: Fuse.FuseResult[] + + if (inputValue) { + ret = fuse.search(inputValue) + } else { + ret = tags.map((tag, i) => ({ item: tag, score: 1, refIndex: i })) + } + + return ret.filter((tag) => !selectedTagKeys.has(tagToKey(tag.item))) + }, [fuse, inputValue, selectedTagKeys]) + + const onSelectionChange: ComponentProps< + typeof ComboBox + >['onSelectionChange'] = (key) => { + if (key) { + setSelectedTagKeys(new Set([...selectedTagArr, key])) + setInputValue('') + } + } + + const onInputChange: ComponentProps['onInputChange'] = ( + value + ) => { + setInputValue(value) + } + + return ( + 0} + wrapper={ + + } + > + + + ({ + key, + children: key, + }))} + onDeleteChip={(chipKey) => { + const newKeys = new Set(selectedTagKeys) + + newKeys.delete(chipKey) + setSelectedTagKeys(newKeys) + }} + inputProps={{ + placeholder: 'Tag filters', + }} + onOpenChange={(isOpen, _trigger) => { + setIsOpen(isOpen) + }} + maxHeight={232} + allowsEmptyCollection + {...(withTitleContent + ? { + startIcon: null, + titleContent: ( + <> + + Search tags + + ), + } + : {})} + showArrow + {...args} + > + {searchResults + .map(({ item: tag, score: _score, refIndex: _refIndex }) => { + const tagStr = tagToKey(tag) + + if (selectedTagKeys.has(tagStr)) { + return null + } + + return ( + + {tagStr} + + } + textValue={tagStr} + /> + ) + }) + .filter(isNonNullable)} + + + + + ) +} diff --git a/src/stories/ComboBox.stories.tsx b/src/stories/ComboBox.stories.tsx index 743e70f2..f7121fae 100644 --- a/src/stories/ComboBox.stories.tsx +++ b/src/stories/ComboBox.stories.tsx @@ -15,6 +15,8 @@ import { WrapWithIf, } from '..' +import { ClusterTagsTemplate } from './ClusterTagsTemplate' + export default { title: 'Combo Box', component: 'ComboBox', @@ -478,3 +480,9 @@ Tags.args = { loading: false, withTitleContent: false, } + +export const ClusterTags = ClusterTagsTemplate.bind({}) +ClusterTags.args = { + loading: false, + withTitleContent: false, +} diff --git a/src/stories/Input.stories.tsx b/src/stories/Input.stories.tsx index e5fa8fe8..08ae7731 100644 --- a/src/stories/Input.stories.tsx +++ b/src/stories/Input.stories.tsx @@ -7,6 +7,7 @@ import BrowseAppsIcon from '../components/icons/BrowseAppsIcon' import CaretDownIcon from '../components/icons/CaretDownIcon' import SearchIcon from '../components/icons/SearchIcon' import Input from '../components/Input' +import Input2 from '../components/Input2' export default { title: 'Input', @@ -17,7 +18,8 @@ function InputSet(props: any) { return ( ) => { + setInputVal(e.target.value) + }, + ...args, + } + + return ( + +
+ +
+
+ +
+
+ +
+
+ ) +} + export const Default = Template.bind({}) Default.args = {} @@ -158,3 +202,22 @@ TitleContent.args = { showClearButton: true, suffix: '', } + +export const Version2 = CustomInputV2Template.bind({}) + +Version2.args = { + startIcon: , + // endIcon: , + titleContent: ( + <> + + Marketplace + + ), + placeholder: 'Search the marketplace', + showClearButton: true, + suffix: '', + prefix: '', + disabled: false, + error: false, +} diff --git a/src/stories/ListBox.stories.tsx b/src/stories/ListBox.stories.tsx index 5d8c29df..d2c3beb3 100644 --- a/src/stories/ListBox.stories.tsx +++ b/src/stories/ListBox.stories.tsx @@ -24,6 +24,14 @@ const portrait = ( url="photo.png" /> ) + +const initials = ( + +) const smallIcon = const chipProps = { @@ -194,12 +202,12 @@ function Template() { } > - {items.map(({ key, label, description }) => ( + {items.map(({ key, label, description }, i) => ( ))} diff --git a/src/theme/resets.ts b/src/theme/resets.ts index 39139ca7..4c389b83 100644 --- a/src/theme/resets.ts +++ b/src/theme/resets.ts @@ -8,10 +8,12 @@ export const resetPartials = { border: 'none', padding: 0, font: 'inherit', - cursor: 'pointer', outline: 'unset', alignItems: 'unset', appearance: 'none', + 'button&:not(:disabled)': { + cursor: 'pointer', + }, }, list: { margin: 0, @@ -22,4 +24,9 @@ export const resetPartials = { margin: 0, padding: 0, }, + input: { + border: 'none', + outline: 'none', + background: 'none', + }, } as const satisfies Record diff --git a/src/types.ts b/src/types.ts index dfefeff5..bd1a00c3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,8 +13,6 @@ export const SEVERITIES = [ 'danger', 'critical', 'neutral', - // deprecated - 'error', ] as const export function isSeverity(sev: string): sev is Severity { diff --git a/src/utils/isNonNullable.ts b/src/utils/isNonNullable.ts new file mode 100644 index 00000000..bcb7e286 --- /dev/null +++ b/src/utils/isNonNullable.ts @@ -0,0 +1,5 @@ +export function isNonNullable( + value: TValue +): value is NonNullable { + return value != null +} diff --git a/tsconfig.json b/tsconfig.json index 514b5e80..9a5fbfeb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ESNext", - "lib": ["ESNext", "ES2021", "DOM"], + "lib": ["ESNext", "ES2021", "DOM", "DOM.Iterable"], "jsx": "react-jsx", "allowJs": true, "module": "ESNext", diff --git a/yarn.lock b/yarn.lock index b7e44423..06acfab5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2897,8 +2897,8 @@ __metadata: "@markdoc/markdoc": 0.4.0 "@monaco-editor/react": 4.6.0 "@pluralsh/eslint-config-typescript": 2.5.147 - "@react-aria/utils": 3.22.0 - "@react-hooks-library/core": 0.5.1 + "@react-aria/utils": 3.23.0 + "@react-hooks-library/core": 0.6.0 "@react-spring/web": ^9.7.3 "@react-stately/utils": 3.9.0 "@react-types/shared": 3.22.0 @@ -2958,12 +2958,12 @@ __metadata: prop-types: 15.8.1 react: 18.2.0 react-animate-height: 3.2.3 - react-aria: 3.31.0 + react-aria: 3.31.1 react-dom: 18.2.0 react-embed: 3.7.0 react-markdown: 9.0.1 react-merge-refs: 2.1.1 - react-stately: 3.29.0 + react-stately: 3.29.1 react-transition-group: 4.4.5 react-use-measure: 2.1.1 rehype-raw: 7.0.0 @@ -3764,9 +3764,9 @@ __metadata: languageName: node linkType: hard -"@react-aria/combobox@npm:^3.8.1": - version: 3.8.1 - resolution: "@react-aria/combobox@npm:3.8.1" +"@react-aria/combobox@npm:^3.8.2": + version: 3.8.2 + resolution: "@react-aria/combobox@npm:3.8.2" dependencies: "@react-aria/i18n": ^3.10.0 "@react-aria/listbox": ^3.11.3 @@ -3774,7 +3774,7 @@ __metadata: "@react-aria/menu": ^3.12.0 "@react-aria/overlays": ^3.20.0 "@react-aria/selection": ^3.17.3 - "@react-aria/textfield": ^3.14.0 + "@react-aria/textfield": ^3.14.1 "@react-aria/utils": ^3.23.0 "@react-stately/collections": ^3.10.4 "@react-stately/combobox": ^3.8.1 @@ -3786,7 +3786,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: c8484ff273256157035885e1b20e893e4264cf10afe7ed1debc97676d2b5b793c34c83839cfc604a6a030505597cfbc76bebdbdb2fd6c3a4be8b269dd7a8af39 + checksum: 7e345bf9b60f01057eba03ddabfc78078fe34632db3bc3097fbf2d4e5f9ef596be6e253f0fa1405ba4e3904b4c18dff98562519112c081559f4d30314f6ac9e6 languageName: node linkType: hard @@ -3819,9 +3819,9 @@ __metadata: languageName: node linkType: hard -"@react-aria/dialog@npm:^3.5.9": - version: 3.5.9 - resolution: "@react-aria/dialog@npm:3.5.9" +"@react-aria/dialog@npm:^3.5.10": + version: 3.5.10 + resolution: "@react-aria/dialog@npm:3.5.10" dependencies: "@react-aria/focus": ^3.16.0 "@react-aria/overlays": ^3.20.0 @@ -3832,7 +3832,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: d7069f4d44e6c30d6338fe71a5132815a5d30332cfb571113d8137f5109485ad8ec5e713adac779bdf2837a92e4076a658fc0cfbace8d50d8ab364aa6e41876d + checksum: 5ef3cdc4414b26f84eb364b4c53fa53d6f2b24605c09d76624cd4a7d8fb1e4a76026fd025295bfe3707e1702d64be7fff39a35a56ecb6429fb15441aaf6acb09 languageName: node linkType: hard @@ -4060,14 +4060,14 @@ __metadata: languageName: node linkType: hard -"@react-aria/numberfield@npm:^3.10.1": - version: 3.10.1 - resolution: "@react-aria/numberfield@npm:3.10.1" +"@react-aria/numberfield@npm:^3.10.2": + version: 3.10.2 + resolution: "@react-aria/numberfield@npm:3.10.2" dependencies: "@react-aria/i18n": ^3.10.0 "@react-aria/interactions": ^3.20.1 "@react-aria/spinbutton": ^3.6.1 - "@react-aria/textfield": ^3.14.0 + "@react-aria/textfield": ^3.14.1 "@react-aria/utils": ^3.23.0 "@react-stately/form": ^3.0.0 "@react-stately/numberfield": ^3.8.0 @@ -4078,7 +4078,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 3a4488c584d8b7580152f17ccb7e869068b869733231c560db1f968fb6afb1636c173df7b752f77ffb8f1d4b91f29f2e1c54f58c7145508007293170dd2f1efb + checksum: a840c952eff9eb467f46c77fbfe887f24a41e3d85409a88f1d06e80197458b8c9e1ab6cadb3a2148b011309eba858f15c7d0ade925e7a7868c9ef378ba14f5f6 languageName: node linkType: hard @@ -4140,12 +4140,12 @@ __metadata: languageName: node linkType: hard -"@react-aria/searchfield@npm:^3.7.0": - version: 3.7.0 - resolution: "@react-aria/searchfield@npm:3.7.0" +"@react-aria/searchfield@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-aria/searchfield@npm:3.7.1" dependencies: "@react-aria/i18n": ^3.10.0 - "@react-aria/textfield": ^3.14.0 + "@react-aria/textfield": ^3.14.1 "@react-aria/utils": ^3.23.0 "@react-stately/searchfield": ^3.5.0 "@react-types/button": ^3.9.1 @@ -4154,7 +4154,7 @@ __metadata: "@swc/helpers": ^0.5.0 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f2b6d2a6f4d5727dafc487210745f5c1eec24505e914499f58a90f2a7b74fabe582f43197c0726bb7b351578a761610e1e379b0572dbc27cd012da34b36a7e9d + checksum: 34f71e188eb1608bef05a1a654093e6882cf274fa710718954e35ec0778a2c33186e8176a9a56036b7e1e9e8a8b663c78915698cf889db8a9289e94ff826d037 languageName: node linkType: hard @@ -4250,17 +4250,6 @@ __metadata: languageName: node linkType: hard -"@react-aria/ssr@npm:^3.9.0": - version: 3.9.0 - resolution: "@react-aria/ssr@npm:3.9.0" - dependencies: - "@swc/helpers": ^0.5.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 7b708494e7c59c4cda8c21f8580d989e7758e854b043d597a0513ee2b08c86e9b877d9f6d5eea9df758bf1b1abdb5adfcba1871e38578ecdf1fe561980addda9 - languageName: node - linkType: hard - "@react-aria/ssr@npm:^3.9.1": version: 3.9.1 resolution: "@react-aria/ssr@npm:3.9.1" @@ -4353,9 +4342,9 @@ __metadata: languageName: node linkType: hard -"@react-aria/textfield@npm:^3.14.0": - version: 3.14.0 - resolution: "@react-aria/textfield@npm:3.14.0" +"@react-aria/textfield@npm:^3.14.1": + version: 3.14.1 + resolution: "@react-aria/textfield@npm:3.14.1" dependencies: "@react-aria/focus": ^3.16.0 "@react-aria/form": ^3.0.1 @@ -4368,7 +4357,7 @@ __metadata: "@swc/helpers": ^0.5.0 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: db80ada50e1fd78977a6cf0c0b1a8632c3817b1123a6efb637a5942ac91ce14cdf2264fd0021da6bd759091cea644c3b263c887d23731807ebf9151f5d0a5bed + checksum: c1750b02fb0376726385ef4f8c62494bc911f75d6ee69789597ac656028743385a96e38569956090700d71355f25423a46c3bb25f8442d74ade9077353d1547b languageName: node linkType: hard @@ -4405,22 +4394,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/utils@npm:3.22.0": - version: 3.22.0 - resolution: "@react-aria/utils@npm:3.22.0" - dependencies: - "@react-aria/ssr": ^3.9.0 - "@react-stately/utils": ^3.9.0 - "@react-types/shared": ^3.22.0 - "@swc/helpers": ^0.5.0 - clsx: ^1.1.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 4ed769920d76eac437dbf31a09ac597bddbcf1f47f6c74336d1e19acbf2fda9a9e4bf00fe0da789512604dddb0ea16599dc9da49a58b74de3a8d7b72015dd787 - languageName: node - linkType: hard - -"@react-aria/utils@npm:^3.23.0": +"@react-aria/utils@npm:3.23.0, @react-aria/utils@npm:^3.23.0": version: 3.23.0 resolution: "@react-aria/utils@npm:3.23.0" dependencies: @@ -4449,21 +4423,21 @@ __metadata: languageName: node linkType: hard -"@react-hooks-library/core@npm:0.5.1": - version: 0.5.1 - resolution: "@react-hooks-library/core@npm:0.5.1" +"@react-hooks-library/core@npm:0.6.0": + version: 0.6.0 + resolution: "@react-hooks-library/core@npm:0.6.0" dependencies: - "@react-hooks-library/shared": 0.5.1 + "@react-hooks-library/shared": 0.6.0 peerDependencies: react: ">=16.9.0" - checksum: d334e5705cf2a12ce6344cb31489c40ae891529f121c558730908aec86a43658b03b8ec4c94ace408ad88f640b0ca760365cbca2966369429f349c9102011a4c + checksum: 2ecd832146cc634beba623e14f1b750273cebf882fd0f7e9312313e41e9eb2d543b14ea18c52117bebcda0588053261dfb2900a7e0d932d7f03a4ede733dc1f3 languageName: node linkType: hard -"@react-hooks-library/shared@npm:0.5.1": - version: 0.5.1 - resolution: "@react-hooks-library/shared@npm:0.5.1" - checksum: 8c1271d440c7e1cff32bda700e230223c6f7e1c9cbc0784fa641ab0b29fafccb2c539f37705a24d55b3db758da491d55b23dd63d27f4c7469eff99553a3812e7 +"@react-hooks-library/shared@npm:0.6.0": + version: 0.6.0 + resolution: "@react-hooks-library/shared@npm:0.6.0" + checksum: 47dc667ee120b6b17db7731ff583d54f989da1c22ef36d90f29beabd6cbd923579c1d8ff6050e4b36dedbb390077e59c2c9b8534556b2f4245cb38fac00ed909 languageName: node linkType: hard @@ -8482,13 +8456,6 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.1.1": - version: 1.2.1 - resolution: "clsx@npm:1.2.1" - checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 - languageName: node - linkType: hard - "clsx@npm:^2.0.0": version: 2.1.0 resolution: "clsx@npm:2.1.0" @@ -16263,18 +16230,18 @@ __metadata: languageName: node linkType: hard -"react-aria@npm:3.31.0": - version: 3.31.0 - resolution: "react-aria@npm:3.31.0" +"react-aria@npm:3.31.1": + version: 3.31.1 + resolution: "react-aria@npm:3.31.1" dependencies: "@internationalized/string": ^3.2.0 "@react-aria/breadcrumbs": ^3.5.9 "@react-aria/button": ^3.9.1 "@react-aria/calendar": ^3.5.4 "@react-aria/checkbox": ^3.13.0 - "@react-aria/combobox": ^3.8.1 + "@react-aria/combobox": ^3.8.2 "@react-aria/datepicker": ^3.9.1 - "@react-aria/dialog": ^3.5.9 + "@react-aria/dialog": ^3.5.10 "@react-aria/dnd": ^3.5.1 "@react-aria/focus": ^3.16.0 "@react-aria/gridlist": ^3.7.3 @@ -16285,11 +16252,11 @@ __metadata: "@react-aria/listbox": ^3.11.3 "@react-aria/menu": ^3.12.0 "@react-aria/meter": ^3.4.9 - "@react-aria/numberfield": ^3.10.1 + "@react-aria/numberfield": ^3.10.2 "@react-aria/overlays": ^3.20.0 "@react-aria/progress": ^3.4.9 "@react-aria/radio": ^3.10.0 - "@react-aria/searchfield": ^3.7.0 + "@react-aria/searchfield": ^3.7.1 "@react-aria/select": ^3.14.1 "@react-aria/selection": ^3.17.3 "@react-aria/separator": ^3.3.9 @@ -16299,7 +16266,7 @@ __metadata: "@react-aria/table": ^3.13.3 "@react-aria/tabs": ^3.8.3 "@react-aria/tag": ^3.3.1 - "@react-aria/textfield": ^3.14.0 + "@react-aria/textfield": ^3.14.1 "@react-aria/tooltip": ^3.7.0 "@react-aria/utils": ^3.23.0 "@react-aria/visually-hidden": ^3.8.8 @@ -16307,7 +16274,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: cafe3e79a0a4ab5328c47b512f0ae178ddff70ffc2dc58a14d1c38b68a7eb6bf7cf8da160730fe86239a1680f652101c8666a8af65770a87d4a87bd9080ef46e + checksum: cc884215ce26921382760f2e436289ecd13cf1716df29390d81a7e44bb80c8e45771806a264b31d7e4f7d58184ccac6d9777d028b669c519be96df7844a32324 languageName: node linkType: hard @@ -16527,9 +16494,9 @@ __metadata: languageName: node linkType: hard -"react-stately@npm:3.29.0": - version: 3.29.0 - resolution: "react-stately@npm:3.29.0" +"react-stately@npm:3.29.1": + version: 3.29.1 + resolution: "react-stately@npm:3.29.1" dependencies: "@react-stately/calendar": ^3.4.3 "@react-stately/checkbox": ^3.6.1 @@ -16556,7 +16523,7 @@ __metadata: "@react-types/shared": ^3.22.0 peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 4e71a9e3405c2fb0a8743f69dad6465b5dcaecc979b893749769f2a5de7369aef8df9b258f5ec3319e154ff068b35155adda31101dbf76a06323cbe1fc533bc6 + checksum: 9dec21bb7a01c07bc3e1a10258b627f1e29ebc09719359c93f2bcb9f33f0e2ad0d3b7a99cedde5c9ed785fba1953e2ab44c92db7cddb1e67b714f80bcffec439 languageName: node linkType: hard