@proyecto-viviana/solidaria-components 0.2.9 → 0.3.1
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 +39 -272
- package/dist/ActionBar.d.ts +21 -13
- package/dist/ActionBar.d.ts.map +1 -1
- package/dist/ActionGroup.d.ts +8 -8
- package/dist/ActionGroup.d.ts.map +1 -1
- package/dist/Alert.d.ts +5 -5
- package/dist/Alert.d.ts.map +1 -1
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +18 -7
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +24 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +38 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +32 -7
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +19 -14
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Color.d.ts +103 -14
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +6 -6
- package/dist/ColorEditor.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +85 -19
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +2 -2
- package/dist/ContextualHelpTrigger.d.ts.map +1 -1
- package/dist/DateField.d.ts +8 -6
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +53 -22
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +23 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +6 -6
- package/dist/DragAndDrop.d.ts.map +1 -1
- package/dist/DragPreview.d.ts +2 -2
- package/dist/DragPreview.d.ts.map +1 -1
- package/dist/DropZone.d.ts +4 -4
- package/dist/DropZone.d.ts.map +1 -1
- package/dist/FieldError.d.ts +9 -5
- package/dist/FieldError.d.ts.map +1 -1
- package/dist/FileTrigger.d.ts +3 -3
- package/dist/FileTrigger.d.ts.map +1 -1
- package/dist/Focusable.d.ts +2 -2
- package/dist/Focusable.d.ts.map +1 -1
- package/dist/Form.d.ts +18 -4
- package/dist/Form.d.ts.map +1 -1
- package/dist/GridList.d.ts +32 -12
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +5 -5
- package/dist/Icon.d.ts.map +1 -1
- package/dist/Keyboard.d.ts +1 -1
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +32 -12
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +6 -6
- package/dist/ListDropTargetDelegate.d.ts.map +1 -1
- package/dist/Menu.d.ts +65 -14
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +3 -3
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +5 -5
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +8 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +28 -5
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +2 -2
- package/dist/Pressable.d.ts.map +1 -1
- package/dist/ProgressBar.d.ts +5 -3
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +34 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +2 -2
- package/dist/RouterProvider.d.ts.map +1 -1
- package/dist/SearchField.d.ts +23 -20
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +41 -11
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +3 -3
- package/dist/SelectionIndicator.d.ts.map +1 -1
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +6 -4
- package/dist/SharedElementTransition.d.ts.map +1 -1
- package/dist/Slider.d.ts +12 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +187 -23
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +45 -9
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +2 -2
- package/dist/TextField.d.ts +15 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +6 -6
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +11 -5
- package/dist/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButtonGroup.d.ts +7 -7
- package/dist/ToggleButtonGroup.d.ts.map +1 -1
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +50 -8
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +66 -17
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +12 -12
- package/dist/Virtualizer.d.ts.map +1 -1
- package/dist/VirtualizerLayouts.d.ts +2 -2
- package/dist/VirtualizerLayouts.d.ts.map +1 -1
- package/dist/VisuallyHidden.d.ts +1 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +5 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23253 -18564
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +18116 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +13 -13
- package/dist/useDragAndDrop.d.ts.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +1 -1
- package/dist/virtualizer/Layout.d.ts.map +1 -1
- package/package.json +31 -32
- package/src/ActionBar.tsx +75 -72
- package/src/ActionGroup.tsx +53 -61
- package/src/Alert.tsx +17 -42
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +149 -80
- package/src/Button.tsx +267 -70
- package/src/Calendar.tsx +218 -138
- package/src/Checkbox.tsx +413 -121
- package/src/Collection.tsx +67 -58
- package/src/Color.tsx +803 -380
- package/src/ColorEditor.tsx +131 -149
- package/src/ComboBox.tsx +414 -249
- package/src/ContextualHelpTrigger.tsx +86 -74
- package/src/DateField.tsx +185 -91
- package/src/DatePicker.tsx +524 -213
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +156 -118
- package/src/Disclosure.tsx +127 -80
- package/src/DragAndDrop.tsx +60 -54
- package/src/DragPreview.tsx +13 -11
- package/src/DropZone.tsx +42 -22
- package/src/FieldError.tsx +45 -23
- package/src/FileTrigger.tsx +19 -19
- package/src/Focusable.tsx +21 -24
- package/src/Form.tsx +71 -16
- package/src/GridList.tsx +273 -197
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +22 -43
- package/src/Keyboard.tsx +3 -3
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +125 -75
- package/src/ListBox.tsx +332 -233
- package/src/ListDropTargetDelegate.ts +81 -80
- package/src/Menu.tsx +1023 -274
- package/src/Meter.tsx +38 -56
- package/src/Modal.tsx +251 -176
- package/src/NumberField.tsx +139 -143
- package/src/Popover.tsx +396 -234
- package/src/Pressable.tsx +21 -21
- package/src/ProgressBar.tsx +48 -57
- package/src/RadioGroup.tsx +524 -122
- package/src/RangeCalendar.tsx +157 -90
- package/src/RouterProvider.tsx +30 -47
- package/src/SearchField.tsx +362 -143
- package/src/Select.tsx +656 -233
- package/src/SelectionIndicator.tsx +18 -15
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +103 -97
- package/src/Slider.tsx +138 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1308 -342
- package/src/Tabs.tsx +324 -103
- package/src/TagGroup.tsx +139 -126
- package/src/Text.tsx +3 -3
- package/src/TextField.tsx +389 -79
- package/src/TimeField.tsx +136 -76
- package/src/Toast.tsx +216 -158
- package/src/ToggleButton.tsx +47 -37
- package/src/ToggleButtonGroup.tsx +39 -34
- package/src/Toolbar.tsx +54 -69
- package/src/Tooltip.tsx +387 -119
- package/src/Tree.tsx +651 -368
- package/src/Virtualizer.tsx +208 -180
- package/src/VirtualizerLayouts.ts +45 -30
- package/src/VisuallyHidden.tsx +19 -19
- package/src/contexts.ts +29 -37
- package/src/index.ts +110 -195
- package/src/useDragAndDrop.ts +87 -71
- package/src/utils.tsx +49 -60
- package/src/virtualizer/Layout.ts +14 -22
- package/dist/index.ssr.js +0 -16996
- package/dist/index.ssr.js.map +0 -1
package/src/Toast.tsx
CHANGED
|
@@ -7,46 +7,60 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
type JSX,
|
|
10
|
+
type Accessor,
|
|
10
11
|
createContext,
|
|
11
12
|
createMemo,
|
|
12
13
|
createEffect,
|
|
14
|
+
createSignal,
|
|
13
15
|
onCleanup,
|
|
14
16
|
splitProps,
|
|
15
17
|
Show,
|
|
16
18
|
useContext,
|
|
17
|
-
} from
|
|
18
|
-
import { Portal
|
|
19
|
+
} from "solid-js";
|
|
20
|
+
import { Portal } from "solid-js/web";
|
|
19
21
|
import {
|
|
20
22
|
type ToastState,
|
|
21
23
|
type QueuedToast,
|
|
22
24
|
type ToastQueueOptions,
|
|
25
|
+
type ToastOptions,
|
|
23
26
|
ToastQueue,
|
|
24
27
|
createToastState,
|
|
25
|
-
} from
|
|
28
|
+
} from "@proyecto-viviana/solid-stately";
|
|
26
29
|
import {
|
|
27
30
|
createToast,
|
|
28
31
|
createToastRegion,
|
|
29
32
|
useUNSAFE_PortalContext,
|
|
30
|
-
} from
|
|
33
|
+
} from "@proyecto-viviana/solidaria";
|
|
31
34
|
import {
|
|
32
35
|
type RenderChildren,
|
|
33
36
|
type ClassNameOrFunction,
|
|
34
37
|
type StyleOrFunction,
|
|
35
38
|
useRenderProps,
|
|
36
39
|
filterDOMProps,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// ============================================
|
|
40
|
-
// TYPES
|
|
41
|
-
// ============================================
|
|
40
|
+
useIsHydrated,
|
|
41
|
+
} from "./utils";
|
|
42
42
|
|
|
43
43
|
export interface ToastContent {
|
|
44
|
+
/** DOM id for the toast root when content is queued through Spectrum ToastQueue. */
|
|
45
|
+
id?: string;
|
|
46
|
+
/** Data attributes for the toast root when content is queued through Spectrum ToastQueue. */
|
|
47
|
+
[dataAttribute: `data-${string}`]: string | number | boolean | undefined;
|
|
48
|
+
/** Spectrum Toast message content. */
|
|
49
|
+
children?: string;
|
|
44
50
|
/** The title of the toast. */
|
|
45
51
|
title?: string;
|
|
46
52
|
/** The description/body of the toast. */
|
|
47
53
|
description?: string;
|
|
48
|
-
/**
|
|
49
|
-
|
|
54
|
+
/** Spectrum variant. */
|
|
55
|
+
variant?: "info" | "positive" | "negative" | "neutral";
|
|
56
|
+
/** Backward-compatible type/variant of the toast. */
|
|
57
|
+
type?: "info" | "success" | "warning" | "error" | "positive" | "negative" | "neutral";
|
|
58
|
+
/** Spectrum action button label. */
|
|
59
|
+
actionLabel?: string;
|
|
60
|
+
/** Handler called when the Spectrum action button is pressed. */
|
|
61
|
+
onAction?: () => void;
|
|
62
|
+
/** Whether the toast should close when the Spectrum action is performed. */
|
|
63
|
+
shouldCloseOnAction?: boolean;
|
|
50
64
|
/** Custom action button. */
|
|
51
65
|
action?: {
|
|
52
66
|
label: string;
|
|
@@ -60,14 +74,14 @@ export interface ToastRenderProps {
|
|
|
60
74
|
/** Whether the toast is currently animating out. */
|
|
61
75
|
isExiting: boolean;
|
|
62
76
|
/** The animation state (entering, exiting, queued). */
|
|
63
|
-
animation:
|
|
77
|
+
animation: "entering" | "exiting" | "queued" | undefined;
|
|
64
78
|
/** The toast data. */
|
|
65
79
|
toast: QueuedToast<ToastContent>;
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
export interface ToastRegionRenderProps {
|
|
69
83
|
/** The visible toasts. */
|
|
70
|
-
visibleToasts: QueuedToast<ToastContent>[]
|
|
84
|
+
visibleToasts: Accessor<QueuedToast<ToastContent>[]>;
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
export interface ToastRegionProps {
|
|
@@ -80,14 +94,28 @@ export interface ToastRegionProps {
|
|
|
80
94
|
/** The toast state to display. If not provided, uses ToastContext. */
|
|
81
95
|
state?: ToastState<ToastContent>;
|
|
82
96
|
/** Accessible label for the region. */
|
|
83
|
-
|
|
97
|
+
"aria-label"?: string;
|
|
84
98
|
/** Whether to render in a portal. */
|
|
85
99
|
portal?: boolean;
|
|
86
100
|
/** Placement of the toast region. */
|
|
87
|
-
placement?:
|
|
101
|
+
placement?:
|
|
102
|
+
| "top"
|
|
103
|
+
| "top start"
|
|
104
|
+
| "top end"
|
|
105
|
+
| "top-start"
|
|
106
|
+
| "top-end"
|
|
107
|
+
| "bottom"
|
|
108
|
+
| "bottom start"
|
|
109
|
+
| "bottom end"
|
|
110
|
+
| "bottom-start"
|
|
111
|
+
| "bottom-end";
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
export interface ToastProps {
|
|
115
|
+
/** DOM id for the toast root. */
|
|
116
|
+
id?: string;
|
|
117
|
+
/** Data attributes for the toast root. */
|
|
118
|
+
[dataAttribute: `data-${string}`]: string | number | boolean | undefined;
|
|
91
119
|
/** The toast data. */
|
|
92
120
|
toast: QueuedToast<ToastContent>;
|
|
93
121
|
/** The children of the component - can be JSX or render function. */
|
|
@@ -98,10 +126,6 @@ export interface ToastProps {
|
|
|
98
126
|
style?: StyleOrFunction<ToastRenderProps>;
|
|
99
127
|
}
|
|
100
128
|
|
|
101
|
-
// ============================================
|
|
102
|
-
// CONTEXT
|
|
103
|
-
// ============================================
|
|
104
|
-
|
|
105
129
|
export const ToastContext = createContext<ToastState<ToastContent> | null>(null);
|
|
106
130
|
|
|
107
131
|
interface ToastAriaContextValue {
|
|
@@ -110,22 +134,19 @@ interface ToastAriaContextValue {
|
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
const ToastAriaContext = createContext<ToastAriaContextValue | null>(null);
|
|
137
|
+
const toastStateByKey = new Map<string, ToastState<ToastContent>>();
|
|
113
138
|
|
|
114
139
|
export function useToastContext(): ToastState<ToastContent> {
|
|
115
140
|
const context = useContext(ToastContext);
|
|
116
141
|
if (!context) {
|
|
117
|
-
throw new Error(
|
|
142
|
+
throw new Error("Toast components must be used within a ToastProvider");
|
|
118
143
|
}
|
|
119
144
|
return context;
|
|
120
145
|
}
|
|
121
146
|
|
|
122
|
-
// ============================================
|
|
123
|
-
// GLOBAL TOAST QUEUE
|
|
124
|
-
// ============================================
|
|
125
|
-
|
|
126
147
|
/** Default global toast queue that can be used for app-wide toasts. */
|
|
127
148
|
export const globalToastQueue = new ToastQueue<ToastContent>({
|
|
128
|
-
maxVisibleToasts:
|
|
149
|
+
maxVisibleToasts: Infinity,
|
|
129
150
|
hasExitAnimation: true,
|
|
130
151
|
});
|
|
131
152
|
|
|
@@ -133,16 +154,13 @@ export const globalToastQueue = new ToastQueue<ToastContent>({
|
|
|
133
154
|
* Add a toast to the global queue.
|
|
134
155
|
* Convenience function for adding toasts from anywhere in the app.
|
|
135
156
|
*/
|
|
136
|
-
export function addToast(
|
|
137
|
-
content: ToastContent,
|
|
138
|
-
options?: { timeout?: number; priority?: number }
|
|
139
|
-
): string {
|
|
157
|
+
export function addToast(content: ToastContent, options?: ToastOptions): string {
|
|
140
158
|
return globalToastQueue.add(content, options);
|
|
141
159
|
}
|
|
142
160
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
161
|
+
function normalizeToastPlacement(placement?: ToastRegionProps["placement"]) {
|
|
162
|
+
return (placement ?? "bottom").replace("-", " ") as NonNullable<ToastRegionProps["placement"]>;
|
|
163
|
+
}
|
|
146
164
|
|
|
147
165
|
export interface ToastProviderProps {
|
|
148
166
|
/** The children of the provider. */
|
|
@@ -172,17 +190,9 @@ export function ToastProvider(props: ToastProviderProps): JSX.Element {
|
|
|
172
190
|
|
|
173
191
|
const state = createToastState({ queue });
|
|
174
192
|
|
|
175
|
-
return
|
|
176
|
-
<ToastContext.Provider value={state}>
|
|
177
|
-
{props.children}
|
|
178
|
-
</ToastContext.Provider>
|
|
179
|
-
);
|
|
193
|
+
return <ToastContext.Provider value={state}>{props.children}</ToastContext.Provider>;
|
|
180
194
|
}
|
|
181
195
|
|
|
182
|
-
// ============================================
|
|
183
|
-
// TOAST REGION COMPONENT
|
|
184
|
-
// ============================================
|
|
185
|
-
|
|
186
196
|
/**
|
|
187
197
|
* ToastRegion is a container that displays all visible toasts.
|
|
188
198
|
* It handles pause on hover/focus and provides the landmark region.
|
|
@@ -199,91 +209,117 @@ export function ToastProvider(props: ToastProviderProps): JSX.Element {
|
|
|
199
209
|
* ```
|
|
200
210
|
*/
|
|
201
211
|
export function ToastRegion(props: ToastRegionProps): JSX.Element {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
// Do NOT early-return on the server: returning null on the server and a <Show>
|
|
213
|
+
// on the client desyncs hydration when the region is in the SSR tree. Render the
|
|
214
|
+
// same structure on both and gate the Portal on useIsHydrated() (see Popover).
|
|
215
|
+
const isHydrated = useIsHydrated();
|
|
205
216
|
|
|
206
217
|
const [local, rest] = splitProps(props, [
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
"children",
|
|
219
|
+
"class",
|
|
220
|
+
"style",
|
|
221
|
+
"state",
|
|
222
|
+
"aria-label",
|
|
223
|
+
"portal",
|
|
224
|
+
"placement",
|
|
214
225
|
]);
|
|
215
226
|
const portalContext = useUNSAFE_PortalContext();
|
|
216
227
|
const portalContainer = () => portalContext.getContainer?.() ?? undefined;
|
|
228
|
+
const [regionElement, setRegionElement] = createSignal<HTMLElement>();
|
|
217
229
|
|
|
218
|
-
// Get state from context if not provided
|
|
219
230
|
const contextState = useContext(ToastContext);
|
|
220
231
|
const state = () => local.state ?? contextState;
|
|
221
232
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
233
|
+
const regionAria = createToastRegion<ToastContent>({
|
|
234
|
+
state: {
|
|
235
|
+
visibleToasts: () => state()?.visibleToasts() ?? [],
|
|
236
|
+
add: (content, options) => state()?.add(content, options) ?? "",
|
|
237
|
+
close: (key) => state()?.close(key),
|
|
238
|
+
remove: (key) => state()?.remove(key),
|
|
239
|
+
clear: () => state()?.clear(),
|
|
240
|
+
pauseAll: () => state()?.pauseAll(),
|
|
241
|
+
resumeAll: () => state()?.resumeAll(),
|
|
242
|
+
},
|
|
243
|
+
ref: regionElement,
|
|
244
|
+
get "aria-label"() {
|
|
245
|
+
return local["aria-label"];
|
|
246
|
+
},
|
|
247
|
+
});
|
|
231
248
|
|
|
232
|
-
// Render props values
|
|
233
249
|
const renderValues = createMemo<ToastRegionRenderProps>(() => ({
|
|
234
|
-
visibleToasts: state()?.visibleToasts() ?? [],
|
|
250
|
+
visibleToasts: () => state()?.visibleToasts() ?? [],
|
|
235
251
|
}));
|
|
236
252
|
|
|
237
|
-
// Resolve render props
|
|
238
253
|
const renderProps = useRenderProps(
|
|
239
254
|
{
|
|
240
|
-
|
|
255
|
+
// Lazy: the region content is gated behind `<Show when={isHydrated() && …}>`
|
|
256
|
+
// (Portal) below, so reading children must not instantiate templates during
|
|
257
|
+
// the component body (would walk getNextElement before the gate the server
|
|
258
|
+
// kept closed → hydration mismatch). See Popover for the full rationale.
|
|
259
|
+
get children() {
|
|
260
|
+
return props.children;
|
|
261
|
+
},
|
|
241
262
|
class: local.class,
|
|
242
263
|
style: local.style,
|
|
243
|
-
defaultClassName:
|
|
264
|
+
defaultClassName: "solidaria-ToastRegion",
|
|
244
265
|
},
|
|
245
|
-
renderValues
|
|
266
|
+
renderValues,
|
|
246
267
|
);
|
|
268
|
+
const renderedChildren = createMemo(() => renderProps.renderChildren());
|
|
247
269
|
|
|
248
|
-
|
|
249
|
-
|
|
270
|
+
const domProps = createMemo(() =>
|
|
271
|
+
filterDOMProps(rest as Record<string, unknown>, { global: true }),
|
|
272
|
+
);
|
|
250
273
|
|
|
251
|
-
// Placement styles
|
|
252
274
|
const placementStyles = createMemo<JSX.CSSProperties>(() => {
|
|
253
|
-
const placement = local.placement
|
|
275
|
+
const placement = normalizeToastPlacement(local.placement);
|
|
276
|
+
const [edge, align = "center"] = placement.split(" ");
|
|
254
277
|
const base: JSX.CSSProperties = {
|
|
255
|
-
position:
|
|
256
|
-
|
|
257
|
-
display:
|
|
258
|
-
|
|
259
|
-
gap:
|
|
260
|
-
padding:
|
|
261
|
-
|
|
278
|
+
position: "fixed",
|
|
279
|
+
"z-index": 100001,
|
|
280
|
+
display: "flex",
|
|
281
|
+
"flex-direction": edge === "top" ? "column" : "column-reverse",
|
|
282
|
+
gap: "8px",
|
|
283
|
+
padding: "16px",
|
|
284
|
+
"pointer-events": "none",
|
|
262
285
|
};
|
|
263
286
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return { ...base, top:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
if (edge === "top") {
|
|
288
|
+
if (align === "end") {
|
|
289
|
+
return { ...base, top: "16px", right: "16px" } as JSX.CSSProperties;
|
|
290
|
+
}
|
|
291
|
+
if (align === "start") {
|
|
292
|
+
return { ...base, top: "16px", left: "16px" } as JSX.CSSProperties;
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
...base,
|
|
296
|
+
top: "16px",
|
|
297
|
+
left: "50%",
|
|
298
|
+
transform: "translateX(-50%)",
|
|
299
|
+
} as JSX.CSSProperties;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (align === "end") {
|
|
303
|
+
return { ...base, bottom: "16px", right: "16px" } as JSX.CSSProperties;
|
|
304
|
+
}
|
|
305
|
+
if (align === "start") {
|
|
306
|
+
return { ...base, bottom: "16px", left: "16px" } as JSX.CSSProperties;
|
|
278
307
|
}
|
|
308
|
+
return {
|
|
309
|
+
...base,
|
|
310
|
+
bottom: "16px",
|
|
311
|
+
left: "50%",
|
|
312
|
+
transform: "translateX(-50%)",
|
|
313
|
+
} as JSX.CSSProperties;
|
|
279
314
|
});
|
|
280
315
|
|
|
316
|
+
const normalizedPlacement = () => normalizeToastPlacement(local.placement);
|
|
317
|
+
|
|
281
318
|
const visibleToasts = () => state()?.visibleToasts() ?? [];
|
|
282
319
|
const hasToasts = () => visibleToasts().length > 0;
|
|
283
320
|
|
|
284
321
|
const regionContent = () => {
|
|
285
|
-
|
|
286
|
-
if (!regionAria || !state()) return null;
|
|
322
|
+
if (!state()) return null;
|
|
287
323
|
|
|
288
324
|
// Merge styles - placement styles are base, renderProps.style() overrides
|
|
289
325
|
const mergedStyle = () => {
|
|
@@ -293,25 +329,24 @@ export function ToastRegion(props: ToastRegionProps): JSX.Element {
|
|
|
293
329
|
return { ...placement, ...custom } as JSX.CSSProperties;
|
|
294
330
|
};
|
|
295
331
|
|
|
296
|
-
// Extract ref from regionProps to avoid type conflicts
|
|
297
332
|
const { ref: _ref, ...cleanRegionProps } = regionAria.regionProps as Record<string, unknown>;
|
|
298
333
|
|
|
299
334
|
return (
|
|
300
335
|
<div
|
|
336
|
+
ref={setRegionElement}
|
|
301
337
|
{...domProps()}
|
|
302
338
|
{...cleanRegionProps}
|
|
303
339
|
class={renderProps.class()}
|
|
304
340
|
style={mergedStyle()}
|
|
305
|
-
data-placement={
|
|
341
|
+
data-placement={normalizedPlacement()}
|
|
306
342
|
>
|
|
307
|
-
{
|
|
343
|
+
{renderedChildren()}
|
|
308
344
|
</div>
|
|
309
345
|
);
|
|
310
346
|
};
|
|
311
347
|
|
|
312
|
-
// Only render when there are toasts
|
|
313
348
|
return (
|
|
314
|
-
<Show when={hasToasts()}>
|
|
349
|
+
<Show when={isHydrated() && hasToasts()}>
|
|
315
350
|
<Show when={local.portal !== false} fallback={regionContent()}>
|
|
316
351
|
<Portal mount={portalContainer()}>{regionContent()}</Portal>
|
|
317
352
|
</Show>
|
|
@@ -319,10 +354,6 @@ export function ToastRegion(props: ToastRegionProps): JSX.Element {
|
|
|
319
354
|
);
|
|
320
355
|
}
|
|
321
356
|
|
|
322
|
-
// ============================================
|
|
323
|
-
// TOAST COMPONENT
|
|
324
|
-
// ============================================
|
|
325
|
-
|
|
326
357
|
/**
|
|
327
358
|
* Toast is an individual notification component.
|
|
328
359
|
*
|
|
@@ -339,53 +370,64 @@ export function ToastRegion(props: ToastRegionProps): JSX.Element {
|
|
|
339
370
|
* ```
|
|
340
371
|
*/
|
|
341
372
|
export function Toast(props: ToastProps): JSX.Element {
|
|
342
|
-
const [local, rest] = splitProps(props, [
|
|
343
|
-
'toast',
|
|
344
|
-
'children',
|
|
345
|
-
'class',
|
|
346
|
-
'style',
|
|
347
|
-
]);
|
|
373
|
+
const [local, rest] = splitProps(props, ["toast", "children", "class", "style"]);
|
|
348
374
|
|
|
349
375
|
let toastRef!: HTMLDivElement;
|
|
350
376
|
|
|
351
|
-
// Get state from context
|
|
352
377
|
const state = useToastContext();
|
|
353
378
|
|
|
354
|
-
|
|
379
|
+
createEffect(() => {
|
|
380
|
+
const key = local.toast.key;
|
|
381
|
+
toastStateByKey.set(key, state);
|
|
382
|
+
onCleanup(() => {
|
|
383
|
+
if (toastStateByKey.get(key) === state) {
|
|
384
|
+
toastStateByKey.delete(key);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const hasTitle = () => !!(local.toast.content.children ?? local.toast.content.title);
|
|
355
390
|
const toastAria = createToast({
|
|
356
391
|
toast: local.toast,
|
|
357
392
|
state,
|
|
358
|
-
hasTitle:
|
|
393
|
+
hasTitle: hasTitle(),
|
|
359
394
|
hasDescription: !!local.toast.content.description,
|
|
360
395
|
});
|
|
361
396
|
|
|
362
|
-
// Render props values
|
|
363
397
|
const renderValues = createMemo<ToastRenderProps>(() => ({
|
|
364
|
-
isEntering: local.toast.animation ===
|
|
365
|
-
isExiting: local.toast.animation ===
|
|
398
|
+
isEntering: local.toast.animation === "entering",
|
|
399
|
+
isExiting: local.toast.animation === "exiting",
|
|
366
400
|
animation: local.toast.animation,
|
|
367
401
|
toast: local.toast,
|
|
368
402
|
}));
|
|
369
403
|
|
|
370
|
-
// Resolve render props
|
|
371
404
|
const renderProps = useRenderProps(
|
|
372
405
|
{
|
|
373
406
|
children: props.children,
|
|
374
407
|
class: local.class,
|
|
375
408
|
style: local.style,
|
|
376
|
-
defaultClassName:
|
|
409
|
+
defaultClassName: "solidaria-Toast",
|
|
377
410
|
},
|
|
378
|
-
renderValues
|
|
411
|
+
renderValues,
|
|
379
412
|
);
|
|
380
413
|
|
|
381
|
-
|
|
382
|
-
|
|
414
|
+
const domProps = createMemo(() =>
|
|
415
|
+
filterDOMProps(rest as Record<string, unknown>, { global: true }),
|
|
416
|
+
);
|
|
383
417
|
|
|
384
|
-
// Merge styles
|
|
385
418
|
const mergedStyle = () => {
|
|
386
419
|
const custom = renderProps.style();
|
|
387
|
-
if (!custom) return {
|
|
388
|
-
return {
|
|
420
|
+
if (!custom) return { "pointer-events": "auto" as const };
|
|
421
|
+
return { "pointer-events": "auto" as const, ...custom } as JSX.CSSProperties;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const handleRootClick = (event: MouseEvent) => {
|
|
425
|
+
const target = event.target;
|
|
426
|
+
if (!(target instanceof Element)) return;
|
|
427
|
+
if (target.closest("[data-solidaria-toast-close-button]")) {
|
|
428
|
+
state.close(local.toast.key);
|
|
429
|
+
state.remove(local.toast.key);
|
|
430
|
+
}
|
|
389
431
|
};
|
|
390
432
|
|
|
391
433
|
// Exit animation lifecycle:
|
|
@@ -395,14 +437,14 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
395
437
|
// Reduced-motion is handled by CSS (shorter/no animations), so the lifecycle
|
|
396
438
|
// naturally completes faster when the user prefers reduced motion.
|
|
397
439
|
createEffect(() => {
|
|
398
|
-
if (local.toast.animation !==
|
|
440
|
+
if (local.toast.animation !== "exiting") return;
|
|
399
441
|
if (!toastRef) {
|
|
400
442
|
state.remove(local.toast.key);
|
|
401
443
|
return;
|
|
402
444
|
}
|
|
403
445
|
|
|
404
446
|
// Check if the element supports the Web Animations API
|
|
405
|
-
if (!(
|
|
447
|
+
if (!("getAnimations" in toastRef)) {
|
|
406
448
|
state.remove(local.toast.key);
|
|
407
449
|
return;
|
|
408
450
|
}
|
|
@@ -434,7 +476,6 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
434
476
|
});
|
|
435
477
|
});
|
|
436
478
|
|
|
437
|
-
// Extract ref from toastProps to avoid type conflicts
|
|
438
479
|
const { ref: _ref, ...cleanToastProps } = toastAria.toastProps as Record<string, unknown>;
|
|
439
480
|
|
|
440
481
|
// Ensure ARIA title/description IDs are present on rendered sub-components,
|
|
@@ -443,17 +484,19 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
443
484
|
if (!toastRef) return;
|
|
444
485
|
|
|
445
486
|
const titleId = (toastAria.titleProps as Record<string, unknown>).id as string | undefined;
|
|
446
|
-
const descriptionId = (toastAria.descriptionProps as Record<string, unknown>).id as
|
|
487
|
+
const descriptionId = (toastAria.descriptionProps as Record<string, unknown>).id as
|
|
488
|
+
| string
|
|
489
|
+
| undefined;
|
|
447
490
|
|
|
448
491
|
if (titleId) {
|
|
449
|
-
const titleEl = toastRef.querySelector(
|
|
492
|
+
const titleEl = toastRef.querySelector("[data-solidaria-toast-title]");
|
|
450
493
|
if (titleEl instanceof HTMLElement) {
|
|
451
494
|
titleEl.id = titleId;
|
|
452
495
|
}
|
|
453
496
|
}
|
|
454
497
|
|
|
455
498
|
if (descriptionId) {
|
|
456
|
-
const descriptionEl = toastRef.querySelector(
|
|
499
|
+
const descriptionEl = toastRef.querySelector("[data-solidaria-toast-description]");
|
|
457
500
|
if (descriptionEl instanceof HTMLElement) {
|
|
458
501
|
descriptionEl.id = descriptionId;
|
|
459
502
|
}
|
|
@@ -461,7 +504,9 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
461
504
|
});
|
|
462
505
|
|
|
463
506
|
return (
|
|
464
|
-
<ToastAriaContext.Provider
|
|
507
|
+
<ToastAriaContext.Provider
|
|
508
|
+
value={{ titleProps: toastAria.titleProps, descriptionProps: toastAria.descriptionProps }}
|
|
509
|
+
>
|
|
465
510
|
<div
|
|
466
511
|
ref={toastRef}
|
|
467
512
|
{...domProps()}
|
|
@@ -469,7 +514,9 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
469
514
|
class={renderProps.class()}
|
|
470
515
|
style={mergedStyle()}
|
|
471
516
|
data-animation={local.toast.animation}
|
|
472
|
-
data-type={local.toast.content.type}
|
|
517
|
+
data-type={local.toast.content.type ?? local.toast.content.variant}
|
|
518
|
+
data-variant={local.toast.content.variant}
|
|
519
|
+
on:click={handleRootClick}
|
|
473
520
|
>
|
|
474
521
|
{renderProps.renderChildren()}
|
|
475
522
|
</div>
|
|
@@ -477,10 +524,6 @@ export function Toast(props: ToastProps): JSX.Element {
|
|
|
477
524
|
);
|
|
478
525
|
}
|
|
479
526
|
|
|
480
|
-
// ============================================
|
|
481
|
-
// TOAST SUB-COMPONENTS
|
|
482
|
-
// ============================================
|
|
483
|
-
|
|
484
527
|
export interface ToastTitleProps {
|
|
485
528
|
children: JSX.Element;
|
|
486
529
|
class?: string;
|
|
@@ -512,10 +555,18 @@ export interface ToastDescriptionProps {
|
|
|
512
555
|
*/
|
|
513
556
|
export function ToastDescription(props: ToastDescriptionProps): JSX.Element {
|
|
514
557
|
const context = useContext(ToastAriaContext);
|
|
515
|
-
const { ref: _ref, ...ariaDescriptionProps } = (context?.descriptionProps ?? {}) as Record<
|
|
558
|
+
const { ref: _ref, ...ariaDescriptionProps } = (context?.descriptionProps ?? {}) as Record<
|
|
559
|
+
string,
|
|
560
|
+
unknown
|
|
561
|
+
>;
|
|
516
562
|
|
|
517
563
|
return (
|
|
518
|
-
<div
|
|
564
|
+
<div
|
|
565
|
+
data-solidaria-toast-description=""
|
|
566
|
+
{...ariaDescriptionProps}
|
|
567
|
+
class={props.class}
|
|
568
|
+
style={props.style}
|
|
569
|
+
>
|
|
519
570
|
{props.children}
|
|
520
571
|
</div>
|
|
521
572
|
);
|
|
@@ -527,17 +578,19 @@ export interface ToastCloseButtonProps {
|
|
|
527
578
|
children?: JSX.Element;
|
|
528
579
|
class?: string;
|
|
529
580
|
style?: JSX.CSSProperties;
|
|
530
|
-
|
|
581
|
+
"aria-label"?: string;
|
|
531
582
|
}
|
|
532
583
|
|
|
533
584
|
/**
|
|
534
585
|
* ToastCloseButton is a button that closes the toast.
|
|
535
586
|
*/
|
|
536
587
|
export function ToastCloseButton(props: ToastCloseButtonProps): JSX.Element {
|
|
537
|
-
const
|
|
538
|
-
|
|
588
|
+
const contextState = useContext(ToastContext);
|
|
539
589
|
const handleClose = () => {
|
|
540
|
-
|
|
590
|
+
const key = props.toast.key;
|
|
591
|
+
const state = contextState ?? toastStateByKey.get(key);
|
|
592
|
+
state?.close(key);
|
|
593
|
+
state?.remove(key);
|
|
541
594
|
};
|
|
542
595
|
|
|
543
596
|
return (
|
|
@@ -545,18 +598,16 @@ export function ToastCloseButton(props: ToastCloseButtonProps): JSX.Element {
|
|
|
545
598
|
type="button"
|
|
546
599
|
class={props.class}
|
|
547
600
|
style={props.style}
|
|
548
|
-
aria-label={props[
|
|
601
|
+
aria-label={props["aria-label"] ?? "Close"}
|
|
602
|
+
data-solidaria-toast-close-button=""
|
|
603
|
+
on:click={handleClose}
|
|
549
604
|
onClick={handleClose}
|
|
550
605
|
>
|
|
551
|
-
{props.children ??
|
|
606
|
+
{props.children ?? "×"}
|
|
552
607
|
</button>
|
|
553
608
|
);
|
|
554
609
|
}
|
|
555
610
|
|
|
556
|
-
// ============================================
|
|
557
|
-
// DEFAULT TOAST RENDERING
|
|
558
|
-
// ============================================
|
|
559
|
-
|
|
560
611
|
export interface DefaultToastProps {
|
|
561
612
|
toast: QueuedToast<ToastContent>;
|
|
562
613
|
}
|
|
@@ -567,26 +618,33 @@ export interface DefaultToastProps {
|
|
|
567
618
|
*/
|
|
568
619
|
export function DefaultToast(props: DefaultToastProps): JSX.Element {
|
|
569
620
|
const content = () => props.toast.content;
|
|
621
|
+
const title = () => content().children ?? content().title;
|
|
622
|
+
const actionLabel = () => content().actionLabel ?? content().action?.label;
|
|
623
|
+
const actionHandler = () => content().onAction ?? content().action?.onAction;
|
|
624
|
+
const state = useToastContext();
|
|
625
|
+
const handleAction = () => {
|
|
626
|
+
actionHandler()?.();
|
|
627
|
+
if (content().shouldCloseOnAction) {
|
|
628
|
+
state.close(props.toast.key);
|
|
629
|
+
state.remove(props.toast.key);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
570
632
|
|
|
571
633
|
return (
|
|
572
634
|
<Toast toast={props.toast}>
|
|
573
|
-
<div style={{ display:
|
|
635
|
+
<div style={{ display: "flex", "align-items": "flex-start", gap: "12px" }}>
|
|
574
636
|
<div style={{ flex: 1 }}>
|
|
575
|
-
<Show when={
|
|
576
|
-
<ToastTitle style={{
|
|
577
|
-
{
|
|
637
|
+
<Show when={title()}>
|
|
638
|
+
<ToastTitle style={{ "font-weight": "bold", "margin-bottom": "4px" }}>
|
|
639
|
+
{title()}
|
|
578
640
|
</ToastTitle>
|
|
579
641
|
</Show>
|
|
580
642
|
<Show when={content().description}>
|
|
581
643
|
<ToastDescription>{content().description}</ToastDescription>
|
|
582
644
|
</Show>
|
|
583
|
-
<Show when={
|
|
584
|
-
<button
|
|
585
|
-
|
|
586
|
-
style={{ 'margin-top': '8px' }}
|
|
587
|
-
onClick={content().action?.onAction}
|
|
588
|
-
>
|
|
589
|
-
{content().action?.label}
|
|
645
|
+
<Show when={actionLabel()}>
|
|
646
|
+
<button type="button" style={{ "margin-top": "8px" }} onClick={handleAction}>
|
|
647
|
+
{actionLabel()}
|
|
590
648
|
</button>
|
|
591
649
|
</Show>
|
|
592
650
|
</div>
|