Skip to content

Commit 860d42d

Browse files
Flash session storage added with confetti and toast notifications (#238)
Co-authored-by: Alem Tuzlak <[email protected]>
1 parent 571b4bc commit 860d42d

22 files changed

+682
-59
lines changed

app/components/confetti.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Index as ConfettiShower } from 'confetti-react'
2+
import { ClientOnly } from 'remix-utils'
3+
4+
/**
5+
* confetti is a unique random identifier which re-renders the component
6+
*/
7+
export function Confetti({ confetti }: { confetti?: string }) {
8+
return (
9+
<ClientOnly>
10+
{() => (
11+
<ConfettiShower
12+
key={confetti}
13+
run={Boolean(confetti)}
14+
recycle={false}
15+
numberOfPieces={500}
16+
width={window.innerWidth}
17+
height={window.innerHeight}
18+
/>
19+
)}
20+
</ClientOnly>
21+
)
22+
}

app/components/ui/icon.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,17 @@ export function Icon({
6363
}
6464

6565
type IconName =
66-
| "avatar"
67-
| "camera"
68-
| "check"
69-
| "cross-1"
70-
| "exit"
71-
| "laptop"
72-
| "lock-closed"
73-
| "lock-open-1"
74-
| "moon"
75-
| "pencil-1"
76-
| "pencil-2"
77-
| "plus"
78-
| "sun"
79-
| "trash"
66+
| 'avatar'
67+
| 'camera'
68+
| 'check'
69+
| 'cross-1'
70+
| 'exit'
71+
| 'laptop'
72+
| 'lock-closed'
73+
| 'lock-open-1'
74+
| 'moon'
75+
| 'pencil-1'
76+
| 'pencil-2'
77+
| 'plus'
78+
| 'sun'
79+
| 'trash'

app/components/ui/toast.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import * as React from 'react'
2+
import * as ToastPrimitives from '@radix-ui/react-toast'
3+
import { cva, type VariantProps } from 'class-variance-authority'
4+
import { cn } from '~/utils/misc.ts'
5+
import { Icon } from './icon.tsx'
6+
7+
const ToastProvider = ToastPrimitives.Provider
8+
9+
const ToastViewport = React.forwardRef<
10+
React.ElementRef<typeof ToastPrimitives.Viewport>,
11+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
12+
>(({ className, ...props }, ref) => (
13+
<ToastPrimitives.Viewport
14+
ref={ref}
15+
className={cn(
16+
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
17+
className,
18+
)}
19+
{...props}
20+
/>
21+
))
22+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
23+
24+
const toastVariants = cva(
25+
'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 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-top-full data-[state=open]:sm:slide-in-from-bottom-full',
26+
{
27+
variants: {
28+
variant: {
29+
default: 'border bg-background',
30+
destructive:
31+
'destructive group border-destructive bg-destructive text-destructive-foreground',
32+
},
33+
},
34+
defaultVariants: {
35+
variant: 'default',
36+
},
37+
},
38+
)
39+
40+
const Toast = React.forwardRef<
41+
React.ElementRef<typeof ToastPrimitives.Root>,
42+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
43+
VariantProps<typeof toastVariants>
44+
>(({ className, variant, ...props }, ref) => {
45+
return (
46+
<ToastPrimitives.Root
47+
ref={ref}
48+
className={cn(toastVariants({ variant }), className)}
49+
{...props}
50+
/>
51+
)
52+
})
53+
Toast.displayName = ToastPrimitives.Root.displayName
54+
55+
const ToastAction = React.forwardRef<
56+
React.ElementRef<typeof ToastPrimitives.Action>,
57+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
58+
>(({ className, ...props }, ref) => (
59+
<ToastPrimitives.Action
60+
ref={ref}
61+
className={cn(
62+
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
63+
className,
64+
)}
65+
{...props}
66+
/>
67+
))
68+
ToastAction.displayName = ToastPrimitives.Action.displayName
69+
70+
const ToastClose = React.forwardRef<
71+
React.ElementRef<typeof ToastPrimitives.Close>,
72+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
73+
>(({ className, ...props }, ref) => (
74+
<ToastPrimitives.Close
75+
ref={ref}
76+
className={cn(
77+
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
78+
className,
79+
)}
80+
toast-close=""
81+
{...props}
82+
>
83+
<Icon name="cross-1" />
84+
</ToastPrimitives.Close>
85+
))
86+
ToastClose.displayName = ToastPrimitives.Close.displayName
87+
88+
const ToastTitle = React.forwardRef<
89+
React.ElementRef<typeof ToastPrimitives.Title>,
90+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
91+
>(({ className, ...props }, ref) => (
92+
<ToastPrimitives.Title
93+
ref={ref}
94+
className={cn('text-sm font-semibold', className)}
95+
{...props}
96+
/>
97+
))
98+
ToastTitle.displayName = ToastPrimitives.Title.displayName
99+
100+
const ToastDescription = React.forwardRef<
101+
React.ElementRef<typeof ToastPrimitives.Description>,
102+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
103+
>(({ className, ...props }, ref) => (
104+
<ToastPrimitives.Description
105+
ref={ref}
106+
className={cn('text-sm opacity-90', className)}
107+
{...props}
108+
/>
109+
))
110+
ToastDescription.displayName = ToastPrimitives.Description.displayName
111+
112+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
113+
114+
type ToastActionElement = React.ReactElement<typeof ToastAction>
115+
116+
export {
117+
type ToastProps,
118+
type ToastActionElement,
119+
ToastProvider,
120+
ToastViewport,
121+
Toast,
122+
ToastTitle,
123+
ToastDescription,
124+
ToastClose,
125+
ToastAction,
126+
}

app/components/ui/toaster.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
Toast,
3+
ToastClose,
4+
ToastDescription,
5+
ToastProvider,
6+
ToastTitle,
7+
ToastViewport,
8+
} from '~/components/ui/toast.tsx'
9+
import { useToast } from '~/components/ui/use-toast.ts'
10+
11+
export function Toaster() {
12+
const { toasts } = useToast()
13+
14+
return (
15+
<ToastProvider>
16+
{toasts.map(function ({ id, title, description, action, ...props }) {
17+
return (
18+
<Toast key={id} {...props}>
19+
<div className="grid gap-1">
20+
{title && <ToastTitle>{title}</ToastTitle>}
21+
{description && (
22+
<ToastDescription>{description}</ToastDescription>
23+
)}
24+
</div>
25+
{action}
26+
<ToastClose />
27+
</Toast>
28+
)
29+
})}
30+
<ToastViewport />
31+
</ToastProvider>
32+
)
33+
}

app/components/ui/tooltip.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import * as React from "react"
2-
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
1+
import * as React from 'react'
2+
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
33

4-
import { cn } from "~/utils/misc.ts"
4+
import { cn } from '~/utils/misc.ts'
55

66
const TooltipProvider = TooltipPrimitive.Provider
77

@@ -10,18 +10,18 @@ const Tooltip = TooltipPrimitive.Root
1010
const TooltipTrigger = TooltipPrimitive.Trigger
1111

1212
const TooltipContent = React.forwardRef<
13-
React.ElementRef<typeof TooltipPrimitive.Content>,
14-
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
13+
React.ElementRef<typeof TooltipPrimitive.Content>,
14+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
1515
>(({ className, sideOffset = 4, ...props }, ref) => (
16-
<TooltipPrimitive.Content
17-
ref={ref}
18-
sideOffset={sideOffset}
19-
className={cn(
20-
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
21-
className
22-
)}
23-
{...props}
24-
/>
16+
<TooltipPrimitive.Content
17+
ref={ref}
18+
sideOffset={sideOffset}
19+
className={cn(
20+
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
21+
className,
22+
)}
23+
{...props}
24+
/>
2525
))
2626
TooltipContent.displayName = TooltipPrimitive.Content.displayName
2727

0 commit comments

Comments
 (0)