diff --git a/src/segment/SegmentState.ts b/src/segment/SegmentState.ts index 55c26223b..876df80a5 100644 --- a/src/segment/SegmentState.ts +++ b/src/segment/SegmentState.ts @@ -48,6 +48,20 @@ const TYPE_MAPPING = { export interface SegmentStateProps { value?: Date; defaultValue?: Date; + /** + * Sets formmating of date based on Intl.DateFormatOptions + * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat + * + * @example + * { + * year: "numeric", + * month: "2-digit", + * day: "2-digit", + * weekday: "long", + * } + * + */ formatOptions?: DateTimeFormatOpts; placeholderDate?: Date; onChange?: (value: Date, ...args: any[]) => void; @@ -124,8 +138,7 @@ export function useSegmentState(props: SegmentStateProps) { ) => { validSegments[type] = true; setValidSegments({ ...validSegments }); - // @ts-ignore - setValue(add(value, type, amount, resolvedOptions)); + setValue(add(value, type, amount, resolvedOptions) as Date); }; return { diff --git a/src/toast/ToastController.tsx b/src/toast/ToastController.tsx index 233333436..7c1abdde0 100644 --- a/src/toast/ToastController.tsx +++ b/src/toast/ToastController.tsx @@ -2,10 +2,29 @@ import React from "react"; import { useTimeout } from "@chakra-ui/hooks"; interface ToastControllerProps { + /** + * unique id of the toast + */ id: string; + /** + * Duration after the toast will be removed automatically if autoDismiss is true + * + * @default 0 + */ duration?: number; + /** + * If True the toast will automatically dismiss after the specified duration + */ autoDismiss?: boolean; + /** + * Dragging threshold at which point the toast will be swiped out + * + * @default 100 + */ dragThreshold?: number; + /** + * Callback to fire when toast is flagged as removed + */ onRequestRemove: (id: string) => void; } diff --git a/src/toast/ToastProvider.tsx b/src/toast/ToastProvider.tsx index 0216d01cd..b0e58e40d 100644 --- a/src/toast/ToastProvider.tsx +++ b/src/toast/ToastProvider.tsx @@ -1,11 +1,11 @@ import React from "react"; import ReactDOM from "react-dom"; import { canUseDOM } from "reakit-utils"; -import { createContext } from "@chakra-ui/utils"; import { ToastStateReturn } from "./ToastState"; import { ToastController } from "./ToastController"; import { useToastState, IToast } from "./ToastState"; +import { isFunction, createContext } from "../utils"; const DEFAULT_TIMEOUT = 5000; const PLACEMENTS = { @@ -41,12 +41,35 @@ export type ToastTypes = Record< >; export type TToastWrapper = (props: any) => React.ReactElement; + type IToastProvider = { + /** + * Specify types of toast in an object + */ toastTypes: ToastTypes; + /** + * If True the toast will automatically dismiss after the specified duration + */ autoDismiss?: boolean; + /** + * Timeout after the toast will be removed automatically if autoDismiss is true + * + * @default 5000 + */ timeout?: number; + /** + * Duration of delay after the toast will be unmounted, so that animations can run + * + * @default 0 + */ animationTimeout?: number; + /** + * Wrapper function to enhance the behaviour of ToastController + */ toastWrapper?: TToastWrapper; + /** + * Placement of the toast on the screen + */ placement?: Placements; }; @@ -75,7 +98,7 @@ export const ToastProvider: React.FC = ({ }} > {toastList.map( - ({ id, type, content, timeout, autoDismiss, isVisible }) => { + ({ id, type = "", content, timeout, autoDismiss, isVisible }) => { return ( = ({ duration={timeout ?? providerTimeout} autoDismiss={autoDismiss ?? providerAutoDismiss} > - {typeof content === "function" + {isFunction(content) ? content({ id, isVisible, remove: state.hide }) - : toastTypes[type || ""]?.({ - content, + : toastTypes[type]?.({ id, - remove: state.hide, + content, isVisible, + remove: state.hide, }) || content} diff --git a/src/toast/ToastState.ts b/src/toast/ToastState.ts index a5f8079b2..020194e16 100644 --- a/src/toast/ToastState.ts +++ b/src/toast/ToastState.ts @@ -3,16 +3,36 @@ import { v4 as uuidv4 } from "uuid"; import { Placements } from "./ToastProvider"; -type JSXFunction = (props: any) => JSX.Element; -type StringOrElement = string | JSXFunction; +type StringOrElement = string | React.ReactNode; export interface IToast { + /** + * uniue id of toast + */ id: string; + /** + * type of toast + */ type?: string; + /** + * content inside the toast + */ content: StringOrElement; + /** + * Timeout after the toast will be removed automatically if autoDismiss is true + */ timeout?: number; + /** + * sets the placement of the toast + */ placement?: Placements; + /** + * If True the toast will automatically dismiss after the specified duration + */ autoDismiss?: boolean; + /** + * Sets toast initial visibility + */ isVisible?: boolean; } diff --git a/src/toast/index.ts b/src/toast/index.ts index 27177dd34..a5b671442 100644 --- a/src/toast/index.ts +++ b/src/toast/index.ts @@ -1,3 +1,3 @@ -export * from "./ToastController"; export * from "./ToastState"; export * from "./ToastProvider"; +export * from "./ToastController"; diff --git a/src/toast/stories/AnimatedToast.stories.tsx b/src/toast/stories/AnimatedToast.stories.tsx index f58ba9792..7b041b184 100644 --- a/src/toast/stories/AnimatedToast.stories.tsx +++ b/src/toast/stories/AnimatedToast.stories.tsx @@ -1,9 +1,9 @@ +import "./style.css"; import React from "react"; import { Meta } from "@storybook/react"; -import { useTransition, animated } from "react-spring"; import { CSSTransition } from "react-transition-group"; +import { useTransition, animated } from "react-spring"; -import "./style.css"; import Demo, { getTransform } from "./Demo"; import { ToastProvider, TToastWrapper } from "../index"; diff --git a/src/toast/stories/BasicToast.stories.tsx b/src/toast/stories/BasicToast.stories.tsx index 25aa4690b..17ca64246 100644 --- a/src/toast/stories/BasicToast.stories.tsx +++ b/src/toast/stories/BasicToast.stories.tsx @@ -1,7 +1,7 @@ +import "./style.css"; import React from "react"; import { Meta } from "@storybook/react"; -import "./style.css"; import Demo from "./Demo"; import { ToastProvider } from "../index"; diff --git a/src/utils/index.ts b/src/utils/index.ts index 0aae67090..c7b00a700 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ +import React from "react"; import { Booleanish } from "./types"; import { warn } from "@chakra-ui/utils"; @@ -51,6 +52,55 @@ export const dataAttr = (condition: boolean | undefined) => export const ariaAttr = (condition: boolean | undefined) => condition ? true : undefined; +export interface CreateContextOptions { + /** + * If `true`, React will throw if context is `null` or `undefined` + * In some cases, you might want to support nested context, so you can set it to `false` + */ + strict?: boolean; + /** + * Error message to throw if the context is `undefined` + */ + errorMessage?: string; + /** + * The display name of the context + */ + name?: string; +} + +type CreateContextReturn = [React.Provider, () => T, React.Context]; + +/** + * Creates a named context, provider, and hook. + * + * @param options create context options + */ +export function createContext(options: CreateContextOptions = {}) { + const { + strict = true, + errorMessage = "useContext: `context` is undefined. Seems you forgot to wrap component within the Provider", + name, + } = options; + + const Context = React.createContext(undefined); + + Context.displayName = name; + + function useContext() { + const context = React.useContext(Context); + + if (!context && strict) { + throw new Error(errorMessage); + } + + return context; + } + + return [Context.Provider, useContext, Context] as CreateContextReturn< + ContextType + >; +} + export const cx = (...classNames: any[]) => classNames.filter(Boolean).join(" ");