Skip to content

Commit

Permalink
feat: add new Toolbar component (#236)
Browse files Browse the repository at this point in the history
* feat: add initial Toolbar and subcomponent declarations

* feat: add initial Toolbar styles structure

* feat: add getContrastColor util fn to simplify setting colours

* feat: add Toolbar container styles

* fix: correct fontSizes of Buttons at various sizes

* feat: add ToolbarButton component and styles

* feat: add ToolbarGroup component and styles

* feat: pass colorScheme in context instead of theme

* feat: map ToolbarIcon size depending on size prop passed into Toolbar

* feat: add ToolbarIconButton component

* feat: update Toolbar stories

* fix: add Button stories for sizing
  • Loading branch information
karrui authored Feb 28, 2023
1 parent 0f38a1f commit 8a6275a
Show file tree
Hide file tree
Showing 16 changed files with 395 additions and 11 deletions.
57 changes: 57 additions & 0 deletions react/src/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,60 @@ LinkPrimary.args = {
variant: 'link',
colorScheme: 'brand.primary',
}

export const Sizes = () => (
<SimpleGrid columns={4} gap="1rem" textAlign="center">
<Text>Solid</Text>
<Text>Outline</Text>
<Text>Clear</Text>
<Text>Reverse</Text>
<Button size="xs" variant="solid">
extra small
</Button>
<Button size="xs" variant="outline">
extra small
</Button>
<Button size="xs" variant="clear" colorScheme="neutral">
extra small
</Button>
<Button size="xs" variant="reverse">
extra small
</Button>
<Button size="sm" variant="solid">
small
</Button>
<Button size="sm" variant="outline">
small
</Button>
<Button size="sm" variant="clear" colorScheme="neutral">
small
</Button>
<Button size="sm" variant="reverse">
small
</Button>
<Button size="md" variant="solid">
medium
</Button>
<Button size="md" variant="outline">
medium
</Button>
<Button size="md" variant="clear" colorScheme="neutral">
medium
</Button>
<Button size="md" variant="reverse">
medium
</Button>
<Button size="lg" variant="solid">
large
</Button>
<Button size="lg" variant="outline">
large
</Button>
<Button size="lg" variant="clear" colorScheme="neutral">
large
</Button>
<Button size="lg" variant="reverse">
large
</Button>
</SimpleGrid>
)
50 changes: 50 additions & 0 deletions react/src/Toolbar/Toolbar.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Spacer, Text } from '@chakra-ui/react'
import { Meta, StoryFn } from '@storybook/react'

import { BxsTimeFive, BxUpload } from '~/icons'

import { Toolbar, ToolbarProps } from './Toolbar'
import { ToolbarButton } from './ToolbarButton'
import { ToolbarDivider } from './ToolbarDivider'
import { ToolbarGroup } from './ToolbarGroup'
import { ToolbarIconButton } from './ToolbarIconButton'

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

const Template: StoryFn<ToolbarProps> = ({ children, ...args }) => {
return (
<Toolbar {...args}>
<Text>1 item selected</Text>
<Spacer />
{children}
<ToolbarGroup>
<ToolbarButton leftIcon={<BxUpload fontSize="1.25rem" />}>
Download
</ToolbarButton>
<ToolbarButton leftIcon={<BxsTimeFive fontSize="1.25rem" />}>
Move
</ToolbarButton>
<ToolbarDivider />
<ToolbarIconButton icon={<BxUpload />} aria-label="Upload" />
<ToolbarDivider />
<ToolbarButton>Cancel</ToolbarButton>
</ToolbarGroup>
</Toolbar>
)
}
export const TemplateExample = Template.bind({})

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

export const SizeXs = Template.bind({})
SizeXs.args = {
size: 'xs',
}
35 changes: 35 additions & 0 deletions react/src/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { PropsWithChildren } from 'react'
import {
createStylesContext,
Flex,
FlexProps,
ThemingProps,
useMultiStyleConfig,
} from '@chakra-ui/react'

import { ToolbarProvider } from './ToolbarContext'

const [ToolbarStylesProvider, useToolbarStyles] = createStylesContext('Toolbar')

export { useToolbarStyles }

export interface ToolbarProps extends PropsWithChildren, FlexProps {
colorScheme?: 'main' | 'neutral' | 'sub'
size?: ThemingProps<'Toolbar'>['size']
}

/**
* Container for the toolbar.
*/
export const Toolbar = ({ children, ...props }: ToolbarProps): JSX.Element => {
const styles = useMultiStyleConfig('Toolbar', props)
return (
<ToolbarProvider {...props}>
<ToolbarStylesProvider value={styles}>
<Flex __css={styles.container} {...props}>
{children}
</Flex>
</ToolbarStylesProvider>
</ToolbarProvider>
)
}
11 changes: 11 additions & 0 deletions react/src/Toolbar/ToolbarButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Button, ButtonProps } from '~/Button'

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

export type ToolbarButtonProps = ButtonProps

export const ToolbarButton = (props: ToolbarButtonProps): JSX.Element => {
const toolbarButtonProps = useToolbarButtonProps()

return <Button variant="clear" {...toolbarButtonProps} {...props} />
}
33 changes: 33 additions & 0 deletions react/src/Toolbar/ToolbarContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createContext, FC, PropsWithChildren, useContext } from 'react'
import { ThemingProps } from '@chakra-ui/react'

export interface ToolbarContextProps {
colorScheme?: 'main' | 'neutral' | 'sub'
size?: ThemingProps<'Toolbar'>['size']
}

export type ToolbarContextReturn = Required<ToolbarContextProps>

const ToolbarContext = createContext<ToolbarContextReturn | undefined>(
undefined,
)

export const ToolbarProvider: FC<PropsWithChildren<ToolbarContextProps>> = ({
children,
colorScheme = 'sub',
size = 'md',
}) => {
return (
<ToolbarContext.Provider value={{ colorScheme, size }}>
{children}
</ToolbarContext.Provider>
)
}

export const useToolbarContext = () => {
const context = useContext(ToolbarContext)
if (!context) {
throw new Error('useToolbar must be used within a ToolbarProvider')
}
return context
}
11 changes: 11 additions & 0 deletions react/src/Toolbar/ToolbarDivider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Divider, DividerProps } from '@chakra-ui/react'

import { useToolbarStyles } from './Toolbar'

export type ToolbarDividerProps = DividerProps

export const ToolbarDivider = (props: ToolbarDividerProps): JSX.Element => {
const styles = useToolbarStyles()

return <Divider orientation="vertical" __css={styles.divider} {...props} />
}
19 changes: 19 additions & 0 deletions react/src/Toolbar/ToolbarGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Wrap, WrapProps } from '@chakra-ui/react'

import { useToolbarStyles } from './Toolbar'

export type ToolbarGroupProps = WrapProps

export const ToolbarGroup = (props: ToolbarGroupProps): JSX.Element => {
const styles = useToolbarStyles()
return (
<Wrap
shouldWrapChildren
overflow="initial"
flexDir="row"
__css={styles.group}
spacing="0.5rem"
{...props}
/>
)
}
13 changes: 13 additions & 0 deletions react/src/Toolbar/ToolbarIconButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IconButton, IconButtonProps } from '~/IconButton'

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

export type ToolbarIconButtonProps = IconButtonProps

export const ToolbarIconButton = (
props: ToolbarIconButtonProps,
): JSX.Element => {
const toolbarButtonProps = useToolbarButtonProps()

return <IconButton variant="clear" {...toolbarButtonProps} {...props} />
}
5 changes: 5 additions & 0 deletions react/src/Toolbar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './Toolbar'
export * from './ToolbarButton'
export * from './ToolbarDivider'
export * from './ToolbarGroup'
export * from './ToolbarIconButton'
42 changes: 42 additions & 0 deletions react/src/Toolbar/utils/useToolbarButtonProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useMemo } from 'react'
import { useBreakpointValue } from '@chakra-ui/react'

import { ButtonProps } from '~/Button'
import { layerStyles } from '~/theme/layerStyles'

import { useToolbarContext } from '../ToolbarContext'

export const useToolbarButtonProps = (): ButtonProps => {
const { colorScheme, size } = useToolbarContext()

const toolbarBreakpointSize = useBreakpointValue(
typeof size === 'string' ? { base: size } : size,
)

const toolbarSize = useMemo(() => {
switch (toolbarBreakpointSize) {
case 'xs':
return 'xs'
default:
return 'sm'
}
}, [toolbarBreakpointSize])

const toolbarButtonStyleProps: Partial<ButtonProps> = useMemo(() => {
switch (colorScheme) {
case 'main':
case 'sub':
return {
colorScheme: 'inverse',
_focusVisible: layerStyles.focusRing.inverse._focusVisible,
}
default:
return { colorScheme }
}
}, [colorScheme])

return {
...toolbarButtonStyleProps,
size: toolbarSize,
}
}
1 change: 1 addition & 0 deletions react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export * from './theme'
export * from './Tile'
export * from './Toast'
export * from './Toggle'
export * from './Toolbar'
export * from './Tooltip'
7 changes: 2 additions & 5 deletions react/src/theme/components/Badge.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineStyle } from '@chakra-ui/react'
import { getColor, SystemStyleObject } from '@chakra-ui/theme-tools'

import { meetsWcagAaRatio } from '~/theme/utils/contrast'
import { getContrastColor } from '~/theme/utils/contrast'

import { textStyles } from '../textStyles'

Expand All @@ -27,10 +27,7 @@ const variantSolid = defineStyle((props) => {
const bgColor = getColor(theme, solidBgTokenMap[c] ?? `${c}.500`)
let textColor = getColor(theme, 'base.content.inverse')

const hasSufficientContrast = meetsWcagAaRatio(textColor, bgColor)
if (!hasSufficientContrast) {
textColor = 'base.content.default'
}
textColor = getContrastColor(textColor, bgColor, 'base.content.default')

return {
bg: bgColor,
Expand Down
15 changes: 9 additions & 6 deletions react/src/theme/components/Button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { merge } from 'lodash'

import { layerStyles } from '../layerStyles'
import { textStyles } from '../textStyles'
import { meetsWcagAaRatio } from '../utils'
import { getContrastColor } from '../utils'
import { hexToRgba } from '../utils/hexToRgba'

import { Link } from './Link'
Expand Down Expand Up @@ -53,14 +53,13 @@ const genVariantSolidColours = ({
}
}
}
const hasSufficientContrast = meetsWcagAaRatio(
// Note that using the fallback content colour for the button text could still result in bad contrast.
color = getContrastColor(
getColor(theme, color),
getColor(theme, solidVariantProps.bg),
'base.content.default',
)
// Note that using the default content colour for the button text could still result in bad contrast.
if (!hasSufficientContrast) {
color = 'base.content.default'
}

return { ...solidVariantProps, color }
}

Expand Down Expand Up @@ -296,18 +295,22 @@ const baseStyle = defineStyle({

const sizes = {
xs: defineStyle({
...textStyles['subhead-2'],
minH: '2.25rem',
minW: '2.25rem',
}),
sm: defineStyle({
...textStyles['subhead-1'],
minH: '2.5rem',
minW: '2.5rem',
}),
md: defineStyle({
...textStyles['subhead-1'],
minH: '2.75rem',
minW: '2.75rem',
}),
lg: defineStyle({
...textStyles['subhead-1'],
minH: '3rem',
minW: '3rem',
}),
Expand Down
Loading

0 comments on commit 8a6275a

Please sign in to comment.