@loword/loword-design-system 0.2.2 → 0.3.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/components/LDAvatar/LDEditableAvatar.d.ts +17 -0
- package/dist/components/LDAvatar/LDEditableAvatar.d.ts.map +1 -0
- package/dist/components/LDAvatar/LDEditableAvatar.js +70 -0
- package/dist/components/LDDataCalendar/LDDataCalendar.d.ts.map +1 -1
- package/dist/components/LDDataCalendar/LDDataCalendar.js +5 -4
- package/dist/components/LDDatePicker/LDDatePicker.js +1 -1
- package/dist/components/LDDateRangePicker/LDDateRangePicker.js +2 -2
- package/dist/components/LDEditor/EditorBase.d.ts.map +1 -1
- package/dist/components/LDEditor/EditorBase.js +25 -2
- package/dist/components/LDEditor/LDEditor.d.ts.map +1 -1
- package/dist/components/LDEditor/LDEditor.js +2 -1
- package/dist/components/LDFileUpload/FileUploadBase.d.ts +20 -4
- package/dist/components/LDFileUpload/FileUploadBase.d.ts.map +1 -1
- package/dist/components/LDFileUpload/FileUploadBase.js +41 -18
- package/dist/components/LDFileUpload/LDFileUpload.d.ts +19 -6
- package/dist/components/LDFileUpload/LDFileUpload.d.ts.map +1 -1
- package/dist/components/LDFileUpload/LDFileUpload.js +124 -28
- package/dist/components/LDFileUpload/LDFileUploadList.js +1 -1
- package/dist/components/LDInput/LDInput.d.ts +3 -1
- package/dist/components/LDInput/LDInput.d.ts.map +1 -1
- package/dist/components/LDInput/LDInput.js +10 -4
- package/dist/components/LDModal/LDModal.d.ts +7 -6
- package/dist/components/LDModal/LDModal.d.ts.map +1 -1
- package/dist/components/LDModal/LDModalView.d.ts +3 -3
- package/dist/components/LDModal/LDModalView.d.ts.map +1 -1
- package/dist/components/LDModal/LDModalView.js +6 -5
- package/dist/components/LDRadio/LDRadioGroup.d.ts +3 -3
- package/dist/components/LDRadio/LDRadioGroup.d.ts.map +1 -1
- package/dist/components/LDRadio/LDRadioGroup.js +1 -1
- package/dist/components/LDSelect/LDMultiSelect.js +1 -1
- package/dist/components/LDSelect/LDSelect.js +1 -1
- package/dist/components/LDTable/LDTable.js +2 -2
- package/dist/components/LDTextarea/LDTextarea.js +7 -7
- package/dist/components/LDTimePicker/LDTimePicker.js +1 -1
- package/dist/index.css +115 -20
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/file.d.ts +2 -0
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +1 -0
- package/package.json +1 -1
- package/src/styles/components/editor.css +14 -0
|
@@ -1,76 +1,172 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import { Images, Link2, Paperclip, Plus, Trash2, X } from 'lucide-react';
|
|
5
|
+
import { createContext, useContext, } from 'react';
|
|
5
6
|
import LDButton from '../../components/LDButton/LDButton.js';
|
|
6
7
|
import LDCarousel from '../../components/LDCarousel/LDCarousel.js';
|
|
7
8
|
import FileUploadBase, { useFileUploadContext, } from '../../components/LDFileUpload/FileUploadBase.js';
|
|
9
|
+
import InputBase from '../../components/LDInput/InputBase.js';
|
|
8
10
|
import RenderIcon from '../../components/RenderIcon/RenderIcon.js';
|
|
11
|
+
import Show from '../../components/Show/Show.js';
|
|
9
12
|
import { FILE_EXTENSIONS } from '../../constants/file.js';
|
|
10
13
|
import cn from '../../utils/cn.js';
|
|
11
|
-
import {
|
|
14
|
+
import { getFileSizeUnit } from '../../utils/file.js';
|
|
15
|
+
import tv from '../../utils/tv.js';
|
|
16
|
+
// disabled·readOnly·기본 상태의 커서는 서로 배타적이다. cursor-not-allowed 는 hover:cursor-pointer 와
|
|
17
|
+
// tailwind-merge 상 다른 그룹이어서 축만으로는 지워지지 않으므로, 겹치지 않게 compound 로 갈라 놓는다.
|
|
18
|
+
const cursorVariants = {
|
|
19
|
+
disabled: { true: 'cursor-not-allowed', false: '' },
|
|
20
|
+
readOnly: { true: '', false: '' },
|
|
21
|
+
};
|
|
22
|
+
const cursorCompounds = [
|
|
23
|
+
{ disabled: false, readOnly: true, className: 'cursor-default' },
|
|
24
|
+
{ disabled: false, readOnly: false, className: 'hover:cursor-pointer' },
|
|
25
|
+
];
|
|
26
|
+
const attachInputVariants = tv({
|
|
27
|
+
base: 'flex w-full flex-row items-center gap-sm rounded-[10px] bg-neutral50 p-5 display16 bold text-neutral700 ld-transition',
|
|
28
|
+
variants: {
|
|
29
|
+
disabled: {
|
|
30
|
+
true: 'cursor-not-allowed opacity-50',
|
|
31
|
+
false: 'hover:cursor-pointer hover:bg-neutral100',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const imageInputVariants = tv({
|
|
36
|
+
base: 'flex size-21 items-center justify-center rounded-xl bg-neutral50 text-neutral500 outline-3 -outline-offset-3 outline-neutral100 outline-dotted ld-transition',
|
|
37
|
+
variants: {
|
|
38
|
+
disabled: {
|
|
39
|
+
true: 'cursor-not-allowed opacity-50',
|
|
40
|
+
false: 'hover:cursor-pointer hover:bg-neutral-100 hover:text-neutral600 hover:outline-neutral200',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
const imageCardVariants = tv({
|
|
45
|
+
base: 'group relative block size-21 overflow-hidden rounded-xl outline-3 -outline-offset-3 outline-neutral200',
|
|
46
|
+
variants: cursorVariants,
|
|
47
|
+
compoundVariants: cursorCompounds,
|
|
48
|
+
});
|
|
49
|
+
const imageCardOverlayVariants = tv({
|
|
50
|
+
base: 'pointer-events-none absolute inset-0 opacity-0 ld-transition',
|
|
51
|
+
variants: { locked: { false: 'bg-black/25 group-hover:opacity-100' } },
|
|
52
|
+
});
|
|
53
|
+
const imageCardDeleteVariants = tv({
|
|
54
|
+
base: 'absolute top-2 right-2 flex size-5 min-w-0 items-center justify-center rounded-sm bg-neutral-100 px-0 text-neutral700 opacity-0 ld-transition',
|
|
55
|
+
variants: { locked: { false: 'group-hover:opacity-100' } },
|
|
56
|
+
});
|
|
57
|
+
const cardVariants = tv({
|
|
58
|
+
base: 'flex w-full flex-row items-center justify-between gap-md rounded-[10px] bg-tintBlue p-5',
|
|
59
|
+
variants: cursorVariants,
|
|
60
|
+
compoundVariants: cursorCompounds,
|
|
61
|
+
});
|
|
62
|
+
const cardDeleteVariants = tv({
|
|
63
|
+
base: 'flex size-4.5 min-w-0 items-center justify-center px-0 text-lb500 ld-transition',
|
|
64
|
+
variants: { locked: { true: 'pointer-events-none opacity-0' } },
|
|
65
|
+
});
|
|
66
|
+
// 링크 카드는 파일 카드와 같은 규격(bg-tintBlue·p-5·rounded-[10px])을 쓰되, 안쪽이 실제 input이라
|
|
67
|
+
// 스타일 박스가 래퍼로 올라가고 focus는 내부 input에 가므로 LDInput처럼 focus-within으로 반응한다.
|
|
68
|
+
const linkCardVariants = tv({
|
|
69
|
+
base: 'flex w-full flex-row items-center gap-sm rounded-[10px] bg-tintBlue p-5 outline-2 -outline-offset-2 outline-transparent outline-solid ld-transition focus-within:outline-lb500',
|
|
70
|
+
variants: {
|
|
71
|
+
disabled: { true: 'cursor-not-allowed opacity-50', false: '' },
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const linkFieldVariants = tv({
|
|
75
|
+
base: 'ld-input-field -my-2 h-full w-full min-w-0 border-0 bg-transparent px-0 py-2 display16 bold text-lb500 placeholder-lb300 outline-none read-only:cursor-not-allowed',
|
|
76
|
+
variants: {
|
|
77
|
+
disabled: { true: 'cursor-not-allowed', false: '' },
|
|
78
|
+
},
|
|
79
|
+
});
|
|
12
80
|
const StyleContext = createContext({});
|
|
13
81
|
/** 파일 업로드의 상태와 Styled 파트를 연결하는 루트입니다. */
|
|
14
82
|
const Root = ({ className, classNames = {}, children, ...props }) => (_jsx(StyleContext, { value: classNames, children: _jsx(FileUploadBase.Root, { ...props, className: cn('flex w-full flex-col gap-sm ld-transition', className, classNames.root), children: children }) }));
|
|
15
83
|
/** 새 파일을 선택하는 입력입니다. */
|
|
16
|
-
const Input = ({ type = 'default', label
|
|
17
|
-
const { disabled, readOnly } = useFileUploadContext();
|
|
84
|
+
const Input = ({ type = 'default', label, icon, options, className, ...props }) => {
|
|
85
|
+
const { disabled, readOnly, addLink } = useFileUploadContext();
|
|
18
86
|
const classNames = useContext(StyleContext);
|
|
19
87
|
if (readOnly)
|
|
20
88
|
return null;
|
|
21
|
-
const
|
|
89
|
+
const resolvedLabel = label ??
|
|
90
|
+
{
|
|
91
|
+
attachment: '파일 첨부하기',
|
|
92
|
+
link: '링크 첨부하기',
|
|
93
|
+
default: '파일 선택',
|
|
94
|
+
}[type === 'image' ? 'default' : type];
|
|
95
|
+
const fileInput = (_jsx(FileUploadBase.Input, { options: options, "aria-label": resolvedLabel, className: "sr-only" }));
|
|
96
|
+
// 링크는 파일 선택 대화창이 아니라 빈 입력 행을 추가하는 버튼이라 file input을 두지 않는다.
|
|
97
|
+
if (type === 'link') {
|
|
98
|
+
return (_jsxs("button", { type: "button", onClick: addLink, disabled: disabled, className: attachInputVariants({
|
|
99
|
+
disabled: !!disabled,
|
|
100
|
+
className: cn(className, classNames.input),
|
|
101
|
+
}), children: [_jsx(RenderIcon, { className: "size-5", children: icon ?? _jsx(Link2, {}) }), resolvedLabel] }));
|
|
102
|
+
}
|
|
103
|
+
if (type === 'attachment') {
|
|
104
|
+
return (_jsxs("label", { className: attachInputVariants({
|
|
105
|
+
disabled: !!disabled,
|
|
106
|
+
className: cn(className, classNames.input),
|
|
107
|
+
}), children: [_jsx(RenderIcon, { className: "size-5", children: icon ?? _jsx(Plus, {}) }), resolvedLabel, fileInput] }));
|
|
108
|
+
}
|
|
22
109
|
if (type === 'image') {
|
|
23
|
-
return (_jsxs("label", { className:
|
|
24
|
-
|
|
25
|
-
:
|
|
110
|
+
return (_jsxs("label", { className: imageInputVariants({
|
|
111
|
+
// 미지정도 false 분기(hover)를 타야 하므로 boolean으로 좁혀 넘긴다.
|
|
112
|
+
disabled: !!disabled,
|
|
113
|
+
className: cn(className, classNames.input),
|
|
114
|
+
}), children: [_jsx(RenderIcon, { className: "size-6", children: icon ?? _jsx(Images, {}) }), fileInput] }));
|
|
26
115
|
}
|
|
27
|
-
return (_jsxs(LDButton, { as: "label", ...props, className: cn(className, classNames.input), disabled: disabled, children: [
|
|
116
|
+
return (_jsxs(LDButton, { as: "label", ...props, className: cn(className, classNames.input), disabled: disabled, children: [resolvedLabel, fileInput] }));
|
|
28
117
|
};
|
|
29
118
|
/** 선택된 파일을 교체하거나 삭제할 수 있는 목록입니다. */
|
|
30
|
-
const CardList = ({ type = 'default', className, }) => {
|
|
119
|
+
const CardList = ({ type = 'default', isExistUploadedAt = true, className, }) => {
|
|
31
120
|
const { fileList, disabled, readOnly, locked, deleteFile } = useFileUploadContext();
|
|
32
121
|
const classNames = useContext(StyleContext);
|
|
33
122
|
if (type === 'image') {
|
|
34
|
-
const cards = fileList.map((file, index) => (_jsxs("label", { className:
|
|
35
|
-
|
|
36
|
-
: readOnly
|
|
37
|
-
|
|
38
|
-
|
|
123
|
+
const cards = fileList.map((file, index) => (_jsxs("label", { className: imageCardVariants({
|
|
124
|
+
disabled: !!disabled,
|
|
125
|
+
readOnly: !!readOnly,
|
|
126
|
+
className: classNames.card,
|
|
127
|
+
}), children: [_jsx("img", { src: file.src, alt: file.fileName, className: "size-full object-cover" }), _jsx("span", { className: imageCardOverlayVariants({ locked }) }), _jsx(FileUploadBase.Input, { replaceIndex: index, "aria-label": `${file.fileName} 교체`, className: "sr-only" }), _jsx(LDButton, { onClick: async (event) => {
|
|
39
128
|
event.preventDefault();
|
|
40
129
|
event.stopPropagation();
|
|
41
130
|
await deleteFile(index);
|
|
42
|
-
}, disabled: locked, variant: "ghost", leftIcon: _jsx(Trash2, {}), color: "neutral", className:
|
|
131
|
+
}, disabled: locked, variant: "ghost", leftIcon: _jsx(Trash2, {}), color: "neutral", className: imageCardDeleteVariants({ locked }) })] }, `${file.fileName}_${index}`)));
|
|
43
132
|
if (fileList.length <= 1) {
|
|
44
133
|
return (_jsx("div", { className: cn('flex flex-row gap-sm', className, classNames.cardList), children: cards }));
|
|
45
134
|
}
|
|
46
135
|
return (_jsx(LDCarousel, { className: cn(className, classNames.cardList), slidesPerView: "auto", gap: 8, arrowPosition: "none", showDots: false, slides: cards }));
|
|
47
136
|
}
|
|
48
|
-
return (_jsx("div", { className: cn('flex w-full flex-col gap-sm', className, classNames.cardList), children: fileList.map((file, index) => (_jsxs("label", { className:
|
|
49
|
-
|
|
50
|
-
: readOnly
|
|
51
|
-
|
|
52
|
-
|
|
137
|
+
return (_jsx("div", { className: cn('flex w-full flex-col gap-sm', className, classNames.cardList), children: fileList.map((file, index) => (_jsxs("label", { className: cardVariants({
|
|
138
|
+
disabled: !!disabled,
|
|
139
|
+
readOnly: !!readOnly,
|
|
140
|
+
className: classNames.card,
|
|
141
|
+
}), children: [_jsxs("div", { className: "flex flex-row items-center gap-sm", children: [_jsx(RenderIcon, { className: "size-5 text-lb500", children: _jsx(Paperclip, {}) }), _jsxs("div", { className: "flex items-center justify-center gap-sm mobile:flex-col mobile:items-start mobile:gap-2xs", children: [_jsx("span", { className: "display16 bold text-lb500", children: file.fileName }), _jsx(Show, { when: isExistUploadedAt && file.uploadedAt, children: (uploadedAt) => (_jsxs("span", { className: "rounded-[5px] bg-blue200 px-1.5 py-0.75 display10 medium text-white", children: [dayjs(uploadedAt).format('YYYY-MM-DD HH:mm'), " \uB9C8\uC9C0\uB9C9 \uC5C5\uB85C\uB4DC"] })) })] })] }), _jsx(FileUploadBase.Input, { replaceIndex: index, "aria-label": `${file.fileName} 교체`, className: "sr-only" }), _jsx(LDButton, { onClick: async (event) => {
|
|
53
142
|
event.preventDefault();
|
|
54
143
|
event.stopPropagation();
|
|
55
144
|
await deleteFile(index);
|
|
56
|
-
}, disabled: locked, variant: "ghost", leftIcon: _jsx(
|
|
145
|
+
}, disabled: locked, variant: "ghost", leftIcon: _jsx(X, { className: "size-4.5" }), color: "neutral", className: cardDeleteVariants({ locked }) })] }, `${file.fileName}_${index}`))) }));
|
|
146
|
+
};
|
|
147
|
+
/** 첨부된 링크를 실시간으로 편집하거나 삭제할 수 있는 목록입니다. */
|
|
148
|
+
const LinkList = ({ placeholder = '링크를 입력해주세요', className, }) => {
|
|
149
|
+
const { linkList, disabled, readOnly, locked, changeLink, deleteLink } = useFileUploadContext();
|
|
150
|
+
const classNames = useContext(StyleContext);
|
|
151
|
+
return (_jsx("div", { className: cn('flex w-full flex-col gap-sm', className, classNames.cardList), children: linkList.map((link, index) => (_jsxs(InputBase.Root, { block: true, className: linkCardVariants({
|
|
152
|
+
disabled: !!disabled,
|
|
153
|
+
className: classNames.card,
|
|
154
|
+
}), children: [_jsx(RenderIcon, { className: "size-5 shrink-0 text-lb500", children: _jsx(Link2, {}) }), _jsx(InputBase.Field, { type: "url", value: link, placeholder: placeholder, "aria-label": `링크 ${index + 1}`, disabled: disabled, readOnly: readOnly, onChange: (event) => changeLink(index, event.target.value), className: linkFieldVariants({ disabled: !!disabled }) }), _jsx(LDButton, { onClick: () => deleteLink(index), "aria-label": `링크 ${index + 1} 삭제`, disabled: locked, variant: "ghost", leftIcon: _jsx(X, { className: "size-4.5" }), color: "neutral", className: cardDeleteVariants({ locked }) })] }, index))) }));
|
|
57
155
|
};
|
|
58
156
|
/** 업로드 제한 조건을 안내합니다. */
|
|
59
157
|
const Description = ({ className, ...props }) => {
|
|
60
158
|
const { recommendedSize, maxSize, allowedExtensions, maxFileCount } = useFileUploadContext();
|
|
61
159
|
const classNames = useContext(StyleContext);
|
|
62
|
-
const recommendedSizeText = recommendedSize
|
|
63
|
-
? `권장 사이즈 ${recommendedSize.width}x${recommendedSize.height}, `
|
|
64
|
-
: '';
|
|
65
160
|
const extensionsText = allowedExtensions
|
|
66
161
|
.map((item) => FILE_EXTENSIONS[item])
|
|
67
162
|
.join(', ');
|
|
68
|
-
return (_jsxs("p", { ...props, className: cn('display12 semibold text-neutral400', className, classNames.description), children: [
|
|
163
|
+
return (_jsxs("p", { ...props, className: cn('display12 semibold text-neutral400', className, classNames.description), children: [_jsx(Show, { when: recommendedSize, children: ({ width, height }) => `권장 사이즈 ${width}x${height}, ` }), "\uCD5C\uB300 \uD06C\uAE30 ", getFileSizeUnit(maxSize), " (", extensionsText, ")", _jsx(Show, { when: maxFileCount > 1, children: `, 최대 ${maxFileCount}개` })] }));
|
|
69
164
|
};
|
|
70
165
|
/** 한 개의 일반 파일을 업로드하는 기본 조합입니다. */
|
|
71
|
-
const LDFileUpload = ({ recommendedSize = { width: 1200, height: 800 }, isExistDescription, fileList, ...props }) => (_jsxs(Root, { ...props, fileList: fileList, recommendedSize: recommendedSize, maxFileCount: 1, children: [fileList[0]?.src
|
|
166
|
+
const LDFileUpload = ({ recommendedSize = { width: 1200, height: 800 }, isExistDescription, isExistUploadedAt, fileList, ...props }) => (_jsxs(Root, { ...props, fileList: fileList, recommendedSize: recommendedSize, maxFileCount: 1, children: [_jsx(Show, { when: fileList[0]?.src, fallback: _jsx(Input, { className: "w-fit" }), children: _jsx(CardList, { isExistUploadedAt: isExistUploadedAt }) }), _jsx(Show, { when: isExistDescription, children: _jsx(Description, {}) })] }));
|
|
72
167
|
LDFileUpload.Root = Root;
|
|
73
168
|
LDFileUpload.Input = Input;
|
|
74
169
|
LDFileUpload.CardList = CardList;
|
|
170
|
+
LDFileUpload.LinkList = LinkList;
|
|
75
171
|
LDFileUpload.Description = Description;
|
|
76
172
|
export default LDFileUpload;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import LDFileUpload from '../../components/LDFileUpload/LDFileUpload.js';
|
|
3
3
|
/** 여러 개의 일반 파일을 업로드하는 기본 조합입니다. */
|
|
4
|
-
const LDFileUploadList = ({ recommendedSize = { width: 1200, height: 800 }, isExistDescription, fileList, maxFileCount, disabled, ...props }) => (_jsxs(LDFileUpload.Root, { ...props, fileList: fileList, recommendedSize: recommendedSize, maxFileCount: maxFileCount, disabled: disabled, children: [fileList.length < maxFileCount && !disabled && (_jsx(LDFileUpload.Input, {
|
|
4
|
+
const LDFileUploadList = ({ recommendedSize = { width: 1200, height: 800 }, isExistDescription, fileList, maxFileCount, disabled, ...props }) => (_jsxs(LDFileUpload.Root, { ...props, fileList: fileList, recommendedSize: recommendedSize, maxFileCount: maxFileCount, disabled: disabled, children: [fileList.length > 0 && _jsx(LDFileUpload.CardList, {}), fileList.length < maxFileCount && !disabled && (_jsx(LDFileUpload.Input, { type: "attachment" })), isExistDescription && _jsx(LDFileUpload.Description, {})] }));
|
|
5
5
|
export default LDFileUploadList;
|
|
@@ -19,6 +19,8 @@ export interface LDInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>
|
|
|
19
19
|
block?: boolean;
|
|
20
20
|
/** 래퍼(Root div)에 적용할 className */
|
|
21
21
|
rootClassName?: string;
|
|
22
|
+
/** 좌측 부가 요소 (아이콘 등) */
|
|
23
|
+
leftAddon?: ReactNode;
|
|
22
24
|
/** 우측 부가 요소 (아이콘 등) */
|
|
23
25
|
rightAddon?: ReactNode;
|
|
24
26
|
/** 우측 addon 컨테이너 ref (addon 폭·위치 측정용) */
|
|
@@ -28,6 +30,6 @@ export interface LDInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>
|
|
|
28
30
|
/** input 요소 ref */
|
|
29
31
|
ref?: Ref<HTMLInputElement>;
|
|
30
32
|
}
|
|
31
|
-
declare const LDInput: ({ variant, color, size, block, type, disabled, className, rootClassName, style, rightAddon, addonRef, suffix, hiddenPasswordToggle, ref, value, defaultValue, onChange, ...props }: LDInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
declare const LDInput: ({ variant, color, size, block, type, disabled, className, rootClassName, style, leftAddon, rightAddon, addonRef, suffix, hiddenPasswordToggle, ref, value, defaultValue, onChange, ...props }: LDInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
32
34
|
export default LDInput;
|
|
33
35
|
//# sourceMappingURL=LDInput.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LDInput.d.ts","sourceRoot":"","sources":["../../../src/components/LDInput/LDInput.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,SAAS,EACd,KAAK,GAAG,EAIT,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAK7C,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC;AAC3E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;AACvE,MAAM,MAAM,SAAS,GAAG,MAAM,CAC5B,IAAI,EACJ,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAC/D,CAAC;AAKF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAOvD,CAAC;AAIF,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAO/C,CAAC;AAqKF,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,OAAO,GAAG,MAAM,CACjB;IACC,mBAAmB;IACnB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,YAAY;IACZ,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,uBAAuB;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB;IACvB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;IAChC,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,GAAG,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;CAC7B;AAED,QAAA,MAAM,OAAO,GAAI,
|
|
1
|
+
{"version":3,"file":"LDInput.d.ts","sourceRoot":"","sources":["../../../src/components/LDInput/LDInput.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,SAAS,EACd,KAAK,GAAG,EAIT,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAK7C,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC;AAC3E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;AACvE,MAAM,MAAM,SAAS,GAAG,MAAM,CAC5B,IAAI,EACJ,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAC/D,CAAC;AAKF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAOvD,CAAC;AAIF,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAO/C,CAAC;AAqKF,MAAM,WAAW,YAAa,SAAQ,IAAI,CACxC,mBAAmB,CAAC,gBAAgB,CAAC,EACrC,OAAO,GAAG,MAAM,CACjB;IACC,mBAAmB;IACnB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,YAAY;IACZ,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,uBAAuB;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB;IACvB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,uBAAuB;IACvB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;IAChC,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,GAAG,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;CAC7B;AAED,QAAA,MAAM,OAAO,GAAI,+LAoBd,YAAY,4CAqLd,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -172,13 +172,14 @@ const passwordToggleVariants = tv({
|
|
|
172
172
|
disabled: { true: 'cursor-not-allowed', false: 'cursor-pointer' },
|
|
173
173
|
},
|
|
174
174
|
});
|
|
175
|
-
const LDInput = ({ variant, color = 'neutral', size, block = false, type = 'text', disabled = false, className, rootClassName, style, rightAddon, addonRef, suffix, hiddenPasswordToggle = false, ref, value, defaultValue, onChange, ...props }) => {
|
|
175
|
+
const LDInput = ({ variant, color = 'neutral', size, block = false, type = 'text', disabled = false, className, rootClassName, style, leftAddon, rightAddon, addonRef, suffix, hiddenPasswordToggle = false, ref, value, defaultValue, onChange, ...props }) => {
|
|
176
176
|
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
177
177
|
const [inputValue, setInputValue] = useState(() => String(value ?? defaultValue ?? ''));
|
|
178
178
|
const [suffixLeft, setSuffixLeft] = useState(0); // suffix의 최종 left 위치(px)
|
|
179
179
|
const valueRef = useRef(null); // 값 폭 측정용 숨은 span
|
|
180
180
|
const suffixRef = useRef(null); // suffix 자체 폭 측정용
|
|
181
181
|
const rootRef = useRef(null); // Root 래퍼(패딩·폭 측정용, suffix 위치 계산)
|
|
182
|
+
const leftAddonRef = useRef(null); // 좌측 addon 폭 측정용(suffix 위치 보정)
|
|
182
183
|
const isPasswordToggle = !hiddenPasswordToggle && type === 'password';
|
|
183
184
|
const resolvedType = isPasswordToggle
|
|
184
185
|
? passwordVisible
|
|
@@ -206,11 +207,16 @@ const LDInput = ({ variant, color = 'neutral', size, block = false, type = 'text
|
|
|
206
207
|
const cs = getComputedStyle(root);
|
|
207
208
|
const padLeft = parseFloat(cs.paddingLeft) || 0; // "12px" → 12
|
|
208
209
|
const padRight = parseFloat(cs.paddingRight) || 0;
|
|
210
|
+
// leftAddon이 있으면 input 시작점이 addon 폭 + gap만큼 밀린다. suffix도 같이 밀어야 값 뒤에 붙는다.
|
|
211
|
+
const leftAddonEl = leftAddonRef.current;
|
|
212
|
+
const leftOffset = leftAddonEl
|
|
213
|
+
? leftAddonEl.offsetWidth + (parseFloat(cs.columnGap) || 0)
|
|
214
|
+
: 0;
|
|
209
215
|
// 값 뒤에 붙이되, suffix 오른끝이 Root 안쪽 오른 경계를 넘지 않게 clamp.
|
|
210
216
|
// clientWidth = content+padding (border 제외) → 안쪽 폭
|
|
211
217
|
const maxLeft = root.clientWidth - padRight - suffixWidth;
|
|
212
|
-
setSuffixLeft(Math.min(padLeft + valueWidth + SUFFIX_GAP, maxLeft));
|
|
213
|
-
}, [displayedValue, resolvedSize, suffix]);
|
|
218
|
+
setSuffixLeft(Math.min(padLeft + leftOffset + valueWidth + SUFFIX_GAP, maxLeft));
|
|
219
|
+
}, [displayedValue, resolvedSize, suffix, leftAddon]);
|
|
214
220
|
const passwordToggleAddon = isPasswordToggle ? (_jsxs("button", { type: "button", "aria-label": passwordVisible ? '비밀번호 숨기기' : '비밀번호 표시', "aria-pressed": passwordVisible, onClick: () => setPasswordVisible(!passwordVisible), disabled: disabled, style: { width: iconSize, height: iconSize }, className: passwordToggleVariants({ disabled }), children: [_jsx(Eye, { size: iconSize, className: cn('absolute top-1/2 left-1/2 -translate-1/2 ld-transition', passwordVisible ? 'opacity-100' : 'opacity-0') }), _jsx(EyeOff, { size: iconSize, className: cn('absolute top-1/2 left-1/2 -translate-1/2 ld-transition', passwordVisible ? 'opacity-0' : 'opacity-100') })] })) : null;
|
|
215
221
|
const { width, height, minWidth, maxWidth, minHeight, maxHeight, ...fieldStyle } = style ?? {};
|
|
216
222
|
// 텍스트 - addon 간격(gap)은 inline으로 부여(패딩의 절반).
|
|
@@ -236,7 +242,7 @@ const LDInput = ({ variant, color = 'neutral', size, block = false, type = 'text
|
|
|
236
242
|
const hasAddon = Boolean(rightAddon) || Boolean(passwordToggleAddon);
|
|
237
243
|
// label 없이 쓰일 수 있어 접근 가능한 이름을 placeholder/name으로 폴백한다.
|
|
238
244
|
const ariaLabel = props['aria-label'] ?? props.placeholder ?? props.name;
|
|
239
|
-
return (_jsxs(InputBase.Root, { ref: rootRef, block: block, style: rootStyle, className: rootClass, children: [_jsx(InputBase.Field, { ref: ref, type: resolvedType, disabled: disabled, className: fieldClassName, style: fieldStyle, value: value, defaultValue: defaultValue, onChange: (event) => {
|
|
245
|
+
return (_jsxs(InputBase.Root, { ref: rootRef, block: block, style: rootStyle, className: rootClass, children: [leftAddon && (_jsx(InputBase.Addon, { ref: leftAddonRef, style: { gap: addonGap }, className: addonVariants({ size: resolvedSize }), children: leftAddon })), _jsx(InputBase.Field, { ref: ref, type: resolvedType, disabled: disabled, className: fieldClassName, style: fieldStyle, value: value, defaultValue: defaultValue, onChange: (event) => {
|
|
240
246
|
setInputValue(event.target.value);
|
|
241
247
|
onChange?.(event);
|
|
242
248
|
}, ...props, "aria-label": ariaLabel }), suffix && displayedValue && (_jsxs(_Fragment, { children: [_jsx("span", { ref: valueRef, "aria-hidden": "true", className: suffixMeasureVariants({ size: resolvedSize }), children: displayedValue }), _jsx("span", { ref: suffixRef, "aria-hidden": "true", className: suffixVariants({ size: resolvedSize }), style: { left: `${suffixLeft}px` }, children: suffix })] })), hasAddon && (_jsxs(InputBase.Addon, { ref: addonRef, style: { gap: addonGap }, className: addonVariants({ size: resolvedSize }), children: [rightAddon, passwordToggleAddon] }))] }));
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
1
2
|
import { type ModalSize, TModalClassNames } from '../../components/LDModal/LDModalView.js';
|
|
2
3
|
import type { Variant } from '../../types/common.js';
|
|
3
4
|
import type { Subset } from '../../types/subset.js';
|
|
4
5
|
export interface LDModalOptions<T = undefined> {
|
|
5
|
-
name?:
|
|
6
|
-
title?:
|
|
7
|
-
description?:
|
|
8
|
-
content?:
|
|
6
|
+
name?: ReactNode;
|
|
7
|
+
title?: ReactNode;
|
|
8
|
+
description?: ReactNode;
|
|
9
|
+
content?: ReactNode | ((args: {
|
|
9
10
|
onClose: (value?: T) => void;
|
|
10
|
-
}) =>
|
|
11
|
+
}) => ReactNode);
|
|
11
12
|
buttons?: {
|
|
12
13
|
value: T;
|
|
13
14
|
label: string;
|
|
@@ -24,7 +25,7 @@ export interface LDModalOptions<T = undefined> {
|
|
|
24
25
|
/** 모달 패널에 적용할 추가 CSS 클래스 */
|
|
25
26
|
className?: string;
|
|
26
27
|
/** 모달 패널 인라인 스타일 (자유 width/height 등) */
|
|
27
|
-
style?:
|
|
28
|
+
style?: CSSProperties;
|
|
28
29
|
onBeforeClose?: () => Promise<boolean> | boolean;
|
|
29
30
|
classNames?: TModalClassNames;
|
|
30
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LDModal.d.ts","sourceRoot":"","sources":["../../../src/components/LDModal/LDModal.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LDModal.d.ts","sourceRoot":"","sources":["../../../src/components/LDModal/LDModal.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAoB,MAAM,OAAO,CAAC;AAG7E,OAAoB,EAElB,KAAK,SAAS,EACd,gBAAgB,EACjB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,SAAS;IAC3C,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,OAAO,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,CAAA;KAAE,KAAK,SAAS,CAAC,CAAC;IAC9E,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,CAAC,CAAC;QACT,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC;QACzD,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;QACvC,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;KAC1D,EAAE,CAAC;IACJ,eAAe;IACf,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACpC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,cAAc,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACtC,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACjD,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,QAAA,MAAM,OAAO,GAAI,CAAC,GAAG,SAAS,EAC5B,SAAS,cAAc,CAAC,CAAC,CAAC,KACzB,OAAO,CAAC,CAAC,GAAG,SAAS,CA2EvB,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
|
@@ -35,11 +35,11 @@ export interface LDModalViewProps {
|
|
|
35
35
|
/** 열림 상태 변경 시 호출되는 콜백 */
|
|
36
36
|
onOpenChange?: (open: boolean) => void;
|
|
37
37
|
/** 서브 타이틀 (title 위에 표시) */
|
|
38
|
-
name?:
|
|
38
|
+
name?: ReactNode;
|
|
39
39
|
/** 모달 제목 */
|
|
40
|
-
title?:
|
|
40
|
+
title?: ReactNode;
|
|
41
41
|
/** 모달 설명 텍스트 */
|
|
42
|
-
description?:
|
|
42
|
+
description?: ReactNode;
|
|
43
43
|
/** 본문 콘텐츠 */
|
|
44
44
|
children?: ReactNode;
|
|
45
45
|
/** 모달 너비 프리셋 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LDModalView.d.ts","sourceRoot":"","sources":["../../../src/components/LDModal/LDModalView.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"LDModalView.d.ts","sourceRoot":"","sources":["../../../src/components/LDModal/LDModalView.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,OAAO,CAAC;AAK/C,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC7B,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,aAAa;IACb,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,EAAE,CAAC,CAAC;IACT,gBAAgB;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC;IACzD,eAAe;IACf,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvC,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,gBAAgB;IAC/B,yBAAyB;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0BAA0B;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yBAAyB;IACzB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,2BAA2B;IAC3B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY;IACZ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gBAAgB;IAChB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,aAAa;IACb,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,gBAAgB;IAChB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,eAAe;IACf,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,aAAa;IACb,cAAc,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACtC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,oCAAoC;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AA6CD,QAAA,MAAM,WAAW,GAAI,iJAelB,gBAAgB,4CAkLlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -3,6 +3,7 @@ import { Check, CircleAlert, X } from 'lucide-react';
|
|
|
3
3
|
import { useRef } from 'react';
|
|
4
4
|
import LDButton from '../../components/LDButton/LDButton.js';
|
|
5
5
|
import ModalBase from '../../components/LDModal/ModalBase.js';
|
|
6
|
+
import Show from '../../components/Show/Show.js';
|
|
6
7
|
import cn from '../../utils/cn.js';
|
|
7
8
|
// ── 스타일 매핑 ──
|
|
8
9
|
const sizeClasses = {
|
|
@@ -62,10 +63,10 @@ const LDModalView = ({ open, defaultOpen, onOpenChange, name, title, description
|
|
|
62
63
|
overlayRef.current)
|
|
63
64
|
onOpenChange?.(false);
|
|
64
65
|
overlayRef.current = false;
|
|
65
|
-
}, children: _jsxs("div", { className: cn('relative rounded-2xl bg-white p-5', sizeClasses[size], 'mobile:max-w-[calc(100vw-40px)]', scrollBehavior === 'inside' && 'flex max-h-[90dvh] flex-col', classNames?.layout), style: style, onClick: (e) => e.stopPropagation(), children: [_jsx(ModalBase.Close, { asChild: true, className: cn('absolute top-4 right-5 cursor-pointer text-neutral500 outline-hidden', classNames?.close), children: _jsx(LDButton, { variant: "ghost", color: "neutral", leftIcon: _jsx(X, {}), compact: true }) }), _jsxs("div", { className: cn('flex w-full shrink-0 flex-col', scrollBehavior === 'inside' && 'min-h-0 flex-1', layoutGapSizeClasses[size], !hasHeader && noHeaderPaddingTopSizeClasses[size], classNames?.panel), children: [hasHeader
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
}, children: _jsxs("div", { className: cn('relative rounded-2xl bg-white p-5', sizeClasses[size], 'mobile:max-w-[calc(100vw-40px)]', scrollBehavior === 'inside' && 'flex max-h-[90dvh] flex-col', classNames?.layout), style: style, onClick: (e) => e.stopPropagation(), children: [_jsx(ModalBase.Close, { asChild: true, className: cn('absolute top-4 right-5 cursor-pointer text-neutral500 outline-hidden', classNames?.close), children: _jsx(LDButton, { variant: "ghost", color: "neutral", leftIcon: _jsx(X, {}), compact: true }) }), _jsxs("div", { className: cn('flex w-full shrink-0 flex-col', scrollBehavior === 'inside' && 'min-h-0 flex-1', layoutGapSizeClasses[size], !hasHeader && noHeaderPaddingTopSizeClasses[size], classNames?.panel), children: [_jsx(Show, { when: hasHeader, children: _jsxs("div", { className: "flex w-full shrink-0 flex-col items-center gap-5 py-5 text-center", children: [_jsx(Show, { when: icon === 'success', children: _jsx("div", { className: cn('flex items-center justify-center rounded-full bg-hyper-blue', statusIconSizeClasses[size]), children: _jsx(Check, { className: "text-white" }) }) }), _jsx(Show, { when: icon === 'fail', children: _jsx("div", { className: cn('flex items-center justify-center rounded-full bg-danger600', statusIconSizeClasses[size]), children: _jsx(CircleAlert, { className: "text-white" }) }) }), _jsx(Show, { when: name, children: _jsx("span", { className: "display16 bold whitespace-pre-wrap text-neutral500", children: name }) }), _jsx(Show, { when: title, children: _jsx(ModalBase.Title, { className: cn('text-center display28 bold whitespace-pre-wrap', classNames?.title), children: title }) }), _jsx(Show, { when: description, children: _jsx(ModalBase.Description, { className: cn('text-center display14 whitespace-pre-wrap text-neutral500', classNames?.description), children: description }) })] }) }), _jsx(Show, { when: children, children: _jsx("div", { className: cn(scrollBehavior === 'inside' &&
|
|
67
|
+
'min-h-10 flex-1 overflow-y-auto'), children: children }) }), _jsx(Show, { when: buttons?.list?.length, children: _jsx("div", { className: cn('flex mobile:flex-1 mobile:flex-col mobile:items-center', buttons?.direction === 'column' ? 'flex-col' : 'flex-row', buttonsGapSizeClasses[size]), children: buttons?.list?.map((button, index) => {
|
|
68
|
+
const isFirst = index === 0;
|
|
69
|
+
return (_jsx("div", { className: "flex w-full flex-1", children: _jsx(LDButton, { type: "button", onClick: () => handleClick(button), variant: button.variant ?? (isFirst ? 'solid' : 'outline'), color: button.color ?? (isFirst ? 'point' : 'neutral'), size: buttonSizeClasses[size], block: true, children: button.label }) }, String(button.value)));
|
|
70
|
+
}) }) })] })] }) })] }));
|
|
70
71
|
};
|
|
71
72
|
export default LDModalView;
|
|
@@ -2,13 +2,13 @@ import type React from 'react';
|
|
|
2
2
|
import { type RadioSize } from '../../components/LDRadio/LDRadio.js';
|
|
3
3
|
export interface RadioGroupOption {
|
|
4
4
|
label: string;
|
|
5
|
-
value: string;
|
|
5
|
+
value: string | boolean;
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
}
|
|
8
8
|
export interface LDRadioGroupProps {
|
|
9
9
|
items: RadioGroupOption[];
|
|
10
|
-
value?: string;
|
|
11
|
-
onChange?: (value: string) => void;
|
|
10
|
+
value?: string | boolean;
|
|
11
|
+
onChange?: (value: string | boolean) => void;
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
type?: 'default' | 'pill' | 'circle';
|
|
14
14
|
size?: RadioSize;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LDRadioGroup.d.ts","sourceRoot":"","sources":["../../../src/components/LDRadio/LDRadioGroup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAIvE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"LDRadioGroup.d.ts","sourceRoot":"","sources":["../../../src/components/LDRadio/LDRadioGroup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAIvE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAaD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IACrC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,QAAA,MAAM,YAAY,GAAI,0JAanB,iBAAiB,4CA4BnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -11,6 +11,6 @@ const groupGapClasses = {
|
|
|
11
11
|
xlarge: 'gap-lg',
|
|
12
12
|
};
|
|
13
13
|
const LDRadioGroup = ({ items, value, onChange, disabled = false, type = 'pill', size = 'small', name, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedby, orientation = 'horizontal', className, style, }) => {
|
|
14
|
-
return (_jsx("div", { role: "radiogroup", "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, className: cn('flex', groupGapClasses[size], orientation === 'vertical' ? 'flex-col' : 'flex-row'), style: style, children: items.map((option) => (_jsx(LDRadio, { name: name, type: type, size: size, checked: value === option.value, className: className, onCheckedChange: () => onChange?.(option.value), label: option.label, disabled: disabled || option.disabled }, option.value))) }));
|
|
14
|
+
return (_jsx("div", { role: "radiogroup", "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, className: cn('flex', groupGapClasses[size], orientation === 'vertical' ? 'flex-col' : 'flex-row'), style: style, children: items.map((option) => (_jsx(LDRadio, { name: name, type: type, size: size, checked: value === option.value, className: className, onCheckedChange: () => onChange?.(option.value), label: option.label, disabled: disabled || option.disabled }, String(option.value)))) }));
|
|
15
15
|
};
|
|
16
16
|
export default LDRadioGroup;
|
|
@@ -136,7 +136,7 @@ const LDMultiSelect = ({ items, placeholder = '선택해주세요', value = [],
|
|
|
136
136
|
size,
|
|
137
137
|
disabled,
|
|
138
138
|
className,
|
|
139
|
-
}), children: truncate ? (_jsxs("div", { className: "flex w-full items-center justify-center gap-sm px-3 py-1.5", children: [_jsx(SelectBase.Value, { placeholder: placeholder, className: valueVariants({ align }), placeholderClassName: "text-
|
|
139
|
+
}), children: truncate ? (_jsxs("div", { className: "flex w-full items-center justify-center gap-sm px-3 py-1.5", children: [_jsx(SelectBase.Value, { placeholder: placeholder, className: valueVariants({ align }), placeholderClassName: "text-neutral300" }), chevron] })) : (_jsxs("div", { className: "flex w-full items-center justify-between gap-sm px-3 py-1.5", children: [_jsx("div", { className: chipListVariants({ align }), children: value.length === 0 ? (_jsx("span", { className: "text-neutral300", children: placeholder })) : (value.map((v) => {
|
|
140
140
|
const label = items.find((i) => i.value === v)?.label || v;
|
|
141
141
|
return (_jsxs("span", { className: "flex animate-fade-in items-center justify-between rounded-sm border border-neutral100 bg-white px-2 py-1 display12 text-neutral900", children: [label, _jsx("div", { role: "button", "aria-label": `${label} 삭제`, onClick: (e) => {
|
|
142
142
|
e.stopPropagation();
|
|
@@ -129,7 +129,7 @@ const Trigger = ({ className, ...props }) => {
|
|
|
129
129
|
/** 현재 선택값을 표시하는 Styled Compound 영역입니다. */
|
|
130
130
|
const Value = ({ className, ...props }) => {
|
|
131
131
|
const { align } = useContext(LDSelectStyleContext);
|
|
132
|
-
return (_jsx(SelectBase.Value, { ...props, className: valueVariants({ align, className }), placeholderClassName: cn('text-
|
|
132
|
+
return (_jsx(SelectBase.Value, { ...props, className: valueVariants({ align, className }), placeholderClassName: cn('text-neutral300', props.placeholderClassName) }));
|
|
133
133
|
};
|
|
134
134
|
/** 옵션 목록을 표시하는 Styled Compound 영역입니다. */
|
|
135
135
|
const Content = ({ className, openClassName = 'opacity-100', closedClassName = 'pointer-events-none opacity-0', ...props }) => (_jsx(SelectBase.Content, { ...props, className: cn('mt-1 scrollbar-hide flex max-h-57.5 w-full flex-col gap-3xs overflow-y-auto rounded-[5px] border border-mono100 bg-white p-2 transition-opacity duration-300 ease-out', className), openClassName: openClassName, closedClassName: closedClassName }));
|
|
@@ -12,10 +12,10 @@ const Toolbar = ({ title, totalCount, rightSideContents, className, }) => {
|
|
|
12
12
|
// ── Styled Sub-components ──
|
|
13
13
|
const Header = ({ className, children, ...props }) => (_jsx(TableBase.Header, { className: cn('h-9 border-b border-neutral700 bg-mono100 ld-transition hover:bg-mono200', className), ...props, children: children }));
|
|
14
14
|
const HeaderRow = ({ className, children, ...props }) => (_jsx(TableBase.HeaderRow, { className: className, ...props, children: children }));
|
|
15
|
-
const HeaderCell = ({ className, children, ...props }) => (_jsx(TableBase.HeaderCell, { className: cn('px-5 display12
|
|
15
|
+
const HeaderCell = ({ className, children, ...props }) => (_jsx(TableBase.HeaderCell, { className: cn('px-5 display14 bold tablet-down:display12', className), ...props, children: children }));
|
|
16
16
|
const Body = ({ className, children, ...props }) => (_jsx(TableBase.Body, { className: className, ...props, children: children }));
|
|
17
17
|
const Row = ({ className, children, ...props }) => (_jsx(TableBase.Row, { className: cn('h-13 border-b border-neutral100 ld-transition hover:bg-mono50', className), ...props, children: children }));
|
|
18
|
-
const Cell = ({ className, children, ...props }) => (_jsx(TableBase.Cell, { className: cn('px-5
|
|
18
|
+
const Cell = ({ className, children, ...props }) => (_jsx(TableBase.Cell, { className: cn('px-5 display16 regular tablet-down:display14', className), ...props, children: children }));
|
|
19
19
|
LDTable.Header = Header;
|
|
20
20
|
LDTable.HeaderRow = HeaderRow;
|
|
21
21
|
LDTable.HeaderCell = HeaderCell;
|
|
@@ -53,15 +53,15 @@ const fieldVariants = tv({
|
|
|
53
53
|
],
|
|
54
54
|
});
|
|
55
55
|
const textareaVariants = tv({
|
|
56
|
-
base: 'w-full bg-transparent px-1 py-0 outline-none placeholder:text-
|
|
56
|
+
base: 'w-full bg-transparent px-1 py-0 outline-none placeholder:text-neutral300 read-only:cursor-default disabled:cursor-not-allowed disabled:text-mono400',
|
|
57
57
|
variants: {
|
|
58
58
|
size: {
|
|
59
|
-
'2xsmall': 'display10',
|
|
60
|
-
xsmall: 'display12',
|
|
61
|
-
small: 'display14',
|
|
62
|
-
medium: 'display16',
|
|
63
|
-
large: 'display18',
|
|
64
|
-
xlarge: 'display20',
|
|
59
|
+
'2xsmall': 'display10 leading-relaxed',
|
|
60
|
+
xsmall: 'display12 leading-relaxed',
|
|
61
|
+
small: 'display14 leading-relaxed',
|
|
62
|
+
medium: 'display16 leading-relaxed',
|
|
63
|
+
large: 'display18 leading-relaxed',
|
|
64
|
+
xlarge: 'display20 leading-relaxed',
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
});
|
|
@@ -87,7 +87,7 @@ const LDTimePicker = ({ value, defaultValue, onChange, placeholder = 'HH:MM', di
|
|
|
87
87
|
e.preventDefault();
|
|
88
88
|
setOpen(true);
|
|
89
89
|
}
|
|
90
|
-
}, placeholder: placeholder, "aria-label": "\uC2DC\uAC04 \uC785\uB825", disabled: disabled, readOnly: !isEditable, className: cn('absolute inset-0 w-full flex-1 rounded-[5px] bg-transparent px-4 outline-none', !resolved && 'text-
|
|
90
|
+
}, placeholder: placeholder, "aria-label": "\uC2DC\uAC04 \uC785\uB825", disabled: disabled, readOnly: !isEditable, className: cn('absolute inset-0 w-full flex-1 rounded-[5px] bg-transparent px-4 outline-none', !resolved && 'text-neutral300', !isEditable && 'cursor-pointer') }) }), _jsxs("div", { className: "z-10 flex shrink-0 items-center gap-2", onClick: () => {
|
|
91
91
|
if (disabled)
|
|
92
92
|
return;
|
|
93
93
|
setOpen((o) => !o);
|