-
Notifications
You must be signed in to change notification settings - Fork 16
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
Checkbox and Switch UI #12
Changes from all commits
bddee4b
cde3eb5
f2f17af
eea8de4
2c2a6ee
badbae6
25a8349
5c1126e
9e5b451
4ef5224
6a8d275
3bad60e
75a3ced
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import Checkbox from './Checkbox'; | ||
import { fn } from '@storybook/test'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { themeColorSubset } from '../../types.ts'; | ||
|
||
const meta = { | ||
title: 'Components/Checkbox', | ||
component: Checkbox, | ||
argTypes: { | ||
color: { control: 'select', options: Object.keys(themeColorSubset) }, | ||
}, | ||
args: { | ||
onClick: fn() | ||
}, | ||
} satisfies Meta<typeof Checkbox>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
const defaultProps = { | ||
onClick: fn() | ||
}; | ||
|
||
const disableControls = { | ||
parameters: { | ||
controls: { | ||
disable: true | ||
}, | ||
actions: { | ||
disable: true | ||
}, | ||
} | ||
}; | ||
|
||
export const Demo: Story = { | ||
args: { | ||
...defaultProps, | ||
}, | ||
tags: ['excludeFromSidebar'] | ||
}; | ||
|
||
export const Default: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'primary', | ||
}, | ||
...disableControls | ||
}; | ||
|
||
export const Secondary: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'secondary', | ||
onClick: fn() | ||
}, | ||
...disableControls | ||
}; | ||
|
||
export const PrimaryOutline: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'primary', | ||
appearance: 'outline' | ||
}, | ||
...disableControls | ||
}; | ||
|
||
export const SecondaryOutline: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'secondary', | ||
appearance: 'outline' | ||
}, | ||
...disableControls | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import styled from 'styled-components'; | ||
import { ThemeColor, ThemeElementAppearance, ThemeElementSize } from '../../types.ts'; | ||
import { readableColor, shade } from 'polished'; | ||
|
||
type StyledButtonProps = { | ||
$color: ThemeColor; | ||
$appearance: ThemeElementAppearance; | ||
$size: ThemeElementSize; | ||
}; | ||
|
||
export const StyledCheckbox = styled.button<StyledButtonProps>` | ||
display: inline-block; | ||
background: ${props => props.theme.colors[props.$color]}; | ||
color: ${props => readableColor(props.theme.colors[props.$color])}; | ||
appearance: none; | ||
border: 0; | ||
border-radius: 0.25rem; | ||
cursor: pointer; | ||
transition: all 0.3s ease; | ||
|
||
// Handle appearance | ||
${props => { | ||
if (props.$appearance === 'outline') { | ||
return ` | ||
background: transparent; | ||
color: ${props.theme.colors[props.$color]}; | ||
border: 1px solid ${props.theme.colors[props.$color]}; | ||
`; | ||
} | ||
}}; | ||
|
||
// Handle sizes | ||
${props => { | ||
if (props.$size === 'sm') { | ||
return ` | ||
width: 1rem; | ||
height: 1rem; | ||
`; | ||
} | ||
if (props.$size === 'lg') { | ||
return ` | ||
width: 2rem; | ||
height: 2rem; | ||
`; | ||
} | ||
return ` | ||
width: 1.5rem; | ||
height: 1.5rem; | ||
`; | ||
}}; | ||
|
||
&:hover, &:focus, &:active { | ||
text-decoration-color: currentColor; | ||
color: ${props => readableColor(props.theme.colors[props.$color])}; | ||
|
||
${props => props.$appearance === 'outline' ? ` | ||
background: ${props.theme.colors[props.$color]}; | ||
` : ` | ||
background: ${shade(0.15, props.theme.colors[props.$color])}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work using the theming and Polished colour functions! |
||
`} | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import Checkbox from './Checkbox'; | ||
|
||
describe('<Checkbox />', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can wait until next tri, but it would be good to have some more extensive tests here e.g., selecting and deselecting |
||
it('should mount', () => { | ||
render(<Checkbox/>); | ||
|
||
const checkbox = screen.getByTestId('Checkbox'); | ||
|
||
expect(checkbox).toBeInTheDocument(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { FC } from 'react'; | ||
import { StyledCheckbox } from './Checkbox.style'; | ||
import { ThemeColor, ThemeElementAppearance, ThemeElementSize } from '../../types.ts'; | ||
|
||
type CheckboxProps = { | ||
color?: ThemeColor; | ||
onClick: () => void; | ||
appearance?: ThemeElementAppearance; | ||
size?: ThemeElementSize; | ||
} | ||
|
||
const Checkbox: FC<CheckboxProps> = ({ | ||
color = 'primary', | ||
onClick, | ||
appearance = 'solid', | ||
size = 'md' | ||
}) => { | ||
return ( | ||
<StyledCheckbox data-testid="Checkbox" onClick={onClick} $color={color} $appearance={appearance} $size={size}> | ||
{} | ||
</StyledCheckbox> | ||
); | ||
}; | ||
|
||
export default Checkbox; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import Switch from './Switch'; | ||
import { fn } from '@storybook/test'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { themeColorSubset } from '../../types.ts'; | ||
|
||
const meta = { | ||
title: 'Components/Switch', | ||
component: Switch, | ||
argTypes: { | ||
color: { control: 'select', options: Object.keys(themeColorSubset) }, | ||
}, | ||
args: { | ||
onClick: fn() | ||
}, | ||
} satisfies Meta<typeof Switch>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
const defaultProps = { | ||
onClick: fn() | ||
}; | ||
|
||
const disableControls = { | ||
parameters: { | ||
controls: { | ||
disable: true | ||
}, | ||
actions: { | ||
disable: true | ||
}, | ||
} | ||
}; | ||
|
||
export const Demo: Story = { | ||
args: { | ||
...defaultProps, | ||
}, | ||
tags: ['excludeFromSidebar'] | ||
}; | ||
|
||
export const Default: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'primary', | ||
}, | ||
...disableControls | ||
}; | ||
|
||
export const Secondary: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'secondary', | ||
onClick: fn() | ||
}, | ||
...disableControls | ||
}; | ||
|
||
export const PrimaryOutline: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'primary', | ||
appearance: 'outline' | ||
}, | ||
...disableControls | ||
}; | ||
|
||
export const SecondaryOutline: Story = { | ||
args: { | ||
...defaultProps, | ||
color: 'secondary', | ||
appearance: 'outline' | ||
}, | ||
...disableControls | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import styled from 'styled-components'; | ||
import { ThemeColor, ThemeElementAppearance, ThemeElementSize } from '../../types.ts'; | ||
import { readableColor, shade } from 'polished'; | ||
|
||
type StyledSwitch = { | ||
$color: ThemeColor; | ||
$appearance: ThemeElementAppearance; | ||
$size: ThemeElementSize | ||
} | ||
export const StyledSwitch = styled.button<StyledSwitch>` | ||
display: inline-block; | ||
background: ${props => props.theme.colors[props.$color]}; | ||
color: ${props => readableColor(props.theme.colors[props.$color])}; | ||
appearance: none; | ||
border: 0; | ||
border-radius: 3rem; | ||
cursor: pointer; | ||
transition: all 0.3s ease; | ||
// Handle appearance | ||
${props => { | ||
if(props.$appearance === 'outline') { | ||
return ` | ||
background: transparent; | ||
color: ${props.theme.colors[props.$color]}; | ||
border: 1px solid ${props.theme.colors[props.$color]}; | ||
`; | ||
} | ||
}}; | ||
// Handle sizes | ||
${props => { | ||
if(props.$size === 'sm') { | ||
return ` | ||
width: 1.5rem; | ||
height: 1rem; | ||
`; | ||
} | ||
if(props.$size === 'lg') { | ||
return ` | ||
width: 4rem; | ||
height: 1rem; | ||
`; | ||
} | ||
return ` | ||
width: 2.5rem; | ||
height: 1rem; | ||
`; | ||
}}; | ||
|
||
&:hover, &:focus, &:active { | ||
text-decoration-color: currentColor; | ||
color: ${props => readableColor(props.theme.colors[props.$color])}; | ||
|
||
${props => props.$appearance === 'outline' ? ` | ||
background: ${props.theme.colors[props.$color]}; | ||
` : ` | ||
background: ${shade(0.15, props.theme.colors[props.$color])}; | ||
` | ||
}}; | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import Switch from './Switch'; | ||
|
||
describe('<Switch />', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like the checkbox, it would be good to have tests for the different switch states. But this can wait until next tri. :) |
||
it('should mount', () => { | ||
render(<Switch/>); | ||
|
||
const switch = screen.getByTestId('Switch'); | ||
|
||
expect(switch).toBeInTheDocument(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { FC } from 'react'; | ||
import { StyledSwitch } from './Switch.style'; | ||
import { ThemeColor, ThemeElementAppearance, ThemeElementSize } from '../../types.ts'; | ||
|
||
type SwitchProps = { | ||
color?: ThemeColor; | ||
onClick: () => void; | ||
appearance?: ThemeElementAppearance; | ||
size?: ThemeElementSize; | ||
} | ||
|
||
const Switch: FC<SwitchProps> = ({ | ||
color = 'primary', | ||
onClick, | ||
appearance = 'solid', | ||
size = 'md' | ||
}) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do we know if the switch is on or off? It should probably have a boolean state value or something like that, which gets toggled on click. It's not enough for it to look on or off, because its value would be used somewhere in the application. |
||
return ( | ||
<StyledSwitch data-testid="Switch" onClick={onClick} $color={color} $appearance={appearance} $size={size}> | ||
{} | ||
</StyledSwitch> | ||
); | ||
}; | ||
|
||
export default Switch; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semantically, a checkbox should be an input checkbox, not a button. Guessing you did it this way because options for styling actual checkboxes are pretty limited; you can get around this with some trickery using pseudo-elements. Here's a resource with examples: https://css-tricks.com/the-checkbox-hack/