Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Button } from "./Button";
import { TrashDisableIcon, TrashOnIcon, TrashWhiteIcon } from "../../../icons";

import type { Meta, StoryObj } from "@storybook/react";

const VARIANTS = ["primary", "secondary"] as const;
const APPEARANCES = ["fill", "outline"] as const;
const SIZES = ["m", "lg"] as const;

const getTrashIcon = (
variant: (typeof VARIANTS)[number],
disabled: boolean,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
) => {
if (variant === "primary") return <TrashWhiteIcon />;
return disabled ? <TrashDisableIcon /> : <TrashOnIcon />;
};

const meta = {
title: "Components/Button/Button",
component: Button,
parameters: {
layout: "centered",
},
argTypes: {
variant: { control: "select", options: VARIANTS },
appearance: { control: "select", options: APPEARANCES },
size: { control: "select", options: SIZES },
disabled: { control: "boolean" },
children: { control: "text" },
},
args: {
variant: "primary",
appearance: "fill",
size: "m",
disabled: false,
children: "생성하기",
},
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const AllVariants: Story = {
parameters: { controls: { disable: true } },
render: () => (
<div className="flex flex-col gap-6">
{SIZES.map((size) => (
<div key={size} className="flex flex-col gap-3">
{[false, true].map((disabled) => (
<div key={String(disabled)} className="flex items-center gap-3">
{VARIANTS.map((variant) =>
APPEARANCES.map((appearance) => (
<Button
key={`${variant}-${appearance}`}
variant={variant}
appearance={appearance}
size={size}
disabled={disabled}
>
{size === "m" ? "생성하기" : "시간 추가하기"}
</Button>
)),
)}
{VARIANTS.map((variant) => (
<Button
key={`${variant}-icon`}
variant={variant}
appearance="fill"
size={size}
disabled={disabled}
icon={getTrashIcon(variant, disabled)}
>
삭제하기
</Button>
))}
</div>
))}
</div>
))}
</div>
),
};

export const WithIcon: Story = {
args: {
icon: <TrashWhiteIcon />,
Comment thread
ehye1 marked this conversation as resolved.
Outdated
children: "삭제하기",
},
};

export const FullWidth: Story = {
parameters: { controls: { disable: true } },
render: () => (
<div className="flex w-100 flex-col gap-3">
<Button variant="primary" appearance="fill" size="m" className="w-full">
w-full
</Button>
<div className="flex gap-3">
<Button
variant="secondary"
appearance="outline"
size="lg"
className="flex-1"
>
닫기
</Button>
<Button
variant="primary"
appearance="fill"
size="lg"
className="flex-1"
>
시간 추가하기
</Button>
</div>
</div>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { cn } from "../../../lib";

import type { ButtonHTMLAttributes, ReactNode } from "react";

type ButtonVariantTypes = "primary" | "secondary";
type ButtonAppearanceTypes = "fill" | "outline";
type ButtonSizeTypes = "m" | "lg";

const BUTTON_VARIANTS: Record<
ButtonVariantTypes,
Record<ButtonAppearanceTypes, string>
> = {
primary: {
fill: "bg-timo-blue-300 text-white disabled:bg-timo-blue-65",
outline:
"border-timo-blue-300 text-timo-blue-300 border bg-white disabled:border-timo-gray-500 disabled:text-timo-gray-700",
},
secondary: {
fill: "bg-timo-gray-300 text-timo-gray-900 disabled:text-timo-gray-700",
outline:
"border-timo-gray-500 text-timo-gray-900 border bg-white disabled:text-timo-gray-700",
},
};

const BUTTON_SIZE: Record<
ButtonSizeTypes,
{ height: string; padding: { text: string; icon: string } }
> = {
m: { height: "h-8.5", padding: { text: "px-4", icon: "px-2" } },
lg: { height: "h-11.5", padding: { text: "px-12", icon: "px-12" } },
};

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant: ButtonVariantTypes;
appearance: ButtonAppearanceTypes;
size: ButtonSizeTypes;
icon?: ReactNode;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

export const Button = ({
variant,
appearance,
size,
icon,
type = "button",
className,
children,
...rest
}: ButtonProps) => {
return (
<button
type={type}
className={cn(
"typo-headline-m-14 inline-flex items-center justify-center gap-0.5 rounded-[4px] whitespace-nowrap outline-hidden",
"focus-visible:ring-timo-blue-300 focus-visible:ring-2 focus-visible:ring-offset-2",
"disabled:cursor-not-allowed",
BUTTON_VARIANTS[variant][appearance],
BUTTON_SIZE[size].height,
BUTTON_SIZE[size].padding[icon ? "icon" : "text"],
className,
)}
{...rest}
>
{icon && (
<span
aria-hidden="true"
className="flex size-5.5 shrink-0 items-center justify-center"
>
{icon}
</span>
)}
{children}
</button>
);
};
1 change: 1 addition & 0 deletions packages/timo-design-system/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { TagNameInput } from "./tag/tag-name-input/TagNameInput";
export { PriorityIcon } from "./priority/priority-icon/PriorityIcon";
export { PrioritySelector } from "./priority/priority-selector/PrioritySelector";
export type { PriorityLevel } from "./priority/priority-selector/PrioritySelector";
export { Button } from "./button/button/Button";
export { CreateButton } from "./button/create-button/CreateButton";
export { DeleteButton } from "./button/delete-button/DeleteButton";
export { AddTaskButton } from "./button/add-task-button/AddTaskButton";
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading