Align Switch with Figma (#921)

Updated Switch styles, added
[demo](https://main--638245578b47d4399fba191a.chromatic.com/?path=/story/library-switch--switch).
Closes #801
This commit is contained in:
Roman Pominov authored and GitHub committed 2023-02-02 15:39:50 +03:00
1 parent d01dbeadce
commit 97fb27c0ce
4 files changed
+101 -90

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,30 @@
import { Switch } from "./switch";
import { StorySection, StoryGrid } from "./storybook";
export default {
title: "Library/Switch",
};
const Story = () => {
return (
<>
<StorySection title="Enabled">
<StoryGrid horizontal>
<Switch defaultChecked />
<Switch />
</StoryGrid>
</StorySection>
<StorySection title="Disabled">
<StoryGrid horizontal>
<Switch defaultChecked disabled />
<Switch disabled />
</StoryGrid>
</StorySection>
<StorySection title="Focussed (initially)">
<Switch autoFocus />
</StorySection>
</>
);
};
export { Story as Switch };
@@ -1,96 +1,77 @@
import React from "react";
import { styled, VariantProps, CSS } from "../stitches.config";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { theme } from "../stitches.config";
/**
* Implementation of the "Switch" component from:
* https://www.figma.com/file/sfCE7iLS0k25qCxiifQNLE/%F0%9F%93%9A-Webstudio-Library?node-id=1086%3A3430&t=WVRoENiFP3BSlQa7-0
*/
const StyledThumb = styled(SwitchPrimitive.Thumb, {
position: "absolute",
left: 0,
width: 13,
height: 13,
backgroundColor: "white",
import { forwardRef, type ComponentProps, type Ref } from "react";
import * as Primitive from "@radix-ui/react-switch";
import { type CSS, css, theme } from "../stitches.config";
const padding = theme.spacing[1];
const thumbOffset = `calc(${padding} + ${theme.spacing[2]})`;
const switchStyles = css({
all: "unset", // reset <button>
boxSizing: "content-box",
width: theme.spacing[11],
height: theme.spacing[8],
borderRadius: theme.borderRadius.pill,
position: "relative",
// in Figma there's an extra container with a padding
// so we need a pseudo element
padding: padding,
"&:before": {
content: "''",
position: "absolute",
inset: padding,
borderRadius: theme.borderRadius.pill,
backgroundColor: theme.colors.backgroundNeutralDark,
},
"&[data-state=checked]:not([data-disabled]):before": {
backgroundColor: theme.colors.backgroundPrimary,
},
"&[data-disabled]:before": {
backgroundColor: theme.colors.foregroundDisabled,
},
"&:focus": {
outline: `2px solid ${theme.colors.borderFocus}`,
},
});
const thumbStyles = css({
width: theme.spacing[6],
height: theme.spacing[6],
borderRadius: theme.borderRadius.round,
boxShadow: "rgba(0, 0, 0, 0.3) 0px 0px 1px, rgba(0, 0, 0, 0.2) 0px 1px 2px;",
transition: "transform 100ms cubic-bezier(0.22, 1, 0.36, 1)",
transform: `translateX(${theme.spacing[1]})`,
willChange: "transform",
'&[data-state="checked"]': {
backgroundColor: theme.colors.foregroundContrastMain,
position: "absolute",
top: thumbOffset,
left: thumbOffset,
transition: "transform 100ms",
"&[data-state=checked]": {
transform: `translateX(${theme.spacing[6]})`,
},
});
const StyledSwitch = styled(SwitchPrimitive.Root, {
all: "unset",
boxSizing: "border-box",
userSelect: "none",
flexShrink: 0,
"&::before": {
boxSizing: "border-box",
},
"&::after": {
boxSizing: "border-box",
},
// Reset
alignItems: "center",
display: "inline-flex",
justifyContent: "center",
lineHeight: "1",
margin: "0",
outline: "none",
WebkitTapHighlightColor: "rgba(0,0,0,0)",
backgroundColor: theme.colors.slate6,
borderRadius: theme.borderRadius.pill,
position: "relative",
"&:focus": {
boxShadow: `0 0 0 2px ${theme.colors.slate8}`,
},
'&[data-state="checked"]': {
backgroundColor: theme.colors.blue9,
"&:focus": {
boxShadow: `0 0 0 2px ${theme.colors.blue8}`,
},
},
variants: {
size: {
"1": {
width: theme.spacing[11],
height: theme.spacing[9],
},
"2": {
width: theme.spacing[17],
height: theme.spacing[11],
[`& ${StyledThumb}`]: {
width: 21,
height: 21,
transform: `translateX(${theme.spacing[2]})`,
'&[data-state="checked"]': {
transform: `translateX(2${theme.spacing[2]})`,
},
},
},
},
},
defaultVariants: {
size: "1",
},
});
type SwitchVariants = VariantProps<typeof StyledSwitch>;
type SwitchPrimitiveProps = React.ComponentProps<typeof SwitchPrimitive.Root>;
type SwitchProps = SwitchPrimitiveProps & SwitchVariants & { css?: CSS };
export const Switch = React.forwardRef<
React.ElementRef<typeof StyledSwitch>,
SwitchProps
>((props, forwardedRef) => (
<StyledSwitch {...props} ref={forwardedRef}>
<StyledThumb />
</StyledSwitch>
));
export const Switch = forwardRef(
(
{
className,
css,
...props
}: ComponentProps<typeof Primitive.Root> & { css?: CSS },
ref: Ref<HTMLButtonElement>
) => (
<Primitive.Root
className={switchStyles({ className, css })}
{...props}
ref={ref}
>
<Primitive.Thumb className={thumbStyles()} />
</Primitive.Root>
)
);
Switch.displayName = "Switch";
+1 -1
View File
@@ -15,6 +15,7 @@ export * from "./components/icon-button"; // mostly aligned, but needs a demo an
export * from "./components/dialog";
export * from "./components/floating-panel-popover";
export { MenuItemButton } from "./components/menu";
export * from "./components/switch";
// Not aligned
@@ -43,7 +44,6 @@ export {
} from "./components/enhanced-tooltip";
export { Link } from "./components/link";
export { Panel } from "./components/panel";
export * from "./components/switch";
export * from "./components/slider";
export * from "./components/radio";
export * from "./components/checkbox";