Skip to content

Commit

Permalink
Merge branch 'main' into rewrite-select
Browse files Browse the repository at this point in the history
  • Loading branch information
vineethasok authored Sep 14, 2023
2 parents 0ed312c + 91f27d5 commit 6e5d3f4
Show file tree
Hide file tree
Showing 12 changed files with 358 additions and 14 deletions.
55 changes: 55 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-tooltip": "^1.0.6",
"lodash": "^4.17.21",
"react-sortablejs": "^6.1.4",
Expand Down
38 changes: 38 additions & 0 deletions src/components/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Button, useToast, ToastProvider, ToastProps } from "@/components";
const ToastTrigger = (props: ToastProps) => {
const { createToast } = useToast();
return (
<Button
onClick={() => {
createToast(props);
}}
>
Create Toast
</Button>
);
};
const ToastExample = (props: ToastProps) => {
return (
<ToastProvider>
<ToastTrigger {...props} />
</ToastProvider>
);
};
export default {
component: ToastExample,
title: "Display/Toast",
tags: ["form-field", "toast", "autodocs"],
argTypes: {
type: {
control: "inline-radio",
options: [undefined, "default", "danger", "warning", "success"],
},
},
};

export const Playground = {
args: {
description: "description",
title: "title",
},
};
232 changes: 232 additions & 0 deletions src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import { ReactNode, createContext, useState } from "react";
import * as RadixUIToast from "@radix-ui/react-toast";
import { Button, ButtonProps, Icon, IconButton, IconName } from "@/components";
import styled, { keyframes } from "styled-components";

interface ContextProps {
createToast: (toast: ToastProps) => void;
}
export const ToastContext = createContext<ContextProps>({
createToast: () => null,
});

type ToastType = "danger" | "warning" | "default" | "success";
export interface ToastProps {
id?: string;
type?: ToastType;
title: string;
description?: ReactNode;
actions?: Array<ButtonProps & { altText: string }>;
}

const ToastIcon = styled(Icon)<{ $type?: ToastType }>`
${({ theme, $type = "default" }) => `
width: ${theme.click.toast.icon.size.width};
height: ${theme.click.toast.icon.size.height};
color: ${theme.click.toast.color.icon[$type]}
`}
`;
const hide = keyframes`
from {
opacity: 1;
}
to {
opacity: 0;
}
`;
const slideIn = keyframes`
from {
transform: translateX(calc(100% + var(--viewport-padding)));
}
to {
transform: translateX(0);
}
`;
const swipeOut = keyframes`
from {
transform: translateX(var(--radix-toast-swipe-end-x));
}
to {
transform: translateX(calc(100% + var(--viewport-padding)));
}
`;

const ToastRoot = styled(RadixUIToast.Root)`
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
${({ theme }) => `
padding: ${theme.click.toast.space.y} ${theme.click.toast.space.x};
gap: ${theme.click.toast.space.gap};
border-radius: ${theme.click.toast.radii.all};
border: 1px solid ${theme.click.toast.color.stroke.default};
background: ${theme.click.global.color.background.default};
box-shadow: ${theme.click.toast.shadow};
`}
&[data-state='open'] {
animation: ${slideIn} 150ms cubic-bezier(0.16, 1, 0.3, 1);
}
&[data-state="closed"] {
animation: ${hide} 100ms ease-in;
}
&[data-swipe="move"] {
transform: translateX(var(--radix-toast-swipe-move-x));
}
&[data-swipe="cancel"] {
transform: translateX(0);
transition: transform 200ms ease-out;
}
&[data-swipe="end"] {
animation: ${swipeOut} 100ms ease-out;
}
`;

const ToastHeader = styled(RadixUIToast.Title)`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
gap: inherit;
${({ theme }) => `
font: ${theme.click.toast.typography.title.default};
color: ${theme.click.toast.color.title.default};
`}
`;

const ToastDescriptionContainer = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
align-items: flex-end;
gap: inherit;
${({ theme }) => `
font: ${theme.click.toast.typography.title.default};
color: ${theme.click.toast.color.title.default};
`}
`;

const ToastDescriptionContent = styled.div`
display: flex;
align-self: stretch;
gap: inherit;
${({ theme }) => `
font: ${theme.click.toast.typography.description.default};
color: ${theme.click.toast.color.description.default};
`}
`;

const Title = styled.div`
flex: 1;
`;

const Toast = ({
type,
title,
description,
actions = [],
onClose,
}: ToastProps & { onClose: (open: boolean) => void }) => {
let iconName = "";
if (type === "default") {
iconName = "info-in-circle";
} else if (type === "success") {
iconName = "check";
} else if (type && ["danger", "warning"].includes(type)) {
iconName = "warning";
}
return (
<ToastRoot onOpenChange={onClose}>
<ToastHeader>
{iconName.length > 0 && (
<ToastIcon
name={iconName as IconName}
$type={type}
/>
)}
<Title>{title}</Title>
<RadixUIToast.Close asChild>
<IconButton
icon="cross"
type="primary"
/>
</RadixUIToast.Close>
</ToastHeader>
{(description || actions.length > 0) && (
<ToastDescriptionContainer>
<ToastDescriptionContent as={RadixUIToast.Description}>
{description}
</ToastDescriptionContent>
{actions.length > 0 && (
<ToastDescriptionContent>
{actions.map(({ altText, ...btnProps }) => (
<RadixUIToast.Action altText={altText}>
<Button {...btnProps} />
</RadixUIToast.Action>
))}
</ToastDescriptionContent>
)}
</ToastDescriptionContainer>
)}
</ToastRoot>
);
};

const Viewport = styled(RadixUIToast.Viewport)`
--viewport-padding: 25px;
position: fixed;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
padding: var(--viewport-padding);
gap: ${({ theme }) => theme.click.toast.space.gap};
width: 390px;
max-width: 100vw;
margin: 0;
list-style: none;
z-index: 2147483647;
outline: none;
`;

export const ToastProvider = ({ children, ...props }: RadixUIToast.ToastProps) => {
const [toasts, setToasts] = useState<Map<string, ToastProps>>(new Map());

const onClose = (id: string) => (open: boolean) => {
if (!open) {
setToasts(currentToasts => {
const newMap = new Map(currentToasts);
newMap.delete(id);
return newMap;
});
}
};
const value = {
createToast: (toast: ToastProps) => {
setToasts(currentToasts => {
const newMap = new Map(currentToasts);
newMap.set(toast?.id ?? String(Date.now()), toast);
return newMap;
});
},
};

return (
<RadixUIToast.Provider
swipeDirection="right"
{...props}
>
<ToastContext.Provider value={value}>
{children}
{Array.from(toasts).map(([id, toast]) => (
<Toast
key={id}
{...toast}
onClose={onClose(id)}
/>
))}
</ToastContext.Provider>
<Viewport />
</RadixUIToast.Provider>
);
};
9 changes: 9 additions & 0 deletions src/components/Toast/useToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useContext } from "react";
import { ToastContext } from "./Toast";
export const useToast = () => {
const result = useContext(ToastContext);
if (!result) {
throw new Error("Context used outside of its Provider!");
}
return result;
};
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ export { Text } from "./Typography/Text/Text";
export { TextField } from "./Input/TextField";
export { Title } from "./Typography/Title/Title";
export { Tooltip, TooltipProvider } from "./Tooltip/Tooltip";
export { ToastProvider } from "./Toast/Toast";
export { useToast } from "./Toast/useToast";
export { UserIcon as ProfileIcon } from "./icons/UserIcon";
1 change: 1 addition & 0 deletions src/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { SidebarNavigationTitleProps } from "./SidebarNavigationTitle/SidebarNav
import { SidebarCollapsibleItemProps } from "./SidebarCollapsibleItem/SidebarCollapsibleItem";
import { SidebarCollapsibleTitleProps } from "./SidebarCollapsibleTitle/SidebarCollapsibleTitle";
export type { Menu, SplitButtonProps } from "./SplitButton/SplitButton";
export type { ToastProps } from "./Toast/Toast";

export type States = "default" | "active" | "disabled" | "error" | "hover";
export type HorizontalDirection = "start" | "end";
Expand Down
Loading

0 comments on commit 6e5d3f4

Please sign in to comment.