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