diff --git a/apps/docs/src/stories/Callout.stories.tsx b/apps/docs/src/stories/Callout.stories.tsx new file mode 100644 index 0000000..39a6d0d --- /dev/null +++ b/apps/docs/src/stories/Callout.stories.tsx @@ -0,0 +1,70 @@ +import { Callout } from "@sopt-makers/ui"; +import { Meta, StoryObj } from "@storybook/react"; + +interface CalloutProps { + children: React.ReactNode; + type: "danger" | "information" | "warning"; + hasIcon?: boolean; + buttonLabel?: string; + isButtonDisabled?: boolean; + onClick?: () => void; +} + +export default { + title: "Components/Callout", + component: Callout, + tags: ["autodocs"], +} as Meta; + +// danger 콜아웃 스토리 +export const Danger: StoryObj = { + args: { + children: "hasIcon 옵션으로 통해 아이콘을 표시할 수 있어요", + type: "danger", + hasIcon: false, + }, +}; +// information 콜아웃 스토리 +export const Information: StoryObj = { + args: { + children: "hasIcon 옵션으로 통해 아이콘을 표시할 수 있어요", + type: "information", + hasIcon: false, + }, +}; +// warning 콜아웃 스토리 +export const Warning: StoryObj = { + args: { + children: "hasIcon 옵션으로 통해 아이콘을 표시할 수 있어요", + type: "warning", + hasIcon: false, + }, +}; + +// warning+icon+button 콜아웃 스토리 +export const CalloutWithBtn: StoryObj = { + args: { + children: ( + <> + 버튼이 있는 경우 hasIcon과 무관하게 아이콘이 항상 표시돼요.
+ isButtonDisabled 옵션으로 disabled state를 확인해보세요. + + ), + type: "warning", + hasIcon: true, + buttonLabel: "hover, press 해보세요!", + isButtonDisabled: false, + }, +}; + +// 여러줄 텍스트 콜아웃 스토리 +export const CalloutWithLongText: StoryObj = { + args: { + children: + "Facebook 정책이 변경되어, 앞으로 Facebook 로그인이 불가해요. 다른 계정으로 재설정 부탁드려요. Facebook 정책이 변경되어, 앞으로 Facebook 로그인이 불가해요. 다른 계정으로 재설정 부탁드려요.", + type: "information", + hasIcon: true, + buttonLabel: "소셜 계정 재설정하기", + isButtonDisabled: false, + }, +}; diff --git a/packages/ui/Callout/Callout.tsx b/packages/ui/Callout/Callout.tsx new file mode 100644 index 0000000..fc75d7d --- /dev/null +++ b/packages/ui/Callout/Callout.tsx @@ -0,0 +1,57 @@ +import { + IconAlertCircle, + IconChevronRight, + IconInfoCircle, +} from "@sopt-makers/icons"; +import type { ReactNode } from "react"; +import { + buttonIcon, + button, + calloutVariant, + container, + iconVariant, + text, +} from "./style.css"; +import type { CalloutType } from "./types"; + +const icons = { + danger: IconAlertCircle, + information: IconInfoCircle, + warning: IconAlertCircle, +}; +interface CalloutProps { + children: ReactNode; + type: CalloutType; + hasIcon?: boolean; + buttonLabel?: string; + isButtonDisabled?: boolean; + onClick?: () => void; +} + +function Callout(props: CalloutProps) { + const { children, type, hasIcon, buttonLabel, isButtonDisabled, onClick } = + props; + const Icon = icons[type]; + + return ( + + ); +} + +export default Callout; diff --git a/packages/ui/Callout/constants.ts b/packages/ui/Callout/constants.ts new file mode 100644 index 0000000..6c640b4 --- /dev/null +++ b/packages/ui/Callout/constants.ts @@ -0,0 +1,24 @@ +import type { CSSProperties } from "react"; +import theme from "../theme.css"; +import type { CalloutType } from "./types"; + +export const iconColors: Record = { + danger: theme.colors.red500, + information: theme.colors.blue500, + warning: theme.colors.yellow500, +}; + +export const calloutColors: Record = { + danger: { + backgroundColor: theme.colors.redAlpha100, + borderColor: theme.colors.red600, + }, + information: { + backgroundColor: theme.colors.blueAlpha100, + borderColor: theme.colors.blueAlpha600, + }, + warning: { + backgroundColor: "rgba(255, 194, 52, 0.1)", + borderColor: theme.colors.yellow600, + }, +}; diff --git a/packages/ui/Callout/index.tsx b/packages/ui/Callout/index.tsx new file mode 100644 index 0000000..68a8991 --- /dev/null +++ b/packages/ui/Callout/index.tsx @@ -0,0 +1 @@ +export { default } from "./Callout"; diff --git a/packages/ui/Callout/style.css.ts b/packages/ui/Callout/style.css.ts new file mode 100644 index 0000000..613009a --- /dev/null +++ b/packages/ui/Callout/style.css.ts @@ -0,0 +1,92 @@ +import { style, styleVariants } from "@vanilla-extract/css"; +import theme from "../theme.css"; +import { calloutColors, iconColors } from "./constants"; + +export const container = style({ + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + gap: 18, +}); + +export const text = style({ + textAlign: "left", + ...theme.fontsObject.BODY_3_14_M, + color: theme.colors.gray30, +}); + +export const button = style({ + display: "flex", + alignItems: "center", + + paddingBottom: 4, + + borderWidth: "0px 0px 0.8px 0px", + borderStyle: "solid", + borderColor: "transparent", + backgroundColor: "transparent", + + cursor: "pointer", + color: theme.colors.gray30, + ...theme.fontsObject.LABEL_4_12_SB, + + ":hover": { + color: theme.colors.gray50, + borderColor: theme.colors.gray50, + }, + ":active": { + color: theme.colors.gray100, + borderColor: theme.colors.gray100, + }, + ":disabled": { + color: theme.colors.gray500, + borderColor: "transparent", + cursor: "default", + }, +}); + +export const buttonIcon = style({ + width: 16, + color: theme.colors.gray30, + selectors: { + [`${button}:hover &`]: { + color: theme.colors.gray50, + }, + [`${button}:active &`]: { + color: theme.colors.gray100, + }, + [`${button}:disabled &`]: { + color: theme.colors.gray500, + }, + }, +}); + +// ▶️ icon styleVariants +const iconBase = style({ + flexShrink: 0, + width: 20, +}); + +export const iconVariant = styleVariants(iconColors, (color) => [ + iconBase, + { color }, +]); + +// ▶️ callout styleVariants +const calloutBase = style({ + display: "flex", + gap: 10, + alignItems: "flex-start", + padding: "14px 18px", + + border: "1px solid", + borderRadius: 10, +}); + +export const calloutVariant = styleVariants( + calloutColors, + ({ backgroundColor, borderColor }) => [ + calloutBase, + { backgroundColor, borderColor }, + ] +); diff --git a/packages/ui/Callout/types.ts b/packages/ui/Callout/types.ts new file mode 100644 index 0000000..887157f --- /dev/null +++ b/packages/ui/Callout/types.ts @@ -0,0 +1 @@ +export type CalloutType = "danger" | "information" | "warning"; diff --git a/packages/ui/index.ts b/packages/ui/index.ts index d78da2a..77d16f9 100644 --- a/packages/ui/index.ts +++ b/packages/ui/index.ts @@ -10,6 +10,7 @@ export type { ToastOptionType } from './Toast'; export { TextField, TextArea, SearchField } from './Input'; export { default as Tag } from "./Tag"; export { default as Chip } from './Chip'; +export { default as Callout } from "./Callout"; // test component export { default as Test } from './Test';