Skip to content

Commit

Permalink
✨ Allow symbol names for Slot and Fill
Browse files Browse the repository at this point in the history
- Allow symbol names for `Slot` and `Fill`
- Remove `nanoevents` from deps
- Reduce bundle size to 400 B
  • Loading branch information
exah committed Dec 25, 2020
1 parent b2e71a3 commit d3dd985
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 23 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ A super lightweight modern alternative to [`react-slot-fill`](https://github.com
- [x] Render content of sub-component in multiple places
- [x] Speedy - `Fill` and `Slot` communicate directly with each other
- [x] Tested with [`testing-library`](https://testing-library.com)
- [x] Uses [`nanoevents`](https://github.com/ai/nanoevents) under hood
- [x] Only 414 B (including deps)
- [x] Zero dependencies
- [x] Only ~400 B

## 📦 Install

Expand Down
5 changes: 0 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@
"jest": {
"preset": "ts-jest"
},
"dependencies": {
"nanoevents": "^5.1.10"
},
"dependencies": {},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0"
},
Expand Down
15 changes: 10 additions & 5 deletions src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,27 @@ import { Fill, Slot, SlotsProvider } from '.'
let isServer = false
jest.mock('./is-server', () => () => isServer)

const SLOT_NAMES = {
FIRST: 'first-slot',
NESTED: Symbol('nested-slot'),
} as const

const Parent = ({ children }: { children: React.ReactNode }) => (
<SlotsProvider>
<ul>
<li data-testid="slot-first">
<Slot name="first" />
<Slot name={SLOT_NAMES.FIRST} />
</li>
<li data-testid="children">{children}</li>
<li>
<ul>
<li>1</li>
<li data-testid="slot-nested">
<Slot name="nested" />
<Slot name={SLOT_NAMES.NESTED} />
</li>
<li>3</li>
<li data-testid="slot-nested">
<Slot name="nested" />
<Slot name={SLOT_NAMES.NESTED} />
</li>
</ul>
</li>
Expand All @@ -33,13 +38,13 @@ const Parent = ({ children }: { children: React.ReactNode }) => (
)

Parent.First = function ParentFirst() {
return <Fill name="first">First</Fill>
return <Fill name={SLOT_NAMES.FIRST}>First</Fill>
}

Parent.Nested = function ParentNested() {
const [count, setCount] = useState(0)
return (
<Fill name="nested">
<Fill name={SLOT_NAMES.NESTED}>
<button type="button" onClick={() => setCount((s) => s + 1)}>
{count}
</button>
Expand Down
36 changes: 28 additions & 8 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { createNanoEvents, Emitter, EventsMap, Unsubscribe } from 'nanoevents'
import {
createContext,
createElement as h,
Expand All @@ -12,6 +11,15 @@ import {

import isServer from './is-server'

type Unsubscribe = () => void
type Callback = (node: React.ReactNode) => void
type Events = { [K in string | symbol]: Callback }

interface Emitter<E extends Events> {
emit<K extends keyof E>(event: K, node: React.ReactNode): void
on<K extends keyof E>(event: K, cb: E[K]): Unsubscribe
}

const useUniversalEffect = (
effect: React.EffectCallback,
deps: React.DependencyList
Expand All @@ -25,18 +33,30 @@ const useUniversalEffect = (
return useLayoutEffect(effect, deps)
}

interface SlotsEventsMap extends EventsMap {
[event: string]: (node: React.ReactNode) => void
}

const SlotsContext = createContext<Emitter<SlotsEventsMap> | null>(null)
const SlotsContext = createContext<Emitter<Events> | null>(null)

export interface SlotsProviderProps {
children: React.ReactNode
}

export function SlotsProvider(props: SlotsProviderProps) {
const emitter = useMemo(() => createNanoEvents<SlotsEventsMap>(), [])
const emitter = useMemo(function <E extends Events>(): Emitter<E> {
const events: { [K in keyof E]?: E[K][] } = {}
return {
emit(event, node) {
const source = events[event]
if (source) source.forEach((cb) => cb(node))
},
on(event, cb) {
const source = (events[event] = events[event] || [])!
source.push(cb)
return () => {
events[event] = source.filter((item) => item !== cb)
}
},
}
}, [])

return (
<SlotsContext.Provider value={emitter}>
{props.children}
Expand All @@ -55,7 +75,7 @@ function useSlotsContext() {
}

export interface SlotProps {
name: string
name: string | symbol
children?: React.ReactNode
}

Expand Down

0 comments on commit d3dd985

Please sign in to comment.