jongsultest 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +339 -0
- package/dist/index.d.ts +339 -0
- package/dist/index.js +1280 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1234 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +59 -0
- package/src/atoms/Badge.stories.tsx +136 -0
- package/src/atoms/Badge.tsx +46 -0
- package/src/atoms/Button.stories.tsx +142 -0
- package/src/atoms/Button.tsx +50 -0
- package/src/atoms/ComboboxAutocomplete.stories.tsx +43 -0
- package/src/atoms/ComboboxAutocomplete.tsx +6 -0
- package/src/atoms/ComboboxSelect.stories.tsx +56 -0
- package/src/atoms/ComboboxSelect.tsx +6 -0
- package/src/atoms/DateTimePicker.stories.tsx +42 -0
- package/src/atoms/DateTimePicker.tsx +6 -0
- package/src/atoms/Icon.stories.tsx +86 -0
- package/src/atoms/Icon.tsx +81 -0
- package/src/atoms/IconButton.stories.tsx +59 -0
- package/src/atoms/IconButton.tsx +19 -0
- package/src/atoms/InputColor.stories.tsx +42 -0
- package/src/atoms/InputColor.tsx +6 -0
- package/src/atoms/InputDatePicker.stories.tsx +55 -0
- package/src/atoms/InputDatePicker.tsx +6 -0
- package/src/atoms/InputFile.stories.tsx +57 -0
- package/src/atoms/InputFile.tsx +15 -0
- package/src/atoms/InputNumber.stories.tsx +58 -0
- package/src/atoms/InputNumber.tsx +15 -0
- package/src/atoms/InputPassword.stories.tsx +42 -0
- package/src/atoms/InputPassword.tsx +24 -0
- package/src/atoms/InputRadio.stories.tsx +65 -0
- package/src/atoms/InputRadio.tsx +19 -0
- package/src/atoms/InputSegmentedControl.stories.tsx +44 -0
- package/src/atoms/InputSegmentedControl.tsx +15 -0
- package/src/atoms/InputSwitch.stories.tsx +45 -0
- package/src/atoms/InputSwitch.tsx +23 -0
- package/src/atoms/InputSwitchInTable.stories.tsx +43 -0
- package/src/atoms/InputSwitchInTable.tsx +35 -0
- package/src/atoms/InputText.stories.tsx +42 -0
- package/src/atoms/InputText.tsx +7 -0
- package/src/atoms/InputTextarea.stories.tsx +50 -0
- package/src/atoms/InputTextarea.tsx +6 -0
- package/src/atoms/index.ts +20 -0
- package/src/atoms/inputClassNames.ts +20 -0
- package/src/blocks/Accordion.stories.tsx +102 -0
- package/src/blocks/Accordion.tsx +124 -0
- package/src/blocks/AccordionDraggable.stories.tsx +169 -0
- package/src/blocks/AccordionDraggable.tsx +200 -0
- package/src/blocks/BoxContainer.stories.tsx +34 -0
- package/src/blocks/BoxContainer.tsx +16 -0
- package/src/blocks/DataTable.tsx +127 -0
- package/src/blocks/DescriptionRow.stories.tsx +34 -0
- package/src/blocks/DescriptionRow.tsx +22 -0
- package/src/blocks/EditorLayout.stories.tsx +79 -0
- package/src/blocks/EditorLayout.tsx +43 -0
- package/src/blocks/ImageUpload.tsx +292 -0
- package/src/blocks/LabeledField.stories.tsx +34 -0
- package/src/blocks/LabeledField.tsx +31 -0
- package/src/blocks/MediDrawer.stories.tsx +58 -0
- package/src/blocks/MediDrawer.tsx +34 -0
- package/src/blocks/Modal.stories.tsx +42 -0
- package/src/blocks/Modal.tsx +31 -0
- package/src/blocks/ModalDeleteConfirm.stories.tsx +31 -0
- package/src/blocks/ModalDeleteConfirm.tsx +56 -0
- package/src/blocks/ModalTokenExpired.tsx +52 -0
- package/src/blocks/NavigationBanner.tsx +100 -0
- package/src/blocks/PageHeader.tsx +24 -0
- package/src/blocks/PageLoading.stories.tsx +22 -0
- package/src/blocks/PageLoading.tsx +19 -0
- package/src/blocks/PageTitle.stories.tsx +39 -0
- package/src/blocks/PageTitle.tsx +25 -0
- package/src/blocks/Pagination.tsx +49 -0
- package/src/blocks/Panel.stories.tsx +84 -0
- package/src/blocks/Panel.tsx +97 -0
- package/src/blocks/SectionGroupWithTitle.stories.tsx +24 -0
- package/src/blocks/SectionGroupWithTitle.tsx +21 -0
- package/src/blocks/Table.tsx +114 -0
- package/src/blocks/TagInput.stories.tsx +74 -0
- package/src/blocks/TagInput.tsx +93 -0
- package/src/blocks/index.ts +25 -0
- package/src/index.ts +51 -0
- package/src/utils/file.ts +7 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Button } from "./Button";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Button> = {
|
|
5
|
+
title: "Atoms/Button",
|
|
6
|
+
component: Button,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
argTypes: {
|
|
9
|
+
size: {
|
|
10
|
+
control: "select",
|
|
11
|
+
options: ["sm", "md"],
|
|
12
|
+
description: "버튼 크기 (sm: 38px, md: 40px)",
|
|
13
|
+
table: { defaultValue: { summary: "md" } },
|
|
14
|
+
},
|
|
15
|
+
color: {
|
|
16
|
+
control: "select",
|
|
17
|
+
options: ["blue", "red", "gray", "green"],
|
|
18
|
+
description: "버튼 색상",
|
|
19
|
+
table: { defaultValue: { summary: "blue" } },
|
|
20
|
+
},
|
|
21
|
+
variant: {
|
|
22
|
+
control: "select",
|
|
23
|
+
options: ["default", "filled", "outline", "light"],
|
|
24
|
+
description: "버튼 스타일 변형",
|
|
25
|
+
table: { defaultValue: { summary: "default" } },
|
|
26
|
+
},
|
|
27
|
+
disabled: {
|
|
28
|
+
control: "boolean",
|
|
29
|
+
description: "비활성화 여부",
|
|
30
|
+
},
|
|
31
|
+
loading: {
|
|
32
|
+
control: "boolean",
|
|
33
|
+
description: "로딩 상태",
|
|
34
|
+
},
|
|
35
|
+
fullWidth: {
|
|
36
|
+
control: "boolean",
|
|
37
|
+
description: "전체 너비 사용 여부",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
args: {
|
|
41
|
+
children: "버튼",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default meta;
|
|
46
|
+
type Story = StoryObj<typeof Button>;
|
|
47
|
+
|
|
48
|
+
export const Default: Story = {};
|
|
49
|
+
|
|
50
|
+
export const Variants: Story = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
|
53
|
+
<Button variant="default">Default</Button>
|
|
54
|
+
<Button variant="filled">Filled</Button>
|
|
55
|
+
<Button variant="outline">Outline</Button>
|
|
56
|
+
<Button variant="light">Light</Button>
|
|
57
|
+
</div>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const Sizes: Story = {
|
|
62
|
+
render: () => (
|
|
63
|
+
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
|
64
|
+
<Button size="sm">Small (38px)</Button>
|
|
65
|
+
<Button size="md">Medium (40px)</Button>
|
|
66
|
+
</div>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Colors: Story = {
|
|
71
|
+
render: () => (
|
|
72
|
+
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
|
73
|
+
<Button variant="filled" color="blue">
|
|
74
|
+
Blue
|
|
75
|
+
</Button>
|
|
76
|
+
<Button variant="filled" color="red">
|
|
77
|
+
Red
|
|
78
|
+
</Button>
|
|
79
|
+
<Button variant="filled" color="gray">
|
|
80
|
+
Gray
|
|
81
|
+
</Button>
|
|
82
|
+
<Button variant="filled" color="green">
|
|
83
|
+
Green
|
|
84
|
+
</Button>
|
|
85
|
+
</div>
|
|
86
|
+
),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const OutlineColors: Story = {
|
|
90
|
+
render: () => (
|
|
91
|
+
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
|
92
|
+
<Button variant="outline" color="blue">
|
|
93
|
+
Blue
|
|
94
|
+
</Button>
|
|
95
|
+
<Button variant="outline" color="red">
|
|
96
|
+
Red
|
|
97
|
+
</Button>
|
|
98
|
+
<Button variant="outline" color="gray">
|
|
99
|
+
Gray
|
|
100
|
+
</Button>
|
|
101
|
+
<Button variant="outline" color="green">
|
|
102
|
+
Green
|
|
103
|
+
</Button>
|
|
104
|
+
</div>
|
|
105
|
+
),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const LightColors: Story = {
|
|
109
|
+
render: () => (
|
|
110
|
+
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
|
111
|
+
<Button variant="light" color="blue">
|
|
112
|
+
Blue
|
|
113
|
+
</Button>
|
|
114
|
+
<Button variant="light" color="red">
|
|
115
|
+
Red
|
|
116
|
+
</Button>
|
|
117
|
+
<Button variant="light" color="gray">
|
|
118
|
+
Gray
|
|
119
|
+
</Button>
|
|
120
|
+
<Button variant="light" color="green">
|
|
121
|
+
Green
|
|
122
|
+
</Button>
|
|
123
|
+
</div>
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const States: Story = {
|
|
128
|
+
render: () => (
|
|
129
|
+
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
|
130
|
+
<Button>기본</Button>
|
|
131
|
+
<Button disabled>비활성화</Button>
|
|
132
|
+
<Button loading>로딩</Button>
|
|
133
|
+
</div>
|
|
134
|
+
),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const FullWidth: Story = {
|
|
138
|
+
args: {
|
|
139
|
+
fullWidth: true,
|
|
140
|
+
children: "로그인",
|
|
141
|
+
},
|
|
142
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Button as MantineButton, ButtonProps } from "@mantine/core";
|
|
2
|
+
import { ComponentPropsWithoutRef } from "react";
|
|
3
|
+
|
|
4
|
+
type Props = ButtonProps & ComponentPropsWithoutRef<"button">;
|
|
5
|
+
|
|
6
|
+
const COMMON_STYLES: Record<string, string> = {
|
|
7
|
+
"--button-fz": "14px",
|
|
8
|
+
"--button-radius": "6px",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const SIZE_STYLES: Record<string, Record<string, string>> = {
|
|
12
|
+
xs: {
|
|
13
|
+
"--button-height": "24px",
|
|
14
|
+
"--button-padding-x": "6px",
|
|
15
|
+
},
|
|
16
|
+
sm: {
|
|
17
|
+
"--button-height": "38px",
|
|
18
|
+
"--button-padding-x": "16px",
|
|
19
|
+
},
|
|
20
|
+
md: {
|
|
21
|
+
"--button-height": "40px",
|
|
22
|
+
"--button-padding-x": "24px",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getVariantStyles = (variant?: string, color?: string) => {
|
|
27
|
+
if (variant === "outline" && color === "gray") {
|
|
28
|
+
return { color: "var(--color-body)", borderColor: "var(--color-gray-3)" };
|
|
29
|
+
}
|
|
30
|
+
if (variant === "default") {
|
|
31
|
+
return { backgroundColor: "var(--mantine-color-blue-6)", color: "#FFFFFF", border: "none" };
|
|
32
|
+
}
|
|
33
|
+
if (variant === "light" && !color) {
|
|
34
|
+
return { backgroundColor: "var(--color-button-light)", color: "var(--color-body)", border: "none" };
|
|
35
|
+
}
|
|
36
|
+
return {};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Button = ({ style, ...props }: Props) => {
|
|
40
|
+
const sizeVars = SIZE_STYLES[props.size as string] ?? SIZE_STYLES.md;
|
|
41
|
+
const variantStyles = getVariantStyles(props.variant, props.color);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<MantineButton
|
|
45
|
+
{...props}
|
|
46
|
+
classNames={{ label: "!font-bold" }}
|
|
47
|
+
style={{ ...COMMON_STYLES, ...sizeVars, ...variantStyles, ...style }}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { ComboboxAutocomplete } from "./ComboboxAutocomplete";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ComboboxAutocomplete> = {
|
|
5
|
+
title: "Atoms/ComboboxAutocomplete",
|
|
6
|
+
component: ComboboxAutocomplete,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
args: {
|
|
9
|
+
label: "검색",
|
|
10
|
+
placeholder: "검색어를 입력하세요",
|
|
11
|
+
data: ["서울", "부산", "대구", "인천", "광주"],
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof ComboboxAutocomplete>;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {};
|
|
19
|
+
|
|
20
|
+
export const WithDescription: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
description: "도시를 검색하세요",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const WithError: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
error: "필수 입력 항목입니다",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Disabled: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
disabled: true,
|
|
35
|
+
value: "서울",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Required: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
withAsterisk: true,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Autocomplete as MantineAutocomplete, AutocompleteProps } from "@mantine/core";
|
|
2
|
+
import { inputClassNames } from "./inputClassNames";
|
|
3
|
+
|
|
4
|
+
export const ComboboxAutocomplete = (props: AutocompleteProps) => {
|
|
5
|
+
return <MantineAutocomplete classNames={inputClassNames} {...props} />;
|
|
6
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { ComboboxSelect } from "./ComboboxSelect";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ComboboxSelect> = {
|
|
5
|
+
title: "Atoms/ComboboxSelect",
|
|
6
|
+
component: ComboboxSelect,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
args: {
|
|
9
|
+
label: "도시",
|
|
10
|
+
placeholder: "선택하세요",
|
|
11
|
+
data: ["서울", "부산", "대구", "인천", "광주"],
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof ComboboxSelect>;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {};
|
|
19
|
+
|
|
20
|
+
export const WithDescription: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
description: "거주 도시를 선택하세요",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const WithError: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
error: "필수 선택 항목입니다",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Disabled: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
disabled: true,
|
|
35
|
+
value: "서울",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Required: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
withAsterisk: true,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Clearable: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
clearable: true,
|
|
48
|
+
value: "서울",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Searchable: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
searchable: true,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { DateTimePicker } from "./DateTimePicker";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof DateTimePicker> = {
|
|
5
|
+
title: "Atoms/DateTimePicker",
|
|
6
|
+
component: DateTimePicker,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
args: {
|
|
9
|
+
label: "예약 일시",
|
|
10
|
+
placeholder: "날짜와 시간을 선택하세요",
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof DateTimePicker>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {};
|
|
18
|
+
|
|
19
|
+
export const WithDescription: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
description: "예약 가능한 날짜와 시간을 선택해주세요",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const WithError: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
error: "필수 입력 항목입니다",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Disabled: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
disabled: true,
|
|
34
|
+
value: new Date(),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Required: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
withAsterisk: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DateTimePicker as MantineDateTimePicker, DateTimePickerProps } from "@mantine/dates";
|
|
2
|
+
import { inputClassNames } from "./inputClassNames";
|
|
3
|
+
|
|
4
|
+
export const DateTimePicker = (props: DateTimePickerProps) => {
|
|
5
|
+
return <MantineDateTimePicker classNames={inputClassNames} {...props} />;
|
|
6
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { PlusIcon, TrashIcon, GripVerticalIcon, ChevronDownIcon } from "./Icon";
|
|
3
|
+
import { Group, Stack, Text } from "@mantine/core";
|
|
4
|
+
|
|
5
|
+
type IconStoryArgs = {
|
|
6
|
+
size?: number;
|
|
7
|
+
color?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const meta: Meta<IconStoryArgs> = {
|
|
11
|
+
title: "Atoms/Icon",
|
|
12
|
+
tags: ["autodocs"],
|
|
13
|
+
argTypes: {
|
|
14
|
+
size: { control: { type: "number", min: 12, max: 48 } },
|
|
15
|
+
color: { control: "text" },
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
size: 20,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
export const AllIcons: StoryObj<IconStoryArgs> = {
|
|
25
|
+
render: (args) => (
|
|
26
|
+
<Group gap="xl">
|
|
27
|
+
<Stack align="center" gap="xs">
|
|
28
|
+
<PlusIcon size={args.size} color={args.color} />
|
|
29
|
+
<Text size="xs">PlusIcon</Text>
|
|
30
|
+
</Stack>
|
|
31
|
+
<Stack align="center" gap="xs">
|
|
32
|
+
<TrashIcon size={args.size} color={args.color} />
|
|
33
|
+
<Text size="xs">TrashIcon</Text>
|
|
34
|
+
</Stack>
|
|
35
|
+
<Stack align="center" gap="xs">
|
|
36
|
+
<GripVerticalIcon size={args.size} color={args.color} />
|
|
37
|
+
<Text size="xs">GripVerticalIcon</Text>
|
|
38
|
+
</Stack>
|
|
39
|
+
<Stack align="center" gap="xs">
|
|
40
|
+
<ChevronDownIcon size={args.size} color={args.color} />
|
|
41
|
+
<Text size="xs">ChevronDownIcon</Text>
|
|
42
|
+
</Stack>
|
|
43
|
+
</Group>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const ColorPresets: StoryObj = {
|
|
48
|
+
render: () => (
|
|
49
|
+
<Group gap="xl">
|
|
50
|
+
{["gray", "red", "white", "disabled"].map((color) => (
|
|
51
|
+
<Stack key={color} align="center" gap="xs" p="sm" bg={color === "white" ? "dark" : undefined}>
|
|
52
|
+
<TrashIcon color={color} />
|
|
53
|
+
<Text size="xs">{color}</Text>
|
|
54
|
+
</Stack>
|
|
55
|
+
))}
|
|
56
|
+
</Group>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const Sizes: StoryObj = {
|
|
61
|
+
render: () => (
|
|
62
|
+
<Group gap="xl" align="end">
|
|
63
|
+
{[12, 16, 20, 24, 32].map((size) => (
|
|
64
|
+
<Stack key={size} align="center" gap="xs">
|
|
65
|
+
<PlusIcon size={size} />
|
|
66
|
+
<Text size="xs">{size}px</Text>
|
|
67
|
+
</Stack>
|
|
68
|
+
))}
|
|
69
|
+
</Group>
|
|
70
|
+
),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const PlusDisabled: StoryObj = {
|
|
74
|
+
render: () => (
|
|
75
|
+
<Group gap="xl">
|
|
76
|
+
<Stack align="center" gap="xs">
|
|
77
|
+
<PlusIcon />
|
|
78
|
+
<Text size="xs">활성</Text>
|
|
79
|
+
</Stack>
|
|
80
|
+
<Stack align="center" gap="xs">
|
|
81
|
+
<PlusIcon disabled />
|
|
82
|
+
<Text size="xs">비활성</Text>
|
|
83
|
+
</Stack>
|
|
84
|
+
</Group>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const colorPreset: Record<string, string> = {
|
|
2
|
+
red: "#FF4242",
|
|
3
|
+
white: "#FFFFFF",
|
|
4
|
+
gray: "#666666",
|
|
5
|
+
disabled: "#DDDDDD",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const resolveColor = (color: string) => colorPreset[color] ?? color;
|
|
9
|
+
|
|
10
|
+
export interface IconSvgProps {
|
|
11
|
+
size?: number;
|
|
12
|
+
/** 프리셋("red", "white", "gray", "disabled") 또는 hex 값 */
|
|
13
|
+
color?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 원형 배경 + 십자 아이콘 (활성: #666666 / 비활성: #DDDDDD) */
|
|
18
|
+
export const PlusIcon = ({ size = 20, color = "gray", disabled }: IconSvgProps) => (
|
|
19
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none">
|
|
20
|
+
<circle cx="10" cy="10" r="10" fill={disabled ? resolveColor("disabled") : resolveColor(color)} />
|
|
21
|
+
<path d="M10 5.5V14.5M14.5 10L5.5 10" stroke="white" strokeWidth="1.2" strokeLinecap="round" />
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/** 휴지통 아이콘 (기본: "gray" / 삭제: "red" / 흰색: "white") */
|
|
26
|
+
export const TrashIcon = ({ size = 20, color = "gray" }: IconSvgProps) => {
|
|
27
|
+
const c = resolveColor(color);
|
|
28
|
+
return (
|
|
29
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none">
|
|
30
|
+
<path
|
|
31
|
+
d="M14 4.5V2C14 1.72386 13.7761 1.5 13.5 1.5H6.5C6.22386 1.5 6 1.72386 6 2V4.5"
|
|
32
|
+
stroke={c}
|
|
33
|
+
strokeWidth="1.2"
|
|
34
|
+
/>
|
|
35
|
+
<path d="M2 4H18" stroke={c} strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
|
|
36
|
+
<path
|
|
37
|
+
d="M4 7.00028V17C4 17.5523 4.44772 18 5 18H15C15.5523 18 16 17.5523 16 17V7"
|
|
38
|
+
stroke={c}
|
|
39
|
+
strokeWidth="1.2"
|
|
40
|
+
strokeLinecap="round"
|
|
41
|
+
/>
|
|
42
|
+
<path d="M8 7V13" stroke={c} strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
|
|
43
|
+
<path d="M12 7V13" stroke={c} strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
|
|
44
|
+
</svg>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** 드래그 핸들 아이콘 (6점 그리드) */
|
|
49
|
+
export const GripVerticalIcon = ({ size = 20, color = "gray" }: IconSvgProps) => {
|
|
50
|
+
const c = resolveColor(color);
|
|
51
|
+
return (
|
|
52
|
+
<svg
|
|
53
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
54
|
+
width={size}
|
|
55
|
+
height={size}
|
|
56
|
+
viewBox="0 0 20 20"
|
|
57
|
+
fill="none"
|
|
58
|
+
className="cursor-grab"
|
|
59
|
+
>
|
|
60
|
+
<rect x="6.5" y="3.5" width="1.2" height="1.2" rx="0.6" stroke={c} />
|
|
61
|
+
<rect x="6.5" y="9.39844" width="1.2" height="1.2" rx="0.6" stroke={c} />
|
|
62
|
+
<rect x="6.5" y="15.3008" width="1.2" height="1.2" rx="0.6" stroke={c} />
|
|
63
|
+
<rect x="12.3008" y="3.5" width="1.2" height="1.2" rx="0.6" stroke={c} />
|
|
64
|
+
<rect x="12.3008" y="9.39844" width="1.2" height="1.2" rx="0.6" stroke={c} />
|
|
65
|
+
<rect x="12.3008" y="15.3008" width="1.2" height="1.2" rx="0.6" stroke={c} />
|
|
66
|
+
</svg>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** 하단 화살표 아이콘 */
|
|
71
|
+
export const ChevronDownIcon = ({ size = 20, color = "#191919" }: IconSvgProps) => (
|
|
72
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 20 20" fill="none">
|
|
73
|
+
<path
|
|
74
|
+
d="M16 7L10.0007 13L4 7"
|
|
75
|
+
stroke={resolveColor(color)}
|
|
76
|
+
strokeWidth="1.2"
|
|
77
|
+
strokeLinecap="round"
|
|
78
|
+
strokeLinejoin="round"
|
|
79
|
+
/>
|
|
80
|
+
</svg>
|
|
81
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { IconButton } from "./IconButton";
|
|
3
|
+
import { PlusIcon, TrashIcon, GripVerticalIcon } from "./Icon";
|
|
4
|
+
import { Group } from "@mantine/core";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof IconButton> = {
|
|
7
|
+
title: "Atoms/IconButton",
|
|
8
|
+
component: IconButton,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof IconButton>;
|
|
14
|
+
|
|
15
|
+
export const WithPlusIcon: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
icon: PlusIcon,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const WithTrashIcon: Story = {
|
|
22
|
+
args: {
|
|
23
|
+
icon: TrashIcon,
|
|
24
|
+
iconColor: "red",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const WithGripIcon: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
icon: GripVerticalIcon,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Disabled: Story = {
|
|
35
|
+
args: {
|
|
36
|
+
icon: PlusIcon,
|
|
37
|
+
disabled: true,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const CustomSize: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
icon: TrashIcon,
|
|
44
|
+
iconSize: 16,
|
|
45
|
+
iconColor: "gray",
|
|
46
|
+
size: "lg",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const AllVariants: Story = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<Group gap="md">
|
|
53
|
+
<IconButton icon={PlusIcon} />
|
|
54
|
+
<IconButton icon={TrashIcon} iconColor="red" />
|
|
55
|
+
<IconButton icon={GripVerticalIcon} />
|
|
56
|
+
<IconButton icon={PlusIcon} disabled />
|
|
57
|
+
</Group>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ActionIcon, ActionIconProps } from "@mantine/core";
|
|
2
|
+
import { ComponentPropsWithoutRef, ComponentType, ReactNode } from "react";
|
|
3
|
+
import { IconSvgProps } from "./Icon";
|
|
4
|
+
|
|
5
|
+
type Props = ActionIconProps &
|
|
6
|
+
ComponentPropsWithoutRef<"button"> & {
|
|
7
|
+
icon?: ComponentType<IconSvgProps>;
|
|
8
|
+
iconSize?: number;
|
|
9
|
+
iconColor?: string;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const IconButton = ({ icon: Icon, iconSize, iconColor, disabled, children, ...props }: Props) => {
|
|
14
|
+
return (
|
|
15
|
+
<ActionIcon variant="subtle" color="gray" disabled={disabled} {...props}>
|
|
16
|
+
{Icon ? <Icon size={iconSize} color={iconColor} disabled={disabled} /> : children}
|
|
17
|
+
</ActionIcon>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { InputColor } from "./InputColor";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof InputColor> = {
|
|
5
|
+
title: "Atoms/InputColor",
|
|
6
|
+
component: InputColor,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
args: {
|
|
9
|
+
label: "브랜드 색상",
|
|
10
|
+
placeholder: "색상을 선택하세요",
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type Story = StoryObj<typeof InputColor>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {};
|
|
18
|
+
|
|
19
|
+
export const WithValue: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
defaultValue: "#3B82F6",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const WithError: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
error: "색상을 선택해주세요",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Disabled: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
disabled: true,
|
|
34
|
+
value: "#10B981",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Required: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
withAsterisk: true,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ColorInput as MantineColorInput, ColorInputProps } from "@mantine/core";
|
|
2
|
+
import { inputClassNames } from "./inputClassNames";
|
|
3
|
+
|
|
4
|
+
export const InputColor = (props: ColorInputProps) => {
|
|
5
|
+
return <MantineColorInput classNames={inputClassNames} {...props} />;
|
|
6
|
+
};
|