UNPKG

react-slottable

Version:

Package that allows to turn react components into slottable ones

68 lines (67 loc) 3.22 kB
import { ComponentType, JSX, ReactNode } from 'react'; import { PascalCase } from '../utils/pascalCase'; type SlotRenderProps<T> = Omit<T, 'children'>; export type StandardSlotsProps<Slot extends string, AdditionalProps extends Partial<Record<Slot, unknown>> = object> = Record<Slot, { children?: ReactNode; }> & AdditionalProps; export type Slottable<Slots extends readonly string[], SlotsProps extends Partial<StandardSlotsProps<Slots[number]>> = StandardSlotsProps<Slots[number]>, ComponentProps extends object = object> = ComponentType<ComponentProps> & { /** * Component attatched to your component after wrapping it with `withSlots`. Lets you to distribute different content in place of related slot outlet. * @param {string} name slot outlet name to which you want to put content of `children` prop * @param {ReactNode} children content that slot outlet's default content will be replaced to */ Slot: <SlotName extends Slots[number]>(props: { name: SlotName; children: ReactNode | ((props: SlotRenderProps<SlotsProps[SlotName]>) => ReactNode); }) => JSX.Element | null; } & { [SlotName in keyof SlotsProps & string as PascalCase<SlotName>]: (props: { children: ReactNode | ((props: SlotRenderProps<SlotsProps[SlotName]>) => ReactNode); }) => JSX.Element; }; /** * Higher-order component that lets you to turn your component into slottable one, * by attaching to it special components that will inject their children into related slot outlets. * @param {ComponentType} Component - component that you want to enrich * @param {readonly string[]} slotNames - slot names that you want to use * @example * import { withSlots, useSlots, SlotsProps as StandardSlotsProps } from 'react-slottable'; * * type Slots = ['header', 'content', 'footer']; * const slots: Slots = ['header', 'content', 'footer']; * * type SlotProps = StandardSlotsProps<Slots, { * header: { test1: string; test2: boolean }; * content: { text: string }; * footer: { test3: number }; * }>; * * type Props = { loading: boolean; children: ReactNode }; * * const Test = ({ loading, children }: Props) => { * const { Header, Content, Footer } = useSlots<Slots, SlotProps>(slots); * * return !loading ? ( * <div> * <Header>default header</Header> * {children} * <div> * <Content text="dynamic text">default text</Content> * </div> * <Footer>default footer</Footer> * </div> * ) : ( * <div>loading</div> * ); * }; * * export const SlottableTest = withSlots<Slots, SlotProps, Props>(Test, slots); * * <SlotsTest loading> * <SlotsTest.Slot name="header">header override</SlotsTest.Slot> * <SlotsTest.Content>{({ text }) => <div>{text}</div>}</SlotsTest.Content> * <SlotsTest.Footer>footer override</SlotsTest.Footer> * </SlotsTest> */ declare const withSlots: <Slot extends string, SlotsProps extends Partial<StandardSlotsProps<Slot, object>> = StandardSlotsProps<Slot, object>, ComponentProps extends object = object>(Component: ComponentType<ComponentProps>, slotNames: readonly Slot[]) => Slottable<readonly Slot[], SlotsProps, ComponentProps>; export default withSlots;