@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
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { type JSX, type ParentComponent, Show, splitProps } from "solid-js";
|
|
2
2
|
import clsx from "clsx";
|
|
3
3
|
import { twMerge } from "tailwind-merge";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type ComponentSizeCompact,
|
|
6
|
+
type SemanticTheme,
|
|
7
|
+
themeTokens,
|
|
8
|
+
} from "../../styles/tokens.styles";
|
|
5
9
|
|
|
6
10
|
export type ProgressTheme = SemanticTheme;
|
|
7
|
-
export type ProgressSize = "sm" | "lg";
|
|
8
11
|
|
|
9
12
|
export interface ProgressProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
/** 진행률 (0~1 범위, 0 = 0%, 1 = 100%) */
|
|
10
14
|
value: number;
|
|
11
15
|
theme?: ProgressTheme;
|
|
12
|
-
size?:
|
|
16
|
+
size?: ComponentSizeCompact;
|
|
13
17
|
inset?: boolean;
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -21,7 +25,7 @@ const baseClass = clsx(
|
|
|
21
25
|
"border border-base-200 dark:border-base-700",
|
|
22
26
|
);
|
|
23
27
|
|
|
24
|
-
const sizeClasses: Record<"default" |
|
|
28
|
+
const sizeClasses: Record<"default" | ComponentSizeCompact, string> = {
|
|
25
29
|
default: "py-1 px-2",
|
|
26
30
|
sm: "py-0.5 px-2",
|
|
27
31
|
lg: "py-2 px-3",
|
|
@@ -46,7 +50,7 @@ export const Progress: ParentComponent<ProgressProps> = (props) => {
|
|
|
46
50
|
return clsx("absolute left-0 top-0 h-full", "z-[1]", "transition-all", barThemeClasses[theme]);
|
|
47
51
|
};
|
|
48
52
|
|
|
49
|
-
const getPercentText = () => (local.value * 100).toFixed(2) + "%";
|
|
53
|
+
const getPercentText = () => (Math.max(0, Math.min(1, local.value)) * 100).toFixed(2) + "%";
|
|
50
54
|
|
|
51
55
|
return (
|
|
52
56
|
<div data-progress class={getClassName()} {...rest}>
|
|
@@ -15,6 +15,7 @@ import "./BusyContainer.css";
|
|
|
15
15
|
|
|
16
16
|
export interface BusyContainerProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
17
17
|
busy?: boolean;
|
|
18
|
+
ready?: boolean;
|
|
18
19
|
variant?: BusyVariant;
|
|
19
20
|
message?: string;
|
|
20
21
|
progressPercent?: number;
|
|
@@ -57,6 +58,7 @@ const barIndicatorClass = clsx("absolute left-0 top-0", "h-1 w-full", "bg-white
|
|
|
57
58
|
export const BusyContainer: ParentComponent<BusyContainerProps> = (props) => {
|
|
58
59
|
const [local, rest] = splitProps(props, [
|
|
59
60
|
"busy",
|
|
61
|
+
"ready",
|
|
60
62
|
"variant",
|
|
61
63
|
"message",
|
|
62
64
|
"progressPercent",
|
|
@@ -68,11 +70,13 @@ export const BusyContainer: ParentComponent<BusyContainerProps> = (props) => {
|
|
|
68
70
|
const currVariant = (): BusyVariant => local.variant ?? busyCtx?.variant() ?? "spinner";
|
|
69
71
|
|
|
70
72
|
// 애니메이션 상태 (mount transition)
|
|
71
|
-
const { mounted, animating, unmount } = createMountTransition(
|
|
73
|
+
const { mounted, animating, unmount } = createMountTransition(
|
|
74
|
+
() => local.ready === false || !!local.busy,
|
|
75
|
+
);
|
|
72
76
|
|
|
73
77
|
const handleTransitionEnd = (e: TransitionEvent) => {
|
|
74
78
|
if (e.propertyName !== "opacity") return;
|
|
75
|
-
if (!local.busy) {
|
|
79
|
+
if (local.ready !== false && !local.busy) {
|
|
76
80
|
unmount();
|
|
77
81
|
}
|
|
78
82
|
};
|
|
@@ -82,7 +86,7 @@ export const BusyContainer: ParentComponent<BusyContainerProps> = (props) => {
|
|
|
82
86
|
|
|
83
87
|
createEffect(() => {
|
|
84
88
|
const handleKeyDownCapture = (e: KeyboardEvent) => {
|
|
85
|
-
if (local.busy) {
|
|
89
|
+
if (local.ready === false || local.busy) {
|
|
86
90
|
e.preventDefault();
|
|
87
91
|
e.stopPropagation();
|
|
88
92
|
}
|
|
@@ -117,7 +121,7 @@ export const BusyContainer: ParentComponent<BusyContainerProps> = (props) => {
|
|
|
117
121
|
<Show when={currVariant() === "spinner"}>
|
|
118
122
|
<div class={spinnerClass} />
|
|
119
123
|
</Show>
|
|
120
|
-
<Show when={currVariant() === "bar" && local.busy}>
|
|
124
|
+
<Show when={currVariant() === "bar" && (local.ready === false || local.busy)}>
|
|
121
125
|
<div class={barIndicatorClass}>
|
|
122
126
|
<div
|
|
123
127
|
class={clsx(
|
|
@@ -155,7 +159,7 @@ export const BusyContainer: ParentComponent<BusyContainerProps> = (props) => {
|
|
|
155
159
|
</Show>
|
|
156
160
|
</div>
|
|
157
161
|
</Show>
|
|
158
|
-
{local.children}
|
|
162
|
+
<Show when={local.ready !== false}>{local.children}</Show>
|
|
159
163
|
</div>
|
|
160
164
|
);
|
|
161
165
|
};
|
|
@@ -65,7 +65,7 @@ export const NotificationBanner: Component = () => {
|
|
|
65
65
|
class={clsx(baseClass, themeClasses[item().theme])}
|
|
66
66
|
>
|
|
67
67
|
<div class={contentClass}>
|
|
68
|
-
<span class="font-
|
|
68
|
+
<span class="font-bold">{item().title}</span>
|
|
69
69
|
<Show when={item().message}>
|
|
70
70
|
<pre class={messageClass}>{item().message}</pre>
|
|
71
71
|
</Show>
|
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
import { type Component, createSignal, For, Show } from "solid-js";
|
|
2
2
|
import { IconBell } from "@tabler/icons-solidjs";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
+
import { twMerge } from "tailwind-merge";
|
|
4
5
|
import { useNotification } from "./NotificationContext";
|
|
5
6
|
import { Dropdown } from "../../disclosure/Dropdown";
|
|
6
7
|
import { Icon } from "../../display/Icon";
|
|
7
8
|
import { NotificationBanner } from "./NotificationBanner";
|
|
9
|
+
import { iconButtonBase } from "../../../styles/patterns.styles";
|
|
8
10
|
|
|
9
11
|
export interface NotificationBellProps {
|
|
10
12
|
showBanner?: boolean;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
const buttonClass =
|
|
14
|
-
"relative",
|
|
15
|
-
"p-2",
|
|
16
|
-
"rounded-full",
|
|
17
|
-
"hover:bg-base-100",
|
|
18
|
-
"dark:hover:bg-base-700",
|
|
19
|
-
"transition-colors",
|
|
20
|
-
"focus:outline-none",
|
|
21
|
-
"focus-visible:ring-2",
|
|
22
|
-
"focus-visible:ring-primary-500",
|
|
23
|
-
);
|
|
15
|
+
const buttonClass = twMerge(iconButtonBase, "relative", "p-2", "rounded-full");
|
|
24
16
|
|
|
25
17
|
const badgeClass = clsx(
|
|
26
18
|
"absolute",
|
|
@@ -108,7 +100,7 @@ export const NotificationBell: Component<NotificationBellProps> = (props) => {
|
|
|
108
100
|
>
|
|
109
101
|
<div class="p-2">
|
|
110
102
|
<div class={dropdownHeaderClass}>
|
|
111
|
-
<span class="font-
|
|
103
|
+
<span class="font-bold">알림</span>
|
|
112
104
|
<Show when={notification.items().length > 0}>
|
|
113
105
|
<button
|
|
114
106
|
type="button"
|
|
@@ -1,69 +1,131 @@
|
|
|
1
|
-
import { type ParentComponent, createEffect,
|
|
2
|
-
import clsx from "clsx";
|
|
3
|
-
import { twMerge } from "tailwind-merge";
|
|
1
|
+
import { type ParentComponent, children, createEffect, createSignal, onCleanup } from "solid-js";
|
|
4
2
|
import "@simplysm/core-browser";
|
|
5
3
|
|
|
6
4
|
export interface InvalidProps {
|
|
7
5
|
/** Validation error message. Non-empty = invalid. */
|
|
8
6
|
message?: string;
|
|
9
|
-
/**
|
|
10
|
-
|
|
7
|
+
/** Visual indicator variant */
|
|
8
|
+
variant?: "border" | "dot";
|
|
9
|
+
/** When true, visual display only appears after target loses focus */
|
|
10
|
+
touchMode?: boolean;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
export const Invalid: ParentComponent<InvalidProps> = (props) => {
|
|
14
|
+
const hiddenInputEl = document.createElement("input");
|
|
15
|
+
hiddenInputEl.type = "text";
|
|
16
|
+
hiddenInputEl.style.cssText =
|
|
17
|
+
"position:absolute; bottom:0; left:50%; width:1px; height:1px; opacity:0; pointer-events:none; z-index:-10;";
|
|
18
|
+
hiddenInputEl.autocomplete = "off";
|
|
19
|
+
hiddenInputEl.tabIndex = -1;
|
|
20
|
+
hiddenInputEl.setAttribute("aria-hidden", "true");
|
|
19
21
|
|
|
20
|
-
const
|
|
22
|
+
const [touched, setTouched] = createSignal(false);
|
|
21
23
|
|
|
22
|
-
const
|
|
23
|
-
"absolute bottom-0 left-0.5",
|
|
24
|
-
"size-px opacity-0",
|
|
25
|
-
"pointer-events-none -z-10",
|
|
26
|
-
"select-none",
|
|
27
|
-
);
|
|
24
|
+
const resolved = children(() => props.children);
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// message 변경 시 setCustomValidity 반응형 업데이트 (touchMode 무관하게 항상)
|
|
27
|
+
createEffect(() => {
|
|
28
|
+
const msg = props.message ?? "";
|
|
29
|
+
hiddenInputEl.setCustomValidity(msg);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// target에 relative 설정 + hidden input을 target 내부에 삽입
|
|
33
|
+
createEffect(() => {
|
|
34
|
+
const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
|
|
35
|
+
if (!targetEl) return;
|
|
36
|
+
|
|
37
|
+
const computedPosition = getComputedStyle(targetEl).position;
|
|
38
|
+
if (computedPosition === "static") {
|
|
39
|
+
targetEl.style.position = "relative";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
targetEl.appendChild(hiddenInputEl);
|
|
31
43
|
|
|
32
|
-
|
|
44
|
+
onCleanup(() => {
|
|
45
|
+
if (hiddenInputEl.parentElement === targetEl) {
|
|
46
|
+
targetEl.removeChild(hiddenInputEl);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
33
50
|
|
|
34
|
-
//
|
|
51
|
+
// 시각적 표시 처리
|
|
35
52
|
createEffect(() => {
|
|
36
|
-
const
|
|
37
|
-
|
|
53
|
+
const variant = props.variant ?? "dot";
|
|
54
|
+
const message = props.message ?? "";
|
|
55
|
+
const touchMode = props.touchMode ?? false;
|
|
56
|
+
const isTouched = touched();
|
|
57
|
+
|
|
58
|
+
const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
|
|
59
|
+
|
|
60
|
+
if (!targetEl) return;
|
|
61
|
+
|
|
62
|
+
const shouldShow = message !== "" && (!touchMode || isTouched);
|
|
63
|
+
|
|
64
|
+
if (variant === "border") {
|
|
65
|
+
if (shouldShow) {
|
|
66
|
+
targetEl.classList.add("border-danger-500");
|
|
67
|
+
} else {
|
|
68
|
+
targetEl.classList.remove("border-danger-500");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onCleanup(() => {
|
|
72
|
+
targetEl.classList.remove("border-danger-500");
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
// variant === "dot"
|
|
76
|
+
const existingDot = targetEl.querySelector("[data-invalid-dot]");
|
|
77
|
+
if (existingDot) {
|
|
78
|
+
existingDot.remove();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (shouldShow) {
|
|
82
|
+
const dot = document.createElement("span");
|
|
83
|
+
dot.setAttribute("data-invalid-dot", "");
|
|
84
|
+
dot.style.cssText =
|
|
85
|
+
"position:absolute; top:2px; right:2px; width:6px; height:6px; border-radius:50%; background:red; pointer-events:none;";
|
|
86
|
+
targetEl.appendChild(dot);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onCleanup(() => {
|
|
90
|
+
const dot = targetEl.querySelector("[data-invalid-dot]");
|
|
91
|
+
if (dot) {
|
|
92
|
+
dot.remove();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
38
96
|
});
|
|
39
97
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
98
|
+
// touchMode: target에 focusout 이벤트 등록하여 touched 상태 추적
|
|
99
|
+
createEffect(() => {
|
|
100
|
+
if (!(props.touchMode ?? false)) return;
|
|
101
|
+
|
|
102
|
+
const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
|
|
103
|
+
|
|
104
|
+
if (!targetEl) return;
|
|
105
|
+
|
|
106
|
+
const handleFocusOut = () => {
|
|
107
|
+
setTouched(true);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
targetEl.addEventListener("focusout", handleFocusOut);
|
|
111
|
+
|
|
112
|
+
onCleanup(() => {
|
|
113
|
+
targetEl.removeEventListener("focusout", handleFocusOut);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// hidden input 포커스 시 target의 focusable child로 리디렉션
|
|
118
|
+
hiddenInputEl.addEventListener("focus", () => {
|
|
119
|
+
const targetEl = resolved.toArray().find((el): el is HTMLElement => el instanceof HTMLElement);
|
|
120
|
+
|
|
121
|
+
if (targetEl) {
|
|
122
|
+
const focusable =
|
|
123
|
+
targetEl.findFirstFocusableChild() ?? (targetEl.tabIndex >= 0 ? targetEl : undefined);
|
|
124
|
+
if (focusable && focusable !== hiddenInputEl) {
|
|
125
|
+
focusable.focus();
|
|
126
|
+
}
|
|
46
127
|
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<div {...rest} class={twMerge("inline", local.class)}>
|
|
51
|
-
<span class={anchorClass}>
|
|
52
|
-
<span
|
|
53
|
-
class={indicatorClass}
|
|
54
|
-
style={{ display: (local.message ?? "") !== "" ? "block" : "none" }}
|
|
55
|
-
/>
|
|
56
|
-
</span>
|
|
57
|
-
{local.children}
|
|
58
|
-
<input
|
|
59
|
-
ref={hiddenInputEl}
|
|
60
|
-
type="text"
|
|
61
|
-
class={hiddenInputClass}
|
|
62
|
-
autocomplete="off"
|
|
63
|
-
tabIndex={-1}
|
|
64
|
-
aria-hidden="true"
|
|
65
|
-
onFocus={handleHiddenInputFocus}
|
|
66
|
-
/>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return <>{resolved()}</>;
|
|
69
131
|
};
|
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
import { type Component, type JSX, splitProps, Switch, Match } from "solid-js";
|
|
2
|
-
import clsx from "clsx";
|
|
3
2
|
import { twMerge } from "tailwind-merge";
|
|
4
3
|
import { IconSun, IconMoon, IconDeviceDesktop } from "@tabler/icons-solidjs";
|
|
5
4
|
import { useTheme, type ThemeMode } from "../../providers/ThemeContext";
|
|
6
5
|
import { Icon } from "../display/Icon";
|
|
7
6
|
import { ripple } from "../../directives/ripple";
|
|
7
|
+
import { iconButtonBase } from "../../styles/patterns.styles";
|
|
8
8
|
|
|
9
9
|
void ripple;
|
|
10
10
|
|
|
11
|
-
const baseClass = clsx(
|
|
12
|
-
"inline-flex",
|
|
13
|
-
"items-center",
|
|
14
|
-
"justify-center",
|
|
15
|
-
"cursor-pointer",
|
|
16
|
-
"rounded",
|
|
17
|
-
"transition-colors",
|
|
18
|
-
"text-base-500 dark:text-base-400",
|
|
19
|
-
"hover:bg-base-200 dark:hover:bg-base-700",
|
|
20
|
-
"focus:outline-none",
|
|
21
|
-
"focus-visible:ring-2",
|
|
22
|
-
);
|
|
23
|
-
|
|
24
11
|
const sizeClasses: Record<"sm" | "lg", string> = {
|
|
25
|
-
sm:
|
|
26
|
-
lg:
|
|
12
|
+
sm: "p-1",
|
|
13
|
+
lg: "p-2",
|
|
27
14
|
};
|
|
28
15
|
|
|
29
16
|
const iconSizes: Record<"sm" | "lg", string> = {
|
|
@@ -69,7 +56,7 @@ export const ThemeToggle: Component<ThemeToggleProps> = (props) => {
|
|
|
69
56
|
const { mode, cycleMode } = useTheme();
|
|
70
57
|
|
|
71
58
|
const getClassName = () =>
|
|
72
|
-
twMerge(
|
|
59
|
+
twMerge(iconButtonBase, "p-1.5", local.size && sizeClasses[local.size], local.class);
|
|
73
60
|
|
|
74
61
|
const iconSize = () => (local.size ? iconSizes[local.size] : "1.25em");
|
|
75
62
|
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
} from "../../../styles/tokens.styles";
|
|
11
11
|
import { insetBase, insetFocusOutlineSelf } from "../../../styles/patterns.styles";
|
|
12
12
|
|
|
13
|
-
export type CheckboxTheme = "primary" | "info" | "success" | "warning" | "danger";
|
|
14
13
|
export type CheckboxSize = ComponentSize;
|
|
15
14
|
|
|
16
15
|
// wrapper 기본 스타일
|
|
@@ -36,14 +35,8 @@ export const indicatorBaseClass = clsx(
|
|
|
36
35
|
"transition-colors",
|
|
37
36
|
);
|
|
38
37
|
|
|
39
|
-
//
|
|
40
|
-
export const
|
|
41
|
-
primary: clsx("border-primary-500 bg-primary-500", "text-white"),
|
|
42
|
-
info: clsx("border-info-500 bg-info-500", "text-white"),
|
|
43
|
-
success: clsx("border-success-500 bg-success-500", "text-white"),
|
|
44
|
-
warning: clsx("border-warning-500 bg-warning-500", "text-white"),
|
|
45
|
-
danger: clsx("border-danger-500 bg-danger-500", "text-white"),
|
|
46
|
-
};
|
|
38
|
+
// 체크 상태 스타일 (primary 고정)
|
|
39
|
+
export const checkedClass = clsx("border-primary-500 bg-primary-500", "text-white");
|
|
47
40
|
|
|
48
41
|
// 사이즈별 스타일
|
|
49
42
|
export const checkboxSizeClasses: Record<CheckboxSize, string> = {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { type JSX, type ParentComponent, Show, splitProps } from "solid-js";
|
|
1
|
+
import { type JSX, type ParentComponent, Show, splitProps, createMemo } from "solid-js";
|
|
2
2
|
import { twMerge } from "tailwind-merge";
|
|
3
3
|
import { IconCheck } from "@tabler/icons-solidjs";
|
|
4
4
|
import { createControllableSignal } from "../../../hooks/createControllableSignal";
|
|
5
5
|
import { ripple } from "../../../directives/ripple";
|
|
6
6
|
import { Icon } from "../../display/Icon";
|
|
7
7
|
import {
|
|
8
|
-
type CheckboxTheme,
|
|
9
8
|
type CheckboxSize,
|
|
10
9
|
checkboxBaseClass,
|
|
11
10
|
indicatorBaseClass,
|
|
12
|
-
|
|
11
|
+
checkedClass,
|
|
13
12
|
checkboxSizeClasses,
|
|
14
13
|
checkboxInsetClass,
|
|
15
14
|
checkboxInsetSizeHeightClasses,
|
|
16
15
|
checkboxInlineClass,
|
|
17
16
|
checkboxDisabledClass,
|
|
18
17
|
} from "./Checkbox.styles";
|
|
18
|
+
import { Invalid } from "../Invalid";
|
|
19
19
|
|
|
20
20
|
// Directive 사용 선언 (TypeScript용)
|
|
21
21
|
void ripple;
|
|
@@ -25,9 +25,11 @@ export interface CheckboxProps {
|
|
|
25
25
|
onValueChange?: (value: boolean) => void;
|
|
26
26
|
disabled?: boolean;
|
|
27
27
|
size?: CheckboxSize;
|
|
28
|
-
theme?: CheckboxTheme;
|
|
29
28
|
inset?: boolean;
|
|
30
29
|
inline?: boolean;
|
|
30
|
+
required?: boolean;
|
|
31
|
+
validate?: (value: boolean) => string | undefined;
|
|
32
|
+
touchMode?: boolean;
|
|
31
33
|
class?: string;
|
|
32
34
|
style?: JSX.CSSProperties;
|
|
33
35
|
children?: JSX.Element;
|
|
@@ -39,9 +41,11 @@ export const Checkbox: ParentComponent<CheckboxProps> = (props) => {
|
|
|
39
41
|
"onValueChange",
|
|
40
42
|
"disabled",
|
|
41
43
|
"size",
|
|
42
|
-
"theme",
|
|
43
44
|
"inset",
|
|
44
45
|
"inline",
|
|
46
|
+
"required",
|
|
47
|
+
"validate",
|
|
48
|
+
"touchMode",
|
|
45
49
|
"class",
|
|
46
50
|
"style",
|
|
47
51
|
"children",
|
|
@@ -75,32 +79,39 @@ export const Checkbox: ParentComponent<CheckboxProps> = (props) => {
|
|
|
75
79
|
local.class,
|
|
76
80
|
);
|
|
77
81
|
|
|
78
|
-
const getIndicatorClass = () =>
|
|
79
|
-
|
|
82
|
+
const getIndicatorClass = () =>
|
|
83
|
+
twMerge(indicatorBaseClass, "rounded-sm", value() && checkedClass);
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
const errorMsg = createMemo(() => {
|
|
86
|
+
const v = local.value ?? false;
|
|
87
|
+
if (local.required && !v) return "필수 선택 항목입니다";
|
|
88
|
+
return local.validate?.(v);
|
|
89
|
+
});
|
|
83
90
|
|
|
84
91
|
return (
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
92
|
+
<Invalid message={errorMsg()} variant="border" touchMode={local.touchMode}>
|
|
93
|
+
<div class="inline-flex">
|
|
94
|
+
<label
|
|
95
|
+
{...rest}
|
|
96
|
+
use:ripple={!local.disabled}
|
|
97
|
+
role="checkbox"
|
|
98
|
+
aria-checked={value()}
|
|
99
|
+
tabIndex={local.disabled ? -1 : 0}
|
|
100
|
+
class={getWrapperClass()}
|
|
101
|
+
style={local.style}
|
|
102
|
+
onClick={handleClick}
|
|
103
|
+
onKeyDown={handleKeyDown}
|
|
104
|
+
>
|
|
105
|
+
<div class={getIndicatorClass()}>
|
|
106
|
+
<Show when={value()}>
|
|
107
|
+
<Icon icon={IconCheck} size="1em" />
|
|
108
|
+
</Show>
|
|
109
|
+
</div>
|
|
110
|
+
<Show when={local.children}>
|
|
111
|
+
<span>{local.children}</span>
|
|
112
|
+
</Show>
|
|
113
|
+
</label>
|
|
100
114
|
</div>
|
|
101
|
-
|
|
102
|
-
<span>{local.children}</span>
|
|
103
|
-
</Show>
|
|
104
|
-
</label>
|
|
115
|
+
</Invalid>
|
|
105
116
|
);
|
|
106
117
|
};
|
|
@@ -1,60 +1,18 @@
|
|
|
1
|
-
import { type JSX
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
|
-
import { createControllableSignal } from "../../../hooks/createControllableSignal";
|
|
1
|
+
import { type JSX } from "solid-js";
|
|
4
2
|
import { Checkbox } from "./Checkbox";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
interface CheckboxGroupContextValue<TValue> {
|
|
8
|
-
value: () => TValue[];
|
|
9
|
-
toggle: (item: TValue) => void;
|
|
10
|
-
disabled: () => boolean;
|
|
11
|
-
size: () => CheckboxSize | undefined;
|
|
12
|
-
theme: () => CheckboxTheme | undefined;
|
|
13
|
-
inline: () => boolean;
|
|
14
|
-
inset: () => boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const CheckboxGroupContext = createContext<CheckboxGroupContextValue<any>>();
|
|
18
|
-
|
|
19
|
-
// --- CheckboxGroup.Item ---
|
|
20
|
-
|
|
21
|
-
interface CheckboxGroupItemProps<TValue> {
|
|
22
|
-
value: TValue;
|
|
23
|
-
disabled?: boolean;
|
|
24
|
-
children?: JSX.Element;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function CheckboxGroupItemInner<TValue>(props: CheckboxGroupItemProps<TValue>) {
|
|
28
|
-
const ctx = useContext(CheckboxGroupContext);
|
|
29
|
-
if (!ctx) throw new Error("CheckboxGroup.Item은 CheckboxGroup 내부에서만 사용할 수 있습니다");
|
|
30
|
-
|
|
31
|
-
const isSelected = () => ctx.value().includes(props.value);
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<Checkbox
|
|
35
|
-
value={isSelected()}
|
|
36
|
-
onValueChange={() => ctx.toggle(props.value)}
|
|
37
|
-
disabled={props.disabled ?? ctx.disabled()}
|
|
38
|
-
size={ctx.size()}
|
|
39
|
-
theme={ctx.theme()}
|
|
40
|
-
inline={ctx.inline()}
|
|
41
|
-
inset={ctx.inset()}
|
|
42
|
-
>
|
|
43
|
-
{props.children}
|
|
44
|
-
</Checkbox>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// --- CheckboxGroup ---
|
|
3
|
+
import { createSelectionGroup } from "../../../hooks/createSelectionGroup";
|
|
4
|
+
import type { CheckboxSize } from "./Checkbox.styles";
|
|
49
5
|
|
|
50
6
|
interface CheckboxGroupProps<TValue> {
|
|
51
7
|
value?: TValue[];
|
|
52
8
|
onValueChange?: (value: TValue[]) => void;
|
|
53
9
|
disabled?: boolean;
|
|
54
10
|
size?: CheckboxSize;
|
|
55
|
-
theme?: CheckboxTheme;
|
|
56
11
|
inline?: boolean;
|
|
57
12
|
inset?: boolean;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
validate?: (value: TValue[]) => string | undefined;
|
|
15
|
+
touchMode?: boolean;
|
|
58
16
|
class?: string;
|
|
59
17
|
style?: JSX.CSSProperties;
|
|
60
18
|
children?: JSX.Element;
|
|
@@ -62,55 +20,18 @@ interface CheckboxGroupProps<TValue> {
|
|
|
62
20
|
|
|
63
21
|
interface CheckboxGroupComponent {
|
|
64
22
|
<TValue = unknown>(props: CheckboxGroupProps<TValue>): JSX.Element;
|
|
65
|
-
Item:
|
|
23
|
+
Item: <TValue = unknown>(props: {
|
|
24
|
+
value: TValue;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
children?: JSX.Element;
|
|
27
|
+
}) => JSX.Element;
|
|
66
28
|
}
|
|
67
29
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"theme",
|
|
75
|
-
"inline",
|
|
76
|
-
"inset",
|
|
77
|
-
"class",
|
|
78
|
-
"style",
|
|
79
|
-
"children",
|
|
80
|
-
]);
|
|
81
|
-
|
|
82
|
-
const [value, setValue] = createControllableSignal({
|
|
83
|
-
value: () => local.value ?? [],
|
|
84
|
-
onChange: () => local.onValueChange,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const toggle = (item: unknown) => {
|
|
88
|
-
setValue((prev) => {
|
|
89
|
-
if (prev.includes(item)) {
|
|
90
|
-
return prev.filter((v) => v !== item);
|
|
91
|
-
}
|
|
92
|
-
return [...prev, item];
|
|
93
|
-
});
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const contextValue: CheckboxGroupContextValue<unknown> = {
|
|
97
|
-
value,
|
|
98
|
-
toggle,
|
|
99
|
-
disabled: () => local.disabled ?? false,
|
|
100
|
-
size: () => local.size,
|
|
101
|
-
theme: () => local.theme,
|
|
102
|
-
inline: () => local.inline ?? false,
|
|
103
|
-
inset: () => local.inset ?? false,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<CheckboxGroupContext.Provider value={contextValue}>
|
|
108
|
-
<div {...rest} class={twMerge("inline-flex", local.class)} style={local.style}>
|
|
109
|
-
{local.children}
|
|
110
|
-
</div>
|
|
111
|
-
</CheckboxGroupContext.Provider>
|
|
112
|
-
);
|
|
113
|
-
};
|
|
30
|
+
const { Group } = createSelectionGroup({
|
|
31
|
+
mode: "multiple",
|
|
32
|
+
contextName: "CheckboxGroup",
|
|
33
|
+
ItemComponent: Checkbox,
|
|
34
|
+
emptyErrorMsg: "항목을 선택해 주세요",
|
|
35
|
+
});
|
|
114
36
|
|
|
115
|
-
export const CheckboxGroup =
|
|
116
|
-
CheckboxGroup.Item = CheckboxGroupItemInner;
|
|
37
|
+
export const CheckboxGroup = Group as unknown as CheckboxGroupComponent;
|