Skip to content

Commit

Permalink
feat(quark): add support for parent-child slots
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel Hobl committed Jun 16, 2020
1 parent dbab1c5 commit 919b073
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 7 deletions.
78 changes: 78 additions & 0 deletions packages/quark/src/Slots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from 'react';
import mergeProps from 'merge-props';
import { ThemedStyle } from './interpolate';

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;
}) {
const parentSlots = React.useContext(SlotContext) || {};
const value = React.useMemo(() => {
return Object.keys(parentSlots)
.concat(Object.keys(slots))
.reduce((accumulator, current) => {
return {
...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>;
}
32 changes: 25 additions & 7 deletions packages/quark/src/quark.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as React from 'react';
import { createComponent, createHook, Component } from 'reakit-system';
import { As } from 'reakit-utils';
import cc from 'classcat';
import { css as toClassname } from 'otion';
import { PropsOf, Dict, Theme, FirstParameters } from './types';
import { PropsOf, Dict, Theme } from './types';
import { domElements, DOMElements, get, merge, objectKeys } from './utils';
import { interpolate, ThemedStyle } from './interpolate';
import { useTheme } from './ThemeContext';

type CreateHookOptions = FirstParameters<typeof createHook>;
import { SlotProvider, useSlotStyles } from './Slots';

type Attrs<T extends As> = PropsOf<T>;

Expand Down Expand Up @@ -58,11 +58,9 @@ function styled<T extends As, O extends QuarkOptions, P extends QuarkHTMLProps>(
const baseStyles = getBaseStyles(config);
const modifierStyle = getModifierStyles(config);

// const slotStyles = getSlotStyles(options);

const useQuark = createHook<QuarkOptions, QuarkHTMLProps>({
keys: ['css', '_css', 'variant', 'size'],
useProps(options, htmlProps) {
useProps(options, { wrapElement: htmlWrapElement, ...htmlProps }) {
const theme = useTheme();
const { _css = {}, css = {} } = options;

Expand All @@ -78,7 +76,7 @@ function styled<T extends As, O extends QuarkOptions, P extends QuarkHTMLProps>(
const computedStyles: ThemedStyle = {
...baseStyles(optionsWithTheme),
...modifierStyle(optionsWithTheme),
// ...slotStyles(optionsWithTheme),
...useSlotStyles(name),
...merge(_css, css),
};

Expand All @@ -99,13 +97,33 @@ function styled<T extends As, O extends QuarkOptions, P extends QuarkHTMLProps>(

const { className, ...elementProps } = computedProps;

const slots = get(
theme,
`components.${config?.themeKey}.slots`,
undefined,
);

const wrapElement = React.useCallback(
(element) => {
if (htmlWrapElement) {
element = htmlWrapElement(element); // eslint-disable-line no-param-reassign
}
if (slots) {
return <SlotProvider slots={slots}>{element}</SlotProvider>;
}
return element;
},
[slots, htmlWrapElement],
);

return {
className: cc([
className,
toClassname(interpolate(computedStyles)(theme)),
]),
// Better classNames for debugging
'data-component': name,
wrapElement,
...elementProps,
};
},
Expand Down

0 comments on commit 919b073

Please sign in to comment.