@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.
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ export { HoverCard } from "./hover-card.js";
2
+ export type { HoverCardContentProps, HoverCardProps, HoverCardTriggerProps } from "./hover-card.js";
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export { HoverCard } from "./hover-card.js";
@@ -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";
@@ -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
- export { Toast, ToastViewport } from "./toast.js";
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
  }