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) {
|
||||
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);
|
||||
|
||||
@ -51,7 +51,6 @@ export const SpreadsheetImportStepper = ({
|
||||
const handleError = useCallback(
|
||||
(description: string) => {
|
||||
enqueueSnackBar(description, {
|
||||
title: 'Error',
|
||||
variant: SnackBarVariant.Error,
|
||||
});
|
||||
},
|
||||
|
||||
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
Reference in New Issue
Block a user