@simplysm/solid 13.0.55 → 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 +3 -1
- 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.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/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/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/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/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 +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -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/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.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/combobox/Combobox.tsx +109 -134
- 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/Field.styles.ts +1 -0
- package/src/components/form-control/field/NumberInput.tsx +131 -88
- package/src/components/form-control/field/TextInput.tsx +139 -88
- 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/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 +5 -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
|
);
|