Merge pull request #98 from snowball-tools/andrehadianto/T-4868-tags

[T-4868] Tags component
This commit is contained in:
Andre Hadianto 2024-02-24 03:41:35 +08:00 committed by GitHub
commit 9db87e6be3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 246 additions and 14 deletions

View File

@ -0,0 +1,93 @@
import { tv } from 'tailwind-variants';
import type { VariantProps } from 'tailwind-variants';
export const tagTheme = 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 TagTheme = VariantProps<typeof tagTheme>;

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { Calendar } from 'components/shared/Calendar';
import { DatePicker } from 'components/shared/DatePicker';
import { Radio } from 'components/shared/Radio';
import { SegmentedControls } from 'components/shared/SegmentedControls';
import { Switch } from 'components/shared/Switch';
import { Value } from 'react-calendar/dist/cjs/shared/types';
import {
renderCheckbox,
renderCheckboxWithDescription,
} from './renders/checkbox';
import { avatars, avatarsFallback } from './renders/avatar';
import { renderBadges } from './renders/badge';
import {
@ -14,21 +14,22 @@ import {
renderLinks,
} from './renders/button';
import {
renderTabWithBadges,
renderTabs,
renderVerticalTabs,
} from './renders/tabs';
import { SegmentedControls } from 'components/shared/SegmentedControls';
import { SEGMENTED_CONTROLS_OPTIONS } from './renders/segmentedControls';
import { Switch } from 'components/shared/Switch';
import { RADIO_OPTIONS } from './renders/radio';
import { Radio } from 'components/shared/Radio';
renderCheckbox,
renderCheckboxWithDescription,
} from './renders/checkbox';
import {
renderInlineNotificationWithDescriptions,
renderInlineNotifications,
} from './renders/inlineNotifications';
import { renderInputs } from './renders/input';
import { DatePicker } from 'components/shared/DatePicker';
import { RADIO_OPTIONS } from './renders/radio';
import { SEGMENTED_CONTROLS_OPTIONS } from './renders/segmentedControls';
import {
renderTabWithBadges,
renderTabs,
renderVerticalTabs,
} from './renders/tabs';
import { renderDefaultTag, renderMinimalTag } from './renders/tag';
import { renderToast, renderToastsWithCta } from './renders/toast';
import { renderTooltips } from './renders/tooltip';
@ -53,6 +54,19 @@ const Page = () => {
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Tag */}
<div className="flex flex-col gap-10 items-center justify-between">
<div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Tag</h1>
<div className="flex flex-col gap-10 items-center justify-center">
{renderDefaultTag()}
{renderMinimalTag()}
</div>
</div>
</div>
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Toast */}
<div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Toasts</h1>

View File

@ -0,0 +1,63 @@
import React from 'react';
import { Tag } from 'components/shared/Tag';
import { PlusIcon } from 'components/shared/CustomIcon';
export const renderDefaultTag = () =>
(['default'] as const).map((style) => (
<div key={style} className="flex gap-4 items-center">
{(
['attention', 'negative', 'positive', 'emphasized', 'neutral'] as const
).map((type) => (
<div key={type} className="flex flex-col gap-4">
<Tag
leftIcon={<PlusIcon />}
rightIcon={<PlusIcon />}
style={style}
type={type}
size="sm"
>
Label
</Tag>
<Tag
leftIcon={<PlusIcon />}
rightIcon={<PlusIcon />}
size="xs"
style={style}
type={type}
>
Label
</Tag>
</div>
))}
</div>
));
export const renderMinimalTag = () =>
(['minimal'] as const).map((style) => (
<div key={style} className="flex gap-4 items-center">
{(
['attention', 'negative', 'positive', 'emphasized', 'neutral'] as const
).map((type) => (
<div key={type} className="flex flex-col gap-4">
<Tag
leftIcon={<PlusIcon />}
rightIcon={<PlusIcon />}
style={style}
type={type}
size="sm"
>
Label
</Tag>
<Tag
leftIcon={<PlusIcon />}
rightIcon={<PlusIcon />}
size="xs"
style={style}
type={type}
>
Label
</Tag>
</div>
))}
</div>
));