@blockle/blocks-react-slot 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -21
- package/dist/Slot/Slot.d.ts +5 -1
- package/dist/Slot/Slot.d.ts.map +1 -1
- package/dist/createSlottable.d.ts +1 -0
- package/dist/createSlottable.d.ts.map +1 -1
- package/dist/createSlottable.js +15 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,44 +1,190 @@
|
|
|
1
1
|
# @blockle/blocks-react-slot
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
> Note: The Slot component must be used as a direct child of the Template component.
|
|
3
|
+
A React utility for creating flexible, composable components using the asChild pattern. This package enables components to merge their props with child elements, allowing for powerful composition patterns.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
npm install @blockle/blocks-slot
|
|
8
|
+
npm install @blockle/blocks-react-slot
|
|
11
9
|
```
|
|
12
10
|
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **asChild Pattern**: Merge component props with child elements
|
|
14
|
+
- **Slot Composition**: Use slots as placeholders for dynamic content
|
|
15
|
+
- **Type-Safe**: Full TypeScript support with proper type inference
|
|
16
|
+
- **Prop Merging**: Automatically merges className, style, and other props
|
|
17
|
+
- **Ref Forwarding**: Supports React refs
|
|
18
|
+
|
|
13
19
|
## Usage
|
|
14
20
|
|
|
21
|
+
### Basic Example
|
|
22
|
+
|
|
15
23
|
```tsx
|
|
16
|
-
import { createSlottable } from '@blockle/blocks-slot';
|
|
24
|
+
import { createSlottable } from '@blockle/blocks-react-slot';
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
// Create Template and Slot components with a default element
|
|
27
|
+
const [Template, Slot] = createSlottable('div');
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
const MyComponent = ({ children, asChild, ...props }) => {
|
|
21
30
|
return (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
[ICON]
|
|
25
|
-
<Slot>{asChild}</Slot>
|
|
31
|
+
<Template asChild={asChild} {...props}>
|
|
32
|
+
<Slot>{children}</Slot>
|
|
26
33
|
</Template>
|
|
27
34
|
);
|
|
28
|
-
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Renders as: <div class="container">Content</div>
|
|
38
|
+
<MyComponent className="container">Content</MyComponent>
|
|
39
|
+
|
|
40
|
+
// Renders as: <a href="#" class="container">Link</a>
|
|
41
|
+
<MyComponent className="container" asChild>
|
|
42
|
+
<a href="#">Link</a>
|
|
43
|
+
</MyComponent>
|
|
29
44
|
```
|
|
30
45
|
|
|
46
|
+
### Creating a Button Component
|
|
47
|
+
|
|
31
48
|
```tsx
|
|
32
|
-
import {
|
|
49
|
+
import { createSlottable } from '@blockle/blocks-react-slot';
|
|
50
|
+
|
|
51
|
+
const [Template, Slot] = createSlottable('button');
|
|
52
|
+
|
|
53
|
+
const Button = ({ children, asChild, variant = 'primary', ...props }) => {
|
|
54
|
+
const className = `btn btn-${variant}`;
|
|
33
55
|
|
|
34
|
-
export const App = () => {
|
|
35
56
|
return (
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
<a href="/">Anchor</a>
|
|
40
|
-
</Button> {/* -> <a class="btn">[ICON] Tag a</a> */}
|
|
41
|
-
</>
|
|
57
|
+
<Template asChild={asChild} className={className} {...props}>
|
|
58
|
+
<Slot>{children}</Slot>
|
|
59
|
+
</Template>
|
|
42
60
|
);
|
|
43
|
-
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Renders as a button element
|
|
64
|
+
<Button onClick={handleClick}>Click me</Button>
|
|
65
|
+
|
|
66
|
+
// Renders as a link that looks like a button
|
|
67
|
+
<Button asChild>
|
|
68
|
+
<a href="/home">Go Home</a>
|
|
69
|
+
</Button>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Advanced Composition with Multiple Children
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
const [Template, Slot] = createSlottable('div');
|
|
76
|
+
|
|
77
|
+
const Card = ({ children, asChild, ...props }) => {
|
|
78
|
+
return (
|
|
79
|
+
<Template asChild={asChild} className="card" {...props}>
|
|
80
|
+
<span className="card-decoration">★</span>
|
|
81
|
+
<Slot>{children}</Slot>
|
|
82
|
+
<span className="card-decoration">★</span>
|
|
83
|
+
</Template>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The Slot is replaced with the child, decorations are preserved
|
|
88
|
+
<Card asChild>
|
|
89
|
+
<article>
|
|
90
|
+
<h2>Title</h2>
|
|
91
|
+
<p>Content</p>
|
|
92
|
+
</article>
|
|
93
|
+
</Card>
|
|
94
|
+
// Renders: <article class="card"><span>★</span><h2>Title</h2><p>Content</p><span>★</span></article>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Using noSlot for Simple Prop Merging
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const [Template] = createSlottable('div');
|
|
101
|
+
|
|
102
|
+
const Container = ({ children, asChild, ...props }) => {
|
|
103
|
+
return (
|
|
104
|
+
<Template asChild={asChild} noSlot className="container" {...props}>
|
|
105
|
+
{children}
|
|
106
|
+
</Template>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Direct prop merging without Slot component
|
|
111
|
+
<Container asChild className="wrapper">
|
|
112
|
+
<section className="content">Hello</section>
|
|
113
|
+
</Container>
|
|
114
|
+
// Renders: <section class="container wrapper content">Hello</section>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## API
|
|
118
|
+
|
|
119
|
+
### `createSlottable(defaultElement)`
|
|
120
|
+
|
|
121
|
+
Creates a Template component and Slot component pair.
|
|
122
|
+
|
|
123
|
+
**Parameters:**
|
|
124
|
+
|
|
125
|
+
- `defaultElement`: A valid HTML element tag name (e.g., `'div'`, `'button'`, `'span'`)
|
|
126
|
+
|
|
127
|
+
**Returns:**
|
|
128
|
+
|
|
129
|
+
- `[Template, Slot]`: A tuple containing the Template component and Slot component
|
|
130
|
+
|
|
131
|
+
### Template Component Props
|
|
132
|
+
|
|
133
|
+
| Prop | Type | Description |
|
|
134
|
+
|------|------|-------------|
|
|
135
|
+
| `asChild` | `boolean` | When true, merges props with child element instead of rendering default element |
|
|
136
|
+
| `noSlot` | `boolean` | When true with `asChild`, directly merges props without requiring a Slot component |
|
|
137
|
+
| `children` | `React.ReactNode` | Content to render |
|
|
138
|
+
| `ref` | `React.Ref<Element>` | React ref |
|
|
139
|
+
| ...props | varies | Any props valid for the default element type |
|
|
140
|
+
|
|
141
|
+
### Slot Component
|
|
142
|
+
|
|
143
|
+
A marker component that indicates where child content should be inserted when using `asChild`.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<Slot>{children}</Slot>
|
|
44
147
|
```
|
|
148
|
+
|
|
149
|
+
## Behavior
|
|
150
|
+
|
|
151
|
+
### Without asChild
|
|
152
|
+
|
|
153
|
+
Renders as the default element with provided props:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<Template className="foo">Content</Template>
|
|
157
|
+
// Renders: <div class="foo">Content</div>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### With asChild and Slot
|
|
161
|
+
|
|
162
|
+
Merges Template props with the child element inside Slot:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
<Template asChild className="parent">
|
|
166
|
+
<Slot>
|
|
167
|
+
<a href="#" className="child">Link</a>
|
|
168
|
+
</Slot>
|
|
169
|
+
</Template>
|
|
170
|
+
// Renders: <a href="#" class="parent child">Link</a>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### With asChild and noSlot
|
|
174
|
+
|
|
175
|
+
Directly merges props with the single child element:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<Template asChild noSlot className="parent">
|
|
179
|
+
<a href="#" className="child">Link</a>
|
|
180
|
+
</Template>
|
|
181
|
+
// Renders: <a href="#" class="parent child">Link</a>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Use Cases
|
|
185
|
+
|
|
186
|
+
- **Polymorphic Components**: Create components that can render as different elements
|
|
187
|
+
- **Design Systems**: Build flexible UI components that work with various HTML elements
|
|
188
|
+
- **Accessibility**: Allow semantic HTML while maintaining component styling and behavior
|
|
189
|
+
- **Link Components**: Create button-styled links without nesting interactive elements
|
|
190
|
+
- **Headless UI**: Separate component logic from rendering
|
package/dist/Slot/Slot.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
type SlotProps = {
|
|
2
|
-
children
|
|
2
|
+
children?: React.ReactNode;
|
|
3
3
|
};
|
|
4
|
+
/**
|
|
5
|
+
* Indicates a placeholder for slottable content.
|
|
6
|
+
* The `children` prop is optional; when omitted, `Slot` renders nothing (`null`).
|
|
7
|
+
*/
|
|
4
8
|
export declare const Slot: React.FC<SlotProps>;
|
|
5
9
|
export {};
|
|
6
10
|
//# sourceMappingURL=Slot.d.ts.map
|
package/dist/Slot/Slot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slot.d.ts","sourceRoot":"","sources":["../../src/Slot/Slot.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Slot.d.ts","sourceRoot":"","sources":["../../src/Slot/Slot.tsx"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG;IACf,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CAEpC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSlottable.d.ts","sourceRoot":"","sources":["../src/createSlottable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAc,MAAM,sBAAsB,CAAC;AACzE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,KAAK,aAAa,GAAG;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"createSlottable.d.ts","sourceRoot":"","sources":["../src/createSlottable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAc,MAAM,sBAAsB,CAAC;AACzE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,KAAK,aAAa,GAAG;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,qBAAqB,EACnE,cAAc,EAAE,CAAC,GAChB;IACD,QAAQ,EAAE,KAAK,CAAC,EAAE,CAChB,aAAa,GAAG,gBAAgB,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAC3D;IACD,IAAI,EAAE,OAAO,IAAI;CAClB,CA2GA"}
|
package/dist/createSlottable.js
CHANGED
|
@@ -7,14 +7,26 @@ function createSlottable(defaultElement) {
|
|
|
7
7
|
const Template = ({
|
|
8
8
|
asChild,
|
|
9
9
|
children,
|
|
10
|
-
|
|
10
|
+
noSlot,
|
|
11
11
|
...rootProps
|
|
12
12
|
}) => {
|
|
13
13
|
if (!asChild) {
|
|
14
|
-
|
|
15
|
-
return /* @__PURE__ */ jsx(Tag, { ...tagProps, children });
|
|
14
|
+
return /* @__PURE__ */ jsx(Tag, { ...rootProps, children });
|
|
16
15
|
}
|
|
17
16
|
const childrenArray = Children.toArray(children);
|
|
17
|
+
if (noSlot) {
|
|
18
|
+
if (childrenArray.length !== 1) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const child = childrenArray[0];
|
|
22
|
+
if (!isValidElement(child)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return cloneElement(
|
|
26
|
+
child,
|
|
27
|
+
mergeProps(rootProps, child.props)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
18
30
|
const slotIndex = childrenArray.findIndex((child) => {
|
|
19
31
|
if (!isValidElement(child)) {
|
|
20
32
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockle/blocks-react-slot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Slot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"homepage": "https://github.com/Blockle/blocks#readme",
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@blockle/blocks-core": ">=1.2.x",
|
|
35
|
-
"react": "
|
|
35
|
+
"react": ">=19.1.0"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public",
|