( children: React.ReactNode, config: Config, )
| 33 | * Note: We can only extract direct children, not nested ones. |
| 34 | */ |
| 35 | export function useSlots<Config extends SlotConfig>( |
| 36 | children: React.ReactNode, |
| 37 | config: Config, |
| 38 | ): [Partial<SlotElements<Config>>, React.ReactNode[]] { |
| 39 | // Object mapping slot names to their elements |
| 40 | const slots: Partial<SlotElements<Config>> = mapValues(config, () => undefined) |
| 41 | |
| 42 | // Array of elements that are not slots |
| 43 | const rest: React.ReactNode[] = [] |
| 44 | |
| 45 | const keys = Object.keys(config) as Array<keyof Config> |
| 46 | const values = Object.values(config) |
| 47 | |
| 48 | // eslint-disable-next-line github/array-foreach |
| 49 | React.Children.forEach(children, child => { |
| 50 | if (!React.isValidElement(child)) { |
| 51 | rest.push(child) |
| 52 | return |
| 53 | } |
| 54 | |
| 55 | const index = values.findIndex(value => { |
| 56 | if (Array.isArray(value)) { |
| 57 | const [component, testFn] = value |
| 58 | return child.type === component && testFn(child.props) |
| 59 | } else { |
| 60 | return child.type === value |
| 61 | } |
| 62 | }) |
| 63 | |
| 64 | // If the child is not a slot, add it to the `rest` array |
| 65 | if (index === -1) { |
| 66 | rest.push(child) |
| 67 | return |
| 68 | } |
| 69 | |
| 70 | const slotKey = keys[index] |
| 71 | |
| 72 | // If slot is already filled, ignore duplicates |
| 73 | if (slots[slotKey]) { |
| 74 | warning(true, `Found duplicate "${String(slotKey)}" slot. Only the first will be rendered.`) |
| 75 | return |
| 76 | } |
| 77 | |
| 78 | // If the child is a slot, add it to the `slots` object |
| 79 | |
| 80 | slots[slotKey] = child as SlotValue<Config, keyof Config> |
| 81 | }) |
| 82 | |
| 83 | return [slots, rest] |
| 84 | } |
| 85 | |
| 86 | /** Map the values of an object */ |
| 87 | function mapValues<T extends Record<string, unknown>, V>(obj: T, fn: (value: T[keyof T]) => V) { |
no test coverage detected