Slots
The @bento/slots package provides a way to customize components using slots.
Allowing developers full control over every part of the component. It
automatically intercepts the slot and slots props of the components
and introduces the requested changes.
This package should be used in conjunction with the @bento/use-props
package.
Installation
npm install --save @bento/slots @bento/use-props
<Source language='tsx' code={ SourceNestedSlots } />
withSlots
The withSlots function is used to create a new component that supports slots.
import { useProps } from '@bento/use-props';
import { withSlots } from '@bento/slots';
export const Button = withSlots('BentoButton', function BentoButton(args) {
const { props, apply } = useProps(args);
});
The withSlots function accepts three arguments:
The name argument does not need to match the name of under which you export
your component but it is recommended to use the same name to avoid confusion.
The reason why we require the name to be unique is because it's used to identify
the component. This allows the component to be targeted with a global slot
override, but it will also be used as displayName to ensure a readable
component name in the React DevTools.
Modifiers
Modifiers are functions that are ran when the supplied component is rendered.
They can be used to modify the component or received props. This allows common
modifications to be shared between components.
Each modifier is a function that takes an object with the following properties:
The modification happen based on the return value of the modifier. If nothing
is returned, the component will be rendered as is. If you want to introduce
a new Component to be rendered, you can return an object with a Component
property. If you want to modify or introduce props that are passed to the
component, you can return an object with a props property. The same applies to
context.
For the props and contex the returned object is merged with the existing
values of their respective properties. So you don't need to merge the existing
values yourself. The Component is always replaced by the returned Component.
In the example below, the modifier function introduces a new prop to the
component:
export function modifier({ Component, context, props }) {
return {
props: {
newProp: 'new value'
}
}
}
This modifier should then be passed in the third argument to withSlots:
import { replaces } from '@bento/slots/modifiers/replaces';
import { override } from '@bento/slots/modifiers/override';
import { withSlots } from '@bento/slots';
import { modifier } from './modifier';
const Button = withSlots('MyButton', function MyButton(props) {
return <button {...props} />;
}, [override, replaces, modifier]);
If you don't want any modifiers to be applied to your component, you can supply
an empty array as the third argument to withSlots:
import { withSlots } from '@bento/slots';
export const Example = withSlots('Example', function Example(props) {
return (
<div {...props}>
<Header slot="header" />
<Body slot="content" />
<Footer slot="footer" />
</div>
);
}, []);
The following modifiers are applied to the created component by default:
override
The override modifier is used to introduce a data-override prop to the
component when it detects that certain overrides are applied to the component.
This attribute makes it easier to determine where the difference between the
original Component and the current rendered component are originating from.
For you as a developer, this means you can easily see which components can be
easily upgrade to the new version without any problems, and which components
would require some additional attention and which areas specifically.
The data-override attribute is a space-separated list of the names that
indicates the following modifications:
style: Custom styling has been applied to the component using the `style
prop.
className: Custom className has been applied to the component using the
className prop.
slot: The component has been modified using slots. When the slots
make changes to style or className, these are also included in the
resulting data-override attribute.
context: The whole component or parent component has been modified using
the replaces modifier and a different component has been rendered
in its place.
import { override } from '@bento/slots/modifiers/override';
replaces
The replaces modifier is used to introduce "global" override of components
in your application. The slot functionality that this package provides is
great for making small changes to a few components, but there might be use-case
where you need to make changes to every instance of a Component, e.g., in the
case of experimentation.
import { replaces } from '@bento/slots/modifiers/replaces';
Slot Merging
Slot merging allows slots to be progressively enhanced as they flow through a component tree. When multiple components define slots for the same slot name, these slots are merged together.
Object Slot Merging
When both parent and child define object slots (props), they are merged with parent taking precedence:
<Component slots={{ label: { className: 'child', id: 'child-id' } }} />
<Child slots={{ label: { className: 'parent', title: 'parent-title' } }} />
Function Slot Merging
When function slots are merged, the parent function becomes active but receives access to all previous implementations via the previous parameter:
import { MergedFunction } from './examples/merged-function.tsx';
const FirstEnhancement = () => (
<BaseComponent slots={{
container: function firstWrapper() {
return <div>First Enhancement</div>;
}
}} />
);
const SecondEnhancement = () => (
<FirstEnhancement slots={{
container: function secondWrapper() {
return <div>Second Enhancement</div>;
}
}} />
);
<ThirdEnhancement slots={{
container: function wrapper({ previous }) {
return (
<div>
{previous[0]()} {/* First Enhancement */}
{previous[1]()} {/* Second Enhancement */}
{previous[2]()} {/* Third Enhancement */}
</div>
);
}
}} />
Function Slot Parameters
Function slots receive an object with:
props: The component's props
original: The original React element
previous: Array of previous slot implementations (child → parent order)
Key Rules
- Object slots: Parent props override child props for same keys
- Function slots: Parent function is called, previous functions available in
previous array
- Mixed types: Function slots take precedence over object slots
- Previous array: Ordered from closest child to furthest ancestor