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   --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -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);
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user