@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.
Files changed (220) hide show
  1. package/README.md +10 -5
  2. package/dist/components/data/Pagination.d.ts +4 -5
  3. package/dist/components/data/Pagination.d.ts.map +1 -1
  4. package/dist/components/data/Pagination.js +14 -14
  5. package/dist/components/data/Pagination.js.map +2 -2
  6. package/dist/components/data/Table.js +1 -1
  7. package/dist/components/data/calendar/Calendar.js +1 -1
  8. package/dist/components/data/kanban/Kanban.d.ts +9 -9
  9. package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
  10. package/dist/components/data/kanban/Kanban.js +4 -4
  11. package/dist/components/data/kanban/Kanban.js.map +2 -2
  12. package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
  13. package/dist/components/data/sheet/DataSheet.js +102 -107
  14. package/dist/components/data/sheet/DataSheet.js.map +2 -2
  15. package/dist/components/data/sheet/DataSheet.styles.js +1 -1
  16. package/dist/components/data/sheet/types.d.ts +2 -2
  17. package/dist/components/data/sheet/types.d.ts.map +1 -1
  18. package/dist/components/disclosure/Dialog.d.ts +8 -8
  19. package/dist/components/disclosure/Dialog.d.ts.map +1 -1
  20. package/dist/components/disclosure/Dialog.js +64 -69
  21. package/dist/components/disclosure/Dialog.js.map +2 -2
  22. package/dist/components/disclosure/DialogContext.d.ts +4 -4
  23. package/dist/components/disclosure/DialogContext.d.ts.map +1 -1
  24. package/dist/components/disclosure/DialogProvider.js +8 -8
  25. package/dist/components/disclosure/DialogProvider.js.map +2 -2
  26. package/dist/components/feedback/Progress.d.ts +3 -3
  27. package/dist/components/feedback/Progress.d.ts.map +1 -1
  28. package/dist/components/feedback/Progress.js +1 -1
  29. package/dist/components/feedback/Progress.js.map +2 -2
  30. package/dist/components/feedback/busy/BusyContainer.d.ts +1 -0
  31. package/dist/components/feedback/busy/BusyContainer.d.ts.map +1 -1
  32. package/dist/components/feedback/busy/BusyContainer.js +13 -6
  33. package/dist/components/feedback/busy/BusyContainer.js.map +2 -2
  34. package/dist/components/feedback/notification/NotificationBanner.js +1 -1
  35. package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
  36. package/dist/components/feedback/notification/NotificationBell.d.ts.map +1 -1
  37. package/dist/components/feedback/notification/NotificationBell.js +4 -2
  38. package/dist/components/feedback/notification/NotificationBell.js.map +2 -2
  39. package/dist/components/feedback/notification/NotificationProvider.d.ts.map +1 -1
  40. package/dist/components/feedback/notification/NotificationProvider.js +1 -0
  41. package/dist/components/feedback/notification/NotificationProvider.js.map +1 -1
  42. package/dist/components/form-control/Invalid.d.ts +4 -2
  43. package/dist/components/form-control/Invalid.d.ts.map +1 -1
  44. package/dist/components/form-control/Invalid.js +81 -41
  45. package/dist/components/form-control/Invalid.js.map +2 -2
  46. package/dist/components/form-control/ThemeToggle.d.ts.map +1 -1
  47. package/dist/components/form-control/ThemeToggle.js +4 -5
  48. package/dist/components/form-control/ThemeToggle.js.map +2 -2
  49. package/dist/components/form-control/checkbox/Checkbox.d.ts +4 -2
  50. package/dist/components/form-control/checkbox/Checkbox.d.ts.map +1 -1
  51. package/dist/components/form-control/checkbox/Checkbox.js +65 -52
  52. package/dist/components/form-control/checkbox/Checkbox.js.map +2 -2
  53. package/dist/components/form-control/checkbox/Checkbox.styles.d.ts +1 -2
  54. package/dist/components/form-control/checkbox/Checkbox.styles.d.ts.map +1 -1
  55. package/dist/components/form-control/checkbox/Checkbox.styles.js +3 -9
  56. package/dist/components/form-control/checkbox/Checkbox.styles.js.map +1 -1
  57. package/dist/components/form-control/checkbox/CheckboxGroup.d.ts +9 -9
  58. package/dist/components/form-control/checkbox/CheckboxGroup.d.ts.map +1 -1
  59. package/dist/components/form-control/checkbox/CheckboxGroup.js +10 -82
  60. package/dist/components/form-control/checkbox/CheckboxGroup.js.map +2 -2
  61. package/dist/components/form-control/checkbox/Radio.d.ts +4 -2
  62. package/dist/components/form-control/checkbox/Radio.d.ts.map +1 -1
  63. package/dist/components/form-control/checkbox/Radio.js +64 -51
  64. package/dist/components/form-control/checkbox/Radio.js.map +2 -2
  65. package/dist/components/form-control/checkbox/RadioGroup.d.ts +9 -9
  66. package/dist/components/form-control/checkbox/RadioGroup.d.ts.map +1 -1
  67. package/dist/components/form-control/checkbox/RadioGroup.js +10 -77
  68. package/dist/components/form-control/checkbox/RadioGroup.js.map +2 -2
  69. package/dist/components/form-control/color-picker/ColorPicker.d.ts +8 -3
  70. package/dist/components/form-control/color-picker/ColorPicker.d.ts.map +1 -1
  71. package/dist/components/form-control/color-picker/ColorPicker.js +43 -26
  72. package/dist/components/form-control/color-picker/ColorPicker.js.map +2 -2
  73. package/dist/components/form-control/combobox/Combobox.d.ts +8 -8
  74. package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
  75. package/dist/components/form-control/combobox/Combobox.js +72 -59
  76. package/dist/components/form-control/combobox/Combobox.js.map +2 -2
  77. package/dist/components/form-control/editor/EditorToolbar.d.ts.map +1 -1
  78. package/dist/components/form-control/editor/EditorToolbar.js +3 -2
  79. package/dist/components/form-control/editor/EditorToolbar.js.map +2 -2
  80. package/dist/components/form-control/field/DatePicker.d.ts +6 -0
  81. package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
  82. package/dist/components/form-control/field/DatePicker.js +138 -117
  83. package/dist/components/form-control/field/DatePicker.js.map +2 -2
  84. package/dist/components/form-control/field/DateTimePicker.d.ts +6 -0
  85. package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
  86. package/dist/components/form-control/field/DateTimePicker.js +138 -115
  87. package/dist/components/form-control/field/DateTimePicker.js.map +2 -2
  88. package/dist/components/form-control/field/Field.styles.d.ts +14 -0
  89. package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
  90. package/dist/components/form-control/field/Field.styles.js +30 -0
  91. package/dist/components/form-control/field/Field.styles.js.map +1 -1
  92. package/dist/components/form-control/field/FieldPlaceholder.d.ts +7 -0
  93. package/dist/components/form-control/field/FieldPlaceholder.d.ts.map +1 -0
  94. package/dist/components/form-control/field/FieldPlaceholder.js +34 -0
  95. package/dist/components/form-control/field/FieldPlaceholder.js.map +6 -0
  96. package/dist/components/form-control/field/NumberInput.d.ts +10 -0
  97. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  98. package/dist/components/form-control/field/NumberInput.js +149 -115
  99. package/dist/components/form-control/field/NumberInput.js.map +2 -2
  100. package/dist/components/form-control/field/TextInput.d.ts +12 -0
  101. package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
  102. package/dist/components/form-control/field/TextInput.js +162 -116
  103. package/dist/components/form-control/field/TextInput.js.map +2 -2
  104. package/dist/components/form-control/field/Textarea.d.ts +10 -0
  105. package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
  106. package/dist/components/form-control/field/Textarea.js +156 -121
  107. package/dist/components/form-control/field/Textarea.js.map +2 -2
  108. package/dist/components/form-control/field/TimePicker.d.ts +10 -0
  109. package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
  110. package/dist/components/form-control/field/TimePicker.js +126 -94
  111. package/dist/components/form-control/field/TimePicker.js.map +2 -2
  112. package/dist/components/form-control/select/Select.d.ts +7 -9
  113. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  114. package/dist/components/form-control/select/Select.js +71 -60
  115. package/dist/components/form-control/select/Select.js.map +2 -2
  116. package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
  117. package/dist/components/form-control/state-preset/StatePreset.js +2 -1
  118. package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
  119. package/dist/components/layout/sidebar/SidebarMenu.js +1 -1
  120. package/dist/components/layout/sidebar/SidebarMenu.js.map +1 -1
  121. package/dist/components/layout/sidebar/SidebarUser.js +2 -2
  122. package/dist/components/layout/sidebar/SidebarUser.js.map +1 -1
  123. package/dist/hooks/createItemTemplate.d.ts +17 -0
  124. package/dist/hooks/createItemTemplate.d.ts.map +1 -0
  125. package/dist/hooks/createItemTemplate.js +40 -0
  126. package/dist/hooks/createItemTemplate.js.map +6 -0
  127. package/dist/hooks/createPointerDrag.d.ts +13 -0
  128. package/dist/hooks/createPointerDrag.d.ts.map +1 -0
  129. package/dist/hooks/createPointerDrag.js +15 -0
  130. package/dist/hooks/createPointerDrag.js.map +6 -0
  131. package/dist/hooks/createSelectionGroup.d.ts +70 -0
  132. package/dist/hooks/createSelectionGroup.d.ts.map +1 -0
  133. package/dist/hooks/createSelectionGroup.js +141 -0
  134. package/dist/hooks/createSelectionGroup.js.map +6 -0
  135. package/dist/hooks/useLocalStorage.d.ts +5 -3
  136. package/dist/hooks/useLocalStorage.d.ts.map +1 -1
  137. package/dist/hooks/useLocalStorage.js.map +1 -1
  138. package/dist/hooks/{createPwaUpdate.d.ts → usePwaUpdate.d.ts} +2 -2
  139. package/dist/hooks/usePwaUpdate.d.ts.map +1 -0
  140. package/dist/hooks/{createPwaUpdate.js → usePwaUpdate.js} +3 -3
  141. package/dist/hooks/usePwaUpdate.js.map +6 -0
  142. package/dist/hooks/useSyncConfig.d.ts +3 -3
  143. package/dist/hooks/useSyncConfig.d.ts.map +1 -1
  144. package/dist/hooks/useSyncConfig.js +6 -7
  145. package/dist/hooks/useSyncConfig.js.map +1 -1
  146. package/dist/index.d.ts +1 -3
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +2 -4
  149. package/dist/index.js.map +1 -1
  150. package/dist/providers/InitializeProvider.js +2 -2
  151. package/dist/providers/InitializeProvider.js.map +2 -2
  152. package/dist/providers/ThemeContext.d.ts.map +1 -1
  153. package/dist/providers/ThemeContext.js +2 -1
  154. package/dist/providers/ThemeContext.js.map +2 -2
  155. package/dist/styles/patterns.styles.d.ts +1 -0
  156. package/dist/styles/patterns.styles.d.ts.map +1 -1
  157. package/dist/styles/patterns.styles.js +11 -0
  158. package/dist/styles/patterns.styles.js.map +1 -1
  159. package/dist/styles/tokens.styles.d.ts +1 -0
  160. package/dist/styles/tokens.styles.d.ts.map +1 -1
  161. package/dist/styles/tokens.styles.js.map +1 -1
  162. package/docs/data-components.md +34 -5
  163. package/docs/disclosure.md +28 -8
  164. package/docs/feedback.md +25 -2
  165. package/docs/form-controls.md +289 -33
  166. package/docs/hooks.md +19 -7
  167. package/docs/layout.md +12 -0
  168. package/docs/providers.md +120 -8
  169. package/docs/styling.md +90 -0
  170. package/package.json +3 -3
  171. package/src/components/data/Pagination.tsx +20 -21
  172. package/src/components/data/Table.tsx +1 -1
  173. package/src/components/data/calendar/Calendar.tsx +1 -1
  174. package/src/components/data/kanban/Kanban.tsx +18 -25
  175. package/src/components/data/sheet/DataSheet.styles.ts +1 -1
  176. package/src/components/data/sheet/DataSheet.tsx +122 -131
  177. package/src/components/data/sheet/types.ts +2 -2
  178. package/src/components/disclosure/Dialog.tsx +87 -100
  179. package/src/components/disclosure/DialogContext.ts +4 -4
  180. package/src/components/disclosure/DialogProvider.tsx +4 -4
  181. package/src/components/feedback/Progress.tsx +9 -5
  182. package/src/components/feedback/busy/BusyContainer.tsx +9 -5
  183. package/src/components/feedback/notification/NotificationBanner.tsx +1 -1
  184. package/src/components/feedback/notification/NotificationBell.tsx +4 -12
  185. package/src/components/feedback/notification/NotificationProvider.tsx +1 -0
  186. package/src/components/form-control/Invalid.tsx +114 -52
  187. package/src/components/form-control/ThemeToggle.tsx +4 -17
  188. package/src/components/form-control/checkbox/Checkbox.styles.ts +2 -9
  189. package/src/components/form-control/checkbox/Checkbox.tsx +39 -28
  190. package/src/components/form-control/checkbox/CheckboxGroup.tsx +18 -97
  191. package/src/components/form-control/checkbox/Radio.tsx +39 -28
  192. package/src/components/form-control/checkbox/RadioGroup.tsx +18 -92
  193. package/src/components/form-control/color-picker/ColorPicker.tsx +36 -16
  194. package/src/components/form-control/combobox/Combobox.tsx +43 -33
  195. package/src/components/form-control/editor/EditorToolbar.tsx +3 -14
  196. package/src/components/form-control/field/DatePicker.tsx +99 -97
  197. package/src/components/form-control/field/DateTimePicker.tsx +107 -95
  198. package/src/components/form-control/field/Field.styles.ts +45 -0
  199. package/src/components/form-control/field/FieldPlaceholder.tsx +18 -0
  200. package/src/components/form-control/field/NumberInput.tsx +122 -94
  201. package/src/components/form-control/field/TextInput.tsx +119 -95
  202. package/src/components/form-control/field/Textarea.tsx +124 -98
  203. package/src/components/form-control/field/TimePicker.tsx +101 -75
  204. package/src/components/form-control/select/Select.tsx +52 -44
  205. package/src/components/form-control/state-preset/StatePreset.tsx +2 -8
  206. package/src/components/layout/sidebar/SidebarMenu.tsx +1 -1
  207. package/src/components/layout/sidebar/SidebarUser.tsx +3 -3
  208. package/src/hooks/createItemTemplate.tsx +42 -0
  209. package/src/hooks/createPointerDrag.ts +28 -0
  210. package/src/hooks/createSelectionGroup.tsx +235 -0
  211. package/src/hooks/useLocalStorage.ts +8 -4
  212. package/src/hooks/{createPwaUpdate.ts → usePwaUpdate.ts} +1 -1
  213. package/src/hooks/useSyncConfig.ts +9 -13
  214. package/src/index.ts +1 -3
  215. package/src/providers/InitializeProvider.tsx +2 -2
  216. package/src/providers/ThemeContext.tsx +2 -1
  217. package/src/styles/patterns.styles.ts +12 -0
  218. package/src/styles/tokens.styles.ts +1 -0
  219. package/dist/hooks/createPwaUpdate.d.ts.map +0 -1
  220. package/dist/hooks/createPwaUpdate.js.map +0 -6
@@ -1,20 +1,20 @@
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 { createControllableSignal } from "../../../hooks/createControllableSignal";
4
4
  import { ripple } from "../../../directives/ripple";
5
5
  import clsx from "clsx";
6
6
  import {
7
- type CheckboxTheme,
8
7
  type CheckboxSize,
9
8
  checkboxBaseClass,
10
9
  indicatorBaseClass,
11
- themeCheckedClasses,
10
+ checkedClass,
12
11
  checkboxSizeClasses,
13
12
  checkboxInsetClass,
14
13
  checkboxInsetSizeHeightClasses,
15
14
  checkboxInlineClass,
16
15
  checkboxDisabledClass,
17
16
  } from "./Checkbox.styles";
17
+ import { Invalid } from "../Invalid";
18
18
 
19
19
  const radioDotClass = clsx("size-2", "rounded-full", "bg-current");
20
20
 
@@ -26,9 +26,11 @@ export interface RadioProps {
26
26
  onValueChange?: (value: boolean) => void;
27
27
  disabled?: boolean;
28
28
  size?: CheckboxSize;
29
- theme?: CheckboxTheme;
30
29
  inset?: boolean;
31
30
  inline?: boolean;
31
+ required?: boolean;
32
+ validate?: (value: boolean) => string | undefined;
33
+ touchMode?: boolean;
32
34
  class?: string;
33
35
  style?: JSX.CSSProperties;
34
36
  children?: JSX.Element;
@@ -40,9 +42,11 @@ export const Radio: ParentComponent<RadioProps> = (props) => {
40
42
  "onValueChange",
41
43
  "disabled",
42
44
  "size",
43
- "theme",
44
45
  "inset",
45
46
  "inline",
47
+ "required",
48
+ "validate",
49
+ "touchMode",
46
50
  "class",
47
51
  "style",
48
52
  "children",
@@ -76,32 +80,39 @@ export const Radio: ParentComponent<RadioProps> = (props) => {
76
80
  local.class,
77
81
  );
78
82
 
79
- const getIndicatorClass = () => {
80
- const theme = local.theme ?? "primary";
83
+ const getIndicatorClass = () =>
84
+ twMerge(indicatorBaseClass, "rounded-full", value() && checkedClass);
81
85
 
82
- return twMerge(indicatorBaseClass, "rounded-full", value() && themeCheckedClasses[theme]);
83
- };
86
+ const errorMsg = createMemo(() => {
87
+ const v = local.value ?? false;
88
+ if (local.required && !v) return "필수 선택 항목입니다";
89
+ return local.validate?.(v);
90
+ });
84
91
 
85
92
  return (
86
- <label
87
- {...rest}
88
- use:ripple={!local.disabled}
89
- role="radio"
90
- aria-checked={value()}
91
- tabIndex={local.disabled ? -1 : 0}
92
- class={getWrapperClass()}
93
- style={local.style}
94
- onClick={handleClick}
95
- onKeyDown={handleKeyDown}
96
- >
97
- <div class={getIndicatorClass()}>
98
- <Show when={value()}>
99
- <div class={radioDotClass} />
100
- </Show>
93
+ <Invalid message={errorMsg()} variant="border" touchMode={local.touchMode}>
94
+ <div class="inline-flex">
95
+ <label
96
+ {...rest}
97
+ use:ripple={!local.disabled}
98
+ role="radio"
99
+ aria-checked={value()}
100
+ tabIndex={local.disabled ? -1 : 0}
101
+ class={getWrapperClass()}
102
+ style={local.style}
103
+ onClick={handleClick}
104
+ onKeyDown={handleKeyDown}
105
+ >
106
+ <div class={getIndicatorClass()}>
107
+ <Show when={value()}>
108
+ <div class={radioDotClass} />
109
+ </Show>
110
+ </div>
111
+ <Show when={local.children}>
112
+ <span>{local.children}</span>
113
+ </Show>
114
+ </label>
101
115
  </div>
102
- <Show when={local.children}>
103
- <span>{local.children}</span>
104
- </Show>
105
- </label>
116
+ </Invalid>
106
117
  );
107
118
  };
@@ -1,60 +1,18 @@
1
- import { type JSX, type ParentComponent, createContext, splitProps, useContext } from "solid-js";
2
- import { twMerge } from "tailwind-merge";
3
- import { createControllableSignal } from "../../../hooks/createControllableSignal";
1
+ import { type JSX } from "solid-js";
4
2
  import { Radio } from "./Radio";
5
- import type { CheckboxSize, CheckboxTheme } from "./Checkbox.styles";
6
-
7
- interface RadioGroupContextValue<TValue> {
8
- value: () => TValue | undefined;
9
- select: (item: TValue) => void;
10
- disabled: () => boolean;
11
- size: () => CheckboxSize | undefined;
12
- theme: () => CheckboxTheme | undefined;
13
- inline: () => boolean;
14
- inset: () => boolean;
15
- }
16
-
17
- const RadioGroupContext = createContext<RadioGroupContextValue<any>>();
18
-
19
- // --- RadioGroup.Item ---
20
-
21
- interface RadioGroupItemProps<TValue> {
22
- value: TValue;
23
- disabled?: boolean;
24
- children?: JSX.Element;
25
- }
26
-
27
- function RadioGroupItemInner<TValue>(props: RadioGroupItemProps<TValue>) {
28
- const ctx = useContext(RadioGroupContext);
29
- if (!ctx) throw new Error("RadioGroup.Item은 RadioGroup 내부에서만 사용할 수 있습니다");
30
-
31
- const isSelected = () => ctx.value() === props.value;
32
-
33
- return (
34
- <Radio
35
- value={isSelected()}
36
- onValueChange={() => ctx.select(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
- </Radio>
45
- );
46
- }
47
-
48
- // --- RadioGroup ---
3
+ import { createSelectionGroup } from "../../../hooks/createSelectionGroup";
4
+ import type { CheckboxSize } from "./Checkbox.styles";
49
5
 
50
6
  interface RadioGroupProps<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 | undefined) => string | undefined;
15
+ touchMode?: boolean;
58
16
  class?: string;
59
17
  style?: JSX.CSSProperties;
60
18
  children?: JSX.Element;
@@ -62,50 +20,18 @@ interface RadioGroupProps<TValue> {
62
20
 
63
21
  interface RadioGroupComponent {
64
22
  <TValue = unknown>(props: RadioGroupProps<TValue>): JSX.Element;
65
- Item: typeof RadioGroupItemInner;
23
+ Item: <TValue = unknown>(props: {
24
+ value: TValue;
25
+ disabled?: boolean;
26
+ children?: JSX.Element;
27
+ }) => JSX.Element;
66
28
  }
67
29
 
68
- const RadioGroupInner: ParentComponent<RadioGroupProps<unknown>> = (props) => {
69
- const [local, rest] = splitProps(props, [
70
- "value",
71
- "onValueChange",
72
- "disabled",
73
- "size",
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 select = (item: unknown) => {
88
- setValue(item);
89
- };
90
-
91
- const contextValue: RadioGroupContextValue<unknown> = {
92
- value,
93
- select,
94
- disabled: () => local.disabled ?? false,
95
- size: () => local.size,
96
- theme: () => local.theme,
97
- inline: () => local.inline ?? false,
98
- inset: () => local.inset ?? false,
99
- };
100
-
101
- return (
102
- <RadioGroupContext.Provider value={contextValue}>
103
- <div {...rest} class={twMerge("inline-flex", local.class)} style={local.style}>
104
- {local.children}
105
- </div>
106
- </RadioGroupContext.Provider>
107
- );
108
- };
30
+ const { Group } = createSelectionGroup({
31
+ mode: "single",
32
+ contextName: "RadioGroup",
33
+ ItemComponent: Radio,
34
+ emptyErrorMsg: "항목을 선택해 주세요",
35
+ });
109
36
 
110
- export const RadioGroup = RadioGroupInner as unknown as RadioGroupComponent;
111
- RadioGroup.Item = RadioGroupItemInner;
37
+ export const RadioGroup = Group as unknown as RadioGroupComponent;
@@ -1,9 +1,9 @@
1
- import { type Component, type JSX, splitProps } from "solid-js";
1
+ import { type Component, createMemo, type JSX, splitProps } from "solid-js";
2
2
  import clsx from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
5
-
6
- type ColorPickerSize = "sm" | "lg";
5
+ import { Invalid } from "../Invalid";
6
+ import { type ComponentSizeCompact } from "../../../styles/tokens.styles";
7
7
 
8
8
  // 기본 스타일
9
9
  const baseClass = clsx(
@@ -20,7 +20,7 @@ const baseClass = clsx(
20
20
  );
21
21
 
22
22
  // 사이즈별 스타일
23
- const sizeClasses: Record<ColorPickerSize, string> = {
23
+ const sizeClasses: Record<ComponentSizeCompact, string> = {
24
24
  sm: "size-field-sm",
25
25
  lg: "size-field-lg",
26
26
  };
@@ -48,7 +48,16 @@ export interface ColorPickerProps {
48
48
  disabled?: boolean;
49
49
 
50
50
  /** 사이즈 */
51
- size?: ColorPickerSize;
51
+ size?: ComponentSizeCompact;
52
+
53
+ /** 필수 입력 여부 */
54
+ required?: boolean;
55
+
56
+ /** 커스텀 유효성 검사 함수 */
57
+ validate?: (value: string | undefined) => string | undefined;
58
+
59
+ /** touchMode: 포커스 해제 후에만 에러 표시 */
60
+ touchMode?: boolean;
52
61
 
53
62
  /** 커스텀 class */
54
63
  class?: string;
@@ -72,6 +81,9 @@ export const ColorPicker: Component<ColorPickerProps> = (props) => {
72
81
  "title",
73
82
  "disabled",
74
83
  "size",
84
+ "required",
85
+ "validate",
86
+ "touchMode",
75
87
  "class",
76
88
  "style",
77
89
  ]);
@@ -93,17 +105,25 @@ export const ColorPicker: Component<ColorPickerProps> = (props) => {
93
105
  local.class,
94
106
  );
95
107
 
108
+ const errorMsg = createMemo(() => {
109
+ const v = props.value;
110
+ if (local.required && (v === undefined || v === "")) return "필수 입력 항목입니다";
111
+ return local.validate?.(v);
112
+ });
113
+
96
114
  return (
97
- <input
98
- {...rest}
99
- data-color-picker
100
- type="color"
101
- class={getClassName()}
102
- style={local.style}
103
- value={value()}
104
- title={local.title}
105
- disabled={local.disabled}
106
- onInput={handleInput}
107
- />
115
+ <Invalid variant="border" message={errorMsg()} touchMode={local.touchMode}>
116
+ <input
117
+ {...rest}
118
+ data-color-picker
119
+ type="color"
120
+ class={getClassName()}
121
+ style={local.style}
122
+ value={value()}
123
+ title={local.title}
124
+ disabled={local.disabled}
125
+ onInput={handleInput}
126
+ />
127
+ </Invalid>
108
128
  );
109
129
  };
@@ -1,4 +1,13 @@
1
- import { children, createSignal, For, type JSX, onCleanup, Show, splitProps } from "solid-js";
1
+ import {
2
+ children,
3
+ createMemo,
4
+ createSignal,
5
+ For,
6
+ type JSX,
7
+ onCleanup,
8
+ Show,
9
+ splitProps,
10
+ } from "solid-js";
2
11
  import clsx from "clsx";
3
12
  import { IconChevronDown, IconLoader2 } from "@tabler/icons-solidjs";
4
13
  import { DebounceQueue } from "@simplysm/core-common";
@@ -9,9 +18,11 @@ import { ComboboxContext, type ComboboxContextValue } from "./ComboboxContext";
9
18
  import { ComboboxItem } from "./ComboboxItem";
10
19
  import { ripple } from "../../../directives/ripple";
11
20
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
21
+ import { createItemTemplate } from "../../../hooks/createItemTemplate";
12
22
  import { splitSlots } from "../../../helpers/splitSlots";
13
23
  import { type ComponentSize, textMuted } from "../../../styles/tokens.styles";
14
24
  import { chevronWrapperClass, getTriggerClass } from "../DropdownTrigger.styles";
25
+ import { Invalid } from "../Invalid";
15
26
 
16
27
  void ripple;
17
28
 
@@ -25,25 +36,8 @@ const inputClass = clsx(
25
36
 
26
37
  const noResultsClass = clsx("px-3 py-2", textMuted);
27
38
 
28
- /**
29
- * Combobox 아이템 렌더링 템플릿
30
- */
31
- interface ComboboxItemTemplateProps<TItem> {
32
- children: (item: TItem, index: number) => JSX.Element;
33
- }
34
-
35
- // 템플릿 함수를 저장하는 전역 WeakMap
36
- const templateFnMap = new WeakMap<HTMLElement, (item: unknown, index: number) => JSX.Element>();
37
-
38
- const ComboboxItemTemplate = <T,>(props: ComboboxItemTemplateProps<T>) => (
39
- <span
40
- ref={(el) => {
41
- templateFnMap.set(el, props.children as (item: unknown, index: number) => JSX.Element);
42
- }}
43
- data-combobox-item-template
44
- style={{ display: "none" }}
45
- />
46
- );
39
+ const { TemplateSlot: ComboboxItemTemplate, getTemplate: getComboboxItemTemplate } =
40
+ createItemTemplate<[item: unknown, index: number]>("data-combobox-item-template");
47
41
 
48
42
  // Props 정의
49
43
  export interface ComboboxProps<TValue = unknown> {
@@ -54,7 +48,7 @@ export interface ComboboxProps<TValue = unknown> {
54
48
  onValueChange?: (value: TValue) => void;
55
49
 
56
50
  /** 아이템 로드 함수 (필수) */
57
- loadItems: (query: string) => Promise<TValue[]>;
51
+ loadItems: (query: string) => TValue[] | Promise<TValue[]>;
58
52
 
59
53
  /** 디바운스 딜레이 (기본값: 300ms) */
60
54
  debounceMs?: number;
@@ -74,6 +68,12 @@ export interface ComboboxProps<TValue = unknown> {
74
68
  /** 필수 입력 */
75
69
  required?: boolean;
76
70
 
71
+ /** 커스텀 유효성 검사 함수 */
72
+ validate?: (value: TValue | undefined) => string | undefined;
73
+
74
+ /** touchMode: 포커스 해제 후에만 에러 표시 */
75
+ touchMode?: boolean;
76
+
77
77
  /** 플레이스홀더 */
78
78
  placeholder?: string;
79
79
 
@@ -146,6 +146,8 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
146
146
  "placeholder",
147
147
  "size",
148
148
  "inset",
149
+ "validate",
150
+ "touchMode",
149
151
  ]);
150
152
 
151
153
  let triggerRef!: HTMLDivElement;
@@ -154,7 +156,7 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
154
156
  const [open, setOpen] = createSignal(false);
155
157
  const [query, setQuery] = createSignal("");
156
158
  const [items, setItems] = createSignal<T[]>([]);
157
- const [busy, setBusy] = createSignal(false);
159
+ const [busyCount, setBusyCount] = createSignal(0);
158
160
 
159
161
  // 선택된 값 관리 (controlled/uncontrolled 패턴)
160
162
  const [getValue, setInternalValue] = createControllableSignal<T | undefined>({
@@ -200,12 +202,12 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
200
202
  // loadItems 함수 참조를 캡처하여 사용
201
203
  const loadItemsFn = local.loadItems;
202
204
  debounceQueue.run(async () => {
203
- setBusy(true);
205
+ setBusyCount((c) => c + 1);
204
206
  try {
205
- const result = await loadItemsFn(searchQuery);
207
+ const result = await Promise.resolve(loadItemsFn(searchQuery));
206
208
  setItems(result);
207
209
  } finally {
208
- setBusy(false);
210
+ setBusyCount((c) => c - 1);
209
211
  }
210
212
  });
211
213
  };
@@ -261,6 +263,14 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
261
263
  }
262
264
  };
263
265
 
266
+ // 유효성 검사 메시지
267
+ const errorMsg = createMemo(() => {
268
+ const v = getValue();
269
+ if (local.required && (v === undefined || v === null || v === ""))
270
+ return "필수 입력 항목입니다";
271
+ return local.validate?.(v);
272
+ });
273
+
264
274
  // 트리거 클래스
265
275
  const getTriggerClassName = () =>
266
276
  getTriggerClass({
@@ -280,9 +290,7 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
280
290
 
281
291
  // itemTemplate 함수 추출
282
292
  const getItemTemplate = (): ((item: T, index: number) => JSX.Element) | undefined => {
283
- const templateSlots = slots().comboboxItemTemplate;
284
- if (templateSlots.length === 0) return undefined;
285
- return templateFnMap.get(templateSlots[0]) as
293
+ return getComboboxItemTemplate(slots().comboboxItemTemplate) as
286
294
  | ((item: T, index: number) => JSX.Element)
287
295
  | undefined;
288
296
  };
@@ -320,7 +328,7 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
320
328
  const itemTemplate = getItemTemplate();
321
329
 
322
330
  // 로딩 중
323
- if (busy()) {
331
+ if (busyCount() > 0) {
324
332
  return <div class={noResultsClass}>검색 중...</div>;
325
333
  }
326
334
 
@@ -372,7 +380,7 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
372
380
  >
373
381
  <div class={selectedValueClass}>{renderDisplayContent()}</div>
374
382
  <div class={chevronWrapperClass}>
375
- <Show when={busy()} fallback={<Icon icon={IconChevronDown} size="1em" />}>
383
+ <Show when={busyCount() > 0} fallback={<Icon icon={IconChevronDown} size="1em" />}>
376
384
  <Icon icon={IconLoader2} size="1em" class="animate-spin" />
377
385
  </Show>
378
386
  </div>
@@ -388,9 +396,11 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
388
396
  };
389
397
 
390
398
  return (
391
- <ComboboxContext.Provider value={contextValue as ComboboxContextValue}>
392
- <ComboboxInner>{local.children}</ComboboxInner>
393
- </ComboboxContext.Provider>
399
+ <Invalid message={errorMsg()} variant="border" touchMode={local.touchMode}>
400
+ <ComboboxContext.Provider value={contextValue as ComboboxContextValue}>
401
+ <ComboboxInner>{local.children}</ComboboxInner>
402
+ </ComboboxContext.Provider>
403
+ </Invalid>
394
404
  );
395
405
  };
396
406
 
@@ -3,6 +3,7 @@ import clsx from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import type { Editor } from "@tiptap/core";
5
5
  import { createEditorTransaction } from "solid-tiptap";
6
+ import { iconButtonBase } from "../../../styles/patterns.styles";
6
7
  import {
7
8
  IconBold,
8
9
  IconItalic,
@@ -39,13 +40,7 @@ const toolbarClass = clsx(
39
40
  );
40
41
 
41
42
  // 툴바 버튼 기본 스타일
42
- const toolbarBtnClass = clsx(
43
- "inline-flex items-center justify-center",
44
- "size-7 rounded",
45
- "text-base-600 dark:text-base-400",
46
- "hover:bg-base-100 dark:hover:bg-base-800",
47
- "transition-colors",
48
- );
43
+ const toolbarBtnClass = twMerge(iconButtonBase, "size-7");
49
44
 
50
45
  // 툴바 버튼 활성 스타일
51
46
  const toolbarBtnActiveClass = clsx(
@@ -57,13 +52,7 @@ const toolbarBtnActiveClass = clsx(
57
52
  const separatorClass = clsx("mx-1 h-5 w-px", "bg-base-300 dark:bg-base-700");
58
53
 
59
54
  // 색상 선택 label 스타일
60
- const colorLabelClass = clsx(
61
- "relative inline-flex items-center justify-center",
62
- "size-7 cursor-pointer rounded",
63
- "text-base-600 dark:text-base-400",
64
- "hover:bg-base-100 dark:hover:bg-base-800",
65
- "transition-colors",
66
- );
55
+ const colorLabelClass = twMerge(iconButtonBase, "relative", "size-7");
67
56
 
68
57
  // 색상 input 숨기기 스타일
69
58
  const colorInputClass = clsx("absolute opacity-0", "size-0");