Component systems comparison
# Systems compared
* Grommet by HP - https://v2.grommet.io
* Fabric by MS - https://developer.microsoft.com/en-us/fabric#/controls/web
* Ant by Alibaba - https://ant.design/components/
* Atlaskit by Atlassian - https://atlaskit.atlassian.com/packages
* Polaris by Shopify - https://polaris.shopify.com/components/get-started#navigation
* BaseUI by Uber - https://baseweb.design/
1. All these libraries are written in Typescript, thus providing good auto-complete support in the IDEs
2. Each library is distributed on the web with an interactive style guide. We'll be using [Storybook](https://storybook.js.org/)
3. They all provide accessibility properties for components, like accessibilityLabel, ariaControls, ariaExpanded, ariaPressed etc
4. Each component is described with "Do"s and "Don't"s
5. Libraries differ in the way the handle 'mega' components (same component doing different things)
# Components compared
* Button
* Table
* Modal
* Grid
* Text Input
## Button
### BaseUI
```jsx
<Button shape={SHAPE.round} startEnhancer={() => <ArrowRight size={24} />} disabled size={SIZE.compact}>Compact size</Button>
```
### Grommet
```jsx
<Button
icon={<Icons.Edit />}
label="Edit"
disabled
primary
onClick={() => {}}
/>
```
### Ant
```jsx
<Button type="danger" disabled block icon="cloud">
Danger
</Button>
```
### Fabric
```jsx
<DefaultButton
data-automation-id="test"
disabled={disabled}
checked={checked}
iconProps={{ iconName: 'Upload' }}
text="Create account"
onClick={alertClicked}
split={true}
aria-roledescription={'split button'}
/>
```
### Polaris
```jsx
<Button loading={false} disabled icon={<Icon source={CirclePlusMinor} />}>Save product</Button>
```
### Atlaskit
```jsx
<Button isLoading={showLoadingState} appearance="primary" isDisabled={true}>
Disabled
</Button>
```
### Conclusions
1. Fabric is the only library using the 'mega' component approach by combining functionality of dropdown, toggle, etc
2. Ant and Fabric use a bad style of providing icons via strings. Other libraries have a separate component for each Icon, thus allowing proper code splitting and tree shaking
3. All libraries support 'type' prop to create basic buttons (primary/danger/etc) without creating new Button components per case
4. Standard html5 attributes are handled (disabled, checked, htmlType)
5. Accessibility is handled by an additional property for screen readers
6. Buttons support 'loading' state
## Text field
### BaseUI
```jsx
<StatefulInput placeholder="Input in an error state" error disabled />
```
### Polaris
```jsx
<TextField
label="Account email"
type="email"
value={this.state.value}
onChange={this.handleChange}
helpText="We’ll use this address if we need to contact you about your account."
/>
```
### Grommet
```jsx
<FormField label="Field label">
<TextInput placeholder="type here" />
</FormField>
```
### Ant
```jsx
<Form.Item label="Password" hasFeedback>
<Input style={{ width: '20%' }} defaultValue="0571" />
</Form.Item>
```
### Fabric
```jsx
<TextField label="Disabled with placeholder" disabled placeholder="I am disabled" />
```
### Atlaskit
```jsx
<label htmlFor="disabled">Disabled</label>
<Textfield
name="disabled"
isDisabled
defaultValue="can't touch this..."
/>
```
### Conclusions
1. Fabric and Polaris are combining both label and text input
2. Atlaskit uses standard HTML5 label and 'htmlFor' which is not very generic. Other libraries have a separate label wrapper
3. Masked input is handled in all libraries by a separate component
## Table
Fabric: No table component
### BaseUI
```jsx
<StyledTable>
<StyledHead>
<SortableHeadCell
title="Name"
direction={this.state.nameDirection}
onSort={() =>
this.handleSort('name', this.state.nameDirection)
}
/>
<SortableHeadCell
title="Age"
direction={this.state.ageDirection}
onSort={() =>
this.handleSort('age', this.state.ageDirection)
}
/>
</StyledHead>
<StyledBody>
{this.getSortedData().map((row, index) => (
<StyledRow key={index}>
{row.map((cell, cellIndex) => (
<StyledCell key={cellIndex}>{cell}</StyledCell>
))}
</StyledRow>
))}
</StyledBody>
</StyledTable>
```
### Atlaskit
```jsx
<DynamicTable
caption={caption}
head={head}
rows={rows}
rowsPerPage={10}
defaultPage={1}
loadingSpinnerSize="large"
isLoading={false}
isFixedSize
defaultSortKey="term"
defaultSortOrder="ASC"
onSort={() => console.log('onSort')}
onSetPage={() => console.log('onSetPage')}
/>
```
### Ant
```jsx
<Table dataSource={data}>
<ColumnGroup title="Name">
<Column title="First Name" dataIndex="firstName" key="firstName" />
<Column title="Last Name" dataIndex="lastName" key="lastName" />
</ColumnGroup>
<Column title="Age" dataIndex="age" key="age" />
<Column title="Address" dataIndex="address" key="address" />
<Column
title="Tags"
dataIndex="tags"
key="tags"
render={tags => (
<span>
{tags.map(tag => (
<Tag color="blue" key={tag}>
{tag}
</Tag>
))}
</span>
)}
/>
<Column
title="Action"
key="action"
render={(text, record) => (
<span>
<a href="javascript:;">Invite {record.lastName}</a>
<Divider type="vertical" />
<a href="javascript:;">Delete</a>
</span>
)}
/>
</Table>
```
### Polaris
```jsx
<DataTable
columnContentTypes={[
'text',
'numeric',
'numeric',
'numeric',
'numeric',
]}
headings={[
'Product',
'Price',
'SKU Number',
'Net quantity',
'Net sales',
]}
rows={rows}
totals={['', '', '', 255, '$155,830.00']}
/>
```
### Grommet
```jsx
<Table>
<TableHeader>
<TableRow>
<TableCell scope="col" border="bottom">
Name
</TableCell>
<TableCell scope="col" border="bottom">
Flavor
</TableCell>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell scope="row">
<strong>Eric</strong>
</TableCell>
<TableCell>Coconut</TableCell>
</TableRow>
<TableRow>
<TableCell scope="row">
<strong>Chris</strong>
</TableCell>
<TableCell>Watermelon</TableCell>
</TableRow>
</TableBody>
</Table>
```
### Conclusions
1. Grommet uses JSX approach (you are expected to write `items.map(item => <TableRow ...>))` and uses same TableCell for both header and content. Not flexible
2. Ant supports both JSX approach and DataSource approach
3. Atlaskit, BaseUI and Ant support custom cell rendering using 'render' prop
4. Datasource approach is very appealing, since it separates data from rendering. It also allows easy table creation by supplying 'dataIndex' - the name of the field to use from the row object, so the mapping is automatic
5. Pagination is provided as a separate component, not part of the Table
6. Sorting and Filtering is supported by all except Grommet
7. All except Grommet support 'loading' state for the table
## Modal
### BaseUI
```jsx
<ModalStateContainer>
{({open, close, isOpen}) => (
<React.Fragment>
<Button onClick={open}>Open Modal</Button>
<Modal onClose={close} isOpen={isOpen}>
<ModalHeader>Hello world</ModalHeader>
<ModalBody>
Proin ut dui sed metus pharetra hend rerit vel non mi.
Nulla ornare faucibus ex, non facilisis nisl. Maecenas
aliquet mauris ut tempus.
</ModalBody>
<ModalFooter>
<ModalButton onClick={close}>Cancel</ModalButton>
<ModalButton onClick={close}>Okay</ModalButton>
</ModalFooter>
</Modal>
</React.Fragment>
)}
</ModalStateContainer>
```
### Grommet
```jsx
{show && <Layer
onEsc={() => setShow(false)}
onClickOutside={() => setShow(false)}
>
<Button label="close" onClick={() => setShow(false)} />
</Layer>}
```
### Atlaskit
```jsx
<ModalDialog
key={name}
actions={
['footer', 'both'].includes(name) ? actions : undefined
}
components={{
Header: name === 'custom header' ? Header : undefined,
Body: name === 'custom body' ? Body : undefined,
Footer: name === 'custom footer' ? Footer : undefined,
Container: 'div',
}}
heading={
['header', 'both'].includes(name)
? `Modal: ${name}`
: undefined
}
onClose={this.close}
width={name === 'custom header' ? 300 : undefined}
{...this.props}
>
<Lorem count="5" />
</ModalDialog>
```
### Polaris
```jsx
<Modal
open={active}
onClose={this.handleChange}
title="Reach more shoppers with Instagram product tags"
primaryAction={{
content: 'Add Instagram',
onAction: this.handleChange,
}}
secondaryActions={[
{
content: 'Learn more',
onAction: this.handleChange,
},
]}
>
<Modal.Section>
<TextContainer>
<p>
Use Instagram posts to share your products with millions of
people. Let shoppers buy from your store without leaving
Instagram.
</p>
</TextContainer>
</Modal.Section>
</Modal>
```
### Ant
```jsx
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
```
### Fabric
```jsx
<Modal
titleAriaId={this._titleId}
subtitleAriaId={this._subtitleId}
isOpen={this.state.showModal}
onDismiss={this._closeModal}
isBlocking={false}
containerClassName={styles.container}
dragOptions={this.state.isDraggable ? this._dragOptions : undefined}
>
<div className={styles.header}>
<span id={this._titleId}>Lorem Ipsum</span>
</div>
<div id={this._subtitleId} className={styles.body}>
<DefaultButton onClick={this._closeModal} text="Close" />
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas lorem nulla, malesuada ut sagittis sit amet, vulputate in
leo. Maecenas vulputate congue sapien eu tincidunt. Etiam eu sem turpis. Fusce tempor sagittis nunc, ut interdum ipsum
</p>
</div>
</Modal>
```
### Conclusions
1. All libraries except Grommet provide a feature-rich component that handles the rendering of the Title, Close Button and bottom actions
2. Fabric a uses 'mega' component approach, while Atlaskit, BaseUI and Polaris use composition of supported Modal parts (Header, Footer, Body, Actions, etc)
3. Ant and Polaris support 'loading' state for the dialog (content is invisible and spinner or skeleton is shown)
4. Customization is provided by className in most libraries (except Shopify, since it has a consistent non-modifiable style)
## Layout primitives
### BaseUI
```jsx
<Block
display="grid"
gridTemplateColumns="repeat(3,1fr)"
justifyItems="center"
gridGap="scale1000"
>
<Inner>1</Inner>
<Inner>2</Inner>
<Inner>3</Inner>
<Inner>4</Inner>
<Inner>5</Inner>
<Inner>6</Inner>
</Block>
<FlexGrid
flexGridColumnCount={3}
flexGridColumnGap="scale800"
flexGridRowGap="scale800"
>
<FlexGridItem {...itemProps}>1</FlexGridItem>
<FlexGridItem {...itemProps}>2</FlexGridItem>
<FlexGridItem {...itemProps}>3</FlexGridItem>
<FlexGridItem {...itemProps}>4</FlexGridItem>
<FlexGridItem {...itemProps}>5</FlexGridItem>
<FlexGridItem {...itemProps}>6</FlexGridItem>
</FlexGrid>
```
### Ant
```jsx
<Row>
<Col span={8}>col-8</Col>
<Col span={8} offset={8}>
col-8
</Col>
</Row>
```
### Grommet
```jsx
<Grid
rows={['xxsmall', 'xsmall']}
columns={['xsmall', 'small']}
gap="small"
areas={[
{ name: 'header', start: [0, 0], end: [1, 0] },
{ name: 'nav', start: [0, 1], end: [0, 1] },
{ name: 'main', start: [1, 1], end: [1, 1] },
]}
>
<Box gridArea="header" background="brand" />
<Box gridArea="nav" background="light-5" />
<Box gridArea="main" background="light-2" />
</Grid>
<Stack anchor="top-right">
<Icons.Notification size="large" />
<Box
background="brand"
pad={{ horizontal: 'xsmall' }}
round
>
<Text>8</Text>
</Box>
</Stack>
```
### Atlaskit
no components
### Polaris
```jsx
<Stack alignment="center">
<Heading>
Order
<br />
#1136
<br />
was paid
</Heading>
<Badge>Paid</Badge>
<Badge>Fulfilled</Badge>
</Stack>
```
### Fabric
```jsx
<Stack styles={stackStyles} tokens={itemAlignmentsStackTokens}>
<Stack.Item align="auto" styles={stackItemStyles}>
<span>Auto-aligned item</span>
</Stack.Item>
<Stack.Item align="stretch" styles={stackItemStyles}>
<span>Stretch-aligned item</span>
</Stack.Item>
<Stack.Item align="baseline" styles={stackItemStyles}>
<span>Baseline-aligned item</span>
</Stack.Item>
<Stack.Item align="start" styles={stackItemStyles}>
<span>Start-aligned item</span>
</Stack.Item>
<Stack.Item align="center" styles={stackItemStyles}>
<span>Center-aligned item</span>
</Stack.Item>
<Stack.Item align="end" styles={stackItemStyles}>
<span>End-aligned item</span>
</Stack.Item>
</Stack>
```
### Conclusions
1. All libraries except Atlaskit have basic building blocks for laying out the components. Either Stack or Grid
2. Basic components are responsive and receive span, offset, etc to support different resolutions