In React there are two ways to make a reusable components
either by using configuration by adding props to configure the component like
```tsx
const Attachment = ({ imageSrc }) => (
<div>
<img src={imageSrc} />
</div>
)
<Attachment src="https://example.com/myimage.png"/>
```
and also by utilizing composition
```tsx
<Attachment>
<AttachmentThumbnail>
<Image src={} />
</AttachmentThumbnail>
</Attachment>
```
both is having its own pros and cons
but for building design system, I will go with [choosing composition over configuration](https://anuradha.hashnode.dev/configuration-vs-composition-design-reusable-components)
because the goal is to make the component as extendable and flexible as possible
why we are choosing composition especially in design system?
- the more we compose to smaller part the more we are flexible / no locking in specific design. for example like in dropdown, we can remove the divider in the Product code level by just removing `<DropdownDivider />` or in attachment if we separate the thumbnail if we later have an attachment list that doesn’t have thumbnail then just not include the thumbnail, doesn’t need to update design system code and add support for that
- the more configuration/props we add the more complex our component and can eventually lead to unmanageable
- give more control to the product level app even if the app need to wrap link with image like
```tsx
<AttachmentThumbnail>
<Link><Image src={} /></Link>
</AttachmentThumbnail>
```
- we can do that without adding additional support in the design system
## Some guide regarding composing
- use Context in the top level to control the child
- usually, there are shared props between child and parents and that is what make us reluctant to use composition. the solution for this is to use context at the most top component. like for example
```tsx
const AttachmentContext = createContext({ status: ATTACHMENT_STATUS.OK })
const Attachment = ({ status }: AttaachmentProps) => <AttachmentContext.Provider value={{ status }}>{}</AttachmentContext.Provider>
const AttachmentDescription = () => {
const { status } = useContext(AttachmentContext);
return <Text variant={status === ATTACHMENT_STATUS.OK ? "" : "">{}</Text>
}
```
- if there is special styled component then build it in the design system, for example lets say the image inside thumbnail can be clickend and will open zoom then we can add support by like
```tsx
<Attachment>
<AttachmentThumbnail>
<AttachmentImage />
</AttachmentThumbnail>
<AttachmentZoom opened={} />
</Attachment>
```
by doing this notice that we
1. doesn’t lose support of the plain image or even the user can have `<AttachmentThumbnail><Icon icon={} /></AttachmentThumbnail>` and also if the Icon inside thumbnail need to be special then we can create `<AttachmentIcon />` . so, to add support for something we create new specific component rather than adding props
2. we can even give control of wheter the zom opened or not to the product code level
- if needed inspiration for component API / Spec, radix ui primitive docs is best place to look. like this one for [AlertDialog](https://www.radix-ui.com/primitives/docs/components/alert-dialog)
- for shared logic, if that shared logic can be extracted into `hooks` or `utils` then it is better to be extracted, for example instead of having a specific component called `NumberInput` we have `numberInput` utils file that consist of `onChange` and `value` helper to transform an `Input` into accepting number only. this also have another benefit that if there is other component that only supporting inputing number, again, we don’t need to add support into that component, can just reuse the `hooks` or `utils` in the product level app
## More extendable by using Radix’s slot component
radix’s **[Slot Component](https://www.radix-ui.com/primitives/docs/utilities/slot)** is actually useful to share logic. we usually already seen an `as` component like
```tsx
<Heading as={h1}>test</Heading>
```
but the problem with that is we can only pass an unrendered component and hard to pass props and usually ended up with adding more props to support what inside the `as` props,
`asChild` is different because the consumer can just render the component they want to be with any props they want to have.
for example the `DropdownTrigger` of Radix UI is more or less just like
```tsx
const DropdownTrigger = ({ asChild, ...buttonProps }) => {
const Comp = asChild ? Slot : "button"; // other trigger logic
return (
<Comp {...buttonProps} onClick={() => { openDropdown(); buttonProps.onClick()} />
)
}
```
that is why we can use it even with a div actually, as long as that element can be passed onClick it work
```tsx
<DropdownTrigger asChild><div>Dropdown</div></DropdownTrigger>
```
if we don’t pass `asChild` it still work and render a button.
it is especially useful for component that most likely have same props. for example Images. if we build `<AttachmentImage />` with `asChild` support then even if in our product app we have super custom image component we can still use it like
```tsx
<AtachmentImage src={} asChild>
<SuperCustomImageComponent />
</AttachmentImage>
```
same with link’s `href`
there is also `Slottable` component if we need to position our `children`
`radix-ui/slot` is currently only installed in `xenith-ui` though I think if needed it is fine if we want to install it in our product code as well if it is helpful
I think equipped with `tailwind` , `cva` , `cn`, and `radix primitives` we can build super generic and composables components