Skip to content

Commit

Permalink
cleanup typing
Browse files Browse the repository at this point in the history
  • Loading branch information
eokoneyo committed Mar 7, 2024
1 parent 10dd777 commit d7fa09f
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 55 deletions.
57 changes: 29 additions & 28 deletions packages/shared-ux/modal/tabbed/src/context/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,46 @@ import React, {
useCallback,
type PropsWithChildren,
type ReactElement,
type ComponentProps,
type Dispatch,
} from 'react';
import { EuiTab } from '@elastic/eui';
import { type EuiTabProps, type CommonProps } from '@elastic/eui';

interface IDispatchAction {
type: string;
payload: unknown;
payload: any;
}

type IReducer<S> = (state: S, action: IDispatchAction) => S;
export type IModalTabState = Record<string, unknown>;

interface IModalTabActionBtn<S> {
label: string;
handler: (args: { state: S }) => void;
}
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
type IModalMetaState = {
selectedTabId: string | null;
};

type IReducer<S extends IModalTabState> = (state: S, action: IDispatchAction) => S;

export type IModalTabContent<S extends Record<string, unknown>> = (props: {
export type IModalTabContent<S extends IModalTabState> = (props: {
state: S;
dispatch: Dispatch<IDispatchAction>;
}) => ReactElement;

export type IModalTabDeclaration<S extends Record<string, unknown>> = ComponentProps<
typeof EuiTab
> & {
interface IModalTabActionBtn<S> extends CommonProps {
label: string;
handler: (args: { state: S }) => void;
}

export interface IModalTabDeclaration<S extends IModalTabState> extends EuiTabProps {
id: string;
title: string;
initialState?: Partial<S>;
reducer?: IReducer<S>;
content: IModalTabContent<S>;
modalActionBtn: IModalTabActionBtn<S>;
};

interface IModalMetaState {
selectedTabId: string | null;
}

interface IModalContext {
tabs: Array<Exclude<IModalTabDeclaration<Record<string, unknown>>, 'reducer' | 'initialState'>>;
state: { meta: IModalMetaState } & Record<string, Record<string, unknown>>;
interface IModalContext<S extends IModalTabState = IModalTabState> {
tabs: Array<Exclude<IModalTabDeclaration<S>, 'reducer' | 'initialState'>>;
state: { meta: IModalMetaState } & Record<string, S>;
dispatch: Dispatch<IDispatchAction>;
}

Expand All @@ -69,7 +69,7 @@ const ModalContext = createContext<IModalContext>({
});

/**
* @description defines state transition for meta information to manage the modal, new meta action types
* @description defines state transition for meta information to manage the modal, meta action types
* must be prefixed with the string 'META_'
*/
const modalMetaReducer: IReducer<IModalMetaState> = (state, action) => {
Expand All @@ -84,16 +84,17 @@ const modalMetaReducer: IReducer<IModalMetaState> = (state, action) => {
}
};

export function ModalContextProvider<
T extends Array<IModalTabDeclaration<Record<string, unknown>>>
>({
export type IModalContextProviderProps<Tabs extends Array<IModalTabDeclaration<IModalTabState>>> =
PropsWithChildren<{
tabs: Tabs;
selectedTabId: Tabs[number]['id'];
}>;

export function ModalContextProvider<T extends Array<IModalTabDeclaration<IModalTabState>>>({
tabs,
selectedTabId,
children,
}: PropsWithChildren<{
tabs: T;
selectedTabId: T[number]['id'];
}>) {
}: IModalContextProviderProps<T>) {
const modalTabDefinitions = useRef<IModalContext['tabs']>([]);

const initialModalState = useRef<IModalContext['state']>({
Expand All @@ -115,7 +116,7 @@ export function ModalContextProvider<
);

const combineReducers = useCallback(
function (reducers: Record<string, IReducer<Record<string, unknown>>>) {
function (reducers: Record<string, IReducer<IModalTabState>>) {
return (state: IModalContext['state'], action: IDispatchAction) => {
const newState = { ...state };

Expand Down
30 changes: 20 additions & 10 deletions packages/shared-ux/modal/tabbed/src/tabbed_modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from './storybook/setup';

import { TabbedModal } from './tabbed_modal';
import { IModalTabDeclaration } from './context';

export default {
title: 'Modal/Tabbed Modal',
Expand All @@ -35,9 +36,12 @@ export const TrivialExample = (params: TabbedModalStorybookParams) => {
title: 'Hello',
content: () => {
return (
<EuiText>
<p>Click the button to shout a message into the void</p>
</EuiText>
<Fragment>
<EuiSpacer size="m" />
<EuiText>
<p>Click the button to send a message into the void</p>
</EuiText>
</Fragment>
);
},
initialState: {
Expand All @@ -60,10 +64,6 @@ export const TrivialExample = (params: TabbedModalStorybookParams) => {
TrivialExample.argTypes = argTypes;

export const NonTrivialExample = (params: TabbedModalStorybookParams) => {
enum ACTION_TYPES {
SelectOption,
}

const checkboxGroupItemId1 = useGeneratedHtmlId({
prefix: 'checkboxGroupItem',
suffix: 'first',
Expand Down Expand Up @@ -95,7 +95,13 @@ export const NonTrivialExample = (params: TabbedModalStorybookParams) => {
},
];

const pizzaSelector = {
enum ACTION_TYPES {
SelectOption,
}

const pizzaSelector: IModalTabDeclaration<{
checkboxIdToSelectedMap: Record<string, boolean>;
}> = {
id: 'order',
title: 'Pizza of choice',
initialState: {
Expand All @@ -104,7 +110,7 @@ export const NonTrivialExample = (params: TabbedModalStorybookParams) => {
},
},
reducer(state, action) {
switch (String(action.type)) {
switch (action.type) {
case String(ACTION_TYPES.SelectOption):
return {
...state,
Expand All @@ -125,7 +131,10 @@ export const NonTrivialExample = (params: TabbedModalStorybookParams) => {
},
};

dispatch({ type: ACTION_TYPES.SelectOption, payload: newCheckboxIdToSelectedMap });
dispatch({
type: String(ACTION_TYPES.SelectOption),
payload: newCheckboxIdToSelectedMap,
});
};

return (
Expand All @@ -151,6 +160,7 @@ export const NonTrivialExample = (params: TabbedModalStorybookParams) => {
},
};

// TODO: fix type mismatch
return (
<TabbedModal
{...params}
Expand Down
30 changes: 13 additions & 17 deletions packages/shared-ux/modal/tabbed/src/tabbed_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
* Side Public License, v 1.
*/

import React, {
useMemo,
Fragment,
type ComponentProps,
type PropsWithChildren,
type FC,
useCallback,
} from 'react';
import React, { useMemo, Fragment, type ComponentProps, type FC, useCallback } from 'react';
import {
EuiButton,
EuiModal,
Expand All @@ -24,13 +17,19 @@ import {
EuiTabs,
EuiTab,
} from '@elastic/eui';
import { ModalContextProvider, useModalContext } from './context';
import {
ModalContextProvider,
useModalContext,
type IModalTabState,
type IModalTabDeclaration,
type IModalContextProviderProps,
} from './context';

interface ITabbedModal extends Pick<ComponentProps<typeof EuiModal>, 'onClose'> {
interface ITabbedModalInner extends Pick<ComponentProps<typeof EuiModal>, 'onClose'> {
modalTitle?: string;
}

const TabbedModalInner: FC<ITabbedModal> = ({ onClose, modalTitle }) => {
const TabbedModalInner: FC<ITabbedModalInner> = ({ onClose, modalTitle }) => {
const { tabs, state, dispatch } = useModalContext();

const selectedTabId = state.meta.selectedTabId;
Expand All @@ -54,7 +53,6 @@ const TabbedModalInner: FC<ITabbedModal> = ({ onClose, modalTitle }) => {
return tabs.map((tab, index) => (
<EuiTab
key={index}
href={tab.href}
onClick={() => onSelectedTabChanged(tab.id)}
isSelected={tab.id === selectedTabId}
disabled={tab.disabled}
Expand Down Expand Up @@ -95,16 +93,14 @@ const TabbedModalInner: FC<ITabbedModal> = ({ onClose, modalTitle }) => {
);
};

export const TabbedModal = ({
export function TabbedModal<T extends Array<IModalTabDeclaration<IModalTabState>>>({
tabs,
selectedTabId,
...rest
}: PropsWithChildren<
Omit<ComponentProps<typeof ModalContextProvider>, 'children'> & ITabbedModal
>) => {
}: Omit<IModalContextProviderProps<T>, 'children'> & ITabbedModalInner) {
return (
<ModalContextProvider tabs={tabs} selectedTabId={selectedTabId}>
<TabbedModalInner {...rest} />
</ModalContextProvider>
);
};
}

0 comments on commit d7fa09f

Please sign in to comment.