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) {
return setColumn(column, field, data);
} else if (index === existingFieldIndex) {
enqueueSnackBar('Columns cannot duplicate', {
title: 'Another column unselected',
enqueueSnackBar('Another column unselected', {
detailedMessage: 'Columns cannot duplicate',
variant: SnackBarVariant.Error,
});
return setColumn(column);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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