diff --git a/src/shared/components/tags-input/tags-input.module.css b/src/shared/components/tags-input/tags-input.module.css new file mode 100644 index 000000000..535a94aa3 --- /dev/null +++ b/src/shared/components/tags-input/tags-input.module.css @@ -0,0 +1,66 @@ +.root { + & [data-disabled='true'] { + opacity: 0.6; + } +} + +.label { + margin-bottom: var(--theme-spacing-sm); +} + +.dropdown { + padding: var(--theme-spacing-xs); + color: var(--theme-colors-surface-foreground); + background: var(--theme-colors-surface); + border: 1px solid var(--theme-colors-border); +} + +.input { + width: 100%; + border: 1px solid transparent; + + &[data-variant='default'] { + color: var(--theme-colors-surface-foreground); + background: var(--theme-colors-surface); + } + + &[data-variant='filled'] { + color: var(--theme-colors-foreground); + background: var(--theme-colors-background); + + & .pill { + background: var(--theme-colors-surface); + } + } +} + +.input:focus-within { + border-color: lighten(var(--theme-colors-border), 10%); +} + +.input-field { + color: inherit; +} + +.option { + position: relative; + padding: var(--theme-spacing-sm) var(--theme-spacing-md); +} + +.option:hover { + background: lighten(var(--theme-colors-surface), 5%); +} + +.pill { + font-size: var(--theme-font-size-sm); + background: var(--theme-colors-background); + border-radius: var(--theme-radius-sm); +} + +.pills-list { + padding-right: var(--theme-spacing-lg); +} + +.clear-button { + background: transparent !important; +} diff --git a/src/shared/components/tags-input/tags-input.tsx b/src/shared/components/tags-input/tags-input.tsx new file mode 100644 index 000000000..4c7232e3f --- /dev/null +++ b/src/shared/components/tags-input/tags-input.tsx @@ -0,0 +1,58 @@ +import { + TagsInput as MantineTagsInput, + TagsInputProps as MantineTagsInputProps, +} from '@mantine/core'; +import { CSSProperties, useMemo } from 'react'; + +import styles from './tags-input.module.css'; + +export interface TagsInputProps extends MantineTagsInputProps { + maxWidth?: CSSProperties['maxWidth']; + width?: CSSProperties['width']; +} + +const defaultClassNames = { + dropdown: styles.dropdown, + input: styles.input, + inputField: styles.inputField, + label: styles.label, + option: styles.option, + pill: styles.pill, + pillsList: styles.pillsList, + root: styles.root, +}; + +const defaultClearButtonProps = { + classNames: { + root: styles.clearButton, + }, + variant: 'transparent' as const, +}; + +export const TagsInput = ({ + classNames, + maxWidth, + variant = 'default', + width, + ...props +}: TagsInputProps) => { + const mergedClassNames = useMemo( + () => (classNames ? { ...defaultClassNames, ...classNames } : defaultClassNames), + [classNames], + ); + + const style = useMemo( + () => (maxWidth || width ? { maxWidth, width } : undefined), + [maxWidth, width], + ); + + return ( + + ); +}; diff --git a/src/types/mantine.d.ts b/src/types/mantine.d.ts index 5f00eb0f5..7ec1c2023 100644 --- a/src/types/mantine.d.ts +++ b/src/types/mantine.d.ts @@ -1,7 +1,8 @@ -import { ActionIconSize, PillVariant } from '@mantine/core'; +import { ActionIconSize, PillVariant, TagsInputVariant } from '@mantine/core'; type ExtendedActionIconSize = 'compact-md' | 'compact-sm' | 'compact-xs' | ActionIconSize; type ExtendedPillVariant = 'outline' | PillVariant; +type ExtendedTagsInputVariant = 'default' | 'filled' | TagsInputVariant; declare module '@mantine/core' { export interface ActionIconProps { @@ -11,4 +12,8 @@ declare module '@mantine/core' { export interface PillProps { variant?: ExtendedPillVariant; } + + export interface TagsInputProps { + variant?: ExtendedTagsInputVariant; + } }