Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions packages/ui/src/Tag/Tag.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Box } from "@mui/material";
import { Tag, type TagProps } from "./Tag";
import { TAG_COLORS, TAG_VARIANTS } from "./constants";
import type { StoryFn, Meta } from "@storybook/react-vite";
import { CheckmarkIcon } from "../internal/icons";

type Args = Omit<TagProps, "startIcon" | "endIcon"> & {
startIcon?: boolean;
endIcon?: boolean;
};

const meta: Meta<Args> = {
title: "Components/Tag",
component: Tag,
argTypes: {
color: {
control: {
type: "select",
},
options: TAG_COLORS,
},
variant: {
control: {
type: "select",
},
options: TAG_VARIANTS,
},
startIcon: {
control: {
type: "boolean",
},
},
endIcon: {
control: {
type: "boolean",
},
},
},
tags: ["autodocs"],
};

export default meta;

function TagRenderer(args: Args) {
return (
<Tag
{...args}
startIcon={args.startIcon ? <CheckmarkIcon /> : undefined}
endIcon={args.endIcon ? <CheckmarkIcon /> : undefined}
/>
);
}

const Template: StoryFn<Args> = (args) => <TagRenderer {...args} />;

export const Default = Template.bind({});

Default.args = {
label: "Text",
startIcon: false,
endIcon: false,
};

const AllVariantsAndColorsTemplate: StoryFn<Omit<Args, "variant" | "color">> = (
args
) => (
<>
{TAG_VARIANTS.map((variant) => (
<Box
key={variant}
sx={{ display: "flex", alignItems: "center", gap: 2, mb: 2 }}
>
{TAG_COLORS.map((color) => (
<Box key={color} sx={{ display: "flex", justifyContent: "center" }}>
<TagRenderer {...args} color={color} variant={variant} />
</Box>
))}
</Box>
))}
</>
);

export const AllVariantsAndColors = AllVariantsAndColorsTemplate.bind({});

AllVariantsAndColors.args = {
label: "Text",
startIcon: false,
endIcon: false,
};

AllVariantsAndColors.parameters = {
controls: {
exclude: ["variant", "color"],
},
};
140 changes: 140 additions & 0 deletions packages/ui/src/Tag/Tag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Box } from "@mui/material";
import { ColorDynamic } from "../color";
import { Typography } from "../Typography";
import type { TAG_COLORS, TAG_VARIANTS } from "./constants";

export type TagColor = (typeof TAG_COLORS)[number];
export type TagVariant = (typeof TAG_VARIANTS)[number];

export interface TagProps {
label: string;
color?: TagColor;
variant?: TagVariant;
isBold?: boolean;
startIcon?: React.ReactNode;
endIcon?: React.ReactNode;
}

const variantToColorMap: Record<
TagVariant,
Record<
TagColor,
{
color: string;
backgroundColor: string;
}
>
> = {
filled: {
blue: {
color: ColorDynamic.White,
backgroundColor: ColorDynamic.Blue300,
},
teal: {
color: ColorDynamic.White,
backgroundColor: ColorDynamic.Teal300,
},
gray: {
color: ColorDynamic.White,
backgroundColor: ColorDynamic.Dark300,
},
green: {
color: ColorDynamic.White,
backgroundColor: ColorDynamic.Green300,
},
purple: {
color: ColorDynamic.White,
backgroundColor: ColorDynamic.Purple300,
},
red: {
color: ColorDynamic.White,
backgroundColor: ColorDynamic.Red300,
},
yellow: {
color: ColorDynamic.White,
backgroundColor: ColorDynamic.Yellow300,
},
},
subtle: {
blue: {
color: ColorDynamic.Blue500,
backgroundColor: ColorDynamic.Blue50,
},
teal: {
color: ColorDynamic.Teal500,
backgroundColor: ColorDynamic.Teal50,
},
gray: {
color: ColorDynamic.Dark300,
backgroundColor: ColorDynamic.Silver200,
},
green: {
color: ColorDynamic.Green500,
backgroundColor: ColorDynamic.Green50,
},
purple: {
color: ColorDynamic.Purple500,
backgroundColor: ColorDynamic.Purple50,
},
red: {
color: ColorDynamic.Red500,
backgroundColor: ColorDynamic.Red50,
},
yellow: {
color: ColorDynamic.Yellow500,
backgroundColor: ColorDynamic.Yellow50,
},
},
};

export function Tag({
label,
variant = "subtle",
color = "blue",
isBold = true,
startIcon,
endIcon,
}: TagProps) {
const colorMap = variantToColorMap[variant][color];

return (
<Box
sx={(theme) => ({
color: colorMap.color,
backgroundColor: colorMap.backgroundColor,
borderRadius: "4px",
height: "22px",
paddingLeft: theme.spacing(0.5),
paddingRight: theme.spacing(0.5),
display: "inline-flex",
flexWrap: "nowrap",
whiteSpace: "nowrap",
alignItems: "center",
gap: theme.spacing(0.5),

[theme.breakpoints.only("xs")]: {
height: "26px",
},
})}
>
{startIcon && <TagIcon>{startIcon}</TagIcon>}

<Typography variant={isBold ? "body-semibold" : "body"}>
{label}
</Typography>

{endIcon && <TagIcon>{endIcon}</TagIcon>}
</Box>
);
}

function TagIcon({ children }: { children: React.ReactNode }) {
return (
<Box
component="span"
sx={{ display: "inherit", "& > :nth-of-type(1)": { fontSize: "16px" } }}
>
{children}
</Box>
);
}
11 changes: 11 additions & 0 deletions packages/ui/src/Tag/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const TAG_COLORS = [
"blue",
"gray",
"green",
"purple",
"red",
"teal",
"yellow",
] as const;

export const TAG_VARIANTS = ["filled", "subtle"] as const;
1 change: 1 addition & 0 deletions packages/ui/src/Tag/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Tag, type TagProps, type TagColor, type TagVariant } from "./Tag";
10 changes: 5 additions & 5 deletions packages/ui/src/Typography/typographyOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
} from "@mui/material/styles";

export function createTypographyOptions(
breakpoints: Breakpoints,
breakpoints: Breakpoints
): TypographyVariantsOptions {
const xsOnly = breakpoints.only("xs");

Expand Down Expand Up @@ -80,8 +80,8 @@ export function createTypographyOptions(

body: {
fontSize: "14px",
lineHeight: "20px",
fontWeight: 600,
lineHeight: "22px",
fontWeight: 400,
[xsOnly]: {
fontSize: "16px",
lineHeight: "26px",
Expand All @@ -90,8 +90,8 @@ export function createTypographyOptions(

"body-semibold": {
fontSize: "14px",
lineHeight: "20px",
fontWeight: 400,
lineHeight: "22px",
fontWeight: 600,
[xsOnly]: {
fontSize: "16px",
lineHeight: "26px",
Expand Down
18 changes: 18 additions & 0 deletions packages/ui/src/internal/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { SvgIcon, type SvgIconProps } from "@mui/material";

export const CheckmarkIcon = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<path
d="M9.08471 15.4676L5.29164 11.736L4 12.9978L9.08471 18L20 7.26174L18.7175 6L9.08471 15.4676Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.29165 11.736L9.08472 15.4676L18.7175 6L20 7.26174L9.08474 18L4.00001 12.9978L5.29165 11.736ZM9.08488 14.7663L18.7176 5.29877L20.713 7.26174L9.08472 18.7014L3.28577 12.9965L5.29289 11.0358L9.08488 14.7663Z"
fill="currentColor"
/>
</SvgIcon>
);
};