feat: 컴포넌트 추가, 스타일 수정
This commit is contained in:
105
web-app/app/shared/components/select/Select.tsx
Normal file
105
web-app/app/shared/components/select/Select.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { type Ref } from 'react';
|
||||
import {
|
||||
Button as AriaButton,
|
||||
Select as AriaSelect,
|
||||
type SelectProps as AriaSelectProps,
|
||||
FieldError,
|
||||
ListBox,
|
||||
ListBoxItem,
|
||||
Popover,
|
||||
SelectValue,
|
||||
} from 'react-aria-components';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { tv } from 'tailwind-variants';
|
||||
|
||||
export type SelectOption = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const selectStyle = tv({
|
||||
base: 'flex items-center text-start gap-4 w-full bg-white text-sm text-dabeeo-black-34 border border-dabeeo-gray-be cursor-default px-3 h-9 min-w-[120px] outline-0',
|
||||
variants: {
|
||||
isDisabled: {
|
||||
true: 'text-dabeeo-gray-99 bg-dabeeo-gray-eb border-dabeeo-gray-be cursor-not-allowed',
|
||||
false:
|
||||
'hover:text-dabeeo-black-34 hover:border-dabeeo-black-34 data-pressed:text-dabeeo-black-34 data-pressed:border-dabeeo-black-34',
|
||||
},
|
||||
isReadOnly: {
|
||||
true: 'text-dabeeo-black-34 bg-dabeeo-yellow-secondary cursor-auto',
|
||||
},
|
||||
isFocused: {
|
||||
true: 'border-dabeeo-black-34',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export interface SelectProps<T extends SelectOption>
|
||||
extends Omit<AriaSelectProps<T>, 'selectionMode' | 'onChange' | 'children'> {
|
||||
items?: Iterable<T>;
|
||||
onChange?: (value: T['value']) => void;
|
||||
maxHeight?: number;
|
||||
placement?: 'bottom' | 'top';
|
||||
ref?: Ref<HTMLButtonElement>;
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const Select = <T extends SelectOption>(props: SelectProps<T>) => {
|
||||
const { className, items, onChange, maxHeight, placement = 'bottom', ref, isReadOnly, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<AriaSelect
|
||||
selectionMode="single"
|
||||
aria-label="선택"
|
||||
className={clsx('group/select', className)}
|
||||
{...restProps}
|
||||
onChange={(key) => {
|
||||
onChange?.(key as T['value']);
|
||||
}}
|
||||
>
|
||||
<AriaButton
|
||||
ref={ref}
|
||||
className={(renderProps) =>
|
||||
selectStyle({ ...renderProps, isDisabled: props.isDisabled || isReadOnly, isReadOnly })}
|
||||
{...{ isDisabled: props.isDisabled || isReadOnly }}
|
||||
>
|
||||
<SelectValue className="flex-1 text-sm data-placeholder:text-dabeeo-gray-99">
|
||||
{({ selectedText, defaultChildren }) => selectedText || defaultChildren}
|
||||
</SelectValue>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="10"
|
||||
height="5"
|
||||
viewBox="0 0 10 5"
|
||||
fill="none"
|
||||
className={clsx('group-data-open/select:rotate-180 transition', isReadOnly && 'hidden')}
|
||||
>
|
||||
<path d="M0 0H10L5 5L0 0Z" fill="#BEBEBE" />
|
||||
</svg>
|
||||
</AriaButton>
|
||||
<FieldError className="mt-0.5 text-dabeeo-red text-xs" />
|
||||
<Popover className="w-(--trigger-width)" offset={0.5} placement={placement}>
|
||||
<ListBox
|
||||
items={items}
|
||||
style={{ maxHeight: maxHeight ? `${maxHeight}px` : 'inherit' }}
|
||||
className={clsx(
|
||||
'w-full outline-0 box-border border-l border-r border-dabeeo-gray-be max-h-[inherit] bg-white overflow-auto',
|
||||
placement === 'top' ? 'border-t' : 'border-b'
|
||||
)}
|
||||
>
|
||||
{(item) => (
|
||||
<ListBoxItem
|
||||
id={item.value}
|
||||
className="outline-0 px-3 py-2 text-sm font-semibold text-dabeeo-black-34 data-selected:text-dabeeo-navy-main data-selected:bg-dabeeo-yellow-secondary data-selected:font-bold data-selected:hover:bg-dabeeo-gray-eb focus:bg-dabeeo-gray-eb hover:bg-dabeeo-gray-eb data-disabled:text-dabeeo-gray-be"
|
||||
isDisabled={!!item.isDisabled}
|
||||
>
|
||||
{item.label}
|
||||
</ListBoxItem>
|
||||
)}
|
||||
</ListBox>
|
||||
</Popover>
|
||||
</AriaSelect>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user