@simplysm/solid 13.0.57 → 13.0.58
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/dist/components/data/crud-detail/CrudDetail.d.ts.map +1 -1
- package/dist/components/data/crud-detail/CrudDetail.js +55 -42
- package/dist/components/data/crud-detail/CrudDetail.js.map +2 -2
- package/dist/components/data/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/data/crud-sheet/CrudSheet.js +112 -83
- package/dist/components/data/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/data/crud-sheet/CrudSheetColumn.js +1 -1
- package/dist/components/data/crud-sheet/CrudSheetColumn.js.map +2 -2
- package/dist/components/data/crud-sheet/types.d.ts +3 -2
- package/dist/components/data/crud-sheet/types.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.styles.js +3 -8
- package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
- package/dist/components/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +33 -23
- package/dist/components/disclosure/Dialog.js.map +2 -2
- package/dist/components/display/Icon.js +1 -1
- package/dist/components/display/Icon.js.map +1 -1
- package/dist/components/feedback/notification/NotificationBanner.js +1 -1
- package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
- package/dist/components/feedback/notification/NotificationContext.d.ts +1 -1
- package/dist/components/feedback/notification/NotificationContext.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationContext.js.map +1 -1
- package/dist/components/feedback/notification/NotificationProvider.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationProvider.js +8 -12
- package/dist/components/feedback/notification/NotificationProvider.js.map +2 -2
- package/docs/feedback.md +9 -8
- package/package.json +3 -3
- package/src/components/data/crud-detail/CrudDetail.tsx +45 -40
- package/src/components/data/crud-sheet/CrudSheet.tsx +91 -92
- package/src/components/data/crud-sheet/CrudSheetColumn.tsx +1 -1
- package/src/components/data/crud-sheet/types.ts +3 -2
- package/src/components/data/sheet/DataSheet.styles.ts +3 -8
- package/src/components/disclosure/Dialog.tsx +23 -20
- package/src/components/display/Icon.tsx +1 -1
- package/src/components/feedback/notification/NotificationBanner.tsx +1 -1
- package/src/components/feedback/notification/NotificationContext.ts +2 -7
- package/src/components/feedback/notification/NotificationProvider.tsx +8 -15
- package/tailwind.config.ts +1 -0
|
@@ -21,7 +21,7 @@ import { useNotification } from "../../feedback/notification/NotificationContext
|
|
|
21
21
|
import { Button } from "../../form-control/Button";
|
|
22
22
|
import { Icon } from "../../display/Icon";
|
|
23
23
|
import { FormGroup } from "../../layout/FormGroup";
|
|
24
|
-
import {
|
|
24
|
+
import { createTopbarActions, TopbarContext } from "../../layout/topbar/TopbarContext";
|
|
25
25
|
import { useDialogInstance } from "../../disclosure/DialogInstanceContext";
|
|
26
26
|
import { Dialog } from "../../disclosure/Dialog";
|
|
27
27
|
import { Link } from "../../display/Link";
|
|
@@ -29,6 +29,7 @@ import { createEventListener } from "@solid-primitives/event-listener";
|
|
|
29
29
|
import clsx from "clsx";
|
|
30
30
|
import {
|
|
31
31
|
IconDeviceFloppy,
|
|
32
|
+
IconExternalLink,
|
|
32
33
|
IconFileExcel,
|
|
33
34
|
IconPlus,
|
|
34
35
|
IconRefresh,
|
|
@@ -37,10 +38,10 @@ import {
|
|
|
37
38
|
IconTrashOff,
|
|
38
39
|
IconUpload,
|
|
39
40
|
} from "@tabler/icons-solidjs";
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
41
|
+
import { CrudSheetColumn, isCrudSheetColumnDef } from "./CrudSheetColumn";
|
|
42
|
+
import { CrudSheetFilter, isCrudSheetFilterDef } from "./CrudSheetFilter";
|
|
43
|
+
import { CrudSheetTools, isCrudSheetToolsDef } from "./CrudSheetTools";
|
|
44
|
+
import { CrudSheetHeader, isCrudSheetHeaderDef } from "./CrudSheetHeader";
|
|
44
45
|
import type {
|
|
45
46
|
CrudSheetColumnDef,
|
|
46
47
|
CrudSheetContext,
|
|
@@ -70,6 +71,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
70
71
|
"editable",
|
|
71
72
|
"itemEditable",
|
|
72
73
|
"itemDeletable",
|
|
74
|
+
"itemDeleted",
|
|
73
75
|
"filterInitial",
|
|
74
76
|
"items",
|
|
75
77
|
"onItemsChange",
|
|
@@ -124,28 +126,27 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
124
126
|
|
|
125
127
|
let formRef: HTMLFormElement | undefined;
|
|
126
128
|
|
|
127
|
-
// -- Auto Refresh Effect --
|
|
128
129
|
createEffect(() => {
|
|
129
|
-
|
|
130
|
-
const currSorts = sorts();
|
|
131
|
-
const currPage = page();
|
|
132
|
-
|
|
133
|
-
queueMicrotask(async () => {
|
|
134
|
-
setBusyCount((c) => c + 1);
|
|
135
|
-
await noti.try(async () => {
|
|
136
|
-
await refresh(currLastFilter, currSorts, currPage);
|
|
137
|
-
}, "조회 실패");
|
|
138
|
-
setBusyCount((c) => c - 1);
|
|
139
|
-
setReady(true);
|
|
140
|
-
});
|
|
130
|
+
void doRefresh();
|
|
141
131
|
});
|
|
142
132
|
|
|
143
|
-
async function
|
|
133
|
+
async function doRefresh() {
|
|
134
|
+
setBusyCount((c) => c + 1);
|
|
135
|
+
try {
|
|
136
|
+
await refresh();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
noti.error(err, "조회 실패");
|
|
139
|
+
}
|
|
140
|
+
setBusyCount((c) => c - 1);
|
|
141
|
+
setReady(true);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function refresh() {
|
|
144
145
|
const usePagination = local.itemsPerPage != null;
|
|
145
146
|
const result: SearchResult<TItem> = await local.search(
|
|
146
|
-
|
|
147
|
-
usePagination ?
|
|
148
|
-
|
|
147
|
+
lastFilter(),
|
|
148
|
+
usePagination ? page() : 0,
|
|
149
|
+
sorts(),
|
|
149
150
|
);
|
|
150
151
|
setItems(reconcile(result.items));
|
|
151
152
|
originalItems = objClone(result.items);
|
|
@@ -210,17 +211,14 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
const currLastFilter = lastFilter();
|
|
214
|
-
const currSorts = sorts();
|
|
215
|
-
const currPage = page();
|
|
216
|
-
|
|
217
214
|
setBusyCount((c) => c + 1);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
await local.inlineEdit!.submit(diffs);
|
|
215
|
+
try {
|
|
216
|
+
await local.inlineEdit.submit(diffs);
|
|
221
217
|
noti.success("저장 완료", "저장되었습니다.");
|
|
222
|
-
await refresh(
|
|
223
|
-
}
|
|
218
|
+
await refresh();
|
|
219
|
+
} catch (err) {
|
|
220
|
+
noti.error(err, "저장 실패");
|
|
221
|
+
}
|
|
224
222
|
setBusyCount((c) => c - 1);
|
|
225
223
|
}
|
|
226
224
|
|
|
@@ -236,10 +234,11 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
236
234
|
if (!result) return;
|
|
237
235
|
|
|
238
236
|
setBusyCount((c) => c + 1);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
237
|
+
try {
|
|
238
|
+
await refresh();
|
|
239
|
+
} catch (err) {
|
|
240
|
+
noti.error(err, "조회 실패");
|
|
241
|
+
}
|
|
243
242
|
setBusyCount((c) => c - 1);
|
|
244
243
|
}
|
|
245
244
|
|
|
@@ -249,11 +248,12 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
249
248
|
if (!result) return;
|
|
250
249
|
|
|
251
250
|
setBusyCount((c) => c + 1);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
await refresh(lastFilter(), sorts(), page());
|
|
251
|
+
try {
|
|
252
|
+
await refresh();
|
|
255
253
|
noti.success("삭제 완료", "삭제되었습니다.");
|
|
256
|
-
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
noti.error(err, "삭제 실패");
|
|
256
|
+
}
|
|
257
257
|
setBusyCount((c) => c - 1);
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -262,11 +262,12 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
262
262
|
if (!local.excel) return;
|
|
263
263
|
|
|
264
264
|
setBusyCount((c) => c + 1);
|
|
265
|
-
|
|
266
|
-
await noti.try(async () => {
|
|
265
|
+
try {
|
|
267
266
|
const result = await local.search(lastFilter(), 0, sorts());
|
|
268
|
-
await local.excel
|
|
269
|
-
}
|
|
267
|
+
await local.excel.download(result.items);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
noti.error(err, "엑셀 다운로드 실패");
|
|
270
|
+
}
|
|
270
271
|
setBusyCount((c) => c - 1);
|
|
271
272
|
}
|
|
272
273
|
|
|
@@ -281,12 +282,13 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
281
282
|
if (file == null) return;
|
|
282
283
|
|
|
283
284
|
setBusyCount((c) => c + 1);
|
|
284
|
-
|
|
285
|
-
await noti.try(async () => {
|
|
285
|
+
try {
|
|
286
286
|
await local.excel!.upload!(file);
|
|
287
287
|
noti.success("완료", "엑셀 업로드가 완료되었습니다.");
|
|
288
|
-
await refresh(
|
|
289
|
-
}
|
|
288
|
+
await refresh();
|
|
289
|
+
} catch (err) {
|
|
290
|
+
noti.error(err, "엑셀 업로드 실패");
|
|
291
|
+
}
|
|
290
292
|
setBusyCount((c) => c - 1);
|
|
291
293
|
};
|
|
292
294
|
input.click();
|
|
@@ -364,6 +366,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
364
366
|
|
|
365
367
|
// -- Render --
|
|
366
368
|
const deleteProp = () => local.inlineEdit?.deleteProp;
|
|
369
|
+
const isItemDeleted = (item: TItem) => local.itemDeleted?.(item) ?? false;
|
|
367
370
|
|
|
368
371
|
return (
|
|
369
372
|
<>
|
|
@@ -385,17 +388,19 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
385
388
|
class={clsx("flex h-full flex-col", local.class)}
|
|
386
389
|
>
|
|
387
390
|
{/* Control mode: inline save/refresh bar */}
|
|
388
|
-
<Show when={!isModal && !topbarCtx
|
|
391
|
+
<Show when={!isModal && !topbarCtx}>
|
|
389
392
|
<div class="flex gap-2 p-2 pb-0">
|
|
390
|
-
<
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
393
|
+
<Show when={canEdit() && local.inlineEdit}>
|
|
394
|
+
<Button
|
|
395
|
+
size="sm"
|
|
396
|
+
theme="primary"
|
|
397
|
+
variant="ghost"
|
|
398
|
+
onClick={() => formRef?.requestSubmit()}
|
|
399
|
+
>
|
|
400
|
+
<Icon icon={IconDeviceFloppy} class="mr-1" />
|
|
401
|
+
저장
|
|
402
|
+
</Button>
|
|
403
|
+
</Show>
|
|
399
404
|
<Button size="sm" theme="info" variant="ghost" onClick={handleRefresh}>
|
|
400
405
|
<Icon icon={IconRefresh} class="mr-1" />
|
|
401
406
|
새로고침
|
|
@@ -506,43 +511,34 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
506
511
|
selectedItems={selectedItems()}
|
|
507
512
|
onSelectedItemsChange={setSelectedItems}
|
|
508
513
|
autoSelect={isSelectMode() && local.selectMode === "single" ? "click" : undefined}
|
|
509
|
-
cellClass={(item
|
|
510
|
-
|
|
511
|
-
if (dp != null && Boolean((item as Record<string, unknown>)[dp])) {
|
|
514
|
+
cellClass={(item) => {
|
|
515
|
+
if (isItemDeleted(item)) {
|
|
512
516
|
return clsx("line-through");
|
|
513
517
|
}
|
|
514
518
|
return undefined;
|
|
515
519
|
}}
|
|
516
520
|
>
|
|
517
|
-
{/* Auto delete column */}
|
|
518
|
-
<Show when={deleteProp() != null && canEdit()
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
<
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
: IconTrash
|
|
539
|
-
}
|
|
540
|
-
/>
|
|
541
|
-
</Link>
|
|
542
|
-
</div>
|
|
543
|
-
)}
|
|
544
|
-
</DataSheetColumn>
|
|
545
|
-
)}
|
|
521
|
+
{/* Auto delete column (inline edit only) */}
|
|
522
|
+
<Show when={deleteProp() != null && canEdit()}>
|
|
523
|
+
<DataSheetColumn<TItem>
|
|
524
|
+
key="__delete"
|
|
525
|
+
header=""
|
|
526
|
+
fixed
|
|
527
|
+
sortable={false}
|
|
528
|
+
resizable={false}
|
|
529
|
+
>
|
|
530
|
+
{(dsCtx) => (
|
|
531
|
+
<div class="flex items-center justify-center px-1 py-0.5">
|
|
532
|
+
<Link
|
|
533
|
+
theme="danger"
|
|
534
|
+
disabled={!(local.itemDeletable?.(dsCtx.item) ?? true)}
|
|
535
|
+
onClick={() => handleToggleDelete(dsCtx.item, dsCtx.index)}
|
|
536
|
+
>
|
|
537
|
+
<Icon icon={isItemDeleted(dsCtx.item) ? IconTrashOff : IconTrash} />
|
|
538
|
+
</Link>
|
|
539
|
+
</div>
|
|
540
|
+
)}
|
|
541
|
+
</DataSheetColumn>
|
|
546
542
|
</Show>
|
|
547
543
|
|
|
548
544
|
{/* User-defined columns -- map CrudSheetColumn to DataSheetColumn */}
|
|
@@ -574,20 +570,23 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
574
570
|
// modalEdit editable column -- wrap with edit link
|
|
575
571
|
if (
|
|
576
572
|
local.modalEdit &&
|
|
577
|
-
col.
|
|
573
|
+
col.editTrigger &&
|
|
578
574
|
canEdit() &&
|
|
579
575
|
(local.itemEditable?.(dsCtx.item) ?? true)
|
|
580
576
|
) {
|
|
581
577
|
return (
|
|
582
578
|
<Link
|
|
583
|
-
class="flex
|
|
579
|
+
class={clsx("flex", "gap-1")}
|
|
584
580
|
onClick={(e) => {
|
|
585
581
|
e.preventDefault();
|
|
586
582
|
e.stopPropagation();
|
|
587
583
|
void handleEditItem(dsCtx.item);
|
|
588
584
|
}}
|
|
589
585
|
>
|
|
590
|
-
{
|
|
586
|
+
<div class={"p-1"}>
|
|
587
|
+
<Icon icon={IconExternalLink} />
|
|
588
|
+
</div>
|
|
589
|
+
<div class={"flex-1"}>{col.cell(crudCtx)}</div>
|
|
591
590
|
</Link>
|
|
592
591
|
);
|
|
593
592
|
}
|
|
@@ -28,7 +28,7 @@ export function CrudSheetColumn<TItem>(props: CrudSheetColumnProps<TItem>): JSX.
|
|
|
28
28
|
width: props.width,
|
|
29
29
|
sortable: props.sortable ?? true,
|
|
30
30
|
resizable: props.resizable ?? true,
|
|
31
|
-
|
|
31
|
+
editTrigger: props.editTrigger ?? false,
|
|
32
32
|
} as unknown as JSX.Element;
|
|
33
33
|
}
|
|
34
34
|
/* eslint-enable solid/reactivity */
|
|
@@ -80,6 +80,7 @@ interface CrudSheetBaseProps<TItem, TFilter extends Record<string, any>> {
|
|
|
80
80
|
editable?: boolean;
|
|
81
81
|
itemEditable?: (item: TItem) => boolean;
|
|
82
82
|
itemDeletable?: (item: TItem) => boolean;
|
|
83
|
+
itemDeleted?: (item: TItem) => boolean;
|
|
83
84
|
filterInitial?: TFilter;
|
|
84
85
|
items?: TItem[];
|
|
85
86
|
onItemsChange?: (items: TItem[]) => void;
|
|
@@ -108,12 +109,12 @@ export interface CrudSheetColumnDef<TItem> {
|
|
|
108
109
|
class?: string;
|
|
109
110
|
sortable: boolean;
|
|
110
111
|
resizable: boolean;
|
|
111
|
-
|
|
112
|
+
editTrigger: boolean;
|
|
112
113
|
cell: (ctx: CrudSheetCellContext<TItem>) => JSX.Element;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
export interface CrudSheetColumnProps<TItem> extends Omit<DataSheetColumnProps<TItem>, "children"> {
|
|
116
|
-
|
|
117
|
+
editTrigger?: boolean;
|
|
117
118
|
children: (ctx: CrudSheetCellContext<TItem>) => JSX.Element;
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -3,7 +3,8 @@ import { borderDefault, borderSubtle } from "../../../styles/tokens.styles";
|
|
|
3
3
|
|
|
4
4
|
export const dataSheetContainerClass = clsx(
|
|
5
5
|
"relative",
|
|
6
|
-
"bg-white dark:bg-base-950",
|
|
6
|
+
// "bg-white dark:bg-base-950",
|
|
7
|
+
"bg-base-100 dark:bg-base-900",
|
|
7
8
|
"overflow-auto",
|
|
8
9
|
);
|
|
9
10
|
|
|
@@ -50,13 +51,7 @@ export const sortableThClass = clsx("cursor-pointer", "hover:underline");
|
|
|
50
51
|
export const sortIconClass = clsx("px-1 py-0.5", "bg-base-100 dark:bg-base-900");
|
|
51
52
|
|
|
52
53
|
// 상단 툴바 (설정 버튼 + 페이지네이션)
|
|
53
|
-
export const toolbarClass = clsx(
|
|
54
|
-
"flex items-center gap-2",
|
|
55
|
-
"px-2 py-1",
|
|
56
|
-
"bg-base-50 dark:bg-base-900",
|
|
57
|
-
"border-b",
|
|
58
|
-
borderDefault,
|
|
59
|
-
);
|
|
54
|
+
export const toolbarClass = clsx("flex items-center gap-2", "px-2 py-1", "border-b", borderDefault);
|
|
60
55
|
|
|
61
56
|
// 고정 컬럼 기본 (sticky)
|
|
62
57
|
export const fixedClass = "sticky";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type JSX,
|
|
3
|
-
type ParentComponent,
|
|
4
2
|
createContext,
|
|
5
3
|
createEffect,
|
|
4
|
+
createSignal,
|
|
6
5
|
createUniqueId,
|
|
6
|
+
For,
|
|
7
|
+
type JSX,
|
|
7
8
|
onCleanup,
|
|
9
|
+
type ParentComponent,
|
|
8
10
|
Show,
|
|
9
11
|
splitProps,
|
|
10
|
-
For,
|
|
11
12
|
useContext,
|
|
12
|
-
createSignal,
|
|
13
13
|
} from "solid-js";
|
|
14
14
|
import { Portal } from "solid-js/web";
|
|
15
15
|
import clsx from "clsx";
|
|
@@ -22,7 +22,8 @@ import { mergeStyles } from "../../helpers/mergeStyles";
|
|
|
22
22
|
import { Icon } from "../display/Icon";
|
|
23
23
|
import { borderSubtle } from "../../styles/tokens.styles";
|
|
24
24
|
import { DialogDefaultsContext } from "./DialogContext";
|
|
25
|
-
import { registerDialog, unregisterDialog
|
|
25
|
+
import { bringToFront, registerDialog, unregisterDialog } from "./dialogZIndex";
|
|
26
|
+
import { Button } from "../form-control/Button";
|
|
26
27
|
|
|
27
28
|
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
28
29
|
|
|
@@ -212,11 +213,19 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
212
213
|
local.onCloseComplete?.();
|
|
213
214
|
};
|
|
214
215
|
|
|
215
|
-
// open 변경 시 closeCompleteEmitted 초기화
|
|
216
|
+
// open 변경 시 closeCompleteEmitted 초기화 + fallback unmount 감지
|
|
217
|
+
let wasMounted = false;
|
|
216
218
|
createEffect(() => {
|
|
217
219
|
if (open()) {
|
|
218
220
|
closeCompleteEmitted = false;
|
|
219
221
|
}
|
|
222
|
+
if (mounted()) {
|
|
223
|
+
wasMounted = true;
|
|
224
|
+
} else if (wasMounted) {
|
|
225
|
+
// fallback timer가 transitionend보다 먼저 실행되어 DOM이 제거된 경우,
|
|
226
|
+
// onCloseComplete가 호출되지 않는 문제 방지
|
|
227
|
+
emitCloseComplete();
|
|
228
|
+
}
|
|
220
229
|
});
|
|
221
230
|
|
|
222
231
|
// dialog ref
|
|
@@ -477,7 +486,8 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
477
486
|
);
|
|
478
487
|
|
|
479
488
|
// 헤더 클래스
|
|
480
|
-
const headerClass = () =>
|
|
489
|
+
const headerClass = () =>
|
|
490
|
+
clsx("flex items-center gap-2", "px-3 py-1", "select-none", "border-b", borderSubtle);
|
|
481
491
|
|
|
482
492
|
return (
|
|
483
493
|
<Show when={mounted()}>
|
|
@@ -516,26 +526,19 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
516
526
|
}
|
|
517
527
|
onPointerDown={handleHeaderPointerDown}
|
|
518
528
|
>
|
|
519
|
-
<h5 id={headerId} class={clsx("flex-1
|
|
529
|
+
<h5 id={headerId} class={clsx("flex-1 font-bold")}>
|
|
520
530
|
{header()!()}
|
|
521
531
|
</h5>
|
|
522
532
|
<Show when={action()}>{action()!()}</Show>
|
|
523
533
|
<Show when={local.closable ?? true}>
|
|
524
|
-
<
|
|
534
|
+
<Button
|
|
525
535
|
data-modal-close
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
"inline-flex items-center justify-center",
|
|
529
|
-
"px-3 py-2",
|
|
530
|
-
"text-base-400 dark:text-base-500",
|
|
531
|
-
"hover:text-base-600 dark:hover:text-base-300",
|
|
532
|
-
"cursor-pointer",
|
|
533
|
-
"transition-colors",
|
|
534
|
-
)}
|
|
536
|
+
size={"sm"}
|
|
537
|
+
variant={"ghost"}
|
|
535
538
|
onClick={handleCloseClick}
|
|
536
539
|
>
|
|
537
|
-
<Icon icon={IconX}
|
|
538
|
-
</
|
|
540
|
+
<Icon icon={IconX} />
|
|
541
|
+
</Button>
|
|
539
542
|
</Show>
|
|
540
543
|
</div>
|
|
541
544
|
</Show>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext
|
|
1
|
+
import { type Accessor, createContext, useContext } from "solid-js";
|
|
2
2
|
|
|
3
3
|
/** 알림 테마 */
|
|
4
4
|
export type NotificationTheme = "info" | "success" | "warning" | "danger";
|
|
@@ -59,12 +59,7 @@ export interface NotificationContextValue {
|
|
|
59
59
|
success: (title: string, message?: string, options?: NotificationOptions) => string;
|
|
60
60
|
warning: (title: string, message?: string, options?: NotificationOptions) => string;
|
|
61
61
|
danger: (title: string, message?: string, options?: NotificationOptions) => string;
|
|
62
|
-
|
|
63
|
-
// 에러 처리 (danger 알림 + 로깅)
|
|
64
|
-
try: <TResult>(
|
|
65
|
-
fn: () => Promise<TResult> | TResult,
|
|
66
|
-
header?: string,
|
|
67
|
-
) => Promise<TResult | undefined>;
|
|
62
|
+
error: (err?: any, header?: string) => void;
|
|
68
63
|
|
|
69
64
|
// 알림 수정
|
|
70
65
|
update: (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createMemo, createSignal, type ParentComponent, Show } from "solid-js";
|
|
2
2
|
import {
|
|
3
3
|
NotificationContext,
|
|
4
4
|
type NotificationContextValue,
|
|
@@ -79,20 +79,13 @@ export const NotificationProvider: ParentComponent = (props) => {
|
|
|
79
79
|
return addNotification("danger", title, message, options);
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return await fn();
|
|
88
|
-
} catch (err) {
|
|
89
|
-
if (err instanceof Error) {
|
|
90
|
-
danger(header ?? err.message, header != null ? err.message : undefined);
|
|
91
|
-
logger.error(err.stack ?? err.message);
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
throw err;
|
|
82
|
+
const error = (err?: any, header?: string): void => {
|
|
83
|
+
if (err instanceof Error) {
|
|
84
|
+
danger(header ?? err.message, header != null ? err.message : undefined);
|
|
85
|
+
logger.error(err.stack ?? err.message);
|
|
86
|
+
return;
|
|
95
87
|
}
|
|
88
|
+
throw err;
|
|
96
89
|
};
|
|
97
90
|
|
|
98
91
|
const update = (
|
|
@@ -151,7 +144,7 @@ export const NotificationProvider: ParentComponent = (props) => {
|
|
|
151
144
|
success,
|
|
152
145
|
warning,
|
|
153
146
|
danger,
|
|
154
|
-
|
|
147
|
+
error,
|
|
155
148
|
update,
|
|
156
149
|
remove,
|
|
157
150
|
markAsRead,
|