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.
Files changed (83) hide show
  1. package/dist/index.d.mts +339 -0
  2. package/dist/index.d.ts +339 -0
  3. package/dist/index.js +1280 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +1234 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +59 -0
  8. package/src/atoms/Badge.stories.tsx +136 -0
  9. package/src/atoms/Badge.tsx +46 -0
  10. package/src/atoms/Button.stories.tsx +142 -0
  11. package/src/atoms/Button.tsx +50 -0
  12. package/src/atoms/ComboboxAutocomplete.stories.tsx +43 -0
  13. package/src/atoms/ComboboxAutocomplete.tsx +6 -0
  14. package/src/atoms/ComboboxSelect.stories.tsx +56 -0
  15. package/src/atoms/ComboboxSelect.tsx +6 -0
  16. package/src/atoms/DateTimePicker.stories.tsx +42 -0
  17. package/src/atoms/DateTimePicker.tsx +6 -0
  18. package/src/atoms/Icon.stories.tsx +86 -0
  19. package/src/atoms/Icon.tsx +81 -0
  20. package/src/atoms/IconButton.stories.tsx +59 -0
  21. package/src/atoms/IconButton.tsx +19 -0
  22. package/src/atoms/InputColor.stories.tsx +42 -0
  23. package/src/atoms/InputColor.tsx +6 -0
  24. package/src/atoms/InputDatePicker.stories.tsx +55 -0
  25. package/src/atoms/InputDatePicker.tsx +6 -0
  26. package/src/atoms/InputFile.stories.tsx +57 -0
  27. package/src/atoms/InputFile.tsx +15 -0
  28. package/src/atoms/InputNumber.stories.tsx +58 -0
  29. package/src/atoms/InputNumber.tsx +15 -0
  30. package/src/atoms/InputPassword.stories.tsx +42 -0
  31. package/src/atoms/InputPassword.tsx +24 -0
  32. package/src/atoms/InputRadio.stories.tsx +65 -0
  33. package/src/atoms/InputRadio.tsx +19 -0
  34. package/src/atoms/InputSegmentedControl.stories.tsx +44 -0
  35. package/src/atoms/InputSegmentedControl.tsx +15 -0
  36. package/src/atoms/InputSwitch.stories.tsx +45 -0
  37. package/src/atoms/InputSwitch.tsx +23 -0
  38. package/src/atoms/InputSwitchInTable.stories.tsx +43 -0
  39. package/src/atoms/InputSwitchInTable.tsx +35 -0
  40. package/src/atoms/InputText.stories.tsx +42 -0
  41. package/src/atoms/InputText.tsx +7 -0
  42. package/src/atoms/InputTextarea.stories.tsx +50 -0
  43. package/src/atoms/InputTextarea.tsx +6 -0
  44. package/src/atoms/index.ts +20 -0
  45. package/src/atoms/inputClassNames.ts +20 -0
  46. package/src/blocks/Accordion.stories.tsx +102 -0
  47. package/src/blocks/Accordion.tsx +124 -0
  48. package/src/blocks/AccordionDraggable.stories.tsx +169 -0
  49. package/src/blocks/AccordionDraggable.tsx +200 -0
  50. package/src/blocks/BoxContainer.stories.tsx +34 -0
  51. package/src/blocks/BoxContainer.tsx +16 -0
  52. package/src/blocks/DataTable.tsx +127 -0
  53. package/src/blocks/DescriptionRow.stories.tsx +34 -0
  54. package/src/blocks/DescriptionRow.tsx +22 -0
  55. package/src/blocks/EditorLayout.stories.tsx +79 -0
  56. package/src/blocks/EditorLayout.tsx +43 -0
  57. package/src/blocks/ImageUpload.tsx +292 -0
  58. package/src/blocks/LabeledField.stories.tsx +34 -0
  59. package/src/blocks/LabeledField.tsx +31 -0
  60. package/src/blocks/MediDrawer.stories.tsx +58 -0
  61. package/src/blocks/MediDrawer.tsx +34 -0
  62. package/src/blocks/Modal.stories.tsx +42 -0
  63. package/src/blocks/Modal.tsx +31 -0
  64. package/src/blocks/ModalDeleteConfirm.stories.tsx +31 -0
  65. package/src/blocks/ModalDeleteConfirm.tsx +56 -0
  66. package/src/blocks/ModalTokenExpired.tsx +52 -0
  67. package/src/blocks/NavigationBanner.tsx +100 -0
  68. package/src/blocks/PageHeader.tsx +24 -0
  69. package/src/blocks/PageLoading.stories.tsx +22 -0
  70. package/src/blocks/PageLoading.tsx +19 -0
  71. package/src/blocks/PageTitle.stories.tsx +39 -0
  72. package/src/blocks/PageTitle.tsx +25 -0
  73. package/src/blocks/Pagination.tsx +49 -0
  74. package/src/blocks/Panel.stories.tsx +84 -0
  75. package/src/blocks/Panel.tsx +97 -0
  76. package/src/blocks/SectionGroupWithTitle.stories.tsx +24 -0
  77. package/src/blocks/SectionGroupWithTitle.tsx +21 -0
  78. package/src/blocks/Table.tsx +114 -0
  79. package/src/blocks/TagInput.stories.tsx +74 -0
  80. package/src/blocks/TagInput.tsx +93 -0
  81. package/src/blocks/index.ts +25 -0
  82. package/src/index.ts +51 -0
  83. package/src/utils/file.ts +7 -0
@@ -0,0 +1,97 @@
1
+ import { Group, Paper as MantinePaper, PaperProps, Text, Title } from "@mantine/core";
2
+ import React, { ReactNode } from "react";
3
+
4
+ /**
5
+ *
6
+ * @param title - 패널 제목
7
+ * @param totalCount - 내부 데이터 총 개수
8
+ * @param description - 패널 설명
9
+ * @param children - 패널 컨텐츠
10
+ * @param headerRightContent - 패널 헤더 오른쪽 컨텐츠
11
+ * @param contentBottomPadding - 패널 컨텐츠 하단 패딩 (기본값: 40px)
12
+ * @param footer - 패널 푸터
13
+ * @returns 패널 컴포넌트
14
+ */
15
+ export const Panel = ({
16
+ title,
17
+ totalCount,
18
+ description,
19
+ children,
20
+ headerRightContent,
21
+ contentBottomPadding = 40,
22
+ footer,
23
+ ...props
24
+ }: PaperProps & {
25
+ title?: string | React.ReactNode;
26
+ totalCount?: number;
27
+ description?: string;
28
+ children?: React.ReactNode;
29
+ headerRightContent?: React.ReactNode;
30
+ contentBottomPadding?: number;
31
+ footer?: React.ReactNode;
32
+ }) => {
33
+ return (
34
+ <MantinePaper bg="white" radius="lg" shadow="xs" className="border-default border-1" {...props}>
35
+ {/* 헤더 영역 */}
36
+ {title && (
37
+ <div className="px-[30px]">
38
+ <Group justify="space-between" align="center" gap="xs" py="24">
39
+ {/* title */}
40
+ <Group align="baseline" gap="10">
41
+ <Title c="blue" order={2} fz={20} fw={700} lh="2">
42
+ {title}
43
+ </Title>
44
+ {totalCount && (
45
+ <Text c="blue" size="20" lh="1">
46
+ {totalCount}
47
+ </Text>
48
+ )}
49
+ </Group>
50
+
51
+ {/* headerRightContent */}
52
+ {headerRightContent}
53
+ </Group>
54
+
55
+ {/* description */}
56
+ {description && (
57
+ <Text c="#666" fz={14} fw={400} lh="160%" mb="24">
58
+ {description}
59
+ </Text>
60
+ )}
61
+ </div>
62
+ )}
63
+
64
+ {/* 컨텐츠 영역 */}
65
+ <div className="px-[30px]" style={{ paddingBottom: `${contentBottomPadding}px` }}>
66
+ {children}
67
+ </div>
68
+
69
+ {/* 푸터 영역 */}
70
+ {footer && <div className="border-gray-2 border-t px-[30px] py-5">{footer}</div>}
71
+ </MantinePaper>
72
+ );
73
+ };
74
+
75
+ // ============================================
76
+ // Sub Component
77
+ // ============================================
78
+ /**
79
+ *
80
+ * @param title - 섹션 제목 (스타일은 패널 제목과 동일)
81
+ * @param children - 섹션 컨텐츠
82
+ * @returns
83
+ */
84
+ function Section({ title, children }: { title?: string; children: ReactNode }) {
85
+ return (
86
+ <div className="mb-6 last:mb-0">
87
+ {title && (
88
+ <Text c="blue" fz={20} fw={700} lh="2" py="24">
89
+ {title}
90
+ </Text>
91
+ )}
92
+ {children}
93
+ </div>
94
+ );
95
+ }
96
+
97
+ Panel.Section = Section;
@@ -0,0 +1,24 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Stack, TextInput } from "@mantine/core";
3
+ import SectionGroupWithTitle from "./SectionGroupWithTitle";
4
+
5
+ const meta: Meta<typeof SectionGroupWithTitle> = {
6
+ title: "Blocks/SectionGroupWithTitle",
7
+ component: SectionGroupWithTitle,
8
+ tags: ["autodocs"],
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof SectionGroupWithTitle>;
13
+
14
+ export const Default: Story = {
15
+ args: {
16
+ title: "설정",
17
+ children: (
18
+ <Stack gap="sm">
19
+ <TextInput label="이름" placeholder="이름을 입력하세요" />
20
+ <TextInput label="전화번호" placeholder="전화번호를 입력하세요" />
21
+ </Stack>
22
+ ),
23
+ },
24
+ };
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { Box, Text } from "@mantine/core";
3
+ import { Divider } from "@mantine/core";
4
+
5
+ interface Props {
6
+ title: string;
7
+ children: React.ReactNode;
8
+ }
9
+ const SectionGroupWithTitle = ({ title, children }: Props) => {
10
+ return (
11
+ <Box>
12
+ <Text fw={600} mb="sm">
13
+ {title}
14
+ </Text>
15
+ <Divider mb="xs" />
16
+ {children}
17
+ </Box>
18
+ );
19
+ };
20
+
21
+ export default SectionGroupWithTitle;
@@ -0,0 +1,114 @@
1
+ "use client";
2
+
3
+ import { ReactNode } from "react";
4
+ import { Table as MantineTable, Text, Box } from "@mantine/core";
5
+
6
+ // ============================================
7
+ // Types
8
+ // ============================================
9
+
10
+ export interface TableColumn<T> {
11
+ id: string;
12
+ header: string;
13
+ accessor?: keyof T;
14
+ render?: (row: T, index: number) => ReactNode;
15
+ align?: "left" | "center" | "right";
16
+ width?: number | string;
17
+ }
18
+
19
+ export interface TableProps<T> {
20
+ columns: TableColumn<T>[];
21
+ data: T[];
22
+ rowKey?: keyof T | ((row: T, index: number) => string);
23
+ emptyMessage?: string;
24
+ striped?: boolean;
25
+ highlightOnHover?: boolean;
26
+ onRowClick?: (row: T, index: number) => void;
27
+ }
28
+
29
+ // ============================================
30
+ // Component
31
+ // ============================================
32
+
33
+ export function Table<T>({
34
+ columns,
35
+ data,
36
+ rowKey,
37
+ emptyMessage = "데이터가 없습니다",
38
+ highlightOnHover = false,
39
+ onRowClick,
40
+ }: TableProps<T>) {
41
+ const getRowKey = (row: T, index: number): string => {
42
+ if (!rowKey) return String(index);
43
+ if (typeof rowKey === "function") return rowKey(row, index);
44
+ return String(row[rowKey]);
45
+ };
46
+
47
+ const getCellValue = (row: T, column: TableColumn<T>, index: number): ReactNode => {
48
+ if (column.render) {
49
+ return column.render(row, index);
50
+ }
51
+ if (column.accessor) {
52
+ return row[column.accessor] as ReactNode;
53
+ }
54
+ return null;
55
+ };
56
+
57
+ if (data.length === 0) {
58
+ return (
59
+ <Box py="xl" style={{ textAlign: "center" }}>
60
+ <Text c="dimmed">{emptyMessage}</Text>
61
+ </Box>
62
+ );
63
+ }
64
+
65
+ return (
66
+ <MantineTable
67
+ highlightOnHover={highlightOnHover}
68
+ verticalSpacing="md"
69
+ classNames={{
70
+ table: "!border-default !overflow-hidden !border-separate",
71
+ thead: "!bg-gray-0",
72
+ th: "!text-center !border-b !border-default",
73
+ td: "!text-center !border-b !border-default",
74
+ }}
75
+ >
76
+ <MantineTable.Thead>
77
+ <MantineTable.Tr>
78
+ {columns.map((column) => (
79
+ <MantineTable.Th
80
+ key={column.id}
81
+ style={{
82
+ textAlign: column.align || "left",
83
+ width: column.width,
84
+ }}
85
+ >
86
+ {column.header}
87
+ </MantineTable.Th>
88
+ ))}
89
+ </MantineTable.Tr>
90
+ </MantineTable.Thead>
91
+ <MantineTable.Tbody>
92
+ {data.map((row, index) => (
93
+ <MantineTable.Tr
94
+ key={getRowKey(row, index)}
95
+ onClick={() => onRowClick?.(row, index)}
96
+ style={{ cursor: onRowClick ? "pointer" : undefined }}
97
+ >
98
+ {columns.map((column) => (
99
+ <MantineTable.Td
100
+ key={column.id}
101
+ style={{
102
+ textAlign: column.align || "left",
103
+ width: column.width,
104
+ }}
105
+ >
106
+ {getCellValue(row, column, index)}
107
+ </MantineTable.Td>
108
+ ))}
109
+ </MantineTable.Tr>
110
+ ))}
111
+ </MantineTable.Tbody>
112
+ </MantineTable>
113
+ );
114
+ }
@@ -0,0 +1,74 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { TagInput, TagInputItem, TagInputProps } from "./TagInput";
4
+
5
+ const meta: Meta<typeof TagInput> = {
6
+ title: "Blocks/TagInput",
7
+ component: TagInput,
8
+ tags: ["autodocs"],
9
+ args: {
10
+ placeholder: "옵션 입력 (쉼표로 구분)",
11
+ badgeVariant: "filled",
12
+ badgeColor: "blue",
13
+ },
14
+ };
15
+
16
+ export default meta;
17
+ type Story = StoryObj<typeof TagInput>;
18
+
19
+ function TagInputWithState({
20
+ initialItems = [],
21
+ dedup,
22
+ ...props
23
+ }: TagInputProps & { initialItems?: TagInputItem[]; dedup?: boolean }) {
24
+ const [items, setItems] = useState<TagInputItem[]>(initialItems);
25
+
26
+ return (
27
+ <TagInput
28
+ {...props}
29
+ items={items}
30
+ onAdd={(values) => {
31
+ const newItems = values.map((v) => ({ id: dedup ? v : crypto.randomUUID(), label: v }));
32
+ setItems((prev) => {
33
+ if (!dedup) return [...prev, ...newItems];
34
+ const existing = new Set(prev.map((i) => i.id));
35
+ return [...prev, ...newItems.filter((i) => !existing.has(i.id))];
36
+ });
37
+ }}
38
+ onRemove={(id) => setItems((prev) => prev.filter((item) => item.id !== id))}
39
+ />
40
+ );
41
+ }
42
+
43
+ export const Default: Story = {
44
+ render: (args) => (
45
+ <TagInputWithState
46
+ {...args}
47
+ initialItems={[
48
+ { id: "1", label: "SNS" },
49
+ { id: "2", label: "친구추천" },
50
+ { id: "3", label: "기타" },
51
+ ]}
52
+ />
53
+ ),
54
+ };
55
+
56
+ export const Empty: Story = {
57
+ render: (args) => <TagInputWithState {...args} placeholder="시간 입력 (예: 10:00, 11:00, 14:00)" dedup />,
58
+ };
59
+
60
+ export const LightVariant: Story = {
61
+ render: (args) => (
62
+ <TagInputWithState
63
+ {...args}
64
+ placeholder="시간 입력 (예: 10:00, 11:00, 14:00)"
65
+ badgeVariant="light"
66
+ dedup
67
+ initialItems={[
68
+ { id: "10:00", label: "10:00" },
69
+ { id: "11:00", label: "11:00" },
70
+ { id: "14:00", label: "14:00" },
71
+ ]}
72
+ />
73
+ ),
74
+ };
@@ -0,0 +1,93 @@
1
+ "use client";
2
+
3
+ import { Badge, Button, InputText } from "../atoms";
4
+ import { ActionIcon, Box, Group } from "@mantine/core";
5
+ import { IconPlus, IconX } from "@tabler/icons-react";
6
+ import { useState } from "react";
7
+
8
+ export interface TagInputItem {
9
+ id: string;
10
+ label: string;
11
+ }
12
+
13
+ export interface TagInputProps {
14
+ label?: string;
15
+ placeholder?: string;
16
+ items: TagInputItem[];
17
+ onAdd: (values: string[]) => void;
18
+ onRemove: (id: string) => void;
19
+ badgeVariant?: "filled" | "light";
20
+ badgeColor?: string;
21
+ }
22
+
23
+ export function TagInput({
24
+ label,
25
+ placeholder,
26
+ items,
27
+ onAdd,
28
+ onRemove,
29
+ badgeVariant = "filled",
30
+ badgeColor = "blue",
31
+ }: TagInputProps) {
32
+ const [inputValue, setInputValue] = useState("");
33
+
34
+ const handleAdd = () => {
35
+ if (!inputValue.trim()) return;
36
+ const values = inputValue
37
+ .split(",")
38
+ .map((s) => s.trim())
39
+ .filter((s) => s.length > 0);
40
+ onAdd(values);
41
+ setInputValue("");
42
+ };
43
+
44
+ return (
45
+ <Box>
46
+ <InputText
47
+ size="xs"
48
+ label={label}
49
+ placeholder={placeholder}
50
+ value={inputValue}
51
+ onChange={(e) => setInputValue(e.currentTarget.value)}
52
+ onKeyDown={(e) => {
53
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
54
+ e.preventDefault();
55
+ handleAdd();
56
+ }
57
+ }}
58
+ rightSection={
59
+ <Button size="xs" variant="light" onClick={handleAdd}>
60
+ <IconPlus size={10} />
61
+ </Button>
62
+ }
63
+ rightSectionWidth={27}
64
+ mb="xs"
65
+ />
66
+ {items.length > 0 && (
67
+ <Group gap={4}>
68
+ {items.map((item) => (
69
+ <Badge
70
+ key={item.id}
71
+ size="sm"
72
+ variant={badgeVariant}
73
+ color={badgeColor}
74
+ h="24"
75
+ rightSection={
76
+ <ActionIcon
77
+ size={14}
78
+ variant="transparent"
79
+ c={badgeVariant === "light" ? "gray" : "white"}
80
+ onClick={() => onRemove(item.id)}
81
+ >
82
+ <IconX size={10} />
83
+ </ActionIcon>
84
+ }
85
+ >
86
+ {item.label}
87
+ </Badge>
88
+ ))}
89
+ </Group>
90
+ )}
91
+ </Box>
92
+ );
93
+ }
@@ -0,0 +1,25 @@
1
+ export { Table } from "./Table";
2
+ export type { TableColumn, TableProps } from "./Table";
3
+ export { Pagination } from "./Pagination";
4
+ export type { PaginationProps } from "./Pagination";
5
+ export { Accordion } from "./Accordion";
6
+ export type { AccordionProps, AccordionItemData } from "./Accordion";
7
+ export { AccordionDraggable } from "./AccordionDraggable";
8
+ export type { AccordionDraggableProps, AccordionDraggableItem } from "./AccordionDraggable";
9
+ export { NavigationBanner } from "./NavigationBanner";
10
+ export type { NavigationBannerItem, NavigationBannerProps } from "./NavigationBanner";
11
+ export { ImageUpload } from "./ImageUpload";
12
+ export { PageTitle } from "./PageTitle";
13
+ export { PageLoading } from "./PageLoading";
14
+ export { Panel } from "./Panel";
15
+ export { default as BoxContainer } from "./BoxContainer";
16
+ export { EditorLayout } from "./EditorLayout";
17
+ export { default as Modal } from "./Modal";
18
+ export { default as ModalDeleteConfirm } from "./ModalDeleteConfirm";
19
+ export { ModalTokenExpired } from "./ModalTokenExpired";
20
+ export { default as DataTable } from "./DataTable";
21
+ export type { Column } from "./DataTable";
22
+ export { default as PageHeader } from "./PageHeader";
23
+ export { default as SectionGroupWithTitle } from "./SectionGroupWithTitle";
24
+ export { TagInput } from "./TagInput";
25
+ export type { TagInputProps, TagInputItem } from "./TagInput";
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ // Atoms
2
+ export { Button } from "./atoms/Button";
3
+ export { InputText } from "./atoms/InputText";
4
+ export { InputPassword } from "./atoms/InputPassword";
5
+ export { InputNumber } from "./atoms/InputNumber";
6
+ export { InputColor } from "./atoms/InputColor";
7
+ export { InputFile } from "./atoms/InputFile";
8
+ export { InputDatePicker } from "./atoms/InputDatePicker";
9
+ export { InputTextarea } from "./atoms/InputTextarea";
10
+ export { InputRadio } from "./atoms/InputRadio";
11
+ export { InputSwitch } from "./atoms/InputSwitch";
12
+ export { InputSwitchInTable } from "./atoms/InputSwitchInTable";
13
+ export { InputSegmentedControl } from "./atoms/InputSegmentedControl";
14
+ export { ComboboxSelect } from "./atoms/ComboboxSelect";
15
+ export { ComboboxAutocomplete } from "./atoms/ComboboxAutocomplete";
16
+ export { DateTimePicker } from "./atoms/DateTimePicker";
17
+ export { Badge } from "./atoms/Badge";
18
+ export { IconButton } from "./atoms/IconButton";
19
+ export { PlusIcon, TrashIcon, GripVerticalIcon, ChevronDownIcon } from "./atoms/Icon";
20
+ export type { IconSvgProps } from "./atoms/Icon";
21
+ export { inputClassNames } from "./atoms/inputClassNames";
22
+
23
+ // Blocks
24
+ export { Table } from "./blocks/Table";
25
+ export type { TableColumn, TableProps } from "./blocks/Table";
26
+ export { Pagination } from "./blocks/Pagination";
27
+ export type { PaginationProps } from "./blocks/Pagination";
28
+ export { Accordion } from "./blocks/Accordion";
29
+ export type { AccordionProps, AccordionItemData } from "./blocks/Accordion";
30
+ export { AccordionDraggable } from "./blocks/AccordionDraggable";
31
+ export type { AccordionDraggableProps, AccordionDraggableItem } from "./blocks/AccordionDraggable";
32
+ export { NavigationBanner } from "./blocks/NavigationBanner";
33
+ export type { NavigationBannerItem, NavigationBannerProps } from "./blocks/NavigationBanner";
34
+ export { ImageUpload } from "./blocks/ImageUpload";
35
+ export { PageTitle } from "./blocks/PageTitle";
36
+ export { PageLoading } from "./blocks/PageLoading";
37
+ export { Panel } from "./blocks/Panel";
38
+ export { default as BoxContainer } from "./blocks/BoxContainer";
39
+ export { EditorLayout } from "./blocks/EditorLayout";
40
+ export { default as Modal } from "./blocks/Modal";
41
+ export { default as ModalDeleteConfirm } from "./blocks/ModalDeleteConfirm";
42
+ export { ModalTokenExpired } from "./blocks/ModalTokenExpired";
43
+ export { default as DataTable } from "./blocks/DataTable";
44
+ export type { Column } from "./blocks/DataTable";
45
+ export { default as PageHeader } from "./blocks/PageHeader";
46
+ export { default as SectionGroupWithTitle } from "./blocks/SectionGroupWithTitle";
47
+ export { TagInput } from "./blocks/TagInput";
48
+ export type { TagInputProps, TagInputItem } from "./blocks/TagInput";
49
+
50
+ // Utils
51
+ export { readFileAsDataURL } from "./utils/file";
@@ -0,0 +1,7 @@
1
+ export function readFileAsDataURL(file: File): Promise<string> {
2
+ return new Promise((resolve) => {
3
+ const reader = new FileReader();
4
+ reader.onloadend = () => resolve(reader.result as string);
5
+ reader.readAsDataURL(file);
6
+ });
7
+ }