@xyhp915/slack-base-ui 0.0.4 → 0.0.6
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 +58 -0
- package/libs/Dialog.d.ts +1 -1
- package/libs/Dialog.d.ts.map +1 -1
- package/libs/Toast.d.ts +16 -0
- package/libs/Toast.d.ts.map +1 -1
- package/libs/index.d.ts +1 -1
- package/libs/index.d.ts.map +1 -1
- package/libs/index.js +2948 -2870
- package/package.json +1 -1
- package/src/components/Dialog.tsx +63 -60
- package/src/components/Toast.tsx +136 -26
- package/src/components/index.ts +1 -1
- package/src/index.css +22 -0
- package/src/pages/ComponentShowcase.tsx +111 -51
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ import React, {
|
|
|
10
10
|
import { X } from 'lucide-react'
|
|
11
11
|
import { Button } from './Button'
|
|
12
12
|
|
|
13
|
-
export type DialogSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
13
|
+
export type DialogSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
|
|
14
14
|
|
|
15
15
|
export interface DialogProps {
|
|
16
16
|
open?: boolean;
|
|
@@ -29,7 +29,7 @@ export const Dialog = ({
|
|
|
29
29
|
title,
|
|
30
30
|
description,
|
|
31
31
|
children,
|
|
32
|
-
size = '
|
|
32
|
+
size = 'xl',
|
|
33
33
|
showCloseButton = true,
|
|
34
34
|
className,
|
|
35
35
|
}: DialogProps) => {
|
|
@@ -38,6 +38,9 @@ export const Dialog = ({
|
|
|
38
38
|
md: 'max-w-md',
|
|
39
39
|
lg: 'max-w-lg',
|
|
40
40
|
xl: 'max-w-xl',
|
|
41
|
+
'2xl': 'max-w-2xl',
|
|
42
|
+
'3xl': 'max-w-3xl',
|
|
43
|
+
'4xl': 'max-w-4xl',
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
return (
|
|
@@ -242,27 +245,27 @@ export const DialogProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
|
|
242
245
|
}, [])
|
|
243
246
|
|
|
244
247
|
const value = useMemo<UseDialogReturn>(
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
() => ({ show, confirm, alert }),
|
|
249
|
+
[show, confirm, alert],
|
|
247
250
|
)
|
|
248
251
|
|
|
249
252
|
return (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
253
|
+
<DialogImperativeContext.Provider value={value}>
|
|
254
|
+
{children}
|
|
255
|
+
{dialogs.map(dialog => (
|
|
256
|
+
<ImperativeDialogItem
|
|
257
|
+
key={dialog.id}
|
|
258
|
+
dialog={dialog}
|
|
259
|
+
onRemove={() => removeDialog(dialog.id)}
|
|
260
|
+
/>
|
|
261
|
+
))}
|
|
262
|
+
</DialogImperativeContext.Provider>
|
|
260
263
|
)
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
DialogProvider.displayName = 'DialogProvider'
|
|
264
267
|
|
|
265
|
-
function ImperativeDialogItem({
|
|
268
|
+
function ImperativeDialogItem ({
|
|
266
269
|
dialog,
|
|
267
270
|
onRemove,
|
|
268
271
|
}: {
|
|
@@ -272,13 +275,13 @@ function ImperativeDialogItem({
|
|
|
272
275
|
const [open, setOpen] = useState(true)
|
|
273
276
|
|
|
274
277
|
const handleClose = useCallback(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
(result: boolean) => {
|
|
279
|
+
setOpen(false)
|
|
280
|
+
dialog.resolve(result)
|
|
281
|
+
// Allow close animation to finish before unmounting
|
|
282
|
+
setTimeout(onRemove, 300)
|
|
283
|
+
},
|
|
284
|
+
[dialog, onRemove],
|
|
282
285
|
)
|
|
283
286
|
|
|
284
287
|
const commonProps = {
|
|
@@ -291,55 +294,55 @@ function ImperativeDialogItem({
|
|
|
291
294
|
|
|
292
295
|
if (dialog.type === 'show') {
|
|
293
296
|
return (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
297
|
+
<Dialog
|
|
298
|
+
{...commonProps}
|
|
299
|
+
showCloseButton={dialog.showCloseButton ?? true}
|
|
300
|
+
onOpenChange={o => !o && handleClose(false)}
|
|
301
|
+
>
|
|
302
|
+
{dialog.content}
|
|
303
|
+
</Dialog>
|
|
301
304
|
)
|
|
302
305
|
}
|
|
303
306
|
|
|
304
307
|
if (dialog.type === 'confirm') {
|
|
305
308
|
return (
|
|
309
|
+
<Dialog
|
|
310
|
+
{...commonProps}
|
|
311
|
+
size={dialog.size ?? 'sm'}
|
|
312
|
+
showCloseButton={false}
|
|
313
|
+
onOpenChange={o => !o && handleClose(false)}
|
|
314
|
+
>
|
|
315
|
+
{dialog.content}
|
|
316
|
+
<DialogFooter>
|
|
317
|
+
<Button variant="secondary" onClick={() => handleClose(false)}>
|
|
318
|
+
{dialog.cancelLabel ?? 'Cancel'}
|
|
319
|
+
</Button>
|
|
320
|
+
<Button
|
|
321
|
+
variant={dialog.confirmVariant ?? 'primary'}
|
|
322
|
+
onClick={() => handleClose(true)}
|
|
323
|
+
>
|
|
324
|
+
{dialog.confirmLabel ?? 'Confirm'}
|
|
325
|
+
</Button>
|
|
326
|
+
</DialogFooter>
|
|
327
|
+
</Dialog>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// alert
|
|
332
|
+
return (
|
|
306
333
|
<Dialog
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
334
|
+
{...commonProps}
|
|
335
|
+
size={dialog.size ?? 'sm'}
|
|
336
|
+
showCloseButton={false}
|
|
337
|
+
onOpenChange={o => !o && handleClose(true)}
|
|
311
338
|
>
|
|
312
339
|
{dialog.content}
|
|
313
340
|
<DialogFooter>
|
|
314
|
-
<Button variant="
|
|
315
|
-
{dialog.
|
|
316
|
-
</Button>
|
|
317
|
-
<Button
|
|
318
|
-
variant={dialog.confirmVariant ?? 'primary'}
|
|
319
|
-
onClick={() => handleClose(true)}
|
|
320
|
-
>
|
|
321
|
-
{dialog.confirmLabel ?? 'Confirm'}
|
|
341
|
+
<Button variant="primary" onClick={() => handleClose(true)}>
|
|
342
|
+
{dialog.confirmLabel ?? 'OK'}
|
|
322
343
|
</Button>
|
|
323
344
|
</DialogFooter>
|
|
324
345
|
</Dialog>
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// alert
|
|
329
|
-
return (
|
|
330
|
-
<Dialog
|
|
331
|
-
{...commonProps}
|
|
332
|
-
size={dialog.size ?? 'sm'}
|
|
333
|
-
showCloseButton={false}
|
|
334
|
-
onOpenChange={o => !o && handleClose(true)}
|
|
335
|
-
>
|
|
336
|
-
{dialog.content}
|
|
337
|
-
<DialogFooter>
|
|
338
|
-
<Button variant="primary" onClick={() => handleClose(true)}>
|
|
339
|
-
{dialog.confirmLabel ?? 'OK'}
|
|
340
|
-
</Button>
|
|
341
|
-
</DialogFooter>
|
|
342
|
-
</Dialog>
|
|
343
346
|
)
|
|
344
347
|
}
|
|
345
348
|
|
|
@@ -363,7 +366,7 @@ function ImperativeDialogItem({
|
|
|
363
366
|
* await alert({ title: 'Error', description: 'Something went wrong.' })
|
|
364
367
|
* ```
|
|
365
368
|
*/
|
|
366
|
-
export function useDialog(): UseDialogReturn {
|
|
369
|
+
export function useDialog (): UseDialogReturn {
|
|
367
370
|
const ctx = useContext(DialogImperativeContext)
|
|
368
371
|
if (!ctx) throw new Error('useDialog must be used within <DialogProvider>')
|
|
369
372
|
return ctx
|
package/src/components/Toast.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { createContext, useContext, useState } from 'react'
|
|
2
2
|
import { Toast as BaseToast } from '@base-ui/react'
|
|
3
3
|
import clsx from 'clsx'
|
|
4
4
|
import { X, Info, CheckCircle, AlertTriangle, XCircle } from 'lucide-react'
|
|
@@ -7,27 +7,71 @@ import { X, Info, CheckCircle, AlertTriangle, XCircle } from 'lucide-react'
|
|
|
7
7
|
|
|
8
8
|
export type ToastType = 'default' | 'info' | 'success' | 'warning' | 'error'
|
|
9
9
|
|
|
10
|
+
export type ToastPosition =
|
|
11
|
+
| 'top-left'
|
|
12
|
+
| 'top-center'
|
|
13
|
+
| 'top-right'
|
|
14
|
+
| 'bottom-left'
|
|
15
|
+
| 'bottom-center'
|
|
16
|
+
| 'bottom-right'
|
|
17
|
+
|
|
18
|
+
// ── Position context ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
interface ToastPositionState {
|
|
21
|
+
position: ToastPosition
|
|
22
|
+
setPosition: (p: ToastPosition) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ToastPositionContext = createContext<ToastPositionState>({
|
|
26
|
+
position: 'bottom-right',
|
|
27
|
+
setPosition: () => {},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const positionClasses: Record<ToastPosition, string> = {
|
|
31
|
+
'top-left': 'top-4 left-4 flex-col',
|
|
32
|
+
'top-center': 'top-4 left-1/2 -translate-x-1/2 flex-col',
|
|
33
|
+
'top-right': 'top-4 right-4 flex-col',
|
|
34
|
+
'bottom-left': 'bottom-4 left-4 flex-col-reverse',
|
|
35
|
+
'bottom-center':'bottom-4 left-1/2 -translate-x-1/2 flex-col-reverse',
|
|
36
|
+
'bottom-right': 'bottom-4 right-4 flex-col-reverse',
|
|
37
|
+
}
|
|
38
|
+
|
|
10
39
|
// ── ToastProvider ─────────────────────────────────────────────────────────────
|
|
11
40
|
|
|
12
41
|
export interface ToastProviderProps {
|
|
13
42
|
children: React.ReactNode
|
|
14
43
|
timeout?: number
|
|
15
44
|
limit?: number
|
|
45
|
+
/** Where toasts appear on screen. Defaults to `'bottom-right'`. */
|
|
46
|
+
position?: ToastPosition
|
|
16
47
|
}
|
|
17
48
|
|
|
18
49
|
/**
|
|
19
50
|
* Wrap your app (or part of it) with `ToastProvider` to enable toasts.
|
|
20
51
|
* Then use the `useToast()` hook inside to add toasts.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* // Positon can be set at provider level:
|
|
55
|
+
* <ToastProvider position="top-center">...</ToastProvider>
|
|
56
|
+
*
|
|
57
|
+
* // Or changed at runtime via the hook:
|
|
58
|
+
* const { setPosition } = useToast()
|
|
59
|
+
* setPosition('top-right')
|
|
21
60
|
*/
|
|
22
61
|
export const ToastProvider: React.FC<ToastProviderProps> = ({
|
|
23
62
|
children,
|
|
24
63
|
timeout = 5000,
|
|
25
64
|
limit = 5,
|
|
65
|
+
position: defaultPosition = 'bottom-right',
|
|
26
66
|
}) => {
|
|
67
|
+
const [position, setPosition] = useState<ToastPosition>(defaultPosition)
|
|
68
|
+
|
|
27
69
|
return (
|
|
28
70
|
<BaseToast.Provider timeout={timeout} limit={limit}>
|
|
29
|
-
{
|
|
30
|
-
|
|
71
|
+
<ToastPositionContext.Provider value={{ position, setPosition }}>
|
|
72
|
+
{children}
|
|
73
|
+
<ToastViewport />
|
|
74
|
+
</ToastPositionContext.Provider>
|
|
31
75
|
</BaseToast.Provider>
|
|
32
76
|
)
|
|
33
77
|
}
|
|
@@ -48,11 +92,14 @@ export interface ToastOptions {
|
|
|
48
92
|
|
|
49
93
|
/**
|
|
50
94
|
* Hook to imperatively add, close and update toasts.
|
|
95
|
+
* Also exposes `position` / `setPosition` for runtime position changes.
|
|
51
96
|
*
|
|
52
97
|
* Must be used inside `<ToastProvider>`.
|
|
53
98
|
*/
|
|
54
99
|
export function useToast() {
|
|
55
100
|
const manager = BaseToast.useToastManager()
|
|
101
|
+
const { position, setPosition } = useContext(ToastPositionContext)
|
|
102
|
+
|
|
56
103
|
return {
|
|
57
104
|
toast: (options: ToastOptions) =>
|
|
58
105
|
manager.add({
|
|
@@ -65,6 +112,10 @@ export function useToast() {
|
|
|
65
112
|
: undefined,
|
|
66
113
|
}),
|
|
67
114
|
dismiss: manager.close,
|
|
115
|
+
/** Current toast position */
|
|
116
|
+
position,
|
|
117
|
+
/** Dynamically change where toasts appear */
|
|
118
|
+
setPosition,
|
|
68
119
|
}
|
|
69
120
|
}
|
|
70
121
|
|
|
@@ -72,9 +123,15 @@ export function useToast() {
|
|
|
72
123
|
|
|
73
124
|
function ToastViewport() {
|
|
74
125
|
const { toasts } = BaseToast.useToastManager()
|
|
126
|
+
const { position } = useContext(ToastPositionContext)
|
|
75
127
|
|
|
76
128
|
return (
|
|
77
|
-
<BaseToast.Viewport
|
|
129
|
+
<BaseToast.Viewport
|
|
130
|
+
className={clsx(
|
|
131
|
+
'fixed z-[100] flex w-80 gap-2 outline-none',
|
|
132
|
+
positionClasses[position],
|
|
133
|
+
)}
|
|
134
|
+
>
|
|
78
135
|
{toasts.map((toast) => (
|
|
79
136
|
<ToastItem key={toast.id} toast={toast} />
|
|
80
137
|
))}
|
|
@@ -84,49 +141,94 @@ function ToastViewport() {
|
|
|
84
141
|
|
|
85
142
|
// ── ToastItem (internal) ──────────────────────────────────────────────────────
|
|
86
143
|
|
|
87
|
-
|
|
88
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Per-type config — icon colors adapt to theme via dark: modifier.
|
|
146
|
+
* Accent strip colors stay the same in both modes.
|
|
147
|
+
*/
|
|
148
|
+
const typeConfig: Record<
|
|
149
|
+
ToastType,
|
|
150
|
+
{
|
|
151
|
+
icon: React.ReactNode
|
|
152
|
+
titleClass: string
|
|
153
|
+
surfaceClass: string
|
|
154
|
+
actionClass: string
|
|
155
|
+
closeClass: string
|
|
156
|
+
}
|
|
157
|
+
> = {
|
|
158
|
+
default: {
|
|
159
|
+
icon: null,
|
|
160
|
+
titleClass: 'text-(--text-primary) dark:text-white',
|
|
161
|
+
surfaceClass: 'bg-(--bg-primary) border-(--border-light) dark:bg-[#2c2f33] dark:border-transparent',
|
|
162
|
+
actionClass: 'text-(--accent) hover:underline dark:text-white/85 dark:hover:text-white',
|
|
163
|
+
closeClass: 'text-(--text-muted) hover:bg-(--bg-hover) hover:text-(--text-primary) dark:text-white/40 dark:hover:bg-white/10 dark:hover:text-white',
|
|
164
|
+
},
|
|
89
165
|
info: {
|
|
90
|
-
icon: <Info size={
|
|
91
|
-
|
|
166
|
+
icon: <Info size={14} className="text-[#1164A3] dark:text-[#5ba4cf]" />,
|
|
167
|
+
titleClass: 'text-[#1164A3] dark:text-[#5ba4cf]',
|
|
168
|
+
surfaceClass: 'bg-[#F6FAFE] border-[#E3EFF9] dark:bg-[#102633] dark:border-[#1A4256]',
|
|
169
|
+
actionClass: 'text-[#0F5A94] hover:underline hover:text-[#0B4673] dark:text-[#7AB7DE] dark:hover:text-[#A7D1EC]',
|
|
170
|
+
closeClass: 'text-[#7C9BB4] hover:bg-[#E8F2FB] hover:text-[#1164A3] dark:text-[#8AA7BD] dark:hover:bg-[#173547] dark:hover:text-[#7AB7DE]',
|
|
92
171
|
},
|
|
93
172
|
success: {
|
|
94
|
-
icon: <CheckCircle size={
|
|
95
|
-
|
|
173
|
+
icon: <CheckCircle size={14} className="text-[#007a5a] dark:text-[#39c088]" />,
|
|
174
|
+
titleClass: 'text-[#007a5a] dark:text-[#39c088]',
|
|
175
|
+
surfaceClass: 'bg-[#F4FBF7] border-[#DFF1E8] dark:bg-[#102A20] dark:border-[#1A4B39]',
|
|
176
|
+
actionClass: 'text-[#0A6B51] hover:underline hover:text-[#08533F] dark:text-[#6FD4A3] dark:hover:text-[#96E6BB]',
|
|
177
|
+
closeClass: 'text-[#7A9E91] hover:bg-[#E7F6EE] hover:text-[#007A5A] dark:text-[#89A99E] dark:hover:bg-[#18372C] dark:hover:text-[#6FD4A3]',
|
|
96
178
|
},
|
|
97
179
|
warning: {
|
|
98
|
-
icon: <AlertTriangle size={
|
|
99
|
-
|
|
180
|
+
icon: <AlertTriangle size={14} className="text-amber-500 dark:text-amber-400" />,
|
|
181
|
+
titleClass: 'text-amber-500 dark:text-amber-400',
|
|
182
|
+
surfaceClass: 'bg-[#FFFBF0] border-[#F7E9BF] dark:bg-[#3A2A09] dark:border-[#5C4514]',
|
|
183
|
+
actionClass: 'text-[#B26A00] hover:underline hover:text-[#8B5300] dark:text-[#F0C46B] dark:hover:text-[#F5D697]',
|
|
184
|
+
closeClass: 'text-[#AA9B73] hover:bg-[#FFF3D6] hover:text-[#B26A00] dark:text-[#A99668] dark:hover:bg-[#4B3810] dark:hover:text-[#F0C46B]',
|
|
100
185
|
},
|
|
101
186
|
error: {
|
|
102
|
-
icon: <XCircle size={
|
|
103
|
-
|
|
187
|
+
icon: <XCircle size={14} className="text-[#E01E5A] dark:text-[#e06082]" />,
|
|
188
|
+
titleClass: 'text-[#E01E5A] dark:text-[#e06082]',
|
|
189
|
+
surfaceClass: 'bg-[#FEF6F8] border-[#F8E2E8] dark:bg-[#3A1822] dark:border-[#5E2735]',
|
|
190
|
+
actionClass: 'text-[#C61B50] hover:underline hover:text-[#9E153F] dark:text-[#F08CA8] dark:hover:text-[#F5B0C2]',
|
|
191
|
+
closeClass: 'text-[#B0929C] hover:bg-[#FBE8EE] hover:text-[#E01E5A] dark:text-[#A88E96] dark:hover:bg-[#4A1F2C] dark:hover:text-[#F08CA8]',
|
|
104
192
|
},
|
|
105
193
|
}
|
|
106
194
|
|
|
107
195
|
function ToastItem({ toast }: { toast: BaseToast.Root.ToastObject }) {
|
|
108
196
|
const type = (toast.type ?? 'default') as ToastType
|
|
109
|
-
const
|
|
197
|
+
const config = typeConfig[type] ?? typeConfig.default
|
|
110
198
|
|
|
111
199
|
return (
|
|
112
200
|
<BaseToast.Root
|
|
113
201
|
toast={toast}
|
|
202
|
+
swipeDirection={[]}
|
|
114
203
|
className={clsx(
|
|
115
|
-
'flex w-full items-start gap-
|
|
116
|
-
|
|
117
|
-
'
|
|
204
|
+
'group relative flex w-full items-start gap-2.5 overflow-hidden',
|
|
205
|
+
'rounded-lg pl-3 pr-3 pt-3 pb-3',
|
|
206
|
+
'border border-(--border-light)',
|
|
207
|
+
'shadow-[0_2px_16px_rgba(0,0,0,0.08),0_1px_4px_rgba(0,0,0,0.05)]',
|
|
208
|
+
'dark:shadow-[0_4px_24px_rgba(0,0,0,0.45)]',
|
|
209
|
+
'data-[starting]:animate-[toast-slide-in_240ms_cubic-bezier(0.16,1,0.3,1)]',
|
|
210
|
+
'data-[ending]:animate-[toast-slide-out_180ms_ease-in_forwards]',
|
|
211
|
+
config.surfaceClass,
|
|
118
212
|
)}
|
|
119
213
|
>
|
|
120
|
-
{
|
|
214
|
+
{/* ── Icon ─────────────────────────────────────────── */}
|
|
215
|
+
{config.icon && (
|
|
216
|
+
<div className="flex h-5 shrink-0 items-center">{config.icon}</div>
|
|
217
|
+
)}
|
|
121
218
|
|
|
122
|
-
|
|
219
|
+
{/* ── Content ──────────────────────────────────────── */}
|
|
220
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
123
221
|
{toast.title && (
|
|
124
|
-
<BaseToast.Title
|
|
222
|
+
<BaseToast.Title
|
|
223
|
+
className={clsx('text-[14px] font-semibold leading-snug', config.titleClass)}
|
|
224
|
+
>
|
|
125
225
|
{toast.title}
|
|
126
226
|
</BaseToast.Title>
|
|
127
227
|
)}
|
|
128
228
|
{toast.description && (
|
|
129
|
-
<BaseToast.Description
|
|
229
|
+
<BaseToast.Description
|
|
230
|
+
className="text-[13px] leading-snug text-(--text-secondary) dark:text-white/65"
|
|
231
|
+
>
|
|
130
232
|
{toast.description}
|
|
131
233
|
</BaseToast.Description>
|
|
132
234
|
)}
|
|
@@ -134,15 +236,23 @@ function ToastItem({ toast }: { toast: BaseToast.Root.ToastObject }) {
|
|
|
134
236
|
<button
|
|
135
237
|
{...toast.actionProps}
|
|
136
238
|
className={clsx(
|
|
137
|
-
'mt-1.5 self-start text-[13px] font-semibold
|
|
138
|
-
|
|
239
|
+
'mt-1.5 self-start rounded text-[13px] font-semibold outline-none transition-colors',
|
|
240
|
+
config.actionClass,
|
|
241
|
+
'focus-visible:ring-1 focus-visible:ring-(--focus-ring) dark:focus-visible:ring-white/50',
|
|
139
242
|
)}
|
|
140
243
|
/>
|
|
141
244
|
)}
|
|
142
245
|
</div>
|
|
143
246
|
|
|
144
|
-
|
|
145
|
-
|
|
247
|
+
{/* ── Close button ─────────────────────────────────── */}
|
|
248
|
+
<BaseToast.Close
|
|
249
|
+
className={clsx(
|
|
250
|
+
'ml-0.5 shrink-0 rounded p-1 outline-none transition-colors',
|
|
251
|
+
config.closeClass,
|
|
252
|
+
'focus-visible:ring-1 focus-visible:ring-(--focus-ring) dark:focus-visible:ring-white/50',
|
|
253
|
+
)}
|
|
254
|
+
>
|
|
255
|
+
<X size={13} />
|
|
146
256
|
</BaseToast.Close>
|
|
147
257
|
</BaseToast.Root>
|
|
148
258
|
)
|
package/src/components/index.ts
CHANGED
|
@@ -174,7 +174,7 @@ export type { ProgressProps } from './Progress'
|
|
|
174
174
|
|
|
175
175
|
// Toast Components
|
|
176
176
|
export { ToastProvider, useToast } from './Toast'
|
|
177
|
-
export type { ToastProviderProps, ToastOptions, ToastType } from './Toast'
|
|
177
|
+
export type { ToastProviderProps, ToastOptions, ToastType, ToastPosition } from './Toast'
|
|
178
178
|
|
|
179
179
|
// Loading Component
|
|
180
180
|
export { Loading } from './Loading'
|
package/src/index.css
CHANGED
|
@@ -75,6 +75,28 @@
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
@keyframes toast-slide-in {
|
|
79
|
+
from {
|
|
80
|
+
opacity: 0;
|
|
81
|
+
transform: translateY(10px) scale(0.96);
|
|
82
|
+
}
|
|
83
|
+
to {
|
|
84
|
+
opacity: 1;
|
|
85
|
+
transform: translateY(0) scale(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@keyframes toast-slide-out {
|
|
90
|
+
from {
|
|
91
|
+
opacity: 1;
|
|
92
|
+
transform: scale(1);
|
|
93
|
+
}
|
|
94
|
+
to {
|
|
95
|
+
opacity: 0;
|
|
96
|
+
transform: scale(0.94);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
:root {
|
|
79
101
|
/* Colors - Slack Brand */
|
|
80
102
|
--slack-aubergine: #3F0E40;
|