Skip to content

Commit

Permalink
feat(Case): add as props & refactor code. (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaywcjlove committed Sep 23, 2023
1 parent 7b9e5d8 commit 0cfc8b8
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 39 deletions.
29 changes: 26 additions & 3 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ import { Switch, Case, Default } from '@uiw/react-only-when/switch'

<Switch>
<Case condition={age < 6}>preschool</Case>
<Case condition={age >= 6}>primary school</Case>
<Case as="div" condition={age >= 6}>primary school</Case>
<Default>you graduated</Default>
</Switch>
```
Expand All @@ -113,18 +113,41 @@ export default function App() {
const [age, setAge] = useState(19)
return (
<Fragment>
<input type="range" onChange={(evn) => setAge(Number(evn.target.value))} /> {age}
<input type="range" onChange={(evn) => setAge(Number(evn.target.value))} /> {age}<br />
<Switch>
<Case condition={age < 6}>Preschool</Case>
<Case condition={age >= 6 && age < 18}>Primary school</Case>
<Case condition={age >= 18}>Went to college</Case>
<Case condition={age >= 18 && age < 60}>Went to college</Case>
<Default>you graduated</Default>
</Switch>
</Fragment>
);
}
```

Defaults to specifying a wrapped HTML Element.

```jsx mdx:preview&background=#fff&codePen=true
import React, { useState, Fragment } from 'react';
import { Switch, Case, Default } from '@uiw/react-only-when/switch'

export default function App() {
const [age, setAge] = useState(19)
return (
<Fragment>
<input type="range" onChange={(evn) => setAge(Number(evn.target.value))} /> {age}
<br />
<Switch>
<Case as="span" condition={age < 6}>Preschool</Case>
<Case as="em" condition={age >= 6 && age < 18}>Primary school</Case>
<Case as="div" condition={age >= 18 && age < 60}>Went to college</Case>
<Default as="p">you graduated</Default>
</Switch>
</Fragment>
);
}
```

## `<Only />` props

| prop name | type | default | isRequired | description |
Expand Down
30 changes: 30 additions & 0 deletions core/src/case.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect, useId } from 'react';
import { type InitialState, useSwitchDispatch } from './switch.store';

type TagType = React.ElementType | keyof JSX.IntrinsicElements;
interface CaseElementProps<T extends TagType> {
as?: T;
readonly condition?: boolean;
}

export type CaseProps<T extends TagType> = CaseElementProps<T> & React.ComponentPropsWithoutRef<T>;

export const Case = <T extends TagType>(props: CaseProps<T>) => {
const ids = useId();
const dispatch = useSwitchDispatch();
const { children, condition, as: Comp, ...reset } = props;
const Elm = Comp as TagType;
const child = Elm ? <Elm {...reset}>{children}</Elm> : children;
const state: InitialState = { [ids]: child, active: { [ids]: !!condition } };
useEffect(() => dispatch(state), [state]);
return null;
};

export const Default = <T extends TagType>(props: CaseProps<T>) => {
const dispatch = useSwitchDispatch();
const { children, as: Comp, ...reset } = props;
const Elm = Comp as TagType;
const child = Elm ? <Elm {...reset}>{children}</Elm> : children;
useEffect(() => dispatch({ default: child }), [props]);
return null;
};
36 changes: 36 additions & 0 deletions core/src/switch.store.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createContext, useContext, useReducer } from 'react';

export type InitialState = {
[key: string]: React.ReactNode;
} & {
default?: React.ReactNode;
active?: Record<string, boolean>;
};

const initialState: InitialState = {};
export const Context = createContext<InitialState>(initialState);

const reducer = (state: InitialState, action: InitialState) => {
action.active = action.active ?? {};
action.active = { ...state.active, ...action.active };
return {
...state,
...action,
};
};

export const useSwitchStore = () => {
return useContext(Context);
};

export function useSwitch() {
return useReducer(reducer, initialState);
}

type Dispatch = React.Dispatch<InitialState>;
export const DispatchSwitch = createContext<Dispatch>(() => {});
DispatchSwitch.displayName = 'OW.DispatchSwitch';

export function useSwitchDispatch() {
return useContext(DispatchSwitch);
}
51 changes: 31 additions & 20 deletions core/src/switch.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { ReactElement, Children } from 'react';
import { FC, PropsWithChildren } from 'react';
import { type FC, type PropsWithChildren } from 'react';
import { DispatchSwitch, Context, useSwitch, useSwitchStore } from './switch.store';
import { Case, Default } from './case';

export * from './case';

export const Switch: FC<PropsWithChildren<{}>> = ({ children }) => {
let matchChild: ReactElement | null = null;
let defaultCase: ReactElement | null = null;
Children.forEach(Children.toArray(children) as ReactElement<PropsWithChildren<CaseProps>>[], (child) => {
if (!matchChild && child.type === Case) {
const { condition } = child.props;
const conditionIsTrue = Boolean(condition);
if (conditionIsTrue) {
matchChild = child;
const [state, dispatch] = useSwitch();
const filteredChildren = [];
const childs = Array.isArray(children) ? children : children ? [children] : null;
if (childs) {
for (let i = 0; i < childs.length; i++) {
const child = childs[i];
if (child && (child.type === Case || child.type === Default)) {
filteredChildren.push(child);
}
} else if (!defaultCase && child.type === Default) {
defaultCase = child;
}
});
return matchChild ?? defaultCase ?? null;
}
return (
<Context.Provider value={state}>
<DispatchSwitch.Provider value={dispatch}>{childs}</DispatchSwitch.Provider>
<Render />
</Context.Provider>
);
};

export interface CaseProps {
readonly condition?: boolean;
}

export const Case: FC<PropsWithChildren<CaseProps>> = ({ children }) => children;
export const Default: FC<PropsWithChildren> = ({ children }) => children;
const Render = () => {
const state = useSwitchStore();
let activeKey;
for (var key in state.active) {
if (state.active[key] === true) {
activeKey = key;
break;
}
}
return state[activeKey ?? 'default'] ?? null;
};
52 changes: 36 additions & 16 deletions test/switch.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* eslint-disable jest/no-conditional-expect */
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { Switch, Case, Default } from '../core/src/switch';


it('<Switch />', () => {
const component = renderer.create(
<Switch></Switch>
Expand All @@ -12,46 +11,67 @@ it('<Switch />', () => {
});

it('<Default />', () => {
const component = renderer.create(
const { container } = render(
<Switch>
<Default>you graduated</Default>
</Switch>
);
const only = component.toJSON();
expect(only).toEqual('you graduated');
expect(container.innerHTML).toEqual('you graduated');
});

it('<Case />', () => {
const component = renderer.create(
const { container } = render(
<Switch>
<Case condition={true}>preschool</Case>
<Default>you graduated</Default>
</Switch>
);
const only = component.toJSON();
expect(only).toEqual('preschool');
expect(container.innerHTML).toEqual('preschool');
});

it('<Case /> condition=true', () => {
const component = renderer.create(
it('<Case />', () => {
const { container } = render(
<Switch>
<Case condition={true}>preschool</Case>
<Case condition={true}>primary school</Case>
<Default>you graduated</Default>
</Switch>
);
const only = component.toJSON();
expect(only).toEqual('preschool');
expect(container.innerHTML).toEqual('preschool');
});

it('<Case /> condition=false', () => {
const component = renderer.create(
it('<Case />', () => {
const { container } = render(
<Switch>
<Case condition={false}>preschool</Case>
<Case condition={false}>primary school</Case>
<Default>you graduated</Default>
</Switch>
);
const only = component.toJSON();
expect(only).toEqual('you graduated');
expect(container.innerHTML).toEqual('you graduated');
});


it('<Case as="span" />', () => {
render(
<Switch>
<Case as="span" data-testid="span" condition={true}>preschool</Case>
</Switch>
);
const span = screen.getByTestId('span');
expect(span.tagName).toEqual('SPAN');
expect(span.innerHTML).toEqual('preschool');
});


it('<Default as="p" />', () => {
render(
<Switch>
<Default as="p" title="test case" data-testid="elm">you graduated</Default>
</Switch>
);
const elm = screen.getByTestId('elm');
expect(elm.tagName).toEqual('P');
expect(elm.innerHTML).toEqual('you graduated');
expect(elm.title).toEqual('test case');
});

0 comments on commit 0cfc8b8

Please sign in to comment.