@beqa/react-slots
v1.1.3
Published
The react-slots runtime library
Downloads
110
Maintainers
Readme
beqa/react-slots - Responsible React Parenting
react-slots
empowers you to prioritize composability in your component APIs.
Featuring
- Lightweight (< 8KB minified, < 3KB minified & gzipped)
- Composability with ease
- Type-safety
- Server Components support
- Not implemented with context
- Intuitive API
- Self-documenting with typescript
- Elegant solution to a11y attributes
- Inversion of control
Installation
The installation process consists of two parts: installing the core library (around 3KB gzipped piece of code that runs in your users' browsers and handles the core logic) and an optional compile-time plugin (for transpiling JSX syntax for your slot elements into regular function invocations).
Docs
You can find the docs on the docs website
Discord
If you need any assistance, feel free to join our Discord server
Implementing
import { useSlot, SlotChildren, Slot } from "@beqa/react-slots";
type ListItemProps = {
children: SlotChildren<
| Slot<"title"> // Shorthand of Slot<"title", {}>
| Slot<"thumbnail"> // Shorthand of Slot<"thumbnail", {}>
| Slot<{ isExpanded: boolean }> // Shorthand of Slot<"default", {isExpanded: boolean}>
>;
};
function ListItem({ children }: ListItemProps) {
const { slot } = useSlot(children);
const [isExpanded, setIsExpanded] = useState();
return (
<li
className={`${isExpanded ? "expanded" : "collapsed"}`}
onClick={() => setIsExpanded(!isExpanded)}
>
{/* Render thumbnail if provided, otherwise nothing*/}
<slot.thumbnail />
<div>
{/* Render a fallback if title is not provided*/}
<slot.title>Expand for more</slot.title>
{/* Render the description and pass the prop up to the parent */}
<slot.default isExpanded={isExpanded} />
</div>
</li>
);
}
Specifying Slot Content From the Parent
With slot-name
attribute
<ListItem>
<img slot-name="thumbnail" src="..." />
<div slot-name="title">A title</div>
this is a description
</ListItem>
With Templates
import { template } from "beqa/react-slots";
<ListItem>
<template.thumbnail>
<img src=".." />
</template.thumbnail>
<template.title>A title</template.title>
<template.default>
{({ isExpanded }) =>
isExpanded ? <strong>A description</strong> : "A description"
}
</template.default>
<template.default>doesn't have to be a function</template.default>
</ListItem>;
With type-safe templates
// Option #1
import { createTemplate } from "@beqa/react-slots";
const template = createTemplate<ListItemProps["children"]>();
// Option #2
import { template, CreateTemplate } from "@beqa/react-slots";
const template = template as CreateTemplate<ListItemProps["children"]>;
// Typo-free and auto-complete for props!
<ListItem>
<template.thumbnail>
<img src="..." />
</template.thumbnail>
<template.title>A title</template.title>
<template.default>
{({ isExpanded }) =>
isExpanded ? <strong>A description</strong> : "A description"
}
</template.default>
<template.default>doesn't have to be a function</template.default>
</ListItem>;
Advanced Examples
| The code samples below represent actual implementations. No need to define external state or event handlers for these components to function. | | --------------------------------------------------------------------------------------------------------------------------------------------- |
Creating highly composable Accordion
and AccordionList
components using react-slots
Checkout live example
<AccordionList>
<Accordion key={1}>
<span slot-name="summary">First Accordion</span>
This part of Accordion is hidden
</Accordion>
<Accordion key={2}>
<span slot-name="summary">Second Accordion</span>
AccordionList makes it so that only one Accordion is open at a time
</Accordion>
<Accordion key={3}>
<span slot-name="summary">Third Accordion</span>
No external state required
</Accordion>
</AccordionList>
Creating highly composable Dialog
and DialogTrigger
components using react-slots
Checkout live example
<DialogTrigger>
<Button>Trigger Dialog</Button>
<Dialog slot-name="dialog">
<span slot-name="title">Look Ma, No External State</span>
<p slot-name="content">... And no event handlers.</p>
<p slot-name="content">Closes automatically on button click.</p>
<p slot-name="content">Can work with external state if desired.</p>
<Button
slot-name="secondary"
onClick={() => alert("But how are the button variants different?")}
>
Close??
</Button>
<Button slot-name="primary">Close!</Button>
</Dialog>
</DialogTrigger>
If you like this project please show support by starring it on Github