diff --git a/src/core/components/toast/styles.ts b/src/core/components/toast/styles.ts new file mode 100644 index 000000000..55b7bc9f2 --- /dev/null +++ b/src/core/components/toast/styles.ts @@ -0,0 +1,61 @@ +import {styled, keyframes, css} from 'styled-components' +import {ThemeColorStateToneKey, getTheme_v2} from '../../../theme' +import {POPOVER_MOTION_CONTENT_OPACITY_PROPERTY} from '../../constants' +import {Flex} from '../../primitives' +import {ThemeProps} from '../../styles' + +export const TextBox = styled(Flex)` + overflow-x: auto; +` + +const loadingAnimation = keyframes` + 0% { + width: 0; + } + 100% { + width: 100%; + } +` + +const LOADING_BAR_HEIGHT = 2 + +export function rootStyles( + props: {$duration?: number; tone: ThemeColorStateToneKey} & ThemeProps, +): ReturnType { + const {color} = getTheme_v2(props.theme) + + const loadingBarColor = color.button.default[props.tone].enabled.bg + + if (!props.$duration) + return css` + pointer-events: all; + & > * { + opacity: var(${POPOVER_MOTION_CONTENT_OPACITY_PROPERTY}, 1); + will-change: opacity; + } + ` + + return css` + pointer-events: all; + width: 100%; + position: relative; + overflow: hidden; + overflow: clip; + padding-bottom: ${LOADING_BAR_HEIGHT}px; + &::before { + content: ''; + position: absolute; + bottom: 0px; + height: ${LOADING_BAR_HEIGHT}px; + background: ${loadingBarColor}; + animation-name: ${loadingAnimation}; + animation-duration: ${props.$duration}ms; + animation-fill-mode: both; + } + + & > * { + opacity: var(${POPOVER_MOTION_CONTENT_OPACITY_PROPERTY}, 1); + will-change: opacity; + } + ` +} diff --git a/src/core/components/toast/toast.tsx b/src/core/components/toast/toast.tsx index ec45f70a5..b24eaa252 100644 --- a/src/core/components/toast/toast.tsx +++ b/src/core/components/toast/toast.tsx @@ -1,9 +1,10 @@ import {CloseIcon} from '@sanity/icons' -import {ThemeColorToneKey} from '@sanity/ui/theme' +import {ThemeColorStateToneKey} from '@sanity/ui/theme' import {styled} from 'styled-components' -import {POPOVER_MOTION_CONTENT_OPACITY_PROPERTY} from '../../constants' -import {Box, Button, Card, Flex, Stack, Text} from '../../primitives' +import {Box, Button, Flex, Stack, Text, Card} from '../../primitives' +import {ThemeProps} from '../../styles' import type {ButtonTone} from '../../types' +import {rootStyles, TextBox} from './styles' /** * @public @@ -15,9 +16,10 @@ export interface ToastProps { radius?: number | number[] title?: React.ReactNode status?: 'error' | 'warning' | 'success' | 'info' + duration?: number } -const STATUS_CARD_TONE: {[key: string]: ThemeColorToneKey} = { +const STATUS_CARD_TONE: {[key: string]: ThemeColorStateToneKey} = { error: 'critical', warning: 'caution', success: 'positive', @@ -38,17 +40,9 @@ const ROLES = { info: 'alert', } as const -const Root = styled(Card)` - pointer-events: all; - & > * { - opacity: var(${POPOVER_MOTION_CONTENT_OPACITY_PROPERTY}, 1); - will-change: opacity; - } -` - -const TextBox = styled(Flex)` - overflow-x: auto; -` +const Root = styled(Card)<{$duration?: number; tone: ThemeColorStateToneKey} & ThemeProps>( + rootStyles, +) /** * The `Toast` component gives feedback to users when an action has taken place. @@ -60,7 +54,7 @@ const TextBox = styled(Flex)` export function Toast( props: ToastProps & Omit, 'as' | 'height' | 'ref' | 'title'>, ): React.ReactElement { - const {closable, description, onClose, radius = 3, title, status, ...restProps} = props + const {closable, description, duration, onClose, radius = 3, title, status, ...restProps} = props const cardTone = status ? STATUS_CARD_TONE[status] : 'default' const buttonTone = status ? BUTTON_TONE[status] : 'default' const role = status ? ROLES[status] : 'status' @@ -74,6 +68,7 @@ export function Toast( radius={radius} shadow={2} tone={cardTone} + $duration={duration} > diff --git a/src/core/components/toast/toastProvider.tsx b/src/core/components/toast/toastProvider.tsx index 5d0999917..b846fd1c8 100644 --- a/src/core/components/toast/toastProvider.tsx +++ b/src/core/components/toast/toastProvider.tsx @@ -177,6 +177,7 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement { onClose={dismiss} status={params.status} title={params.title} + duration={params.duration} /> ))}