diff --git a/core/README.md b/core/README.md index 8ef3a57..71279e7 100644 --- a/core/README.md +++ b/core/README.md @@ -100,7 +100,7 @@ import { Switch, Case, Default } from '@uiw/react-only-when/switch' preschool - = 6}>primary school + = 6}>primary school you graduated ``` @@ -113,11 +113,11 @@ export default function App() { const [age, setAge] = useState(19) return ( - setAge(Number(evn.target.value))} /> {age} + setAge(Number(evn.target.value))} /> {age}
Preschool = 6 && age < 18}>Primary school - = 18}>Went to college + = 18 && age < 60}>Went to college you graduated
@@ -125,6 +125,29 @@ export default function App() { } ``` +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 ( + + setAge(Number(evn.target.value))} /> {age} +
+ + Preschool + = 6 && age < 18}>Primary school + = 18 && age < 60}>Went to college + you graduated + +
+ ); +} +``` + ## `` props | prop name | type | default | isRequired | description | diff --git a/core/src/case.tsx b/core/src/case.tsx new file mode 100644 index 0000000..145f602 --- /dev/null +++ b/core/src/case.tsx @@ -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 { + as?: T; + readonly condition?: boolean; +} + +export type CaseProps = CaseElementProps & React.ComponentPropsWithoutRef; + +export const Case = (props: CaseProps) => { + const ids = useId(); + const dispatch = useSwitchDispatch(); + const { children, condition, as: Comp, ...reset } = props; + const Elm = Comp as TagType; + const child = Elm ? {children} : children; + const state: InitialState = { [ids]: child, active: { [ids]: !!condition } }; + useEffect(() => dispatch(state), [state]); + return null; +}; + +export const Default = (props: CaseProps) => { + const dispatch = useSwitchDispatch(); + const { children, as: Comp, ...reset } = props; + const Elm = Comp as TagType; + const child = Elm ? {children} : children; + useEffect(() => dispatch({ default: child }), [props]); + return null; +}; diff --git a/core/src/switch.store.tsx b/core/src/switch.store.tsx new file mode 100644 index 0000000..d3d76d8 --- /dev/null +++ b/core/src/switch.store.tsx @@ -0,0 +1,36 @@ +import { createContext, useContext, useReducer } from 'react'; + +export type InitialState = { + [key: string]: React.ReactNode; +} & { + default?: React.ReactNode; + active?: Record; +}; + +const initialState: InitialState = {}; +export const Context = createContext(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; +export const DispatchSwitch = createContext(() => {}); +DispatchSwitch.displayName = 'OW.DispatchSwitch'; + +export function useSwitchDispatch() { + return useContext(DispatchSwitch); +} diff --git a/core/src/switch.tsx b/core/src/switch.tsx index 267b9d1..b7a2eb4 100644 --- a/core/src/switch.tsx +++ b/core/src/switch.tsx @@ -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> = ({ children }) => { - let matchChild: ReactElement | null = null; - let defaultCase: ReactElement | null = null; - Children.forEach(Children.toArray(children) as ReactElement>[], (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 ( + + {childs} + + + ); }; -export interface CaseProps { - readonly condition?: boolean; -} - -export const Case: FC> = ({ children }) => children; -export const Default: FC = ({ 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; +}; diff --git a/test/switch.test.tsx b/test/switch.test.tsx index 0471d29..9fd2a82 100644 --- a/test/switch.test.tsx +++ b/test/switch.test.tsx @@ -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('', () => { const component = renderer.create( @@ -12,46 +11,67 @@ it('', () => { }); it('', () => { - const component = renderer.create( + const { container } = render( you graduated ); - const only = component.toJSON(); - expect(only).toEqual('you graduated'); + expect(container.innerHTML).toEqual('you graduated'); }); it('', () => { - const component = renderer.create( + const { container } = render( preschool you graduated ); - const only = component.toJSON(); - expect(only).toEqual('preschool'); + expect(container.innerHTML).toEqual('preschool'); }); -it(' condition=true', () => { - const component = renderer.create( +it('', () => { + const { container } = render( preschool primary school you graduated ); - const only = component.toJSON(); - expect(only).toEqual('preschool'); + expect(container.innerHTML).toEqual('preschool'); }); -it(' condition=false', () => { - const component = renderer.create( +it('', () => { + const { container } = render( preschool primary school you graduated ); - const only = component.toJSON(); - expect(only).toEqual('you graduated'); + expect(container.innerHTML).toEqual('you graduated'); +}); + + +it('', () => { + render( + + preschool + + ); + const span = screen.getByTestId('span'); + expect(span.tagName).toEqual('SPAN'); + expect(span.innerHTML).toEqual('preschool'); +}); + + +it('', () => { + render( + + you graduated + + ); + const elm = screen.getByTestId('elm'); + expect(elm.tagName).toEqual('P'); + expect(elm.innerHTML).toEqual('you graduated'); + expect(elm.title).toEqual('test case'); });