diff --git a/packages/frontend/src/components/shared/Select/SelectItem/SelectItem.theme.ts b/packages/frontend/src/components/shared/Select/SelectItem/SelectItem.theme.ts new file mode 100644 index 00000000..75ac68df --- /dev/null +++ b/packages/frontend/src/components/shared/Select/SelectItem/SelectItem.theme.ts @@ -0,0 +1,56 @@ +import { tv, VariantProps } from 'tailwind-variants'; + +export const selectItemTheme = tv({ + slots: { + wrapper: [ + 'p-2', + 'gap-3', + 'flex', + 'items-start', + 'justify-between', + 'rounded-lg', + 'group', + 'data-[disabled]:cursor-not-allowed', + ], + icon: ['h-4.5', 'w-4.5', 'text-elements-high-em'], + content: ['flex', 'flex-1', 'whitespace-nowrap'], + label: [ + 'text-sm', + 'text-elements-high-em', + 'tracking-[-0.006em]', + 'data-[disabled]:text-elements-disabled', + ], + description: [ + 'text-xs', + 'text-elements-low-em', + 'data-[disabled]:text-elements-disabled', + ], + dot: ['h-1', 'w-1', 'rounded-full', 'bg-border-interactive-hovered/[0.14]'], + }, + variants: { + orientation: { + horizontal: { + wrapper: ['items-center'], + content: ['flex-row', 'items-center', 'gap-2'], + }, + vertical: { + content: ['flex-col', 'gap-0.5'], + }, + }, + variant: { + default: { + wrapper: [], + }, + danger: { + wrapper: [], + }, + }, + active: { + true: { + wrapper: ['bg-base-bg-emphasized', 'data-[disabled]:bg-transparent'], + }, + }, + }, +}); + +export type SelectItemTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Select/SelectItem/SelectItem.tsx b/packages/frontend/src/components/shared/Select/SelectItem/SelectItem.tsx new file mode 100644 index 00000000..5112fb4e --- /dev/null +++ b/packages/frontend/src/components/shared/Select/SelectItem/SelectItem.tsx @@ -0,0 +1,86 @@ +import React, { ComponentPropsWithoutRef, useMemo } from 'react'; +import { Overwrite, UseComboboxGetItemPropsReturnValue } from 'downshift'; +import { SelectOption, SelectOrientation } from 'components/shared/Select'; +import { selectItemTheme, SelectItemTheme } from './SelectItem.theme'; +import { cloneIcon } from 'utils/cloneIcon'; +import { cn } from 'utils/classnames'; +import { CheckRadioIcon } from 'components/shared/CustomIcon'; +import { OmitCommon } from 'types/common'; + +/** + * Represents a type that merges ComponentPropsWithoutRef<'li'> with certain exclusions. + * @type {MergedComponentPropsWithoutRef} + */ +type MergedComponentPropsWithoutRef = OmitCommon< + ComponentPropsWithoutRef<'li'>, + Omit< + Overwrite, + 'index' | 'item' + > +>; + +export interface SelectItemProps + extends MergedComponentPropsWithoutRef, + SelectItemTheme { + selected: boolean; + option: SelectOption; + orientation?: SelectOrientation; + hovered?: boolean; + empty?: boolean; +} + +export const SelectItem = ({ + className, + selected, + option, + orientation, + hovered, + empty, + variant, + ...props +}: SelectItemProps) => { + const theme = selectItemTheme({ active: hovered, orientation, variant }); + + const { label, description, leftIcon, rightIcon, disabled } = option; + + const renderRightIcon = useMemo(() => { + if (rightIcon) { + return cloneIcon(rightIcon, { className: theme.icon() }); + } else if (selected) { + return ( + + ); + } + return null; + }, [rightIcon]); + + if (empty) { + return ( +
  • + No results found +
  • + ); + } + + return ( +
  • + {leftIcon && cloneIcon(leftIcon, { className: theme.icon() })} +
    +

    + {label} +

    + {orientation === 'horizontal' && } + {description && ( +

    + {description} +

    + )} +
    + {renderRightIcon} +
  • + ); +}; diff --git a/packages/frontend/src/components/shared/Select/SelectItem/index.ts b/packages/frontend/src/components/shared/Select/SelectItem/index.ts new file mode 100644 index 00000000..fbd9e186 --- /dev/null +++ b/packages/frontend/src/components/shared/Select/SelectItem/index.ts @@ -0,0 +1 @@ +export * from './SelectItem';