@primer/components
Version:
Primer react components
105 lines (82 loc) • 3.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _useForceUpdate = require("./use-force-update");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/** createSlots is a factory that can create a
* typesafe Slots + Slot pair to use in a component definition
* For example: ActionList.Item uses createSlots to get a Slots wrapper
* + Slot component that is used by LeadingVisual, Description
*/
const createSlots = slotNames => {
const SlotsContext = /*#__PURE__*/_react.default.createContext({
registerSlot: () => null,
unregisterSlot: () => null,
context: {}
});
/** Slots uses a Double render strategy inspired by [reach-ui/descendants](https://github.com/reach/reach-ui/tree/develop/packages/descendants)
* Slot registers themself with the Slots parent.
* When all the children have mounted = registered themselves in slot,
* we re-render the parent component to render with slots
*/
const Slots = ({
context = {},
children
}) => {
// initialise slots
const slotsDefinition = {};
slotNames.map(name => slotsDefinition[name] = null);
const slotsRef = _react.default.useRef(slotsDefinition);
const rerenderWithSlots = (0, _useForceUpdate.useForceUpdate)();
const [isMounted, setIsMounted] = _react.default.useState(false); // fires after all the effects in children
_react.default.useEffect(() => {
rerenderWithSlots();
setIsMounted(true);
}, [rerenderWithSlots]);
const registerSlot = _react.default.useCallback((name, contents) => {
slotsRef.current[name] = contents; // don't render until the component mounts = all slots are registered
if (isMounted) rerenderWithSlots();
}, [isMounted, rerenderWithSlots]); // Slot can be removed from the tree as well,
// we need to unregister them from the slot
const unregisterSlot = _react.default.useCallback(name => {
slotsRef.current[name] = null;
rerenderWithSlots();
}, [rerenderWithSlots]);
/**
* Slots uses a render prop API so abstract the
* implementation detail of using a context provider.
*/
const slots = slotsRef.current;
return /*#__PURE__*/_react.default.createElement(SlotsContext.Provider, {
value: {
registerSlot,
unregisterSlot,
context
}
}, children(slots));
};
const Slot = ({
name,
children
}) => {
const {
registerSlot,
unregisterSlot,
context
} = _react.default.useContext(SlotsContext);
_react.default.useEffect(() => {
registerSlot(name, typeof children === 'function' ? children(context) : children);
return () => unregisterSlot(name);
}, [name, children, registerSlot, unregisterSlot, context]);
return null;
};
return {
Slots,
Slot
};
};
var _default = createSlots;
exports.default = _default;