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: Improve Switch styling and enable css prop #518

Merged
merged 4 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { type StorybookConfig } from '@storybook/builder-vite'
import { mergeConfig } from 'vite'

import viteConfig from '../vite.config'
import { type StorybookConfig } from '@storybook/react-vite'

const config: StorybookConfig = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: {
name: '@storybook/react-vite',
},
core: {
builder: '@storybook/builder-vite',
},
viteFinal: async config => (mergeConfig(config, viteConfig)),
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: '@storybook/react-vite',
}

export default config
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@tanstack/react-virtual": "3.0.0-beta.54",
"@types/chroma-js": "2.4.0",
"@types/lodash-es": "4.17.8",
"babel-plugin-styled-components": "2.1.4",
"chroma-js": "2.4.2",
"classnames": "2.3.2",
"grommet": "2.32.2",
Expand Down Expand Up @@ -119,7 +120,7 @@
"rimraf": "5.0.1",
"storybook": "7.4.0",
"styled-components": "5.3.11",
"typescript": "4.9.5",
"typescript": "5.2.2",
"vite": "4.4.9",
"vitest": "0.34.3"
},
Expand Down
29 changes: 16 additions & 13 deletions src/components/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import {
useState,
} from 'react'
import React from 'react'
import { animated, useSpring } from 'react-spring'
import { useSpring } from 'react-spring'
import styled, { useTheme } from 'styled-components'

import useResizeObserver from '../hooks/useResizeObserver'
import { type UseDisclosureProps, useDisclosure } from '../hooks/useDisclosure'
import { CaretDownIcon } from '../icons'

import Card from './Card'
import { AnimatedDiv } from './AnimatedDiv'

const paddingTransition = '0.2s ease'

Expand Down Expand Up @@ -133,17 +134,19 @@ function AccordionContentUnstyled({
const mOffset = theme.spacing.medium - theme.spacing.small

return (
<animated.div
style={{
overflow: 'hidden',
...(!_unstyled
? {
marginTop: -mOffset,
marginBottom: isOpen ? 0 : mOffset,
}
: {}),
...springs,
}}
<AnimatedDiv
style={
{
overflow: 'hidden',
...(!_unstyled
? {
marginTop: -mOffset,
marginBottom: isOpen ? 0 : mOffset,
}
: {}),
...springs,
} as any
}
>
<div
className={className}
Expand All @@ -152,7 +155,7 @@ function AccordionContentUnstyled({
>
{children}
</div>
</animated.div>
</AnimatedDiv>
)
}
const AccordionContent = styled(AccordionContentUnstyled)(
Expand Down
6 changes: 6 additions & 0 deletions src/components/AnimatedDiv.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Workaround for issue with styled components `css` prop and `animated.div`
// https://github.com/pmndrs/react-spring/issues/1515
import { animated } from 'react-spring'
import styled from 'styled-components'

export const AnimatedDiv = styled(animated.div)<any>``
8 changes: 5 additions & 3 deletions src/components/Layer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
useState,
} from 'react'
import { createPortal } from 'react-dom'
import { type UseTransitionProps, animated, useTransition } from 'react-spring'
import { type UseTransitionProps, useTransition } from 'react-spring'
import { isNil } from 'lodash-es'
import styled, { useTheme } from 'styled-components'

import usePrevious from '../hooks/usePrevious'

import { AnimatedDiv } from './AnimatedDiv'

const DIRECTIONS = ['up', 'down', 'left', 'right'] as const

type Direction = (typeof DIRECTIONS)[number]
Expand Down Expand Up @@ -290,13 +292,13 @@ function LayerRef(
margin={margin}
>
{transitions((styles) => (
<animated.div
<AnimatedDiv
className="animated"
ref={finalRef}
style={{ ...styles }}
>
{children}
</animated.div>
</AnimatedDiv>
))}
</LayerWrapper>
)
Expand Down
164 changes: 65 additions & 99 deletions src/components/LightDarkSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { useEffect, useRef, useState } from 'react'
import { useToggleState } from 'react-stately'
import {
type AriaSwitchProps,
VisuallyHidden,
useFocusRing,
useSwitch,
} from 'react-aria'
import { useEffect, useState } from 'react'
import { VisuallyHidden } from 'react-aria'
import styled, { keyframes, useTheme } from 'styled-components'

import usePrevious from '../hooks/usePrevious'

import { type SwitchProps, type SwitchStyleProps, useSwitch } from './Switch'

const HANDLE_SIZE = 16
const HANDLE_MARGIN = 4
const SWITCH_WIDTH = 42
Expand Down Expand Up @@ -49,7 +45,7 @@ const slideOffAnim = keyframes`
}
`

const MoonSC = styled.svg<{ $selected: boolean }>(({ $selected }) => ({
const MoonSC = styled.svg<SwitchStyleProps>(({ $checked }) => ({
position: 'absolute',
top: 6,
left: 24,
Expand All @@ -59,21 +55,21 @@ const MoonSC = styled.svg<{ $selected: boolean }>(({ $selected }) => ({
transition: 'all 0.15s ease 0.1s',
},
'.moonFill': {
opacity: $selected ? 1 : 0,
opacity: $checked ? 1 : 0,
zIndex: 0,
},
'.moonOutline': {
opacity: $selected ? 0 : 1,
opacity: $checked ? 0 : 1,
zIndex: 1,
},
}))

function Moon({ selected }: { selected: boolean }) {
function Moon(props: SwitchStyleProps) {
const theme = useTheme()

return (
<MoonSC
$selected={selected}
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 12 12"
>
Expand All @@ -91,7 +87,7 @@ function Moon({ selected }: { selected: boolean }) {
)
}

const SunSC = styled.svg<{ $selected: boolean }>((_) => ({
const SunSC = styled.svg<SwitchStyleProps>((_) => ({
position: 'absolute',
top: 6,
left: 6,
Expand All @@ -102,15 +98,15 @@ const SunSC = styled.svg<{ $selected: boolean }>((_) => ({
},
}))

function Sun({ selected }: { selected: boolean }) {
function Sun(props: SwitchStyleProps) {
const theme = useTheme()
const color = selected
const color = !props.$checked
? theme.colors.yellow[500]
: theme.colors['text-primary-disabled']

return (
<SunSC
$selected={selected}
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 12 12"
>
Expand Down Expand Up @@ -142,52 +138,48 @@ function Sun({ selected }: { selected: boolean }) {
)
}

const SwitchSC = styled.label<{
$checked: boolean
$disabled: boolean
$readOnly: boolean
}>(({ $disabled, $readOnly, theme }) => ({
display: 'flex',
columnGap: theme.spacing.xsmall,
alignItems: 'center',
...theme.partials.text.body2,
cursor: $disabled ? 'not-allowed' : $readOnly ? 'default' : 'pointer',
color: theme.colors['text-light'],
...($disabled || $readOnly
? {}
: {
'&:hover': {
color: theme.colors.text,
[SwitchToggleSC]: {
backgroundColor: theme.colors['action-input-hover'],
const SwitchSC = styled.label<SwitchStyleProps>(
({ $disabled, $readOnly, theme }) => ({
display: 'flex',
columnGap: theme.spacing.xsmall,
alignItems: 'center',
...theme.partials.text.body2,
cursor: $disabled ? 'not-allowed' : $readOnly ? 'default' : 'pointer',
color: theme.colors['text-light'],
...($disabled || $readOnly
? {}
: {
'&:hover': {
color: theme.colors.text,
[SwitchToggleSC]: {
backgroundColor: theme.colors['action-input-hover'],
},
},
},
}),
}))

const SwitchToggleSC = styled.div<{
$disabled: boolean
$focused: boolean
$checked: boolean
}>(({ $focused, $disabled, theme }) => ({
position: 'relative',
width: 42,
height: 24,
borderRadius: 12,
backgroundColor: 'transparent',
outlineWidth: 1,
outlineStyle: 'solid',
outlineOffset: -1,
outlineColor: $disabled
? theme.colors['border-disabled']
: $focused
? theme.colors['border-outline-focused']
: theme.colors['border-input'],
transition: 'all 0.15s ease',
}))
}),
})
)

const SwitchToggleSC = styled.div<SwitchStyleProps>(
({ $focused, $disabled, theme }) => ({
position: 'relative',
width: 42,
height: 24,
borderRadius: 12,
backgroundColor: 'transparent',
outlineWidth: 1,
outlineStyle: 'solid',
outlineOffset: -1,
outlineColor: $disabled
? theme.colors['border-disabled']
: $focused
? theme.colors['border-outline-focused']
: theme.colors['border-input'],
transition: 'all 0.15s ease',
})
)

const SwitchHandleSC = styled(
styled.div<{ $checked: boolean; $disabled: boolean; $animate: boolean }>(
styled.div<SwitchStyleProps & { $animate: boolean }>(
({ $checked, $disabled, theme }) => ({
position: 'absolute',
width: '100%',
Expand Down Expand Up @@ -222,29 +214,12 @@ const SwitchHandleSC = styled(

export function LightDarkSwitch({
children,
checked,
disabled,
readOnly,
as,
className,
...props
}: Omit<
AriaSwitchProps & Parameters<typeof useToggleState>[0],
'isDisabled' | 'isReadonly' | 'isSelected'
> & { checked?: boolean; disabled?: boolean; readOnly?: boolean }) {
const ariaProps: AriaSwitchProps = {
isSelected: checked,
isDisabled: disabled,
isReadOnly: readOnly,
...props,
}

const state = useToggleState(ariaProps)
const ref = useRef<HTMLInputElement>(null)
const { inputProps, isSelected, isDisabled, isReadOnly } = useSwitch(
{ ...ariaProps },
state,
ref
)
const { focusProps, isFocusVisible } = useFocusRing()
}: SwitchProps) {
const { inputProps, styleProps, state } = useSwitch(props)
const { isSelected } = state
const wasSelected = usePrevious(isSelected) ?? isSelected
const [animate, setAnimate] = useState(false)

Expand All @@ -256,27 +231,18 @@ export function LightDarkSwitch({

return (
<SwitchSC
$disabled={isDisabled}
$checked={isSelected}
$readOnly={isReadOnly}
as={as}
className={className}
{...styleProps}
>
<VisuallyHidden>
<input
{...inputProps}
{...focusProps}
ref={ref}
/>
<input {...inputProps} />
</VisuallyHidden>
<SwitchToggleSC
$focused={isFocusVisible}
$disabled={isDisabled}
$checked={isSelected}
>
<Sun selected={!isSelected} />
<Moon selected={isSelected} />
<SwitchToggleSC {...styleProps}>
<Sun {...styleProps} />
<Moon {...styleProps} />
<SwitchHandleSC
$disabled={isDisabled}
$checked={isSelected}
{...styleProps}
$animate={animate}
/>
</SwitchToggleSC>
Expand Down
Loading
Loading