@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.
- package/README.md +6 -2
- package/dist/components/data/crud-detail/CrudDetail.d.ts +14 -0
- package/dist/components/data/crud-detail/CrudDetail.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetail.js +348 -0
- package/dist/components/data/crud-detail/CrudDetail.js.map +6 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.d.ts +7 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.js +14 -0
- package/dist/components/data/crud-detail/CrudDetailAfter.js.map +6 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.d.ts +7 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.js +14 -0
- package/dist/components/data/crud-detail/CrudDetailBefore.js.map +6 -0
- package/dist/components/data/crud-detail/CrudDetailTools.d.ts +7 -0
- package/dist/components/data/crud-detail/CrudDetailTools.d.ts.map +1 -0
- package/dist/components/data/crud-detail/CrudDetailTools.js +14 -0
- package/dist/components/data/crud-detail/CrudDetailTools.js.map +6 -0
- package/dist/components/data/crud-detail/types.d.ts +45 -0
- package/dist/components/data/crud-detail/types.d.ts.map +1 -0
- package/dist/components/data/crud-detail/types.js +1 -0
- package/dist/components/data/crud-detail/types.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheet.d.ts +17 -0
- package/dist/components/data/crud-sheet/CrudSheet.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheet.js +679 -0
- package/dist/components/data/crud-sheet/CrudSheet.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.d.ts +5 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.js +29 -0
- package/dist/components/data/crud-sheet/CrudSheetColumn.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.d.ts +7 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.js +14 -0
- package/dist/components/data/crud-sheet/CrudSheetFilter.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.d.ts +7 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.js +14 -0
- package/dist/components/data/crud-sheet/CrudSheetHeader.js.map +6 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.d.ts +7 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.js +14 -0
- package/dist/components/data/crud-sheet/CrudSheetTools.js.map +6 -0
- package/dist/components/data/crud-sheet/types.d.ts +109 -0
- package/dist/components/data/crud-sheet/types.d.ts.map +1 -0
- package/dist/components/data/crud-sheet/types.js +1 -0
- package/dist/components/data/crud-sheet/types.js.map +6 -0
- package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
- package/dist/components/data/kanban/Kanban.js +137 -138
- package/dist/components/data/kanban/Kanban.js.map +2 -2
- package/dist/components/data/kanban/KanbanContext.d.ts +5 -1
- package/dist/components/data/kanban/KanbanContext.d.ts.map +1 -1
- package/dist/components/data/kanban/KanbanContext.js.map +1 -1
- package/dist/components/data/list/ListItem.d.ts.map +1 -1
- package/dist/components/data/list/ListItem.js +109 -99
- package/dist/components/data/list/ListItem.js.map +2 -2
- package/dist/components/data/sheet/DataSheet.css +28 -10
- package/dist/components/data/sheet/DataSheet.js +1 -1
- package/dist/components/data/sheet/DataSheet.js.map +2 -2
- package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.styles.js +1 -1
- package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
- package/dist/components/disclosure/Dialog.d.ts +16 -10
- package/dist/components/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +126 -91
- package/dist/components/disclosure/Dialog.js.map +2 -2
- package/dist/components/disclosure/DialogContext.d.ts +2 -4
- package/dist/components/disclosure/DialogContext.d.ts.map +1 -1
- package/dist/components/disclosure/DialogContext.js.map +1 -1
- package/dist/components/disclosure/DialogProvider.d.ts.map +1 -1
- package/dist/components/disclosure/DialogProvider.js +14 -9
- package/dist/components/disclosure/DialogProvider.js.map +2 -2
- package/dist/components/disclosure/Dropdown.d.ts +46 -22
- package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
- package/dist/components/disclosure/Dropdown.js +100 -65
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/feedback/notification/NotificationBanner.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationBanner.js +3 -3
- package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
- package/dist/components/feedback/notification/NotificationBell.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationBell.js +84 -84
- package/dist/components/feedback/notification/NotificationBell.js.map +2 -2
- package/dist/components/form-control/Invalid.js +1 -1
- package/dist/components/form-control/combobox/Combobox.d.ts +6 -3
- package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
- package/dist/components/form-control/combobox/Combobox.js +150 -168
- package/dist/components/form-control/combobox/Combobox.js.map +2 -2
- package/dist/components/form-control/combobox/ComboboxContext.d.ts +3 -0
- package/dist/components/form-control/combobox/ComboboxContext.d.ts.map +1 -1
- package/dist/components/form-control/combobox/ComboboxContext.js.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +0 -2
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.js +9 -17
- package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
- package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DatePicker.js +3 -2
- package/dist/components/form-control/field/DatePicker.js.map +2 -2
- package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DateTimePicker.js +3 -2
- package/dist/components/form-control/field/DateTimePicker.js.map +2 -2
- package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
- package/dist/components/form-control/field/Field.styles.js +2 -1
- package/dist/components/form-control/field/Field.styles.js.map +1 -1
- package/dist/components/form-control/field/NumberInput.d.ts +15 -5
- package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
- package/dist/components/form-control/field/NumberInput.js +181 -141
- package/dist/components/form-control/field/NumberInput.js.map +2 -2
- package/dist/components/form-control/field/TextInput.d.ts +9 -5
- package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
- package/dist/components/form-control/field/TextInput.js +199 -154
- package/dist/components/form-control/field/TextInput.js.map +2 -2
- package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/TimePicker.js +3 -2
- package/dist/components/form-control/field/TimePicker.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts +3 -3
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +116 -100
- package/dist/components/form-control/select/Select.js.map +2 -2
- package/dist/components/form-control/select/SelectContext.d.ts +9 -1
- package/dist/components/form-control/select/SelectContext.d.ts.map +1 -1
- package/dist/components/form-control/select/SelectContext.js.map +1 -1
- package/dist/components/form-control/select/SelectItem.d.ts.map +1 -1
- package/dist/components/form-control/select/SelectItem.js +77 -67
- package/dist/components/form-control/select/SelectItem.js.map +2 -2
- package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
- package/dist/components/form-control/state-preset/StatePreset.js +1 -1
- package/dist/components/form-control/state-preset/StatePreset.js.map +1 -1
- package/dist/components/layout/topbar/Topbar.d.ts +2 -0
- package/dist/components/layout/topbar/Topbar.d.ts.map +1 -1
- package/dist/components/layout/topbar/Topbar.js +2 -0
- package/dist/components/layout/topbar/Topbar.js.map +2 -2
- package/dist/components/layout/topbar/TopbarActions.d.ts +3 -0
- package/dist/components/layout/topbar/TopbarActions.d.ts.map +1 -0
- package/dist/components/layout/topbar/TopbarActions.js +17 -0
- package/dist/components/layout/topbar/TopbarActions.js.map +6 -0
- package/dist/components/layout/topbar/TopbarContainer.d.ts +1 -1
- package/dist/components/layout/topbar/TopbarContainer.d.ts.map +1 -1
- package/dist/components/layout/topbar/TopbarContainer.js +21 -12
- package/dist/components/layout/topbar/TopbarContainer.js.map +2 -2
- package/dist/components/layout/topbar/TopbarContext.d.ts +9 -0
- package/dist/components/layout/topbar/TopbarContext.d.ts.map +1 -0
- package/dist/components/layout/topbar/TopbarContext.js +29 -0
- package/dist/components/layout/topbar/TopbarContext.js.map +6 -0
- package/dist/components/layout/topbar/TopbarMenu.d.ts.map +1 -1
- package/dist/components/layout/topbar/TopbarMenu.js +63 -57
- package/dist/components/layout/topbar/TopbarMenu.js.map +2 -2
- package/dist/components/layout/topbar/TopbarUser.d.ts.map +1 -1
- package/dist/components/layout/topbar/TopbarUser.js +53 -54
- package/dist/components/layout/topbar/TopbarUser.js.map +2 -2
- package/dist/hooks/createControllableStore.d.ts +29 -0
- package/dist/hooks/createControllableStore.d.ts.map +1 -0
- package/dist/hooks/createControllableStore.js +19 -0
- package/dist/hooks/createControllableStore.js.map +6 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/styles/patterns.styles.d.ts.map +1 -1
- package/dist/styles/patterns.styles.js +7 -1
- package/dist/styles/patterns.styles.js.map +1 -1
- package/docs/data-components.md +428 -0
- package/docs/disclosure.md +65 -35
- package/docs/form-controls.md +18 -3
- package/docs/helpers.md +0 -39
- package/docs/hooks.md +39 -0
- package/docs/layout.md +70 -1
- package/package.json +4 -3
- package/src/components/data/crud-detail/CrudDetail.tsx +346 -0
- package/src/components/data/crud-detail/CrudDetailAfter.tsx +19 -0
- package/src/components/data/crud-detail/CrudDetailBefore.tsx +19 -0
- package/src/components/data/crud-detail/CrudDetailTools.tsx +19 -0
- package/src/components/data/crud-detail/types.ts +58 -0
- package/src/components/data/crud-sheet/CrudSheet.tsx +628 -0
- package/src/components/data/crud-sheet/CrudSheetColumn.tsx +34 -0
- package/src/components/data/crud-sheet/CrudSheetFilter.tsx +21 -0
- package/src/components/data/crud-sheet/CrudSheetHeader.tsx +19 -0
- package/src/components/data/crud-sheet/CrudSheetTools.tsx +21 -0
- package/src/components/data/crud-sheet/types.ts +133 -0
- package/src/components/data/kanban/Kanban.tsx +72 -65
- package/src/components/data/kanban/KanbanContext.ts +7 -1
- package/src/components/data/list/ListItem.tsx +31 -18
- package/src/components/data/sheet/DataSheet.css +28 -10
- package/src/components/data/sheet/DataSheet.styles.ts +1 -1
- package/src/components/data/sheet/DataSheet.tsx +1 -1
- package/src/components/disclosure/Dialog.tsx +143 -105
- package/src/components/disclosure/DialogContext.ts +2 -4
- package/src/components/disclosure/DialogProvider.tsx +4 -2
- package/src/components/disclosure/Dropdown.tsx +174 -86
- package/src/components/feedback/notification/NotificationBanner.tsx +3 -9
- package/src/components/feedback/notification/NotificationBell.tsx +51 -57
- package/src/components/form-control/Invalid.tsx +1 -1
- package/src/components/form-control/combobox/Combobox.tsx +109 -133
- package/src/components/form-control/combobox/ComboboxContext.ts +4 -1
- package/src/components/form-control/date-range-picker/DateRangePicker.tsx +6 -16
- package/src/components/form-control/field/DatePicker.tsx +4 -1
- package/src/components/form-control/field/DateTimePicker.tsx +3 -0
- package/src/components/form-control/field/Field.styles.ts +1 -0
- package/src/components/form-control/field/NumberInput.tsx +131 -86
- package/src/components/form-control/field/TextInput.tsx +139 -88
- package/src/components/form-control/field/TimePicker.tsx +3 -0
- package/src/components/form-control/select/Select.tsx +85 -67
- package/src/components/form-control/select/SelectContext.ts +12 -1
- package/src/components/form-control/select/SelectItem.tsx +39 -18
- package/src/components/form-control/state-preset/StatePreset.tsx +1 -0
- package/src/components/layout/topbar/Topbar.tsx +3 -0
- package/src/components/layout/topbar/TopbarActions.tsx +8 -0
- package/src/components/layout/topbar/TopbarContainer.tsx +9 -5
- package/src/components/layout/topbar/TopbarContext.ts +36 -0
- package/src/components/layout/topbar/TopbarMenu.tsx +52 -55
- package/src/components/layout/topbar/TopbarUser.tsx +28 -31
- package/src/hooks/createControllableStore.ts +47 -0
- package/src/index.ts +6 -1
- package/src/styles/patterns.styles.ts +7 -1
- package/tailwind.css +4 -0
- package/dist/helpers/splitSlots.d.ts +0 -25
- package/dist/helpers/splitSlots.d.ts.map +0 -1
- package/dist/helpers/splitSlots.js +0 -25
- package/dist/helpers/splitSlots.js.map +0 -6
- package/dist/hooks/createItemTemplate.d.ts +0 -17
- package/dist/hooks/createItemTemplate.d.ts.map +0 -1
- package/dist/hooks/createItemTemplate.js +0 -40
- package/dist/hooks/createItemTemplate.js.map +0 -6
- package/src/helpers/splitSlots.ts +0 -51
- package/src/hooks/createItemTemplate.tsx +0 -42
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
type ParentComponent,
|
|
4
4
|
createSignal,
|
|
5
5
|
createEffect,
|
|
6
|
+
createContext,
|
|
7
|
+
useContext,
|
|
6
8
|
onCleanup,
|
|
7
9
|
Show,
|
|
8
10
|
splitProps,
|
|
@@ -12,20 +14,47 @@ import { createMountTransition } from "../../hooks/createMountTransition";
|
|
|
12
14
|
import { Portal } from "solid-js/web";
|
|
13
15
|
import clsx from "clsx";
|
|
14
16
|
import { twMerge } from "tailwind-merge";
|
|
15
|
-
import { createControllableSignal } from "../../hooks/createControllableSignal";
|
|
16
17
|
import { mergeStyles } from "../../helpers/mergeStyles";
|
|
17
18
|
import { borderSubtle } from "../../styles/tokens.styles";
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
// --- DropdownContext (internal) ---
|
|
21
|
+
|
|
22
|
+
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
23
|
+
|
|
24
|
+
interface DropdownContextValue {
|
|
25
|
+
toggle: () => void;
|
|
26
|
+
setTrigger: (content: SlotAccessor) => void;
|
|
27
|
+
setContent: (content: SlotAccessor) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DropdownContext = createContext<DropdownContextValue>();
|
|
31
|
+
|
|
32
|
+
// --- DropdownTrigger ---
|
|
33
|
+
|
|
34
|
+
const DropdownTrigger: ParentComponent = (props) => {
|
|
35
|
+
const ctx = useContext(DropdownContext)!;
|
|
36
|
+
// eslint-disable-next-line solid/reactivity -- slot accessor: children은 렌더 시점에 lazy 평가됨
|
|
37
|
+
ctx.setTrigger(() => props.children);
|
|
38
|
+
onCleanup(() => ctx.setTrigger(undefined));
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// --- DropdownContent ---
|
|
43
|
+
|
|
44
|
+
const DropdownContent: ParentComponent = (props) => {
|
|
45
|
+
const ctx = useContext(DropdownContext)!;
|
|
46
|
+
// eslint-disable-next-line solid/reactivity -- slot accessor: children은 렌더 시점에 lazy 평가됨
|
|
47
|
+
ctx.setContent(() => props.children);
|
|
48
|
+
onCleanup(() => ctx.setContent(undefined));
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
25
51
|
|
|
52
|
+
// --- Dropdown ---
|
|
53
|
+
|
|
54
|
+
export interface DropdownProps {
|
|
26
55
|
/**
|
|
27
56
|
* 절대 위치 (컨텍스트 메뉴 등, minWidth 없음)
|
|
28
|
-
*
|
|
57
|
+
* Trigger와 함께 사용 시 Trigger 기준 위치 계산
|
|
29
58
|
*/
|
|
30
59
|
position?: { x: number; y: number };
|
|
31
60
|
|
|
@@ -44,62 +73,118 @@ export interface DropdownProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>,
|
|
|
44
73
|
*/
|
|
45
74
|
maxHeight?: number;
|
|
46
75
|
|
|
76
|
+
/**
|
|
77
|
+
* 비활성화 (Trigger 클릭 무시)
|
|
78
|
+
*/
|
|
79
|
+
disabled?: boolean;
|
|
80
|
+
|
|
47
81
|
/**
|
|
48
82
|
* 키보드 네비게이션 활성화 (Select 등에서 사용)
|
|
49
83
|
*
|
|
50
84
|
* direction=down일 때:
|
|
51
|
-
* - 트리거에서 ArrowDown
|
|
52
|
-
* - 첫 아이템에서 ArrowUp
|
|
53
|
-
* - 트리거에서 ArrowUp
|
|
85
|
+
* - 트리거에서 ArrowDown -> 첫 focusable 아이템 포커스
|
|
86
|
+
* - 첫 아이템에서 ArrowUp -> 트리거 포커스
|
|
87
|
+
* - 트리거에서 ArrowUp -> 닫기
|
|
54
88
|
*
|
|
55
89
|
* direction=up일 때:
|
|
56
|
-
* - 트리거에서 ArrowUp
|
|
57
|
-
* - 마지막 아이템에서 ArrowDown
|
|
58
|
-
* - 트리거에서 ArrowDown
|
|
90
|
+
* - 트리거에서 ArrowUp -> 마지막 focusable 아이템 포커스
|
|
91
|
+
* - 마지막 아이템에서 ArrowDown -> 트리거 포커스
|
|
92
|
+
* - 트리거에서 ArrowDown -> 닫기
|
|
59
93
|
*/
|
|
60
94
|
keyboardNav?: boolean;
|
|
61
95
|
|
|
62
96
|
/**
|
|
63
|
-
*
|
|
97
|
+
* 팝업에 적용할 커스텀 class
|
|
98
|
+
*/
|
|
99
|
+
class?: string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 팝업에 적용할 커스텀 style
|
|
103
|
+
*/
|
|
104
|
+
style?: JSX.CSSProperties;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* children (Dropdown.Trigger, Dropdown.Content)
|
|
64
108
|
*/
|
|
65
109
|
children: JSX.Element;
|
|
66
110
|
}
|
|
67
111
|
|
|
112
|
+
interface DropdownComponent extends ParentComponent<DropdownProps> {
|
|
113
|
+
Trigger: typeof DropdownTrigger;
|
|
114
|
+
Content: typeof DropdownContent;
|
|
115
|
+
}
|
|
116
|
+
|
|
68
117
|
/**
|
|
69
118
|
* 드롭다운 팝업 컴포넌트
|
|
70
119
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
120
|
+
* Trigger/Content 슬롯 패턴으로 트리거와 컨텐츠를 분리합니다.
|
|
121
|
+
* Trigger 클릭 시 auto-toggle되며, disabled prop으로 비활성화할 수 있습니다.
|
|
73
122
|
*
|
|
74
123
|
* @example
|
|
75
124
|
* ```tsx
|
|
76
|
-
*
|
|
77
|
-
*
|
|
125
|
+
* <Dropdown>
|
|
126
|
+
* <Dropdown.Trigger>
|
|
127
|
+
* <Button>열기</Button>
|
|
128
|
+
* </Dropdown.Trigger>
|
|
129
|
+
* <Dropdown.Content>
|
|
130
|
+
* <div>팝업 내용</div>
|
|
131
|
+
* </Dropdown.Content>
|
|
132
|
+
* </Dropdown>
|
|
133
|
+
* ```
|
|
78
134
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
135
|
+
* @example Context menu (Trigger 없이 position 사용)
|
|
136
|
+
* ```tsx
|
|
137
|
+
* <Dropdown position={{ x: 300, y: 200 }} open={true}>
|
|
138
|
+
* <Dropdown.Content>
|
|
139
|
+
* <div>메뉴</div>
|
|
140
|
+
* </Dropdown.Content>
|
|
82
141
|
* </Dropdown>
|
|
83
142
|
* ```
|
|
84
143
|
*/
|
|
85
|
-
export const Dropdown:
|
|
144
|
+
export const Dropdown: DropdownComponent = ((props: DropdownProps) => {
|
|
86
145
|
const [local, rest] = splitProps(props, [
|
|
87
|
-
"triggerRef",
|
|
88
146
|
"position",
|
|
89
147
|
"open",
|
|
90
148
|
"onOpenChange",
|
|
91
149
|
"maxHeight",
|
|
150
|
+
"disabled",
|
|
92
151
|
"keyboardNav",
|
|
93
152
|
"class",
|
|
94
153
|
"style",
|
|
95
154
|
"children",
|
|
96
155
|
]);
|
|
97
156
|
|
|
98
|
-
const [open,
|
|
99
|
-
|
|
100
|
-
|
|
157
|
+
const [open, setOpenInternal] = createSignal(false);
|
|
158
|
+
|
|
159
|
+
// props.open 변경 시 내부 상태 동기화
|
|
160
|
+
createEffect(() => {
|
|
161
|
+
const propOpen = local.open;
|
|
162
|
+
if (propOpen !== undefined) {
|
|
163
|
+
setOpenInternal(propOpen);
|
|
164
|
+
}
|
|
101
165
|
});
|
|
102
166
|
|
|
167
|
+
// 콜백 포함 setter
|
|
168
|
+
const setOpen = (value: boolean) => {
|
|
169
|
+
setOpenInternal(value);
|
|
170
|
+
local.onOpenChange?.(value);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// toggle 함수 (disabled 체크 포함)
|
|
174
|
+
const toggle = () => {
|
|
175
|
+
if (local.disabled) return;
|
|
176
|
+
setOpen(!open());
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// 슬롯 등록 시그널
|
|
180
|
+
const [triggerSlot, _setTriggerSlot] = createSignal<SlotAccessor>();
|
|
181
|
+
const setTrigger = (content: SlotAccessor) => _setTriggerSlot(() => content);
|
|
182
|
+
const [contentSlot, _setContentSlot] = createSignal<SlotAccessor>();
|
|
183
|
+
const setContent = (content: SlotAccessor) => _setContentSlot(() => content);
|
|
184
|
+
|
|
185
|
+
// Trigger wrapper ref (위치 계산에 필요)
|
|
186
|
+
let triggerRef: HTMLDivElement | undefined;
|
|
187
|
+
|
|
103
188
|
// 팝업 ref
|
|
104
189
|
const [popupRef, setPopupRef] = createSignal<HTMLDivElement>();
|
|
105
190
|
|
|
@@ -119,11 +204,8 @@ export const Dropdown: ParentComponent<DropdownProps> = (props) => {
|
|
|
119
204
|
|
|
120
205
|
const style: JSX.CSSProperties = {};
|
|
121
206
|
|
|
122
|
-
if (
|
|
123
|
-
const
|
|
124
|
-
if (!trigger) return;
|
|
125
|
-
|
|
126
|
-
const rect = trigger.getBoundingClientRect();
|
|
207
|
+
if (triggerRef) {
|
|
208
|
+
const rect = triggerRef.getBoundingClientRect();
|
|
127
209
|
const viewportHeight = window.innerHeight;
|
|
128
210
|
const viewportWidth = window.innerWidth;
|
|
129
211
|
|
|
@@ -193,13 +275,10 @@ export const Dropdown: ParentComponent<DropdownProps> = (props) => {
|
|
|
193
275
|
// 팝업 내부 클릭은 무시
|
|
194
276
|
if (popup?.contains(target)) return;
|
|
195
277
|
|
|
196
|
-
//
|
|
197
|
-
if (
|
|
198
|
-
const trigger = local.triggerRef();
|
|
199
|
-
if (trigger?.contains(target)) return;
|
|
200
|
-
}
|
|
278
|
+
// Trigger 내부 클릭도 무시
|
|
279
|
+
if (triggerRef?.contains(target)) return;
|
|
201
280
|
|
|
202
|
-
// 외부 클릭
|
|
281
|
+
// 외부 클릭 -> 닫기
|
|
203
282
|
setOpen(false);
|
|
204
283
|
};
|
|
205
284
|
|
|
@@ -221,13 +300,10 @@ export const Dropdown: ParentComponent<DropdownProps> = (props) => {
|
|
|
221
300
|
// 팝업 내부로 이동은 무시
|
|
222
301
|
if (popup?.contains(relatedTarget)) return;
|
|
223
302
|
|
|
224
|
-
//
|
|
225
|
-
if (
|
|
226
|
-
const trigger = local.triggerRef();
|
|
227
|
-
if (trigger?.contains(relatedTarget)) return;
|
|
228
|
-
}
|
|
303
|
+
// Trigger 내부로 이동도 무시
|
|
304
|
+
if (triggerRef?.contains(relatedTarget)) return;
|
|
229
305
|
|
|
230
|
-
// 외부로 포커스 이동
|
|
306
|
+
// 외부로 포커스 이동 -> 닫기
|
|
231
307
|
setOpen(false);
|
|
232
308
|
};
|
|
233
309
|
|
|
@@ -301,8 +377,7 @@ export const Dropdown: ParentComponent<DropdownProps> = (props) => {
|
|
|
301
377
|
// List 등에서 이미 처리된 이벤트는 무시
|
|
302
378
|
if (e.defaultPrevented) return;
|
|
303
379
|
|
|
304
|
-
|
|
305
|
-
if (!trigger) return;
|
|
380
|
+
if (!triggerRef) return;
|
|
306
381
|
|
|
307
382
|
const dir = direction();
|
|
308
383
|
|
|
@@ -310,24 +385,13 @@ export const Dropdown: ParentComponent<DropdownProps> = (props) => {
|
|
|
310
385
|
// 트리거로 포커스 이동
|
|
311
386
|
if (dir === "down" && e.key === "ArrowUp") {
|
|
312
387
|
e.preventDefault();
|
|
313
|
-
|
|
388
|
+
triggerRef.focus();
|
|
314
389
|
} else if (dir === "up" && e.key === "ArrowDown") {
|
|
315
390
|
e.preventDefault();
|
|
316
|
-
|
|
391
|
+
triggerRef.focus();
|
|
317
392
|
}
|
|
318
393
|
};
|
|
319
394
|
|
|
320
|
-
// 트리거에 키보드 핸들러 등록
|
|
321
|
-
createEffect(() => {
|
|
322
|
-
if (!local.keyboardNav) return;
|
|
323
|
-
|
|
324
|
-
const trigger = local.triggerRef?.();
|
|
325
|
-
if (!trigger) return;
|
|
326
|
-
|
|
327
|
-
trigger.addEventListener("keydown", handleTriggerKeyDown);
|
|
328
|
-
onCleanup(() => trigger.removeEventListener("keydown", handleTriggerKeyDown));
|
|
329
|
-
});
|
|
330
|
-
|
|
331
395
|
// 스크롤 감지
|
|
332
396
|
createEffect(() => {
|
|
333
397
|
if (!open()) return;
|
|
@@ -363,7 +427,7 @@ export const Dropdown: ParentComponent<DropdownProps> = (props) => {
|
|
|
363
427
|
if (e.propertyName !== "opacity") return;
|
|
364
428
|
|
|
365
429
|
if (!open()) {
|
|
366
|
-
// 닫힘 애니메이션 완료
|
|
430
|
+
// 닫힘 애니메이션 완료 -> DOM에서 제거
|
|
367
431
|
unmount();
|
|
368
432
|
}
|
|
369
433
|
};
|
|
@@ -384,33 +448,57 @@ export const Dropdown: ParentComponent<DropdownProps> = (props) => {
|
|
|
384
448
|
};
|
|
385
449
|
|
|
386
450
|
return (
|
|
387
|
-
<
|
|
388
|
-
|
|
451
|
+
<DropdownContext.Provider value={{ toggle, setTrigger, setContent }}>
|
|
452
|
+
{local.children}
|
|
453
|
+
|
|
454
|
+
{/* Trigger 슬롯 렌더링 (wrapper div에 click/keyboard handler 부착) */}
|
|
455
|
+
<Show when={triggerSlot()}>
|
|
389
456
|
<div
|
|
390
|
-
{
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
"z-dropdown",
|
|
397
|
-
"bg-white dark:bg-base-800",
|
|
398
|
-
"border",
|
|
399
|
-
borderSubtle,
|
|
400
|
-
"shadow-lg dark:shadow-black/30",
|
|
401
|
-
"rounded-md",
|
|
402
|
-
"overflow-y-auto",
|
|
403
|
-
animationClass(),
|
|
404
|
-
),
|
|
405
|
-
local.class,
|
|
406
|
-
)}
|
|
407
|
-
style={mergeStyles(computedStyle(), local.style, { "max-height": `${maxHeight()}px` })}
|
|
408
|
-
onTransitionEnd={handleTransitionEnd}
|
|
409
|
-
onKeyDown={handlePopupKeyDown}
|
|
457
|
+
ref={(el) => {
|
|
458
|
+
triggerRef = el;
|
|
459
|
+
}}
|
|
460
|
+
data-dropdown-trigger
|
|
461
|
+
onClick={toggle}
|
|
462
|
+
onKeyDown={handleTriggerKeyDown}
|
|
410
463
|
>
|
|
411
|
-
{
|
|
464
|
+
{triggerSlot()!()}
|
|
412
465
|
</div>
|
|
413
|
-
</
|
|
414
|
-
|
|
466
|
+
</Show>
|
|
467
|
+
|
|
468
|
+
{/* Content 슬롯: Portal + 팝업 */}
|
|
469
|
+
<Show when={mounted()}>
|
|
470
|
+
<Portal>
|
|
471
|
+
<div
|
|
472
|
+
{...rest}
|
|
473
|
+
ref={setPopupRef}
|
|
474
|
+
data-dropdown
|
|
475
|
+
class={twMerge(
|
|
476
|
+
clsx(
|
|
477
|
+
"fixed",
|
|
478
|
+
"z-dropdown",
|
|
479
|
+
"bg-white dark:bg-base-800",
|
|
480
|
+
"border",
|
|
481
|
+
borderSubtle,
|
|
482
|
+
"shadow-lg dark:shadow-black/30",
|
|
483
|
+
"rounded-md",
|
|
484
|
+
"overflow-y-auto",
|
|
485
|
+
animationClass(),
|
|
486
|
+
),
|
|
487
|
+
local.class,
|
|
488
|
+
)}
|
|
489
|
+
style={mergeStyles(computedStyle(), local.style, {
|
|
490
|
+
"max-height": `${maxHeight()}px`,
|
|
491
|
+
})}
|
|
492
|
+
onTransitionEnd={handleTransitionEnd}
|
|
493
|
+
onKeyDown={handlePopupKeyDown}
|
|
494
|
+
>
|
|
495
|
+
<Show when={contentSlot()}>{contentSlot()!()}</Show>
|
|
496
|
+
</div>
|
|
497
|
+
</Portal>
|
|
498
|
+
</Show>
|
|
499
|
+
</DropdownContext.Provider>
|
|
415
500
|
);
|
|
416
|
-
};
|
|
501
|
+
}) as DropdownComponent;
|
|
502
|
+
|
|
503
|
+
Dropdown.Trigger = DropdownTrigger;
|
|
504
|
+
Dropdown.Content = DropdownContent;
|
|
@@ -8,7 +8,7 @@ import { themeTokens } from "../../../styles/tokens.styles";
|
|
|
8
8
|
|
|
9
9
|
const baseClass = clsx(
|
|
10
10
|
"fixed",
|
|
11
|
-
"top-
|
|
11
|
+
"top-8",
|
|
12
12
|
"right-4",
|
|
13
13
|
"z-50",
|
|
14
14
|
"flex",
|
|
@@ -31,15 +31,9 @@ const themeClasses: Record<string, string> = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
const contentClass = clsx("flex flex-col", "gap-0.5", "min-w-0");
|
|
34
|
-
const messageClass = clsx("
|
|
34
|
+
const messageClass = clsx("opacity-90", "overflow-auto");
|
|
35
35
|
const actionsClass = clsx("flex items-center", "gap-2", "shrink-0");
|
|
36
|
-
const actionButtonClass = clsx(
|
|
37
|
-
"rounded",
|
|
38
|
-
"bg-white/20",
|
|
39
|
-
"px-3 py-1",
|
|
40
|
-
"text-sm",
|
|
41
|
-
"hover:bg-white/30",
|
|
42
|
-
);
|
|
36
|
+
const actionButtonClass = clsx("rounded", "bg-white/20", "px-3 py-1", "hover:bg-white/30");
|
|
43
37
|
const dismissButtonClass = clsx("rounded", "p-1", "hover:bg-white/20");
|
|
44
38
|
|
|
45
39
|
export const NotificationBanner: Component = () => {
|
|
@@ -53,7 +53,6 @@ const itemTimeClass = clsx("mt-1 text-xs", "text-base-400");
|
|
|
53
53
|
export const NotificationBell: Component<NotificationBellProps> = (props) => {
|
|
54
54
|
const notification = useNotification();
|
|
55
55
|
const [open, setOpen] = createSignal(false);
|
|
56
|
-
let buttonRef: HTMLButtonElement | undefined;
|
|
57
56
|
|
|
58
57
|
const handleClear = () => {
|
|
59
58
|
notification.clear();
|
|
@@ -73,65 +72,60 @@ export const NotificationBell: Component<NotificationBellProps> = (props) => {
|
|
|
73
72
|
<NotificationBanner />
|
|
74
73
|
</Show>
|
|
75
74
|
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
<Dropdown open={open()} onOpenChange={handleOpenChange} maxHeight={400}>
|
|
76
|
+
<Dropdown.Trigger>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
data-notification-bell
|
|
80
|
+
class={buttonClass}
|
|
81
|
+
aria-label={`알림 ${notification.unreadCount()}개`}
|
|
82
|
+
aria-haspopup="true"
|
|
83
|
+
aria-expanded={open()}
|
|
84
|
+
>
|
|
85
|
+
<Icon icon={IconBell} />
|
|
86
|
+
<Show when={notification.unreadCount() > 0}>
|
|
87
|
+
<span data-notification-badge aria-hidden="true" class={badgeClass}>
|
|
88
|
+
{notification.unreadCount()}
|
|
89
|
+
</span>
|
|
90
|
+
</Show>
|
|
91
|
+
</button>
|
|
92
|
+
</Dropdown.Trigger>
|
|
93
|
+
<Dropdown.Content>
|
|
94
|
+
<div class="w-80 p-2">
|
|
95
|
+
<div class={dropdownHeaderClass}>
|
|
96
|
+
<span class="font-bold">알림</span>
|
|
97
|
+
<Show when={notification.items().length > 0}>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
data-notification-clear
|
|
101
|
+
class={clearButtonClass}
|
|
102
|
+
onClick={handleClear}
|
|
103
|
+
>
|
|
104
|
+
전체 삭제
|
|
105
|
+
</button>
|
|
106
|
+
</Show>
|
|
107
|
+
</div>
|
|
93
108
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
>
|
|
111
|
-
전체 삭제
|
|
112
|
-
</button>
|
|
109
|
+
<Show
|
|
110
|
+
when={notification.items().length > 0}
|
|
111
|
+
fallback={<div class={emptyClass}>알림이 없습니다</div>}
|
|
112
|
+
>
|
|
113
|
+
<div class={listClass}>
|
|
114
|
+
<For each={[...notification.items()].reverse()}>
|
|
115
|
+
{(item) => (
|
|
116
|
+
<div class={clsx(itemBaseClass, themeStyles[item.theme])}>
|
|
117
|
+
<div class="font-medium">{item.title}</div>
|
|
118
|
+
<Show when={item.message}>
|
|
119
|
+
<pre class={itemMessageClass}>{item.message}</pre>
|
|
120
|
+
</Show>
|
|
121
|
+
<div class={itemTimeClass}>{item.createdAt.toLocaleTimeString()}</div>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
</For>
|
|
125
|
+
</div>
|
|
113
126
|
</Show>
|
|
114
127
|
</div>
|
|
115
|
-
|
|
116
|
-
<Show
|
|
117
|
-
when={notification.items().length > 0}
|
|
118
|
-
fallback={<div class={emptyClass}>알림이 없습니다</div>}
|
|
119
|
-
>
|
|
120
|
-
<div class={listClass}>
|
|
121
|
-
<For each={[...notification.items()].reverse()}>
|
|
122
|
-
{(item) => (
|
|
123
|
-
<div class={clsx(itemBaseClass, themeStyles[item.theme])}>
|
|
124
|
-
<div class="font-medium">{item.title}</div>
|
|
125
|
-
<Show when={item.message}>
|
|
126
|
-
<pre class={itemMessageClass}>{item.message}</pre>
|
|
127
|
-
</Show>
|
|
128
|
-
<div class={itemTimeClass}>{item.createdAt.toLocaleTimeString()}</div>
|
|
129
|
-
</div>
|
|
130
|
-
)}
|
|
131
|
-
</For>
|
|
132
|
-
</div>
|
|
133
|
-
</Show>
|
|
134
|
-
</div>
|
|
128
|
+
</Dropdown.Content>
|
|
135
129
|
</Dropdown>
|
|
136
130
|
</>
|
|
137
131
|
);
|
|
@@ -19,7 +19,7 @@ export const Invalid: ParentComponent<InvalidProps> = (props) => {
|
|
|
19
19
|
"size-px opacity-0",
|
|
20
20
|
"pointer-events-none -z-10",
|
|
21
21
|
);
|
|
22
|
-
hiddenInputEl.autocomplete = "
|
|
22
|
+
hiddenInputEl.autocomplete = "one-time-code";
|
|
23
23
|
hiddenInputEl.tabIndex = -1;
|
|
24
24
|
hiddenInputEl.setAttribute("aria-hidden", "true");
|
|
25
25
|
|