diff --git a/contributor-docs/adrs/adr-004-children-as-api.md b/contributor-docs/adrs/adr-004-children-as-api.md new file mode 100644 index 00000000000..0dff7e8f0e8 --- /dev/null +++ b/contributor-docs/adrs/adr-004-children-as-api.md @@ -0,0 +1,416 @@ +# ADR 004: Strict props or Composite components + +## Status + +Approved 2022-05-10 + +
+ +_Note: Consumer is used multiple times on this page. It refers to the developers consuming the component API and not end users._ + +
+ +## Decision: + + +1. Prefer using children for “content” + +2. For composite components, the API should be decided by how much customisation is available for children. + +For components that have design decisions baked in, should use strict props. For example, the color of the icon inside a Button component is decided by the `variant` prop on the Button. The API does not allow for changing that. + +```jsx + +``` + +On the other hand, if we want consumers to have more control over children, a composite API is the better choice. + +```jsx + + + + + mona + +``` + +## Prefer using children for “content” + +With React, `children` is the out-of-the-box way for putting [phrasing content](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#phrasing_content) inside your component. By using `children` instead of our own custom prop, we can make the API “predictable” for its consumers. + +image + +```jsx +// prefer this +Changes saved! +// over this + +``` + +

+ +Children as an API for content is an open and composable approach. The contract here is that the `Flash` controls the container while the consumer of the component controls how the contents inside look. + +Take this example of composition: + +flash with icon + +```jsx +import {Flash} from '@primer/react' +import {CheckIcon} from '@primer/octicons-react' + +render( + + Changes saved! + +) +``` + +
+ +### Pros of this approach here: + +1. The component is aware of recommended use cases and comes with those design decisions backed-in. For example, using an `Icon` with `Flash` is a recognised use case. We don’t lock-in a specific icons, but we do set the size, variant-compatible color and the margin between the icon and text. + + For example: + + flash variants + + ```jsx + + Changes saved! + + + Your changes were not saved! + + ``` + +2. You can bring your own icon components, the component does not depend on a specific version of octicons. +3. When a product team wants to explore a use cases which isn’t baked into the component, they are not blocked by our team. + + Example: + + flash with icon and close + + ```jsx + + + Changes saved! + + + +``` + +```jsx +// we prefer this: + +// over these: + + + + + +
+ +--- + +
+ +### 2. Exposing customisation options for internal components: + +Another place where composite patterns lead to aesthetic predictable APIs is when we want to expose customisation options for internal components. + +For Example, [legacy ActionMenu](https://primer.style/react/deprecated/ActionMenu) accepted `overlayProps` and `anchorContent` to pass it down to the implementation details: + +image 10 + +```jsx + + +``` + +
+ +When we see a a prop which resembles “childProps" or `renderChild` on the container/parent, it's a sign that we should surface this detail in the API by creating a composite component: + +```jsx +// we created an additional layer so that +// the overlay props go on the overlay component + + Open column menu + + ... + + +``` + +
+ +--- + +
+ +### 3. Layout components with unstructured content + +In components where there is a place for consumers to fill in freeform or unstructured content, we should prefer the composite children components. This is especially common in the cases of Dialogs, Menus, Groups. + +Consider this fake `Flash` example where description is unstructured content: + +image 11 + +```jsx +// prefer this: + + Changes saved + + These changes will be applied to your next build. Learn more about builds. + + + +// over this: +// Trying to systemise content by finding patterns in +// unconstructured content can lead to overly prescriptive API +// that is not prectictable and hard to remember + +``` + +
+ +_Sidenote: It’s tempting to change `icon` to `Flash.Icon` here so that it’s consistent with the rest of the contents. This is a purely aesthetic optional choice here:_ + +```jsx + + + Changes saved + + These changes will be applied to your next build. Learn more about builds. + + +``` + +
+ +--- + +
+ +We use this pattern in `ActionList` : + +actionlist + +```jsx + + + + mona + Monalisa Octocat + + + + primer-css + GitHub + + +``` + +--- + +
+ +### Case study with Button: + +image 12 + +Prefer using children for “content” + +```jsx +// we prefer: + +``` + +But, we want to discourage customising the Icon’s color and size in the application. So, in the spirit of making the right thing easy and the wrong thing hard, we ask for the component in a prop instead: + +```jsx +// we prefer: + +// over these: + + +``` + + +image 14 + + +We want to add a `Counter` that adapts to the variant without supporting all the props of a `CounterLabel` like `scheme`. + +`Button.Counter` is a restricted version of `CounterLabel`, making the right thing easy and wrong thing hard: + +```jsx +// we prefer: + +// over this: + + +// it's possible to make a strong case for this option as well: + +```