Skip to content

Commit

Permalink
refactor(toast): ♻️ add new toast state & examples (#199)
Browse files Browse the repository at this point in the history
Co-authored-by: Anurag <[email protected]>
  • Loading branch information
navin-moorthy and anuraghazra authored Mar 31, 2021
1 parent 45cb762 commit e62928f
Show file tree
Hide file tree
Showing 36 changed files with 5,053 additions and 4,751 deletions.
3 changes: 2 additions & 1 deletion .storybook/postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ function rewriteRootRule() {
function addIdScope() {
return root => {
const filename = root.source.input.file;

const isTailwind = path.basename(path.dirname(filename)) === "tailwind";

if (isTailwind) return scopify("#tailwind")(root);

const basename = path.basename(filename, ".css");
const id = kebabCase(basename);

return scopify(`#${id}`)(root);
};
}
Expand Down
7 changes: 7 additions & 0 deletions .storybook/preview-body.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<style>
html {
width: 100%;
height: 100%;
}
body {
box-sizing: border-box;
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
min-width: 100%;
min-height: 100%;
}
</style>
3 changes: 2 additions & 1 deletion .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
crossorigin="anonymous"
/>
3 changes: 3 additions & 0 deletions .storybook/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ module.exports = ({ config }) => {
};
config.module.rules.push({
test: /\.css$/,
exclude: /node_modules/,
include: path.resolve(process.cwd(), "src"),

use: [
{
loader: require.resolve("postcss-loader"),
Expand Down
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = function (api) {
const plugins = [
"date-fns",
"@chakra-ui/babel-plugin",
"@babel/plugin-proposal-class-properties",
["@babel/plugin-proposal-class-properties", { loose: true }],
];

return {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"@storybook/addon-actions": "6.1.16",
"@storybook/addon-essentials": "6.1.16",
"@storybook/react": "6.1.16",
"@tailwindcss/postcss7-compat": "^2.0.3",
"@testing-library/dom": "^7.28.1",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.4",
Expand Down Expand Up @@ -161,7 +162,7 @@
"standard-version": "9.1.0",
"storybook-addon-preview": "^2.1.0",
"strip-comments": "^2.0.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3",
"ts-jest": "26.5.0",
"ts-morph": "^9.1.0",
"ts-node": "^9.1.1",
Expand Down
2 changes: 1 addition & 1 deletion src/datepicker/stories/DatePickerStyled.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import { Meta, Story } from "@storybook/react";

import "../../../tailwind/index.css";
import "./tailwind/index.css";
import { DatePicker } from "./styled/DatePicker.component";
import { RangeDatePicker } from "./styled/RangeDatePicker.component";

Expand Down
15 changes: 8 additions & 7 deletions tailwind/index.css → src/datepicker/stories/tailwind/index.css
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
@import "tailwindcss/base";

@import "tailwindcss/components";

@import "tailwindcss/utilities";
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
.styled-datepicker .calendar__cell {
height: 32px;
width: 32px;
max-height: 32px;
max-width: 32px;
@apply text-center rounded-lg text-sm;
@apply text-sm text-center rounded-lg;
}
.styled-datepicker .calendar__cell[data-is-range-selection] {
@apply bg-blue-100 rounded-none text-gray-800 !important;
Expand All @@ -32,12 +30,15 @@
.styled-datepicker.calendar [data-weekend] {
@apply text-red-600;
}

.styled-datepicker.calendar [aria-selected="true"] {
@apply bg-blue-400 text-white;
@apply text-white bg-blue-400;
}

.styled-datepicker.calendar [aria-disabled="true"] {
@apply text-gray-500;
}

.styled-datepicker.calendar span {
outline: none;
}
Expand Down
1 change: 0 additions & 1 deletion src/select/stories/SelectStyled.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from "react";
import { Meta, Story } from "@storybook/react";

import "../../../tailwind/index.css";
import { App as Select } from "./SelectStyled.component";
import { createPreviewTabs } from "../../../scripts/create-preview-tabs";
import { selectStyledTemplate, selectStyledTemplateJs } from "./templates";
Expand Down
1 change: 0 additions & 1 deletion src/slider/stories/MultiSlider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export default {
js: multiSliderTemplateJs,
ts: multiSliderTemplate,
css: sliderCssTemplate,
deps: ["reakit@latest"],
}),
},
decorators: [
Expand Down
251 changes: 251 additions & 0 deletions src/toast/CreateToastContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import * as React from "react";
import { createContext } from "@chakra-ui/utils";

import {
genId,
ActionType,
useToastState,
DefaultToast,
StateReturnType,
} from "./index";

export type DefaultToastOptions<T extends DefaultToast> = Omit<
T,
"id" | "visible" | "reverseOrder" | "createdAt"
>;

export type DefaultToastProviderOptions<T extends DefaultToast> = Partial<
DefaultToastOptions<T>
>;

export type ConfigurableToastOptions<T extends DefaultToast> = Omit<
T,
"visible"
>;

export type ToastOptions<T extends DefaultToast> = Partial<
ConfigurableToastOptions<T>
>;

export interface ToastStore<T extends DefaultToast>
extends StateReturnType<T> {}

export interface CreateToast<T extends DefaultToast, Content> {
createToast: CreateToastHandler<T, Content>;
}

export type CreateToastHandler<T extends DefaultToast, Content> = (
content: Content,
options?: ToastOptions<T>,
) => T;

export type AddToast<T extends DefaultToast, Content> = (
content: Content,
options?: ToastOptions<T>,
) => string;

export type ShowToast<T extends DefaultToast, Content> = (
content: Content,
options?: ToastOptions<T>,
) => string;

export type UpdateToast<T extends DefaultToast> = (
toastId: string,
toast: Partial<T>,
) => void;

export type UpdateFieldToast<T extends DefaultToast> = (
field: keyof T,
fieldValue: any,
toast: Partial<T>,
) => void;

export type UpdateAllToast<T extends DefaultToast> = (
toast: Partial<T>,
) => void;

export type DismissToast = (toastId?: string) => void;

export type RemoveToast = (toastId?: string) => void;

export type ToastHandlers<T extends DefaultToast, Content> = {
addToast: AddToast<T, Content>;
showToast: ShowToast<T, Content>;
updateToast: UpdateToast<T>;
updateFieldToast: UpdateFieldToast<T>;
updateAllToast: UpdateAllToast<T>;
dismissToast: DismissToast;
removeToast: RemoveToast;
};

export function createToastStore<T extends DefaultToast, Content>(
defaultOptions: DefaultToastOptions<T>,
) {
const [ToastStoreProvider, useToastStore] = createContext<ToastStore<T>>({
strict: false,
name: "ToastsState",
errorMessage: "useToastStore must be used within ToastProvider",
});

const [CreateToastProvider, useCreateToast] = createContext<
CreateToast<T, Content>
>({
strict: false,
name: "CreateToast",
errorMessage: "useCreateToast must be used within ToastProvider",
});

const [ToastHandlersProvider, useToastHandlers] = createContext<
ToastHandlers<T, Content>
>({
strict: false,
name: "ToastHandlers",
errorMessage: "useToastHandlers must be used within ToastProvider",
});

const ToastProvider: React.FC<DefaultToastProviderOptions<T>> = props => {
const { children, ...rest } = props;
const { toasts, dispatch } = useToastState<T>();
const context = React.useMemo(
() => ({
toasts,
dispatch,
}),
[toasts, dispatch],
);

const createToast = React.useCallback(
(content: Content, opts?: ToastOptions<T>) =>
(({
visible: false,
reverseOrder: true,
createdAt: Date.now(),
...defaultOptions,
...rest,
...opts,
content,
id: opts?.id || genId(),
} as unknown) as T),
// Since its only a few object https://twitter.com/dan_abramov/status/1104414272753487872
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(rest)],
);

const addToast: AddToast<T, Content> = React.useCallback(
(content, options) => {
const toast = createToast(content, options);

dispatch({
type: ActionType.ADD_TOAST,
toast: { ...toast, visible: true },
});

return toast.id;
},
[createToast, dispatch],
);

const showToast: ShowToast<T, Content> = React.useCallback(
(content, options) => {
const toast = createToast(content, options);

dispatch({ type: ActionType.ADD_TOAST, toast });

setTimeout(() => {
dispatch({
type: ActionType.UPDATE_TOAST,
toast: { ...toast, visible: true },
});
}, 0);

return toast.id;
},
[createToast, dispatch],
);

const updateToast: UpdateToast<T> = React.useCallback(
(toastId, toast) => {
dispatch({
type: ActionType.UPDATE_TOAST,
toast: { ...toast, id: toastId },
});
},
[dispatch],
);

const updateFieldToast: UpdateFieldToast<T> = React.useCallback(
(field, fieldValue, toast) => {
dispatch({
type: ActionType.UPDATE_FIELD_TOAST,
field,
fieldValue,
toast,
});
},
[dispatch],
);

const updateAllToast: UpdateAllToast<T> = React.useCallback(
toast => {
dispatch({ type: ActionType.UPDATE_ALL_TOAST, toast });
},
[dispatch],
);

const dismissToast: DismissToast = React.useCallback(
toastId => {
const unmountDuration = defaultOptions.animationDuration;

dispatch({ type: ActionType.DISMISS_TOAST, toastId });

setTimeout(() => {
dispatch({ type: ActionType.REMOVE_TOAST, toastId });
}, unmountDuration);
},
[dispatch],
);

const removeToast: RemoveToast = React.useCallback(
toastId => {
dispatch({ type: ActionType.REMOVE_TOAST, toastId });
},
[dispatch],
);

return (
<ToastStoreProvider value={context}>
<CreateToastProvider value={{ createToast }}>
<ToastHandlersProvider
value={{
addToast,
showToast,
updateToast,
updateFieldToast,
updateAllToast,
dismissToast,
removeToast,
}}
>
{children}
</ToastHandlersProvider>
</CreateToastProvider>
</ToastStoreProvider>
);
};

ToastProvider.displayName = "ToastStoreProvider";

return [
ToastProvider,
useToastStore,
useCreateToast,
useToastHandlers,
] as CreateToastStoreReturn<T, Content>;
}

export type CreateToastStoreReturn<T extends DefaultToast, Content> = [
React.FC<Partial<DefaultToastOptions<T>>>,
() => ToastStore<T>,
() => CreateToast<T, Content>,
() => ToastHandlers<T, Content>,
];
Loading

1 comment on commit e62928f

@vercel
Copy link

@vercel vercel bot commented on e62928f Mar 31, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.