Skip to content

Commit

Permalink
feat: 버튼 컴포넌트 개발 (#6)
Browse files Browse the repository at this point in the history
* feat(button): created a button component

LH-359

* feat(button): crtead a ButtonIcon Component

LH-359

* feat: font 관련 설정 추가

LH-359
  • Loading branch information
sgd122 authored Apr 3, 2023
1 parent 3334578 commit b2d8c53
Show file tree
Hide file tree
Showing 13 changed files with 736 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { createStyles } from "@travelmakers-design-v2/styles";
import { TM_HEIGHTS } from "../../../constants";
import { Props } from "./Button";

const sizes = {
small: {
height: TM_HEIGHTS.small,
padding: "0 24px",
},

medium: {
height: TM_HEIGHTS.medium,
padding: "0 24px",
},

large: {
height: TM_HEIGHTS.large,
padding: "0 24px",
},
};

const getWidthStyles = (fullWidth: boolean) => ({
display: fullWidth ? "block" : "inline-block",
width: fullWidth ? "100%" : "auto",
});

/** style에서 사용할 Props Type을 선언합니다.
* ex)
* export default createStyles((theme, { type }: Pick<Props, "prop">) => {
* // writhing styles
* }
* */
export default createStyles(
(theme, { size, variant, fullWidth, roundness }: Props) => {
const isLineType = variant === "secondary";
const isNonBoxShadow = variant === "text";
return {
root: {
...sizes[size],
...getWidthStyles(fullWidth),
borderRadius: roundness
? theme.radius.radius100
: theme.radius.radius20,
cursor: "pointer",
},
inner: {
display: "flex",
gap: theme.spacing.spacing10,
alignItems: "center",
justifyContent: "center",
},
icon: {
display: "flex",
alignItems: "center",
},
label: {
whiteSpace: "nowrap",
overflow: "hidden",
display: "flex",
alignItems: "center",
},
solid: {
color: theme.palettes[variant][5],
backgroundColor: theme.palettes[variant][0],
border: isLineType ? `1px solid ${theme.colors.primary}` : "none",
[":not(:disabled)"]: {
"&:hover": {
boxShadow: isNonBoxShadow
? "none"
: "0px 4px 8px rgba(0, 0, 0, 0.15)",
filter: isNonBoxShadow
? "drop-shadow(0px 4px 8px rgba(0, 0, 0, 0.15))"
: "none",
},

"&:focus-visible": {
color: theme.palettes[variant][4],
boxShadow: isNonBoxShadow
? "none"
: "0px 4px 8px rgba(0, 0, 0, 0.15)",
},

"&:active": {
color: theme.palettes[variant][4],
backgroundColor: theme.palettes[variant][1],
boxShadow: "none !important",
},
},

[`&:disabled`]: {
backgroundColor: theme.palettes[variant][2],
border: isLineType
? `1px solid ${theme.palettes[variant][3]}`
: "none",
color: theme.palettes[variant][3],
cursor: "auto",
},
},
};
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
ClassNames,
PolymorphicComponentProps,
PolymorphicRef,
TmComponentProps,
TmSize,
TmPalette,
} from "@travelmakers-design-v2/styles";
import { forwardRef, PropsWithChildren, useRef } from "react";
import { View } from "../../View";
import useStyles from "./Button.style";
import { Typography } from "../../Typography/Typography";

export type ButtonStylesNames = ClassNames<typeof useStyles>;

export interface Props {
// 컴포넌트 내에서 사용할 props 타입 정의
/** Button 컴포넌트의 크기를 정합니다. */
size?: TmSize;

/** Button 컴포넌트의 색상을 정합니다. */
variant?: TmPalette;

/** true일 경우 radius를 100px로 지정합니다. (default: full) */
roundness?: boolean;

/** true일 경우 좌우 공간을 모두 차지합니다. */
fullWidth?: boolean;

/** Button 요소의 type을 지정합니다. */
type?: "submit" | "button" | "reset";

/** true일 경우 Button이 disabled 됩니다. */
disabled?: boolean;

/** Button 컴포넌트 좌측 영역에 요소를 추가합니다. */
leftIcon?: React.ReactNode;

/** Button 컴포넌트 좌측 영역에 요소를 추가합니다. */
rightIcon?: React.ReactNode;
}
export interface SharedButtonProps
extends Props,
TmComponentProps<ButtonStylesNames> {}

export type ButtonProps<C extends React.ElementType> =
PolymorphicComponentProps<C, SharedButtonProps>;

type ButtonComponent = <C extends React.ElementType = "div">(
props: ButtonProps<C>
) => React.ReactElement;

export const Button: ButtonComponent & {
displayName?: string;
} = forwardRef(
<C extends React.ElementType = "div">(
{
size = "small",
variant = "primary",
roundness = false,
fullWidth = false,
type = "button",
disabled = false,
leftIcon = "",
rightIcon = "",
className,
children,
...props
}: PropsWithChildren<ButtonProps<C>>,
ref: PolymorphicRef<C>
) => {
const { classes, cx } = useStyles({
size,
variant,
fullWidth,
roundness,
});

return (
<View<React.ElementType>
component={"button"}
ref={ref}
type={type}
disabled={disabled}
className={cx(classes.root, classes.solid, className)}
{...props}
>
<div className={classes.inner}>
{leftIcon && <span className={cx(classes.icon)}>{leftIcon}</span>}

<Typography
level={
size === "small" ? "body3" : size === "medium" ? "body2" : "body1"
}
className={classes.label}
>
{children}
</Typography>

{rightIcon && <span className={cx(classes.icon)}>{rightIcon}</span>}
</div>
</View>
);
}
);

Button.displayName = "Button";
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
ArgsTable,
Description,
PRIMARY_STORY,
Primary,
Stories,
Subtitle,
Title,
} from "@storybook/addon-docs";

import { Button } from "../Button";
import { Meta } from "@storybook/react";
import React from "react";
import { Icon } from "../../../Icon";

export default {
title: "@travelmakers-design-v2/core/General/Button",
component: Button,
argTypes: {
size: {
defaultValue: "small",
description: "Button 컴포넌트의 크기를 정합니다.",
options: ["small", "medium", "large"],
table: {
type: {
summary: "string",
},
},
control: { type: "inline-radio" },
},
variant: {
defaultValue: "primary",
description: "Button 컴포넌트의 색상을 정합니다.",
options: ["primary", "secondary", "tertiary", "error", "tonal", "text"],
table: {
type: {
summary: "string",
},
},
control: { type: "inline-radio" },
},
roundness: {
defaultValue: false,
description: "true일 경우 radius를 100px로 지정합니다. (default: full)",
table: {
type: {
summary: "boolean",
},
},
control: { type: "boolean" },
},
fullWidth: {
defaultValue: false,
description: "true일 경우 좌우 공간을 모두 차지합니다.",
table: {
type: {
summary: "boolean",
},
},
control: { type: "boolean" },
},
disabled: {
defaultValue: false,
description: "true일 경우 button이 disabled 됩니다.",
table: {
type: {
summary: "boolean",
},
},
control: { type: "boolean" },
},
leftIcon: {
defaultValue: "",
table: {
type: {
summary: "React.ReactNode",
},
},
description: "Button 컴포넌트 좌측 영역에 요소를 추가합니다.",
control: { type: "text" },
},
rightIcon: {
defaultValue: "",
table: {
type: {
summary: "React.ReactNode",
},
},
description: "Button 컴포넌트 좌측 영역에 요소를 추가합니다.",
control: { type: "text" },
},
},
parameters: {
docs: {
page: () => (
<>
<Title />
<Subtitle />
<Description />
<Primary />
<ArgsTable story={PRIMARY_STORY} />
<Stories />
</>
),
},
actions: {
handles: ["click button"],
},
},
} as Meta;

export const Default = (props) => {
return (
<div style={{ margin: "0 auto" }}>
<Button {...props}>Button</Button>
</div>
);
};
Loading

0 comments on commit b2d8c53

Please sign in to comment.