Skip to content
145 changes: 109 additions & 36 deletions apps/docs/src/stories/Callout.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,146 @@
import { Callout } from "@sopt-makers/ui";
import { Meta, StoryObj } from "@storybook/react";
import { Callout } from '@sopt-makers/ui';
import { Meta, StoryObj } from '@storybook/react';
import type React from 'react';

interface CalloutProps {
children: React.ReactNode;
type: "danger" | "information" | "warning";
hasIcon?: boolean;
buttonLabel?: string;
isButtonDisabled?: boolean;
onClick?: () => void;
}
type CalloutProps = React.ComponentProps<typeof Callout>;

export default {
title: "Components/Callout",
title: 'Components/Callout',
component: Callout,
tags: ["autodocs"],
tags: ['autodocs'],
argTypes: {
type: { control: 'radio', options: ['danger', 'information', 'warning'] },
}
children: {
description: '콜아웃의 내용을 작성합니다.',
control: 'text',
table: {
type: { summary: 'ReactNode' },
},
},
type: {
description: '콜아웃의 타입을 지정합니다. 타입에 따라 색상과 아이콘이 변경됩니다.',
control: 'radio',
options: ['danger', 'information', 'warning'],
table: {
type: { summary: 'danger | information | warning' },
},
},
hasIcon: {
description: '아이콘 표시 여부를 지정합니다. buttonLabel이 있으면 이 옵션과 무관하게 아이콘이 항상 표시됩니다.',
control: 'boolean',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'true' },
},
},
buttonLabel: {
description: '버튼의 텍스트를 지정합니다. 지정하면 버튼이 표시됩니다.',
control: 'text',
table: {
type: { summary: 'string' },
},
},
isButtonDisabled: {
description: '버튼의 비활성화 상태를 지정합니다.',
control: 'boolean',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
onClick: {
description: '버튼 클릭 시 실행할 함수를 지정합니다.',
action: 'clicked',
table: {
type: { summary: '() => void' },
},
},
},
} as Meta<CalloutProps>;

const content = (
<>
hasIcon 옵션으로 통해 아이콘을 표시할 수 있으며 <br />
buttonLabel과 onClick 옵션을 통해 버튼의 text와 클릭 핸들러를 설정할 수 있어요
</>
);
// danger 콜아웃 스토리
export const Danger: StoryObj<CalloutProps> = {
args: {
children: "hasIcon 옵션으로 통해 아이콘을 표시할 수 있어요",
type: "danger",
children: content,
type: 'danger',
hasIcon: false,
},
};
// information 콜아웃 스토리
export const Information: StoryObj<CalloutProps> = {
args: {
children: "hasIcon 옵션으로 통해 아이콘을 표시할 수 있어요",
type: "information",
children: 'hasIcon 옵션으로 통해 아이콘을 표시할 수 있으며 ',
type: 'information',
hasIcon: false,
},
};
// warning 콜아웃 스토리
export const Warning: StoryObj<CalloutProps> = {
args: {
children: "hasIcon 옵션으로 통해 아이콘을 표시할 수 있어요",
type: "warning",
children: 'buttonLabel과 onClick 옵션을 통해 버튼의 text와 클릭 핸들러를 설정할 수 있어요',
type: 'warning',
hasIcon: false,
},
};
// danger+icon 콜아웃 스토리
export const MultipleIconCallouts: StoryObj<CalloutProps> = {
render: () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Callout {...(Danger.args as CalloutProps)} hasIcon />
<Callout {...(Information.args as CalloutProps)} hasIcon />
<Callout {...(Warning.args as CalloutProps)} hasIcon />
</div>
),
};

// warning+icon+button 콜아웃 스토리
export const CalloutWithBtn: StoryObj<CalloutProps> = {
export const MultipleButtonCallouts: StoryObj<CalloutProps> = {
render: () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Callout {...(Danger.args as CalloutProps)} buttonLabel='hover, press 해보세요!'>
<>
버튼이 있는 경우 hasIcon과 무관하게 아이콘이 항상 표시돼요. <br />
isButtonDisabled 옵션으로 disabled state를 확인해보세요.
</>
</Callout>
<Callout {...(Information.args as CalloutProps)} buttonLabel='hover, press 해보세요!'>
<>
버튼이 있는 경우 hasIcon과 무관하게 아이콘이 항상 표시돼요. <br />
isButtonDisabled 옵션으로 disabled state를 확인해보세요.
</>
</Callout>
<Callout {...(Warning.args as CalloutProps)} buttonLabel='hover, press 해보세요!'>
<>
버튼이 있는 경우 hasIcon과 무관하게 아이콘이 항상 표시돼요. <br />
isButtonDisabled 옵션으로 disabled state를 확인해보세요.
</>
</Callout>
</div>
),
};

// 여러줄 텍스트 콜아웃 스토리
export const CalloutWithLongText: StoryObj<CalloutProps> = {
args: {
children: (
<>
버튼이 있는 경우 hasIcon과 무관하게 아이콘이 항상 표시돼요. <br />
isButtonDisabled 옵션으로 disabled state를 확인해보세요.
</>
),
type: "warning",
children:
'Facebook 정책이 변경되어, 앞으로 Facebook 로그인이 불가해요. 다른 계정으로 재설정 부탁드려요. Facebook 정책이 변경되어, 앞으로 Facebook 로그인이 불가해요. 다른 계정으로 재설정 부탁드려요.',
type: 'information',
hasIcon: true,
buttonLabel: "hover, press 해보세요!",
buttonLabel: '소셜 계정 재설정하기',
isButtonDisabled: false,
},
};

// 여러줄 텍스트 콜아웃 스토리
export const CalloutWithLongText: StoryObj<CalloutProps> = {
export const CalloutWithShortText: StoryObj<CalloutProps> = {
args: {
children:
"Facebook 정책이 변경되어, 앞으로 Facebook 로그인이 불가해요. 다른 계정으로 재설정 부탁드려요. Facebook 정책이 변경되어, 앞으로 Facebook 로그인이 불가해요. 다른 계정으로 재설정 부탁드려요.",
type: "information",
children: '짧은 텍스트 ',
type: 'information',
hasIcon: true,
buttonLabel: "소셜 계정 재설정하기",
buttonLabel: '짧은 라벨',
isButtonDisabled: false,
},
};
35 changes: 9 additions & 26 deletions packages/ui/Callout/Callout.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변경된 부분은 아니긴한데 아래 Icon의 visible 조건에 buttonLabel가 같이 있는데 이렇게 되면 hasIconfalse여도 buttonLabel이 있으면 아이콘이 뜨는 case가 생길 것 같아요!

그리고 hasIconoptional인데 초기 값이 없어서 만약 buttonLabel을 조건에서 없애게 되면 기본 값 지정을 해주면 어떨까요?

<aside className={calloutVariant[type]}>
    {buttonLabel || hasIcon ? <Icon className={iconVariant[type]} /> : null}
    // ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buttonLabel을 넣은 것은 해당 문서(이미지)를 보고 'buttonLabel이 존재하는 케이스'에서는 무조건 icon이 존재한다고 판단했기 때문이에요! 그렇게 사용되어야한다면 variant를 활용해 icon, default, buttonLabel로 나누었으면 좋았겠다는 생각이 드네요.

피그마 문서 링크도 남깁니다! 다만 Callout 디자인시스템을 플그만 사용하고 있어서 영향이 가지 않는 선에서 더 좋은 개선방안을 고민해보겠습니다!

https://www.figma.com/design/ZuAGH1zZDxduHsjCZm8kfH/%F0%9F%9A%80-makers-design-system?node-id=9198-6952&m=dev
image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aeb722f 초기값 추가했습니다 감사합니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래 Icon에서 특별하게 스크린 리더에 담길 내용이 없다면 아이콘 2개 다 aria-hidden='true'을 달아도 좋을 것 같아요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

098b26e 좋은 리뷰 감사합니다 반영완료했습니다 ~

Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
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";
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,
Expand All @@ -29,24 +18,18 @@ interface CalloutProps {
}

function Callout(props: CalloutProps) {
const { children, type, hasIcon, buttonLabel, isButtonDisabled, onClick } =
props;
const { children, type, hasIcon = true, buttonLabel, isButtonDisabled, onClick } = props;
const Icon = icons[type];

return (
<aside className={calloutVariant[type]}>
{buttonLabel || hasIcon ? <Icon className={iconVariant[type]} /> : null}
{buttonLabel || hasIcon ? <Icon aria-hidden='true' className={iconVariant[type]} /> : null}
<div className={container}>
<span className={text}>{children}</span>
{buttonLabel ? (
<button
className={button}
disabled={isButtonDisabled}
onClick={onClick}
type="button"
>
{children && buttonLabel ? (
<button className={button} disabled={isButtonDisabled} onClick={onClick} type='button'>
<span>{buttonLabel}</span>
<IconChevronRight className={buttonIcon} />
<IconChevronRight aria-hidden='true' className={buttonIcon} />
</button>
) : null}
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/Callout/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CSSProperties } from "react";
import theme from "../theme.css";
import type { CalloutType } from "./types";
import type { CSSProperties } from 'react';
import theme from '../theme.css';
import type { CalloutType } from './types';

export const iconColors: Record<CalloutType, string> = {
danger: theme.colors.red500,
Expand All @@ -18,7 +18,7 @@ export const calloutColors: Record<CalloutType, CSSProperties> = {
borderColor: theme.colors.blueAlpha600,
},
warning: {
backgroundColor: "rgba(255, 194, 52, 0.1)",
backgroundColor: 'rgba(255, 194, 52, 0.1)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 색상이 하드 코딩 되어있는데 저희 mds에 추가가 안되어있네요..
규홍님이 올려두신 color 관련 작업 PR에서 yellow/green Alpha 색상 추가되면 변경해주시면 좋을 것 같아요!

추가로 피그마에도 반영이 안되어있어서 yellowAlpha600으로 color 지정해달라고 디자이너 분들께 전달해주시면 감사하겠습니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 이부분 가온님께 요청드렸고 추후 머지되면 한 번 더 확인하겠습니다~

borderColor: theme.colors.yellow600,
},
};
2 changes: 1 addition & 1 deletion packages/ui/Callout/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from "./Callout";
export { default } from './Callout';
68 changes: 32 additions & 36 deletions packages/ui/Callout/style.css.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
import { style, styleVariants } from "@vanilla-extract/css";
import theme from "../theme.css";
import { calloutColors, iconColors } from "./constants";
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",
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: 18,
});

export const text = style({
textAlign: "left",
textAlign: 'left',
...theme.fontsObject.BODY_3_14_M,
color: theme.colors.gray30,
});

export const button = style({
display: "flex",
alignItems: "center",
'display': 'flex',
'alignItems': 'center',

paddingBottom: 4,
'paddingBottom': 4,

borderWidth: "0px 0px 0.8px 0px",
borderStyle: "solid",
borderColor: "transparent",
backgroundColor: "transparent",
'borderWidth': '0px 0px 0.8px 0px',
'borderStyle': 'solid',
'borderColor': 'transparent',
'backgroundColor': 'transparent',

cursor: "pointer",
color: theme.colors.gray30,
'cursor': 'pointer',
'color': theme.colors.gray30,
...theme.fontsObject.LABEL_4_12_SB,

":hover": {
':hover': {
color: theme.colors.gray50,
borderColor: theme.colors.gray50,
},
":active": {
':active': {
color: theme.colors.gray100,
borderColor: theme.colors.gray100,
},
":disabled": {
':disabled': {
color: theme.colors.gray500,
borderColor: "transparent",
cursor: "default",
borderColor: 'transparent',
cursor: 'default',
},
});

Expand All @@ -67,26 +67,22 @@ const iconBase = style({
width: 20,
});

export const iconVariant = styleVariants(iconColors, (color) => [
iconBase,
{ color },
]);
export const iconVariant = styleVariants(iconColors, (color) => [iconBase, { color }]);

// ▶️ callout styleVariants
const calloutBase = style({
display: "flex",
display: 'flex',
gap: 10,
alignItems: "flex-start",
padding: "14px 18px",
alignItems: 'flex-start',
padding: '14px 18px',

border: "1px solid",
minWidth: 358,
minHeight: 50,
border: '1px solid',
borderRadius: 10,
});

export const calloutVariant = styleVariants(
calloutColors,
({ backgroundColor, borderColor }) => [
calloutBase,
{ backgroundColor, borderColor },
]
);
export const calloutVariant = styleVariants(calloutColors, ({ backgroundColor, borderColor }) => [
calloutBase,
{ backgroundColor, borderColor },
]);
2 changes: 1 addition & 1 deletion packages/ui/Callout/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type CalloutType = "danger" | "information" | "warning";
export type CalloutType = 'danger' | 'information' | 'warning';