@simplysm/solid 13.0.53 → 13.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +6 -2
  2. package/dist/components/data/crud-detail/CrudDetail.d.ts +14 -0
  3. package/dist/components/data/crud-detail/CrudDetail.d.ts.map +1 -0
  4. package/dist/components/data/crud-detail/CrudDetail.js +348 -0
  5. package/dist/components/data/crud-detail/CrudDetail.js.map +6 -0
  6. package/dist/components/data/crud-detail/CrudDetailAfter.d.ts +7 -0
  7. package/dist/components/data/crud-detail/CrudDetailAfter.d.ts.map +1 -0
  8. package/dist/components/data/crud-detail/CrudDetailAfter.js +14 -0
  9. package/dist/components/data/crud-detail/CrudDetailAfter.js.map +6 -0
  10. package/dist/components/data/crud-detail/CrudDetailBefore.d.ts +7 -0
  11. package/dist/components/data/crud-detail/CrudDetailBefore.d.ts.map +1 -0
  12. package/dist/components/data/crud-detail/CrudDetailBefore.js +14 -0
  13. package/dist/components/data/crud-detail/CrudDetailBefore.js.map +6 -0
  14. package/dist/components/data/crud-detail/CrudDetailTools.d.ts +7 -0
  15. package/dist/components/data/crud-detail/CrudDetailTools.d.ts.map +1 -0
  16. package/dist/components/data/crud-detail/CrudDetailTools.js +14 -0
  17. package/dist/components/data/crud-detail/CrudDetailTools.js.map +6 -0
  18. package/dist/components/data/crud-detail/types.d.ts +45 -0
  19. package/dist/components/data/crud-detail/types.d.ts.map +1 -0
  20. package/dist/components/data/crud-detail/types.js +1 -0
  21. package/dist/components/data/crud-detail/types.js.map +6 -0
  22. package/dist/components/data/crud-sheet/CrudSheet.d.ts +17 -0
  23. package/dist/components/data/crud-sheet/CrudSheet.d.ts.map +1 -0
  24. package/dist/components/data/crud-sheet/CrudSheet.js +679 -0
  25. package/dist/components/data/crud-sheet/CrudSheet.js.map +6 -0
  26. package/dist/components/data/crud-sheet/CrudSheetColumn.d.ts +5 -0
  27. package/dist/components/data/crud-sheet/CrudSheetColumn.d.ts.map +1 -0
  28. package/dist/components/data/crud-sheet/CrudSheetColumn.js +29 -0
  29. package/dist/components/data/crud-sheet/CrudSheetColumn.js.map +6 -0
  30. package/dist/components/data/crud-sheet/CrudSheetFilter.d.ts +7 -0
  31. package/dist/components/data/crud-sheet/CrudSheetFilter.d.ts.map +1 -0
  32. package/dist/components/data/crud-sheet/CrudSheetFilter.js +14 -0
  33. package/dist/components/data/crud-sheet/CrudSheetFilter.js.map +6 -0
  34. package/dist/components/data/crud-sheet/CrudSheetHeader.d.ts +7 -0
  35. package/dist/components/data/crud-sheet/CrudSheetHeader.d.ts.map +1 -0
  36. package/dist/components/data/crud-sheet/CrudSheetHeader.js +14 -0
  37. package/dist/components/data/crud-sheet/CrudSheetHeader.js.map +6 -0
  38. package/dist/components/data/crud-sheet/CrudSheetTools.d.ts +7 -0
  39. package/dist/components/data/crud-sheet/CrudSheetTools.d.ts.map +1 -0
  40. package/dist/components/data/crud-sheet/CrudSheetTools.js +14 -0
  41. package/dist/components/data/crud-sheet/CrudSheetTools.js.map +6 -0
  42. package/dist/components/data/crud-sheet/types.d.ts +109 -0
  43. package/dist/components/data/crud-sheet/types.d.ts.map +1 -0
  44. package/dist/components/data/crud-sheet/types.js +1 -0
  45. package/dist/components/data/crud-sheet/types.js.map +6 -0
  46. package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
  47. package/dist/components/data/kanban/Kanban.js +137 -138
  48. package/dist/components/data/kanban/Kanban.js.map +2 -2
  49. package/dist/components/data/kanban/KanbanContext.d.ts +5 -1
  50. package/dist/components/data/kanban/KanbanContext.d.ts.map +1 -1
  51. package/dist/components/data/kanban/KanbanContext.js.map +1 -1
  52. package/dist/components/data/list/ListItem.d.ts.map +1 -1
  53. package/dist/components/data/list/ListItem.js +109 -99
  54. package/dist/components/data/list/ListItem.js.map +2 -2
  55. package/dist/components/data/sheet/DataSheet.css +28 -10
  56. package/dist/components/data/sheet/DataSheet.js +1 -1
  57. package/dist/components/data/sheet/DataSheet.js.map +2 -2
  58. package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
  59. package/dist/components/data/sheet/DataSheet.styles.js +1 -1
  60. package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
  61. package/dist/components/disclosure/Dialog.d.ts +16 -10
  62. package/dist/components/disclosure/Dialog.d.ts.map +1 -1
  63. package/dist/components/disclosure/Dialog.js +126 -91
  64. package/dist/components/disclosure/Dialog.js.map +2 -2
  65. package/dist/components/disclosure/DialogContext.d.ts +2 -4
  66. package/dist/components/disclosure/DialogContext.d.ts.map +1 -1
  67. package/dist/components/disclosure/DialogContext.js.map +1 -1
  68. package/dist/components/disclosure/DialogProvider.d.ts.map +1 -1
  69. package/dist/components/disclosure/DialogProvider.js +14 -9
  70. package/dist/components/disclosure/DialogProvider.js.map +2 -2
  71. package/dist/components/disclosure/Dropdown.d.ts +46 -22
  72. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  73. package/dist/components/disclosure/Dropdown.js +100 -65
  74. package/dist/components/disclosure/Dropdown.js.map +2 -2
  75. package/dist/components/feedback/notification/NotificationBanner.d.ts.map +1 -1
  76. package/dist/components/feedback/notification/NotificationBanner.js +3 -3
  77. package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
  78. package/dist/components/feedback/notification/NotificationBell.d.ts.map +1 -1
  79. package/dist/components/feedback/notification/NotificationBell.js +84 -84
  80. package/dist/components/feedback/notification/NotificationBell.js.map +2 -2
  81. package/dist/components/form-control/Invalid.js +1 -1
  82. package/dist/components/form-control/combobox/Combobox.d.ts +6 -3
  83. package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
  84. package/dist/components/form-control/combobox/Combobox.js +150 -168
  85. package/dist/components/form-control/combobox/Combobox.js.map +2 -2
  86. package/dist/components/form-control/combobox/ComboboxContext.d.ts +3 -0
  87. package/dist/components/form-control/combobox/ComboboxContext.d.ts.map +1 -1
  88. package/dist/components/form-control/combobox/ComboboxContext.js.map +1 -1
  89. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +0 -2
  90. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
  91. package/dist/components/form-control/date-range-picker/DateRangePicker.js +9 -17
  92. package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
  93. package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
  94. package/dist/components/form-control/field/DatePicker.js +3 -2
  95. package/dist/components/form-control/field/DatePicker.js.map +2 -2
  96. package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
  97. package/dist/components/form-control/field/DateTimePicker.js +3 -2
  98. package/dist/components/form-control/field/DateTimePicker.js.map +2 -2
  99. package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
  100. package/dist/components/form-control/field/Field.styles.js +2 -1
  101. package/dist/components/form-control/field/Field.styles.js.map +1 -1
  102. package/dist/components/form-control/field/NumberInput.d.ts +15 -5
  103. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  104. package/dist/components/form-control/field/NumberInput.js +181 -141
  105. package/dist/components/form-control/field/NumberInput.js.map +2 -2
  106. package/dist/components/form-control/field/TextInput.d.ts +9 -5
  107. package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
  108. package/dist/components/form-control/field/TextInput.js +199 -154
  109. package/dist/components/form-control/field/TextInput.js.map +2 -2
  110. package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
  111. package/dist/components/form-control/field/TimePicker.js +3 -2
  112. package/dist/components/form-control/field/TimePicker.js.map +2 -2
  113. package/dist/components/form-control/select/Select.d.ts +3 -3
  114. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  115. package/dist/components/form-control/select/Select.js +116 -100
  116. package/dist/components/form-control/select/Select.js.map +2 -2
  117. package/dist/components/form-control/select/SelectContext.d.ts +9 -1
  118. package/dist/components/form-control/select/SelectContext.d.ts.map +1 -1
  119. package/dist/components/form-control/select/SelectContext.js.map +1 -1
  120. package/dist/components/form-control/select/SelectItem.d.ts.map +1 -1
  121. package/dist/components/form-control/select/SelectItem.js +77 -67
  122. package/dist/components/form-control/select/SelectItem.js.map +2 -2
  123. package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
  124. package/dist/components/form-control/state-preset/StatePreset.js +1 -1
  125. package/dist/components/form-control/state-preset/StatePreset.js.map +1 -1
  126. package/dist/components/layout/topbar/Topbar.d.ts +2 -0
  127. package/dist/components/layout/topbar/Topbar.d.ts.map +1 -1
  128. package/dist/components/layout/topbar/Topbar.js +2 -0
  129. package/dist/components/layout/topbar/Topbar.js.map +2 -2
  130. package/dist/components/layout/topbar/TopbarActions.d.ts +3 -0
  131. package/dist/components/layout/topbar/TopbarActions.d.ts.map +1 -0
  132. package/dist/components/layout/topbar/TopbarActions.js +17 -0
  133. package/dist/components/layout/topbar/TopbarActions.js.map +6 -0
  134. package/dist/components/layout/topbar/TopbarContainer.d.ts +1 -1
  135. package/dist/components/layout/topbar/TopbarContainer.d.ts.map +1 -1
  136. package/dist/components/layout/topbar/TopbarContainer.js +21 -12
  137. package/dist/components/layout/topbar/TopbarContainer.js.map +2 -2
  138. package/dist/components/layout/topbar/TopbarContext.d.ts +9 -0
  139. package/dist/components/layout/topbar/TopbarContext.d.ts.map +1 -0
  140. package/dist/components/layout/topbar/TopbarContext.js +29 -0
  141. package/dist/components/layout/topbar/TopbarContext.js.map +6 -0
  142. package/dist/components/layout/topbar/TopbarMenu.d.ts.map +1 -1
  143. package/dist/components/layout/topbar/TopbarMenu.js +63 -57
  144. package/dist/components/layout/topbar/TopbarMenu.js.map +2 -2
  145. package/dist/components/layout/topbar/TopbarUser.d.ts.map +1 -1
  146. package/dist/components/layout/topbar/TopbarUser.js +53 -54
  147. package/dist/components/layout/topbar/TopbarUser.js.map +2 -2
  148. package/dist/hooks/createControllableStore.d.ts +29 -0
  149. package/dist/hooks/createControllableStore.d.ts.map +1 -0
  150. package/dist/hooks/createControllableStore.js +19 -0
  151. package/dist/hooks/createControllableStore.js.map +6 -0
  152. package/dist/index.d.ts +6 -1
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +7 -2
  155. package/dist/index.js.map +1 -1
  156. package/dist/styles/patterns.styles.d.ts.map +1 -1
  157. package/dist/styles/patterns.styles.js +7 -1
  158. package/dist/styles/patterns.styles.js.map +1 -1
  159. package/docs/data-components.md +428 -0
  160. package/docs/disclosure.md +65 -35
  161. package/docs/form-controls.md +18 -3
  162. package/docs/helpers.md +0 -39
  163. package/docs/hooks.md +39 -0
  164. package/docs/layout.md +70 -1
  165. package/package.json +4 -3
  166. package/src/components/data/crud-detail/CrudDetail.tsx +346 -0
  167. package/src/components/data/crud-detail/CrudDetailAfter.tsx +19 -0
  168. package/src/components/data/crud-detail/CrudDetailBefore.tsx +19 -0
  169. package/src/components/data/crud-detail/CrudDetailTools.tsx +19 -0
  170. package/src/components/data/crud-detail/types.ts +58 -0
  171. package/src/components/data/crud-sheet/CrudSheet.tsx +628 -0
  172. package/src/components/data/crud-sheet/CrudSheetColumn.tsx +34 -0
  173. package/src/components/data/crud-sheet/CrudSheetFilter.tsx +21 -0
  174. package/src/components/data/crud-sheet/CrudSheetHeader.tsx +19 -0
  175. package/src/components/data/crud-sheet/CrudSheetTools.tsx +21 -0
  176. package/src/components/data/crud-sheet/types.ts +133 -0
  177. package/src/components/data/kanban/Kanban.tsx +72 -65
  178. package/src/components/data/kanban/KanbanContext.ts +7 -1
  179. package/src/components/data/list/ListItem.tsx +31 -18
  180. package/src/components/data/sheet/DataSheet.css +28 -10
  181. package/src/components/data/sheet/DataSheet.styles.ts +1 -1
  182. package/src/components/data/sheet/DataSheet.tsx +1 -1
  183. package/src/components/disclosure/Dialog.tsx +143 -105
  184. package/src/components/disclosure/DialogContext.ts +2 -4
  185. package/src/components/disclosure/DialogProvider.tsx +4 -2
  186. package/src/components/disclosure/Dropdown.tsx +174 -86
  187. package/src/components/feedback/notification/NotificationBanner.tsx +3 -9
  188. package/src/components/feedback/notification/NotificationBell.tsx +51 -57
  189. package/src/components/form-control/Invalid.tsx +1 -1
  190. package/src/components/form-control/combobox/Combobox.tsx +109 -133
  191. package/src/components/form-control/combobox/ComboboxContext.ts +4 -1
  192. package/src/components/form-control/date-range-picker/DateRangePicker.tsx +6 -16
  193. package/src/components/form-control/field/DatePicker.tsx +4 -1
  194. package/src/components/form-control/field/DateTimePicker.tsx +3 -0
  195. package/src/components/form-control/field/Field.styles.ts +1 -0
  196. package/src/components/form-control/field/NumberInput.tsx +131 -86
  197. package/src/components/form-control/field/TextInput.tsx +139 -88
  198. package/src/components/form-control/field/TimePicker.tsx +3 -0
  199. package/src/components/form-control/select/Select.tsx +85 -67
  200. package/src/components/form-control/select/SelectContext.ts +12 -1
  201. package/src/components/form-control/select/SelectItem.tsx +39 -18
  202. package/src/components/form-control/state-preset/StatePreset.tsx +1 -0
  203. package/src/components/layout/topbar/Topbar.tsx +3 -0
  204. package/src/components/layout/topbar/TopbarActions.tsx +8 -0
  205. package/src/components/layout/topbar/TopbarContainer.tsx +9 -5
  206. package/src/components/layout/topbar/TopbarContext.ts +36 -0
  207. package/src/components/layout/topbar/TopbarMenu.tsx +52 -55
  208. package/src/components/layout/topbar/TopbarUser.tsx +28 -31
  209. package/src/hooks/createControllableStore.ts +47 -0
  210. package/src/index.ts +6 -1
  211. package/src/styles/patterns.styles.ts +7 -1
  212. package/tailwind.css +4 -0
  213. package/dist/helpers/splitSlots.d.ts +0 -25
  214. package/dist/helpers/splitSlots.d.ts.map +0 -1
  215. package/dist/helpers/splitSlots.js +0 -25
  216. package/dist/helpers/splitSlots.js.map +0 -6
  217. package/dist/hooks/createItemTemplate.d.ts +0 -17
  218. package/dist/hooks/createItemTemplate.d.ts.map +0 -1
  219. package/dist/hooks/createItemTemplate.js +0 -40
  220. package/dist/hooks/createItemTemplate.js.map +0 -6
  221. package/src/helpers/splitSlots.ts +0 -51
  222. package/src/hooks/createItemTemplate.tsx +0 -42
@@ -0,0 +1,21 @@
1
+ import type { JSX } from "solid-js";
2
+ import type { CrudSheetToolsDef } from "./types";
3
+
4
+ export function isCrudSheetToolsDef(value: unknown): value is CrudSheetToolsDef<any> {
5
+ return (
6
+ value != null &&
7
+ typeof value === "object" &&
8
+ (value as Record<string, unknown>)["__type"] === "crud-sheet-tools"
9
+ );
10
+ }
11
+
12
+ /* eslint-disable solid/reactivity -- plain object 반환 패턴으로 reactive context 불필요 */
13
+ export function CrudSheetTools<_TItem>(props: {
14
+ children: (ctx: any) => JSX.Element;
15
+ }): JSX.Element {
16
+ return {
17
+ __type: "crud-sheet-tools",
18
+ children: props.children,
19
+ } as unknown as JSX.Element;
20
+ }
21
+ /* eslint-enable solid/reactivity */
@@ -0,0 +1,133 @@
1
+ import type { JSX } from "solid-js";
2
+ import type { SetStoreFunction } from "solid-js/store";
3
+ import type { ArrayDiffs2Result } from "@simplysm/core-common";
4
+ import type { DataSheetColumnProps, SortingDef } from "../sheet/types";
5
+
6
+ // ── Search ──
7
+
8
+ export interface SearchResult<TItem> {
9
+ items: TItem[];
10
+ pageCount?: number;
11
+ }
12
+
13
+ // ── Feature Configs ──
14
+
15
+ export interface InlineEditConfig<TItem> {
16
+ submit: (diffs: ArrayDiffs2Result<TItem>[]) => Promise<void>;
17
+ newItem: () => TItem;
18
+ deleteProp?: keyof TItem & string;
19
+ }
20
+
21
+ export interface ModalEditConfig<TItem> {
22
+ editItem: (item?: TItem) => Promise<boolean>;
23
+ deleteItems?: (items: TItem[]) => Promise<boolean>;
24
+ }
25
+
26
+ export interface ExcelConfig<TItem> {
27
+ download: (items: TItem[]) => Promise<void>;
28
+ upload?: (file: File) => Promise<void>;
29
+ }
30
+
31
+ export interface SelectResult<TItem> {
32
+ items: TItem[];
33
+ keys: (string | number)[];
34
+ }
35
+
36
+ // ── Cell Context ──
37
+
38
+ export interface CrudSheetCellContext<TItem> {
39
+ item: TItem;
40
+ index: number;
41
+ row: number;
42
+ depth: number;
43
+ setItem: <TKey extends keyof TItem>(key: TKey, value: TItem[TKey]) => void;
44
+ }
45
+
46
+ // ── CrudSheet Context (for Tools render prop) ──
47
+
48
+ export interface CrudSheetContext<TItem> {
49
+ items(): TItem[];
50
+ selectedItems(): TItem[];
51
+ page(): number;
52
+ sorts(): SortingDef[];
53
+ busy(): boolean;
54
+ hasChanges(): boolean;
55
+
56
+ save(): Promise<void>;
57
+ refresh(): Promise<void>;
58
+ addItem(): void;
59
+ setPage(page: number): void;
60
+ setSorts(sorts: SortingDef[]): void;
61
+ }
62
+
63
+ // ── Props ──
64
+
65
+ export type CrudSheetProps<TItem, TFilter extends Record<string, any>> = CrudSheetBaseProps<
66
+ TItem,
67
+ TFilter
68
+ > &
69
+ (
70
+ | { inlineEdit: InlineEditConfig<TItem>; modalEdit?: never }
71
+ | { modalEdit: ModalEditConfig<TItem>; inlineEdit?: never }
72
+ | { inlineEdit?: never; modalEdit?: never }
73
+ );
74
+
75
+ interface CrudSheetBaseProps<TItem, TFilter extends Record<string, any>> {
76
+ search: (filter: TFilter, page: number, sorts: SortingDef[]) => Promise<SearchResult<TItem>>;
77
+ getItemKey: (item: TItem) => string | number | undefined;
78
+ persistKey?: string;
79
+ itemsPerPage?: number;
80
+ editable?: boolean;
81
+ itemEditable?: (item: TItem) => boolean;
82
+ itemDeletable?: (item: TItem) => boolean;
83
+ filterInitial?: TFilter;
84
+ items?: TItem[];
85
+ onItemsChange?: (items: TItem[]) => void;
86
+ excel?: ExcelConfig<TItem>;
87
+ selectMode?: "single" | "multi";
88
+ onSelect?: (result: SelectResult<TItem>) => void;
89
+ hideAutoTools?: boolean;
90
+ class?: string;
91
+ children: JSX.Element;
92
+ }
93
+
94
+ // ── Sub-component Defs ──
95
+
96
+ export interface CrudSheetColumnDef<TItem> {
97
+ __type: "crud-sheet-column";
98
+ key: string;
99
+ header: string[];
100
+ headerContent?: () => JSX.Element;
101
+ headerStyle?: string;
102
+ summary?: () => JSX.Element;
103
+ tooltip?: string;
104
+ fixed: boolean;
105
+ hidden: boolean;
106
+ collapse: boolean;
107
+ width?: string;
108
+ class?: string;
109
+ sortable: boolean;
110
+ resizable: boolean;
111
+ editable: boolean;
112
+ cell: (ctx: CrudSheetCellContext<TItem>) => JSX.Element;
113
+ }
114
+
115
+ export interface CrudSheetColumnProps<TItem> extends Omit<DataSheetColumnProps<TItem>, "children"> {
116
+ editable?: boolean;
117
+ children: (ctx: CrudSheetCellContext<TItem>) => JSX.Element;
118
+ }
119
+
120
+ export interface CrudSheetFilterDef<TFilter> {
121
+ __type: "crud-sheet-filter";
122
+ children: (filter: TFilter, setFilter: SetStoreFunction<TFilter>) => JSX.Element;
123
+ }
124
+
125
+ export interface CrudSheetToolsDef<TItem> {
126
+ __type: "crud-sheet-tools";
127
+ children: (ctx: CrudSheetContext<TItem>) => JSX.Element;
128
+ }
129
+
130
+ export interface CrudSheetHeaderDef {
131
+ __type: "crud-sheet-header";
132
+ children: JSX.Element;
133
+ }
@@ -1,5 +1,4 @@
1
1
  import {
2
- children,
3
2
  createEffect,
4
3
  createMemo,
5
4
  createSignal,
@@ -9,6 +8,7 @@ import {
9
8
  type ParentComponent,
10
9
  Show,
11
10
  splitProps,
11
+ useContext,
12
12
  } from "solid-js";
13
13
  import clsx from "clsx";
14
14
  import { twMerge } from "tailwind-merge";
@@ -18,7 +18,6 @@ import { Checkbox } from "../../form-control/checkbox/Checkbox";
18
18
  import { Icon } from "../../display/Icon";
19
19
  import { BusyContainer } from "../../feedback/busy/BusyContainer";
20
20
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
21
- import { splitSlots } from "../../../helpers/splitSlots";
22
21
  import "./Kanban.css";
23
22
  import { iconButtonBase } from "../../../styles/patterns.styles";
24
23
  import {
@@ -35,15 +34,23 @@ import {
35
34
 
36
35
  // ─── KanbanLaneTitle ─────────────────────────────────────────────
37
36
 
38
- const KanbanLaneTitle: ParentComponent = (props) => (
39
- <div data-kanban-lane-title>{props.children}</div>
40
- );
37
+ const KanbanLaneTitle: ParentComponent = (props) => {
38
+ const ctx = useContext(KanbanLaneContext)!;
39
+ // eslint-disable-next-line solid/reactivity -- 슬롯 accessor로 저장, JSX tracked scope에서 호출됨
40
+ ctx.setTitle(() => props.children);
41
+ onCleanup(() => ctx.setTitle(undefined));
42
+ return null;
43
+ };
41
44
 
42
45
  // ─── KanbanLaneTools ─────────────────────────────────────────────
43
46
 
44
- const KanbanLaneTools: ParentComponent = (props) => (
45
- <div data-kanban-lane-tools>{props.children}</div>
46
- );
47
+ const KanbanLaneTools: ParentComponent = (props) => {
48
+ const ctx = useContext(KanbanLaneContext)!;
49
+ // eslint-disable-next-line solid/reactivity -- 슬롯 accessor로 저장, JSX tracked scope에서 호출됨
50
+ ctx.setTools(() => props.children);
51
+ onCleanup(() => ctx.setTools(undefined));
52
+ return null;
53
+ };
47
54
 
48
55
  // ─── KanbanCard ──────────────────────────────────────────────────
49
56
 
@@ -378,64 +385,66 @@ const KanbanLane: ParentComponent<KanbanLaneProps> = (props) => {
378
385
  }
379
386
  };
380
387
 
388
+ // Slot signals
389
+ type SlotAccessor = (() => JSX.Element) | undefined;
390
+ const [title, _setTitle] = createSignal<SlotAccessor>();
391
+ const [tools, _setTools] = createSignal<SlotAccessor>();
392
+ const setTitle = (content: SlotAccessor) => _setTitle(() => content);
393
+ const setTools = (content: SlotAccessor) => _setTools(() => content);
394
+
381
395
  const laneContextValue: KanbanLaneContextValue = {
382
396
  value: () => local.value,
383
397
  dropTarget,
384
398
  setDropTarget,
385
399
  registerCard,
386
400
  unregisterCard,
401
+ setTitle,
402
+ setTools,
387
403
  };
388
404
 
389
- // Provider 안에서 children을 resolve해야 splitSlots가 올바르게 동작
390
- const LaneInner: ParentComponent = (innerProps) => {
391
- const resolved = children(() => innerProps.children);
392
- const [slots, content] = splitSlots(resolved, ["kanbanLaneTitle", "kanbanLaneTools"] as const);
393
-
394
- const hasHeader = () =>
395
- local.collapsible ||
396
- hasSelectableCards() ||
397
- slots().kanbanLaneTitle.length > 0 ||
398
- slots().kanbanLaneTools.length > 0;
399
-
400
- // placeholder div (Lane이 소유, DOM 직접 제어)
401
- let bodyRef: HTMLDivElement | undefined;
402
- const placeholderEl = document.createElement("div");
403
- placeholderEl.className = placeholderBaseClass;
404
-
405
- createEffect(() => {
406
- const target = dropTarget();
407
- const dc = boardCtx.dragCard();
408
-
409
- if (!target || !dc || !bodyRef) {
410
- if (placeholderEl.parentNode) {
411
- placeholderEl.remove();
412
- }
413
- return;
414
- }
415
-
416
- // placeholder 높이 설정
417
- placeholderEl.style.height = `${dc.heightOnDrag}px`;
405
+ const hasHeader = () =>
406
+ local.collapsible || hasSelectableCards() || title() != null || tools() != null;
418
407
 
419
- // 삽입 위치 계산
420
- const referenceNode =
421
- target.position === "before" ? target.element : target.element.nextElementSibling;
422
-
423
- // 이미 올바른 위치면 DOM 조작 생략
424
- if (placeholderEl.parentNode === bodyRef && placeholderEl.nextSibling === referenceNode) {
425
- return;
426
- }
408
+ // placeholder div (Lane이 소유, DOM 직접 제어)
409
+ let bodyRef: HTMLDivElement | undefined;
410
+ const placeholderEl = document.createElement("div");
411
+ placeholderEl.className = placeholderBaseClass;
427
412
 
428
- bodyRef.insertBefore(placeholderEl, referenceNode);
429
- });
413
+ createEffect(() => {
414
+ const target = dropTarget();
415
+ const dc = boardCtx.dragCard();
430
416
 
431
- // placeholder cleanup
432
- onCleanup(() => {
417
+ if (!target || !dc || !bodyRef) {
433
418
  if (placeholderEl.parentNode) {
434
419
  placeholderEl.remove();
435
420
  }
436
- });
421
+ return;
422
+ }
423
+
424
+ // placeholder 높이 설정
425
+ placeholderEl.style.height = `${dc.heightOnDrag}px`;
426
+
427
+ // 삽입 위치 계산
428
+ const referenceNode =
429
+ target.position === "before" ? target.element : target.element.nextElementSibling;
437
430
 
438
- return (
431
+ // 이미 올바른 위치면 DOM 조작 생략
432
+ if (placeholderEl.parentNode === bodyRef && placeholderEl.nextSibling === referenceNode) {
433
+ return;
434
+ }
435
+
436
+ bodyRef.insertBefore(placeholderEl, referenceNode);
437
+ });
438
+
439
+ // placeholder cleanup
440
+ onCleanup(() => {
441
+ if (placeholderEl.parentNode) {
442
+ placeholderEl.remove();
443
+ }
444
+ });
445
+
446
+ return (
447
+ <KanbanLaneContext.Provider value={laneContextValue}>
439
448
  <BusyContainer busy={local.busy} variant="bar">
440
449
  <div
441
450
  {...rest}
@@ -460,25 +469,23 @@ const KanbanLane: ParentComponent<KanbanLaneProps> = (props) => {
460
469
  <Show when={hasSelectableCards()}>
461
470
  <Checkbox value={isAllSelected()} onValueChange={handleSelectAll} inline />
462
471
  </Show>
463
- <div class="flex-1">{slots().kanbanLaneTitle}</div>
464
- <Show when={slots().kanbanLaneTools.length > 0}>
465
- <div class={laneToolsClass}>{slots().kanbanLaneTools}</div>
472
+ <div class="flex-1">
473
+ <Show when={title()}>{title()!()}</Show>
474
+ </div>
475
+ <Show when={tools()}>
476
+ <div class={laneToolsClass}>{tools()!()}</div>
466
477
  </Show>
467
478
  </div>
468
479
  </Show>
469
- <Show when={!collapsed()}>
470
- <div ref={bodyRef} class={laneBodyBaseClass}>
471
- {content()}
472
- </div>
473
- </Show>
480
+ <div
481
+ ref={bodyRef}
482
+ class={laneBodyBaseClass}
483
+ style={{ display: collapsed() ? "none" : undefined }}
484
+ >
485
+ {local.children}
486
+ </div>
474
487
  </div>
475
488
  </BusyContainer>
476
- );
477
- };
478
-
479
- return (
480
- <KanbanLaneContext.Provider value={laneContextValue}>
481
- <LaneInner>{local.children}</LaneInner>
482
489
  </KanbanLaneContext.Provider>
483
490
  );
484
491
  };
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, type Accessor, type Setter } from "solid-js";
1
+ import { createContext, useContext, type Accessor, type JSX, type Setter } from "solid-js";
2
2
 
3
3
  // ── 타입 ──────────────────────────────────────────────────────
4
4
 
@@ -50,6 +50,8 @@ export function useKanbanContext(): KanbanContextValue {
50
50
 
51
51
  // ── Lane Context ───────────────────────────────────────────────
52
52
 
53
+ type SlotAccessor = (() => JSX.Element) | undefined;
54
+
53
55
  export interface KanbanLaneContextValue<L = unknown, T = unknown> {
54
56
  value: Accessor<L | undefined>;
55
57
  dropTarget: Accessor<KanbanDropTarget<T> | undefined>;
@@ -58,6 +60,10 @@ export interface KanbanLaneContextValue<L = unknown, T = unknown> {
58
60
  // Card registration (Phase 4)
59
61
  registerCard: (id: string, info: { value: T | undefined; selectable: boolean }) => void;
60
62
  unregisterCard: (id: string) => void;
63
+
64
+ // Slot registration
65
+ setTitle: (content: SlotAccessor) => void;
66
+ setTools: (content: SlotAccessor) => void;
61
67
  }
62
68
 
63
69
  export const KanbanLaneContext = createContext<KanbanLaneContextValue>();
@@ -1,10 +1,13 @@
1
1
  import {
2
- children,
3
2
  type Component,
3
+ createContext,
4
+ createSignal,
4
5
  type JSX,
6
+ onCleanup,
5
7
  type ParentComponent,
6
8
  Show,
7
9
  splitProps,
10
+ useContext,
8
11
  } from "solid-js";
9
12
  import { IconChevronDown, type IconProps } from "@tabler/icons-solidjs";
10
13
  import { Icon } from "../../display/Icon";
@@ -15,7 +18,6 @@ import { Collapse } from "../../disclosure/Collapse";
15
18
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
16
19
  import { useListContext } from "./ListContext";
17
20
  import { List } from "./List";
18
- import { splitSlots } from "../../../helpers/splitSlots";
19
21
  import {
20
22
  listItemBaseClass,
21
23
  listItemSizeClasses,
@@ -30,6 +32,14 @@ import type { ComponentSize } from "../../../styles/tokens.styles";
30
32
 
31
33
  void ripple;
32
34
 
35
+ type SlotAccessor = (() => JSX.Element) | undefined;
36
+
37
+ interface ListItemSlotsContextValue {
38
+ setChildren: (content: SlotAccessor) => void;
39
+ }
40
+
41
+ const ListItemSlotsContext = createContext<ListItemSlotsContextValue>();
42
+
33
43
  const chevronClass = clsx("transition-transform duration-200 motion-reduce:transition-none");
34
44
 
35
45
  /**
@@ -50,14 +60,13 @@ const chevronClass = clsx("transition-transform duration-200 motion-reduce:trans
50
60
  * </List.Item>
51
61
  * ```
52
62
  */
53
- const ListItemChildren: ParentComponent = (props) => (
54
- <div class="flex" data-list-item-children>
55
- <div class={listItemIndentGuideClass} />
56
- <List inset class="flex-1">
57
- {props.children}
58
- </List>
59
- </div>
60
- );
63
+ const ListItemChildren: ParentComponent = (props) => {
64
+ const ctx = useContext(ListItemSlotsContext)!;
65
+ // eslint-disable-next-line solid/reactivity -- 슬롯 accessor로 저장, JSX tracked scope에서 호출됨
66
+ ctx.setChildren(() => props.children);
67
+ onCleanup(() => ctx.setChildren(undefined));
68
+ return null;
69
+ };
61
70
 
62
71
  export interface ListItemProps extends Omit<
63
72
  JSX.ButtonHTMLAttributes<HTMLButtonElement>,
@@ -157,10 +166,9 @@ export const ListItem: ListItemComponent = (props) => {
157
166
  onChange: () => local.onOpenChange,
158
167
  });
159
168
 
160
- const resolved = children(() => local.children);
161
- const [slots, content] = splitSlots(resolved, ["listItemChildren"] as const);
162
-
163
- const hasChildren = () => slots().listItemChildren.length > 0;
169
+ const [childrenSlot, _setChildrenSlot] = createSignal<SlotAccessor>();
170
+ const setChildrenSlot = (content: SlotAccessor) => _setChildrenSlot(() => content);
171
+ const hasChildren = () => childrenSlot() !== undefined;
164
172
 
165
173
  const useRipple = () => !(local.readonly || local.disabled);
166
174
 
@@ -189,7 +197,7 @@ export const ListItem: ListItemComponent = (props) => {
189
197
  const getSelectedIconClassName = () => getListItemSelectedIconClass(local.selected ?? false);
190
198
 
191
199
  return (
192
- <>
200
+ <ListItemSlotsContext.Provider value={{ setChildren: setChildrenSlot }}>
193
201
  <button
194
202
  {...rest}
195
203
  type="button"
@@ -215,17 +223,22 @@ export const ListItem: ListItemComponent = (props) => {
215
223
  <Show when={local.selectedIcon && !hasChildren()}>
216
224
  <Icon icon={local.selectedIcon!} class={getSelectedIconClassName()} />
217
225
  </Show>
218
- <span class={listItemContentClass}>{content()}</span>
226
+ <span class={listItemContentClass}>{local.children}</span>
219
227
  <Show when={hasChildren()}>
220
228
  <Icon icon={IconChevronDown} size="1em" class={getChevronClassName()} />
221
229
  </Show>
222
230
  </button>
223
231
  <Show when={hasChildren()}>
224
232
  <Collapse open={openState()} data-collapsed={!openState() || undefined}>
225
- {slots().listItemChildren.single()}
233
+ <div class="flex">
234
+ <div class={listItemIndentGuideClass} />
235
+ <List inset class="flex-1">
236
+ {childrenSlot()!()}
237
+ </List>
238
+ </div>
226
239
  </Collapse>
227
240
  </Show>
228
- </>
241
+ </ListItemSlotsContext.Provider>
229
242
  );
230
243
  };
231
244
 
@@ -1,10 +1,27 @@
1
- [data-sheet] tbody tr:hover > td {
2
- box-shadow: inset 0 0 0 9999px rgb(0 0 0 / 3%);
1
+ [data-sheet] tbody tr {
2
+ position: relative;
3
+ }
4
+
5
+ /* ::after 공통 — content가 없으면 렌더링되지 않으므로 각 상태에서 content 설정 */
6
+ [data-sheet] tbody tr::after {
7
+ position: absolute;
8
+ top: 0;
9
+ right: 0;
10
+ bottom: 0;
11
+ left: 0;
12
+ pointer-events: none;
13
+ z-index: 10;
14
+ }
15
+
16
+ [data-sheet] tbody tr:hover::after {
17
+ content: "";
18
+ background: rgb(0 0 0 / 3%);
3
19
  }
4
20
 
5
21
  /* 선택 행 시각 효과 — 호버보다 약간 더 진하게 */
6
- [data-sheet] tbody tr[data-selected] > td {
7
- box-shadow: inset 0 0 0 9999px rgb(0 0 0 / 5%);
22
+ [data-sheet] tbody tr[data-selected]::after {
23
+ content: "";
24
+ background: rgb(0 0 0 / 5%);
8
25
  }
9
26
 
10
27
  /* 드래그 중인 행 */
@@ -13,14 +30,15 @@
13
30
  }
14
31
 
15
32
  /* inside 드롭 대상 행 */
16
- [data-sheet] tbody tr[data-drag-over="inside"] > td {
17
- box-shadow: inset 0 0 0 9999px rgb(59 130 246 / 10%);
33
+ [data-sheet] tbody tr[data-drag-over="inside"]::after {
34
+ content: "";
35
+ background: rgb(59 130 246 / 10%);
18
36
  }
19
37
 
20
- .dark [data-sheet] tbody tr:hover > td {
21
- box-shadow: inset 0 0 0 9999px rgb(255 255 255 / 4%);
38
+ .dark [data-sheet] tbody tr:hover::after {
39
+ background: rgb(255 255 255 / 4%);
22
40
  }
23
41
 
24
- .dark [data-sheet] tbody tr[data-selected] > td {
25
- box-shadow: inset 0 0 0 9999px rgb(255 255 255 / 6%);
42
+ .dark [data-sheet] tbody tr[data-selected]::after {
43
+ background: rgb(255 255 255 / 6%);
26
44
  }
@@ -62,7 +62,7 @@ export const toolbarClass = clsx(
62
62
  export const fixedClass = "sticky";
63
63
 
64
64
  // 고정/비고정 경계 시각 효과 — 고정 컬럼의 마지막 셀에 적용
65
- export const fixedLastClass = clsx("border-r-2 border-r-base-300", "dark:border-r-base-700");
65
+ export const fixedLastClass = clsx("border-r border-r-base-400", "dark:border-r-base-600");
66
66
 
67
67
  // 리사이저 핸들 (헤더 셀 우측 드래그 영역)
68
68
  export const resizerClass = clsx(
@@ -187,7 +187,7 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
187
187
  const result = await modal.show<DataSheetConfig>(
188
188
  () => <DataSheetConfigDialog columnInfos={columnInfos} currentConfig={currentConfig} />,
189
189
  {
190
- title: "시트 설정",
190
+ header: "시트 설정",
191
191
  closeOnBackdrop: true,
192
192
  closeOnEscape: true,
193
193
  },