@simplysm/solid 13.0.29 → 13.0.31
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 +10 -5
- package/dist/components/data/Pagination.d.ts +4 -5
- package/dist/components/data/Pagination.d.ts.map +1 -1
- package/dist/components/data/Pagination.js +14 -14
- package/dist/components/data/Pagination.js.map +2 -2
- package/dist/components/data/Table.js +1 -1
- package/dist/components/data/calendar/Calendar.js +1 -1
- package/dist/components/data/kanban/Kanban.d.ts +9 -9
- package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
- package/dist/components/data/kanban/Kanban.js +4 -4
- package/dist/components/data/kanban/Kanban.js.map +2 -2
- package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.js +102 -107
- package/dist/components/data/sheet/DataSheet.js.map +2 -2
- package/dist/components/data/sheet/DataSheet.styles.js +1 -1
- package/dist/components/data/sheet/types.d.ts +2 -2
- package/dist/components/data/sheet/types.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.d.ts +8 -8
- package/dist/components/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +64 -69
- package/dist/components/disclosure/Dialog.js.map +2 -2
- package/dist/components/disclosure/DialogContext.d.ts +4 -4
- package/dist/components/disclosure/DialogContext.d.ts.map +1 -1
- package/dist/components/disclosure/DialogProvider.js +8 -8
- package/dist/components/disclosure/DialogProvider.js.map +2 -2
- package/dist/components/feedback/Progress.d.ts +3 -3
- package/dist/components/feedback/Progress.d.ts.map +1 -1
- package/dist/components/feedback/Progress.js +1 -1
- package/dist/components/feedback/Progress.js.map +2 -2
- package/dist/components/feedback/busy/BusyContainer.d.ts +1 -0
- package/dist/components/feedback/busy/BusyContainer.d.ts.map +1 -1
- package/dist/components/feedback/busy/BusyContainer.js +13 -6
- package/dist/components/feedback/busy/BusyContainer.js.map +2 -2
- package/dist/components/feedback/notification/NotificationBanner.js +1 -1
- 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 +4 -2
- package/dist/components/feedback/notification/NotificationBell.js.map +2 -2
- package/dist/components/feedback/notification/NotificationProvider.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationProvider.js +1 -0
- package/dist/components/feedback/notification/NotificationProvider.js.map +1 -1
- package/dist/components/form-control/Invalid.d.ts +4 -2
- package/dist/components/form-control/Invalid.d.ts.map +1 -1
- package/dist/components/form-control/Invalid.js +81 -41
- package/dist/components/form-control/Invalid.js.map +2 -2
- package/dist/components/form-control/ThemeToggle.d.ts.map +1 -1
- package/dist/components/form-control/ThemeToggle.js +4 -5
- package/dist/components/form-control/ThemeToggle.js.map +2 -2
- package/dist/components/form-control/checkbox/Checkbox.d.ts +4 -2
- package/dist/components/form-control/checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Checkbox.js +65 -52
- package/dist/components/form-control/checkbox/Checkbox.js.map +2 -2
- package/dist/components/form-control/checkbox/Checkbox.styles.d.ts +1 -2
- package/dist/components/form-control/checkbox/Checkbox.styles.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Checkbox.styles.js +3 -9
- package/dist/components/form-control/checkbox/Checkbox.styles.js.map +1 -1
- package/dist/components/form-control/checkbox/CheckboxGroup.d.ts +9 -9
- package/dist/components/form-control/checkbox/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/CheckboxGroup.js +10 -82
- package/dist/components/form-control/checkbox/CheckboxGroup.js.map +2 -2
- package/dist/components/form-control/checkbox/Radio.d.ts +4 -2
- package/dist/components/form-control/checkbox/Radio.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Radio.js +64 -51
- package/dist/components/form-control/checkbox/Radio.js.map +2 -2
- package/dist/components/form-control/checkbox/RadioGroup.d.ts +9 -9
- package/dist/components/form-control/checkbox/RadioGroup.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/RadioGroup.js +10 -77
- package/dist/components/form-control/checkbox/RadioGroup.js.map +2 -2
- package/dist/components/form-control/color-picker/ColorPicker.d.ts +8 -3
- package/dist/components/form-control/color-picker/ColorPicker.d.ts.map +1 -1
- package/dist/components/form-control/color-picker/ColorPicker.js +43 -26
- package/dist/components/form-control/color-picker/ColorPicker.js.map +2 -2
- package/dist/components/form-control/combobox/Combobox.d.ts +8 -8
- package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
- package/dist/components/form-control/combobox/Combobox.js +72 -59
- package/dist/components/form-control/combobox/Combobox.js.map +2 -2
- package/dist/components/form-control/editor/EditorToolbar.d.ts.map +1 -1
- package/dist/components/form-control/editor/EditorToolbar.js +3 -2
- package/dist/components/form-control/editor/EditorToolbar.js.map +2 -2
- package/dist/components/form-control/field/DatePicker.d.ts +6 -0
- package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DatePicker.js +138 -117
- package/dist/components/form-control/field/DatePicker.js.map +2 -2
- package/dist/components/form-control/field/DateTimePicker.d.ts +6 -0
- package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DateTimePicker.js +138 -115
- package/dist/components/form-control/field/DateTimePicker.js.map +2 -2
- package/dist/components/form-control/field/Field.styles.d.ts +14 -0
- package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
- package/dist/components/form-control/field/Field.styles.js +30 -0
- package/dist/components/form-control/field/Field.styles.js.map +1 -1
- package/dist/components/form-control/field/FieldPlaceholder.d.ts +7 -0
- package/dist/components/form-control/field/FieldPlaceholder.d.ts.map +1 -0
- package/dist/components/form-control/field/FieldPlaceholder.js +34 -0
- package/dist/components/form-control/field/FieldPlaceholder.js.map +6 -0
- package/dist/components/form-control/field/NumberInput.d.ts +10 -0
- package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
- package/dist/components/form-control/field/NumberInput.js +149 -115
- package/dist/components/form-control/field/NumberInput.js.map +2 -2
- package/dist/components/form-control/field/TextInput.d.ts +12 -0
- package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
- package/dist/components/form-control/field/TextInput.js +162 -116
- package/dist/components/form-control/field/TextInput.js.map +2 -2
- package/dist/components/form-control/field/Textarea.d.ts +10 -0
- package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
- package/dist/components/form-control/field/Textarea.js +156 -121
- package/dist/components/form-control/field/Textarea.js.map +2 -2
- package/dist/components/form-control/field/TimePicker.d.ts +10 -0
- package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/TimePicker.js +126 -94
- package/dist/components/form-control/field/TimePicker.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts +7 -9
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +71 -60
- package/dist/components/form-control/select/Select.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 +2 -1
- package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
- package/dist/components/layout/sidebar/SidebarMenu.js +1 -1
- package/dist/components/layout/sidebar/SidebarMenu.js.map +1 -1
- package/dist/components/layout/sidebar/SidebarUser.js +2 -2
- package/dist/components/layout/sidebar/SidebarUser.js.map +1 -1
- package/dist/hooks/createItemTemplate.d.ts +17 -0
- package/dist/hooks/createItemTemplate.d.ts.map +1 -0
- package/dist/hooks/createItemTemplate.js +40 -0
- package/dist/hooks/createItemTemplate.js.map +6 -0
- package/dist/hooks/createPointerDrag.d.ts +13 -0
- package/dist/hooks/createPointerDrag.d.ts.map +1 -0
- package/dist/hooks/createPointerDrag.js +15 -0
- package/dist/hooks/createPointerDrag.js.map +6 -0
- package/dist/hooks/createSelectionGroup.d.ts +70 -0
- package/dist/hooks/createSelectionGroup.d.ts.map +1 -0
- package/dist/hooks/createSelectionGroup.js +141 -0
- package/dist/hooks/createSelectionGroup.js.map +6 -0
- package/dist/hooks/useLocalStorage.d.ts +5 -3
- package/dist/hooks/useLocalStorage.d.ts.map +1 -1
- package/dist/hooks/useLocalStorage.js.map +1 -1
- package/dist/hooks/{createPwaUpdate.d.ts → usePwaUpdate.d.ts} +2 -2
- package/dist/hooks/usePwaUpdate.d.ts.map +1 -0
- package/dist/hooks/{createPwaUpdate.js → usePwaUpdate.js} +3 -3
- package/dist/hooks/usePwaUpdate.js.map +6 -0
- package/dist/hooks/useSyncConfig.d.ts +3 -3
- package/dist/hooks/useSyncConfig.d.ts.map +1 -1
- package/dist/hooks/useSyncConfig.js +6 -7
- package/dist/hooks/useSyncConfig.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/providers/InitializeProvider.js +2 -2
- package/dist/providers/InitializeProvider.js.map +2 -2
- package/dist/providers/ThemeContext.d.ts.map +1 -1
- package/dist/providers/ThemeContext.js +2 -1
- package/dist/providers/ThemeContext.js.map +2 -2
- package/dist/styles/patterns.styles.d.ts +1 -0
- package/dist/styles/patterns.styles.d.ts.map +1 -1
- package/dist/styles/patterns.styles.js +11 -0
- package/dist/styles/patterns.styles.js.map +1 -1
- package/dist/styles/tokens.styles.d.ts +1 -0
- package/dist/styles/tokens.styles.d.ts.map +1 -1
- package/dist/styles/tokens.styles.js.map +1 -1
- package/docs/data-components.md +34 -5
- package/docs/disclosure.md +28 -8
- package/docs/feedback.md +25 -2
- package/docs/form-controls.md +289 -33
- package/docs/hooks.md +19 -7
- package/docs/layout.md +12 -0
- package/docs/providers.md +120 -8
- package/docs/styling.md +90 -0
- package/package.json +3 -3
- package/src/components/data/Pagination.tsx +20 -21
- package/src/components/data/Table.tsx +1 -1
- package/src/components/data/calendar/Calendar.tsx +1 -1
- package/src/components/data/kanban/Kanban.tsx +18 -25
- package/src/components/data/sheet/DataSheet.styles.ts +1 -1
- package/src/components/data/sheet/DataSheet.tsx +122 -131
- package/src/components/data/sheet/types.ts +2 -2
- package/src/components/disclosure/Dialog.tsx +87 -100
- package/src/components/disclosure/DialogContext.ts +4 -4
- package/src/components/disclosure/DialogProvider.tsx +4 -4
- package/src/components/feedback/Progress.tsx +9 -5
- package/src/components/feedback/busy/BusyContainer.tsx +9 -5
- package/src/components/feedback/notification/NotificationBanner.tsx +1 -1
- package/src/components/feedback/notification/NotificationBell.tsx +4 -12
- package/src/components/feedback/notification/NotificationProvider.tsx +1 -0
- package/src/components/form-control/Invalid.tsx +114 -52
- package/src/components/form-control/ThemeToggle.tsx +4 -17
- package/src/components/form-control/checkbox/Checkbox.styles.ts +2 -9
- package/src/components/form-control/checkbox/Checkbox.tsx +39 -28
- package/src/components/form-control/checkbox/CheckboxGroup.tsx +18 -97
- package/src/components/form-control/checkbox/Radio.tsx +39 -28
- package/src/components/form-control/checkbox/RadioGroup.tsx +18 -92
- package/src/components/form-control/color-picker/ColorPicker.tsx +36 -16
- package/src/components/form-control/combobox/Combobox.tsx +43 -33
- package/src/components/form-control/editor/EditorToolbar.tsx +3 -14
- package/src/components/form-control/field/DatePicker.tsx +99 -97
- package/src/components/form-control/field/DateTimePicker.tsx +107 -95
- package/src/components/form-control/field/Field.styles.ts +45 -0
- package/src/components/form-control/field/FieldPlaceholder.tsx +18 -0
- package/src/components/form-control/field/NumberInput.tsx +122 -94
- package/src/components/form-control/field/TextInput.tsx +119 -95
- package/src/components/form-control/field/Textarea.tsx +124 -98
- package/src/components/form-control/field/TimePicker.tsx +101 -75
- package/src/components/form-control/select/Select.tsx +52 -44
- package/src/components/form-control/state-preset/StatePreset.tsx +2 -8
- package/src/components/layout/sidebar/SidebarMenu.tsx +1 -1
- package/src/components/layout/sidebar/SidebarUser.tsx +3 -3
- package/src/hooks/createItemTemplate.tsx +42 -0
- package/src/hooks/createPointerDrag.ts +28 -0
- package/src/hooks/createSelectionGroup.tsx +235 -0
- package/src/hooks/useLocalStorage.ts +8 -4
- package/src/hooks/{createPwaUpdate.ts → usePwaUpdate.ts} +1 -1
- package/src/hooks/useSyncConfig.ts +9 -13
- package/src/index.ts +1 -3
- package/src/providers/InitializeProvider.tsx +2 -2
- package/src/providers/ThemeContext.tsx +2 -1
- package/src/styles/patterns.styles.ts +12 -0
- package/src/styles/tokens.styles.ts +1 -0
- package/dist/hooks/createPwaUpdate.d.ts.map +0 -1
- package/dist/hooks/createPwaUpdate.js.map +0 -6
|
@@ -9,6 +9,7 @@ import { useNotification } from "../../feedback/notification/NotificationContext
|
|
|
9
9
|
import { Icon } from "../../display/Icon";
|
|
10
10
|
import { textPlaceholder } from "../../../styles/tokens.styles";
|
|
11
11
|
import type { ComponentSize } from "../../../styles/tokens.styles";
|
|
12
|
+
import { iconButtonBase } from "../../../styles/patterns.styles";
|
|
12
13
|
|
|
13
14
|
// ── Types ──
|
|
14
15
|
|
|
@@ -50,14 +51,7 @@ const chipSizeClasses: Record<StatePresetSize, string> = {
|
|
|
50
51
|
|
|
51
52
|
const chipNameBtnClass = clsx("cursor-pointer", "hover:underline", "focus:outline-none");
|
|
52
53
|
|
|
53
|
-
const iconBtnClass =
|
|
54
|
-
"inline-flex items-center justify-center",
|
|
55
|
-
"rounded-full",
|
|
56
|
-
"cursor-pointer",
|
|
57
|
-
"transition-colors",
|
|
58
|
-
"focus:outline-none",
|
|
59
|
-
"hover:bg-base-300 dark:hover:bg-base-600",
|
|
60
|
-
);
|
|
54
|
+
const iconBtnClass = twMerge(iconButtonBase, "rounded-full");
|
|
61
55
|
|
|
62
56
|
const iconBtnDefaultClass = "p-0.5";
|
|
63
57
|
|
|
@@ -117,9 +117,9 @@ export const SidebarUser: Component<SidebarUserProps> = (props) => {
|
|
|
117
117
|
<div class={avatarClass}>
|
|
118
118
|
<Icon icon={local.icon ?? IconUser} class="size-6" />
|
|
119
119
|
</div>
|
|
120
|
-
<Show when={local.description} fallback={<span class="font-
|
|
120
|
+
<Show when={local.description} fallback={<span class="font-bold">{local.name}</span>}>
|
|
121
121
|
<div class="flex flex-col">
|
|
122
|
-
<span class="font-
|
|
122
|
+
<span class="font-bold">{local.name}</span>
|
|
123
123
|
<span class={clsx("text-sm", "text-base-500 dark:text-base-400")}>
|
|
124
124
|
{local.description}
|
|
125
125
|
</span>
|
|
@@ -129,7 +129,7 @@ export const SidebarUser: Component<SidebarUserProps> = (props) => {
|
|
|
129
129
|
</button>
|
|
130
130
|
<Show when={hasMenus()}>
|
|
131
131
|
<Collapse open={open()}>
|
|
132
|
-
<hr class={clsx("border-base-
|
|
132
|
+
<hr class={clsx("border-base-100 dark:border-base-800")} />
|
|
133
133
|
<List inset>
|
|
134
134
|
<For each={local.menus}>
|
|
135
135
|
{(menu) => <ListItem onClick={() => handleMenuClick(menu)}>{menu.title}</ListItem>}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { JSX } from "solid-js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WeakMap 기반 템플릿 슬롯 패턴을 생성합니다. SolidJS children을 통해 렌더 함수를 전달할 때 사용합니다.
|
|
5
|
+
*
|
|
6
|
+
* Select와 Combobox의 ItemTemplate 서브 컴포넌트 패턴에서 사용됩니다.
|
|
7
|
+
* TemplateSlot은 WeakMap에 렌더 함수를 ref로 저장하는 숨겨진 span을 렌더링합니다.
|
|
8
|
+
* getTemplate은 resolved 슬롯 엘리먼트에서 렌더 함수를 가져옵니다.
|
|
9
|
+
*
|
|
10
|
+
* @param dataAttr - 숨겨진 span에 사용할 HTML 속성 이름 (예: "data-select-item-template")
|
|
11
|
+
*/
|
|
12
|
+
export function createItemTemplate<TArgs extends unknown[]>(
|
|
13
|
+
dataAttr: string,
|
|
14
|
+
): {
|
|
15
|
+
TemplateSlot: (props: { children(...args: TArgs): JSX.Element }) => JSX.Element;
|
|
16
|
+
getTemplate: (slotElements: Element[]) => ((...args: TArgs) => JSX.Element) | undefined;
|
|
17
|
+
} {
|
|
18
|
+
const templateFnMap = new WeakMap<HTMLElement, (...args: TArgs) => JSX.Element>();
|
|
19
|
+
|
|
20
|
+
function TemplateSlot(props: { children(...args: TArgs): JSX.Element }): JSX.Element {
|
|
21
|
+
return (
|
|
22
|
+
<span
|
|
23
|
+
ref={(el) => {
|
|
24
|
+
templateFnMap.set(el, props.children);
|
|
25
|
+
}}
|
|
26
|
+
{...{ [dataAttr]: true }}
|
|
27
|
+
style={{ display: "none" }}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getTemplate(slotElements: Element[]): ((...args: TArgs) => JSX.Element) | undefined {
|
|
33
|
+
if (slotElements.length === 0) return undefined;
|
|
34
|
+
const el = slotElements[0];
|
|
35
|
+
if (el instanceof HTMLElement) {
|
|
36
|
+
return templateFnMap.get(el);
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { TemplateSlot, getTemplate };
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets up pointer capture and manages pointermove/pointerup lifecycle on a target element.
|
|
3
|
+
*
|
|
4
|
+
* @param target - Element to capture pointer on
|
|
5
|
+
* @param pointerId - Pointer ID from the initiating PointerEvent
|
|
6
|
+
* @param options.onMove - Called on each pointermove
|
|
7
|
+
* @param options.onEnd - Called on pointerup (after listener cleanup)
|
|
8
|
+
*/
|
|
9
|
+
export function createPointerDrag(
|
|
10
|
+
target: HTMLElement,
|
|
11
|
+
pointerId: number,
|
|
12
|
+
options: {
|
|
13
|
+
onMove: (e: PointerEvent) => void;
|
|
14
|
+
onEnd: (e: PointerEvent) => void;
|
|
15
|
+
},
|
|
16
|
+
): void {
|
|
17
|
+
target.setPointerCapture(pointerId);
|
|
18
|
+
|
|
19
|
+
const onPointerMove = (e: PointerEvent) => options.onMove(e);
|
|
20
|
+
const onPointerUp = (e: PointerEvent) => {
|
|
21
|
+
target.removeEventListener("pointermove", onPointerMove);
|
|
22
|
+
target.removeEventListener("pointerup", onPointerUp);
|
|
23
|
+
options.onEnd(e);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
target.addEventListener("pointermove", onPointerMove);
|
|
27
|
+
target.addEventListener("pointerup", onPointerUp);
|
|
28
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type JSX,
|
|
3
|
+
type ParentComponent,
|
|
4
|
+
createContext,
|
|
5
|
+
createMemo,
|
|
6
|
+
splitProps,
|
|
7
|
+
useContext,
|
|
8
|
+
} from "solid-js";
|
|
9
|
+
import { twMerge } from "tailwind-merge";
|
|
10
|
+
import { createControllableSignal } from "./createControllableSignal";
|
|
11
|
+
import { Invalid } from "../components/form-control/Invalid";
|
|
12
|
+
import type { CheckboxSize } from "../components/form-control/checkbox/Checkbox.styles";
|
|
13
|
+
|
|
14
|
+
interface SelectionItemComponentProps {
|
|
15
|
+
value: boolean;
|
|
16
|
+
onValueChange?: (value: boolean) => void;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
size?: CheckboxSize;
|
|
19
|
+
inline?: boolean;
|
|
20
|
+
inset?: boolean;
|
|
21
|
+
children?: JSX.Element;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface SelectionGroupContextBase {
|
|
25
|
+
disabled: () => boolean;
|
|
26
|
+
size: () => CheckboxSize | undefined;
|
|
27
|
+
inline: () => boolean;
|
|
28
|
+
inset: () => boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface MultiSelectContext extends SelectionGroupContextBase {
|
|
32
|
+
value: () => unknown[];
|
|
33
|
+
toggle: (item: unknown) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface SingleSelectContext extends SelectionGroupContextBase {
|
|
37
|
+
value: () => unknown | undefined;
|
|
38
|
+
select: (item: unknown) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface MultiGroupConfig {
|
|
42
|
+
mode: "multiple";
|
|
43
|
+
contextName: string;
|
|
44
|
+
ItemComponent: (props: SelectionItemComponentProps) => JSX.Element;
|
|
45
|
+
emptyErrorMsg: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface SingleGroupConfig {
|
|
49
|
+
mode: "single";
|
|
50
|
+
contextName: string;
|
|
51
|
+
ItemComponent: (props: SelectionItemComponentProps) => JSX.Element;
|
|
52
|
+
emptyErrorMsg: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface SelectionGroupItemProps<TValue> {
|
|
56
|
+
value: TValue;
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
children?: JSX.Element;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface MultiGroupProps<TValue> {
|
|
62
|
+
value?: TValue[];
|
|
63
|
+
onValueChange?: (value: TValue[]) => void;
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
size?: CheckboxSize;
|
|
66
|
+
inline?: boolean;
|
|
67
|
+
inset?: boolean;
|
|
68
|
+
required?: boolean;
|
|
69
|
+
validate?: (value: TValue[]) => string | undefined;
|
|
70
|
+
touchMode?: boolean;
|
|
71
|
+
class?: string;
|
|
72
|
+
style?: JSX.CSSProperties;
|
|
73
|
+
children?: JSX.Element;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface SingleGroupProps<TValue> {
|
|
77
|
+
value?: TValue;
|
|
78
|
+
onValueChange?: (value: TValue) => void;
|
|
79
|
+
disabled?: boolean;
|
|
80
|
+
size?: CheckboxSize;
|
|
81
|
+
inline?: boolean;
|
|
82
|
+
inset?: boolean;
|
|
83
|
+
required?: boolean;
|
|
84
|
+
validate?: (value: TValue | undefined) => string | undefined;
|
|
85
|
+
touchMode?: boolean;
|
|
86
|
+
class?: string;
|
|
87
|
+
style?: JSX.CSSProperties;
|
|
88
|
+
children?: JSX.Element;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createSelectionGroup(config: MultiGroupConfig): {
|
|
92
|
+
Group: {
|
|
93
|
+
<TValue = unknown>(props: MultiGroupProps<TValue>): JSX.Element;
|
|
94
|
+
Item: <TValue = unknown>(props: SelectionGroupItemProps<TValue>) => JSX.Element;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export function createSelectionGroup(config: SingleGroupConfig): {
|
|
98
|
+
Group: {
|
|
99
|
+
<TValue = unknown>(props: SingleGroupProps<TValue>): JSX.Element;
|
|
100
|
+
Item: <TValue = unknown>(props: SelectionGroupItemProps<TValue>) => JSX.Element;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
export function createSelectionGroup(config: MultiGroupConfig | SingleGroupConfig): {
|
|
104
|
+
Group: {
|
|
105
|
+
<TValue = unknown>(props: MultiGroupProps<TValue> | SingleGroupProps<TValue>): JSX.Element;
|
|
106
|
+
Item: <TValue = unknown>(props: SelectionGroupItemProps<TValue>) => JSX.Element;
|
|
107
|
+
};
|
|
108
|
+
} {
|
|
109
|
+
const Context = createContext<MultiSelectContext | SingleSelectContext>();
|
|
110
|
+
const ItemComponent = config.ItemComponent;
|
|
111
|
+
|
|
112
|
+
function ItemInner<TValue>(props: SelectionGroupItemProps<TValue>) {
|
|
113
|
+
const ctx = useContext(Context);
|
|
114
|
+
if (!ctx)
|
|
115
|
+
throw new Error(
|
|
116
|
+
`${config.contextName}.Item은 ${config.contextName} 내부에서만 사용할 수 있습니다`,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const isSelected = (): boolean => {
|
|
120
|
+
if (config.mode === "multiple") {
|
|
121
|
+
return (ctx as MultiSelectContext).value().includes(props.value);
|
|
122
|
+
}
|
|
123
|
+
return (ctx as SingleSelectContext).value() === props.value;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleChange = () => {
|
|
127
|
+
if (config.mode === "multiple") {
|
|
128
|
+
(ctx as MultiSelectContext).toggle(props.value);
|
|
129
|
+
} else {
|
|
130
|
+
(ctx as SingleSelectContext).select(props.value);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<ItemComponent
|
|
136
|
+
value={isSelected()}
|
|
137
|
+
onValueChange={handleChange}
|
|
138
|
+
disabled={props.disabled ?? ctx.disabled()}
|
|
139
|
+
size={ctx.size()}
|
|
140
|
+
inline={ctx.inline()}
|
|
141
|
+
inset={ctx.inset()}
|
|
142
|
+
>
|
|
143
|
+
{props.children}
|
|
144
|
+
</ItemComponent>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const GroupInner: ParentComponent<MultiGroupProps<unknown>> = (props) => {
|
|
149
|
+
const [local, rest] = splitProps(props, [
|
|
150
|
+
"value",
|
|
151
|
+
"onValueChange",
|
|
152
|
+
"disabled",
|
|
153
|
+
"size",
|
|
154
|
+
"inline",
|
|
155
|
+
"inset",
|
|
156
|
+
"required",
|
|
157
|
+
"validate",
|
|
158
|
+
"touchMode",
|
|
159
|
+
"class",
|
|
160
|
+
"style",
|
|
161
|
+
"children",
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
let contextValue: MultiSelectContext | SingleSelectContext;
|
|
165
|
+
|
|
166
|
+
if (config.mode === "multiple") {
|
|
167
|
+
const [value, setValue] = createControllableSignal<unknown[]>({
|
|
168
|
+
value: () => local.value ?? [],
|
|
169
|
+
onChange: () => local.onValueChange as ((v: unknown[]) => void) | undefined,
|
|
170
|
+
});
|
|
171
|
+
const toggle = (item: unknown) => {
|
|
172
|
+
setValue((prev) => {
|
|
173
|
+
if (prev.includes(item)) return prev.filter((v) => v !== item);
|
|
174
|
+
return [...prev, item];
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
contextValue = {
|
|
178
|
+
value,
|
|
179
|
+
toggle,
|
|
180
|
+
disabled: () => local.disabled ?? false,
|
|
181
|
+
size: () => local.size,
|
|
182
|
+
inline: () => local.inline ?? false,
|
|
183
|
+
inset: () => local.inset ?? false,
|
|
184
|
+
};
|
|
185
|
+
} else {
|
|
186
|
+
const [value, setValue] = createControllableSignal<unknown>({
|
|
187
|
+
value: () => local.value as unknown | undefined,
|
|
188
|
+
onChange: () => local.onValueChange as ((v: unknown) => void) | undefined,
|
|
189
|
+
});
|
|
190
|
+
const select = (item: unknown) => {
|
|
191
|
+
setValue(item);
|
|
192
|
+
};
|
|
193
|
+
contextValue = {
|
|
194
|
+
value,
|
|
195
|
+
select,
|
|
196
|
+
disabled: () => local.disabled ?? false,
|
|
197
|
+
size: () => local.size,
|
|
198
|
+
inline: () => local.inline ?? false,
|
|
199
|
+
inset: () => local.inset ?? false,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const errorMsg = createMemo(() => {
|
|
204
|
+
if (config.mode === "multiple") {
|
|
205
|
+
const v = local.value ?? [];
|
|
206
|
+
if (local.required && v.length === 0) return config.emptyErrorMsg;
|
|
207
|
+
return (local.validate as ((v: unknown[]) => string | undefined) | undefined)?.(v);
|
|
208
|
+
} else {
|
|
209
|
+
const v = local.value as unknown | undefined;
|
|
210
|
+
if (local.required && (v === undefined || v === null)) return config.emptyErrorMsg;
|
|
211
|
+
return (local.validate as ((v: unknown | undefined) => string | undefined) | undefined)?.(
|
|
212
|
+
v,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<Invalid message={errorMsg()} variant="dot" touchMode={local.touchMode}>
|
|
219
|
+
<Context.Provider value={contextValue}>
|
|
220
|
+
<div {...rest} class={twMerge("inline-flex", local.class)} style={local.style}>
|
|
221
|
+
{local.children}
|
|
222
|
+
</div>
|
|
223
|
+
</Context.Provider>
|
|
224
|
+
</Invalid>
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const Group = GroupInner as unknown as {
|
|
229
|
+
<TValue = unknown>(props: MultiGroupProps<TValue> | SingleGroupProps<TValue>): JSX.Element;
|
|
230
|
+
Item: <TValue = unknown>(props: SelectionGroupItemProps<TValue>) => JSX.Element;
|
|
231
|
+
};
|
|
232
|
+
Group.Item = ItemInner;
|
|
233
|
+
|
|
234
|
+
return { Group };
|
|
235
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { type Accessor,
|
|
1
|
+
import { type Accessor, createSignal } from "solid-js";
|
|
2
2
|
import { useConfig } from "../providers/ConfigContext";
|
|
3
3
|
|
|
4
|
+
type StorageSetter<TValue> = (
|
|
5
|
+
value: TValue | undefined | ((prev: TValue | undefined) => TValue | undefined),
|
|
6
|
+
) => TValue | undefined;
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* localStorage 기반 저장소 훅.
|
|
6
10
|
* syncStorage 설정과 무관하게 항상 localStorage를 사용한다.
|
|
@@ -11,7 +15,7 @@ import { useConfig } from "../providers/ConfigContext";
|
|
|
11
15
|
* @template T - 저장할 값의 타입
|
|
12
16
|
* @param key - localStorage 키
|
|
13
17
|
* @param initialValue - 초기값 (옵셔널)
|
|
14
|
-
* @returns [Accessor<T | undefined>,
|
|
18
|
+
* @returns [Accessor<T | undefined>, StorageSetter<T>] 튜플
|
|
15
19
|
*
|
|
16
20
|
* @example
|
|
17
21
|
* ```tsx
|
|
@@ -30,7 +34,7 @@ import { useConfig } from "../providers/ConfigContext";
|
|
|
30
34
|
export function useLocalStorage<TValue>(
|
|
31
35
|
key: string,
|
|
32
36
|
initialValue?: TValue,
|
|
33
|
-
): [Accessor<TValue | undefined>,
|
|
37
|
+
): [Accessor<TValue | undefined>, StorageSetter<TValue>] {
|
|
34
38
|
const config = useConfig();
|
|
35
39
|
const prefixedKey = `${config.clientName}.${key}`;
|
|
36
40
|
|
|
@@ -69,5 +73,5 @@ export function useLocalStorage<TValue>(
|
|
|
69
73
|
return resolved;
|
|
70
74
|
};
|
|
71
75
|
|
|
72
|
-
return [value, setAndStore
|
|
76
|
+
return [value, setAndStore];
|
|
73
77
|
}
|
|
@@ -15,7 +15,7 @@ const UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
|
15
15
|
*
|
|
16
16
|
* Must be called inside NotificationProvider.
|
|
17
17
|
*/
|
|
18
|
-
export function
|
|
18
|
+
export function usePwaUpdate(): void {
|
|
19
19
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return;
|
|
20
20
|
|
|
21
21
|
const notification = useNotification();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Accessor, type Setter, createEffect, createSignal
|
|
1
|
+
import { type Accessor, type Setter, createEffect, createSignal } from "solid-js";
|
|
2
2
|
import { useConfig } from "../providers/ConfigContext";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -9,13 +9,13 @@ import { useConfig } from "../providers/ConfigContext";
|
|
|
9
9
|
*
|
|
10
10
|
* @param key - Storage key for the config value
|
|
11
11
|
* @param defaultValue - Default value if no stored value exists
|
|
12
|
-
* @returns Tuple of [value accessor, value setter,
|
|
12
|
+
* @returns Tuple of [value accessor, value setter, ready state accessor]
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```tsx
|
|
16
|
-
* const [theme, setTheme,
|
|
16
|
+
* const [theme, setTheme, ready] = useSyncConfig("user-theme", "light");
|
|
17
17
|
*
|
|
18
|
-
* <Show when={
|
|
18
|
+
* <Show when={ready()}>
|
|
19
19
|
* <button onClick={() => setTheme(theme() === "light" ? "dark" : "light")}>
|
|
20
20
|
* Toggle theme
|
|
21
21
|
* </button>
|
|
@@ -29,7 +29,7 @@ export function useSyncConfig<TValue>(
|
|
|
29
29
|
const config = useConfig();
|
|
30
30
|
const prefixedKey = `${config.clientName}.${key}`;
|
|
31
31
|
const [value, setValue] = createSignal<TValue>(defaultValue);
|
|
32
|
-
const [
|
|
32
|
+
const [ready, setReady] = createSignal(false);
|
|
33
33
|
|
|
34
34
|
// Initialize from storage
|
|
35
35
|
const initializeFromStorage = async () => {
|
|
@@ -43,11 +43,11 @@ export function useSyncConfig<TValue>(
|
|
|
43
43
|
} catch {
|
|
44
44
|
// Ignore parse errors, keep default value
|
|
45
45
|
}
|
|
46
|
+
setReady(true);
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// Use syncStorage asynchronously
|
|
50
|
-
setBusy(true);
|
|
51
51
|
try {
|
|
52
52
|
const stored = await config.syncStorage.getItem(prefixedKey);
|
|
53
53
|
if (stored !== null) {
|
|
@@ -64,7 +64,7 @@ export function useSyncConfig<TValue>(
|
|
|
64
64
|
// Ignore parse errors
|
|
65
65
|
}
|
|
66
66
|
} finally {
|
|
67
|
-
|
|
67
|
+
setReady(true);
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
|
|
@@ -73,6 +73,7 @@ export function useSyncConfig<TValue>(
|
|
|
73
73
|
|
|
74
74
|
// Save to storage whenever value changes
|
|
75
75
|
createEffect(() => {
|
|
76
|
+
if (!ready()) return; // Don't save until storage has been read
|
|
76
77
|
const currentValue = value();
|
|
77
78
|
const serialized = JSON.stringify(currentValue);
|
|
78
79
|
|
|
@@ -93,10 +94,5 @@ export function useSyncConfig<TValue>(
|
|
|
93
94
|
})();
|
|
94
95
|
});
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
onCleanup(() => {
|
|
98
|
-
// No cleanup needed for storage operations
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return [value, setValue, busy];
|
|
97
|
+
return [value, setValue, ready];
|
|
102
98
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,11 +5,9 @@ export * from "./components/form-control/Button";
|
|
|
5
5
|
|
|
6
6
|
// Select
|
|
7
7
|
export * from "./components/form-control/select/Select";
|
|
8
|
-
export * from "./components/form-control/select/SelectContext";
|
|
9
8
|
|
|
10
9
|
// Combobox
|
|
11
10
|
export * from "./components/form-control/combobox/Combobox";
|
|
12
|
-
export * from "./components/form-control/combobox/ComboboxContext";
|
|
13
11
|
|
|
14
12
|
// Field
|
|
15
13
|
export * from "./components/form-control/field/TextInput";
|
|
@@ -132,7 +130,7 @@ export * from "./hooks/usePrint";
|
|
|
132
130
|
export { createControllableSignal } from "./hooks/createControllableSignal";
|
|
133
131
|
export { createIMEHandler } from "./hooks/createIMEHandler";
|
|
134
132
|
export { createMountTransition } from "./hooks/createMountTransition";
|
|
135
|
-
export {
|
|
133
|
+
export { usePwaUpdate } from "./hooks/usePwaUpdate";
|
|
136
134
|
export { useRouterLink } from "./hooks/useRouterLink";
|
|
137
135
|
|
|
138
136
|
//#endregion
|
|
@@ -7,12 +7,12 @@ import { NotificationProvider } from "../components/feedback/notification/Notifi
|
|
|
7
7
|
import { NotificationBanner } from "../components/feedback/notification/NotificationBanner";
|
|
8
8
|
import { BusyProvider } from "../components/feedback/busy/BusyProvider";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { usePwaUpdate } from "../hooks/usePwaUpdate";
|
|
11
11
|
import { useLogger } from "../hooks/useLogger";
|
|
12
12
|
|
|
13
13
|
/** Runs PWA update detection inside NotificationProvider context */
|
|
14
14
|
function PwaUpdater() {
|
|
15
|
-
|
|
15
|
+
usePwaUpdate();
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -85,7 +85,7 @@ export function useTheme(): ThemeContextValue {
|
|
|
85
85
|
* ```
|
|
86
86
|
*/
|
|
87
87
|
export const ThemeProvider: ParentComponent = (props) => {
|
|
88
|
-
const [mode, setMode] = useSyncConfig<ThemeMode>("theme", "system");
|
|
88
|
+
const [mode, setMode, ready] = useSyncConfig<ThemeMode>("theme", "system");
|
|
89
89
|
|
|
90
90
|
// OS 다크모드 감지
|
|
91
91
|
const prefersDark = createMediaQuery("(prefers-color-scheme: dark)");
|
|
@@ -109,6 +109,7 @@ export const ThemeProvider: ParentComponent = (props) => {
|
|
|
109
109
|
|
|
110
110
|
// <html>에 dark 클래스 토글
|
|
111
111
|
createEffect(() => {
|
|
112
|
+
if (!ready()) return; // Don't apply theme until storage has been read
|
|
112
113
|
const isDark = resolvedTheme() === "dark";
|
|
113
114
|
document.documentElement.classList.toggle("dark", isDark);
|
|
114
115
|
});
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import { bgSurface, borderDefault, textDefault, textPlaceholder } from "./tokens.styles";
|
|
3
3
|
|
|
4
|
+
// ── 아이콘 버튼 공통 ──
|
|
5
|
+
export const iconButtonBase = clsx(
|
|
6
|
+
"inline-flex items-center justify-center",
|
|
7
|
+
"cursor-pointer",
|
|
8
|
+
"rounded",
|
|
9
|
+
"transition-colors",
|
|
10
|
+
"text-base-600 dark:text-base-300",
|
|
11
|
+
"hover:bg-base-200 dark:hover:bg-base-700",
|
|
12
|
+
"focus:outline-none",
|
|
13
|
+
"focus-visible:ring-2",
|
|
14
|
+
);
|
|
15
|
+
|
|
4
16
|
// ── inset 포커스 아웃라인 (focus-within 버전: Field, TextArea) ──
|
|
5
17
|
export const insetFocusOutline = clsx(
|
|
6
18
|
"focus-within:[outline-style:solid]",
|
|
@@ -15,6 +15,7 @@ export const disabledOpacity = "cursor-default opacity-50 pointer-events-none";
|
|
|
15
15
|
|
|
16
16
|
// ── 사이즈 ──
|
|
17
17
|
export type ComponentSize = "sm" | "lg" | "xl";
|
|
18
|
+
export type ComponentSizeCompact = "sm" | "lg";
|
|
18
19
|
export const paddingSm = "px-1.5 py-0.5";
|
|
19
20
|
export const paddingLg = "px-3 py-2";
|
|
20
21
|
export const paddingXl = "px-4 py-3";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createPwaUpdate.d.ts","sourceRoot":"","sources":["../../src/hooks/createPwaUpdate.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAuDtC"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/hooks/createPwaUpdate.ts"],
|
|
4
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAEhC,MAAM,kBAAkB,IAAI,KAAK;AAc1B,SAAS,kBAAwB;AACtC,MAAI,OAAO,cAAc,eAAe,EAAE,mBAAmB,WAAY;AAEzE,QAAM,eAAe,gBAAgB;AACrC,MAAI;AAEJ,OAAK,UAAU,cAAc,gBAAgB,EAAE,KAAK,CAAC,iBAAiB;AACpE,QAAI,gBAAgB,KAAM;AAG1B,iBAAa,YAAY,MAAM;AAC7B,WAAK,aAAa,OAAO;AAAA,IAC3B,GAAG,eAAe;AAGlB,QAAI,aAAa,WAAW,MAAM;AAChC,mBAAa,aAAa,OAAO;AAAA,IACnC;AAGA,iBAAa,iBAAiB,eAAe,MAAM;AACjD,YAAM,QAAQ,aAAa;AAC3B,UAAI,SAAS,KAAM;AAEnB,YAAM,iBAAiB,eAAe,MAAM;AAC1C,YAAI,MAAM,UAAU,eAAe,UAAU,cAAc,cAAc,MAAM;AAC7E,uBAAa,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,CAAC;AAGD,QAAM,qBAAqB,MAAM;AAC/B,WAAO,SAAS,OAAO;AAAA,EACzB;AACA,YAAU,cAAc,iBAAiB,oBAAoB,kBAAkB;AAE/E,YAAU,MAAM;AACd,QAAI,cAAc,MAAM;AACtB,oBAAc,UAAU;AAAA,IAC1B;AACA,cAAU,cAAc,oBAAoB,oBAAoB,kBAAkB;AAAA,EACpF,CAAC;AAED,WAAS,aAAa,WAAgC;AACpD,iBAAa,KAAK,uEAAgB,2HAA4B;AAAA,MAC5D,QAAQ;AAAA,QACN,OAAO;AAAA,QACP,SAAS,MAAM;AACb,oBAAU,YAAY,EAAE,MAAM,eAAe,CAAC;AAAA,QAChD;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|