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
48 changes: 31 additions & 17 deletions src/components/ui/__stories__/Tag.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Meta, StoryObj } from "@storybook/react"

import { HStack, VStack } from "../flex"
import { Tag } from "../tag"
import { Tag, TagButton } from "../tag"

const meta = {
title: "Molecules / Display Content / New Tags",
Expand All @@ -16,22 +16,36 @@ type Story = StoryObj<typeof meta>
const statusArray = ["normal", "tag", "success", "error", "warning"] as const

// "subtle" is default variant
const variantArray = ["subtle", "highContrast", "solid", "outline"] as const

const StyleVariantList = () => (
<HStack>
{statusArray.map((status) => (
<VStack key={status}>
{variantArray.map((variant) => (
<Tag key={variant} status={status} variant={variant}>
Tag Name
</Tag>
))}
</VStack>
))}
</HStack>
)
const variantArray = ["subtle", "high-contrast", "solid", "outline"] as const

export const StyleVariantsBasic: Story = {
render: (args) => <StyleVariantList {...args} />,
render: () => (
<HStack>
{statusArray.map((status) => (
<VStack key={status}>
{variantArray.map((variant) => (
<Tag key={variant} status={status} variant={variant}>
Tag Name
</Tag>
))}
</VStack>
))}
</HStack>
),
}

export const StyleVariantsButton: Story = {
render: () => (
<HStack>
{statusArray.map((status) => (
<VStack key={status}>
{variantArray.map((variant) => (
<TagButton key={variant} status={status} variant={variant}>
Tag Name
</TagButton>
))}
</VStack>
))}
</HStack>
),
}
90 changes: 65 additions & 25 deletions src/components/ui/tag.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { forwardRef } from "react"
import { cva, VariantProps } from "class-variance-authority"
import { Slot } from "@radix-ui/react-slot"

import { cn } from "@/lib/utils/cn"

const tagVariants = cva(
"inline-flex items-center rounded-full border px-2 py-0.5 min-h-8 text-xs uppercase transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
"inline-flex items-center rounded-full border px-2 py-0.5 min-h-8 text-xs uppercase transition-colors",
{
variants: {
status: {
normal: "bg-background-highlight text-body-medium border-body-medium",
tag: "bg-primary-low-contrast text-primary-high-contrast border-primary",
success: "bg-success-light text-success border-success-border",
error: "bg-error-light text-error border-error-border",
warning: "bg-warning-light text-warning-dark border-warning-border",
normal:
"bg-background-highlight text-body-medium border-body-medium hover:shadow-body-medium",
tag: "bg-primary-low-contrast text-primary-high-contrast border-primary hover:shadow-primary-high-contrast",
success:
"bg-success-light text-success border-success-border hover:shadow-success",
error:
"bg-error-light text-error border-error-border hover:shadow-error",
warning:
"bg-warning-light text-warning-dark border-warning-border hover:shadow-warning-dark dark:hover:shadow-warning",
},
variant: {
subtle: "border-transparent",
highContrast: "border-transparent",
"high-contrast": "border-transparent",
solid: "border-transparent text-body-inverse",
outline: "bg-transparent",
},
Expand All @@ -25,45 +30,50 @@ const tagVariants = cva(
{
variant: "solid",
status: "normal",
className: "bg-body-medium",
className:
"bg-body-medium focus-visible:outline-body hover:shadow-body",
},
{
variant: "solid",
status: "tag",
className: "bg-primary",
className:
"bg-primary focus-visible:outline-primary-high-contrast hover:shadow-primary-high-contrast",
},
{
variant: "solid",
status: "success",
className: "bg-success text-success-light",
className:
"bg-success text-success-light focus-visible:outline-success-dark hover:shadow-success-dark dark:hover:shadow-success-light",
},
{
variant: "solid",
status: "error",
className: "bg-error text-error-light",
className:
"bg-error text-error-light focus-visible:outline-error-dark hover:shadow-error-dark dark:hover:shadow-error-light",
},
{
variant: "solid",
status: "warning",
className: "bg-warning text-warning-dark",
className:
"bg-warning text-warning-dark focus-visible:outline-warning-border hover:shadow-warning-dark dark:hover:shadow-warning-light",
},
{
variant: "highContrast",
variant: "high-contrast",
status: "normal",
className: "bg-body-light text-body",
},
{
variant: "highContrast",
variant: "high-contrast",
status: "tag",
className: "bg-background-highlight",
},
{
variant: "highContrast",
variant: "high-contrast",
status: "success",
className: "text-success-dark",
},
{
variant: "highContrast",
variant: "high-contrast",
status: "error",
className: "text-error-dark",
},
Expand Down Expand Up @@ -96,14 +106,44 @@ export interface TagProps
asChild?: boolean
}

function Tag({ className, asChild, variant, status, ...props }: TagProps) {
const Comp = asChild ? Slot : "div"
return (
<Comp
className={cn(tagVariants({ variant, status }), className)}
{...props}
/>
)
const Tag = forwardRef<HTMLDivElement, TagProps>(
({ className, asChild, variant, status, ...props }, ref) => {
const Comp = asChild ? Slot : "div"
return (
<Comp
ref={ref}
className={cn(tagVariants({ variant, status }), className)}
{...props}
/>
)
}
)

Tag.displayName = "Tag"

export interface TagButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof tagVariants> {
asChild?: boolean
}

export { Tag }
const TagButton = forwardRef<HTMLButtonElement, TagButtonProps>(
({ className, asChild, variant, status, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
ref={ref}
className={cn(
"hover:shadow-[2px_2px] focus-visible:outline focus-visible:outline-4 focus-visible:-outline-offset-1 focus-visible:outline-inherit",
tagVariants({ variant, status }),
className
)}
{...props}
/>
)
}
)

TagButton.displayName = "TagButton"

export { Tag, TagButton }