From 2f93670d1438b1d869a5f95d000abccc110222f5 Mon Sep 17 00:00:00 2001 From: Siddharth Kshetrapal Date: Tue, 12 Apr 2022 14:59:51 +0200 Subject: [PATCH 01/11] ADR: Use Box to author new components --- contributor-docs/adrs/adr-005-box-sx.md | 197 ++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 contributor-docs/adrs/adr-005-box-sx.md diff --git a/contributor-docs/adrs/adr-005-box-sx.md b/contributor-docs/adrs/adr-005-box-sx.md new file mode 100644 index 00000000000..497f7838ef7 --- /dev/null +++ b/contributor-docs/adrs/adr-005-box-sx.md @@ -0,0 +1,197 @@ +# ADR 005: Using Box for building components + +## Status + +Proposed + +## Context + +There are multiple ways to create components within primer/react and composing components from primer/react. + +The 2 popular methods are: + +1. Creating components with styled-components + + ```tsx + const Avatar = styled.img.attrs(props => ({ + height: props.size, + width: props.size + }))` + display: inline-block; + overflow: hidden; + line-height: ${get('lineHeights.condensedUltra')}; + border-radius: ${props => getBorderRadius(props)}; + ${sx} + ` + ``` + +
+ show full code example: + + ```tsx + import styled from 'styled-components' + import {get} from './constants' + import sx, {SxProp} from './sx' + import {ComponentProps} from './utils/types' + + // this component accepts sx prop + type StyledAvatarProps = { ... } & SxProp + + export const Avatar = styled.img.attrs(props => ({ + height: props.size, + width: props.size + }))` + display: inline-block; + overflow: hidden; + // get from theme with util + line-height: ${get('lineHeights.condensedUltra')}; + // value can be a function + border-radius: ${props => getBorderRadius(props)}; + // accepts sx prop + ${sx} + ` + + // default props need to be defined after the component + Avatar.defaultProps = { + size: 20, + alt: '', + square: false + } + + // border radius can come from a function + const getBorderRadius = ({size, square}: StyledAvatarProps) => { + if (square) { + return size && size <= 24 ? '4px' : '6px' + } else { + return '50%' + } + } + + // types are exported with util + export type AvatarProps = ComponentProps + ``` + + Rendered output: + + ```html + + + ``` + +
+ +2. Creating components with Box + + ```tsx + const Avatar: React.FC = ({size = 20, alt = '', square = false, sx = {}, ...props}) => { + const styles = { + display: 'inline-block', + overflow: 'hidden', + lineHeight: 'condensedUltra', + borderRadius: getBorderRadius({size, square}) + } + + return ( + + ) + } + ``` + +
+ show full code example: + + ```tsx + import React from 'react' + import Box from './Box' + import {SxProp, merge} from './sx' + + // this component accepts sx prop (no need for util) + export type AvatarProps = { ... } & SxProp + + // default props are defined on the component + export const Avatar: React.FC = ({size = 20, alt = '', square = false, sx = {}, ...props}) => { + const styles = { + display: 'inline-block', + overflow: 'hidden', + // theme values used with styled-system + lineHeight: 'condensedUltra', + // value can be a function + borderRadius: getBorderRadius({size, square}) + } + + return ( + + ) + } + + const getBorderRadius = ({size, square}: Pick) => { + if (square) { + return size && size <= 24 ? '4px' : '6px' + } else { + return '50%' + } + } + + export default Avatar + ``` + + ```html + + + ``` + +
+ +  + +## Decision + +Prefer using method #2: Creating components with Box for the following reasons: + +- Better authoring experience with typescript. With Box, we can improve the API and autocomplete for consuming primitives. +- The styling library used used is an implementation detail and we should be able to replace it. (Avoid leaky abstractions) +- We have had issues with exporting types, we can increase confidence by keeping the exported types close to what we author. + +  + +This conversation can be extended to overriding stlyes composing components and adding styles to them. We want to use the `sx` prop to add these styles. + +For example, `ActionMenu.Button` uses the `Button` component but adds Menu specific styles to it: + +```tsx +const MenuButton = ({sx = {}, ...props}) => { + // additional styles for Button when used with ActionMenu + const styles = { + '[data-component=trailingIcon]': {marginX: -1} + } + + return ( + +