forked from cerc-io/snowballtools-base
Merge pull request #98 from snowball-tools/andrehadianto/T-4868-tags
[T-4868] Tags component
This commit is contained in:
commit
9db87e6be3
93
packages/frontend/src/components/shared/Tag/Tag.theme.ts
Normal file
93
packages/frontend/src/components/shared/Tag/Tag.theme.ts
Normal 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>;
|
60
packages/frontend/src/components/shared/Tag/Tag.tsx
Normal file
60
packages/frontend/src/components/shared/Tag/Tag.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
2
packages/frontend/src/components/shared/Tag/index.ts
Normal file
2
packages/frontend/src/components/shared/Tag/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './Tag';
|
||||||
|
export * from './Tag.theme';
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Calendar } from 'components/shared/Calendar';
|
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 { Value } from 'react-calendar/dist/cjs/shared/types';
|
||||||
import {
|
|
||||||
renderCheckbox,
|
|
||||||
renderCheckboxWithDescription,
|
|
||||||
} from './renders/checkbox';
|
|
||||||
import { avatars, avatarsFallback } from './renders/avatar';
|
import { avatars, avatarsFallback } from './renders/avatar';
|
||||||
import { renderBadges } from './renders/badge';
|
import { renderBadges } from './renders/badge';
|
||||||
import {
|
import {
|
||||||
@ -14,21 +14,22 @@ import {
|
|||||||
renderLinks,
|
renderLinks,
|
||||||
} from './renders/button';
|
} from './renders/button';
|
||||||
import {
|
import {
|
||||||
renderTabWithBadges,
|
renderCheckbox,
|
||||||
renderTabs,
|
renderCheckboxWithDescription,
|
||||||
renderVerticalTabs,
|
} from './renders/checkbox';
|
||||||
} 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';
|
|
||||||
import {
|
import {
|
||||||
renderInlineNotificationWithDescriptions,
|
renderInlineNotificationWithDescriptions,
|
||||||
renderInlineNotifications,
|
renderInlineNotifications,
|
||||||
} from './renders/inlineNotifications';
|
} from './renders/inlineNotifications';
|
||||||
import { renderInputs } from './renders/input';
|
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 { renderToast, renderToastsWithCta } from './renders/toast';
|
||||||
import { renderTooltips } from './renders/tooltip';
|
import { renderTooltips } from './renders/tooltip';
|
||||||
|
|
||||||
@ -53,6 +54,19 @@ const Page = () => {
|
|||||||
|
|
||||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
<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 */}
|
{/* Toast */}
|
||||||
<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">Toasts</h1>
|
<h1 className="text-2xl font-bold">Toasts</h1>
|
||||||
|
63
packages/frontend/src/pages/components/renders/tag.tsx
Normal file
63
packages/frontend/src/pages/components/renders/tag.tsx
Normal 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>
|
||||||
|
));
|
Loading…
Reference in New Issue
Block a user