From 45c74fc7eca1d004ef64213e65d6068f16ff6cb4 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 20 Jun 2023 21:23:23 +0300 Subject: [PATCH] Add toggle group component (#1809) Ref https://github.com/webstudio-is/webstudio-builder/issues/1779 https://github.com/webstudio-is/webstudio-builder/issues/1779 - Here added new toggle group component aligned with figma. - Replaced all legacy toggle group usages. - Removed local/remote/overwritten colors from controls with labels. - Prevented unselect of toggle group with single toggle - Dropped unused toggle component from design system --- .../features/props-panel/controls/url.tsx | 8 +- .../controls/toggle/toggle-control.tsx | 46 +----- .../backgrounds/background-content.tsx | 18 +-- .../sections/borders/border-style.tsx | 10 +- .../sections/flex-child/flex-child.tsx | 7 - .../sections/outline/outline-style.tsx | 10 +- .../style-panel/sections/size/size.tsx | 2 - .../sections/typography/typography.tsx | 3 - .../src/components/toggle-group.stories.tsx | 152 ++++++++++++++++++ .../src/components/toggle-group.tsx | 147 ++++++++++++----- .../design-system/src/components/toggle.tsx | 68 -------- packages/design-system/src/index.ts | 1 - 12 files changed, 287 insertions(+), 185 deletions(-) create mode 100644 packages/design-system/src/components/toggle-group.stories.tsx delete mode 100644 packages/design-system/src/components/toggle.tsx diff --git a/apps/builder/app/builder/features/props-panel/controls/url.tsx b/apps/builder/app/builder/features/props-panel/controls/url.tsx index ecf9625ab6..f2cf538137 100644 --- a/apps/builder/app/builder/features/props-panel/controls/url.tsx +++ b/apps/builder/app/builder/features/props-panel/controls/url.tsx @@ -7,7 +7,7 @@ import { InputField, Flex, ToggleGroup, - ToggleGroupItem, + ToggleGroupButton, Select, Tooltip, Label, @@ -385,9 +385,9 @@ export const UrlControl = ({ }} > {Object.entries(modes).map(([key, { icon, label }]) => ( - - {icon} - + + {icon} + ))} diff --git a/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx b/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx index 01e8577514..487854454a 100644 --- a/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx +++ b/apps/builder/app/builder/features/style-panel/controls/toggle/toggle-control.tsx @@ -1,15 +1,13 @@ import { - styled, Flex, ToggleGroup, - ToggleGroupItem, + ToggleGroupButton, EnhancedTooltip, } from "@webstudio-is/design-system"; import type { StyleSource } from "../../shared/style-info"; -import { theme } from "@webstudio-is/design-system"; export type ToggleGroupControlProps = { - styleSource: StyleSource; + styleSource?: StyleSource; value: string; items: { child: JSX.Element; label: string; value: string }[]; onValueChange?: (value: string) => void; @@ -24,6 +22,7 @@ export const ToggleGroupControl = ({ }: ToggleGroupControlProps) => { return ( {items.map(({ child, label, value }, index) => { return ( - - + + {child} - - + + ); })} ); }; - -const ToggleGroupControlItem = styled(ToggleGroupItem, { - variants: { - source: { - default: {}, - preset: {}, - local: { - "&[data-state=on]": { - color: theme.colors.foregroundLocalMain, - backgroundColor: theme.colors.backgroundLocalMain, - }, - }, - overwritten: { - "&[data-state=on]": { - color: theme.colors.foregroundOverwrittenMain, - backgroundColor: theme.colors.backgroundOverwrittenMain, - }, - }, - remote: { - "&[data-state=on]": { - color: theme.colors.foregroundRemoteMain, - backgroundColor: theme.colors.backgroundRemoteMain, - }, - }, - }, - }, -}); diff --git a/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.tsx b/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.tsx index f384d6be44..c22ef8c633 100644 --- a/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.tsx @@ -9,7 +9,7 @@ import { Flex, Grid, ToggleGroup, - ToggleGroupItem, + ToggleGroupButton, Separator, styled, } from "@webstudio-is/design-system"; @@ -131,12 +131,12 @@ export const BackgroundContent = (props: BackgroundContentProps) => { } }} > - + Image - - + + Gradient - + @@ -311,12 +311,12 @@ export const BackgroundContent = (props: BackgroundContentProps) => { }); }} > - + Scroll - - + + Fixed - + diff --git a/apps/builder/app/builder/features/style-panel/sections/borders/border-style.tsx b/apps/builder/app/builder/features/style-panel/sections/borders/border-style.tsx index ecf4711831..d72b89e8ee 100644 --- a/apps/builder/app/builder/features/style-panel/sections/borders/border-style.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/borders/border-style.tsx @@ -4,7 +4,7 @@ import { Grid, theme, ToggleGroup, - ToggleGroupItem, + ToggleGroupButton, Tooltip, } from "@webstudio-is/design-system"; import { @@ -92,11 +92,11 @@ export const BorderStyle = ( }} > {borderStyleValues.map(({ value, Icon }) => ( - - + + - - + + ))} diff --git a/apps/builder/app/builder/features/style-panel/sections/flex-child/flex-child.tsx b/apps/builder/app/builder/features/style-panel/sections/flex-child/flex-child.tsx index 8f092d2f03..7137d728bd 100644 --- a/apps/builder/app/builder/features/style-panel/sections/flex-child/flex-child.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/flex-child/flex-child.tsx @@ -19,7 +19,6 @@ import { MenuEllipsesIcon, } from "@webstudio-is/icons"; import { FloatingPanel } from "~/builder/shared/floating-panel"; -import { getStyleSource } from "../../shared/style-info"; import { CollapsibleSection } from "../../shared/collapsible-section"; const properties: StyleProperty[] = [ @@ -59,7 +58,6 @@ const FlexChildSectionAlign = (props: RenderCategoryProps) => { onReset={() => deleteProperty("alignSelf")} /> setAlignSelf({ type: "keyword", value })} value={toValue(currentStyle.alignSelf?.value)} items={[ @@ -117,10 +115,6 @@ const FlexChildSectionSizing = (props: RenderCategoryProps) => { }} /> { switch (value) { case "none": { @@ -279,7 +273,6 @@ const FlexChildSectionOrder = (props: RenderCategoryProps) => { onReset={() => deleteProperty("order")} /> { switch (value) { case "0": diff --git a/apps/builder/app/builder/features/style-panel/sections/outline/outline-style.tsx b/apps/builder/app/builder/features/style-panel/sections/outline/outline-style.tsx index 85f9992153..e2b1516cfd 100644 --- a/apps/builder/app/builder/features/style-panel/sections/outline/outline-style.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/outline/outline-style.tsx @@ -3,7 +3,7 @@ import { Grid, theme, ToggleGroup, - ToggleGroupItem, + ToggleGroupButton, Tooltip, } from "@webstudio-is/design-system"; import { @@ -65,11 +65,11 @@ export const OutlineStyle = ( } > {outlineStyleValues.map(({ value, Icon }) => ( - - + + - - + + ))} diff --git a/apps/builder/app/builder/features/style-panel/sections/size/size.tsx b/apps/builder/app/builder/features/style-panel/sections/size/size.tsx index fcf465c66c..5b69127858 100644 --- a/apps/builder/app/builder/features/style-panel/sections/size/size.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/size/size.tsx @@ -15,7 +15,6 @@ import { ScrollIcon, AutoScrollIcon, } from "@webstudio-is/icons"; -import { getStyleSource } from "../../shared/style-info"; import { CollapsibleSection } from "../../shared/collapsible-section"; import { theme } from "@webstudio-is/design-system"; import { ToggleGroupControl } from "../../controls/toggle/toggle-control"; @@ -133,7 +132,6 @@ export const SizeSection = ({ onReset={() => deleteProperty("overflow")} /> , diff --git a/apps/builder/app/builder/features/style-panel/sections/typography/typography.tsx b/apps/builder/app/builder/features/style-panel/sections/typography/typography.tsx index cbd8f57837..27debfe137 100644 --- a/apps/builder/app/builder/features/style-panel/sections/typography/typography.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/typography/typography.tsx @@ -347,7 +347,6 @@ export const TypographySectionAdvancedPopover = ( onReset={() => deleteProperty("direction")} /> setDirection({ type: "keyword", value }) } @@ -374,7 +373,6 @@ export const TypographySectionAdvancedPopover = ( onReset={() => deleteProperty("hyphens")} /> setHyphens({ type: "keyword", value })} value={toValue(currentStyle.hyphens?.value)} items={[ @@ -399,7 +397,6 @@ export const TypographySectionAdvancedPopover = ( onReset={() => deleteProperty("textOverflow")} /> setTextOverflow({ type: "keyword", value }) } diff --git a/packages/design-system/src/components/toggle-group.stories.tsx b/packages/design-system/src/components/toggle-group.stories.tsx new file mode 100644 index 0000000000..c7710a6fc8 --- /dev/null +++ b/packages/design-system/src/components/toggle-group.stories.tsx @@ -0,0 +1,152 @@ +import type { ComponentProps } from "react"; +import { BorderRadiusIndividualIcon } from "@webstudio-is/icons"; +import { ToggleGroup, ToggleGroupButton } from "./toggle-group"; +import { StorySection, StoryGrid } from "./storybook"; +import { EnhancedTooltip, EnhancedTooltipProvider } from "./enhanced-tooltip"; + +const toggleGroupColors = [ + "default", + "preset", + "local", + "overwritten", + "remote", +] as const; + +const ToggleGroupButtons = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const Demo = ({ + type = "single", + color = "default", + disabled = false, +}: ComponentProps) => ( + <> + + + + + + + + + + + {toggleGroupColors.map((color) => ( + + + + ))} + + + + + + {toggleGroupColors.map((color) => ( + + + + ))} + + + + + + + + + One + + + Two + + + Three + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +Demo.argTypes = { + type: { + control: { type: "inline-radio", options: ["single", "multiple"] }, + }, + color: { + control: { type: "inline-radio", options: toggleGroupColors }, + }, + disabled: { + control: { type: "boolean" }, + }, +}; + +Demo.storyName = "Toggle Group"; + +export default { + title: "Library/Toggle Group", + parameters: { + // to make the variant=contrast visible + backgrounds: { default: "Panel" }, + }, +}; diff --git a/packages/design-system/src/components/toggle-group.tsx b/packages/design-system/src/components/toggle-group.tsx index de0bcfd606..f4a6f1cf18 100644 --- a/packages/design-system/src/components/toggle-group.tsx +++ b/packages/design-system/src/components/toggle-group.tsx @@ -1,48 +1,111 @@ -import { styled } from "../stitches.config"; +/** + * Implementation of the "Toggle Group" component from: + * https://www.figma.com/file/sfCE7iLS0k25qCxiifQNLE/%F0%9F%93%9A-Webstudio-Library?type=design&node-id=4-2831&t=9qVuJbUcZqhAI06U-0 + */ + +import { + type ComponentProps, + type ElementRef, + createContext, + useContext, + forwardRef, +} from "react"; import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"; -import { theme } from "../stitches.config"; +import { styled, theme } from "../stitches.config"; +import { IconButton } from "./icon-button"; +import { textVariants } from "./text"; -export const ToggleGroup = styled(ToggleGroupPrimitive.Root, { - display: "inline-flex", - borderRadius: theme.spacing[3], - boxShadow: `0 0 0 ${theme.spacing[1]} ${theme.colors.slate7}`, - padding: 2, - backgroundColor: theme.colors.backgroundControls, -}); +type Color = "default" | "preset" | "local" | "remote" | "overwritten"; -export const ToggleGroupItem = styled(ToggleGroupPrimitive.Item, { - // all: "unset", // @note weird bug, this somehow gets into weird specifity issues with how styles are inserted - border: "none", - backgroundColor: theme.colors.backgroundControls, - color: theme.colors.hiContrast, +const ToggleGroupContext = createContext<{ + color?: Color; +}>({}); + +type ToggleGroupProps = ComponentProps< + typeof ToggleGroupPrimitive.ToggleGroup +> & { + color?: Color; +}; + +const BaseToggleGroup = forwardRef, ToggleGroupProps>( + ({ color = "default", children, onValueChange, ...props }, ref) => { + return ( + + { + // prevent unselecting buttons when only single can be selected + if (newValue !== "") { + onValueChange?.(newValue as string & string[]); + } + }} + > + {children} + + + ); + } +); + +BaseToggleGroup.displayName = "BaseToggleGroup"; + +export const ToggleGroup = styled(BaseToggleGroup, { + boxSizing: "border-box", display: "flex", - whiteSpace: "nowrap", - fontSize: theme.deprecatedFontSize[4], - lineHeight: 1, + flexDirection: "row", alignItems: "center", - justifyContent: "center", - height: theme.spacing[11], - outline: "none", - "&": { - px: theme.spacing[3], - }, - "&:first-child": { - marginLeft: 0, - borderTopLeftRadius: theme.spacing[3], - borderBottomLeftRadius: theme.spacing[3], - }, - "&:last-child": { - borderTopRightRadius: theme.spacing[3], - borderBottomRightRadius: theme.spacing[3], - }, - "&:hover": { backgroundColor: theme.colors.slateA3 }, - // @note because the outline is outside of the element others can end up covering it - "&:focus-visible": { - outline: `2px solid ${theme.colors.borderFocus}`, - outlineOffset: "-2px", - borderRadius: theme.spacing[3], - - zIndex: 1, - }, - "&[data-state=on]": { backgroundColor: theme.colors.slate5 }, + padding: 1, + background: theme.colors.backgroundControls, + border: `1px solid ${theme.colors.borderMain}`, + borderRadius: theme.borderRadius[4], }); + +const BaseToggleGroupButton = forwardRef< + ElementRef<"button">, + ComponentProps +>((props, ref) => { + const { color } = useContext(ToggleGroupContext); + return ( + + ); +}); + +BaseToggleGroupButton.displayName = "BaseToggleGroupButton"; + +type ToggleGroupButtonProps = ComponentProps; + +export const ToggleGroupButton = forwardRef< + ElementRef<"button">, + ToggleGroupButtonProps +>(({ children, ...props }, ref) => { + return ( + + {children} + + ); +}); + +ToggleGroupButton.displayName = "ToggleGroupButton"; diff --git a/packages/design-system/src/components/toggle.tsx b/packages/design-system/src/components/toggle.tsx deleted file mode 100644 index 214f747a3f..0000000000 --- a/packages/design-system/src/components/toggle.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { styled } from "../stitches.config"; -import * as TogglePrimitive from "@radix-ui/react-toggle"; -import { theme } from "../stitches.config"; - -export const Toggle = styled(TogglePrimitive.Root, { - // Reset - alignItems: "center", - appearance: "none", - borderWidth: "0", - boxSizing: "border-box", - display: "inline-flex", - flexShrink: 0, - fontFamily: "inherit", - fontSize: theme.deprecatedFontSize[4], - WebkitFontSmoothing: "antialiased", - justifyContent: "center", - lineHeight: "1", - outline: "none", - padding: "0", - textDecoration: "none", - userSelect: "none", - WebkitTapHighlightColor: "transparent", - color: theme.colors.hiContrast, - "&::before": { - boxSizing: "border-box", - }, - "&::after": { - boxSizing: "border-box", - }, - height: theme.spacing[11], - width: theme.spacing[11], - backgroundColor: "transparent", - "@hover": { - "&:hover": { - backgroundColor: theme.colors.slateA3, - }, - }, - "&:active": { - backgroundColor: theme.colors.slateA4, - }, - "&:focus": { - boxShadow: `inset 0 0 0 1px ${theme.colors.slateA8}, 0 0 0 1px ${theme.colors.slateA8}`, - zIndex: 1, - }, - - '&[data-state="on"]': { - backgroundColor: theme.colors.slateA5, - "@hover": { - "&:hover": { - backgroundColor: theme.colors.slateA5, - }, - }, - "&:active": { - backgroundColor: theme.colors.slateA7, - }, - }, - - variants: { - shape: { - circle: { - borderRadius: theme.borderRadius.round, - }, - square: { - borderRadius: theme.borderRadius[4], - }, - }, - }, -}); diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index 5c519c2030..26334dd0b2 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -55,7 +55,6 @@ export { SidebarTabsTrigger, } from "./components/sidebar-tabs"; export { Card } from "./components/card"; -export { Toggle } from "./components/toggle"; export { Tooltip, InputErrorsTooltip } from "./components/tooltip"; export { EnhancedTooltip,