@simplysm/solid 13.0.51 → 13.0.55

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 (97) hide show
  1. package/README.md +3 -1
  2. package/dist/components/data/sheet/DataSheet.css +28 -10
  3. package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
  4. package/dist/components/data/sheet/DataSheet.js +13 -4
  5. package/dist/components/data/sheet/DataSheet.js.map +2 -2
  6. package/dist/components/data/sheet/sheetUtils.d.ts +2 -2
  7. package/dist/components/data/sheet/sheetUtils.d.ts.map +1 -1
  8. package/dist/components/data/sheet/sheetUtils.js +16 -13
  9. package/dist/components/data/sheet/sheetUtils.js.map +1 -1
  10. package/dist/components/data/sheet/types.d.ts +6 -0
  11. package/dist/components/data/sheet/types.d.ts.map +1 -1
  12. package/dist/components/display/Icon.d.ts.map +1 -1
  13. package/dist/components/display/Icon.js +7 -2
  14. package/dist/components/display/Icon.js.map +2 -2
  15. package/dist/components/display/Link.d.ts +4 -0
  16. package/dist/components/display/Link.d.ts.map +1 -1
  17. package/dist/components/display/Link.js +13 -3
  18. package/dist/components/display/Link.js.map +2 -2
  19. package/dist/components/form-control/Invalid.d.ts.map +1 -1
  20. package/dist/components/form-control/Invalid.js +4 -3
  21. package/dist/components/form-control/Invalid.js.map +2 -2
  22. package/dist/components/form-control/checkbox/Checkbox.styles.d.ts +1 -1
  23. package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
  24. package/dist/components/form-control/combobox/Combobox.js +2 -2
  25. package/dist/components/form-control/combobox/Combobox.js.map +1 -1
  26. package/dist/components/form-control/editor/RichTextEditor.js +1 -1
  27. package/dist/components/form-control/editor/RichTextEditor.js.map +1 -1
  28. package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
  29. package/dist/components/form-control/field/DatePicker.js +3 -2
  30. package/dist/components/form-control/field/DatePicker.js.map +2 -2
  31. package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
  32. package/dist/components/form-control/field/DateTimePicker.js +3 -2
  33. package/dist/components/form-control/field/DateTimePicker.js.map +2 -2
  34. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  35. package/dist/components/form-control/field/NumberInput.js +1 -1
  36. package/dist/components/form-control/field/NumberInput.js.map +1 -1
  37. package/dist/components/form-control/field/TextInput.js +2 -2
  38. package/dist/components/form-control/field/TextInput.js.map +1 -1
  39. package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
  40. package/dist/components/form-control/field/TimePicker.js +3 -2
  41. package/dist/components/form-control/field/TimePicker.js.map +2 -2
  42. package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
  43. package/dist/components/form-control/state-preset/StatePreset.js +1 -1
  44. package/dist/components/form-control/state-preset/StatePreset.js.map +1 -1
  45. package/dist/components/layout/topbar/Topbar.d.ts +2 -0
  46. package/dist/components/layout/topbar/Topbar.d.ts.map +1 -1
  47. package/dist/components/layout/topbar/Topbar.js +2 -0
  48. package/dist/components/layout/topbar/Topbar.js.map +2 -2
  49. package/dist/components/layout/topbar/TopbarActions.d.ts +3 -0
  50. package/dist/components/layout/topbar/TopbarActions.d.ts.map +1 -0
  51. package/dist/components/layout/topbar/TopbarActions.js +17 -0
  52. package/dist/components/layout/topbar/TopbarActions.js.map +6 -0
  53. package/dist/components/layout/topbar/TopbarContainer.d.ts +1 -1
  54. package/dist/components/layout/topbar/TopbarContainer.d.ts.map +1 -1
  55. package/dist/components/layout/topbar/TopbarContainer.js +21 -12
  56. package/dist/components/layout/topbar/TopbarContainer.js.map +2 -2
  57. package/dist/components/layout/topbar/TopbarContext.d.ts +9 -0
  58. package/dist/components/layout/topbar/TopbarContext.d.ts.map +1 -0
  59. package/dist/components/layout/topbar/TopbarContext.js +29 -0
  60. package/dist/components/layout/topbar/TopbarContext.js.map +6 -0
  61. package/dist/helpers/createAppStructure.d.ts +2 -6
  62. package/dist/helpers/createAppStructure.d.ts.map +1 -1
  63. package/dist/helpers/createAppStructure.js +26 -58
  64. package/dist/helpers/createAppStructure.js.map +1 -1
  65. package/dist/index.d.ts +1 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +1 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/styles/patterns.styles.js +2 -2
  70. package/dist/styles/patterns.styles.js.map +1 -1
  71. package/dist/styles/tokens.styles.d.ts +2 -2
  72. package/dist/styles/tokens.styles.js +2 -2
  73. package/docs/layout.md +70 -1
  74. package/package.json +3 -3
  75. package/src/components/data/sheet/DataSheet.css +28 -10
  76. package/src/components/data/sheet/DataSheet.tsx +27 -4
  77. package/src/components/data/sheet/sheetUtils.ts +16 -11
  78. package/src/components/data/sheet/types.ts +6 -0
  79. package/src/components/display/Icon.tsx +10 -1
  80. package/src/components/display/Link.tsx +19 -9
  81. package/src/components/form-control/Invalid.tsx +13 -6
  82. package/src/components/form-control/combobox/Combobox.tsx +2 -1
  83. package/src/components/form-control/editor/RichTextEditor.tsx +1 -1
  84. package/src/components/form-control/field/DatePicker.tsx +4 -1
  85. package/src/components/form-control/field/DateTimePicker.tsx +3 -0
  86. package/src/components/form-control/field/NumberInput.tsx +2 -0
  87. package/src/components/form-control/field/TextInput.tsx +2 -2
  88. package/src/components/form-control/field/TimePicker.tsx +3 -0
  89. package/src/components/form-control/state-preset/StatePreset.tsx +1 -0
  90. package/src/components/layout/topbar/Topbar.tsx +3 -0
  91. package/src/components/layout/topbar/TopbarActions.tsx +8 -0
  92. package/src/components/layout/topbar/TopbarContainer.tsx +9 -5
  93. package/src/components/layout/topbar/TopbarContext.ts +36 -0
  94. package/src/helpers/createAppStructure.ts +32 -74
  95. package/src/index.ts +1 -0
  96. package/src/styles/patterns.styles.ts +2 -2
  97. package/src/styles/tokens.styles.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/solid",
3
- "version": "13.0.51",
3
+ "version": "13.0.55",
4
4
  "description": "심플리즘 패키지 - SolidJS 라이브러리",
5
5
  "author": "김석래",
6
6
  "license": "Apache-2.0",
@@ -49,8 +49,8 @@
49
49
  "solid-tiptap": "^0.8.0",
50
50
  "tailwind-merge": "^3.5.0",
51
51
  "tailwindcss": "^3.4.19",
52
- "@simplysm/core-browser": "13.0.51",
53
- "@simplysm/core-common": "13.0.51"
52
+ "@simplysm/core-browser": "13.0.55",
53
+ "@simplysm/core-common": "13.0.55"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@solidjs/testing-library": "^0.8.10"
@@ -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
  }
@@ -267,6 +267,12 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
267
267
  return sortedItems().slice((page - 1) * ipp, page * ipp);
268
268
  });
269
269
 
270
+ const originalIndexMap = createMemo(() => {
271
+ const map = new Map<T, number>();
272
+ (local.items ?? []).forEach((item, i) => map.set(item, i));
273
+ return map;
274
+ });
275
+
270
276
  // #region Feature Column Setup (확장/선택 기능 컬럼 공통)
271
277
  const hasExpandFeature = () => local.getChildren != null;
272
278
  const hasSelectFeature = () => local.selectMode != null;
@@ -462,13 +468,24 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
462
468
 
463
469
  function toggleExpandAll(): void {
464
470
  if (!local.getChildren) return;
465
- const allExpandable = collectAllExpandable(pagedItems(), local.getChildren);
471
+ const indexMap = originalIndexMap();
472
+ const allExpandable = collectAllExpandable(
473
+ pagedItems(),
474
+ local.getChildren,
475
+ (item) => indexMap.get(item) ?? -1,
476
+ );
466
477
  const isAllExpanded = allExpandable.every((item) => expandedItems().includes(item));
467
478
  setExpandedItems(isAllExpanded ? [] : allExpandable);
468
479
  }
469
480
 
470
481
  const flatItems = createMemo((): FlatItem<T>[] => {
471
- return flattenTree(pagedItems(), expandedItems(), local.getChildren);
482
+ const indexMap = originalIndexMap();
483
+ return flattenTree(
484
+ pagedItems(),
485
+ expandedItems(),
486
+ local.getChildren,
487
+ (item) => indexMap.get(item) ?? -1,
488
+ );
472
489
  });
473
490
 
474
491
  // #region Selection
@@ -732,7 +749,12 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
732
749
  // 전체 확장 상태인지
733
750
  const isAllExpanded = createMemo(() => {
734
751
  if (!local.getChildren) return false;
735
- const allExpandable = collectAllExpandable(pagedItems(), local.getChildren);
752
+ const indexMap = originalIndexMap();
753
+ const allExpandable = collectAllExpandable(
754
+ pagedItems(),
755
+ local.getChildren,
756
+ (item) => indexMap.get(item) ?? -1,
757
+ );
736
758
  return (
737
759
  allExpandable.length > 0 && allExpandable.every((item) => expandedItems().includes(item))
738
760
  );
@@ -1127,7 +1149,7 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
1127
1149
  {(() => {
1128
1150
  const selectable = () => getItemSelectable(flat.item);
1129
1151
  const isSelected = () => selectedItems().includes(flat.item);
1130
- const rowIndex = () => displayItems().indexOf(flat);
1152
+ const rowIndex = () => flat.row;
1131
1153
 
1132
1154
  return (
1133
1155
  <td
@@ -1244,6 +1266,7 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
1244
1266
  {col.cell({
1245
1267
  item: flat.item,
1246
1268
  index: flat.index,
1269
+ row: flat.row,
1247
1270
  depth: flat.depth,
1248
1271
  })}
1249
1272
  </td>
@@ -103,25 +103,29 @@ export function flattenTree<TNode>(
103
103
  items: TNode[],
104
104
  expandedItems: TNode[],
105
105
  getChildren?: (item: TNode, index: number) => TNode[] | undefined,
106
+ getOriginalIndex?: (item: TNode) => number,
106
107
  ): FlatItem<TNode>[] {
107
108
  if (!getChildren) {
108
109
  return items.map((item, i) => ({
109
110
  item,
110
- index: i,
111
+ index: getOriginalIndex ? getOriginalIndex(item) : i,
112
+ row: i,
111
113
  depth: 0,
112
114
  hasChildren: false,
113
115
  }));
114
116
  }
115
117
 
116
118
  const result: FlatItem<TNode>[] = [];
117
- let index = 0;
119
+ let row = 0;
118
120
 
119
121
  function walk(list: TNode[], depth: number, parent?: TNode): void {
120
- for (const item of list) {
122
+ for (let localIdx = 0; localIdx < list.length; localIdx++) {
123
+ const item = list[localIdx];
124
+ const index = depth === 0 && getOriginalIndex ? getOriginalIndex(item) : localIdx;
121
125
  const children = getChildren!(item, index);
122
126
  const hasChildren = children != null && children.length > 0;
123
- result.push({ item, index, depth, hasChildren, parent });
124
- index++;
127
+ result.push({ item, index, row, depth, hasChildren, parent });
128
+ row++;
125
129
 
126
130
  if (hasChildren && expandedItems.includes(item)) {
127
131
  walk(children, depth + 1, item);
@@ -136,22 +140,23 @@ export function flattenTree<TNode>(
136
140
  export function collectAllExpandable<TItem>(
137
141
  items: TItem[],
138
142
  getChildren: (item: TItem, index: number) => TItem[] | undefined,
143
+ getOriginalIndex?: (item: TItem) => number,
139
144
  ): TItem[] {
140
145
  const result: TItem[] = [];
141
- let index = 0;
142
146
 
143
- function walk(list: TItem[]): void {
144
- for (const item of list) {
147
+ function walk(list: TItem[], depth: number): void {
148
+ for (let localIdx = 0; localIdx < list.length; localIdx++) {
149
+ const item = list[localIdx];
150
+ const index = depth === 0 && getOriginalIndex ? getOriginalIndex(item) : localIdx;
145
151
  const children = getChildren(item, index);
146
- index++;
147
152
  if (children != null && children.length > 0) {
148
153
  result.push(item);
149
- walk(children);
154
+ walk(children, depth + 1);
150
155
  }
151
156
  }
152
157
  }
153
158
 
154
- walk(items);
159
+ walk(items, 0);
155
160
  return result;
156
161
  }
157
162
 
@@ -64,7 +64,10 @@ export interface DataSheetColumnProps<TItem> {
64
64
 
65
65
  export interface DataSheetCellContext<TItem> {
66
66
  item: TItem;
67
+ /** 소속 배열 내 위치 (루트: items[], 자식: parent.children[]) */
67
68
  index: number;
69
+ /** 플랫 표시 행 위치 (현재 페이지 내 순번) */
70
+ row: number;
68
71
  depth: number;
69
72
  }
70
73
 
@@ -116,7 +119,10 @@ export interface HeaderDef {
116
119
 
117
120
  export interface FlatItem<TItem> {
118
121
  item: TItem;
122
+ /** 소속 배열 내 위치 (루트: items[], 자식: parent.children[]) */
119
123
  index: number;
124
+ /** 플랫 표시 행 위치 (현재 페이지 내 순번) */
125
+ row: number;
120
126
  depth: number;
121
127
  hasChildren: boolean;
122
128
  parent?: TItem;
@@ -1,6 +1,7 @@
1
1
  import { type Component, splitProps } from "solid-js";
2
2
  import { Dynamic } from "solid-js/web";
3
3
  import type { IconProps as TablerIconProps } from "@tabler/icons-solidjs";
4
+ import clsx from "clsx";
4
5
 
5
6
  export interface IconProps extends Omit<TablerIconProps, "size"> {
6
7
  icon: Component<TablerIconProps>;
@@ -9,5 +10,13 @@ export interface IconProps extends Omit<TablerIconProps, "size"> {
9
10
 
10
11
  export const Icon: Component<IconProps> = (props) => {
11
12
  const [local, rest] = splitProps(props, ["icon", "size"]);
12
- return <Dynamic component={local.icon} data-icon size={local.size ?? "1.25em"} {...rest} />;
13
+ return (
14
+ <Dynamic
15
+ data-icon
16
+ component={local.icon}
17
+ size={local.size ?? "1.25em"}
18
+ {...rest}
19
+ class={clsx("inline", rest.class)}
20
+ />
21
+ );
13
22
  };
@@ -1,21 +1,31 @@
1
1
  import { type JSX, type ParentComponent, splitProps } from "solid-js";
2
2
  import clsx from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
+ import { disabledOpacity, type SemanticTheme, themeTokens } from "../../styles/tokens.styles";
4
5
 
5
- export interface LinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {}
6
+ export type LinkTheme = SemanticTheme;
6
7
 
7
- const baseClass = clsx(
8
- "text-primary-600",
9
- "dark:text-primary-400",
10
- "hover:underline",
11
- "cursor-pointer",
12
- );
8
+ export interface LinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {
9
+ theme?: LinkTheme;
10
+ disabled?: boolean;
11
+ }
12
+
13
+ const baseClass = clsx("hover:underline", "cursor-pointer");
14
+
15
+ const themeClasses: Record<LinkTheme, string> = Object.fromEntries(
16
+ Object.entries(themeTokens).map(([theme, t]) => [theme, t.text]),
17
+ ) as Record<LinkTheme, string>;
13
18
 
14
19
  export const Link: ParentComponent<LinkProps> = (props) => {
15
- const [local, rest] = splitProps(props, ["children", "class"]);
20
+ const [local, rest] = splitProps(props, ["children", "class", "theme", "disabled"]);
21
+
22
+ const getClassName = () => {
23
+ const theme = local.theme ?? "primary";
24
+ return twMerge(baseClass, themeClasses[theme], local.disabled && disabledOpacity, local.class);
25
+ };
16
26
 
17
27
  return (
18
- <a class={twMerge(baseClass, local.class)} {...rest}>
28
+ <a class={getClassName()} aria-disabled={local.disabled ?? undefined} {...rest}>
19
29
  {local.children}
20
30
  </a>
21
31
  );
@@ -1,4 +1,5 @@
1
- import { type ParentComponent, children, createEffect, createSignal, onCleanup } from "solid-js";
1
+ import { children, createEffect, createSignal, onCleanup, type ParentComponent } from "solid-js";
2
+ import clsx from "clsx";
2
3
  import "@simplysm/core-browser";
3
4
 
4
5
  export interface InvalidProps {
@@ -13,9 +14,12 @@ export interface InvalidProps {
13
14
  export const Invalid: ParentComponent<InvalidProps> = (props) => {
14
15
  const hiddenInputEl = document.createElement("input");
15
16
  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";
17
+ hiddenInputEl.className = clsx(
18
+ "absolute bottom-0 left-1/2",
19
+ "size-px opacity-0",
20
+ "pointer-events-none -z-10",
21
+ );
22
+ hiddenInputEl.autocomplete = "one-time-code";
19
23
  hiddenInputEl.tabIndex = -1;
20
24
  hiddenInputEl.setAttribute("aria-hidden", "true");
21
25
 
@@ -81,8 +85,11 @@ export const Invalid: ParentComponent<InvalidProps> = (props) => {
81
85
  if (shouldShow) {
82
86
  const dot = document.createElement("span");
83
87
  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;";
88
+ dot.className = clsx(
89
+ "absolute left-0.5 top-0.5",
90
+ "size-1.5 rounded-full",
91
+ "pointer-events-none bg-danger-500",
92
+ );
86
93
  targetEl.appendChild(dot);
87
94
  }
88
95
 
@@ -277,7 +277,7 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
277
277
  size: local.size,
278
278
  disabled: local.disabled,
279
279
  inset: local.inset,
280
- class: local.class,
280
+ class: clsx(!local.inset && "bg-primary-50 dark:bg-primary-950/30", local.class),
281
281
  });
282
282
 
283
283
  // 참고: 초기 검색은 handleTriggerClick에서 수행됨
@@ -314,6 +314,7 @@ export const Combobox: ComboboxComponent = <T,>(props: ComboboxProps<T>) => {
314
314
  value={query()}
315
315
  placeholder={currentValue === undefined ? local.placeholder : undefined}
316
316
  disabled={local.disabled}
317
+ autocomplete="one-time-code"
317
318
  onInput={handleInput}
318
319
  />
319
320
  );
@@ -49,7 +49,7 @@ export interface RichTextEditorProps {
49
49
  // 에디터 wrapper 스타일
50
50
  const editorWrapperClass = clsx(
51
51
  "flex flex-col",
52
- "bg-white dark:bg-base-900",
52
+ "bg-primary-50 dark:bg-primary-950/30",
53
53
  "text-base-900 dark:text-base-100",
54
54
  "border border-base-300 dark:border-base-700",
55
55
  "rounded",
@@ -3,7 +3,7 @@ import { type Component, createMemo, type JSX, Show, splitProps } from "solid-js
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import { DateOnly } from "@simplysm/core-common";
5
5
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
6
- import { type FieldSize, fieldInputClass, getFieldWrapperClass } from "./Field.styles";
6
+ import { fieldInputClass, type FieldSize, getFieldWrapperClass } from "./Field.styles";
7
7
  import { Invalid } from "../../form-control/Invalid";
8
8
 
9
9
  type DatePickerUnit = "year" | "month" | "date";
@@ -181,6 +181,7 @@ export const DatePicker: Component<DatePickerProps> = (props) => {
181
181
  disabled: local.disabled,
182
182
  inset: local.inset,
183
183
  includeCustomClass: includeCustomClass && local.class,
184
+ extra: "min-w-32",
184
185
  });
185
186
 
186
187
  // 편집 가능 여부
@@ -231,6 +232,7 @@ export const DatePicker: Component<DatePickerProps> = (props) => {
231
232
  title={local.title}
232
233
  min={formatDateValue(local.min, fieldType())}
233
234
  max={formatDateValue(local.max, fieldType())}
235
+ autocomplete="one-time-code"
234
236
  onChange={handleChange}
235
237
  />
236
238
  </div>
@@ -257,6 +259,7 @@ export const DatePicker: Component<DatePickerProps> = (props) => {
257
259
  title={local.title}
258
260
  min={formatDateValue(local.min, fieldType())}
259
261
  max={formatDateValue(local.max, fieldType())}
262
+ autocomplete="one-time-code"
260
263
  onChange={handleChange}
261
264
  />
262
265
  </div>
@@ -176,6 +176,7 @@ export const DateTimePicker: Component<DateTimePickerProps> = (props) => {
176
176
  disabled: local.disabled,
177
177
  inset: local.inset,
178
178
  includeCustomClass: includeCustomClass && local.class,
179
+ extra: "min-w-44",
179
180
  });
180
181
 
181
182
  // 편집 가능 여부
@@ -230,6 +231,7 @@ export const DateTimePicker: Component<DateTimePickerProps> = (props) => {
230
231
  min={formatDateTimeValue(local.min, fieldType())}
231
232
  max={formatDateTimeValue(local.max, fieldType())}
232
233
  step={getStep()}
234
+ autocomplete="one-time-code"
233
235
  onChange={handleChange}
234
236
  />
235
237
  </div>
@@ -262,6 +264,7 @@ export const DateTimePicker: Component<DateTimePickerProps> = (props) => {
262
264
  min={formatDateTimeValue(local.min, fieldType())}
263
265
  max={formatDateTimeValue(local.max, fieldType())}
264
266
  step={getStep()}
267
+ autocomplete="one-time-code"
265
268
  onChange={handleChange}
266
269
  />
267
270
  </div>
@@ -328,6 +328,7 @@ export const NumberInput: Component<NumberInputProps> = (props) => {
328
328
  value={displayValue()}
329
329
  placeholder={local.placeholder}
330
330
  title={local.title}
331
+ autocomplete="one-time-code"
331
332
  onInput={handleInput}
332
333
  onFocus={handleFocus}
333
334
  onBlur={handleBlur}
@@ -361,6 +362,7 @@ export const NumberInput: Component<NumberInputProps> = (props) => {
361
362
  value={displayValue()}
362
363
  placeholder={local.placeholder}
363
364
  title={local.title}
365
+ autocomplete="one-time-code"
364
366
  onInput={handleInput}
365
367
  onFocus={handleFocus}
366
368
  onBlur={handleBlur}
@@ -284,7 +284,7 @@ export const TextInput: Component<TextInputProps> = (props) => {
284
284
  value={inputValue()}
285
285
  placeholder={local.placeholder}
286
286
  title={local.title}
287
- autocomplete={local.autocomplete}
287
+ autocomplete={local.autocomplete ?? "one-time-code"}
288
288
  onInput={handleInput}
289
289
  onCompositionStart={handleCompositionStart}
290
290
  onCompositionEnd={handleCompositionEnd}
@@ -314,7 +314,7 @@ export const TextInput: Component<TextInputProps> = (props) => {
314
314
  value={inputValue()}
315
315
  placeholder={local.placeholder}
316
316
  title={local.title}
317
- autocomplete={local.autocomplete}
317
+ autocomplete={local.autocomplete ?? "one-time-code"}
318
318
  onInput={handleInput}
319
319
  onCompositionStart={handleCompositionStart}
320
320
  onCompositionEnd={handleCompositionEnd}
@@ -151,6 +151,7 @@ export const TimePicker: Component<TimePickerProps> = (props) => {
151
151
  disabled: local.disabled,
152
152
  inset: local.inset,
153
153
  includeCustomClass: includeCustomClass && local.class,
154
+ extra: "min-w-24",
154
155
  });
155
156
 
156
157
  // 편집 가능 여부
@@ -203,6 +204,7 @@ export const TimePicker: Component<TimePickerProps> = (props) => {
203
204
  value={displayValue()}
204
205
  title={local.title}
205
206
  step={getStep()}
207
+ autocomplete="one-time-code"
206
208
  onChange={handleChange}
207
209
  />
208
210
  </div>
@@ -228,6 +230,7 @@ export const TimePicker: Component<TimePickerProps> = (props) => {
228
230
  value={displayValue()}
229
231
  title={local.title}
230
232
  step={getStep()}
233
+ autocomplete="one-time-code"
231
234
  onChange={handleChange}
232
235
  />
233
236
  </div>
@@ -289,6 +289,7 @@ function StatePresetInner<TValue>(props: StatePresetProps<TValue>): JSX.Element
289
289
  type="text"
290
290
  class={resolvedInputClass()}
291
291
  placeholder="이름..."
292
+ autocomplete="one-time-code"
292
293
  value={inputValue()}
293
294
  onInput={(e) => setInputValue(e.currentTarget.value)}
294
295
  onKeyDown={handleInputKeyDown}
@@ -5,6 +5,7 @@ import { Icon } from "../../display/Icon";
5
5
  import { twMerge } from "tailwind-merge";
6
6
  import { Button } from "../../form-control/Button";
7
7
  import { useSidebarContextOptional } from "../sidebar/SidebarContext";
8
+ import { TopbarActions } from "./TopbarActions";
8
9
  import { TopbarContainer } from "./TopbarContainer";
9
10
  import { TopbarMenu } from "./TopbarMenu";
10
11
  import { TopbarUser } from "./TopbarUser";
@@ -58,6 +59,7 @@ export interface TopbarProps extends JSX.HTMLAttributes<HTMLElement> {
58
59
  * ```
59
60
  */
60
61
  interface TopbarComponent extends ParentComponent<TopbarProps> {
62
+ Actions: typeof TopbarActions;
61
63
  Container: typeof TopbarContainer;
62
64
  Menu: typeof TopbarMenu;
63
65
  User: typeof TopbarUser;
@@ -88,6 +90,7 @@ const TopbarBase: ParentComponent<TopbarProps> = (props) => {
88
90
  };
89
91
 
90
92
  export const Topbar = TopbarBase as TopbarComponent;
93
+ Topbar.Actions = TopbarActions;
91
94
  Topbar.Container = TopbarContainer;
92
95
  Topbar.Menu = TopbarMenu;
93
96
  Topbar.User = TopbarUser;
@@ -0,0 +1,8 @@
1
+ import { type Component, useContext } from "solid-js";
2
+ import { TopbarContext } from "./TopbarContext";
3
+
4
+ export const TopbarActions: Component = () => {
5
+ const context = useContext(TopbarContext);
6
+
7
+ return <span data-topbar-actions>{context?.actions()}</span>;
8
+ };
@@ -1,6 +1,7 @@
1
- import { type JSX, type ParentComponent, splitProps } from "solid-js";
1
+ import { type JSX, type ParentComponent, splitProps, createSignal } from "solid-js";
2
2
  import clsx from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
+ import { TopbarContext } from "./TopbarContext";
4
5
 
5
6
  const containerClass = clsx("flex h-full flex-col");
6
7
 
@@ -13,7 +14,7 @@ export interface TopbarContainerProps extends JSX.HTMLAttributes<HTMLDivElement>
13
14
  *
14
15
  * @remarks
15
16
  * - `flex flex-col h-full` 구조로 Topbar와 콘텐츠를 수직 배치
16
- * - Context 없이 순수 레이아웃 역할만 수행
17
+ * - TopbarContext.Provider로 actions 상태 공유
17
18
  * - 부모 요소에 높이가 지정되어야 함
18
19
  *
19
20
  * @example
@@ -29,12 +30,15 @@ export interface TopbarContainerProps extends JSX.HTMLAttributes<HTMLDivElement>
29
30
  */
30
31
  export const TopbarContainer: ParentComponent<TopbarContainerProps> = (props) => {
31
32
  const [local, rest] = splitProps(props, ["children", "class"]);
33
+ const [actions, setActions] = createSignal<JSX.Element | undefined>(undefined);
32
34
 
33
35
  const getClassName = () => twMerge(containerClass, local.class);
34
36
 
35
37
  return (
36
- <div {...rest} data-topbar-container class={getClassName()}>
37
- {local.children}
38
- </div>
38
+ <TopbarContext.Provider value={{ actions, setActions }}>
39
+ <div {...rest} data-topbar-container class={getClassName()}>
40
+ {local.children}
41
+ </div>
42
+ </TopbarContext.Provider>
39
43
  );
40
44
  };
@@ -0,0 +1,36 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ onCleanup,
5
+ type Accessor,
6
+ type JSX,
7
+ type Setter,
8
+ } from "solid-js";
9
+
10
+ export interface TopbarContextValue {
11
+ actions: Accessor<JSX.Element | undefined>;
12
+ setActions: Setter<JSX.Element | undefined>;
13
+ }
14
+
15
+ export const TopbarContext = createContext<TopbarContextValue>();
16
+
17
+ export function useTopbarActionsAccessor(): Accessor<JSX.Element | undefined> {
18
+ const context = useContext(TopbarContext);
19
+ if (!context) {
20
+ throw new Error("useTopbarActionsAccessor는 Topbar.Container 내부에서만 사용할 수 있습니다");
21
+ }
22
+ return context.actions;
23
+ }
24
+
25
+ export function createTopbarActions(accessor: () => JSX.Element): void {
26
+ const context = useContext(TopbarContext);
27
+ if (!context) {
28
+ throw new Error("createTopbarActions는 Topbar.Container 내부에서만 사용할 수 있습니다");
29
+ }
30
+
31
+ context.setActions(() => accessor());
32
+
33
+ onCleanup(() => {
34
+ context.setActions(undefined);
35
+ });
36
+ }