11import * as React from 'react' ;
2- import { styled , keyframes } from 'goober' ;
2+ import { styled } from 'goober' ;
33
44import { Toast , ToastPosition , resolveValue , Renderable } from '../core/types' ;
55import { ToastIcon } from './toast-icon' ;
66import { prefersReducedMotion } from '../core/utils' ;
77
8- const enterAnimation = ( factor : number ) => `
9- 0% {transform: translate3d(0,${ factor * - 200 } %,0) scale(.6); opacity:.5;}
10- 100% {transform: translate3d(0,0,0) scale(1); opacity:1;}
11- ` ;
12-
13- const exitAnimation = ( factor : number ) => `
14- 0% {transform: translate3d(0,0,-1px) scale(1); opacity:1;}
15- 100% {transform: translate3d(0,${ factor * - 150 } %,-1px) scale(.6); opacity:0;}
16- ` ;
17-
18- const fadeInAnimation = `0%{opacity:0;} 100%{opacity:1;}` ;
19- const fadeOutAnimation = `0%{opacity:1;} 100%{opacity:0;}` ;
20-
218// Use :where() for zero specificity - allows Tailwind to override easily
229const ToastBarBase = styled ( 'div' ) `
2310 :where(&) {
@@ -33,6 +20,84 @@ const ToastBarBase = styled('div')`
3320 padding: 8px 10px;
3421 border-radius: 8px;
3522 }
23+
24+ @keyframes rht-enter-from-top {
25+ 0% { transform: translate3d(0, -200%, 0) scale(.6); opacity: .5; }
26+ 100% { transform: translate3d(0, 0, 0) scale(1); opacity: 1; }
27+ }
28+
29+ @keyframes rht-enter-from-bottom {
30+ 0% { transform: translate3d(0, 200%, 0) scale(.6); opacity: .5; }
31+ 100% { transform: translate3d(0, 0, 0) scale(1); opacity: 1; }
32+ }
33+
34+ @keyframes rht-exit-to-top {
35+ 0% { transform: translate3d(0, 0, -1px) scale(1); opacity: 1; }
36+ 100% { transform: translate3d(0, -150%, -1px) scale(.6); opacity: 0; }
37+ }
38+
39+ @keyframes rht-exit-to-bottom {
40+ 0% { transform: translate3d(0, 0, -1px) scale(1); opacity: 1; }
41+ 100% { transform: translate3d(0, 150%, -1px) scale(.6); opacity: 0; }
42+ }
43+
44+ @keyframes rht-fade-in {
45+ 0% { opacity: 0; }
46+ 100% { opacity: 1; }
47+ }
48+
49+ @keyframes rht-fade-out {
50+ 0% { opacity: 1; }
51+ 100% { opacity: 0; }
52+ }
53+
54+ &.rht-enter-from-top {
55+ animation: rht-enter-from-top 0.35s cubic-bezier(.21,1.02,.73,1) forwards;
56+ }
57+
58+ &.rht-enter-from-bottom {
59+ animation: rht-enter-from-bottom 0.35s cubic-bezier(.21,1.02,.73,1) forwards;
60+ }
61+
62+ &.rht-exit-to-top {
63+ animation: rht-exit-to-top 0.4s forwards cubic-bezier(.06,.71,.55,1);
64+ }
65+
66+ &.rht-exit-to-bottom {
67+ animation: rht-exit-to-bottom 0.4s forwards cubic-bezier(.06,.71,.55,1);
68+ }
69+
70+ &.rht-fade-in {
71+ animation: rht-fade-in 0.35s cubic-bezier(.21,1.02,.73,1) forwards;
72+ }
73+
74+ &.rht-fade-out {
75+ animation: rht-fade-out 0.4s forwards cubic-bezier(.06,.71,.55,1);
76+ }
77+
78+ &.rht-invisible {
79+ opacity: 0;
80+ }
81+
82+ &.rht-success {
83+ background: var(--rht-success-bg, #ecfdf5);
84+ color: var(--rht-success-fg, #065f46);
85+ }
86+
87+ &.rht-error {
88+ background: var(--rht-error-bg, #fef2f2);
89+ color: var(--rht-error-fg, #991b1b);
90+ }
91+
92+ &.rht-loading {
93+ background: var(--rht-loading-bg, #fff);
94+ color: var(--rht-loading-fg, #363636);
95+ }
96+
97+ &.rht-blank {
98+ background: var(--rht-blank-bg, #fff);
99+ color: var(--rht-blank-fg, #363636);
100+ }
36101` ;
37102
38103const Message = styled ( 'div' ) `
@@ -54,32 +119,36 @@ interface ToastBarProps {
54119 } ) => Renderable ;
55120}
56121
57- const getAnimationStyle = (
122+ const getAnimationClass = (
58123 position : ToastPosition ,
59- visible : boolean
60- ) : React . CSSProperties => {
124+ visible : boolean ,
125+ hasHeight : boolean
126+ ) : string => {
127+ if ( ! hasHeight ) {
128+ return 'rht-invisible' ;
129+ }
130+
61131 const top = position . includes ( 'top' ) ;
62- const factor = top ? 1 : - 1 ;
132+ const reduced = prefersReducedMotion ( ) ;
63133
64- const [ enter , exit ] = prefersReducedMotion ( )
65- ? [ fadeInAnimation , fadeOutAnimation ]
66- : [ enterAnimation ( factor ) , exitAnimation ( factor ) ] ;
134+ if ( reduced ) {
135+ return visible ? 'rht-fade-in' : 'rht-fade-out' ;
136+ }
137+
138+ if ( visible ) {
139+ return top ? 'rht-enter-from-top' : 'rht-enter-from-bottom' ;
140+ }
67141
68- return {
69- animation : visible
70- ? `${ keyframes ( enter ) } 0.35s cubic-bezier(.21,1.02,.73,1) forwards`
71- : `${ keyframes ( exit ) } 0.4s forwards cubic-bezier(.06,.71,.55,1)` ,
72- } ;
142+ return top ? 'rht-exit-to-top' : 'rht-exit-to-bottom' ;
73143} ;
74144
75145export const ToastBar : React . FC < ToastBarProps > = React . memo (
76146 ( { toast, position, style, children } ) => {
77- const animationStyle : React . CSSProperties = toast . height
78- ? getAnimationStyle (
79- toast . position || position || 'top-center' ,
80- toast . visible
81- )
82- : { opacity : 0 } ;
147+ const animationClass = getAnimationClass (
148+ toast . position || position || 'top-center' ,
149+ toast . visible ,
150+ ! ! toast . height
151+ ) ;
83152
84153 const icon = < ToastIcon toast = { toast } /> ;
85154 const message = (
@@ -88,11 +157,18 @@ export const ToastBar: React.FC<ToastBarProps> = React.memo(
88157 </ Message >
89158 ) ;
90159
160+ const className = [
161+ toast . className ,
162+ animationClass ,
163+ toast . type ? `rht-${ toast . type } ` : null ,
164+ ]
165+ . filter ( Boolean )
166+ . join ( ' ' ) ;
167+
91168 return (
92169 < ToastBarBase
93- className = { toast . className }
170+ className = { className }
94171 style = { {
95- ...animationStyle ,
96172 ...style ,
97173 ...toast . style ,
98174 } }
0 commit comments