@dilipod/ui 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/form-field.d.ts +3 -1
- package/dist/components/form-field.d.ts.map +1 -1
- package/dist/components/sidebar.d.ts +8 -0
- package/dist/components/sidebar.d.ts.map +1 -1
- package/dist/components/toast.d.ts +19 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/toaster.d.ts +2 -0
- package/dist/components/toaster.d.ts.map +1 -0
- package/dist/components/use-toast.d.ts +45 -0
- package/dist/components/use-toast.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +263 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +253 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/components/form-field.tsx +13 -8
- package/src/components/sidebar.tsx +19 -1
- package/src/components/toast.tsx +144 -0
- package/src/components/toaster.tsx +40 -0
- package/src/components/use-toast.tsx +187 -0
- package/src/index.ts +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dilipod/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Dilipod Design System - Shared UI components and styles",
|
|
5
5
|
"author": "Dilipod <hello@dilipod.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"@radix-ui/react-dropdown-menu": "^2.1.3",
|
|
68
68
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
|
69
69
|
"@radix-ui/react-slot": "^1.2.3",
|
|
70
|
+
"@radix-ui/react-toast": "^1.2.15",
|
|
70
71
|
"class-variance-authority": "^0.7.1",
|
|
71
72
|
"clsx": "^2.1.1",
|
|
72
73
|
"tailwind-merge": "^3.3.0"
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { cn } from '../lib/utils'
|
|
5
5
|
import { Label } from './label'
|
|
6
|
-
import { Input } from './input'
|
|
7
|
-
import { Textarea } from './textarea'
|
|
8
6
|
|
|
9
7
|
export interface FormFieldProps {
|
|
10
8
|
/** Field label */
|
|
11
9
|
label?: string
|
|
12
10
|
/** Error message to display */
|
|
13
11
|
error?: string
|
|
14
|
-
/** Helper text to display */
|
|
12
|
+
/** Helper text to display below the field */
|
|
15
13
|
helperText?: string
|
|
14
|
+
/** Hint element to display on the right side of the label (e.g., link) */
|
|
15
|
+
hint?: React.ReactNode
|
|
16
16
|
/** Whether the field is required */
|
|
17
17
|
required?: boolean
|
|
18
18
|
/** Field ID for accessibility */
|
|
@@ -24,7 +24,7 @@ export interface FormFieldProps {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(
|
|
27
|
-
({ label, error, helperText, required, id, className, children, ...props }, ref) => {
|
|
27
|
+
({ label, error, helperText, hint, required, id, className, children, ...props }, ref) => {
|
|
28
28
|
const fieldId = id || React.useId()
|
|
29
29
|
const errorId = `${fieldId}-error`
|
|
30
30
|
const helperId = `${fieldId}-helper`
|
|
@@ -48,10 +48,15 @@ const FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(
|
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
50
|
<div ref={ref} className={cn('space-y-2', className)} {...props}>
|
|
51
|
-
{label && (
|
|
52
|
-
<
|
|
53
|
-
{label
|
|
54
|
-
|
|
51
|
+
{(label || hint) && (
|
|
52
|
+
<div className="flex items-center justify-between">
|
|
53
|
+
{label && (
|
|
54
|
+
<Label htmlFor={fieldId} className={required ? 'after:content-["*"] after:ml-0.5 after:text-red-500' : ''}>
|
|
55
|
+
{label}
|
|
56
|
+
</Label>
|
|
57
|
+
)}
|
|
58
|
+
{hint && <div>{hint}</div>}
|
|
59
|
+
</div>
|
|
55
60
|
)}
|
|
56
61
|
{enhancedChildren}
|
|
57
62
|
{error && (
|
|
@@ -29,6 +29,12 @@ export interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
29
29
|
label?: string
|
|
30
30
|
icon?: React.ComponentType<React.SVGProps<SVGSVGElement> & { weight?: 'fill' | 'regular' }>
|
|
31
31
|
}
|
|
32
|
+
/** Optional assistant button configuration */
|
|
33
|
+
assistantButton?: {
|
|
34
|
+
label?: string
|
|
35
|
+
icon?: React.ComponentType<React.SVGProps<SVGSVGElement> & { weight?: 'fill' | 'regular' }>
|
|
36
|
+
onClick?: () => void
|
|
37
|
+
}
|
|
32
38
|
/** Header content (e.g., Logo) */
|
|
33
39
|
header?: React.ReactNode
|
|
34
40
|
/** Custom Link component (e.g., Next.js Link) */
|
|
@@ -92,6 +98,7 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
|
|
|
92
98
|
pathname,
|
|
93
99
|
searchButton,
|
|
94
100
|
helpLink,
|
|
101
|
+
assistantButton,
|
|
95
102
|
header,
|
|
96
103
|
LinkComponent,
|
|
97
104
|
className,
|
|
@@ -159,7 +166,7 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
|
|
|
159
166
|
{children}
|
|
160
167
|
|
|
161
168
|
{/* Bottom Navigation */}
|
|
162
|
-
{(bottomNav.length > 0 || helpLink) && (
|
|
169
|
+
{(bottomNav.length > 0 || helpLink || assistantButton) && (
|
|
163
170
|
<div className="px-3 pb-3 space-y-1 border-t pt-3">
|
|
164
171
|
{bottomNav.map((item) => (
|
|
165
172
|
<SidebarNavItem
|
|
@@ -169,6 +176,17 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
|
|
|
169
176
|
LinkComponent={LinkComponent}
|
|
170
177
|
/>
|
|
171
178
|
))}
|
|
179
|
+
{assistantButton && (
|
|
180
|
+
<button
|
|
181
|
+
onClick={assistantButton.onClick}
|
|
182
|
+
className="flex w-full items-center gap-3 rounded-sm px-3 py-2 text-sm bg-[var(--cyan)]/10 text-[var(--black)] hover:bg-[var(--cyan)]/20 transition-colors font-medium"
|
|
183
|
+
>
|
|
184
|
+
{assistantButton.icon && (
|
|
185
|
+
<assistantButton.icon className="h-4 w-4 text-[var(--cyan)]" />
|
|
186
|
+
)}
|
|
187
|
+
{assistantButton.label}
|
|
188
|
+
</button>
|
|
189
|
+
)}
|
|
172
190
|
{helpLink && (
|
|
173
191
|
<a
|
|
174
192
|
href={helpLink.href}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as ToastPrimitives from '@radix-ui/react-toast'
|
|
5
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
6
|
+
import { X, CheckCircle, WarningCircle, Info } from '@phosphor-icons/react'
|
|
7
|
+
import { cn } from '../lib/utils'
|
|
8
|
+
|
|
9
|
+
const ToastProvider = ToastPrimitives.Provider
|
|
10
|
+
|
|
11
|
+
const ToastViewport = React.forwardRef<
|
|
12
|
+
React.ComponentRef<typeof ToastPrimitives.Viewport>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
|
14
|
+
>(({ className, ...props }, ref) => (
|
|
15
|
+
<ToastPrimitives.Viewport
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn(
|
|
18
|
+
'fixed bottom-0 right-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:max-w-[420px]',
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
))
|
|
24
|
+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
|
25
|
+
|
|
26
|
+
const toastVariants = cva(
|
|
27
|
+
'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-sm border p-4 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-bottom-full',
|
|
28
|
+
{
|
|
29
|
+
variants: {
|
|
30
|
+
variant: {
|
|
31
|
+
default: 'border-gray-200 bg-white text-[var(--black)]',
|
|
32
|
+
success: 'border-emerald-200 bg-emerald-50 text-emerald-900',
|
|
33
|
+
error: 'border-red-200 bg-red-50 text-red-900',
|
|
34
|
+
warning: 'border-amber-200 bg-amber-50 text-amber-900',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: 'default',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const Toast = React.forwardRef<
|
|
44
|
+
React.ComponentRef<typeof ToastPrimitives.Root>,
|
|
45
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
|
46
|
+
VariantProps<typeof toastVariants>
|
|
47
|
+
>(({ className, variant, ...props }, ref) => {
|
|
48
|
+
return (
|
|
49
|
+
<ToastPrimitives.Root
|
|
50
|
+
ref={ref}
|
|
51
|
+
className={cn(toastVariants({ variant }), className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
Toast.displayName = ToastPrimitives.Root.displayName
|
|
57
|
+
|
|
58
|
+
const ToastAction = React.forwardRef<
|
|
59
|
+
React.ComponentRef<typeof ToastPrimitives.Action>,
|
|
60
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
|
61
|
+
>(({ className, ...props }, ref) => (
|
|
62
|
+
<ToastPrimitives.Action
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn(
|
|
65
|
+
'inline-flex h-8 shrink-0 items-center justify-center rounded-sm border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
))
|
|
71
|
+
ToastAction.displayName = ToastPrimitives.Action.displayName
|
|
72
|
+
|
|
73
|
+
const ToastClose = React.forwardRef<
|
|
74
|
+
React.ComponentRef<typeof ToastPrimitives.Close>,
|
|
75
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
|
76
|
+
>(({ className, ...props }, ref) => (
|
|
77
|
+
<ToastPrimitives.Close
|
|
78
|
+
ref={ref}
|
|
79
|
+
className={cn(
|
|
80
|
+
'absolute right-2 top-2 rounded-sm p-1 text-gray-500 opacity-0 transition-opacity hover:text-gray-900 focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100',
|
|
81
|
+
className
|
|
82
|
+
)}
|
|
83
|
+
toast-close=""
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
<X size={16} />
|
|
87
|
+
</ToastPrimitives.Close>
|
|
88
|
+
))
|
|
89
|
+
ToastClose.displayName = ToastPrimitives.Close.displayName
|
|
90
|
+
|
|
91
|
+
const ToastTitle = React.forwardRef<
|
|
92
|
+
React.ComponentRef<typeof ToastPrimitives.Title>,
|
|
93
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
|
94
|
+
>(({ className, ...props }, ref) => (
|
|
95
|
+
<ToastPrimitives.Title
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn('text-sm font-semibold', className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
))
|
|
101
|
+
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
|
102
|
+
|
|
103
|
+
const ToastDescription = React.forwardRef<
|
|
104
|
+
React.ComponentRef<typeof ToastPrimitives.Description>,
|
|
105
|
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
|
106
|
+
>(({ className, ...props }, ref) => (
|
|
107
|
+
<ToastPrimitives.Description
|
|
108
|
+
ref={ref}
|
|
109
|
+
className={cn('text-sm opacity-90', className)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
))
|
|
113
|
+
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
|
114
|
+
|
|
115
|
+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
|
116
|
+
|
|
117
|
+
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
|
118
|
+
|
|
119
|
+
// Toast icon helper
|
|
120
|
+
const ToastIcon = ({ variant }: { variant?: 'default' | 'success' | 'error' | 'warning' }) => {
|
|
121
|
+
switch (variant) {
|
|
122
|
+
case 'success':
|
|
123
|
+
return <CheckCircle size={20} weight="fill" className="text-emerald-600" />
|
|
124
|
+
case 'error':
|
|
125
|
+
return <WarningCircle size={20} weight="fill" className="text-red-600" />
|
|
126
|
+
case 'warning':
|
|
127
|
+
return <WarningCircle size={20} weight="fill" className="text-amber-600" />
|
|
128
|
+
default:
|
|
129
|
+
return <Info size={20} weight="fill" className="text-gray-600" />
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export {
|
|
134
|
+
type ToastProps,
|
|
135
|
+
type ToastActionElement,
|
|
136
|
+
ToastProvider,
|
|
137
|
+
ToastViewport,
|
|
138
|
+
Toast,
|
|
139
|
+
ToastTitle,
|
|
140
|
+
ToastDescription,
|
|
141
|
+
ToastClose,
|
|
142
|
+
ToastAction,
|
|
143
|
+
ToastIcon,
|
|
144
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Toast,
|
|
5
|
+
ToastClose,
|
|
6
|
+
ToastDescription,
|
|
7
|
+
ToastIcon,
|
|
8
|
+
ToastProvider,
|
|
9
|
+
ToastTitle,
|
|
10
|
+
ToastViewport,
|
|
11
|
+
} from './toast'
|
|
12
|
+
import { useToast } from './use-toast'
|
|
13
|
+
|
|
14
|
+
export function Toaster() {
|
|
15
|
+
const { toasts } = useToast()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<ToastProvider>
|
|
19
|
+
{toasts.map(function ({ id, title, description, action, variant, ...props }) {
|
|
20
|
+
const safeVariant = variant ?? undefined
|
|
21
|
+
return (
|
|
22
|
+
<Toast key={id} variant={safeVariant} {...props}>
|
|
23
|
+
<div className="flex gap-3">
|
|
24
|
+
<ToastIcon variant={safeVariant} />
|
|
25
|
+
<div className="grid gap-1">
|
|
26
|
+
{title && <ToastTitle>{title}</ToastTitle>}
|
|
27
|
+
{description && (
|
|
28
|
+
<ToastDescription>{description}</ToastDescription>
|
|
29
|
+
)}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
{action}
|
|
33
|
+
<ToastClose />
|
|
34
|
+
</Toast>
|
|
35
|
+
)
|
|
36
|
+
})}
|
|
37
|
+
<ToastViewport />
|
|
38
|
+
</ToastProvider>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import type { ToastActionElement, ToastProps } from './toast'
|
|
5
|
+
|
|
6
|
+
const TOAST_LIMIT = 5
|
|
7
|
+
const TOAST_REMOVE_DELAY = 5000
|
|
8
|
+
|
|
9
|
+
type ToasterToast = ToastProps & {
|
|
10
|
+
id: string
|
|
11
|
+
title?: React.ReactNode
|
|
12
|
+
description?: React.ReactNode
|
|
13
|
+
action?: ToastActionElement
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const actionTypes = {
|
|
17
|
+
ADD_TOAST: 'ADD_TOAST',
|
|
18
|
+
UPDATE_TOAST: 'UPDATE_TOAST',
|
|
19
|
+
DISMISS_TOAST: 'DISMISS_TOAST',
|
|
20
|
+
REMOVE_TOAST: 'REMOVE_TOAST',
|
|
21
|
+
} as const
|
|
22
|
+
|
|
23
|
+
let count = 0
|
|
24
|
+
|
|
25
|
+
function genId() {
|
|
26
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
|
27
|
+
return count.toString()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type ActionType = typeof actionTypes
|
|
31
|
+
|
|
32
|
+
type Action =
|
|
33
|
+
| {
|
|
34
|
+
type: ActionType['ADD_TOAST']
|
|
35
|
+
toast: ToasterToast
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
type: ActionType['UPDATE_TOAST']
|
|
39
|
+
toast: Partial<ToasterToast>
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
type: ActionType['DISMISS_TOAST']
|
|
43
|
+
toastId?: ToasterToast['id']
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
type: ActionType['REMOVE_TOAST']
|
|
47
|
+
toastId?: ToasterToast['id']
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface State {
|
|
51
|
+
toasts: ToasterToast[]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
|
55
|
+
|
|
56
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
57
|
+
if (toastTimeouts.has(toastId)) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const timeout = setTimeout(() => {
|
|
62
|
+
toastTimeouts.delete(toastId)
|
|
63
|
+
dispatch({
|
|
64
|
+
type: 'REMOVE_TOAST',
|
|
65
|
+
toastId: toastId,
|
|
66
|
+
})
|
|
67
|
+
}, TOAST_REMOVE_DELAY)
|
|
68
|
+
|
|
69
|
+
toastTimeouts.set(toastId, timeout)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const reducer = (state: State, action: Action): State => {
|
|
73
|
+
switch (action.type) {
|
|
74
|
+
case 'ADD_TOAST':
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case 'UPDATE_TOAST':
|
|
81
|
+
return {
|
|
82
|
+
...state,
|
|
83
|
+
toasts: state.toasts.map((t) =>
|
|
84
|
+
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
85
|
+
),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case 'DISMISS_TOAST': {
|
|
89
|
+
const { toastId } = action
|
|
90
|
+
|
|
91
|
+
if (toastId) {
|
|
92
|
+
addToRemoveQueue(toastId)
|
|
93
|
+
} else {
|
|
94
|
+
state.toasts.forEach((toast) => {
|
|
95
|
+
addToRemoveQueue(toast.id)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
...state,
|
|
101
|
+
toasts: state.toasts.map((t) =>
|
|
102
|
+
t.id === toastId || toastId === undefined
|
|
103
|
+
? {
|
|
104
|
+
...t,
|
|
105
|
+
open: false,
|
|
106
|
+
}
|
|
107
|
+
: t
|
|
108
|
+
),
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
case 'REMOVE_TOAST':
|
|
112
|
+
if (action.toastId === undefined) {
|
|
113
|
+
return {
|
|
114
|
+
...state,
|
|
115
|
+
toasts: [],
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
...state,
|
|
120
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const listeners: Array<(state: State) => void> = []
|
|
126
|
+
|
|
127
|
+
let memoryState: State = { toasts: [] }
|
|
128
|
+
|
|
129
|
+
function dispatch(action: Action) {
|
|
130
|
+
memoryState = reducer(memoryState, action)
|
|
131
|
+
listeners.forEach((listener) => {
|
|
132
|
+
listener(memoryState)
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
type Toast = Omit<ToasterToast, 'id'>
|
|
137
|
+
|
|
138
|
+
function toast({ ...props }: Toast) {
|
|
139
|
+
const id = genId()
|
|
140
|
+
|
|
141
|
+
const update = (props: ToasterToast) =>
|
|
142
|
+
dispatch({
|
|
143
|
+
type: 'UPDATE_TOAST',
|
|
144
|
+
toast: { ...props, id },
|
|
145
|
+
})
|
|
146
|
+
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })
|
|
147
|
+
|
|
148
|
+
dispatch({
|
|
149
|
+
type: 'ADD_TOAST',
|
|
150
|
+
toast: {
|
|
151
|
+
...props,
|
|
152
|
+
id,
|
|
153
|
+
open: true,
|
|
154
|
+
onOpenChange: (open) => {
|
|
155
|
+
if (!open) dismiss()
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
id: id,
|
|
162
|
+
dismiss,
|
|
163
|
+
update,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function useToast() {
|
|
168
|
+
const [state, setState] = React.useState<State>(memoryState)
|
|
169
|
+
|
|
170
|
+
React.useEffect(() => {
|
|
171
|
+
listeners.push(setState)
|
|
172
|
+
return () => {
|
|
173
|
+
const index = listeners.indexOf(setState)
|
|
174
|
+
if (index > -1) {
|
|
175
|
+
listeners.splice(index, 1)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}, [state])
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
...state,
|
|
182
|
+
toast,
|
|
183
|
+
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export { useToast, toast }
|
package/src/index.ts
CHANGED
|
@@ -139,6 +139,22 @@ export {
|
|
|
139
139
|
export { Divider } from './components/divider'
|
|
140
140
|
export type { DividerProps } from './components/divider'
|
|
141
141
|
|
|
142
|
+
// Toast Components
|
|
143
|
+
export {
|
|
144
|
+
ToastProvider,
|
|
145
|
+
ToastViewport,
|
|
146
|
+
Toast,
|
|
147
|
+
ToastTitle,
|
|
148
|
+
ToastDescription,
|
|
149
|
+
ToastClose,
|
|
150
|
+
ToastAction,
|
|
151
|
+
ToastIcon,
|
|
152
|
+
} from './components/toast'
|
|
153
|
+
export type { ToastProps, ToastActionElement } from './components/toast'
|
|
154
|
+
|
|
155
|
+
export { Toaster } from './components/toaster'
|
|
156
|
+
export { useToast, toast } from './components/use-toast'
|
|
157
|
+
|
|
142
158
|
// Utilities
|
|
143
159
|
export { cn } from './lib/utils'
|
|
144
160
|
|