Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DTRA-2034 / Kate / Expand Snackbar functionality #465

Merged
merged 2 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ exports[`Snackbar renders correctly without action button 1`] = `<div />`;
exports[`Snackbar renders correctly without close button 1`] = `<div />`;

exports[`Snackbar renders with default props 1`] = `<div />`;

exports[`Snackbar renders without specific className if hasFixedHeight === false 1`] = `<div />`;
14 changes: 14 additions & 0 deletions lib/components/Snackbar/__tests__/snackbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ describe("Snackbar", () => {
const { container } = renderComponent();

expect(screen.getByText(testMessage)).toBeInTheDocument();
expect(screen.getByText(testMessage)).toHaveClass(
"quill-snackbar__message--has-fix-height",
);
expect(screen.getByText("Action")).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
Expand All @@ -61,6 +64,17 @@ describe("Snackbar", () => {
expect(screen.queryByText("Action")).not.toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it("renders without specific className if hasFixedHeight === false", () => {
defaultProps.hasFixedHeight = false;
(useSnackbar as jest.Mock).mockReturnValue({
queue: [{ ...defaultProps }],
});
const { container } = renderComponent();
expect(screen.getByText(testMessage)).not.toHaveClass(
"quill-snackbar__message--has-fix-height",
);
expect(container).toMatchSnapshot();
});
it("renders correctly without close button", () => {
defaultProps.hasCloseButton = false;
(useSnackbar as jest.Mock).mockReturnValue({
Expand Down
27 changes: 26 additions & 1 deletion lib/components/Snackbar/snackbar-standalone.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,20 @@ const meta = {
},
onCloseAction: {
description:
"Optional. Only for standalone snackbar when you pass funciton for close button `onClick`.",
"Optional. Only for standalone snackbar when you pass function for close button `onClick`.",
},
onSnackbarRemove: {
description:
"Optional. Callback for removing snackbar, will be triggered on close button click, on action text click, on timeout expiry and on dismissing the snackbar.",
},
hasCloseButton: {
control: "boolean",
description: "Optional. Set to true by default.",
},
hasFixedHeight: {
control: "boolean",
description: "Optional. Set to true by default.",
},
},
} satisfies Meta<typeof Snackbar>;

Expand All @@ -87,3 +95,20 @@ export const WithActionButton: Story = {
actionText: "Action",
},
};

export const WithRemoveCallback: Story = {
args: {
message: "Please, open the console",
actionText: "Action",
onSnackbarRemove: () => console.log("onSnackbarRemove was called"),
},
};

export const WithoutFixedHeight: Story = {
args: {
icon: undefined,
hasFixedHeight: false,
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut hendrerit, leo sed viverra consequat, quam nulla maximus est, et faucibus nunc urna sit amet ex. Donec sagittis fermentum finibus.",
},
};
8 changes: 5 additions & 3 deletions lib/components/Snackbar/snackbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@
}
}
&__message {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
&--has-fix-height {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
flex: 1 0 0;

text-overflow: ellipsis;
Expand Down
23 changes: 23 additions & 0 deletions lib/components/Snackbar/snackbar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,18 @@ const meta = {
description:
"Optional. Must be included if we have actionText to trigger action and close snackbar.",
},
onSnackbarRemove: {
description:
"Optional. Callback for removing snackbar, will be triggered on close button click, on action text click, on timeout expiry and on dismissing the snackbar.",
},
hasCloseButton: {
control: "boolean",
description: "Optional. Set to true by default.",
},
hasFixedHeight: {
control: "boolean",
description: "Optional. Set to true by default.",
},
onCloseAction: {
table: { disable: true },
},
Expand Down Expand Up @@ -238,3 +246,18 @@ SnackbarWithTwoLinesMessageWithActionButtonMobileOnly.args = {
hasCloseButton: false,
onActionClick: fn(),
};

export const SnackbarWithoutFixedHeight = Template.bind(this) as Story;
SnackbarWithoutFixedHeight.args = {
...meta.args,
hasFixedHeight: false,
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut hendrerit, leo sed viverra consequat, quam nulla maximus est, et faucibus nunc urna sit amet ex. Donec sagittis fermentum finibus.",
};

export const SnackbarWithRemoveCallback = Template.bind(this) as Story;
SnackbarWithRemoveCallback.args = {
...meta.args,
message: "Please, open the console",
onSnackbarRemove: () => console.log("onSnackbarRemove was called"),
};
38 changes: 29 additions & 9 deletions lib/components/Snackbar/snackbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ export interface SnackbarProps extends HTMLAttributes<HTMLDivElement> {
message: ReactNode;
actionText?: string;
hasCloseButton?: boolean;
hasFixedHeight?: boolean;
onActionClick?: () => void;
onCloseAction?: () => void;
onSnackbarRemove?: () => void;
delay?: number;
standalone?: boolean;
status?: "fail" | "neutral";
Expand All @@ -30,24 +32,38 @@ export const Snackbar = ({
actionText,
onActionClick,
onCloseAction,
onSnackbarRemove,
hasCloseButton = true,
hasFixedHeight = true,
delay,
standalone = true,
status = "neutral",
...rest
}: SnackbarProps) => {
const { removeSnackbar } = useSnackbar();
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const snackbarDelayedRemove = (id: string, delay: number = 4000) => {
const snackbarDelayedRemove = ({
id,
onSnackbarRemove,
delay = 4000,
}: {
id: string;
onSnackbarRemove?: () => void;
delay?: number;
}) => {
return setTimeout(() => {
removeSnackbar(id);
removeSnackbar(id, onSnackbarRemove);
}, delay);
};

const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
id && (timerRef.current = snackbarDelayedRemove(id));
id &&
(timerRef.current = snackbarDelayedRemove({
id,
onSnackbarRemove,
}));

return () => {
clearTimeout(timerRef.current ?? "");
Expand All @@ -57,25 +73,25 @@ export const Snackbar = ({
const handleClose = (delay: number = 100) => {
onCloseAction?.();
if (timerRef) clearTimeout(timerRef.current ?? "");
id && snackbarDelayedRemove(id, delay);
id && snackbarDelayedRemove({ id, onSnackbarRemove, delay });
};

const handleActionClick = () => {
onActionClick?.();
handleClose();
};

id && useOnClickOutside(ref, () => removeSnackbar(id));
id && useOnClickOutside(ref, () => removeSnackbar(id, onSnackbarRemove));

if (standalone && !isVisible) return null;

const color = {
neutral: "white-black",
fail: "white"
}
fail: "white",
};

const buttonColor = color[status] as TDefaultColor;

return (
<div
{...rest}
Expand All @@ -97,7 +113,11 @@ export const Snackbar = ({
)}
<div className="quill-snackbar__message--container">
<Text
className="quill-snackbar__message"
className={clsx(
"quill-snackbar__message",
hasFixedHeight &&
"quill-snackbar__message--has-fix-height",
)}
size="sm"
style={{
color: `var(--component-snackbar-label-color-${status})`,
Expand Down
2 changes: 1 addition & 1 deletion lib/providers/snackbar/snackbarContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createContext } from "react";
export type SnackbarContextValue = {
queue: SnackbarProps[];
addSnackbar: (props: Omit<SnackbarProps, "id" | "isVisible">) => void;
removeSnackbar: (id: string) => void;
removeSnackbar: (id: string, onSnackbarRemove?: () => void) => void;
};

export const SnackbarContext = createContext<SnackbarContextValue>({
Expand Down
3 changes: 2 additions & 1 deletion lib/providers/snackbar/snackbarProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ export const SnackbarProvider = ({
});
};

const removeSnackbar = (id: string) => {
const removeSnackbar = (id: string, onSnackbarRemove?: () => void) => {
setQueue((prevQueue: SnackbarProps[]) => {
return prevQueue.map((item, index) => {
if (index === 0) {
setTimeout(() => {
removeSnackbarFromQueue(id);
onSnackbarRemove?.();
}, 500);
return { ...item, isVisible: false };
}
Expand Down