Files
web/packages/design-system/lib/components/DeprecatedSelect/index.tsx
Rasmus Langvad c65091b36a Merged in feat/SW-3644-storybook-v10 (pull request #3240)
feat(SW-3644): Storybook v10

* Auto update to Storybook v10

* Add scandic theme and logo

* Update yarn.lock

* Update formatting of package.json

* Update vitest config and playwright plugin

* Remove vitest 4 update

* Re-added comment

* Update the Typography component to explicitly return React.ReactNode

* Add an explicit type assertion to the export

* Add an explicit type assertion to the export for Checkbox

* Explicit return type assertion

* Add an explicit type assertion to the export

* Update @types/react and fix ts warnings

* Updated typings


Approved-by: Linus Flood
Approved-by: Matilda Landström
2025-11-28 08:05:40 +00:00

171 lines
4.6 KiB
TypeScript

'use client'
import { ReactElement, useState } from 'react'
import {
Button,
type Key,
ListBox,
ListBoxItem,
Popover,
Select as ReactAriaSelect,
SelectValue,
} from 'react-aria-components'
import SelectChevron from './SelectChevron'
import styles from './select.module.css'
import Body from '../Body'
import { Label } from '../Label'
interface SelectProps extends Omit<
React.SelectHTMLAttributes<HTMLSelectElement>,
'onSelect'
> {
defaultSelectedKey?: Key
items: { label: string; value: Key }[]
label: string
name: string
onSelect: (key: Key) => void
value?: string | number
maxHeight?: number
showRadioButton?: boolean
discreet?: boolean
isNestedInModal?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
optionsIcon?: ReactElement<any>
}
type SelectPortalContainer = HTMLDivElement | undefined
type SelectPortalContainerArgs = HTMLDivElement | null
const DELIMITER = ':'
/**
* @deprecated Do not use.
*/
export default function Select({
className = '',
'aria-label': ariaLabel,
defaultSelectedKey,
items,
label,
name,
onSelect,
disabled,
required = false,
tabIndex,
value,
maxHeight,
showRadioButton = false,
discreet = false,
isNestedInModal = false,
optionsIcon,
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
const setOverflowVisible = useSetOverflowVisibleOnRA(isNestedInModal)
function setRef(node: SelectPortalContainerArgs) {
if (node) {
setRootDiv(node)
}
}
function handleOnSelect(key: Key | null) {
if (key !== null) {
onSelect(key)
}
}
let chevronProps = {}
if (discreet) {
chevronProps = { color: 'baseButtonTextOnFillNormal' }
} else if (disabled) {
chevronProps = { color: 'disabled' }
}
return (
<div className={`${styles.container} ${className}`} ref={setRef}>
<ReactAriaSelect
aria-label={ariaLabel}
className={`${styles.select} ${discreet ? styles.discreet : ''} select-container`}
defaultSelectedKey={defaultSelectedKey}
name={name}
onSelectionChange={handleOnSelect}
selectedKey={value as Key}
onOpenChange={setOverflowVisible}
isDisabled={disabled}
>
<Body asChild fontOnly>
<Button
className={`${styles.input} select-button`}
data-testid={name}
>
<SelectValue tabIndex={tabIndex}>
{({ selectedText }) => (
<>
<Label
required={required}
size={discreet ? 'discreet' : 'regular'}
>
{label}
{discreet && DELIMITER}
</Label>
{selectedText && (
<Body className={optionsIcon ? styles.iconLabel : ''}>
{optionsIcon ? optionsIcon : null}
{selectedText}
</Body>
)}
</>
)}
</SelectValue>
<SelectChevron {...chevronProps} />
</Button>
</Body>
<Body asChild fontOnly>
<Popover
className={styles.popover}
placement="bottom"
shouldFlip={false}
/**
* react-aria uses portals to render Popover in body
* unless otherwise specified. We need it to be contained
* by this component to both access css variables assigned
* on the container as well as to not overflow it at any time.
*/
UNSTABLE_portalContainer={rootDiv}
maxHeight={maxHeight}
>
<ListBox className={styles.listBox}>
{items.map((item) => (
<ListBoxItem
aria-label={item.label}
className={`${styles.listBoxItem} ${showRadioButton && styles.showRadioButton} ${optionsIcon && styles.iconLabel}`}
id={item.value}
key={`${item.value}_${item.label}`}
data-testid={item.label}
>
{optionsIcon}
{item.label}
</ListBoxItem>
))}
</ListBox>
</Popover>
</Body>
</ReactAriaSelect>
</div>
)
}
function useSetOverflowVisibleOnRA(isNestedInModal?: boolean) {
function setOverflowVisible(isOpen: boolean) {
if (isOpen) {
document.body.style.overflow = 'visible'
} else if (!isNestedInModal) {
document.body.style.overflow = ''
}
}
return setOverflowVisible
}