Skip to content

Commit

Permalink
chore: allow shape prop to use object notation with breakpoints as keys
Browse files Browse the repository at this point in the history
Signed-off-by: Paul-Xavier Ceccaldi <[email protected]>
  • Loading branch information
P1X3L committed Jun 10, 2024
1 parent 34a564d commit 318b7f5
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 27 deletions.
1 change: 1 addition & 0 deletions docs/pages/components/button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Built with [Ariakit](https://ariakit.org/components/button) for a better accessi
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
<Button variant="ghost">Ghost</Button>
<Button shape={{_: 'square', md: 'default', lg: 'circle'}}><WttjIcon /></Button>
```

### States
Expand Down
3 changes: 2 additions & 1 deletion packages/Button/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
"@welcome-ui/box": "^5.16.2",
"@welcome-ui/loader": "^5.17.0",
"@welcome-ui/system": "^5.16.2",
"@welcome-ui/utils": "^5.16.2"
"@welcome-ui/utils": "^5.16.2",
"@welcome-ui/core": "^5.16.2"
},
"devDependencies": {
"@welcome-ui/icons": "^5.16.2",
Expand Down
4 changes: 3 additions & 1 deletion packages/Button/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React from 'react'
import { CreateWuiProps, forwardRef } from '@welcome-ui/system'
import { Box } from '@welcome-ui/box'
import { Loader } from '@welcome-ui/loader'
import type { WuiTheme } from '@welcome-ui/core'

import * as S from './styles'

export type Shape = 'circle' | 'square'
export type ShapeValues = 'circle' | 'square' | 'default'
export type Shape = ShapeValues | Record<keyof WuiTheme['screens'], ShapeValues>
export type Size = 'xxs' | 'xs' | 'sm' | 'md' | 'lg'
export type Variant =
| 'primary'
Expand Down
89 changes: 64 additions & 25 deletions packages/Button/src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,57 @@ import styled, { css, system, th } from '@xstyled/styled-components'
import { Button as AriakitButton } from '@ariakit/react'
import { shouldForwardProp } from '@welcome-ui/system'
import { hideFocusRingsDataAttribute } from '@welcome-ui/utils'
import type { WuiTheme } from '@welcome-ui/core'

import { ButtonOptions } from './index'

const shapeStyles = (size: ButtonOptions['size'], shape: ButtonOptions['shape'] = 'square') => css`
width: ${th(`buttons.sizes.${size}.height`)};
padding: 0;
${shape === 'circle' &&
css`
border-radius: ${th(`buttons.sizes.${size}.height`)};
`};
`
const shapeStyles = (
size: ButtonOptions['size'],
shape: ButtonOptions['shape'],
theme: WuiTheme
) => {
if (!shape) return
const styles = {
circle: css`
width: ${theme.buttons.sizes[size].height};
padding: 0;
border-radius: ${theme.buttons.sizes[size].height};
`,
// square and circle styles must override each other for mediaqueries to be able
// to work as expected
square: css`
width: ${theme.buttons.sizes[size].height};
padding: 0;
border-radius: 0;
`,
default: css`
width: auto;
padding: ${theme.buttons.sizes[size].padding};
border-radius: 0;
`,
}

if (typeof shape === 'string') {
return styles[shape as keyof typeof styles]
}

return Object.keys(shape).map((breakpoint: keyof WuiTheme['screens']) => {
const screenWidth = theme.screens[breakpoint]
if (breakpoint === '_') {
return styles[shape[breakpoint] as keyof typeof styles]
}
if (screenWidth) {
return css`
@media (width >= ${screenWidth}px) {
${styles[shape[breakpoint] as keyof typeof styles]};
}
`
}
})
}

export const Button = styled(AriakitButton).withConfig({ shouldForwardProp })<ButtonOptions>(
({ disabled, shape, size = 'md', variant }) => css`
({ disabled, shape, size = 'md', theme, variant }: ButtonOptions & { theme: WuiTheme }) => css`
${th(`buttons.${variant}`)};
position: relative;
display: inline-flex;
Expand All @@ -33,7 +70,7 @@ export const Button = styled(AriakitButton).withConfig({ shouldForwardProp })<Bu
appearance: none;
overflow: hidden;
transition: medium;
${shape && shapeStyles(size, shape)};
${shapeStyles(size, shape, theme)}};
${system};
& > svg.wui-icon,
Expand All @@ -56,21 +93,23 @@ export const Button = styled(AriakitButton).withConfig({ shouldForwardProp })<Bu
margin-right: sm;
}
${!disabled &&
css`
[${hideFocusRingsDataAttribute}] &:focus {
box-shadow: none;
}
&:focus {
${th(`buttons.focus.${variant || 'primary'}`)};
}
&:hover {
${th(`buttons.hover.${variant || 'primary'}`)};
}
&:active {
${th(`buttons.active.${variant || 'primary'}`)};
}
`};
${
!disabled &&
css`
[${hideFocusRingsDataAttribute}] &:focus {
box-shadow: none;
}
&:focus {
${th(`buttons.focus.${variant || 'primary'}`)};
}
&:hover {
${th(`buttons.hover.${variant || 'primary'}`)};
}
&:active {
${th(`buttons.active.${variant || 'primary'}`)};
}
`
};
&[disabled] {
cursor: not-allowed;
Expand Down
123 changes: 123 additions & 0 deletions packages/Button/tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,39 @@ describe('<Button />', () => {
expect(button).toHaveStyleRule('height', theme.buttons.sizes.sm.height)
})

it('should look like a circle', () => {
const theme = createTheme()

render(
<Button dataTestId="button" shape="circle" size="sm">
{content}
</Button>
)
const button = screen.getByTestId('button')

expect(button).toHaveStyleRule('width', theme.buttons.sizes.sm.height)
expect(button).toHaveStyleRule('padding', '0')
expect(button).toHaveStyleRule('border-radius', theme.buttons.sizes.sm.height)
})

it('should look like the default button', () => {
const theme = createTheme()

render(
// Disabling type check since people wui user can be wrong on the value if not using TS
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
<Button dataTestId="button" shape="wrong-value" size="sm">
{content}
</Button>
)
const button = screen.getByTestId('button')

expect(button).toHaveStyleRule('width', 'auto')
expect(button).toHaveStyleRule('padding', theme.buttons.sizes.sm.padding)
expect(button).toHaveStyleRule('border-radius', theme.buttons.primary.borderRadius)
})

it('should have correct size', () => {
const theme = createTheme()

Expand Down Expand Up @@ -138,6 +171,96 @@ describe('<Button />', () => {
expect(button).toHaveAttribute('rel', 'noopener noreferrer') // added by target="_blank" on Link
})

it('should render width the shape prop being an object', () => {
const theme = createTheme()

render(
<Button dataTestId="button" shape={{ _: 'circle' }} size="sm">
{content}
</Button>
)

const button = screen.getByTestId('button')

expect(button).toHaveStyle({
height: theme.buttons.sizes.sm.height,
})
})

it('should render width the shape prop set as circle for md breakpoint', () => {
const theme = createTheme()
render(
<Button dataTestId="button" shape={{ md: 'circle' }} size="sm">
{content}
</Button>
)

const button = screen.getByTestId('button')

expect(button).toHaveStyleRule('width', theme.buttons.sizes.sm.height, {
media: `(width >= ${theme.screens.md}px)`,
})
expect(button).toHaveStyleRule('padding', '0', {
media: `(width >= ${theme.screens.md}px)`,
})
expect(button).toHaveStyleRule('border-radius', theme.buttons.sizes.sm.height, {
media: `(width >= ${theme.screens.md}px)`,
})
})

it('should render width the shape prop being and object and set as circle (using _)', () => {
const theme = createTheme()
render(
<Button dataTestId="button" shape={{ _: 'circle' }} size="sm">
{content}
</Button>
)

const button = screen.getByTestId('button')

expect(button).toHaveStyleRule('width', theme.buttons.sizes.sm.height)
expect(button).toHaveStyleRule('padding', '0')
expect(button).toHaveStyleRule('border-radius', theme.buttons.sizes.sm.height)
})

it('should render width the shape prop set as circle for _, then default for md and square for lg breakpoints', () => {
const theme = createTheme()
render(
<Button dataTestId="button" shape={{ _: 'circle', md: 'default', lg: 'square' }}>
{content}
</Button>
)

const button = screen.getByTestId('button')

// breakpoint '_'
expect(button).toHaveStyleRule('width', theme.buttons.sizes.md.height)
expect(button).toHaveStyleRule('padding', '0')
expect(button).toHaveStyleRule('border-radius', theme.buttons.sizes.md.height)

// breakpoint 'md'
expect(button).toHaveStyleRule('width', 'auto', {
media: `(width >= ${theme.screens.md}px)`,
})
expect(button).toHaveStyleRule('padding', theme.buttons.sizes.md.padding, {
media: `(width >= ${theme.screens.md}px)`,
})
expect(button).toHaveStyleRule('border-radius', '0', {
media: `(width >= ${theme.screens.md}px)`,
})

// breakpoint 'lg'
expect(button).toHaveStyleRule('width', theme.buttons.sizes.md.height, {
media: `(width >= ${theme.screens.lg}px)`,
})
expect(button).toHaveStyleRule('padding', '0', {
media: `(width >= ${theme.screens.lg}px)`,
})
expect(button).toHaveStyleRule('border-radius', '0', {
media: `(width >= ${theme.screens.lg}px)`,
})
})

it('should have correct Icon size with Icon and text', () => {
const theme = createTheme()

Expand Down

0 comments on commit 318b7f5

Please sign in to comment.