@simplysm/solid 13.0.53 → 13.0.56
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/README.md +6 -2
- package/dist/components/data/crud-detail/CrudDetail.d.ts +14 -0
- package/dist/components/data/crud-detail/CrudDetail.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetail.js +348 -0
- package/dist/components/data/crud-detail/CrudDetail.js.map +6 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.d.ts +7 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.js +14 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.js.map +6 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.d.ts +7 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.js +14 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.js.map +6 -0
- package/dist/components/data/crud-detail/CrudDetailTools.d.ts +7 -0
- package/dist/components/data/crud-detail/CrudDetailTools.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetailTools.js +14 -0
- package/dist/components/data/crud-detail/CrudDetailTools.js.map +6 -0
- package/dist/components/data/crud-detail/types.d.ts +45 -0
- package/dist/components/data/crud-detail/types.d.ts.map +1 -0
- package/dist/components/data/crud-detail/types.js +1 -0
- package/dist/components/data/crud-detail/types.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheet.d.ts +17 -0
- package/dist/components/data/crud-sheet/CrudSheet.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheet.js +679 -0
- package/dist/components/data/crud-sheet/CrudSheet.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.d.ts +5 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.js +29 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.d.ts +7 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.js +14 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.d.ts +7 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.js +14 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.d.ts +7 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.js +14 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.js.map +6 -0
- package/dist/components/data/crud-sheet/types.d.ts +109 -0
- package/dist/components/data/crud-sheet/types.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/types.js +1 -0
- package/dist/components/data/crud-sheet/types.js.map +6 -0
- package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
- package/dist/components/data/kanban/Kanban.js +137 -138
- package/dist/components/data/kanban/Kanban.js.map +2 -2
- package/dist/components/data/kanban/KanbanContext.d.ts +5 -1
- package/dist/components/data/kanban/KanbanContext.d.ts.map +1 -1
- package/dist/components/data/kanban/KanbanContext.js.map +1 -1
- package/dist/components/data/list/ListItem.d.ts.map +1 -1
- package/dist/components/data/list/ListItem.js +109 -99
- package/dist/components/data/list/ListItem.js.map +2 -2
- package/dist/components/data/sheet/DataSheet.css +28 -10
- package/dist/components/data/sheet/DataSheet.js +1 -1
- package/dist/components/data/sheet/DataSheet.js.map +2 -2
- package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.styles.js +1 -1
- package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
- package/dist/components/disclosure/Dialog.d.ts +16 -10
- package/dist/components/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +126 -91
- package/dist/components/disclosure/Dialog.js.map +2 -2
- package/dist/components/disclosure/DialogContext.d.ts +2 -4
- package/dist/components/disclosure/DialogContext.d.ts.map +1 -1
- package/dist/components/disclosure/DialogContext.js.map +1 -1
- package/dist/components/disclosure/DialogProvider.d.ts.map +1 -1
- package/dist/components/disclosure/DialogProvider.js +14 -9
- package/dist/components/disclosure/DialogProvider.js.map +2 -2
- package/dist/components/disclosure/Dropdown.d.ts +46 -22
- package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
- package/dist/components/disclosure/Dropdown.js +100 -65
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/feedback/notification/NotificationBanner.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationBanner.js +3 -3
- package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
- package/dist/components/feedback/notification/NotificationBell.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationBell.js +84 -84
- package/dist/components/feedback/notification/NotificationBell.js.map +2 -2
- package/dist/components/form-control/Invalid.js +1 -1
- package/dist/components/form-control/combobox/Combobox.d.ts +6 -3
- package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
- package/dist/components/form-control/combobox/Combobox.js +150 -168
- package/dist/components/form-control/combobox/Combobox.js.map +2 -2
- package/dist/components/form-control/combobox/ComboboxContext.d.ts +3 -0
- package/dist/components/form-control/combobox/ComboboxContext.d.ts.map +1 -1
- package/dist/components/form-control/combobox/ComboboxContext.js.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +0 -2
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.js +9 -17
- package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
- package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DatePicker.js +3 -2
- package/dist/components/form-control/field/DatePicker.js.map +2 -2
- package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DateTimePicker.js +3 -2
- package/dist/components/form-control/field/DateTimePicker.js.map +2 -2
- package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
- package/dist/components/form-control/field/Field.styles.js +2 -1
- package/dist/components/form-control/field/Field.styles.js.map +1 -1
- package/dist/components/form-control/field/NumberInput.d.ts +15 -5
- package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
- package/dist/components/form-control/field/NumberInput.js +181 -141
- package/dist/components/form-control/field/NumberInput.js.map +2 -2
- package/dist/components/form-control/field/TextInput.d.ts +9 -5
- package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
- package/dist/components/form-control/field/TextInput.js +199 -154
- package/dist/components/form-control/field/TextInput.js.map +2 -2
- package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/TimePicker.js +3 -2
- package/dist/components/form-control/field/TimePicker.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts +3 -3
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +116 -100
- package/dist/components/form-control/select/Select.js.map +2 -2
- package/dist/components/form-control/select/SelectContext.d.ts +9 -1
- package/dist/components/form-control/select/SelectContext.d.ts.map +1 -1
- package/dist/components/form-control/select/SelectContext.js.map +1 -1
- package/dist/components/form-control/select/SelectItem.d.ts.map +1 -1
- package/dist/components/form-control/select/SelectItem.js +77 -67
- package/dist/components/form-control/select/SelectItem.js.map +2 -2
- package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
- package/dist/components/form-control/state-preset/StatePreset.js +1 -1
- package/dist/components/form-control/state-preset/StatePreset.js.map +1 -1
- package/dist/components/layout/topbar/Topbar.d.ts +2 -0
- package/dist/components/layout/topbar/Topbar.d.ts.map +1 -1
- package/dist/components/layout/topbar/Topbar.js +2 -0
- package/dist/components/layout/topbar/Topbar.js.map +2 -2
- package/dist/components/layout/topbar/TopbarActions.d.ts +3 -0
- package/dist/components/layout/topbar/TopbarActions.d.ts.map +1 -0
- package/dist/components/layout/topbar/TopbarActions.js +17 -0
- package/dist/components/layout/topbar/TopbarActions.js.map +6 -0
- package/dist/components/layout/topbar/TopbarContainer.d.ts +1 -1
- package/dist/components/layout/topbar/TopbarContainer.d.ts.map +1 -1
- package/dist/components/layout/topbar/TopbarContainer.js +21 -12
- package/dist/components/layout/topbar/TopbarContainer.js.map +2 -2
- package/dist/components/layout/topbar/TopbarContext.d.ts +9 -0
- package/dist/components/layout/topbar/TopbarContext.d.ts.map +1 -0
- package/dist/components/layout/topbar/TopbarContext.js +29 -0
- package/dist/components/layout/topbar/TopbarContext.js.map +6 -0
- package/dist/components/layout/topbar/TopbarMenu.d.ts.map +1 -1
- package/dist/components/layout/topbar/TopbarMenu.js +63 -57
- package/dist/components/layout/topbar/TopbarMenu.js.map +2 -2
- package/dist/components/layout/topbar/TopbarUser.d.ts.map +1 -1
- package/dist/components/layout/topbar/TopbarUser.js +53 -54
- package/dist/components/layout/topbar/TopbarUser.js.map +2 -2
- package/dist/hooks/createControllableStore.d.ts +29 -0
- package/dist/hooks/createControllableStore.d.ts.map +1 -0
- package/dist/hooks/createControllableStore.js +19 -0
- package/dist/hooks/createControllableStore.js.map +6 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/styles/patterns.styles.d.ts.map +1 -1
- package/dist/styles/patterns.styles.js +7 -1
- package/dist/styles/patterns.styles.js.map +1 -1
- package/docs/data-components.md +428 -0
- package/docs/disclosure.md +65 -35
- package/docs/form-controls.md +18 -3
- package/docs/helpers.md +0 -39
- package/docs/hooks.md +39 -0
- package/docs/layout.md +70 -1
- package/package.json +4 -3
- package/src/components/data/crud-detail/CrudDetail.tsx +346 -0
- package/src/components/data/crud-detail/CrudDetailAfter.tsx +19 -0
- package/src/components/data/crud-detail/CrudDetailBefore.tsx +19 -0
- package/src/components/data/crud-detail/CrudDetailTools.tsx +19 -0
- package/src/components/data/crud-detail/types.ts +58 -0
- package/src/components/data/crud-sheet/CrudSheet.tsx +628 -0
- package/src/components/data/crud-sheet/CrudSheetColumn.tsx +34 -0
- package/src/components/data/crud-sheet/CrudSheetFilter.tsx +21 -0
- package/src/components/data/crud-sheet/CrudSheetHeader.tsx +19 -0
- package/src/components/data/crud-sheet/CrudSheetTools.tsx +21 -0
- package/src/components/data/crud-sheet/types.ts +133 -0
- package/src/components/data/kanban/Kanban.tsx +72 -65
- package/src/components/data/kanban/KanbanContext.ts +7 -1
- package/src/components/data/list/ListItem.tsx +31 -18
- package/src/components/data/sheet/DataSheet.css +28 -10
- package/src/components/data/sheet/DataSheet.styles.ts +1 -1
- package/src/components/data/sheet/DataSheet.tsx +1 -1
- package/src/components/disclosure/Dialog.tsx +143 -105
- package/src/components/disclosure/DialogContext.ts +2 -4
- package/src/components/disclosure/DialogProvider.tsx +4 -2
- package/src/components/disclosure/Dropdown.tsx +174 -86
- package/src/components/feedback/notification/NotificationBanner.tsx +3 -9
- package/src/components/feedback/notification/NotificationBell.tsx +51 -57
- package/src/components/form-control/Invalid.tsx +1 -1
- package/src/components/form-control/combobox/Combobox.tsx +109 -133
- package/src/components/form-control/combobox/ComboboxContext.ts +4 -1
- package/src/components/form-control/date-range-picker/DateRangePicker.tsx +6 -16
- package/src/components/form-control/field/DatePicker.tsx +4 -1
- package/src/components/form-control/field/DateTimePicker.tsx +3 -0
- package/src/components/form-control/field/Field.styles.ts +1 -0
- package/src/components/form-control/field/NumberInput.tsx +131 -86
- package/src/components/form-control/field/TextInput.tsx +139 -88
- package/src/components/form-control/field/TimePicker.tsx +3 -0
- package/src/components/form-control/select/Select.tsx +85 -67
- package/src/components/form-control/select/SelectContext.ts +12 -1
- package/src/components/form-control/select/SelectItem.tsx +39 -18
- package/src/components/form-control/state-preset/StatePreset.tsx +1 -0
- package/src/components/layout/topbar/Topbar.tsx +3 -0
- package/src/components/layout/topbar/TopbarActions.tsx +8 -0
- package/src/components/layout/topbar/TopbarContainer.tsx +9 -5
- package/src/components/layout/topbar/TopbarContext.ts +36 -0
- package/src/components/layout/topbar/TopbarMenu.tsx +52 -55
- package/src/components/layout/topbar/TopbarUser.tsx +28 -31
- package/src/hooks/createControllableStore.ts +47 -0
- package/src/index.ts +6 -1
- package/src/styles/patterns.styles.ts +7 -1
- package/tailwind.css +4 -0
- package/dist/helpers/splitSlots.d.ts +0 -25
- package/dist/helpers/splitSlots.d.ts.map +0 -1
- package/dist/helpers/splitSlots.js +0 -25
- package/dist/helpers/splitSlots.js.map +0 -6
- package/dist/hooks/createItemTemplate.d.ts +0 -17
- package/dist/hooks/createItemTemplate.d.ts.map +0 -1
- package/dist/hooks/createItemTemplate.js +0 -40
- package/dist/hooks/createItemTemplate.js.map +0 -6
- package/src/helpers/splitSlots.ts +0 -51
- package/src/hooks/createItemTemplate.tsx +0 -42
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
createContext,
|
|
3
3
|
createEffect,
|
|
4
4
|
createMemo,
|
|
5
|
+
createSignal,
|
|
5
6
|
type JSX,
|
|
7
|
+
onCleanup,
|
|
8
|
+
type ParentComponent,
|
|
6
9
|
Show,
|
|
7
10
|
splitProps,
|
|
8
|
-
|
|
11
|
+
useContext,
|
|
9
12
|
} from "solid-js";
|
|
10
13
|
import clsx from "clsx";
|
|
11
14
|
import { twMerge } from "tailwind-merge";
|
|
12
|
-
import type { IconProps as TablerIconProps } from "@tabler/icons-solidjs";
|
|
13
15
|
import { createControllableSignal } from "../../../hooks/createControllableSignal";
|
|
14
16
|
import {
|
|
15
17
|
type FieldSize,
|
|
@@ -17,7 +19,6 @@ import {
|
|
|
17
19
|
fieldGapClasses,
|
|
18
20
|
getFieldWrapperClass,
|
|
19
21
|
} from "./Field.styles";
|
|
20
|
-
import { Icon } from "../../display/Icon";
|
|
21
22
|
import { PlaceholderFallback } from "./FieldPlaceholder";
|
|
22
23
|
import { Invalid } from "../../form-control/Invalid";
|
|
23
24
|
|
|
@@ -29,6 +30,22 @@ const numberInputClass = clsx(
|
|
|
29
30
|
"[&::-webkit-inner-spin-button]:appearance-none",
|
|
30
31
|
);
|
|
31
32
|
|
|
33
|
+
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
34
|
+
|
|
35
|
+
interface NumberInputSlotsContextValue {
|
|
36
|
+
setPrefix: (content: SlotAccessor) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const NumberInputSlotsContext = createContext<NumberInputSlotsContextValue>();
|
|
40
|
+
|
|
41
|
+
const NumberInputPrefix: ParentComponent = (props) => {
|
|
42
|
+
const ctx = useContext(NumberInputSlotsContext)!;
|
|
43
|
+
// eslint-disable-next-line solid/reactivity -- slot accessor: children은 렌더 시점에 lazy 평가됨
|
|
44
|
+
ctx.setPrefix(() => props.children);
|
|
45
|
+
onCleanup(() => ctx.setPrefix(undefined));
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
|
|
32
49
|
export interface NumberInputProps {
|
|
33
50
|
/** 입력 값 */
|
|
34
51
|
value?: number;
|
|
@@ -66,9 +83,6 @@ export interface NumberInputProps {
|
|
|
66
83
|
/** 커스텀 style */
|
|
67
84
|
style?: JSX.CSSProperties;
|
|
68
85
|
|
|
69
|
-
/** 접두 아이콘 */
|
|
70
|
-
prefixIcon?: Component<TablerIconProps>;
|
|
71
|
-
|
|
72
86
|
/** 필수 입력 여부 */
|
|
73
87
|
required?: boolean;
|
|
74
88
|
|
|
@@ -83,6 +97,9 @@ export interface NumberInputProps {
|
|
|
83
97
|
|
|
84
98
|
/** touchMode: 포커스 해제 후에만 에러 표시 */
|
|
85
99
|
touchMode?: boolean;
|
|
100
|
+
|
|
101
|
+
/** 자식 요소 (Prefix 슬롯 등) */
|
|
102
|
+
children?: JSX.Element;
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
/**
|
|
@@ -161,6 +178,11 @@ function isValidNumberInput(str: string): boolean {
|
|
|
161
178
|
return /^-?\d*\.?\d*$/.test(cleanStr);
|
|
162
179
|
}
|
|
163
180
|
|
|
181
|
+
interface NumberInputComponent {
|
|
182
|
+
(props: NumberInputProps): JSX.Element;
|
|
183
|
+
Prefix: typeof NumberInputPrefix;
|
|
184
|
+
}
|
|
185
|
+
|
|
164
186
|
/**
|
|
165
187
|
* NumberInput 컴포넌트
|
|
166
188
|
*
|
|
@@ -174,9 +196,14 @@ function isValidNumberInput(str: string): boolean {
|
|
|
174
196
|
*
|
|
175
197
|
* // 최소 소수점 자릿수 지정
|
|
176
198
|
* <NumberInput value={price()} minDigits={2} />
|
|
199
|
+
*
|
|
200
|
+
* // Prefix 슬롯
|
|
201
|
+
* <NumberInput value={price()}>
|
|
202
|
+
* <NumberInput.Prefix>₩</NumberInput.Prefix>
|
|
203
|
+
* </NumberInput>
|
|
177
204
|
* ```
|
|
178
205
|
*/
|
|
179
|
-
export const NumberInput:
|
|
206
|
+
export const NumberInput: NumberInputComponent = (props) => {
|
|
180
207
|
const [local, rest] = splitProps(props, [
|
|
181
208
|
"value",
|
|
182
209
|
"onValueChange",
|
|
@@ -188,7 +215,6 @@ export const NumberInput: Component<NumberInputProps> = (props) => {
|
|
|
188
215
|
"readonly",
|
|
189
216
|
"size",
|
|
190
217
|
"inset",
|
|
191
|
-
"prefixIcon",
|
|
192
218
|
"required",
|
|
193
219
|
"min",
|
|
194
220
|
"max",
|
|
@@ -196,6 +222,7 @@ export const NumberInput: Component<NumberInputProps> = (props) => {
|
|
|
196
222
|
"touchMode",
|
|
197
223
|
"class",
|
|
198
224
|
"style",
|
|
225
|
+
"children",
|
|
199
226
|
]);
|
|
200
227
|
|
|
201
228
|
// 입력 중인 상태를 추적하기 위한 내부 문자열 상태
|
|
@@ -208,6 +235,10 @@ export const NumberInput: Component<NumberInputProps> = (props) => {
|
|
|
208
235
|
onChange: () => local.onValueChange,
|
|
209
236
|
});
|
|
210
237
|
|
|
238
|
+
const [prefix, _setPrefix] = createSignal<SlotAccessor>();
|
|
239
|
+
const setPrefix = (content: SlotAccessor) => _setPrefix(() => content);
|
|
240
|
+
const prefixEl = () => prefix() !== undefined;
|
|
241
|
+
|
|
211
242
|
// 외부 값 변경 시 입력 문자열 동기화
|
|
212
243
|
createEffect(() => {
|
|
213
244
|
const val = value();
|
|
@@ -269,17 +300,11 @@ export const NumberInput: Component<NumberInputProps> = (props) => {
|
|
|
269
300
|
disabled: local.disabled,
|
|
270
301
|
inset: local.inset,
|
|
271
302
|
includeCustomClass: includeCustomClass && local.class,
|
|
272
|
-
extra:
|
|
303
|
+
extra: prefixEl() && fieldGapClasses[local.size ?? "default"],
|
|
273
304
|
});
|
|
274
305
|
|
|
275
306
|
const isEditable = () => !local.disabled && !local.readonly;
|
|
276
307
|
|
|
277
|
-
const prefixIconEl = () => (
|
|
278
|
-
<Show when={local.prefixIcon}>
|
|
279
|
-
<Icon icon={local.prefixIcon!} class="shrink-0 opacity-70" />
|
|
280
|
-
</Show>
|
|
281
|
-
);
|
|
282
|
-
|
|
283
308
|
// 유효성 검사 메시지 (순서대로 검사, 최초 실패 메시지 반환)
|
|
284
309
|
const errorMsg = createMemo(() => {
|
|
285
310
|
const v = value();
|
|
@@ -292,83 +317,103 @@ export const NumberInput: Component<NumberInputProps> = (props) => {
|
|
|
292
317
|
});
|
|
293
318
|
|
|
294
319
|
return (
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
320
|
+
<NumberInputSlotsContext.Provider value={{ setPrefix }}>
|
|
321
|
+
{local.children}
|
|
322
|
+
<Invalid
|
|
323
|
+
message={errorMsg()}
|
|
324
|
+
variant={local.inset ? "dot" : "border"}
|
|
325
|
+
touchMode={local.touchMode}
|
|
326
|
+
>
|
|
327
|
+
<Show
|
|
328
|
+
when={local.inset}
|
|
329
|
+
fallback={
|
|
330
|
+
// standalone 모드: 기존 Show 패턴 유지
|
|
331
|
+
<Show
|
|
332
|
+
when={isEditable()}
|
|
333
|
+
fallback={
|
|
334
|
+
<div
|
|
335
|
+
{...rest}
|
|
336
|
+
data-number-field
|
|
337
|
+
class={twMerge(getWrapperClass(true), "sd-number-field", "justify-end")}
|
|
338
|
+
style={local.style}
|
|
339
|
+
title={local.title}
|
|
340
|
+
>
|
|
341
|
+
<Show when={prefix()}>
|
|
342
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
343
|
+
</Show>
|
|
344
|
+
<PlaceholderFallback
|
|
345
|
+
value={formatNumber(value(), local.comma ?? true, local.minDigits)}
|
|
346
|
+
placeholder={local.placeholder}
|
|
347
|
+
/>
|
|
348
|
+
</div>
|
|
349
|
+
}
|
|
350
|
+
>
|
|
351
|
+
<div {...rest} data-number-field class={getWrapperClass(true)} style={local.style}>
|
|
352
|
+
<Show when={prefix()}>
|
|
353
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
354
|
+
</Show>
|
|
355
|
+
<input
|
|
356
|
+
type="text"
|
|
357
|
+
inputmode="numeric"
|
|
358
|
+
class={numberInputClass}
|
|
359
|
+
value={displayValue()}
|
|
317
360
|
placeholder={local.placeholder}
|
|
361
|
+
title={local.title}
|
|
362
|
+
autocomplete="one-time-code"
|
|
363
|
+
onInput={handleInput}
|
|
364
|
+
onFocus={handleFocus}
|
|
365
|
+
onBlur={handleBlur}
|
|
318
366
|
/>
|
|
319
367
|
</div>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
<input
|
|
325
|
-
type="text"
|
|
326
|
-
inputmode="numeric"
|
|
327
|
-
class={numberInputClass}
|
|
328
|
-
value={displayValue()}
|
|
329
|
-
placeholder={local.placeholder}
|
|
330
|
-
title={local.title}
|
|
331
|
-
onInput={handleInput}
|
|
332
|
-
onFocus={handleFocus}
|
|
333
|
-
onBlur={handleBlur}
|
|
334
|
-
/>
|
|
335
|
-
</div>
|
|
336
|
-
</Show>
|
|
337
|
-
}
|
|
338
|
-
>
|
|
339
|
-
{/* inset 모드: dual-element overlay 패턴 */}
|
|
340
|
-
<div {...rest} data-number-field class={clsx("relative", local.class)} style={local.style}>
|
|
368
|
+
</Show>
|
|
369
|
+
}
|
|
370
|
+
>
|
|
371
|
+
{/* inset 모드: dual-element overlay 패턴 */}
|
|
341
372
|
<div
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
373
|
+
{...rest}
|
|
374
|
+
data-number-field
|
|
375
|
+
class={clsx("relative", local.class)}
|
|
376
|
+
style={local.style}
|
|
346
377
|
>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
type="text"
|
|
359
|
-
inputmode="numeric"
|
|
360
|
-
class={numberInputClass}
|
|
361
|
-
value={displayValue()}
|
|
378
|
+
<div
|
|
379
|
+
data-number-field-content
|
|
380
|
+
class={twMerge(getWrapperClass(false), "justify-end")}
|
|
381
|
+
style={{ visibility: isEditable() ? "hidden" : undefined }}
|
|
382
|
+
title={local.title}
|
|
383
|
+
>
|
|
384
|
+
<Show when={prefix()}>
|
|
385
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
386
|
+
</Show>
|
|
387
|
+
<PlaceholderFallback
|
|
388
|
+
value={formatNumber(value(), local.comma ?? true, local.minDigits)}
|
|
362
389
|
placeholder={local.placeholder}
|
|
363
|
-
title={local.title}
|
|
364
|
-
onInput={handleInput}
|
|
365
|
-
onFocus={handleFocus}
|
|
366
|
-
onBlur={handleBlur}
|
|
367
390
|
/>
|
|
368
391
|
</div>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
392
|
+
|
|
393
|
+
<Show when={isEditable()}>
|
|
394
|
+
<div class={twMerge(getWrapperClass(false), "absolute left-0 top-0 size-full")}>
|
|
395
|
+
<Show when={prefix()}>
|
|
396
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
397
|
+
</Show>
|
|
398
|
+
<input
|
|
399
|
+
type="text"
|
|
400
|
+
inputmode="numeric"
|
|
401
|
+
class={numberInputClass}
|
|
402
|
+
value={displayValue()}
|
|
403
|
+
placeholder={local.placeholder}
|
|
404
|
+
title={local.title}
|
|
405
|
+
autocomplete="one-time-code"
|
|
406
|
+
onInput={handleInput}
|
|
407
|
+
onFocus={handleFocus}
|
|
408
|
+
onBlur={handleBlur}
|
|
409
|
+
/>
|
|
410
|
+
</div>
|
|
411
|
+
</Show>
|
|
412
|
+
</div>
|
|
413
|
+
</Show>
|
|
414
|
+
</Invalid>
|
|
415
|
+
</NumberInputSlotsContext.Provider>
|
|
373
416
|
);
|
|
374
417
|
};
|
|
418
|
+
|
|
419
|
+
NumberInput.Prefix = NumberInputPrefix;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
createEffect,
|
|
5
|
+
createMemo,
|
|
6
|
+
createSignal,
|
|
7
|
+
type JSX,
|
|
8
|
+
onCleanup,
|
|
9
|
+
type ParentComponent,
|
|
10
|
+
Show,
|
|
11
|
+
splitProps,
|
|
12
|
+
useContext,
|
|
13
|
+
} from "solid-js";
|
|
3
14
|
import { twMerge } from "tailwind-merge";
|
|
4
|
-
import type { IconProps as TablerIconProps } from "@tabler/icons-solidjs";
|
|
5
15
|
import { createControllableSignal } from "../../../hooks/createControllableSignal";
|
|
6
16
|
import { createIMEHandler } from "../../../hooks/createIMEHandler";
|
|
7
17
|
import {
|
|
@@ -11,11 +21,26 @@ import {
|
|
|
11
21
|
getFieldWrapperClass,
|
|
12
22
|
} from "./Field.styles";
|
|
13
23
|
import { PlaceholderFallback } from "./FieldPlaceholder";
|
|
14
|
-
import { Icon } from "../../display/Icon";
|
|
15
24
|
import { Invalid } from "../../form-control/Invalid";
|
|
16
25
|
|
|
26
|
+
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
27
|
+
|
|
28
|
+
interface TextInputSlotsContextValue {
|
|
29
|
+
setPrefix: (content: SlotAccessor) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const TextInputSlotsContext = createContext<TextInputSlotsContextValue>();
|
|
33
|
+
|
|
17
34
|
type TextInputType = "text" | "password" | "email";
|
|
18
35
|
|
|
36
|
+
const TextInputPrefix: ParentComponent = (props) => {
|
|
37
|
+
const ctx = useContext(TextInputSlotsContext)!;
|
|
38
|
+
// eslint-disable-next-line solid/reactivity -- slot accessor: children은 렌더 시점에 lazy 평가됨
|
|
39
|
+
ctx.setPrefix(() => props.children);
|
|
40
|
+
onCleanup(() => ctx.setPrefix(undefined));
|
|
41
|
+
return null;
|
|
42
|
+
};
|
|
43
|
+
|
|
19
44
|
export interface TextInputProps {
|
|
20
45
|
/** 입력 값 */
|
|
21
46
|
value?: string;
|
|
@@ -50,9 +75,6 @@ export interface TextInputProps {
|
|
|
50
75
|
/** 입력 포맷 (예: XXX-XXXX-XXXX) */
|
|
51
76
|
format?: string;
|
|
52
77
|
|
|
53
|
-
/** 접두 아이콘 */
|
|
54
|
-
prefixIcon?: Component<TablerIconProps>;
|
|
55
|
-
|
|
56
78
|
/** 필수 입력 여부 */
|
|
57
79
|
required?: boolean;
|
|
58
80
|
|
|
@@ -76,6 +98,9 @@ export interface TextInputProps {
|
|
|
76
98
|
|
|
77
99
|
/** 커스텀 style */
|
|
78
100
|
style?: JSX.CSSProperties;
|
|
101
|
+
|
|
102
|
+
/** children (TextInput.Prefix 슬롯) */
|
|
103
|
+
children?: JSX.Element;
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
/**
|
|
@@ -111,13 +136,16 @@ function applyFormat(value: string, format: string): string {
|
|
|
111
136
|
function removeFormat(formattedValue: string, format: string): string {
|
|
112
137
|
if (!formattedValue || !format) return formattedValue;
|
|
113
138
|
|
|
114
|
-
|
|
139
|
+
const separators = new Set<string>();
|
|
140
|
+
for (const ch of format) {
|
|
141
|
+
if (ch !== "X") separators.add(ch);
|
|
142
|
+
}
|
|
115
143
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
144
|
+
let result = "";
|
|
145
|
+
for (const ch of formattedValue) {
|
|
146
|
+
if (!separators.has(ch)) {
|
|
147
|
+
result += ch;
|
|
119
148
|
}
|
|
120
|
-
// 포맷 문자가 아닌 경우 (구분자) 스킵
|
|
121
149
|
}
|
|
122
150
|
|
|
123
151
|
return result;
|
|
@@ -138,7 +166,12 @@ function removeFormat(formattedValue: string, format: string): string {
|
|
|
138
166
|
* <TextInput type="password" placeholder="비밀번호 입력" />
|
|
139
167
|
* ```
|
|
140
168
|
*/
|
|
141
|
-
|
|
169
|
+
interface TextInputComponent {
|
|
170
|
+
(props: TextInputProps): JSX.Element;
|
|
171
|
+
Prefix: typeof TextInputPrefix;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const TextInputInner = (props: TextInputProps) => {
|
|
142
175
|
const [local, rest] = splitProps(props, [
|
|
143
176
|
"value",
|
|
144
177
|
"onValueChange",
|
|
@@ -151,7 +184,6 @@ export const TextInput: Component<TextInputProps> = (props) => {
|
|
|
151
184
|
"size",
|
|
152
185
|
"inset",
|
|
153
186
|
"format",
|
|
154
|
-
"prefixIcon",
|
|
155
187
|
"required",
|
|
156
188
|
"minLength",
|
|
157
189
|
"maxLength",
|
|
@@ -160,6 +192,7 @@ export const TextInput: Component<TextInputProps> = (props) => {
|
|
|
160
192
|
"touchMode",
|
|
161
193
|
"class",
|
|
162
194
|
"style",
|
|
195
|
+
"children",
|
|
163
196
|
]);
|
|
164
197
|
|
|
165
198
|
// controlled/uncontrolled 패턴 지원
|
|
@@ -210,6 +243,11 @@ export const TextInput: Component<TextInputProps> = (props) => {
|
|
|
210
243
|
ime.handleCompositionEnd(extractValue(e.currentTarget));
|
|
211
244
|
};
|
|
212
245
|
|
|
246
|
+
// Prefix 슬롯 Context 등록
|
|
247
|
+
const [prefix, _setPrefix] = createSignal<SlotAccessor>();
|
|
248
|
+
const setPrefix = (content: SlotAccessor) => _setPrefix(() => content);
|
|
249
|
+
const prefixEl = () => prefix() !== undefined;
|
|
250
|
+
|
|
213
251
|
// wrapper 클래스 (includeCustomClass=false일 때 local.class 제외 — inset에서 outer에만 적용)
|
|
214
252
|
const getWrapperClass = (includeCustomClass: boolean) =>
|
|
215
253
|
getFieldWrapperClass({
|
|
@@ -217,18 +255,12 @@ export const TextInput: Component<TextInputProps> = (props) => {
|
|
|
217
255
|
disabled: local.disabled,
|
|
218
256
|
inset: local.inset,
|
|
219
257
|
includeCustomClass: includeCustomClass && local.class,
|
|
220
|
-
extra:
|
|
258
|
+
extra: prefixEl() && fieldGapClasses[local.size ?? "default"],
|
|
221
259
|
});
|
|
222
260
|
|
|
223
261
|
// 편집 가능 여부
|
|
224
262
|
const isEditable = () => !local.disabled && !local.readonly;
|
|
225
263
|
|
|
226
|
-
const prefixIconEl = () => (
|
|
227
|
-
<Show when={local.prefixIcon}>
|
|
228
|
-
<Icon icon={local.prefixIcon!} class="shrink-0 opacity-70" />
|
|
229
|
-
</Show>
|
|
230
|
-
);
|
|
231
|
-
|
|
232
264
|
// disabled 전환 시 미커밋 조합 값 flush
|
|
233
265
|
createEffect(() => {
|
|
234
266
|
if (!isEditable()) {
|
|
@@ -252,77 +284,96 @@ export const TextInput: Component<TextInputProps> = (props) => {
|
|
|
252
284
|
});
|
|
253
285
|
|
|
254
286
|
return (
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
when={local.inset}
|
|
262
|
-
fallback={
|
|
263
|
-
// standalone 모드: 기존 Show 패턴 유지
|
|
264
|
-
<Show
|
|
265
|
-
when={isEditable()}
|
|
266
|
-
fallback={
|
|
267
|
-
<div
|
|
268
|
-
{...rest}
|
|
269
|
-
data-text-field
|
|
270
|
-
class={twMerge(getWrapperClass(true), "sd-text-field")}
|
|
271
|
-
style={local.style}
|
|
272
|
-
title={local.title}
|
|
273
|
-
>
|
|
274
|
-
{prefixIconEl()}
|
|
275
|
-
<PlaceholderFallback value={displayValue()} placeholder={local.placeholder} />
|
|
276
|
-
</div>
|
|
277
|
-
}
|
|
278
|
-
>
|
|
279
|
-
<div {...rest} data-text-field class={getWrapperClass(true)} style={local.style}>
|
|
280
|
-
{prefixIconEl()}
|
|
281
|
-
<input
|
|
282
|
-
type={local.type ?? "text"}
|
|
283
|
-
class={fieldInputClass}
|
|
284
|
-
value={inputValue()}
|
|
285
|
-
placeholder={local.placeholder}
|
|
286
|
-
title={local.title}
|
|
287
|
-
autocomplete={local.autocomplete}
|
|
288
|
-
onInput={handleInput}
|
|
289
|
-
onCompositionStart={handleCompositionStart}
|
|
290
|
-
onCompositionEnd={handleCompositionEnd}
|
|
291
|
-
/>
|
|
292
|
-
</div>
|
|
293
|
-
</Show>
|
|
294
|
-
}
|
|
287
|
+
<TextInputSlotsContext.Provider value={{ setPrefix }}>
|
|
288
|
+
{local.children}
|
|
289
|
+
<Invalid
|
|
290
|
+
message={errorMsg()}
|
|
291
|
+
variant={local.inset ? "dot" : "border"}
|
|
292
|
+
touchMode={local.touchMode}
|
|
295
293
|
>
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
<Show
|
|
295
|
+
when={local.inset}
|
|
296
|
+
fallback={
|
|
297
|
+
// standalone 모드: 기존 Show 패턴 유지
|
|
298
|
+
<Show
|
|
299
|
+
when={isEditable()}
|
|
300
|
+
fallback={
|
|
301
|
+
<div
|
|
302
|
+
{...rest}
|
|
303
|
+
data-text-field
|
|
304
|
+
class={twMerge(getWrapperClass(true), "sd-text-field")}
|
|
305
|
+
style={local.style}
|
|
306
|
+
title={local.title}
|
|
307
|
+
>
|
|
308
|
+
<Show when={prefix()}>
|
|
309
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
310
|
+
</Show>
|
|
311
|
+
<PlaceholderFallback value={displayValue()} placeholder={local.placeholder} />
|
|
312
|
+
</div>
|
|
313
|
+
}
|
|
314
|
+
>
|
|
315
|
+
<div {...rest} data-text-field class={getWrapperClass(true)} style={local.style}>
|
|
316
|
+
<Show when={prefix()}>
|
|
317
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
318
|
+
</Show>
|
|
319
|
+
<input
|
|
320
|
+
type={local.type ?? "text"}
|
|
321
|
+
class={fieldInputClass}
|
|
322
|
+
value={inputValue()}
|
|
323
|
+
placeholder={local.placeholder}
|
|
324
|
+
title={local.title}
|
|
325
|
+
autocomplete={local.autocomplete ?? "one-time-code"}
|
|
326
|
+
onInput={handleInput}
|
|
327
|
+
onCompositionStart={handleCompositionStart}
|
|
328
|
+
onCompositionEnd={handleCompositionEnd}
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
331
|
+
</Show>
|
|
332
|
+
}
|
|
333
|
+
>
|
|
334
|
+
{/* inset 모드: dual-element overlay 패턴 */}
|
|
298
335
|
<div
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
336
|
+
{...rest}
|
|
337
|
+
data-text-field
|
|
338
|
+
class={clsx("relative", "[text-decoration:inherit]", local.class)}
|
|
339
|
+
style={local.style}
|
|
303
340
|
>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
{
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
value={inputValue()}
|
|
315
|
-
placeholder={local.placeholder}
|
|
316
|
-
title={local.title}
|
|
317
|
-
autocomplete={local.autocomplete}
|
|
318
|
-
onInput={handleInput}
|
|
319
|
-
onCompositionStart={handleCompositionStart}
|
|
320
|
-
onCompositionEnd={handleCompositionEnd}
|
|
321
|
-
/>
|
|
341
|
+
<div
|
|
342
|
+
data-text-field-content
|
|
343
|
+
class={getWrapperClass(false)}
|
|
344
|
+
style={{ visibility: isEditable() ? "hidden" : undefined }}
|
|
345
|
+
title={local.title}
|
|
346
|
+
>
|
|
347
|
+
<Show when={prefix()}>
|
|
348
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
349
|
+
</Show>
|
|
350
|
+
<PlaceholderFallback value={displayValue()} placeholder={local.placeholder} />
|
|
322
351
|
</div>
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
352
|
+
|
|
353
|
+
<Show when={isEditable()}>
|
|
354
|
+
<div class={twMerge(getWrapperClass(false), "absolute left-0 top-0 size-full")}>
|
|
355
|
+
<Show when={prefix()}>
|
|
356
|
+
<span class="shrink-0">{prefix()!()}</span>
|
|
357
|
+
</Show>
|
|
358
|
+
<input
|
|
359
|
+
type={local.type ?? "text"}
|
|
360
|
+
class={fieldInputClass}
|
|
361
|
+
value={inputValue()}
|
|
362
|
+
placeholder={local.placeholder}
|
|
363
|
+
title={local.title}
|
|
364
|
+
autocomplete={local.autocomplete ?? "one-time-code"}
|
|
365
|
+
onInput={handleInput}
|
|
366
|
+
onCompositionStart={handleCompositionStart}
|
|
367
|
+
onCompositionEnd={handleCompositionEnd}
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
</Show>
|
|
371
|
+
</div>
|
|
372
|
+
</Show>
|
|
373
|
+
</Invalid>
|
|
374
|
+
</TextInputSlotsContext.Provider>
|
|
327
375
|
);
|
|
328
376
|
};
|
|
377
|
+
|
|
378
|
+
export const TextInput = TextInputInner as unknown as TextInputComponent;
|
|
379
|
+
TextInput.Prefix = TextInputPrefix;
|
|
@@ -151,6 +151,7 @@ export const TimePicker: Component<TimePickerProps> = (props) => {
|
|
|
151
151
|
disabled: local.disabled,
|
|
152
152
|
inset: local.inset,
|
|
153
153
|
includeCustomClass: includeCustomClass && local.class,
|
|
154
|
+
extra: "min-w-24",
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
// 편집 가능 여부
|
|
@@ -203,6 +204,7 @@ export const TimePicker: Component<TimePickerProps> = (props) => {
|
|
|
203
204
|
value={displayValue()}
|
|
204
205
|
title={local.title}
|
|
205
206
|
step={getStep()}
|
|
207
|
+
autocomplete="one-time-code"
|
|
206
208
|
onChange={handleChange}
|
|
207
209
|
/>
|
|
208
210
|
</div>
|
|
@@ -228,6 +230,7 @@ export const TimePicker: Component<TimePickerProps> = (props) => {
|
|
|
228
230
|
value={displayValue()}
|
|
229
231
|
title={local.title}
|
|
230
232
|
step={getStep()}
|
|
233
|
+
autocomplete="one-time-code"
|
|
231
234
|
onChange={handleChange}
|
|
232
235
|
/>
|
|
233
236
|
</div>
|