/
Slots.tsx
82 lines (75 loc) · 2.01 KB
/
Slots.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import * as React from 'react';
import { ThemedStyle } from '@gumption-ui/interpolate';
import { mergeProps } from '@gumption-ui/utils';
type Slots = { [slot: string]: ThemedStyle };
const SlotContext = React.createContext<Slots | null>(null);
/**
* Consumes slots props from parents.
*
* @example
* const props = useSlotProps(props, 'title')
*/
export function useSlotProps<T>(
props: T & { slot?: string },
defaultSlot: string,
) {
const slot = props.slot ?? defaultSlot;
const { [slot]: slotProps = {} } = React.useContext(SlotContext) || {};
return mergeProps(slotProps, props);
}
/**
* Consumes slots styles from parents.
*
* @example
* const styles = useSlotStyles('title')
*/
export function useSlotStyles(slot: string) {
const { [slot]: slotStyles = {} } = React.useContext(SlotContext) || {};
return slotStyles;
}
/**
* Provides slots for underlying components.
*
* @example
* <SlotProvider slots={{ Title: { color: 'red.5' } }}>
* {children}
* </SlotProvider>
*/
export function SlotProvider({
slots,
children,
}: {
slots: Slots;
children: React.ReactNode;
}) {
// parentSlots is always the memoized `value`, therefore won't trigger unnecessary re-renders.
const parentSlots = React.useContext(SlotContext) || {}; // eslint-disable-line react-hooks/exhaustive-deps
const value = React.useMemo(
() =>
Object.keys(parentSlots)
.concat(Object.keys(slots))
.reduce(
(accumulator, current) => ({
...accumulator,
[current]: mergeProps(
parentSlots[current] || {},
slots[current] || {},
),
}),
{},
),
[parentSlots, slots],
);
return <SlotContext.Provider value={value}>{children}</SlotContext.Provider>;
}
/**
* Resets/Clears the slots context.
*
* @example
* <ClearSlots>
* {children}
* </ClearSlots>
*/
export function ClearSlots({ children }: { children: React.ReactNode }) {
return <SlotContext.Provider value={{}}>{children}</SlotContext.Provider>;
}