Skip to content

Commit

Permalink
Merge branch 'feat/toast-limit' into next-release
Browse files Browse the repository at this point in the history
  • Loading branch information
mucahit committed Sep 16, 2021
2 parents 9a72c71 + 92c0c78 commit dcd9acf
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 12 deletions.
24 changes: 23 additions & 1 deletion src/core/utils/array/arrayUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {isNonNegativeInteger} from "../number/numberUtils";

function filterOutItemsByKey<T extends {[x: string]: any}>(
array: T[],
key: keyof T,
Expand All @@ -22,4 +24,24 @@ function updateAtIndex<Item>(items: Item[], index: number, newItem: Item): Item[
return newItems;
}

export {filterOutItemsByKey, updateAtIndex};
/**
* Slices an array at an offset from the end so that the new array's length would be `limit` at most.
*
* @param limit - Limit for the length of that array
* @param array - List of items
* @returns The same array if array.length <=limit, otherwise returns a new array.
*/
function limitArrayLengthFromTheEnd<Item extends any>(
limit: undefined | number,
array: Item[]
): Item[] {
let slicedArray = array;

if (isNonNegativeInteger(limit) && array.length > limit) {
slicedArray = array.slice(array.length - limit);
}

return slicedArray;
}

export {filterOutItemsByKey, updateAtIndex, limitArrayLengthFromTheEnd};
7 changes: 6 additions & 1 deletion src/core/utils/number/numberUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ function getThousandthSeparatorCount(value: string) {
return formatNumber({locale: "en"})(parseFloat(value)).match(/,/g)?.length || 0;
}

function isNonNegativeInteger(x: unknown): x is number {
return typeof x === "number" && Number.isFinite(x) && x >= 0;
}

export {
formatNumber,
parseNumber,
Expand All @@ -186,5 +190,6 @@ export {
getNegativeZero,
mapDigitsToLocalVersion,
removeLeadingZeros,
getThousandthSeparatorCount
getThousandthSeparatorCount,
isNonNegativeInteger
};
20 changes: 17 additions & 3 deletions src/toast/ToastProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, {createContext, useReducer} from "react";
import React, {createContext, useReducer, useEffect} from "react";

import ToastStack from "./stack/ToastStack";
import {initialToastState} from "./util/toastConstants";
import toastReducer from "./util/toastReducer";
import {ToastAction, ToastContextState} from "./util/toastTypes";
import {isNonNegativeInteger} from "../core/utils/number/numberUtils";

const ToastContext = createContext<[ToastContextState, React.Dispatch<ToastAction>]>([
initialToastState,
Expand All @@ -16,6 +17,7 @@ interface ToastContextProviderProps {
children: React.ReactNode;
customRootId?: string;
autoCloseToasts?: boolean;
limit?: number;
}

/**
Expand All @@ -26,13 +28,25 @@ interface ToastContextProviderProps {
function ToastContextProvider({
children,
customRootId,
autoCloseToasts = true
autoCloseToasts = true,
limit
}: ToastContextProviderProps) {
const [state, dispatch] = useReducer(toastReducer, {
...initialToastState,
autoCloseToasts
autoCloseToasts,
limit
});

useEffect(() => {
if (isNonNegativeInteger(limit)) {
dispatch({type: "SET_LIMIT", limit});
}
}, [limit]);

useEffect(() => {
dispatch({type: "SET_AUTO_CLOSE", autoCloseToasts});
}, [autoCloseToasts]);

return (
<ToastContext.Provider value={[state, dispatch]}>
{children}
Expand Down
33 changes: 28 additions & 5 deletions src/toast/util/toastReducer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {updateAtIndex} from "../../core/utils/array/arrayUtils";
import {
limitArrayLengthFromTheEnd,
updateAtIndex
} from "../../core/utils/array/arrayUtils";
import {not} from "../../core/utils/function/functionUtils";
import {initialToastState} from "./toastConstants";
import {ToastAction} from "./toastTypes";
Expand All @@ -12,13 +15,14 @@ function toastReducer(state: ToastState, action: ToastAction): ToastState {
switch (action.type) {
case "DISPLAY": {
const {toastData} = action;
const newToastStack = [
...state.toastStack.filter(not(isSameToast(toastData.id))),
toastData
];

newState = {
...state,
toastStack: [
...state.toastStack.filter(not(isSameToast(toastData.id))),
toastData
]
toastStack: limitArrayLengthFromTheEnd(state.limit, newToastStack)
};
break;
}
Expand Down Expand Up @@ -57,6 +61,25 @@ function toastReducer(state: ToastState, action: ToastAction): ToastState {
break;
}

case "SET_LIMIT": {
const {limit} = action;

newState = {
...state,
limit,
toastStack: limitArrayLengthFromTheEnd(limit, state.toastStack)
};
break;
}

case "SET_AUTO_CLOSE": {
newState = {
...state,
autoCloseToasts: action.autoCloseToasts
};
break;
}

default:
break;
}
Expand Down
9 changes: 9 additions & 0 deletions src/toast/util/toastTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@ export type ToastAction =
type: "UPDATE";
toastId: string;
toastData: Partial<Omit<ToastData, "id">>;
}
| {
type: "SET_LIMIT";
limit: number;
}
| {
type: "SET_AUTO_CLOSE";
autoCloseToasts: boolean;
};

export interface ToastContextState {
toastStack: (Omit<ToastData, "id"> & {id: string})[];
autoCloseToasts: boolean;
limit?: number;
}
51 changes: 50 additions & 1 deletion stories/13-Toast.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import "./utils/constants/toast/_toast.scss";
import {storiesOf} from "@storybook/react";
import React from "react";

import StateProvider from "./utils/StateProvider";

import FormField from "../src/form/field/FormField";
import CheckboxInput from "../src/form/input/checkbox/CheckboxInput";
import Input from "../src/form/input/Input";
import Button from "../src/button/Button";
import {useToaster} from "../src/toast/util/toastHooks";
import StoryFragment from "./utils/StoryFragment";
Expand All @@ -23,6 +28,12 @@ function ToastComponent() {
<ToastContextProvider autoCloseToasts={false}>
<ToastExamples />
</ToastContextProvider>

<p>{"ToastProvider with limit={3}"}</p>

<ToastContextProvider limit={3}>
<ToastExamples />
</ToastContextProvider>
</StoryFragment>
);
}
Expand Down Expand Up @@ -173,4 +184,42 @@ function ToastExamples() {
);
}

storiesOf("Toast", module).add("Toast Message", () => <ToastComponent />);
storiesOf("Toast", module)
.add("Toast Message", () => <ToastComponent />)
.add("Toast with dynamic props", () => (
<StateProvider initialState={{limit: "3", autoCloseToasts: false}}>
{(state, setState) => (
<StoryFragment>
<FormField label={"Toast limit"}>
<Input
localizationOptions={{maximumFractionDigits: 0}}
name={"price"}
type={"number"}
onChange={(e) => setState({...state, limit: e.currentTarget.value})}
value={state.limit}
placeholder={"3"}
/>
</FormField>

<CheckboxInput
onSelect={() => setState({...state, autoCloseToasts: !state.autoCloseToasts})}
isSelected={state.autoCloseToasts}
item={{
id: "autoCloseToasts",
content: "autoCloseToasts",
inputProps: {
name: "termsAndConditions",
htmlFor: "termsAndConditions",
value: "yes"
}
}}
/>
<ToastContextProvider
limit={Boolean(state.limit) && parseInt(state.limit)}
autoCloseToasts={state.autoCloseToasts}>
<ToastExamples />
</ToastContextProvider>
</StoryFragment>
)}
</StateProvider>
));
13 changes: 12 additions & 1 deletion stories/utils/StateProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import React, {Fragment, useState} from "react";

function StateProvider({children, initialState}) {
interface StateProviderProps<State> {
children: (
state: State,
dispatch: React.Dispatch<React.SetStateAction<State>>
) => React.ReactNode;
initialState: State;
}

function StateProvider<State extends Record<string, any>>({
children,
initialState
}: StateProviderProps<State>) {
const [state, setState] = useState(initialState);

return <Fragment>{children(state, setState)}</Fragment>;
Expand Down

0 comments on commit dcd9acf

Please sign in to comment.