Widget Patterns - README improvements
- Dominant language
- TypeScript
- Stars
- 92
- Forks
- 63
- PR merge metrics
- No merged PRs in 30d
Description
# Widget Patterns
## Children and Child renderers
If a node is rendered inside a widget, it should be passed as a child rather than via a child renderer. This child could be any `RenderResult`.
If the widget doesn’t need to determine where the children are rendered, or when they are rendered in a single location, then normal children are fine.
```tsx
Hello, World
```
When a widget needs to inject functions or properties into the child widgets, a child function should be used:
```tsx
{ (foo) => }
```
When multiple children are accepted and are to be placed in different locations by the parent widget, a child object should be used. This approach is use for example in our `Card` widget as it accepts content for different sections of the card and renders them in the appropriate locations with wrapping styles / classes. The child object can contain a mix of both `RenderResult` and functions that return a `RenderResult`.
```tsx
{{
foo: (foo) => ,
bar: bar,
baz: 'hello, world'
}}
```
## Partial Control
Widgets should work out of the box wherever possible and be easy to use without needing to be fully controlled. As a result, we have re-written most of our widgets to use the `icache` middleware and maintain their own state.
Form input widgets should accept an `initialValue` which can be used to control the `value` but does not need to be set each time an input calls its `onValue` callback.
In the case of widgets that hide / show such as dialogs or sidepane, control over the `open` and `requestClose` properties will still need to be maintained by the parent.
The lower level inputs, text-input, radio and checkbox are the exceptions to the "uncontrolled" approach as they are generally used as building blocks for more specialised widgets. For example, text-input allows you to control validation whereas the email-input, which utilises a text-input internally, is uncontrolled and manages its own validation state.
In the same fashion, checkboxes and radio buttons are fully controlled but we anticipate them to be used most frequently via checkbox-group and radio-group, both of which are partially controlled and follow the same `initialValue` pattern as other input widgets.
```tsx
// controlled input
let value = 'foo';
{ value = newValue; invalidator(); }} />
// uncontrolled input
```
## Theming composite widgets
When writing composite widgets, widgets that build upon other widgets, there are two options available for passing classes down. These are:
### classes
Classes allow you to pass css classes directly to a child widget. This should be used when you wish to pass a class for positioning or other stylistic need which you do not want to be overridden by a theme. This approach offers little discoverability to our users as they would need to _know_ what widget types you are using within your widget.
```tsx
//in loginform.tsx
//...
login
```
The above code will pass `loginButton` to the root of the button class but as the original `classes` property is being overwritten and not being passed to the `Button`, a user would not actually be able to pass any themes to it. In some cases this may be the desired functionality.
When using `classes`, if you wish to allow a user to pass classes alongside yours, you will need to merge the original `classes` object with your own classes.
```tsx
const { classes } = properties();
const passedButtonClasses = (classes && classes['@dojo/widgets/button']) || {};
const passedButtonRootClasses = passedButtonClasses && passedButtonClasses.root || [];
// ...
login
```
As you can see, the above code is a bit messy and can be hard to follow what is overriding what. When you wish to allow your users to theme and override classes, it is often better to use `theme.compose`.
### theme.compose
Theme compose should be used when you wish to allow a child widget to be easily themed and styled by users and theme creators. It allows the child widgets classes to be targetted via a prefix. This approach works with both a `theme` or via `classes`.
```tsx
//in loginform.tsx
//...
login
```
Now, the root class of the login button can be targeted via a `.loginRoot` css selector in the login form's theme or via `classes` applied to the login form widget.
When working with widget variants, ie. the `email-input`, `theme.compose` can be used without the prefix which allows the same class names to work form both the bass widget and the widget variant.
```tsx
//in emailInput.tsx
//...
```
Wether you have passed a prefix or not to theme.compose, the composed class will always fall back to the base if the variant does not receive a themed class via our theming mechanism.
## Theme Variants
Themes now support `variants`. These allow the themes to be altered by loading new css-variables, to support there are rules that much be used when working on themes and widgets.
### Themes
- individual themes must no longer import and use local variables
- the theme config is now split into `theme` and `variants` sections
- variables now live inside variant files which are wrapped n a single `root` class
- each theme must have at least one variant, named `default`.
```css
// variants/default.m.css
.root {
--variable-one: red;
}
```
### Widgets
- each widget must add the current `variant` class to it's root
- using middleware this is accessed via `theme.variant()`
- using mixin this is accessed via `this.variant()`
- the css-variables for the selected variant will be applied via this class to the widget root
- the `theme` property passed to a widget now optionally includes both a `theme` and a `variant`.
Contributor guide
Assessment
This issue has not been assessed yet.