@zvk/ui 0.1.1 → 0.1.3
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 +4 -3
- package/dist/components/calendar/calendar.d.ts +16 -0
- package/dist/components/calendar/calendar.js +178 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.js +2 -0
- package/dist/components/carousel/carousel.d.ts +51 -0
- package/dist/components/carousel/carousel.js +210 -0
- package/dist/components/carousel/index.d.ts +2 -0
- package/dist/components/carousel/index.js +2 -0
- package/dist/components/date-picker/date-picker.d.ts +16 -0
- package/dist/components/date-picker/date-picker.js +50 -0
- package/dist/components/date-picker/index.d.ts +2 -0
- package/dist/components/date-picker/index.js +2 -0
- package/dist/components/hover-card/hover-card.d.ts +31 -0
- package/dist/components/hover-card/hover-card.js +268 -0
- package/dist/components/hover-card/index.d.ts +2 -0
- package/dist/components/hover-card/index.js +2 -0
- package/dist/components/index.d.ts +10 -2
- package/dist/components/index.js +5 -1
- package/dist/components/popover/popover.d.ts +2 -1
- package/dist/components/popover/popover.js +4 -3
- package/dist/components/toast/index.d.ts +2 -2
- package/dist/components/toast/index.js +2 -1
- package/dist/components/toast/toast.d.ts +26 -0
- package/dist/components/toast/toast.js +101 -1
- package/dist/styles.css +335 -3
- package/package.json +17 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { DismissableLayer } from "../../internal/dismissable-layer/index.js";
|
|
5
|
+
import { useFloatingPosition } from "../../internal/floating/index.js";
|
|
6
|
+
import { Portal } from "../../internal/portal/index.js";
|
|
7
|
+
import { useControllableState } from "../../hooks/use-controllable-state.js";
|
|
8
|
+
import { cn } from "../../utils/cn.js";
|
|
9
|
+
import { composeEventHandlers } from "../../utils/compose-event-handlers.js";
|
|
10
|
+
const defaultContentPositioning = {
|
|
11
|
+
sideOffset: 8,
|
|
12
|
+
collisionPadding: 0
|
|
13
|
+
};
|
|
14
|
+
const HoverCardContext = React.createContext(null);
|
|
15
|
+
function useHoverCardContext(calledBy) {
|
|
16
|
+
const context = React.useContext(HoverCardContext);
|
|
17
|
+
if (context === null) {
|
|
18
|
+
throw new Error(`"${calledBy}" must be used within a <HoverCard />`);
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
22
|
+
function composeRefs(...refs) {
|
|
23
|
+
return (node) => {
|
|
24
|
+
for (const ref of refs) {
|
|
25
|
+
if (typeof ref === "function") {
|
|
26
|
+
ref(node);
|
|
27
|
+
}
|
|
28
|
+
else if (ref !== undefined) {
|
|
29
|
+
ref.current = node;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function joinIds(...ids) {
|
|
35
|
+
const value = ids.filter((id) => id !== undefined && id !== "").join(" ");
|
|
36
|
+
return value.length > 0 ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
function getPlacementParts(placement) {
|
|
39
|
+
const [side, align = "center"] = placement.split("-");
|
|
40
|
+
return { side, align };
|
|
41
|
+
}
|
|
42
|
+
function HoverCardRoot({ children, closeDelay = 300, defaultOpen = false, disabled = false, onOpenChange, open: openProp, openDelay = 700, placement = "bottom" }) {
|
|
43
|
+
const generatedId = React.useId();
|
|
44
|
+
const contentId = `${generatedId}-content`;
|
|
45
|
+
const [open, setOpen] = useControllableState({
|
|
46
|
+
...(openProp !== undefined ? { value: openProp } : {}),
|
|
47
|
+
defaultValue: defaultOpen,
|
|
48
|
+
...(onOpenChange ? { onChange: onOpenChange } : {})
|
|
49
|
+
});
|
|
50
|
+
const [contentPositioning, setContentPositioning] = React.useState(defaultContentPositioning);
|
|
51
|
+
const openTimerRef = React.useRef(null);
|
|
52
|
+
const closeTimerRef = React.useRef(null);
|
|
53
|
+
const focusCloseTimerRef = React.useRef(null);
|
|
54
|
+
const referenceElement = React.useRef(null);
|
|
55
|
+
const contentElement = React.useRef(null);
|
|
56
|
+
const { floatingRef, floatingStyle, placement: resolvedPlacement, referenceRef, updatePosition } = useFloatingPosition({
|
|
57
|
+
open,
|
|
58
|
+
placement,
|
|
59
|
+
offset: contentPositioning.sideOffset,
|
|
60
|
+
collisionPadding: contentPositioning.collisionPadding,
|
|
61
|
+
flip: true,
|
|
62
|
+
shift: true
|
|
63
|
+
});
|
|
64
|
+
const clearOpenTimer = React.useCallback(() => {
|
|
65
|
+
if (openTimerRef.current === null) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
window.clearTimeout(openTimerRef.current);
|
|
69
|
+
openTimerRef.current = null;
|
|
70
|
+
}, []);
|
|
71
|
+
const clearCloseTimer = React.useCallback(() => {
|
|
72
|
+
if (closeTimerRef.current === null) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
window.clearTimeout(closeTimerRef.current);
|
|
76
|
+
closeTimerRef.current = null;
|
|
77
|
+
}, []);
|
|
78
|
+
const clearFocusCloseTimer = React.useCallback(() => {
|
|
79
|
+
if (focusCloseTimerRef.current === null) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
window.clearTimeout(focusCloseTimerRef.current);
|
|
83
|
+
focusCloseTimerRef.current = null;
|
|
84
|
+
}, []);
|
|
85
|
+
const close = React.useCallback(() => {
|
|
86
|
+
clearOpenTimer();
|
|
87
|
+
clearCloseTimer();
|
|
88
|
+
clearFocusCloseTimer();
|
|
89
|
+
setOpen(false);
|
|
90
|
+
}, [clearCloseTimer, clearFocusCloseTimer, clearOpenTimer, setOpen]);
|
|
91
|
+
const openImmediately = React.useCallback(() => {
|
|
92
|
+
if (disabled) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
clearOpenTimer();
|
|
96
|
+
clearCloseTimer();
|
|
97
|
+
clearFocusCloseTimer();
|
|
98
|
+
setOpen(true);
|
|
99
|
+
}, [clearCloseTimer, clearFocusCloseTimer, clearOpenTimer, disabled, setOpen]);
|
|
100
|
+
const openWithDelay = React.useCallback(() => {
|
|
101
|
+
if (disabled) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
clearOpenTimer();
|
|
105
|
+
clearCloseTimer();
|
|
106
|
+
clearFocusCloseTimer();
|
|
107
|
+
if (openDelay > 0) {
|
|
108
|
+
openTimerRef.current = window.setTimeout(() => {
|
|
109
|
+
setOpen(true);
|
|
110
|
+
}, openDelay);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
setOpen(true);
|
|
114
|
+
}, [clearCloseTimer, clearFocusCloseTimer, clearOpenTimer, disabled, openDelay, setOpen]);
|
|
115
|
+
const closeWithDelay = React.useCallback(() => {
|
|
116
|
+
clearOpenTimer();
|
|
117
|
+
clearCloseTimer();
|
|
118
|
+
if (closeDelay > 0) {
|
|
119
|
+
closeTimerRef.current = window.setTimeout(() => {
|
|
120
|
+
setOpen(false);
|
|
121
|
+
}, closeDelay);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setOpen(false);
|
|
125
|
+
}, [clearCloseTimer, clearOpenTimer, closeDelay, setOpen]);
|
|
126
|
+
const isInsideHoverCard = React.useCallback((node) => {
|
|
127
|
+
if (node === null) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return Boolean(referenceElement.current?.contains(node) || contentElement.current?.contains(node));
|
|
131
|
+
}, []);
|
|
132
|
+
const closeAfterFocusLeaves = React.useCallback((nextTarget) => {
|
|
133
|
+
clearFocusCloseTimer();
|
|
134
|
+
if (isInsideHoverCard(nextTarget)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (nextTarget !== null) {
|
|
138
|
+
close();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
focusCloseTimerRef.current = window.setTimeout(() => {
|
|
142
|
+
focusCloseTimerRef.current = null;
|
|
143
|
+
if (!isInsideHoverCard(document.activeElement)) {
|
|
144
|
+
close();
|
|
145
|
+
}
|
|
146
|
+
}, 0);
|
|
147
|
+
}, [clearFocusCloseTimer, close, isInsideHoverCard]);
|
|
148
|
+
const setReferenceRef = React.useCallback((node) => {
|
|
149
|
+
referenceElement.current = node;
|
|
150
|
+
referenceRef(node);
|
|
151
|
+
}, [referenceRef]);
|
|
152
|
+
const setFloatingRef = React.useCallback((node) => {
|
|
153
|
+
contentElement.current = node;
|
|
154
|
+
floatingRef(node);
|
|
155
|
+
}, [floatingRef]);
|
|
156
|
+
React.useEffect(() => {
|
|
157
|
+
if (open) {
|
|
158
|
+
updatePosition();
|
|
159
|
+
}
|
|
160
|
+
}, [open, updatePosition]);
|
|
161
|
+
React.useEffect(() => {
|
|
162
|
+
if (disabled) {
|
|
163
|
+
close();
|
|
164
|
+
}
|
|
165
|
+
}, [close, disabled]);
|
|
166
|
+
React.useEffect(() => () => {
|
|
167
|
+
clearOpenTimer();
|
|
168
|
+
clearCloseTimer();
|
|
169
|
+
clearFocusCloseTimer();
|
|
170
|
+
}, [clearCloseTimer, clearFocusCloseTimer, clearOpenTimer]);
|
|
171
|
+
const contextValue = React.useMemo(() => ({
|
|
172
|
+
cancelClose: clearCloseTimer,
|
|
173
|
+
close,
|
|
174
|
+
closeWithDelay,
|
|
175
|
+
closeAfterFocusLeaves,
|
|
176
|
+
contentElement,
|
|
177
|
+
contentId,
|
|
178
|
+
disabled,
|
|
179
|
+
floatingRef: setFloatingRef,
|
|
180
|
+
floatingStyle,
|
|
181
|
+
isInsideHoverCard,
|
|
182
|
+
open,
|
|
183
|
+
openImmediately,
|
|
184
|
+
openWithDelay,
|
|
185
|
+
referenceElement,
|
|
186
|
+
referenceRef: setReferenceRef,
|
|
187
|
+
resolvedPlacement,
|
|
188
|
+
setContentPositioning
|
|
189
|
+
}), [
|
|
190
|
+
clearCloseTimer,
|
|
191
|
+
close,
|
|
192
|
+
closeWithDelay,
|
|
193
|
+
closeAfterFocusLeaves,
|
|
194
|
+
contentId,
|
|
195
|
+
disabled,
|
|
196
|
+
floatingStyle,
|
|
197
|
+
isInsideHoverCard,
|
|
198
|
+
open,
|
|
199
|
+
openImmediately,
|
|
200
|
+
openWithDelay,
|
|
201
|
+
resolvedPlacement,
|
|
202
|
+
setFloatingRef,
|
|
203
|
+
setReferenceRef
|
|
204
|
+
]);
|
|
205
|
+
return (_jsx(HoverCardContext.Provider, { value: contextValue, children: children }));
|
|
206
|
+
}
|
|
207
|
+
function HoverCardTrigger({ children }) {
|
|
208
|
+
const context = useHoverCardContext("HoverCard.Trigger");
|
|
209
|
+
if (!React.isValidElement(children) || context.disabled) {
|
|
210
|
+
return children;
|
|
211
|
+
}
|
|
212
|
+
const trigger = children;
|
|
213
|
+
const childProps = trigger.props;
|
|
214
|
+
const childClassName = typeof childProps.className === "string" ? childProps.className : undefined;
|
|
215
|
+
const childAriaControls = childProps["aria-controls"];
|
|
216
|
+
const childAriaDescribedBy = childProps["aria-describedby"];
|
|
217
|
+
return React.cloneElement(trigger, {
|
|
218
|
+
...childProps,
|
|
219
|
+
ref: composeRefs(childProps.ref, context.referenceRef),
|
|
220
|
+
"aria-controls": joinIds(typeof childAriaControls === "string" ? childAriaControls : undefined, context.contentId),
|
|
221
|
+
"aria-describedby": joinIds(typeof childAriaDescribedBy === "string" ? childAriaDescribedBy : undefined, context.open ? context.contentId : undefined),
|
|
222
|
+
"aria-expanded": context.open,
|
|
223
|
+
"aria-haspopup": "dialog",
|
|
224
|
+
className: cn("liano-hover-card__trigger", childClassName),
|
|
225
|
+
"data-state": context.open ? "open" : "closed",
|
|
226
|
+
onBlur: composeEventHandlers(childProps.onBlur, (event) => {
|
|
227
|
+
context.closeAfterFocusLeaves(event.relatedTarget);
|
|
228
|
+
}),
|
|
229
|
+
onFocus: composeEventHandlers(childProps.onFocus, context.openImmediately),
|
|
230
|
+
onMouseEnter: composeEventHandlers(childProps.onMouseEnter, context.openWithDelay),
|
|
231
|
+
onMouseLeave: composeEventHandlers(childProps.onMouseLeave, context.closeWithDelay),
|
|
232
|
+
onPointerEnter: composeEventHandlers(childProps.onPointerEnter, context.openWithDelay),
|
|
233
|
+
onPointerLeave: composeEventHandlers(childProps.onPointerLeave, context.closeWithDelay)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function HoverCardContent({ children, className, collisionPadding = defaultContentPositioning.collisionPadding, container, forceMount = false, id, onBlur, onFocus, onMouseEnter, onMouseLeave, onPointerEnter, onPointerLeave, ref, sideOffset = defaultContentPositioning.sideOffset, style, ...props }) {
|
|
237
|
+
const context = useHoverCardContext("HoverCard.Content");
|
|
238
|
+
const contentId = id ?? context.contentId;
|
|
239
|
+
const placementParts = getPlacementParts(context.resolvedPlacement);
|
|
240
|
+
const { setContentPositioning } = context;
|
|
241
|
+
React.useEffect(() => {
|
|
242
|
+
setContentPositioning({
|
|
243
|
+
sideOffset,
|
|
244
|
+
collisionPadding
|
|
245
|
+
});
|
|
246
|
+
return () => setContentPositioning(defaultContentPositioning);
|
|
247
|
+
}, [collisionPadding, setContentPositioning, sideOffset]);
|
|
248
|
+
if (!context.open && !forceMount) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const content = (_jsx("div", { ...props, ref: composeRefs(ref, context.floatingRef), id: contentId, role: "dialog", className: cn("liano-hover-card__content", className), "data-align": placementParts.align, "data-side": placementParts.side, "data-state": context.open ? "open" : "closed", hidden: context.open ? undefined : true, onBlur: composeEventHandlers(onBlur, (event) => {
|
|
252
|
+
context.closeAfterFocusLeaves(event.relatedTarget);
|
|
253
|
+
}), onFocus: composeEventHandlers(onFocus, context.openImmediately), onMouseEnter: composeEventHandlers(onMouseEnter, context.cancelClose), onMouseLeave: composeEventHandlers(onMouseLeave, context.closeWithDelay), onPointerEnter: composeEventHandlers(onPointerEnter, context.cancelClose), onPointerLeave: composeEventHandlers(onPointerLeave, context.closeWithDelay), style: { ...context.floatingStyle, ...style }, children: children }));
|
|
254
|
+
if (!context.open) {
|
|
255
|
+
return _jsx(Portal, { ...(container === undefined ? {} : { container }), children: content });
|
|
256
|
+
}
|
|
257
|
+
return (_jsx(Portal, { ...(container === undefined ? {} : { container }), children: _jsx(DismissableLayer, { open: true, modal: false, onDismiss: context.close, onPointerDownOutside: (event) => {
|
|
258
|
+
const pointerEvent = event.detail;
|
|
259
|
+
const target = pointerEvent?.target;
|
|
260
|
+
if (context.referenceElement.current?.contains(target)) {
|
|
261
|
+
event.preventDefault();
|
|
262
|
+
}
|
|
263
|
+
}, children: content }) }));
|
|
264
|
+
}
|
|
265
|
+
export const HoverCard = Object.assign(HoverCardRoot, {
|
|
266
|
+
Trigger: HoverCardTrigger,
|
|
267
|
+
Content: HoverCardContent
|
|
268
|
+
});
|
|
@@ -12,6 +12,10 @@ export { Button } from "./button/index.js";
|
|
|
12
12
|
export type { ButtonProps, ButtonSize, ButtonVariant } from "./button/index.js";
|
|
13
13
|
export { Card } from "./card/index.js";
|
|
14
14
|
export type { CardDescriptionProps, CardPadding, CardProps, CardSlotProps, CardTitleProps, CardVariant } from "./card/index.js";
|
|
15
|
+
export { Calendar } from "./calendar/index.js";
|
|
16
|
+
export type { CalendarProps } from "./calendar/index.js";
|
|
17
|
+
export { Carousel } from "./carousel/index.js";
|
|
18
|
+
export type { CarouselApi, CarouselButtonProps, CarouselOrientation, CarouselPageProps, CarouselProps, CarouselSlideProps, CarouselTrackProps, CarouselViewportProps } from "./carousel/index.js";
|
|
15
19
|
export { Checkbox } from "./checkbox/index.js";
|
|
16
20
|
export type { CheckboxProps, CheckboxSize } from "./checkbox/index.js";
|
|
17
21
|
export { CodeBlock } from "./code-block/index.js";
|
|
@@ -20,6 +24,8 @@ export { Collapsible } from "./collapsible/index.js";
|
|
|
20
24
|
export type { CollapsibleContentProps, CollapsibleProps, CollapsibleTriggerProps } from "./collapsible/index.js";
|
|
21
25
|
export { CopyButton, CopyableText } from "./copy-button/index.js";
|
|
22
26
|
export type { CopyButtonProps, CopyButtonSize, CopyStatus, CopyableTextProps } from "./copy-button/index.js";
|
|
27
|
+
export { DatePicker } from "./date-picker/index.js";
|
|
28
|
+
export type { DatePickerProps } from "./date-picker/index.js";
|
|
23
29
|
export { Dialog } from "./dialog/index.js";
|
|
24
30
|
export type { DialogCloseProps, DialogContentProps, DialogDescriptionProps, DialogFooterProps, DialogHeaderProps, DialogOverlayProps, DialogProps, DialogTitleProps, DialogTriggerProps } from "./dialog/index.js";
|
|
25
31
|
export { DropdownMenu } from "./dropdown-menu/index.js";
|
|
@@ -48,6 +54,8 @@ export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, For
|
|
|
48
54
|
export type { FormControlProps, FormFieldProps, FormItemProps, FormLabelProps, FormProps, FormTextProps } from "./form/index.js";
|
|
49
55
|
export { FileUploadInput } from "./file-upload-input/index.js";
|
|
50
56
|
export type { FileUploadInputProps, FileUploadInputSize } from "./file-upload-input/index.js";
|
|
57
|
+
export { HoverCard } from "./hover-card/index.js";
|
|
58
|
+
export type { HoverCardContentProps, HoverCardProps, HoverCardTriggerProps } from "./hover-card/index.js";
|
|
51
59
|
export { IconButton } from "./icon-button/index.js";
|
|
52
60
|
export type { IconButtonProps } from "./icon-button/index.js";
|
|
53
61
|
export { Input } from "./input/index.js";
|
|
@@ -90,8 +98,8 @@ export { TabsWithSidebar } from "./tabs-with-sidebar/index.js";
|
|
|
90
98
|
export type { TabsWithSidebarPanelProps, TabsWithSidebarProps, TabsWithSidebarSidebarProps, TabsWithSidebarWidth } from "./tabs-with-sidebar/index.js";
|
|
91
99
|
export { Textarea } from "./textarea/index.js";
|
|
92
100
|
export type { TextareaProps, TextareaSize } from "./textarea/index.js";
|
|
93
|
-
export { Toast, ToastViewport } from "./toast/index.js";
|
|
94
|
-
export type { ToastActionProps, ToastCloseProps, ToastPlacement, ToastProps, ToastTextProps, ToastTone, ToastViewportProps } from "./toast/index.js";
|
|
101
|
+
export { createToastController, Toast, ToastProvider, ToastViewport, useToast } from "./toast/index.js";
|
|
102
|
+
export type { ToastActionProps, ToastCloseProps, ToastController, ToastInput, ToastOptions, ToastPlacement, ToastProviderProps, ToastProps, ToastTextProps, ToastTone, ToastViewportProps } from "./toast/index.js";
|
|
95
103
|
export { Tooltip } from "./tooltip/index.js";
|
|
96
104
|
export type { TooltipProps } from "./tooltip/index.js";
|
|
97
105
|
export { Toggle } from "./toggle/index.js";
|
package/dist/components/index.js
CHANGED
|
@@ -5,10 +5,13 @@ export { Badge } from "./badge/index.js";
|
|
|
5
5
|
export { Breadcrumbs } from "./breadcrumbs/index.js";
|
|
6
6
|
export { Button } from "./button/index.js";
|
|
7
7
|
export { Card } from "./card/index.js";
|
|
8
|
+
export { Calendar } from "./calendar/index.js";
|
|
9
|
+
export { Carousel } from "./carousel/index.js";
|
|
8
10
|
export { Checkbox } from "./checkbox/index.js";
|
|
9
11
|
export { CodeBlock } from "./code-block/index.js";
|
|
10
12
|
export { Collapsible } from "./collapsible/index.js";
|
|
11
13
|
export { CopyButton, CopyableText } from "./copy-button/index.js";
|
|
14
|
+
export { DatePicker } from "./date-picker/index.js";
|
|
12
15
|
export { Dialog } from "./dialog/index.js";
|
|
13
16
|
export { DropdownMenu } from "./dropdown-menu/index.js";
|
|
14
17
|
export { AlertDialog } from "./alert-dialog/index.js";
|
|
@@ -23,6 +26,7 @@ export { ErrorBoundary, ErrorFallback } from "./error-boundary/index.js";
|
|
|
23
26
|
export { Field } from "./field/index.js";
|
|
24
27
|
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./form/index.js";
|
|
25
28
|
export { FileUploadInput } from "./file-upload-input/index.js";
|
|
29
|
+
export { HoverCard } from "./hover-card/index.js";
|
|
26
30
|
export { IconButton } from "./icon-button/index.js";
|
|
27
31
|
export { Input } from "./input/index.js";
|
|
28
32
|
export { Label } from "./label/index.js";
|
|
@@ -44,7 +48,7 @@ export { Table } from "./table/index.js";
|
|
|
44
48
|
export { Tabs } from "./tabs/index.js";
|
|
45
49
|
export { TabsWithSidebar } from "./tabs-with-sidebar/index.js";
|
|
46
50
|
export { Textarea } from "./textarea/index.js";
|
|
47
|
-
export { Toast, ToastViewport } from "./toast/index.js";
|
|
51
|
+
export { createToastController, Toast, ToastProvider, ToastViewport, useToast } from "./toast/index.js";
|
|
48
52
|
export { Tooltip } from "./tooltip/index.js";
|
|
49
53
|
export { Toggle } from "./toggle/index.js";
|
|
50
54
|
export { ToggleGroup } from "./toggle-group/index.js";
|
|
@@ -13,6 +13,7 @@ export interface PopoverTriggerProps extends React.ButtonHTMLAttributes<HTMLButt
|
|
|
13
13
|
ref?: React.Ref<HTMLButtonElement>;
|
|
14
14
|
}
|
|
15
15
|
export interface PopoverContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
placement?: FloatingPlacement;
|
|
16
17
|
sideOffset?: number;
|
|
17
18
|
collisionPadding?: number;
|
|
18
19
|
matchTriggerWidth?: boolean;
|
|
@@ -24,7 +25,7 @@ export interface PopoverContentProps extends React.HTMLAttributes<HTMLDivElement
|
|
|
24
25
|
}
|
|
25
26
|
declare function PopoverRoot({ children, className, defaultOpen, modal, onOpenChange, open, placement, ref, ...props }: PopoverProps): React.JSX.Element;
|
|
26
27
|
declare function PopoverTrigger({ className, onClick, ref, type, ...props }: PopoverTriggerProps): React.JSX.Element;
|
|
27
|
-
declare function PopoverContent({ className, container, forceMount, id, disableEscapeKeyDown, disableOutsidePointerDown, ref, sideOffset, collisionPadding, matchTriggerWidth, style, ...props }: PopoverContentProps): React.JSX.Element | null;
|
|
28
|
+
declare function PopoverContent({ className, container, forceMount, id, disableEscapeKeyDown, disableOutsidePointerDown, ref, placement, sideOffset, collisionPadding, matchTriggerWidth, style, ...props }: PopoverContentProps): React.JSX.Element | null;
|
|
28
29
|
export declare const Popover: typeof PopoverRoot & {
|
|
29
30
|
Trigger: typeof PopoverTrigger;
|
|
30
31
|
Content: typeof PopoverContent;
|
|
@@ -68,7 +68,7 @@ function PopoverRoot({ children, className, defaultOpen = false, modal = false,
|
|
|
68
68
|
const referenceElement = React.useRef(null);
|
|
69
69
|
const { floatingStyle, floatingRef, referenceRef, placement: resolvedPlacement, updatePosition } = useFloatingPosition({
|
|
70
70
|
open: isOpen,
|
|
71
|
-
placement,
|
|
71
|
+
placement: contentPositioning.placement ?? placement,
|
|
72
72
|
offset: contentPositioning.sideOffset,
|
|
73
73
|
collisionPadding: contentPositioning.collisionPadding,
|
|
74
74
|
matchReferenceWidth: contentPositioning.matchTriggerWidth
|
|
@@ -108,7 +108,7 @@ function PopoverTrigger({ className, onClick, ref, type = "button", ...props })
|
|
|
108
108
|
}, [context.referenceRef, ref]);
|
|
109
109
|
return (_jsx("button", { ...props, ref: handleRef, type: type, className: cn("liano-popover__trigger", className), "aria-expanded": context.open, "aria-controls": context.contentId, "aria-haspopup": "dialog", "data-state": context.open ? "open" : "closed", onClick: composeEventHandlers(onClick, handleClick) }));
|
|
110
110
|
}
|
|
111
|
-
function PopoverContent({ className, container, forceMount = false, id, disableEscapeKeyDown = false, disableOutsidePointerDown = false, ref, sideOffset = 0, collisionPadding = 0, matchTriggerWidth = false, style, ...props }) {
|
|
111
|
+
function PopoverContent({ className, container, forceMount = false, id, disableEscapeKeyDown = false, disableOutsidePointerDown = false, ref, placement, sideOffset = 0, collisionPadding = 0, matchTriggerWidth = false, style, ...props }) {
|
|
112
112
|
const context = usePopoverContext("Popover.Content");
|
|
113
113
|
const { defaultContentId, setContentId } = context;
|
|
114
114
|
const transformOrigin = getTransformOrigin(context.floatingPlacement);
|
|
@@ -141,6 +141,7 @@ function PopoverContent({ className, container, forceMount = false, id, disableE
|
|
|
141
141
|
}, [context.referenceElement]);
|
|
142
142
|
React.useEffect(() => {
|
|
143
143
|
context.updateContentPositioning({
|
|
144
|
+
...(placement === undefined ? {} : { placement }),
|
|
144
145
|
sideOffset,
|
|
145
146
|
collisionPadding,
|
|
146
147
|
matchTriggerWidth
|
|
@@ -148,7 +149,7 @@ function PopoverContent({ className, container, forceMount = false, id, disableE
|
|
|
148
149
|
return () => {
|
|
149
150
|
context.updateContentPositioning(defaultPositioning);
|
|
150
151
|
};
|
|
151
|
-
}, [context.updateContentPositioning, collisionPadding, matchTriggerWidth, sideOffset]);
|
|
152
|
+
}, [context.updateContentPositioning, collisionPadding, matchTriggerWidth, placement, sideOffset]);
|
|
152
153
|
if (!forceMount && !context.open) {
|
|
153
154
|
return null;
|
|
154
155
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Toast, ToastViewport } from "./toast.js";
|
|
2
|
-
export type { ToastActionProps, ToastCloseProps, ToastPlacement, ToastProps, ToastTextProps, ToastTone, ToastViewportProps } from "./toast.js";
|
|
1
|
+
export { createToastController, Toast, ToastProvider, ToastViewport, useToast } from "./toast.js";
|
|
2
|
+
export type { ToastActionProps, ToastCloseProps, ToastController, ToastInput, ToastOptions, ToastPlacement, ToastProviderProps, ToastProps, ToastTextProps, ToastTone, ToastViewportProps } from "./toast.js";
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
export { createToastController, Toast, ToastProvider, ToastViewport, useToast } from "./toast.js";
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
export type ToastTone = "neutral" | "success" | "warning" | "destructive" | "info";
|
|
3
3
|
export type ToastPlacement = "top-right" | "top-left" | "bottom-right" | "bottom-left";
|
|
4
|
+
export type ToastInput = string | {
|
|
5
|
+
title?: React.ReactNode;
|
|
6
|
+
description?: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export type ToastOptions = {
|
|
9
|
+
id?: string;
|
|
10
|
+
description?: React.ReactNode;
|
|
11
|
+
duration?: number;
|
|
12
|
+
action?: React.ReactNode;
|
|
13
|
+
cancel?: React.ReactNode;
|
|
14
|
+
};
|
|
15
|
+
export type ToastController = {
|
|
16
|
+
(input: ToastInput, options?: ToastOptions): string;
|
|
17
|
+
success: (input: ToastInput, options?: ToastOptions) => string;
|
|
18
|
+
error: (input: ToastInput, options?: ToastOptions) => string;
|
|
19
|
+
warning: (input: ToastInput, options?: ToastOptions) => string;
|
|
20
|
+
info: (input: ToastInput, options?: ToastOptions) => string;
|
|
21
|
+
dismiss: (id?: string) => void;
|
|
22
|
+
};
|
|
4
23
|
export interface ToastViewportProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
24
|
placement?: ToastPlacement;
|
|
6
25
|
ref?: React.Ref<HTMLDivElement>;
|
|
@@ -18,7 +37,14 @@ export interface ToastActionProps extends React.ButtonHTMLAttributes<HTMLButtonE
|
|
|
18
37
|
export interface ToastCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
19
38
|
ref?: React.Ref<HTMLButtonElement>;
|
|
20
39
|
}
|
|
40
|
+
export interface ToastProviderProps extends Omit<ToastViewportProps, "children"> {
|
|
41
|
+
children?: React.ReactNode;
|
|
42
|
+
controller?: ToastController;
|
|
43
|
+
}
|
|
21
44
|
export declare function ToastViewport({ className, placement, ref, ...props }: ToastViewportProps): React.ReactElement;
|
|
45
|
+
export declare function createToastController(): ToastController;
|
|
46
|
+
export declare function useToast(): ToastController;
|
|
47
|
+
export declare function ToastProvider({ children, controller, placement, ref, ...viewportProps }: ToastProviderProps): React.ReactElement;
|
|
22
48
|
declare function ToastRoot({ className, ref, role, tone, ...props }: ToastProps): React.JSX.Element;
|
|
23
49
|
declare function ToastTitle({ className, ref, ...props }: ToastTextProps): React.JSX.Element;
|
|
24
50
|
declare function ToastDescription({ className, ref, ...props }: ToastTextProps): React.JSX.Element;
|
|
@@ -1,9 +1,109 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../../utils/cn.js";
|
|
4
|
+
const DEFAULT_TOAST_DURATION = 5000;
|
|
5
|
+
const toastControllerStores = new WeakMap();
|
|
6
|
+
const ToastControllerContext = React.createContext(null);
|
|
7
|
+
const emptyToastRecords = [];
|
|
8
|
+
const emptyToastStore = {
|
|
9
|
+
getSnapshot: () => emptyToastRecords,
|
|
10
|
+
subscribe: () => () => undefined
|
|
11
|
+
};
|
|
12
|
+
let toastIdCounter = 0;
|
|
13
|
+
let toastVersionCounter = 0;
|
|
14
|
+
function createToastId() {
|
|
15
|
+
toastIdCounter += 1;
|
|
16
|
+
return `toast-${toastIdCounter}`;
|
|
17
|
+
}
|
|
18
|
+
function resolveToastContent(input, options) {
|
|
19
|
+
if (typeof input === "string") {
|
|
20
|
+
return {
|
|
21
|
+
title: input,
|
|
22
|
+
description: options.description
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
title: input.title,
|
|
27
|
+
description: options.description ?? input.description
|
|
28
|
+
};
|
|
29
|
+
}
|
|
4
30
|
export function ToastViewport({ className, placement = "top-right", ref, ...props }) {
|
|
5
31
|
return (_jsx("div", { ...props, ref: ref, "aria-label": props["aria-label"] ?? "Notifications", className: cn("liano-toast-viewport", className), "data-placement": placement, role: "region" }));
|
|
6
32
|
}
|
|
33
|
+
export function createToastController() {
|
|
34
|
+
const listeners = new Set();
|
|
35
|
+
let records = [];
|
|
36
|
+
function notify() {
|
|
37
|
+
listeners.forEach((listener) => listener());
|
|
38
|
+
}
|
|
39
|
+
function show(tone, input, options = {}) {
|
|
40
|
+
const id = options.id ?? createToastId();
|
|
41
|
+
const { title, description } = resolveToastContent(input, options);
|
|
42
|
+
toastVersionCounter += 1;
|
|
43
|
+
const toast = {
|
|
44
|
+
id,
|
|
45
|
+
tone,
|
|
46
|
+
duration: options.duration ?? DEFAULT_TOAST_DURATION,
|
|
47
|
+
version: toastVersionCounter,
|
|
48
|
+
...(title !== undefined ? { title } : {}),
|
|
49
|
+
...(description !== undefined ? { description } : {}),
|
|
50
|
+
...(options.action !== undefined ? { action: options.action } : {}),
|
|
51
|
+
...(options.cancel !== undefined ? { cancel: options.cancel } : {})
|
|
52
|
+
};
|
|
53
|
+
records = [...records.filter((record) => record.id !== id), toast];
|
|
54
|
+
notify();
|
|
55
|
+
return id;
|
|
56
|
+
}
|
|
57
|
+
const controller = ((input, options) => show("neutral", input, options));
|
|
58
|
+
controller.success = (input, options) => show("success", input, options);
|
|
59
|
+
controller.error = (input, options) => show("destructive", input, options);
|
|
60
|
+
controller.warning = (input, options) => show("warning", input, options);
|
|
61
|
+
controller.info = (input, options) => show("info", input, options);
|
|
62
|
+
controller.dismiss = (id) => {
|
|
63
|
+
records = id ? records.filter((toast) => toast.id !== id) : [];
|
|
64
|
+
notify();
|
|
65
|
+
};
|
|
66
|
+
toastControllerStores.set(controller, {
|
|
67
|
+
getSnapshot() {
|
|
68
|
+
return records;
|
|
69
|
+
},
|
|
70
|
+
subscribe(listener) {
|
|
71
|
+
listeners.add(listener);
|
|
72
|
+
return () => {
|
|
73
|
+
listeners.delete(listener);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return controller;
|
|
78
|
+
}
|
|
79
|
+
export function useToast() {
|
|
80
|
+
const controller = React.useContext(ToastControllerContext);
|
|
81
|
+
if (!controller) {
|
|
82
|
+
throw new Error("useToast must be used within a ToastProvider.");
|
|
83
|
+
}
|
|
84
|
+
return controller;
|
|
85
|
+
}
|
|
86
|
+
export function ToastProvider({ children, controller, placement = "top-right", ref, ...viewportProps }) {
|
|
87
|
+
const [localController] = React.useState(() => createToastController());
|
|
88
|
+
const activeController = controller ?? localController;
|
|
89
|
+
const store = toastControllerStores.get(activeController) ?? emptyToastStore;
|
|
90
|
+
const toasts = React.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
91
|
+
const dismissToast = React.useCallback((id) => {
|
|
92
|
+
activeController.dismiss(id);
|
|
93
|
+
}, [activeController]);
|
|
94
|
+
const viewportRefProps = ref ? { ref } : {};
|
|
95
|
+
return (_jsxs(ToastControllerContext.Provider, { value: activeController, children: [children, _jsx(ToastViewport, { ...viewportProps, ...viewportRefProps, placement: placement, children: toasts.map((toast) => (_jsx(ToastProviderItem, { toast: toast, onDismiss: dismissToast }, toast.id))) })] }));
|
|
96
|
+
}
|
|
97
|
+
function ToastProviderItem({ onDismiss, toast }) {
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
if (toast.duration === Infinity) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const timeout = window.setTimeout(() => onDismiss(toast.id), toast.duration);
|
|
103
|
+
return () => window.clearTimeout(timeout);
|
|
104
|
+
}, [onDismiss, toast.duration, toast.id, toast.version]);
|
|
105
|
+
return (_jsxs(Toast, { tone: toast.tone, children: [(toast.title || toast.description) && (_jsxs("div", { className: "liano-toast__content", children: [toast.title && _jsx(Toast.Title, { children: toast.title }), toast.description && _jsx(Toast.Description, { children: toast.description })] })), (toast.action || toast.cancel) && (_jsxs("div", { className: "liano-toast__controls", children: [toast.action, toast.cancel] })), _jsx(Toast.Close, { "aria-label": "Dismiss notification", onClick: () => onDismiss(toast.id) })] }));
|
|
106
|
+
}
|
|
7
107
|
function ToastRoot({ className, ref, role, tone = "neutral", ...props }) {
|
|
8
108
|
return (_jsx("div", { ...props, ref: ref, className: cn("liano-toast", className), "data-tone": tone, role: role ?? (tone === "destructive" ? "alert" : "status") }));
|
|
9
109
|
}
|