Skip to content

Commit cd38dad

Browse files
committed
Simplify package
1 parent 7c7676f commit cd38dad

18 files changed

+273
-1264
lines changed

.changeset/spicy-snails-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@zemd/react-slottable": major
3+
---
4+
5+
Make it lightweight, get rid of unnecessary burden

README.md

Lines changed: 45 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
# React Slottable for advanced components
1+
# React Slottable for customizable components
22

3-
> Foundation for component libraries with highly customizable approach
3+
> A lightweight concept to customize subcomponents in React
44
55
[![](https://img.shields.io/npm/v/@zemd/react-slottable?color=%230000ff&labelColor=%23000)](https://www.npmjs.com/package/@zemd/react-slottable)
66

7-
The package provides a way to create your components in a customizable, easy-to-use, and maintainable way. Zero dependencies.
8-
9-
With it, you can build your own complex component libraries, composing different components without losing control over their internals, providing a great developer experience to your team.
7+
The package provides a lightweight approach to give your component users ability to customize it's subcomponents easily. The idea is highly inspired by [Material-UI](https://mui.com/x/common-concepts/custom-components/).
108

119
## Usage
1210

@@ -19,128 +17,69 @@ pnpm add -D @zemd/react-slottable
1917

2018
## Writing components
2119

22-
Just look at the example of such a `Button` built with the library:
20+
The core concept of the library is **slot**. A **slot** is a part of a component that can be overridden and/or customized. For example, you want to create a `Calendar`, but you do not want to create a numerous amount of props to customize nested components. Instead, you can divide your components on **slots** and provide your users with the ability to customize them.
21+
22+
Let's create a simple `Button` component with `startDecorator` and `endDecorator` slots to show how it works:
2323

24-
```typescript
25-
import {
26-
type TSlottablePropsFactory,
27-
slottable,
28-
useSlot,
29-
clsx
30-
} from "@zemd/react-slottable";
24+
```tsx
25+
import { type PropsWithSlots, useSlot } from "@zemd/react-slottable";
3126

32-
type TButtonProps = TSlottablePropsFactory<
33-
{
27+
type ButtonProps = PropsWithSlots<
28+
React.PropsWithChildren<{
29+
// here you define your regular component props
3430
fullWidth?: boolean;
3531
disabled?: boolean;
3632
size?: "sm" | "md" | "xl";
3733
variant?: "solid" | "outlined";
3834
color?: "primary" | "secondary";
39-
},
40-
"startDecorator" | "endDecorator" // this is how cool you can define your slots
41-
// in case you want control the type of slots explicitly, you can use an object:
42-
// { startDecorator: typeof MyCustomComponent, endDecorator: React.ElementType }
35+
className?: string;
36+
}>,
37+
["startDecorator", "endDecorator"] // here you define your slots
4338
>;
4439

45-
export const Button = slottable<"button", TButtonProps>(
46-
// params for the component are the same as you would expect from `forwardRef`
47-
function Button(inProps, ref) {
48-
const {
49-
component = "button", // each Slottable component is overridable by default using `component` prop, if you don't need - just Omit it
50-
className, // TSlottablePropsFactory provides `className` prop by default
51-
children, // TSlottablePropsFactory provides `children` prop by default
52-
startDecorator, // TSlottablePropsFactory provides ability to provide kind of `children` element for your slot
53-
endDecorator, // same as ^
54-
slots = {}, // slots are not receiving default value by default
55-
slotProps = {}, // as well as slotProps
56-
disabled, // here we just handle all props that we defined in `TButtonProps`
57-
fullWidth,
58-
size = "md",
59-
variant = "solid",
60-
color = "primary",
61-
...rest
62-
} = inProps;
63-
64-
const props = Object.assign({}, rest, { slots, slotProps }); // building a map with all important props for root slot and slot information
65-
66-
const [SlotRoot, rootProps] = useSlot("root", {
67-
ref, // ref is mandatory for the `root` slot
68-
component,
69-
className: clsx(
70-
"button-default-className", // use Tailwind, jss, or any other styling solution you want
71-
`button__size_${size}`,
72-
`button__color_${color}`,
73-
`button__variant_${variant}`,
74-
"text-blue-400 text-xl text-green-500",
75-
{
76-
"button__width_full": fullWidth
77-
},
78-
className // don't forget that your users would like to customize this prop
79-
),
80-
props,
81-
extraProps: {
82-
// optional `extraProps` field, which can include everything you want to send specifically to the slot component
83-
},
84-
// classNameMergeFn: twMerge, // if you need to normalize classNames you can pass a function reference
85-
});
86-
87-
const [SlotStartDecorator, startDecoratorProps] = useSlot(
88-
"startDecorator",
89-
{
90-
component: slots["startDecorator"] ?? ("div" as ElementType),
91-
className: "start-decorator-className",
92-
props,
93-
}
94-
);
95-
96-
const [SlotEndDecorator, endDecoratorProps] = useSlot("endDecorator", {
97-
component: slots["endDecorator"] ?? ("div" as ElementType),
98-
className: "end-decorator-className",
99-
props,
100-
});
101-
102-
// Wow! How easy to maintain it looks!!! Such declarative!
103-
return (
104-
<SlotRoot {...rootProps}>
105-
{startDecorator ? (
106-
<SlotStartDecorator {...startDecoratorProps}>
107-
{startDecorator}
108-
</SlotStartDecorator>
109-
) : null}
110-
{children}
111-
{endDecorator ? (
112-
<SlotEndDecorator {...endDecoratorProps}>
113-
{endDecorator}
114-
</SlotEndDecorator>
115-
) : null}
116-
</SlotRoot>
117-
);
118-
}
119-
);
40+
const DefaultDecorator: React.FC<{ className?: string }> = ({ className }) => {
41+
return <div className={className}>Default decorator</div>;
42+
};
43+
44+
export const Button: React.FC<ButtonProps> = (rootProps) => {
45+
const { slots, slotProps, ...props } = rootProps;
46+
const StartDecoratorSlot = useSlot("startDecorator", rootProps, {
47+
slot: DefaultDecorator, // provide default decorator, but can be overridden by user
48+
});
49+
const EndDecoratorSlot = useSlot("startDecorator", rootProps);
50+
51+
return (
52+
<button {...props}>
53+
{/* ^^^ do not forget to handle not standard attributes, e.g. fullWidth ...*/}
54+
<StartDecoratorSlot className="class-override" />
55+
{/* ^^^ you can provide default className ^^^ */}
56+
{props.children}
57+
<EndDecoratorSlot />
58+
</button>
59+
);
60+
};
12061
```
12162

12263
Now your users can use this `Button`:
12364

124-
```typescript
125-
import { Button } from "./MyButton";
65+
```tsx
66+
const MyCustomLabelComponent: React.FC = () => {
67+
return <span>My custom label</span>;
68+
};
12669

127-
export function HomePage() {
128-
const buttonRef = useRef(null);
70+
export function HomePage(): React.JSX.Element {
12971
return (
13072
<div>
13173
<Button
132-
// ref={buttonRef} // just in case if you need
133-
startDecorator={<MySvgIcon />}
134-
endDecorator={<span>no icon. just label.</span>}
13574
slots={{
136-
endDecorator: MyCustomLabelComponent
75+
endDecorator: MyCustomLabelComponent,
13776
}}
13877
slotProps={{
13978
startDecorator: {
140-
prop1: "value"
141-
}
79+
prop1: "value",
80+
},
14281
}}
143-
className: "my-custom-button-className"
82+
className="my-custom-button-className"
14483
>
14584
Click me!
14685
</Button>
@@ -149,7 +88,7 @@ export function HomePage() {
14988
}
15089
```
15190

152-
The package exposes three essential parts: `slottable`, the `useSlot` hook, and the `TSlottablePropsFactory` typescript type. Combining them together, you get a backbone for your fantastic components.
91+
As you can see, `StartDecoratorSlot` was predefined with default component, which will be always shown until user overrides it. However, the `EndDecoratorSlot` was not predefined, so it will be empty until user provides a component for it.
15392

15493
## License
15594

README.md.template

Lines changed: 43 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
{{ block badgeNpmVersion packageName="@zemd/react-slottable" color="#0000ff" labelColor="#000" }}
66

7-
The package provides a way to create your components in a customizable, easy-to-use, and maintainable way. Zero dependencies.
8-
9-
With it, you can build your own complex component libraries, composing different components without losing control over their internals, providing a great developer experience to your team.
7+
The package provides a lightweight approach to give your component users ability to customize it's subcomponents easily. The idea is highly inspired by [Material-UI](https://mui.com/x/common-concepts/custom-components/).
108

119
## Usage
1210

@@ -16,128 +14,69 @@ With it, you can build your own complex component libraries, composing different
1614

1715
## Writing components
1816

19-
Just look at the example of such a `Button` built with the library:
17+
The core concept of the library is **slot**. A **slot** is a part of a component that can be overridden and/or customized. For example, you want to create a `Calendar`, but you do not want to create a numerous amount of props to customize nested components. Instead, you can divide your components on **slots** and provide your users with the ability to customize them.
18+
19+
Let's create a simple `Button` component with `startDecorator` and `endDecorator` slots to show how it works:
2020

21-
```typescript
22-
import {
23-
type TSlottablePropsFactory,
24-
slottable,
25-
useSlot,
26-
clsx
27-
} from "@zemd/react-slottable";
21+
```tsx
22+
import { type PropsWithSlots, useSlot } from "@zemd/react-slottable";
2823

29-
type TButtonProps = TSlottablePropsFactory<
30-
{
24+
type ButtonProps = PropsWithSlots<
25+
React.PropsWithChildren<{
26+
// here you define your regular component props
3127
fullWidth?: boolean;
3228
disabled?: boolean;
3329
size?: "sm" | "md" | "xl";
3430
variant?: "solid" | "outlined";
3531
color?: "primary" | "secondary";
36-
},
37-
"startDecorator" | "endDecorator" // this is how cool you can define your slots
38-
// in case you want control the type of slots explicitly, you can use an object:
39-
// { startDecorator: typeof MyCustomComponent, endDecorator: React.ElementType }
32+
className?: string;
33+
}>,
34+
["startDecorator", "endDecorator"] // here you define your slots
4035
>;
4136

42-
export const Button = slottable<"button", TButtonProps>(
43-
// params for the component are the same as you would expect from `forwardRef`
44-
function Button(inProps, ref) {
45-
const {
46-
component = "button", // each Slottable component is overridable by default using `component` prop, if you don't need - just Omit it
47-
className, // TSlottablePropsFactory provides `className` prop by default
48-
children, // TSlottablePropsFactory provides `children` prop by default
49-
startDecorator, // TSlottablePropsFactory provides ability to provide kind of `children` element for your slot
50-
endDecorator, // same as ^
51-
slots = {}, // slots are not receiving default value by default
52-
slotProps = {}, // as well as slotProps
53-
disabled, // here we just handle all props that we defined in `TButtonProps`
54-
fullWidth,
55-
size = "md",
56-
variant = "solid",
57-
color = "primary",
58-
...rest
59-
} = inProps;
60-
61-
const props = Object.assign({}, rest, { slots, slotProps }); // building a map with all important props for root slot and slot information
62-
63-
const [SlotRoot, rootProps] = useSlot("root", {
64-
ref, // ref is mandatory for the `root` slot
65-
component,
66-
className: clsx(
67-
"button-default-className", // use Tailwind, jss, or any other styling solution you want
68-
`button__size_${size}`,
69-
`button__color_${color}`,
70-
`button__variant_${variant}`,
71-
"text-blue-400 text-xl text-green-500",
72-
{
73-
"button__width_full": fullWidth
74-
},
75-
className // don't forget that your users would like to customize this prop
76-
),
77-
props,
78-
extraProps: {
79-
// optional `extraProps` field, which can include everything you want to send specifically to the slot component
80-
},
81-
// classNameMergeFn: twMerge, // if you need to normalize classNames you can pass a function reference
82-
});
83-
84-
const [SlotStartDecorator, startDecoratorProps] = useSlot(
85-
"startDecorator",
86-
{
87-
component: slots["startDecorator"] ?? ("div" as ElementType),
88-
className: "start-decorator-className",
89-
props,
90-
}
91-
);
92-
93-
const [SlotEndDecorator, endDecoratorProps] = useSlot("endDecorator", {
94-
component: slots["endDecorator"] ?? ("div" as ElementType),
95-
className: "end-decorator-className",
96-
props,
97-
});
98-
99-
// Wow! How easy to maintain it looks!!! Such declarative!
100-
return (
101-
<SlotRoot {...rootProps}>
102-
{startDecorator ? (
103-
<SlotStartDecorator {...startDecoratorProps}>
104-
{startDecorator}
105-
</SlotStartDecorator>
106-
) : null}
107-
{children}
108-
{endDecorator ? (
109-
<SlotEndDecorator {...endDecoratorProps}>
110-
{endDecorator}
111-
</SlotEndDecorator>
112-
) : null}
113-
</SlotRoot>
114-
);
115-
}
116-
);
37+
const DefaultDecorator: React.FC<{ className?: string }> = ({ className }) => {
38+
return <div className={className}>Default decorator</div>;
39+
};
40+
41+
export const Button: React.FC<ButtonProps> = (rootProps) => {
42+
const { slots, slotProps, ...props } = rootProps;
43+
const StartDecoratorSlot = useSlot("startDecorator", rootProps, {
44+
slot: DefaultDecorator, // provide default decorator, but can be overridden by user
45+
});
46+
const EndDecoratorSlot = useSlot("startDecorator", rootProps);
47+
48+
return (
49+
<button {...props}>
50+
{/* ^^^ do not forget to handle not standard attributes, e.g. fullWidth ...*/}
51+
<StartDecoratorSlot className="class-override" />
52+
{/* ^^^ you can provide default className ^^^ */}
53+
{props.children}
54+
<EndDecoratorSlot />
55+
</button>
56+
);
57+
};
11758
```
11859

11960
Now your users can use this `Button`:
12061

121-
```typescript
122-
import { Button } from "./MyButton";
62+
```tsx
63+
const MyCustomLabelComponent: React.FC = () => {
64+
return <span>My custom label</span>;
65+
};
12366

124-
export function HomePage() {
125-
const buttonRef = useRef(null);
67+
export function HomePage(): React.JSX.Element {
12668
return (
12769
<div>
12870
<Button
129-
// ref={buttonRef} // just in case if you need
130-
startDecorator={<MySvgIcon />}
131-
endDecorator={<span>no icon. just label.</span>}
13271
slots={{
133-
endDecorator: MyCustomLabelComponent
72+
endDecorator: MyCustomLabelComponent,
13473
}}
13574
slotProps={{
13675
startDecorator: {
137-
prop1: "value"
138-
}
76+
prop1: "value",
77+
},
13978
}}
140-
className: "my-custom-button-className"
79+
className="my-custom-button-className"
14180
>
14281
Click me!
14382
</Button>
@@ -146,7 +85,7 @@ export function HomePage() {
14685
}
14786
```
14887

149-
The package exposes three essential parts: `slottable`, the `useSlot` hook, and the `TSlottablePropsFactory` typescript type. Combining them together, you get a backbone for your fantastic components.
88+
As you can see, `StartDecoratorSlot` was predefined with default component, which will be always shown until user overrides it. However, the `EndDecoratorSlot` was not predefined, so it will be empty until user provides a component for it.
15089

15190
## License
15291

index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)