@blockle/blocks-react-slot 1.1.4 → 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 CHANGED
@@ -1,44 +1,190 @@
1
1
  # @blockle/blocks-react-slot
2
2
 
3
- A simple slot component for React.
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
- const [Template, Slot] = createSlottable('button'); // Provide default html tag
26
+ // Create Template and Slot components with a default element
27
+ const [Template, Slot] = createSlottable('div');
19
28
 
20
- export const Button = ({asChild}) => {
29
+ const MyComponent = ({ children, asChild, ...props }) => {
21
30
  return (
22
- // Template setup
23
- <Template className="btn" onClick={() => console.log('clicked')}>
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 { Button } from './Button';
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
- <Button>Button</Button> {/* -> <button class="btn">[ICON] Tag button</button> */}
38
- <Button asChild>
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
@@ -1,6 +1,10 @@
1
1
  type SlotProps = {
2
- children: React.ReactNode;
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
@@ -1 +1 @@
1
- {"version":3,"file":"Slot.d.ts","sourceRoot":"","sources":["../../src/Slot/Slot.tsx"],"names":[],"mappings":"AACA,KAAK,SAAS,GAAG;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,SAAS,CAEpC,CAAC"}
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"}
@@ -5,6 +5,7 @@ type TemplateProps = {
5
5
  asChild?: boolean;
6
6
  children?: React.ReactNode;
7
7
  ref?: React.Ref<Element>;
8
+ noSlot?: boolean;
8
9
  };
9
10
  /**
10
11
  * Create a Template component that can render as a child of another component with asChild prop.
@@ -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;CAC1B,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,CAmFA"}
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"}
@@ -7,14 +7,26 @@ function createSlottable(defaultElement) {
7
7
  const Template = ({
8
8
  asChild,
9
9
  children,
10
- ref,
10
+ noSlot,
11
11
  ...rootProps
12
12
  }) => {
13
13
  if (!asChild) {
14
- const tagProps = { ref, ...rootProps };
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.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "Slot",
5
5
  "type": "module",
6
6
  "exports": {