Skip to content

Commit

Permalink
feat(components): theme switch
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsimao committed Apr 1, 2024
1 parent 01e6fcd commit 5e0e4e4
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 35 deletions.
53 changes: 25 additions & 28 deletions packages/components/src/Switch/Switch.style.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -12,8 +12,7 @@ const StyledWrapper = styled.label<StyledWrapperProps>`
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`
Expand All @@ -32,39 +31,37 @@ const StyledInput = styled.input`
type StyledSwitchProps = {
$isFocusVisible: boolean;
$isChecked?: boolean;
$size: SwitchSize;
};

const StyledSwitch = styled.span<StyledSwitchProps>`
flex-grow: 0;
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)`
Expand Down
10 changes: 7 additions & 3 deletions packages/components/src/Switch/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,6 +17,7 @@ type Props = {
onPress?: (e: PressEvent) => void;
labelProps?: TextProps;
labelPlacement?: Extract<Placement, 'left' | 'right'>;
size?: SwitchSize;
};

type NativeAttrs = Omit<HTMLAttributes<unknown>, keyof Props>;
Expand All @@ -26,7 +27,10 @@ type InheritAttrs = Omit<AriaSwitchProps, keyof Props>;
type SwitchProps = Props & NativeAttrs & InheritAttrs;

const Switch = forwardRef<HTMLLabelElement, SwitchProps>(
({ 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<HTMLInputElement>(null);

Expand All @@ -50,7 +54,7 @@ const Switch = forwardRef<HTMLLabelElement, SwitchProps>(
style={style}
>
<StyledInput {...mergeProps(inputProps, focusProps, pressProps, { onChange })} ref={inputRef} />
<StyledSwitch $isChecked={inputProps.checked} $isFocusVisible={isFocusVisible} />
<StyledSwitch $isChecked={inputProps.checked} $isFocusVisible={isFocusVisible} $size={size} />
{children && <StyledLabel {...labelProps}>{children}</StyledLabel>}
</StyledWrapper>
);
Expand Down
15 changes: 13 additions & 2 deletions packages/core/theme/src/components/switch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { StyledObject } from 'styled-components';

type SwitchTheme = StyledObject<object>;
type SwitchSize = 's' | 'md' | 'lg';

export type { SwitchTheme };
type SwitchTheme = {
base: StyledObject<object>;
checked: StyledObject<object>;
size: Record<
SwitchSize,
{ base: StyledObject<object>; indicator: StyledObject<object>; checked: { indicator: StyledObject<object> } }
>;
focusVisible: string;
indicator: StyledObject<object>;
};

export type { SwitchTheme, SwitchSize };
3 changes: 2 additions & 1 deletion packages/core/theme/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export type {
ProgressBarSize,
AlertStatus,
TokenInputSize,
TabsSize
TabsSize,
SwitchSize
} from './components';
export type {
IconsSizes,
Expand Down
41 changes: 40 additions & 1 deletion packages/core/theme/src/themes/bob/switch.ts
Original file line number Diff line number Diff line change
@@ -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 };

0 comments on commit 5e0e4e4

Please sign in to comment.