RFC: Shorthand polymorphism using kind
property #512
Description
Feature Request
Problem description
Shorthand is a powerful way how you can define component slots:
<Button icon={{name: "user", size: "micro"}} content="Click me!" />
In the example above, both icon
and content
are shorthands. It allows you to define as few properties as possible while still giving you full control over the slot props. And in all cases shorthand still manages state, styling and accessibility for you.
Shorthand becomes extremely useful when defining collection of children, for example in ButtonGroup
:
<Button.Group
buttons={[
{ key: 'book', icon: 'book', iconOnly: true },
{ key: 'coffee', icon: 'coffee', iconOnly: true },
{ key: 'play', icon: 'play', iconOnly: true },
]}
/>
But what if you would like to add different component to the collection of buttons? What if you want to add a divider?
kind
For that reason we plan to add kind
property to the shorthand definition. With that, you can choose which component will be created using the shorthand:
<Button.Group
buttons={[
{ key: 'book', icon: 'book', iconOnly: true },
{ key: 'coffee', icon: 'coffee', iconOnly: true },
{ kind: 'divider' },
{ key: 'play', icon: 'play', iconOnly: true },
]}
/>
Difference between as
and kind
All Startdust components already support as
property which allows you to override element type in the rendered result. So what is the reason for introducing kind
? What is the difference between the two?
Stardust component defines resulting DOM structure, state, styles and accessibility. With as
, you are overriding ElementType
- that is usually the root element in the resulting DOM structure.
But the original component is still created - with the structure, state, styles and accessibility of the original component.
On the other hand when kind
is used, different component is created - with its own structure, state, styles and accessibility.
To summarize - kind
defines what component is created. as
defines ElementType
of the created component.
It is technically possible to combine the two:
<Button.Group
buttons={[
...
{ kind: 'divider', as: MyCustomDivider },
...
]}
/>
Component implementation
There is no magic happening in factory, it is component's responsibility to handle kind
attribute.
Component should allow the kind
to be only one of predefined options (ButtonGroup
should allow only 'button'
or 'divider'
and default to 'button'
if no kind is specified and nothing else).
Possible use cases considered:
- ButtonGroup - (button), divider
- Form - field
- FormGroup - field
- Menu - (item), divider, header, menu
- Chat - (message), control, divider
- Breadcrumb - divider, content
- List - (item), header, divider
Other syntaxes considered
Dropdown.Divider.create(),
<Dropdown.Divider />,
() => <Dropdown.Divider />,
resolve => resolve(null, () => <Dropdown.Divider />),
[Dropdown.Header, { as: '?', icon: "trash", text: "Move to trash" }],
[Dropdown.Header, 'Move to trash'],
['header', 'Move to trash'],
{ use: Dropdown.Header, as: 'hr', icon: "trash", text: "Move to trash" },
Dropdown.Header.create(
...computedItemProps
{ create: Dropdown.Header, as: 'hr', icon: "trash", text: "Move to trash" },
)
{ is: 'header', as: 'hr', icon: "trash", text: "Move to trash" },
{ use: 'header', as: 'hr', icon: "trash", text: "Move to trash" },
{ kind: 'header', as: 'hr', icon: "trash", text: "Move to trash" },
{ ofType: 'header', as: 'hr', icon: "trash", text: "Move to trash" },
{ create: 'header', as: 'hr', icon: "trash", text: "Move to trash" },
{ component: 'header', as: 'h1' },
{ kind: 'header', as: 'hr', icon: "trash", text: "Move to trash" }, // WINNER
{ kind: 'Header', as: 'hr', icon: "trash", text: "Move to trash" },
{ kind: 'HEADER', as: 'hr', icon: "trash", text: "Move to trash" },
{ kind: Dropdown.Header, as: 'hr', icon: "trash", text: "Move to trash" },
{ kind: Dropdown.KindHeader, as: 'hr', icon: "trash", text: "Move to trash" },
{ kind: KindHeader, as: 'hr', icon: "trash", text: "Move to trash" },
{ kind: 'Dropdown.Header', as: 'hr', icon: "trash", text: "Move to trash" },