framer-motion#useReducedMotion TypeScript Examples
The following examples show how to use
framer-motion#useReducedMotion.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: LinearProgress.tsx From chroma-react with MIT License | 6 votes |
LinearProgress: React.FC<LinearProgressProps> = ({
className,
variant = 'determinate',
value = 0,
...rootProps
}) => {
const classes = useStyles({});
const shouldReduceMotion = useReducedMotion();
return (
<div
className={clsx(classes.root, className)}
role="progressbar"
aria-valuenow={variant === 'determinate' ? value : undefined}
aria-valuemin={variant === 'determinate' ? 0 : undefined}
aria-valuemax={variant === 'determinate' ? 100 : undefined}
{...rootProps}
>
<div
className={clsx(
classes.bar,
variant === 'indeterminate' && classes.indeterminate,
shouldReduceMotion && classes.indeterminateReduced
)}
style={
variant === 'determinate'
? { transform: `translateX(-${100 - value}%)` }
: undefined
}
/>
</div>
);
}
Example #2
Source File: parallax.tsx From samuelkraft-next with MIT License | 6 votes |
Parallax = ({ children, offset = 50, clampInitial, clampFinal }: ParallaxProps): JSX.Element => {
const prefersReducedMotion = useReducedMotion()
const [elementTop, setElementTop] = useState(0)
const [clientHeight, setClientHeight] = useState(0)
const ref = useRef(null)
const { scrollY } = useViewportScroll()
const initial = elementTop - clientHeight
const final = elementTop + offset
const yRange = useTransform(scrollY, [initial, final], [clampInitial ? 0 : offset, clampFinal ? 0 : -offset])
const y = useSpring(yRange, { stiffness: 400, damping: 90 })
useLayoutEffect(() => {
const element = ref.current
const onResize = () => {
setElementTop(element.getBoundingClientRect().top + window.scrollY || window.pageYOffset)
setClientHeight(window.innerHeight)
}
onResize()
window.addEventListener('resize', onResize)
return () => window.removeEventListener('resize', onResize)
}, [ref])
// Don't parallax if the user has "reduced motion" enabled
if (prefersReducedMotion) {
return <>{children}</>
}
return (
<motion.div ref={ref} style={{ y }}>
{children}
</motion.div>
)
}
Example #3
Source File: Popover.tsx From chroma-react with MIT License | 5 votes |
Popover: React.FC<PopoverProps> = ({
'aria-label': ariaLabel,
anchorElement,
children,
className,
gutter,
placement = 'bottom',
title,
usePortal = false,
unstable_offset,
...rootProps
}) => {
const classes = useStyles({});
const shouldReduceMotion = useReducedMotion();
const popover = usePopoverState({ placement, gutter, unstable_offset });
return (
<>
<PopoverDisclosure {...popover} {...anchorElement.props}>
{(disclosureProps) =>
React.cloneElement(
React.Children.only(anchorElement),
disclosureProps
)
}
</PopoverDisclosure>
<ConditionalWrapper
condition={usePortal}
wrapper={(children: React.ReactNode) => <Portal>{children}</Portal>}
>
<ReakitPopover
{...popover}
aria-label={ariaLabel}
as={motion.div}
className={clsx(classes.root, className)}
aria-hidden={popover.visible ? undefined : 'true'}
animate={
popover.visible
? shouldReduceMotion
? { opacity: 1 }
: {
opacity: 1,
transition: { duration: 0.3 },
}
: shouldReduceMotion
? { opacity: 0 }
: {
opacity: 0,
transition: { duration: 0.1 },
}
}
{...rootProps}
>
{!!title && (
<Text className={classes.title} weight="bold" size="subbody">
{title}
</Text>
)}
{typeof children === 'function'
? children({ popover } as PopoverRenderProps)
: children}
</ReakitPopover>
</ConditionalWrapper>
</>
);
}
Example #4
Source File: Tooltip.tsx From chroma-react with MIT License | 5 votes |
Tooltip: React.FC<TooltipProps> = ({
children,
className,
defaultVisible = false,
placement = 'bottom',
title = '',
gutter,
...rootProps
}) => {
const classes = useStyles({});
const shouldReduceMotion = useReducedMotion();
const tooltip = useTooltipState({
placement,
visible: defaultVisible,
gutter,
});
React.useEffect(() => {
tooltip.place(placement);
}, [placement, tooltip]);
return (
<>
<TooltipReference {...tooltip}>
{(referenceProps) =>
React.cloneElement(React.Children.only(children), referenceProps)
}
</TooltipReference>
<Portal>
<ReakitTooltip
{...tooltip}
as={motion.div}
className={clsx(classes.root, className)}
animate={
tooltip.visible
? shouldReduceMotion
? { opacity: 1 }
: {
opacity: 1,
transition: { delay: 0.75 },
}
: {
opacity: 0,
}
}
{...rootProps}
>
{title}
</ReakitTooltip>
</Portal>
</>
);
}
Example #5
Source File: Parallax.tsx From vignette-web with MIT License | 5 votes |
Parallax = ({
children,
id,
offset = 30,
className,
fadeIn,
}: ParallaxProps): JSX.Element => {
const prefersReducedMotion = useReducedMotion()
const [elementTop, setElementTop] = useState(0)
const [clientHeight, setClientHeight] = useState(0)
const ref = useRef<HTMLDivElement>(null)
const { scrollY } = useViewportScroll()
const initial = elementTop - clientHeight
const final = elementTop + offset
const router = useRouter()
const yRange = useTransform(scrollY, [initial, final], [offset, -offset], {
clamp: true,
})
const y = useSpring(yRange, { stiffness: 400, damping: 90 })
useEffect(() => {
const element = ref.current
const onResize = () => {
if (element) {
setElementTop(
element.getBoundingClientRect().top + window.scrollY ||
window.pageYOffset,
)
}
setClientHeight(window.innerHeight)
}
onResize()
window.addEventListener(`resize`, onResize)
return () => window.removeEventListener(`resize`, onResize)
}, [ref, router.pathname])
// Don't parallax if the user has "reduced motion" enabled
if (prefersReducedMotion) {
return <>{children}</>
}
return (
<motion.div
id={id}
className={className}
ref={ref}
style={{ y }}
transition={fadeIn ? { delay: 0.15, duration: 0.3 } : {}}
initial={fadeIn && { opacity: 0 }}
whileInView={fadeIn ? { opacity: 1 } : {}}
viewport={{ once: true }}
>
{children}
</motion.div>
)
}
Example #6
Source File: Menu.tsx From chroma-react with MIT License | 4 votes |
Menu: React.FC<MenuProps> = ({
'aria-label': ariaLabel,
anchorElement,
children,
className,
gutter,
items,
placement = 'bottom',
title,
usePortal = false,
...rootProps
}) => {
const classes = useStyles({});
const shouldReduceMotion = useReducedMotion();
const menu = useMenuState({ placement, gutter });
const handleStopPropagation = (e: React.SyntheticEvent) =>
e.stopPropagation();
return (
<>
<MenuButton
{...menu}
{...anchorElement.props}
onClick={handleStopPropagation}
>
{(disclosureProps) =>
React.cloneElement(
React.Children.only(anchorElement),
disclosureProps
)
}
</MenuButton>
<ConditionalWrapper
condition={usePortal}
wrapper={(children: React.ReactNode) => <Portal>{children}</Portal>}
>
<ReakitMenu
{...menu}
as={motion.div}
aria-label={ariaLabel}
className={clsx(classes.root, className)}
animate={
menu.visible
? shouldReduceMotion
? { opacity: 1 }
: {
opacity: 1,
transition: { duration: 0.3 },
}
: shouldReduceMotion
? { opacity: 0 }
: {
opacity: 0,
transition: { duration: 0.1 },
}
}
{...rootProps}
>
{!!title && (
<Text className={classes.title} weight="bold" size="subbody">
{title}
</Text>
)}
{items &&
items.map((item, i) =>
isMenuItemElement(item) ? (
<ReakitMenuItem
{...menu}
{...item.props}
key={`item-${i}`}
onClick={(e: any) => {
menu.hide();
item.props.onClick && item.props.onClick(e);
}}
>
{(itemProps) =>
React.cloneElement(React.Children.only(item), itemProps)
}
</ReakitMenuItem>
) : (
item
)
)}
</ReakitMenu>
</ConditionalWrapper>
</>
);
}
Example #7
Source File: Modal.tsx From chroma-react with MIT License | 4 votes |
Content = React.forwardRef<HTMLDivElement, ModalProps>(
(
{
actions,
className,
children,
contentClassName,
fullWidth,
justifyActions,
isFormContent,
onFormSubmit,
onClick,
onDismiss,
customHeader,
size,
title,
poses = {},
...rootProps
},
ref
) => {
const classes = useStyles({});
const shouldReduceMotion = useReducedMotion();
const poseVariants = {
init: poses.init || { y: 50, scale: 0.3 },
open: poses.open || { y: 0, scale: 1 },
exit: poses.exit || { scale: 0.5, transition: { duration: 0.15 } },
};
return (
<motion.div
className={clsx(
classes.content,
fullWidth && classes.contentFullWidth,
className
)}
role="dialog"
aria-modal="true"
aria-describedby={ariaDescribedBy}
aria-labelledby={ariaLabelledBy}
tabIndex={-1}
onClick={composeEventHandlers([
onClick,
(event) => {
event.stopPropagation();
},
])}
ref={ref}
variants={poseVariants}
initial={shouldReduceMotion ? {} : 'init'}
animate={shouldReduceMotion ? {} : 'open'}
exit={shouldReduceMotion ? {} : 'exit'}
{...rootProps}
>
{customHeader ? (
customHeader
) : (
<div
id={ariaLabelledBy}
className={clsx(classes.modalHeader, classes.verticalPadding)}
>
{!!title && (
<Text size="subbody" weight="bold">
{title}
</Text>
)}
<IconButton
aria-label="Close open modal"
icon={X}
size={0}
tabIndex={0}
onClick={onDismiss}
paddingRight={0}
/>
</div>
)}
<ConditionalWrapper
condition={Boolean(isFormContent && onFormSubmit)}
wrapper={(children: React.ReactNode) => (
<form onSubmit={onFormSubmit}>{children}</form>
)}
>
{children && (
<div
id={ariaDescribedBy}
className={clsx(
classes.modalChildrenContainer,
classes.verticalPadding,
{
[classes.contentSize0]: size === 0,
[classes.contentSize1]: size === 1,
},
contentClassName
)}
>
{children}
</div>
)}
{!!actions && (
<ModalActions
className={clsx(classes.modalActions, classes.verticalPadding)}
justify={justifyActions}
>
{actions}
</ModalActions>
)}
</ConditionalWrapper>
</motion.div>
);
}
)
Example #8
Source File: ComboBox.tsx From chroma-react with MIT License | 4 votes |
ComboBox: React.FC<ComboBoxProps> = ({
['aria-label']: ariaLabel,
children,
className,
color = 'default',
errorMessage,
fullWidth,
hasError,
helpMessage,
id,
label,
secondaryLabel,
onChange,
placeholder,
placement,
popoverAriaLabel,
selectedOptionDisplay,
value,
...rootProps
}) => {
const classes = useStyles({});
const popover = usePopoverState({ placement });
const rover = useRoverState({ loop: true, orientation: 'vertical' });
const shouldReduceMotion = useReducedMotion();
const buttonRef = React.useRef<any>();
// This width is used to determine the popover width when open,
// as well as setting the `maxWidth` of the text inside to ensure
// the element size doesn't shift when the selected option text
// gets long.
const [width, setWidth] = React.useState<number>(50);
const [internalSelections, setInternalSelections] = React.useState<Array<
SelectOptionProps
> | null>(null);
// A uniqueId is required to wire up aria-attributes
const [uniqueId] = React.useState<string>(
() => id || name || generateUniqueId('combobox-')
);
// Update the opened popover width when the window width changes
// TODO: Review the best way to handle this without breaking
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => {
setWidth(buttonRef.current.getBoundingClientRect().width);
});
// Determine which of the child select option values are matches based on
// the "value" prop. If a select option value is included in the "value",
// then we add it to the list of internal select options (which keep
// track of which options are selected to apply classes)
React.useEffect(() => {
const matches: any = React.Children.toArray(children).filter((child) => {
if (!React.isValidElement(child)) {
return null;
}
return value?.includes(child.props.value);
});
if (matches.length === 0) {
setInternalSelections([]);
return;
}
setInternalSelections(matches.map((m: any) => m.props));
}, [children, value]);
const handleOptionSelected = (optionValue: string, meta: any) => {
const isAlreadySelected = internalSelections?.find(
(s) => s.value === optionValue
);
if (isAlreadySelected) {
// Filter out the option the user selected from our list of internal selections
// (so that it's removed), and then pass the values and meta arrays back
// to the consumer.
const filteredSelections =
internalSelections?.filter((s) => s.value !== optionValue) || [];
onChange?.(
[...filteredSelections?.map((s) => s.value)],
[...filteredSelections?.map((s) => s.meta)]
);
return;
}
// We are adding a selected option, so we collect all of the values
// and meta arrays, add in our new selection, and return the updated
// list to the consumer
const valueOptions = internalSelections?.map((s) => s.value) || [];
const metaOptions = internalSelections?.map((s) => s.meta) || [];
onChange?.([...valueOptions, optionValue], [...metaOptions, meta]);
};
if (!label && !ariaLabel && process.env.NODE_ENV === 'development') {
throw new Error(
'If a "label" is not provided to ComboBox, please provide "aria-label".'
);
}
return (
<div className={clsx(classes.root, className)}>
<label
aria-hidden="true"
className={clsx(
classes.label,
color === 'inverse' && classes.labelInverse,
!label && ariaLabel && classes.srOnly
)}
htmlFor={uniqueId}
>
{label || ariaLabel}
{secondaryLabel ? (
<span
className={clsx(
classes.labelSecondary,
color === 'inverse' && classes.labelInverse
)}
>
{secondaryLabel}
</span>
) : null}
</label>
<PopoverDisclosure
className={clsx(
classes.button,
classes.comboxBoxOverflow,
hasError && classes.buttonError,
fullWidth && classes.buttonFullWidth,
{
[classes.buttonInverse]: color === 'inverse',
}
)}
ref={buttonRef}
aria-describedby={buildDescribedBy({
hasError,
hasHelpMessage: !!helpMessage,
uniqueId,
})}
id={uniqueId}
{...popover}
{...rootProps}
>
<>
{internalSelections?.length === 0 && (
<motion.div
initial={{ opacity: 0 }}
animate={{
opacity: 1,
transition: { duration: 0.2, ease: 'easeIn' },
}}
>
<Text
className={clsx(
classes.buttonText,
classes.placeholderText,
classes.comboBoxPlaceholder
)}
size="subbody"
>
{placeholder}
</Text>
</motion.div>
)}
{internalSelections &&
internalSelections?.length > 0 &&
!selectedOptionDisplay && (
<Text
className={clsx(classes.buttonText, classes.chipList)}
size="subbody"
style={fullWidth ? {} : { maxWidth: width - 42 }}
>
{internalSelections?.map((selectedOption, index) => (
<motion.span
key={index}
className={classes.chip}
initial={
shouldReduceMotion
? { opacity: 0 }
: { opacity: 0, y: -8 }
}
animate={
shouldReduceMotion
? { opacity: 1 }
: {
opacity: 1,
transition: { duration: 0.2, ease: 'easeOut' },
y: 0,
}
}
>
{selectedOption.title}
</motion.span>
))}
</Text>
)}
{internalSelections &&
internalSelections?.length > 0 &&
selectedOptionDisplay && (
<Text className={classes.buttonText} size="subbody">
{selectedOptionDisplay(internalSelections)}
</Text>
)}
<div className={classes.buttonArrowContainer} role="presentation">
<ChevronDown
className={clsx(
classes.arrowIcon,
popover.visible && classes.rotate
)}
aria-hidden
role="img"
width={18}
height={18}
/>
</div>
</>
</PopoverDisclosure>
{helpMessage && (
<FormHelpMessage
className={classes.message}
color={color}
rootElementId={uniqueId}
describedById={helpFor(uniqueId)}
>
{helpMessage}
</FormHelpMessage>
)}
{hasError && (
<FormErrorMessage
className={classes.message}
color={color}
rootElementId={uniqueId}
describedById={errorFor(uniqueId)}
>
{errorMessage}
</FormErrorMessage>
)}
{/*
A few things here:
1) We want to always portal our select menu results so we don't
run into weird layout issues (rendering inside Popover or Modal)
2) We need to trap focus inside of the menu when it is open - <FocusLock /> handles this for us
3) We need keyboard support via <Rover />
Reference for #1 & #2: https://github.com/reakit/reakit/issues/566
*/}
<Portal>
<FocusLock>
<ReakitPopover
aria-label={label || ariaLabel || popoverAriaLabel}
className={classes.popover}
{...popover}
style={{ width }}
as={motion.div}
animate={popover.visible ? 'open' : 'closed'}
variants={
shouldReduceMotion ? popoverVariantsReduced : popoverVariants
}
>
<motion.ul
className={classes.ul}
variants={
shouldReduceMotion
? listMotionVariantsReduced
: listMotionVariants
}
role="listbox"
aria-multiselectable={true}
>
{React.Children.map(children, (child) => {
if (!React.isValidElement(child)) {
return null;
}
const option: React.ReactElement<SelectOptionProps> = child;
return (
<Rover
{...rover}
aria-selected={value?.includes(option?.props?.value)}
className={classes.option}
as={motion.li}
disabled={child?.props?.disabled}
role="option"
value={option?.props?.value}
variants={
shouldReduceMotion
? listItemMotionVariantsReduced
: listItemMotionVariants
}
onClick={() =>
handleOptionSelected(
option?.props?.value,
option?.props?.meta
)
}
>
{React.cloneElement<SelectOptionProps>(option, {
isChecked: value?.includes(option?.props?.value),
...option.props,
})}
</Rover>
);
})}
</motion.ul>
</ReakitPopover>
</FocusLock>
</Portal>
</div>
);
}
Example #9
Source File: Select.tsx From chroma-react with MIT License | 4 votes |
Select: React.FC<SelectProps> = ({
['aria-label']: ariaLabel,
children,
className,
color = 'default',
errorMessage,
fullWidth,
hasError,
helpMessage,
icon: Icon,
id,
label,
secondaryLabel,
onChange,
placeholder,
placement,
popoverAriaLabel,
selectedOptionDisplay,
tooltipMessage,
value,
...rootProps
}) => {
const classes = useStyles({});
const popover = usePopoverState({ placement });
const shouldReduceMotion = useReducedMotion();
const buttonRef = React.useRef<any>(); // TODO: Need to type this properly...
const [width, setWidth] = React.useState<number>(50);
const [
internalSelection,
setInternalSelection,
] = React.useState<SelectOptionProps | null>(null);
const [uniqueId] = React.useState<string>(
() => id || name || generateUniqueId('select-')
);
const rover = useRoverState({ loop: true, orientation: 'vertical' });
// Update the opened popover width any time the button width changes
// TODO: Review the best way to handle this without breaking
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => {
setWidth(buttonRef.current.getBoundingClientRect().width);
});
React.useEffect(() => {
// TODO: Need to type this properly as well...
const match: any = React.Children.toArray(children).find((child) => {
if (!React.isValidElement(child)) {
return null;
}
if (isHeadingElement(child)) {
return null;
}
return child.props.value === value;
});
// If there is no match, set internalSelection to null so placeholder will be shown
setInternalSelection(match ? match.props : null);
}, [children, value]);
const hidePopover = popover.hide;
const handleOptionSelected = React.useCallback(
(optionValue: string, meta: any) => {
hidePopover();
onChange?.(optionValue, meta);
},
[hidePopover, onChange]
);
if (!label && !ariaLabel && process.env.NODE_ENV === 'development') {
throw new Error(
'If a "label" is not provided to Select, please provide "aria-label".'
);
}
return (
<div className={clsx(classes.root, className)}>
<label
aria-hidden="true"
className={clsx(
classes.label,
color === 'inverse' && classes.labelInverse,
!label && ariaLabel && classes.srOnly
)}
htmlFor={uniqueId}
>
{label || ariaLabel}
{!!Icon && tooltipMessage && (
<Tooltip title={tooltipMessage}>
<span className={classes.tooltipContainer}>
<Icon
className={clsx(
classes.labelIcon,
color === 'inverse' && classes.labelIconInverse
)}
width={16}
height={16}
role="img"
aria-hidden
/>
</span>
</Tooltip>
)}
{secondaryLabel ? (
<span
className={clsx(
classes.labelSecondary,
color === 'inverse' && classes.labelInverse
)}
>
{secondaryLabel}
</span>
) : null}
</label>
<PopoverDisclosure
className={clsx(
classes.button,
hasError && classes.buttonError,
fullWidth && classes.buttonFullWidth,
{
[classes.buttonInverse]: color === 'inverse',
}
)}
ref={buttonRef}
aria-describedby={buildDescribedBy({
hasError,
hasHelpMessage: !!helpMessage,
uniqueId,
})}
id={uniqueId}
{...popover}
{...rootProps}
>
<>
{!internalSelection && (
<Text
className={clsx(classes.placeholderText, classes.buttonText)}
size="subbody"
{...getTestProps(testIds.placeholderText)}
>
{placeholder}
</Text>
)}
{internalSelection && !selectedOptionDisplay && (
<Text className={classes.buttonText} size="subbody">
{internalSelection.title}
</Text>
)}
{internalSelection && selectedOptionDisplay && (
<Text className={classes.buttonText} size="subbody">
{selectedOptionDisplay(internalSelection)}
</Text>
)}
<div className={classes.buttonArrowContainer} role="presentation">
<ChevronDown
className={clsx(
classes.arrowIcon,
popover.visible && classes.rotate
)}
aria-hidden
role="img"
width={18}
height={18}
/>
</div>
</>
</PopoverDisclosure>
{helpMessage && (
<FormHelpMessage
className={classes.message}
color={color}
rootElementId={uniqueId}
describedById={helpFor(uniqueId)}
>
{helpMessage}
</FormHelpMessage>
)}
{hasError && (
<FormErrorMessage
className={classes.message}
color={color}
rootElementId={uniqueId}
describedById={errorFor(uniqueId)}
>
{errorMessage}
</FormErrorMessage>
)}
{/*
A few things here:
1) We want to always portal our select menu results so we don't
run into weird layout issues (rendering inside Popover or Modal)
2) We need to trap focus inside of the menu when it is open - <FocusLock /> handles this for us
3) We need keyboard support via <Rover />
Reference for #1 & #2: https://github.com/reakit/reakit/issues/566
*/}
<Portal>
<FocusLock>
<ReakitPopover
aria-label={label || ariaLabel || popoverAriaLabel}
className={classes.popover}
{...popover}
style={{ width }}
as={motion.div}
animate={popover.visible ? 'open' : 'closed'}
variants={
shouldReduceMotion ? popoverVariantsReduced : popoverVariants
}
>
<motion.ul
className={classes.ul}
role="listbox"
variants={
shouldReduceMotion
? listMotionVariantsReduced
: listMotionVariants
}
>
{popover.visible &&
React.Children.map(children, (child) => {
if (!React.isValidElement(child)) {
return null;
}
if (isHeadingElement(child)) {
return child;
}
const option: React.ReactElement<SelectOptionProps> = child;
return (
<RoverOption
rover={rover}
value={value}
option={option}
handleOptionSelect={handleOptionSelected}
variants={
shouldReduceMotion
? listItemMotionVariantsReduced
: listItemMotionVariants
}
disabled={child?.props?.disabled}
/>
);
})}
</motion.ul>
</ReakitPopover>
</FocusLock>
</Portal>
</div>
);
}
Example #10
Source File: Snackbar.tsx From chroma-react with MIT License | 4 votes |
Snackbar: React.FC<SnackbarProps> = React.forwardRef<
HTMLDivElement,
SnackbarProps
>(
(
{
className,
duration = 6000,
icon: Icon,
isOpen = false,
allowDismiss = false,
onClose,
role = 'status',
statusType = 'info',
title,
children,
...rootProps
},
ref
) => {
const classes = useStyles({});
const shouldReduceMotion = useReducedMotion();
const [snackbarTimeout, setSnackbarTimeout] = React.useState<number | null>(
duration
);
// Event handlers
const onMouseEnter = () => setSnackbarTimeout(null);
const onMouseLeave = () => setSnackbarTimeout(duration);
const closeSnackbar = React.useCallback(() => {
onClose && onClose();
}, [onClose]);
// Use a ref to close our Snackbar after the timeout
const callbackRef = React.useRef<() => void | null>();
React.useEffect(() => {
if (!callbackRef.current) {
callbackRef.current = closeSnackbar;
}
}, [closeSnackbar]);
React.useEffect(() => {
// Ignore setting up a timer for the Snackbar
// if one is not isOpen.
if (!isOpen) {
return;
}
const tick = () => {
if (callbackRef.current) {
callbackRef.current();
}
};
if (snackbarTimeout) {
const id = setTimeout(tick, snackbarTimeout);
return () => clearTimeout(id);
}
}, [snackbarTimeout, isOpen]);
return (
<AnimatePresence initial={false}>
{isOpen ? (
<motion.div
ref={ref}
className={clsx(
classes.root,
{
[classes.infoModifier]: statusType === 'info',
[classes.errorModifier]: statusType === 'error',
[classes.warningModifier]: statusType === 'warning',
[classes.successModifier]: statusType === 'success',
},
className
)}
aria-live={role === 'alert' ? 'assertive' : 'polite'}
role={role}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
positionTransition
initial={
shouldReduceMotion ? { opacity: 0 } : { opacity: 0, y: -40 }
}
animate={
shouldReduceMotion
? { opacity: 1 }
: {
opacity: 1,
y: 0,
}
}
exit={
shouldReduceMotion
? { opacity: 0 }
: {
opacity: 0,
y: 60,
transition: { duration: 0.25, ease: 'easeIn' },
}
}
{...rootProps}
>
{!!Icon && <Icon role="img" aria-hidden className={classes.icon} />}
{children ? (
children
) : (
<Text className={classes.title}>{title}</Text>
)}
{allowDismiss && (
<>
<IconButton
className={classes.closeButton}
aria-label="Close Notification"
size={0}
paddingTop={0}
paddingBottom={0}
paddingRight={0}
icon={X}
onClick={closeSnackbar}
/>
</>
)}
</motion.div>
) : null}
</AnimatePresence>
);
}
)