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 new Toolbar component #236

Merged
merged 12 commits into from
Feb 28, 2023
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