Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Table component and styling #238

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions react/.storybook/foundations/Colours/ColourTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import {
TableCaption,
Tbody,
Td,
Th,
Thead,
Tr,
useTheme,
} from '@chakra-ui/react'
import { get } from 'lodash'
import { Th } from '~/Table'

interface ColourTableProps {
label: string
Expand All @@ -25,7 +25,7 @@ export const ColourTable = ({
const theme = useTheme()

return (
<Table variant="simple">
<Table colorScheme='neutral'>
<TableCaption>{label}</TableCaption>
<Thead>
<Tr>
Expand Down
4 changes: 2 additions & 2 deletions react/.storybook/foundations/Utilities/Utilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useTheme,
} from '@chakra-ui/react'
import { Th } from '~/Table'

export const Utilities = (): JSX.Element => {
const theme = useTheme()
Expand Down Expand Up @@ -88,7 +88,7 @@ export const Utilities = (): JSX.Element => {
<Text as="h2" textStyle="h2" color="brand.primary.500" mb="1.5rem">
Spacing
</Text>
<Table variant="simple">
<Table colorScheme='neutral'>
<TableCaption>Spacing</TableCaption>
<Thead>
<Tr>
Expand Down
10 changes: 5 additions & 5 deletions react/src/Menu/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@ export const Playground: StoryFn = () => {
</>
)}
</Menu>
<Table variant="simple" width="80%" mt="20px">
<Thead bgColor="#444">
<Table width="80%" mt="20px">
<Thead>
<Tr>
<Th textColor="white">#</Th>
<Th textColor="white">Response ID</Th>
<Th textColor="white">Time</Th>
<Th>#</Th>
<Th>Response ID</Th>
<Th>Time</Th>
</Tr>
</Thead>
<Tbody>
Expand Down
97 changes: 97 additions & 0 deletions react/src/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
Table,
TableCaption,
TableContainer,
TableProps,
Tbody,
Td,
Tfoot,
Thead,
Tr,
} from '@chakra-ui/react'
import { Meta, StoryFn } from '@storybook/react'

import { Th } from './Th'

export default {
title: 'Components/Table',
component: Table,
decorators: [],
tags: ['autodocs'],
} as Meta<TableProps>

const Template: StoryFn<TableProps> = (args) => {
return (
<TableContainer>
<Table {...args}>
<TableCaption>Imperial to metric conversion factors</TableCaption>
<Thead>
<Tr>
<Th>To convert</Th>
<Th>into</Th>
<Th isNumeric>multiply by</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>inches</Td>
<Td>millimetres (mm)</Td>
<Td isNumeric>25.4</Td>
</Tr>
<Tr>
<Td>feet</Td>
<Td>centimetres (cm)</Td>
<Td isNumeric>30.48</Td>
</Tr>
<Tr>
<Td>yards</Td>
<Td>metres (m)</Td>
<Td isNumeric>0.91444</Td>
</Tr>
</Tbody>
<Tfoot>
<Tr>
<Th>To convert</Th>
<Th>into</Th>
<Th isNumeric>multiply by</Th>
</Tr>
</Tfoot>
</Table>
</TableContainer>
)
}
export const Default = Template.bind({})

export const NeutralColorScheme = Template.bind({})
NeutralColorScheme.args = {
colorScheme: 'neutral',
}

export const InteractableHeaders: StoryFn<TableProps> = (args) => {
return (
<TableContainer>
<Table {...args}>
<Thead>
<Tr>
<Th onClick={() => alert('Column 1 clicked')}>Interactable</Th>
<Th>Not interactable</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>inches</Td>
<Td>millimetres (mm)</Td>
</Tr>
<Tr>
<Td>feet</Td>
<Td>centimetres (cm)</Td>
</Tr>
<Tr>
<Td>yards</Td>
<Td>metres (m)</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
)
}
19 changes: 19 additions & 0 deletions react/src/Table/Th.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { TableColumnHeaderProps, useTableStyles } from '@chakra-ui/react'
import { chakra, forwardRef } from '@chakra-ui/system'

import { omitInteractivePseudos } from './utils/omitInteractivePseudos'

export const Th = forwardRef<TableColumnHeaderProps, 'th'>(
({ isNumeric, ...props }, ref) => {
const styles = useTableStyles()
const thStyles = omitInteractivePseudos(props, styles.th)
return (
<chakra.th
{...props}
ref={ref}
__css={thStyles}
data-is-numeric={isNumeric}
/>
)
},
)
14 changes: 14 additions & 0 deletions react/src/Table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Tfoot,
Thead,
Tr,
} from '@chakra-ui/react'

export * from './Th'

export { Table, TableCaption, TableContainer, Tbody, Td, Tfoot, Thead, Tr }
54 changes: 54 additions & 0 deletions react/src/Table/utils/omitInteractivePseudos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { DOMAttributes } from 'react'
import { SystemStyleObject } from '@chakra-ui/react'
import { omit } from 'lodash'

type InteractableProps = Pick<
DOMAttributes<unknown>,
| 'onClick'
| 'onKeyDown'
| 'onKeyUp'
| 'onKeyPress'
| 'onMouseDown'
| 'onMouseUp'
| 'onMouseEnter'
| 'onMouseLeave'
| 'onMouseMove'
| 'onMouseOver'
| 'onMouseOut'
| 'onFocus'
| 'onBlur'
>

const isInteractive = (props: InteractableProps) => {
return (
!!props.onClick ||
!!props.onKeyDown ||
!!props.onKeyUp ||
!!props.onKeyPress ||
!!props.onMouseDown ||
!!props.onMouseUp ||
!!props.onMouseEnter ||
!!props.onMouseLeave ||
!!props.onMouseMove ||
!!props.onMouseOver ||
!!props.onMouseOut ||
!!props.onFocus ||
!!props.onBlur
)
}

const INTERACTIVE_PSEUDO_PROPS = ['_active', '_hover', '_pressed', '_selected']

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is _focus not included here? from mdn docs, it seems to be interactable (also, should we base this off the mdn one?)


/**
* Function to omit interactive pseudos from a style object if the object is not
* interactive (i.e. if it does not have an `onClick`, `onKeyDown` props, etc)
*/
export function omitInteractivePseudos(
props: InteractableProps,
styles: SystemStyleObject,
): SystemStyleObject {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: look like low hanging fruit to do Omit<SystemStyleObject, InteractableProps>

if (!isInteractive(props)) {
return omit(styles, INTERACTIVE_PSEUDO_PROPS)
}
return styles
}
1 change: 1 addition & 0 deletions react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export * from './Searchbar'
export * from './SingleSelect'
export * from './Spinner'
export * from './Switch'
export * from './Table'
export * from './Tabs'
export * from './Tag'
export * from './TagInput'
Expand Down
140 changes: 140 additions & 0 deletions react/src/theme/components/Table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { tableAnatomy as parts } from '@chakra-ui/anatomy'
import {
createMultiStyleConfigHelpers,
SystemStyleObject,
} from '@chakra-ui/react'

import { layerStyles } from '../layerStyles'
import { textStyles } from '../textStyles'

const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys)

const baseStyle = definePartsStyle({
tr: {
pos: 'relative',
},
})

const sizes = {
xs: definePartsStyle({
th: {
py: '0.875rem',
},
td: {
py: '0.875rem',
px: '1rem',
textStyle: 'caption-2',
},
}),
sm: definePartsStyle({
th: {
...textStyles['subhead-2'],
py: '0.875rem',
},
td: {
py: '0.875rem',
px: '1rem',
textStyle: 'caption-2',
},
}),
md: {
th: {
py: '1rem',
},
td: {
textStyle: 'body-2',
p: '1rem',
},
},
lg: {
th: {
py: '1.125rem',
},
td: {
textStyle: 'body-2',
px: '1rem',
py: '1.125rem',
},
},
}

const getSubtleVariantThStyles = (c: string): SystemStyleObject => {
const baseStyles: SystemStyleObject = {
textTransform: 'initial',
_focusVisible: {
...layerStyles.focusRing.tightDefault._focusVisible,
outlineOffset: '-2px',
},
}

switch (c) {
case 'main':
case 'sub':
case 'critical':
case 'warning':
case 'success': {
return {
bg: `interaction.${c}-subtle.default`,
color: `interaction.${c}.default`,
_hover: {
bg: `interaction.${c}-subtle.hover`,
cursor: 'pointer',
},
_active: {
bg: `interaction.${c}-subtle.active`,
},
...baseStyles,
}
}
case 'neutral': {
return {
bg: `interaction.${c}-subtle.default`,
color: 'base.content.medium',
_hover: {
bg: `interaction.${c}-subtle.hover`,
cursor: 'pointer',
},
_active: {
bg: `interaction.${c}-subtle.active`,
},
...baseStyles,
}
}
default: {
return {
bg: `interaction.main-subtle.default`,
...baseStyles,
}
}
}
}

const variantSubtle = definePartsStyle(({ colorScheme: c }) => {
return {
thead: {
bg: `interaction.${c}-subtle.default`,
opacity: 1,
zIndex: 1,
},
th: getSubtleVariantThStyles(c),
td: {
color: 'base.content.default',
},
}
})

const variants = {
subtle: variantSubtle,
}

export const Table = defineMultiStyleConfig({
baseStyle,
variants,
defaultProps: {
variant: 'subtle',
size: 'md',
colorScheme: 'main',
},
sizes,
})
Loading