vega-frontend-monorepo/libs/ui-toolkit/src/components/select/select.tsx
2023-02-06 11:35:40 -08:00

142 lines
4.6 KiB
TypeScript

import type { Ref, SelectHTMLAttributes } from 'react';
import { useRef } from 'react';
import { forwardRef } from 'react';
import classNames from 'classnames';
import { Icon } from '..';
import { defaultSelectElement } from '../../utils/shared';
import * as SelectPrimitive from '@radix-ui/react-select';
export interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
hasError?: boolean;
className?: string;
value?: string | number;
children?: React.ReactNode;
}
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
({ className, hasError, ...props }, ref) => (
<div className="relative">
<select
ref={ref}
{...props}
className={classNames(
defaultSelectElement(hasError),
className,
'appearance-none rounded-md'
)}
/>
<Icon
name="chevron-down"
className="absolute top-3 right-4 z-10 pointer-events-none"
/>
</div>
)
);
export type RichSelectProps = React.ComponentProps<
typeof SelectPrimitive.Root
> & {
placeholder: string;
hasError?: boolean;
id?: string;
'data-testid'?: string;
};
export const RichSelect = forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
RichSelectProps
>(({ id, children, placeholder, hasError, ...props }, forwardedRef) => {
const containerRef = useRef<HTMLDivElement>();
const contentRef = useRef<HTMLDivElement>();
const setWidth = () => {
if (contentRef.current) {
contentRef.current.style.width = containerRef.current
? `${containerRef.current.getBoundingClientRect().width}px`
: 'auto';
}
};
return (
<div
ref={containerRef as Ref<HTMLDivElement>}
className="flex items-center relative"
>
<SelectPrimitive.Root
{...props}
onOpenChange={() => {
setWidth();
}}
defaultOpen={false}
>
<SelectPrimitive.Trigger
data-testid={props['data-testid'] || 'rich-select-trigger'}
className={classNames(
defaultSelectElement(hasError),
'rounded-md pl-2 pr-11',
'max-w-full overflow-hidden break-all'
)}
id={id}
ref={forwardedRef}
>
<SelectPrimitive.Value placeholder={placeholder} />
<SelectPrimitive.Icon className={classNames('absolute right-4')}>
<Icon name="chevron-down" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal container={containerRef.current}>
<SelectPrimitive.Content
ref={contentRef as Ref<HTMLDivElement>}
className={classNames(
'z-20',
'bg-white dark:bg-black',
'border border-neutral-500 focus:border-black dark:focus:border-white',
'overflow-hidden',
'shadow-lg'
)}
position={'item-aligned'}
side={'bottom'}
align={'center'}
>
<SelectPrimitive.ScrollUpButton className="flex items-center justify-center p-1 absolute w-full h-6 z-20 bg-gradient-to-t from-transparent to-neutral-50 dark:to-neutral-900">
<Icon name="chevron-up" />
</SelectPrimitive.ScrollUpButton>
<SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>
<SelectPrimitive.ScrollDownButton className="flex items-center justify-center p-1 absolute bottom-0 w-full h-6 z-20 bg-gradient-to-b from-transparent to-neutral-50 dark:to-neutral-900">
<Icon name="chevron-down" />
</SelectPrimitive.ScrollDownButton>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
</SelectPrimitive.Root>
</div>
);
});
export const Option = forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentProps<typeof SelectPrimitive.Item>
>(({ children, className, ...props }, forwardedRef) => (
<SelectPrimitive.Item
data-testid="rich-select-option"
className={classNames(
'relative',
'text-black dark:text-white',
'cursor-pointer outline-none',
'hover:bg-neutral-100 dark:hover:bg-neutral-800',
'focus:bg-neutral-100 dark:focus:bg-neutral-800',
'pl-2 py-2',
'pr-12',
'w-full',
'text-sm',
'data-selected:bg-vega-yellow dark:data-selected:text-black dark:data-selected:bg-vega-yellow',
className
)}
{...props}
ref={forwardedRef}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator className="absolute right-4 top-[50%] translate-y-[-50%]">
<Icon name="tick" />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
));