Refactor SnackBar API (#9276)

Resolves #9259

## Changes
- Simplified API by removing `title` prop in favor of required `message`
prop
- Added `detailedMessage` prop for supporting additional context
- Updated styling for improved message display
- Renamed `defaultTitleByVariant` to `defaultAriaLabelByVariant` for
clarity
- Adjusted header alignment and icon styling

## Testing
- [x] Verified all SnackBar variants display correctly
- [x] Tested with and without detailed messages
- [x] Checked responsive behavior

## Screenshots

![SnackBar1](https://github.com/user-attachments/assets/fd0e222e-54c1-4cd7-b685-6d18efd6a681)

![SnackBar2](https://github.com/user-attachments/assets/bd1598b4-0f99-44c0-9ba1-6801b2959e3b)

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Samyak Piya
2024-12-30 06:19:04 -05:00
committed by GitHub
parent 578ba97dad
commit 0fa59d7718
8 changed files with 52 additions and 36 deletions

View File

@ -192,8 +192,8 @@ export const MatchColumnsStep = <T extends string>({
if (columnIndex === index) { if (columnIndex === index) {
return setColumn(column, field, data); return setColumn(column, field, data);
} else if (index === existingFieldIndex) { } else if (index === existingFieldIndex) {
enqueueSnackBar('Columns cannot duplicate', { enqueueSnackBar('Another column unselected', {
title: 'Another column unselected', detailedMessage: 'Columns cannot duplicate',
variant: SnackBarVariant.Error, variant: SnackBarVariant.Error,
}); });
return setColumn(column); return setColumn(column);

View File

@ -51,7 +51,6 @@ export const SpreadsheetImportStepper = ({
const handleError = useCallback( const handleError = useCallback(
(description: string) => { (description: string) => {
enqueueSnackBar(description, { enqueueSnackBar(description, {
title: 'Error',
variant: SnackBarVariant.Error, variant: SnackBarVariant.Error,
}); });
}, },

View File

@ -109,8 +109,8 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
onDropRejected: (fileRejections) => { onDropRejected: (fileRejections) => {
setLoading(false); setLoading(false);
fileRejections.forEach((fileRejection) => { fileRejections.forEach((fileRejection) => {
enqueueSnackBar(fileRejection.errors[0].message, { enqueueSnackBar(`${fileRejection.file.name} upload rejected`, {
title: `${fileRejection.file.name} upload rejected`, detailedMessage: fileRejection.errors[0].message,
variant: SnackBarVariant.Error, variant: SnackBarVariant.Error,
}); });
}); });

View File

@ -24,15 +24,13 @@ export enum SnackBarVariant {
Warning = 'warning', Warning = 'warning',
} }
export type SnackBarProps = Pick< export type SnackBarProps = Pick<ComponentPropsWithoutRef<'div'>, 'id'> & {
ComponentPropsWithoutRef<'div'>,
'id' | 'title'
> & {
className?: string; className?: string;
progress?: number; progress?: number;
duration?: number; duration?: number;
icon?: ReactNode; icon?: ReactNode;
message?: string; message: string;
detailedMessage?: string;
onCancel?: () => void; onCancel?: () => void;
onClose?: () => void; onClose?: () => void;
role?: 'alert' | 'status'; role?: 'alert' | 'status';
@ -73,7 +71,17 @@ const StyledHeader = styled.div`
display: flex; display: flex;
font-weight: ${({ theme }) => theme.font.weight.medium}; font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
height: ${({ theme }) => theme.spacing(6)}; margin-bottom: ${({ theme }) => theme.spacing(1)};
`;
const StyledMessage = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.sm};
`;
const StyledIcon = styled.div`
align-items: center;
display: flex;
`; `;
const StyledActions = styled.div` const StyledActions = styled.div`
@ -82,7 +90,16 @@ const StyledActions = styled.div`
margin-left: auto; margin-left: auto;
`; `;
const defaultTitleByVariant: Record<SnackBarVariant, string> = { const StyledDescription = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.sm};
padding-left: ${({ theme }) => theme.spacing(6)};
overflow: hidden;
text-overflow: ellipsis;
width: 200px;
`;
const defaultAriaLabelByVariant: Record<SnackBarVariant, string> = {
[SnackBarVariant.Default]: 'Alert', [SnackBarVariant.Default]: 'Alert',
[SnackBarVariant.Error]: 'Error', [SnackBarVariant.Error]: 'Error',
[SnackBarVariant.Info]: 'Info', [SnackBarVariant.Info]: 'Info',
@ -97,11 +114,11 @@ export const SnackBar = ({
icon: iconComponent, icon: iconComponent,
id, id,
message, message,
detailedMessage,
onCancel, onCancel,
onClose, onClose,
role = 'status', role = 'status',
variant = SnackBarVariant.Default, variant = SnackBarVariant.Default,
title = defaultTitleByVariant[variant],
}: SnackBarProps) => { }: SnackBarProps) => {
const theme = useTheme(); const theme = useTheme();
const { animation: progressAnimation, value: progressValue } = const { animation: progressAnimation, value: progressValue } =
@ -119,7 +136,7 @@ export const SnackBar = ({
return iconComponent; return iconComponent;
} }
const ariaLabel = defaultTitleByVariant[variant]; const ariaLabel = defaultAriaLabelByVariant[variant];
const color = theme.snackBar[variant].color; const color = theme.snackBar[variant].color;
const size = theme.icon.size.md; const size = theme.icon.size.md;
@ -164,7 +181,7 @@ export const SnackBar = ({
aria-live={role === 'alert' ? 'assertive' : 'polite'} aria-live={role === 'alert' ? 'assertive' : 'polite'}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
title={message || title || defaultTitleByVariant[variant]} title={message || defaultAriaLabelByVariant[variant]}
{...{ className, id, role, variant }} {...{ className, id, role, variant }}
> >
<StyledProgressBar <StyledProgressBar
@ -172,8 +189,8 @@ export const SnackBar = ({
value={progressValue} value={progressValue}
/> />
<StyledHeader> <StyledHeader>
{icon} <StyledIcon>{icon}</StyledIcon>
{message} <StyledMessage>{message}</StyledMessage>
<StyledActions> <StyledActions>
{!!onCancel && <LightButton title="Cancel" onClick={onCancel} />} {!!onCancel && <LightButton title="Cancel" onClick={onCancel} />}
@ -182,6 +199,9 @@ export const SnackBar = ({
)} )}
</StyledActions> </StyledActions>
</StyledHeader> </StyledHeader>
{detailedMessage && (
<StyledDescription>{detailedMessage}</StyledDescription>
)}
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -46,7 +46,7 @@ export const SnackBarProvider = ({ children }: React.PropsWithChildren) => {
<StyledSnackBarContainer> <StyledSnackBarContainer>
<AnimatePresence> <AnimatePresence>
{snackBarInternal.queue.map( {snackBarInternal.queue.map(
({ duration, icon, id, message, title, variant }) => ( ({ duration, icon, id, message, detailedMessage, variant }) => (
<motion.div <motion.div
key={id} key={id}
variants={variants} variants={variants}
@ -57,7 +57,7 @@ export const SnackBarProvider = ({ children }: React.PropsWithChildren) => {
layout layout
> >
<SnackBar <SnackBar
{...{ duration, icon, message, title, variant }} {...{ duration, icon, message, detailedMessage, variant }}
onClose={() => handleSnackBarClose(id)} onClose={() => handleSnackBarClose(id)}
/> />
</motion.div> </motion.div>

View File

@ -19,8 +19,8 @@ const meta: Meta<typeof SnackBar> = {
icon: { control: false }, icon: { control: false },
}, },
args: { args: {
title: 'Lorem ipsum', message: 'Lorem ipsum',
message: detailedMessage:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec eros tincidunt lacinia.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec eros tincidunt lacinia.',
onCancel: undefined, onCancel: undefined,
onClose: fn(), onClose: fn(),

View File

@ -52,19 +52,17 @@ export const RecordShowPageWorkflowHeader = ({
assertWorkflowWithCurrentVersionIsDefined(workflowWithCurrentVersion); assertWorkflowWithCurrentVersionIsDefined(workflowWithCurrentVersion);
if (!canWorkflowBeTested) { if (!canWorkflowBeTested) {
enqueueSnackBar( enqueueSnackBar('Workflow cannot be tested', {
'Trigger type should be Manual - when no record(s) are selected', variant: SnackBarVariant.Error,
{ detailedMessage:
variant: SnackBarVariant.Error, 'Trigger type should be Manual - when no record(s) are selected',
title: 'Workflow cannot be tested', icon: (
icon: ( <IconSettingsAutomation
<IconSettingsAutomation size={16}
size={16} color={theme.snackBar.error.color}
color={theme.snackBar.error.color} />
/> ),
), });
},
);
return; return;
} }

View File

@ -37,9 +37,8 @@ export const useRunWorkflowVersion = () => {
variables: { input: { workflowVersionId, payload } }, variables: { input: { workflowVersionId, payload } },
}); });
enqueueSnackBar('', { enqueueSnackBar(`${capitalize(workflowName)} starting...`, {
variant: SnackBarVariant.Success, variant: SnackBarVariant.Success,
title: `${capitalize(workflowName)} starting...`,
icon: ( icon: (
<IconSettingsAutomation <IconSettingsAutomation
size={16} size={16}