From 5e0e4e4ec76860b94b92b46996b6d04b719d6059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Sim=C3=A3o?= Date: Mon, 1 Apr 2024 18:23:57 +0100 Subject: [PATCH] feat(components): theme switch --- .../components/src/Switch/Switch.style.tsx | 53 +++++++++---------- packages/components/src/Switch/Switch.tsx | 10 ++-- packages/core/theme/src/components/switch.ts | 15 +++++- packages/core/theme/src/index.ts | 3 +- packages/core/theme/src/themes/bob/switch.ts | 41 +++++++++++++- 5 files changed, 87 insertions(+), 35 deletions(-) diff --git a/packages/components/src/Switch/Switch.style.tsx b/packages/components/src/Switch/Switch.style.tsx index 8ade9d098..724bd6640 100644 --- a/packages/components/src/Switch/Switch.style.tsx +++ b/packages/components/src/Switch/Switch.style.tsx @@ -1,5 +1,5 @@ -import styled from 'styled-components'; -import { theme } from '@interlay/theme'; +import styled, { css } from 'styled-components'; +import { SwitchSize } from '@interlay/theme'; import { Span } from '../Text'; @@ -12,8 +12,7 @@ const StyledWrapper = styled.label` flex-direction: ${({ $reverse }) => $reverse && 'row-reverse'}; align-items: center; position: relative; - min-height: ${theme.spacing.spacing8}; - gap: ${theme.spacing.spacing2}; + gap: ${({ theme }) => theme.spacing('md')}; `; const StyledInput = styled.input` @@ -32,6 +31,7 @@ const StyledInput = styled.input` type StyledSwitchProps = { $isFocusVisible: boolean; $isChecked?: boolean; + $size: SwitchSize; }; const StyledSwitch = styled.span` @@ -39,32 +39,29 @@ const StyledSwitch = styled.span` flex-shrink: 0; display: inline-block; position: relative; - width: ${theme.spacing.spacing10}; - height: ${theme.spacing.spacing6}; - border-radius: ${theme.rounded.full}; - margin: ${theme.spacing.spacing1} 0; - background-color: ${({ $isChecked }) => ($isChecked ? theme.switch.checked.bg : theme.switch.unchecked.bg)}; - transition: ${theme.transition.default}; - outline: ${({ $isFocusVisible }) => $isFocusVisible && theme.outline.default}; outline-offset: 2px; - &::before { - content: ''; - box-sizing: border-box; - display: block; - background-color: ${theme.switch.indicator.bg}; - position: absolute; - width: calc(${theme.spacing.spacing6} * 0.7); - height: calc(${theme.spacing.spacing6} * 0.7); - top: calc(50% - ${theme.spacing.spacing6} * 0.35); - left: 0; - border-radius: ${theme.rounded.full}; - transition: transform ${theme.transition.duration.duration250}ms ease 0s; - transform: ${({ $isChecked }) => - $isChecked - ? `translateX(calc(${theme.spacing.spacing10} - ${theme.spacing.spacing10} / 15 - ${theme.spacing.spacing6} * 0.7))` - : `translateX(calc(${theme.spacing.spacing10} / 15))`}; - } + ${({ theme, $isChecked, $isFocusVisible, $size }) => { + const { base, checked, indicator, focusVisible } = theme.switch; + const sizeStyle = theme.switch.size[$size]; + + return css` + ${base} + ${$isChecked && checked} + ${sizeStyle.base} + outline: ${$isFocusVisible && focusVisible}; + + &::before { + content: ''; + box-sizing: border-box; + display: block; + position: absolute; + ${indicator} + ${sizeStyle.indicator} + ${$isChecked && sizeStyle.checked.indicator} + } + `; + }} `; const StyledLabel = styled(Span)` diff --git a/packages/components/src/Switch/Switch.tsx b/packages/components/src/Switch/Switch.tsx index ee61e5b5c..aae0fc527 100644 --- a/packages/components/src/Switch/Switch.tsx +++ b/packages/components/src/Switch/Switch.tsx @@ -5,7 +5,7 @@ import { mergeProps } from '@react-aria/utils'; import { useToggleState } from '@react-stately/toggle'; import { PressEvent } from '@react-types/shared'; import { ChangeEvent, forwardRef, HTMLAttributes, useRef } from 'react'; -import { Placement } from '@interlay/theme'; +import { Placement, SwitchSize } from '@interlay/theme'; import { useDOMRef } from '@interlay/hooks'; import { TextProps } from '../Text'; @@ -17,6 +17,7 @@ type Props = { onPress?: (e: PressEvent) => void; labelProps?: TextProps; labelPlacement?: Extract; + size?: SwitchSize; }; type NativeAttrs = Omit, keyof Props>; @@ -26,7 +27,10 @@ type InheritAttrs = Omit; type SwitchProps = Props & NativeAttrs & InheritAttrs; const Switch = forwardRef( - ({ children, onChange, className, style, hidden, labelProps, labelPlacement, ...props }, ref): JSX.Element => { + ( + { children, onChange, className, style, hidden, labelProps, labelPlacement, size = 'md', ...props }, + ref + ): JSX.Element => { const labelRef = useDOMRef(ref); const inputRef = useRef(null); @@ -50,7 +54,7 @@ const Switch = forwardRef( style={style} > - + {children && {children}} ); diff --git a/packages/core/theme/src/components/switch.ts b/packages/core/theme/src/components/switch.ts index ba4638ce1..da660af1d 100644 --- a/packages/core/theme/src/components/switch.ts +++ b/packages/core/theme/src/components/switch.ts @@ -1,5 +1,16 @@ import { StyledObject } from 'styled-components'; -type SwitchTheme = StyledObject; +type SwitchSize = 's' | 'md' | 'lg'; -export type { SwitchTheme }; +type SwitchTheme = { + base: StyledObject; + checked: StyledObject; + size: Record< + SwitchSize, + { base: StyledObject; indicator: StyledObject; checked: { indicator: StyledObject } } + >; + focusVisible: string; + indicator: StyledObject; +}; + +export type { SwitchTheme, SwitchSize }; diff --git a/packages/core/theme/src/index.ts b/packages/core/theme/src/index.ts index e57c19eb7..d35594c47 100644 --- a/packages/core/theme/src/index.ts +++ b/packages/core/theme/src/index.ts @@ -11,7 +11,8 @@ export type { ProgressBarSize, AlertStatus, TokenInputSize, - TabsSize + TabsSize, + SwitchSize } from './components'; export type { IconsSizes, diff --git a/packages/core/theme/src/themes/bob/switch.ts b/packages/core/theme/src/themes/bob/switch.ts index da2461e10..f3eadf9f3 100644 --- a/packages/core/theme/src/themes/bob/switch.ts +++ b/packages/core/theme/src/themes/bob/switch.ts @@ -1,5 +1,44 @@ +import { rounded, spacing, transition } from '../../core'; import { SwitchTheme } from '../../components'; -const _switch: SwitchTheme = {}; +import { color } from './colors'; + +const getSizeStyles = (width: string, height: string, percentage: string): SwitchTheme['size']['s'] => ({ + base: { + width, + height + }, + indicator: { + width: `calc(${height} * ${percentage})`, + height: `calc(${height} * ${percentage})`, + transform: `translateX(calc(${width} / 15))`, + top: `calc(50% - (${height} * 0.35))`, + left: 0 + }, + checked: { + indicator: { + transform: `translateX(calc(${width} - ${width} / 15 - ${height} * ${percentage}))` + } + } +}); + +const _switch: SwitchTheme = { + base: { + borderRadius: rounded('full'), + margin: `${spacing('xs')} 0`, + backgroundColor: color('grey-300'), + ...transition('common', 'normal') + }, + indicator: { backgroundColor: color('light'), borderRadius: rounded('full'), ...transition('common', 'normal') }, + checked: { + backgroundColor: color('primary-500') + }, + focusVisible: `2px solid ${color('primary-500')}`, + size: { + s: getSizeStyles(spacing('4xl'), spacing('2xl'), '0.7'), + md: getSizeStyles(spacing('5xl'), spacing('3xl'), '0.75'), + lg: getSizeStyles(spacing('7xl'), spacing('4xl'), '0.75') + } +}; export { _switch };