@boyernick/standard-ui-react 0.1.1-canary.14 → 0.1.1-canary.15
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/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/text-animate.tsx +57 -21
- package/src/toast.tsx +154 -41
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -606,6 +606,8 @@ export {
|
|
|
606
606
|
ToastViewport,
|
|
607
607
|
ToastRoot,
|
|
608
608
|
ToastContent,
|
|
609
|
+
ToastIcon,
|
|
610
|
+
toastRootVariants,
|
|
609
611
|
ToastTitle,
|
|
610
612
|
ToastDescription,
|
|
611
613
|
ToastAction,
|
|
@@ -619,6 +621,8 @@ export {
|
|
|
619
621
|
type ToastViewportProps,
|
|
620
622
|
type ToastRootProps,
|
|
621
623
|
type ToastContentProps,
|
|
624
|
+
type ToastIconProps,
|
|
625
|
+
type ToastType,
|
|
622
626
|
type ToastTitleProps,
|
|
623
627
|
type ToastDescriptionProps,
|
|
624
628
|
type ToastActionProps,
|
package/src/text-animate.tsx
CHANGED
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
} from "react"
|
|
9
9
|
import { cn } from "./lib/cn"
|
|
10
10
|
|
|
11
|
+
/** How many glyphs are scrambling at once. The window travels the line; behind
|
|
12
|
+
* it the text is settled, ahead of it nothing is drawn yet. 8 is the reference
|
|
13
|
+
* default from baffle.js, which this effect follows. */
|
|
14
|
+
const DECODE_WINDOW = 8
|
|
15
|
+
|
|
11
16
|
const DECODE_GLYPHS =
|
|
12
17
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#@$%&*"
|
|
13
18
|
|
|
@@ -46,6 +51,7 @@ export const TextAnimate = ({
|
|
|
46
51
|
useEffect(() => {
|
|
47
52
|
let cancelled = false
|
|
48
53
|
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
54
|
+
let frameId: number | undefined
|
|
49
55
|
|
|
50
56
|
const start = () => {
|
|
51
57
|
if (
|
|
@@ -82,30 +88,59 @@ export const TextAnimate = ({
|
|
|
82
88
|
return
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
// Decode follows baffle.js's `reveal`, which is the reference for this
|
|
92
|
+
// effect: a fixed-width window of scrambled glyphs travels left to
|
|
93
|
+
// right, the real text sits behind it, and nothing at all is drawn ahead
|
|
94
|
+
// of it — so the line grows in with a churning leading edge rather than
|
|
95
|
+
// resolving out of a full-width block of noise.
|
|
96
|
+
//
|
|
97
|
+
// It runs on rAF with a time accumulator rather than `setTimeout(speed)`.
|
|
98
|
+
// A 28ms timer does not divide into a ~16.7ms frame, so steps land on
|
|
99
|
+
// uneven frames and the text visibly stutters; accumulating elapsed time
|
|
100
|
+
// keeps the reference's cadence while landing each step on a real frame.
|
|
101
|
+
const slots = chars.reduce<number[]>((acc, char, index) => {
|
|
102
|
+
if (char !== " ") acc.push(index)
|
|
103
|
+
return acc
|
|
104
|
+
}, [])
|
|
105
|
+
|
|
106
|
+
// The window starts off the front of the line so the first glyph is
|
|
107
|
+
// already scrambling as it arrives.
|
|
108
|
+
let position = -DECODE_WINDOW
|
|
109
|
+
let previousStep = -Infinity
|
|
110
|
+
const startedAt = performance.now()
|
|
111
|
+
|
|
112
|
+
const frame = (now: number) => {
|
|
91
113
|
if (cancelled) return
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
|
|
115
|
+
const step = Math.floor((now - startedAt) / speed) - DECODE_WINDOW
|
|
116
|
+
if (step !== previousStep) {
|
|
117
|
+
previousStep = step
|
|
118
|
+
position = step
|
|
119
|
+
|
|
120
|
+
if (position > slots.length) {
|
|
121
|
+
setOutput(text)
|
|
122
|
+
setDone(true)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const next = [...chars]
|
|
127
|
+
for (let p = Math.max(position, 0); p < slots.length; p += 1) {
|
|
128
|
+
next[slots[p]] =
|
|
129
|
+
p < position + DECODE_WINDOW
|
|
130
|
+
? DECODE_GLYPHS[
|
|
131
|
+
Math.floor(Math.random() * DECODE_GLYPHS.length)
|
|
132
|
+
]
|
|
133
|
+
: ""
|
|
134
|
+
}
|
|
135
|
+
setOutput(next.join(""))
|
|
106
136
|
}
|
|
137
|
+
|
|
138
|
+
frameId = requestAnimationFrame(frame)
|
|
107
139
|
}
|
|
108
|
-
|
|
140
|
+
|
|
141
|
+
setOutput("")
|
|
142
|
+
frameId = requestAnimationFrame(frame)
|
|
143
|
+
return
|
|
109
144
|
}
|
|
110
145
|
|
|
111
146
|
timeoutId = setTimeout(start, delay)
|
|
@@ -113,6 +148,7 @@ export const TextAnimate = ({
|
|
|
113
148
|
return () => {
|
|
114
149
|
cancelled = true
|
|
115
150
|
if (timeoutId) clearTimeout(timeoutId)
|
|
151
|
+
if (frameId) cancelAnimationFrame(frameId)
|
|
116
152
|
}
|
|
117
153
|
}, [chars, delay, effect, replay, speed, text])
|
|
118
154
|
|
package/src/toast.tsx
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { Toast as BaseToast } from "@base-ui/react/toast"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
5
|
import type { ComponentProps } from "react"
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
IconCircleCheck,
|
|
8
|
+
IconCircleInfo,
|
|
9
|
+
IconCrossSmall,
|
|
10
|
+
IconExclamationCircle,
|
|
11
|
+
IconExclamationTriangle,
|
|
12
|
+
} from "./icons"
|
|
13
|
+
import { Spinner } from "./spinner"
|
|
6
14
|
import { cn } from "./lib/cn"
|
|
7
15
|
import { motion } from "./lib/motion"
|
|
8
16
|
|
|
9
17
|
export type ToastProviderProps = ComponentProps<typeof BaseToast.Provider>
|
|
10
18
|
export type ToastPortalProps = ComponentProps<typeof BaseToast.Portal>
|
|
11
19
|
export type ToastViewportProps = ComponentProps<typeof BaseToast.Viewport>
|
|
12
|
-
export type ToastRootProps = ComponentProps<typeof BaseToast.Root>
|
|
20
|
+
export type ToastRootProps = ComponentProps<typeof BaseToast.Root> &
|
|
21
|
+
VariantProps<typeof toastRootVariants>
|
|
13
22
|
export type ToastContentProps = ComponentProps<typeof BaseToast.Content>
|
|
14
23
|
export type ToastTitleProps = ComponentProps<typeof BaseToast.Title>
|
|
15
24
|
export type ToastDescriptionProps = ComponentProps<typeof BaseToast.Description>
|
|
@@ -18,6 +27,10 @@ export type ToastCloseProps = ComponentProps<typeof BaseToast.Close>
|
|
|
18
27
|
export type ToastPositionerProps = ComponentProps<typeof BaseToast.Positioner>
|
|
19
28
|
export type ToastArrowProps = ComponentProps<typeof BaseToast.Arrow>
|
|
20
29
|
|
|
30
|
+
/** The types that carry a glyph. Anything else renders no icon. */
|
|
31
|
+
export type ToastType = "success" | "error" | "warning" | "info" | "loading"
|
|
32
|
+
export type ToastIconProps = ComponentProps<"span"> & { type?: string }
|
|
33
|
+
|
|
21
34
|
export const ToastProvider = (props: ToastProviderProps) => (
|
|
22
35
|
<BaseToast.Provider {...props} />
|
|
23
36
|
)
|
|
@@ -29,54 +42,149 @@ export const ToastPortal = (props: ToastPortalProps) => (
|
|
|
29
42
|
export const ToastViewport = ({ className, ...props }: ToastViewportProps) => (
|
|
30
43
|
<BaseToast.Viewport
|
|
31
44
|
// No flex column: the toasts inside are absolutely positioned so they pile
|
|
32
|
-
// up as a stack. In flow they would march down past the
|
|
45
|
+
// up as a stack. In flow they would march down past the edge.
|
|
33
46
|
className={cn(
|
|
34
|
-
"fixed
|
|
47
|
+
"fixed top-8 left-1/2 z-[100] w-[min(100vw-2rem,28rem)] -translate-x-1/2 outline-none",
|
|
35
48
|
className,
|
|
36
49
|
)}
|
|
37
50
|
{...props}
|
|
38
51
|
/>
|
|
39
52
|
)
|
|
40
53
|
|
|
41
|
-
/** Each toast is pinned to the
|
|
42
|
-
* index, so the stack reads as a pile of cards
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
54
|
+
/** Each toast is pinned to the top of the viewport and pushed down by its own
|
|
55
|
+
* index, so the stack reads as a pile of cards: the one behind peeks out below
|
|
56
|
+
* by `--peek` and sits a step smaller. Hovering the viewport sets
|
|
57
|
+
* `data-expanded`, which fans them out to their real heights.
|
|
58
|
+
*
|
|
59
|
+
* `origin-top` is what keeps the arithmetic simple — scaling a card toward its
|
|
60
|
+
* own top edge leaves that edge where it is, so the peek is the only offset
|
|
61
|
+
* the collapsed stack needs. */
|
|
62
|
+
const toastRootVariants = cva(
|
|
63
|
+
cn(
|
|
64
|
+
"absolute top-0 right-0 left-0 z-[calc(1000-var(--toast-index))] box-border w-full origin-top",
|
|
65
|
+
"[--gap:0.625rem] [--peek:0.875rem]",
|
|
66
|
+
"[--scale:calc(max(0,1-(var(--toast-index)*0.05)))]",
|
|
67
|
+
"[--height:var(--toast-frontmost-height,var(--toast-height))]",
|
|
68
|
+
"[--offset-y:calc(var(--toast-offset-y)+calc(var(--toast-index)*var(--gap))+var(--toast-swipe-movement-y,0px))]",
|
|
69
|
+
// A row, not an overlay: the action and the close sit inline at the right,
|
|
70
|
+
// so the close cannot be absolutely positioned over reserved padding.
|
|
71
|
+
"flex items-start gap-2 rounded-xl border py-2.5 pr-2 pl-4 shadow-lg outline-none select-none",
|
|
72
|
+
"h-[var(--height)] data-expanded:h-[var(--toast-height)]",
|
|
73
|
+
"[transform:translateX(var(--toast-swipe-movement-x,0px))_translateY(calc(var(--toast-swipe-movement-y,0px)+(var(--toast-index)*var(--peek))))_scale(var(--scale))]",
|
|
74
|
+
"data-expanded:[transform:translateX(var(--toast-swipe-movement-x,0px))_translateY(var(--offset-y))]",
|
|
75
|
+
// Bridges the gap between cards so crossing it does not collapse the fan.
|
|
76
|
+
"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
|
|
77
|
+
"transition-[transform,opacity,height] duration-[var(--duration-md)] ease-enter motion-reduce:transition-none",
|
|
78
|
+
// Anchored to the top, so they arrive and leave upwards.
|
|
79
|
+
"data-starting-style:[transform:translateY(-150%)]",
|
|
80
|
+
"data-ending-style:opacity-0 data-limited:opacity-0",
|
|
81
|
+
"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(-150%)]",
|
|
82
|
+
),
|
|
83
|
+
{
|
|
84
|
+
variants: {
|
|
85
|
+
variant: {
|
|
86
|
+
// The page's own surface. The border stays transparent so the edge is
|
|
87
|
+
// `shadow-lg`'s hairline, the same one every dropdown carries.
|
|
88
|
+
default: "border-transparent bg-surface text-fg-primary",
|
|
89
|
+
// Flipped against the page. Here the hairline is invisible — a black
|
|
90
|
+
// line on a near-black card — so the edge is drawn with the inverted
|
|
91
|
+
// foreground, which flips with the theme alongside the surface.
|
|
92
|
+
inverted: "border-fg-inverted/10 bg-surface-inverted text-fg-inverted",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
defaultVariants: { variant: "default" },
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
/** Each toast is pinned to the top of the viewport and pushed down by its own
|
|
100
|
+
* index, so the stack reads as a pile of cards: the one behind peeks out below
|
|
101
|
+
* by `--peek` and sits a step smaller. Hovering the viewport sets
|
|
102
|
+
* `data-expanded`, which fans them out to their real heights.
|
|
103
|
+
*
|
|
104
|
+
* `origin-top` is what keeps the arithmetic simple — scaling a card toward its
|
|
105
|
+
* own top edge leaves that edge where it is, so the peek is the only offset
|
|
106
|
+
* the collapsed stack needs.
|
|
107
|
+
*
|
|
108
|
+
* The variant is published as `data-variant` rather than passed down: the
|
|
109
|
+
* title, description, action, close and icon all have to follow the surface,
|
|
110
|
+
* and a data attribute lets each one carry its own pair of rules instead of
|
|
111
|
+
* every caller threading a prop through five components. */
|
|
112
|
+
export const ToastRoot = ({
|
|
113
|
+
className,
|
|
114
|
+
variant = "default",
|
|
115
|
+
...props
|
|
116
|
+
}: ToastRootProps) => (
|
|
46
117
|
<BaseToast.Root
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"[--gap:0.625rem] [--peek:0.75rem]",
|
|
50
|
-
"[--scale:calc(max(0,1-(var(--toast-index)*0.05)))] [--shrink:calc(1-var(--scale))]",
|
|
51
|
-
"[--height:var(--toast-frontmost-height,var(--toast-height))]",
|
|
52
|
-
"[--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y,0px))]",
|
|
53
|
-
// No border: `shadow-lg` composes `--elevation-hairline`, the same edge the
|
|
54
|
-
// dropdowns carry — theme-aware and 0.5px on hi-dpi. A border on top of it
|
|
55
|
-
// stacked a second, darker line. The error type tints that edge with a
|
|
56
|
-
// ring, which also sits outside the box model so the height never shifts.
|
|
57
|
-
"rounded-xl bg-surface px-3.5 py-3 shadow-lg outline-none select-none",
|
|
58
|
-
// Collapsed, every toast behind the front one is scaled down and shifted
|
|
59
|
-
// up; `--shrink` cancels the gap that scaling opens at the bottom edge.
|
|
60
|
-
"h-[var(--height)] data-expanded:h-[var(--toast-height)]",
|
|
61
|
-
"[transform:translateX(var(--toast-swipe-movement-x,0px))_translateY(calc(var(--toast-swipe-movement-y,0px)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))]",
|
|
62
|
-
"data-expanded:[transform:translateX(var(--toast-swipe-movement-x,0px))_translateY(var(--offset-y))]",
|
|
63
|
-
// Bridges the gap between cards so crossing it does not collapse the fan.
|
|
64
|
-
"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
|
|
65
|
-
"transition-[transform,opacity,height] duration-[var(--duration-md)] ease-enter motion-reduce:transition-none",
|
|
66
|
-
"data-starting-style:[transform:translateY(150%)]",
|
|
67
|
-
"data-ending-style:opacity-0 data-limited:opacity-0",
|
|
68
|
-
"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]",
|
|
69
|
-
"data-[type=error]:ring-1 data-[type=error]:ring-destructive/40",
|
|
70
|
-
className,
|
|
71
|
-
)}
|
|
118
|
+
data-variant={variant}
|
|
119
|
+
className={cn("group/toast", toastRootVariants({ variant }), className)}
|
|
72
120
|
{...props}
|
|
73
121
|
/>
|
|
74
122
|
)
|
|
75
123
|
|
|
124
|
+
export { toastRootVariants }
|
|
125
|
+
|
|
126
|
+
const toastGlyphs = {
|
|
127
|
+
success: {
|
|
128
|
+
Icon: IconCircleCheck,
|
|
129
|
+
tone: `text-status-success group-data-[variant=inverted]/toast:text-status-success-inverted`,
|
|
130
|
+
},
|
|
131
|
+
error: {
|
|
132
|
+
Icon: IconExclamationCircle,
|
|
133
|
+
tone: `text-status-critical group-data-[variant=inverted]/toast:text-status-critical-inverted`,
|
|
134
|
+
},
|
|
135
|
+
warning: {
|
|
136
|
+
Icon: IconExclamationTriangle,
|
|
137
|
+
tone: `text-status-warning group-data-[variant=inverted]/toast:text-status-warning-inverted`,
|
|
138
|
+
},
|
|
139
|
+
info: {
|
|
140
|
+
Icon: IconCircleInfo,
|
|
141
|
+
tone: `text-status-info group-data-[variant=inverted]/toast:text-status-info-inverted`,
|
|
142
|
+
},
|
|
143
|
+
} as const
|
|
144
|
+
|
|
145
|
+
/** The glyph tracks the title, not the block. The row is `items-start` so the
|
|
146
|
+
* glyph and the content column measure from the same top edge, and `mt-0.5`
|
|
147
|
+
* is the half-leading above a 14px title in a 20px line — which centres the
|
|
148
|
+
* 16px glyph on the title whether or not a description follows it.
|
|
149
|
+
*
|
|
150
|
+
* The margin is on both axes, not just the top. Every item beside the content
|
|
151
|
+
* column is collapsed to a 20px outer box — the height of the title's line —
|
|
152
|
+
* so it centres on that line while contributing nothing extra to the row. A
|
|
153
|
+
* top-only offset lines the item up but leaves its full height in the box, and
|
|
154
|
+
* the tallest control then stretches the toast downward and everything reads
|
|
155
|
+
* high. Hit areas are untouched: the boxes overflow, they do not shrink. */
|
|
156
|
+
const ICON_ALIGN = "my-0.5 flex shrink-0"
|
|
157
|
+
|
|
158
|
+
/** The glyph for a toast's type. Each tone carries both surfaces: the plain
|
|
159
|
+
* status tokens are tuned for the page, and the `-inverted` pair for a card
|
|
160
|
+
* that flips against it. Pass `toast.type` from the render loop — Base UI
|
|
161
|
+
* gives no way to read the toast from inside the root. */
|
|
162
|
+
export const ToastIcon = ({ type, className, ...props }: ToastIconProps) => {
|
|
163
|
+
if (type === "loading") {
|
|
164
|
+
return (
|
|
165
|
+
<span className={cn(ICON_ALIGN, className)} {...props}>
|
|
166
|
+
<Spinner size="sm" />
|
|
167
|
+
</span>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const glyph = toastGlyphs[type as keyof typeof toastGlyphs]
|
|
172
|
+
if (!glyph) return null
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<span
|
|
176
|
+
className={cn(ICON_ALIGN, glyph.tone, className)}
|
|
177
|
+
{...props}
|
|
178
|
+
>
|
|
179
|
+
<glyph.Icon size={16} className="size-4" aria-hidden />
|
|
180
|
+
</span>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
76
184
|
export const ToastContent = ({ className, ...props }: ToastContentProps) => (
|
|
77
185
|
<BaseToast.Content
|
|
78
186
|
className={cn(
|
|
79
|
-
"flex flex-col gap-0.5 overflow-hidden transition-opacity duration-[var(--duration-sm)]",
|
|
187
|
+
"flex min-w-0 flex-1 flex-col gap-0.5 overflow-hidden transition-opacity duration-[var(--duration-sm)]",
|
|
80
188
|
"data-behind:pointer-events-none data-behind:opacity-0 data-expanded:data-behind:opacity-100",
|
|
81
189
|
className,
|
|
82
190
|
)}
|
|
@@ -86,7 +194,7 @@ export const ToastContent = ({ className, ...props }: ToastContentProps) => (
|
|
|
86
194
|
|
|
87
195
|
export const ToastTitle = ({ className, ...props }: ToastTitleProps) => (
|
|
88
196
|
<BaseToast.Title
|
|
89
|
-
className={cn("text-
|
|
197
|
+
className={cn("text-sm-strong text-fg-primary group-data-[variant=inverted]/toast:text-fg-inverted", className)}
|
|
90
198
|
{...props}
|
|
91
199
|
/>
|
|
92
200
|
)
|
|
@@ -96,7 +204,10 @@ export const ToastDescription = ({
|
|
|
96
204
|
...props
|
|
97
205
|
}: ToastDescriptionProps) => (
|
|
98
206
|
<BaseToast.Description
|
|
99
|
-
className={cn(
|
|
207
|
+
className={cn(
|
|
208
|
+
"text-sm text-fg-secondary group-data-[variant=inverted]/toast:text-fg-inverted-secondary",
|
|
209
|
+
className,
|
|
210
|
+
)}
|
|
100
211
|
{...props}
|
|
101
212
|
/>
|
|
102
213
|
)
|
|
@@ -104,9 +215,10 @@ export const ToastDescription = ({
|
|
|
104
215
|
export const ToastAction = ({ className, ...props }: ToastActionProps) => (
|
|
105
216
|
<BaseToast.Action
|
|
106
217
|
className={cn(
|
|
107
|
-
"
|
|
218
|
+
"text-xs -my-1 inline-flex h-7 shrink-0 cursor-pointer items-center justify-center rounded-md border border-border-secondary px-2.5 text-fg-primary outline-none",
|
|
108
219
|
motion.colors,
|
|
109
|
-
"hover:bg-background-tertiary
|
|
220
|
+
"hover:bg-background-tertiary focus-visible:ring-2 focus-visible:ring-ring/40",
|
|
221
|
+
"group-data-[variant=inverted]/toast:border-fg-inverted/15 group-data-[variant=inverted]/toast:text-fg-inverted group-data-[variant=inverted]/toast:hover:bg-fg-inverted/10 group-data-[variant=inverted]/toast:focus-visible:ring-fg-inverted/50",
|
|
110
222
|
className,
|
|
111
223
|
)}
|
|
112
224
|
{...props}
|
|
@@ -120,9 +232,10 @@ export const ToastClose = ({
|
|
|
120
232
|
}: ToastCloseProps) => (
|
|
121
233
|
<BaseToast.Close
|
|
122
234
|
className={cn(
|
|
123
|
-
"
|
|
235
|
+
"-my-0.5 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-fg-tertiary outline-none",
|
|
124
236
|
motion.colors,
|
|
125
|
-
"hover:bg-background-tertiary hover:text-fg-primary
|
|
237
|
+
"hover:bg-background-tertiary hover:text-fg-primary focus-visible:ring-2 focus-visible:ring-ring/40",
|
|
238
|
+
"group-data-[variant=inverted]/toast:text-fg-inverted/50 group-data-[variant=inverted]/toast:hover:bg-fg-inverted/10 group-data-[variant=inverted]/toast:hover:text-fg-inverted group-data-[variant=inverted]/toast:focus-visible:ring-fg-inverted/50",
|
|
126
239
|
className,
|
|
127
240
|
)}
|
|
128
241
|
{...props}
|