️ feat: implement tags component

This commit is contained in:
Andre H 2024-02-22 17:01:36 +07:00
parent 496404195c
commit fb932eeb04
3 changed files with 170 additions and 0 deletions

View File

@ -0,0 +1,93 @@
import { tv } from 'tailwind-variants';
import type { VariantProps } from 'tailwind-variants';
export const tagsTheme = tv(
{
slots: {
wrapper: ['flex', 'gap-1.5', 'rounded-lg', 'border'],
icon: ['h-4', 'w-4'],
label: ['font-inter', 'text-xs'],
},
variants: {
type: {
attention: {
icon: ['text-elements-warning'],
},
negative: {
icon: ['text-elements-danger'],
},
positive: {
icon: ['text-elements-success'],
},
emphasized: {
icon: ['text-elements-on-secondary'],
},
neutral: {
icon: ['text-elements-mid-em'],
},
},
style: {
default: {},
minimal: {
wrapper: ['border-border-interactive', 'bg-controls-tertiary'],
label: ['text-elements-high-em'],
},
},
size: {
sm: {
wrapper: ['px-2', 'py-2'],
},
xs: {
wrapper: ['px-2', 'py-1.5'],
},
},
},
compoundVariants: [
{
type: 'attention',
style: 'default',
class: {
wrapper: ['border-orange-200', 'bg-orange-50'],
},
},
{
type: 'negative',
style: 'default',
class: {
wrapper: ['border-rose-200', 'bg-rose-50'],
},
},
{
type: 'positive',
style: 'default',
class: {
wrapper: ['border-emerald-200', 'bg-emerald-50'],
},
},
{
type: 'emphasized',
style: 'default',
class: {
wrapper: ['border-snowball-200', 'bg-snowball-50'],
},
},
{
type: 'neutral',
style: 'default',
class: {
wrapper: ['border-gray-200', 'bg-gray-50'],
},
},
],
defaultVariants: {
type: 'attention',
style: 'default',
size: 'sm',
},
},
{
responsiveVariants: true,
},
);
export type TagsTheme = VariantProps<typeof tagsTheme>;

View File

@ -0,0 +1,75 @@
import React, {
type ReactNode,
type ComponentPropsWithoutRef,
useMemo,
} from 'react';
import { TagsTheme, tagsTheme } from './Tags.theme';
import { cloneIcon } from 'utils/cloneIcon';
type TagsProps = ComponentPropsWithoutRef<'div'> &
TagsTheme & {
/**
* The optional left icon element for a component.
* @type {ReactNode}
*/
leftIcon?: ReactNode;
/**
* The optional right icon element to display.
* @type {ReactNode}
*/
rightIcon?: ReactNode;
/**
* The optional type of the tags component.
* @type {TagsTheme['type']}
**/
type?: TagsTheme['type'];
/**
* The optional style of the tags component.
* @type {TagsTheme['style']}
*/
style?: TagsTheme['style'];
/**
* The optional size of the tags component.
* @type {TagsTheme['size']}
*/
size?: TagsTheme['size'];
};
export const Tags = ({
children,
leftIcon,
rightIcon,
type = 'attention',
style = 'default',
size = 'sm',
}: TagsProps) => {
const {
wrapper: wrapperCls,
icon: iconCls,
label: labelCls,
} = tagsTheme({
type,
style,
size,
});
const renderLeftIcon = useMemo(() => {
if (!leftIcon) return null;
return <div className={iconCls()}>{cloneIcon(leftIcon, { size: 16 })}</div>;
}, [iconCls, leftIcon]);
const renderRightIcon = useMemo(() => {
if (!rightIcon) return null;
return (
<div className={iconCls()}>{cloneIcon(rightIcon, { size: 16 })}</div>
);
}, [iconCls, rightIcon]);
return (
<div className={wrapperCls()}>
{renderLeftIcon}
<p className={labelCls()}>{children}</p>
{renderRightIcon}
</div>
);
};

View File

@ -0,0 +1,2 @@
export * from './Tags';
export * from './Tags.theme';