diff --git a/apps/designer/app/designer/features/topbar/share.tsx b/apps/designer/app/designer/features/topbar/share.tsx index 58fa4f8827..b1c967c5b1 100644 --- a/apps/designer/app/designer/features/topbar/share.tsx +++ b/apps/designer/app/designer/features/topbar/share.tsx @@ -1,22 +1,25 @@ import { Button, - Flex, DeprecatedIconButton, Popover, PopoverContent, PopoverPortal, PopoverTrigger, - TextField, } from "@webstudio-is/design-system"; import { Share1Icon } from "@webstudio-is/icons"; import { useIsShareDialogOpen } from "../../shared/nano-states"; import { theme } from "@webstudio-is/design-system"; +import { + ShareProjectDeprecated, + ShareProject, + LinkOptions, +} from "~/shared/share-project"; -type ShareButtonProps = { +type ShareButtonDeprecatedProps = { url: string; }; -const Content = ({ url }: ShareButtonProps) => { +const Content = ({ url }: ShareButtonDeprecatedProps) => { if (typeof location === "undefined") { return null; } @@ -29,31 +32,12 @@ const Content = ({ url }: ShareButtonProps) => { event.preventDefault(); }} > -
{ - event.preventDefault(); - window.open(url, "_blank"); - }} - > - - { - event?.target.select(); - }} - /> - - -
+ ); }; -export const ShareButton = (props: ShareButtonProps) => { +export const ShareButtonDeprecated = (props: ShareButtonDeprecatedProps) => { const [isOpen, setIsOpen] = useIsShareDialogOpen(); return ( @@ -68,3 +52,24 @@ export const ShareButton = (props: ShareButtonProps) => { ); }; + +export const ShareButton = () => { + const handleChange = (link: LinkOptions) => { + // @todo implement + }; + const handleDelete = (link: LinkOptions) => { + // @todo implement + }; + const handleCreate = () => { + // @todo implement + }; + return ( + + + + ); +}; diff --git a/apps/designer/app/designer/features/topbar/topbar.tsx b/apps/designer/app/designer/features/topbar/topbar.tsx index 7b8cfac98a..e2c4283811 100644 --- a/apps/designer/app/designer/features/topbar/topbar.tsx +++ b/apps/designer/app/designer/features/topbar/topbar.tsx @@ -1,13 +1,14 @@ import type { Publish } from "~/shared/pubsub"; import { darkTheme, Flex, type CSS } from "@webstudio-is/design-system"; import { PreviewButton } from "./preview"; -import { ShareButton } from "./share"; +import { ShareButton, ShareButtonDeprecated } from "./share"; import { PublishButton } from "./publish"; import { SyncStatus } from "./sync-status"; import { Menu } from "./menu"; import { Breakpoints } from "../breakpoints"; import type { Project } from "@webstudio-is/project"; import { theme } from "@webstudio-is/design-system"; +import { isFeatureEnabled } from "@webstudio-is/feature-flags"; type TopbarProps = { css: CSS; @@ -55,7 +56,8 @@ export const Topbar = ({ css, project, publish, previewUrl }: TopbarProps) => { > - + + {isFeatureEnabled("share2") && } diff --git a/apps/designer/app/shared/share-project/index.ts b/apps/designer/app/shared/share-project/index.ts new file mode 100644 index 0000000000..bc7d2e6a7a --- /dev/null +++ b/apps/designer/app/shared/share-project/index.ts @@ -0,0 +1,2 @@ +export { ShareProject as ShareProjectDeprecated } from "./share-project"; +export * from "./share-project2"; diff --git a/apps/designer/app/shared/share-project/share-project.stories.tsx b/apps/designer/app/shared/share-project/share-project.stories.tsx new file mode 100644 index 0000000000..5b4ac678e4 --- /dev/null +++ b/apps/designer/app/shared/share-project/share-project.stories.tsx @@ -0,0 +1,69 @@ +import { v4 as uuid } from "uuid"; +import type { ComponentStory } from "@storybook/react"; +import { Button } from "@webstudio-is/design-system"; +import { useState } from "react"; +import { type LinkOptions, ShareProject } from "./share-project2"; + +export default { + component: ShareProject, +}; + +const initialLinks: Array = [ + { + url: `https://google.com/${uuid()}`, + name: "View Only", + permission: "view", + }, + { + url: `https://google.com/${uuid()}`, + name: "View and Edit", + permission: "edit", + }, + { + url: `https://google.com/${uuid()}`, + name: "Build", + permission: "build", + }, +]; + +const useShareProject = (initialLinks: Array = []) => { + const [links, setLinks] = useState(initialLinks); + + const onChange = (updatedLink: LinkOptions) => { + setLinks( + links.map((link) => (link.url === updatedLink.url ? updatedLink : link)) + ); + }; + const onDelete = (deletedLink: LinkOptions) => { + setLinks(links.filter((link) => link.url !== deletedLink.url)); + }; + const onCreate = () => { + setLinks([ + ...links, + { + url: `https://google.com/${uuid()}`, + name: "Custom Link", + permission: "view", + }, + ]); + }; + return { links, onChange, onDelete, onCreate }; +}; + +export const Empty: ComponentStory = () => { + const props = useShareProject(); + return ( + + + + ); +}; + +export const WithLinks: ComponentStory = () => { + const props = useShareProject(initialLinks); + return ( + + + + ); +}; diff --git a/apps/designer/app/shared/share-project/share-project.tsx b/apps/designer/app/shared/share-project/share-project.tsx new file mode 100644 index 0000000000..b217baefb8 --- /dev/null +++ b/apps/designer/app/shared/share-project/share-project.tsx @@ -0,0 +1,28 @@ +import { Button, Flex, TextField } from "@webstudio-is/design-system"; + +type ShareProjectProps = { url: string }; + +export const ShareProject = ({ url }: ShareProjectProps) => { + return ( +
{ + event.preventDefault(); + window.open(url, "_blank"); + }} + > + + { + event?.target.select(); + }} + /> + + +
+ ); +}; diff --git a/apps/designer/app/shared/share-project/share-project2.tsx b/apps/designer/app/shared/share-project/share-project2.tsx new file mode 100644 index 0000000000..62d6ed586c --- /dev/null +++ b/apps/designer/app/shared/share-project/share-project2.tsx @@ -0,0 +1,263 @@ +import { + Box, + Button, + css, + Flex, + Label, + Popover, + PopoverContent, + PopoverHeader, + PopoverPortal, + PopoverTrigger, + rawTheme, + Separator, + Switch, + TextField, + theme, + Tooltip, + useId, +} from "@webstudio-is/design-system"; +import { CopyIcon, InfoIcon, MenuIcon, PlusIcon } from "@webstudio-is/icons"; +import { Fragment, type ComponentProps } from "react"; + +const Item = (props: ComponentProps) => ( + +); + +type PermissionProps = { + title: string; + info: string; + checked: boolean; + onCheckedChange: (checked: boolean) => void; +}; +const Permission = ({ + title, + info, + checked, + onCheckedChange, +}: PermissionProps) => { + const id = useId(); + + return ( + + + + + + + + ); +}; + +type MenuProps = { + permission: Permission; + name: string; + onChangePermission: (permission: Permission) => void; + onChangeName: (name: string) => void; + onDelete: () => void; +}; + +const Menu = ({ + permission, + name, + onChangePermission, + onChangeName, + onDelete, +}: MenuProps) => { + const handleCheckedChange = + (permission: Permission) => (checked: boolean) => { + if (checked) { + onChangePermission(permission); + } + }; + + return ( + + + + + + + + { + onChangeName(event.target.value); + }} + /> + + + + + + + + + + + {/* @todo need a menu item that looks like one from dropdown but without DropdownMenu */} + + + + + ); +}; + +const itemStyle = css({ + display: "flex", + alignItems: "center", + gap: theme.spacing[3], + py: theme.spacing[5], + px: theme.spacing[9], +}); + +type Permission = "view" | "edit" | "build"; + +export type LinkOptions = { + url: string; + name: string; + permission: Permission; +}; + +type SharedLinkItemType = LinkOptions & { + onChangePermission: (permission: Permission) => void; + onChangeName: (name: string) => void; + onDelete: () => void; +}; + +const SharedLinkItem = ({ + url, + name, + permission, + onChangePermission, + onChangeName, + onDelete, +}: SharedLinkItemType) => { + return ( + + + + + + ); +}; + +type ShareProjectProps = { + links?: Array; + onChange: (link: LinkOptions) => void; + onDelete: (link: LinkOptions) => void; + onCreate: () => void; + children: JSX.Element; + isOpen?: boolean; + onOpenChange?: (isOpen: boolean) => void; +}; + +export const ShareProject = ({ + links = [], + onChange, + onDelete, + onCreate, + children, + isOpen, + onOpenChange, +}: ShareProjectProps) => { + const items = links.map((link) => ( + + { + onChange({ ...link, permission }); + }} + onChangeName={(name) => { + onChange({ ...link, name }); + }} + onDelete={() => { + onDelete(link); + }} + /> + + + )); + + const create = ( + + + + ); + + // @todo this should use FloatingPanel instead of Popover, which we need to refactor first + return ( + + {children} + + + + {items} + {create} + + + + + + ); +}; diff --git a/packages/design-system/src/components/label.tsx b/packages/design-system/src/components/label.tsx index b694ae7f2e..bfd3b6fe00 100644 --- a/packages/design-system/src/components/label.tsx +++ b/packages/design-system/src/components/label.tsx @@ -3,7 +3,7 @@ * https://www.figma.com/file/sfCE7iLS0k25qCxiifQNLE/%F0%9F%93%9A-Webstudio-Library?node-id=4%3A3274 */ -import type { LabelHTMLAttributes, ReactNode, Ref } from "react"; +import type { ComponentProps, ReactNode, Ref } from "react"; import { forwardRef } from "react"; import { typography } from "../__generated__/figma-design-tokens"; import { styled, theme } from "../stitches.config"; @@ -62,7 +62,7 @@ const StyledLabel = styled("label", typography.labelSentenceCase, { }, }); -type Props = LabelHTMLAttributes & { +type Props = ComponentProps & { color?: "default" | "preset" | "local" | "remote"; disabled?: boolean; children: ReactNode; diff --git a/packages/design-system/src/components/popover.tsx b/packages/design-system/src/components/popover.tsx index c47e3324a8..99bff4c44d 100644 --- a/packages/design-system/src/components/popover.tsx +++ b/packages/design-system/src/components/popover.tsx @@ -22,7 +22,7 @@ const StyledContent = styled(PopoverPrimitive.Content, panelStyles, { backgroundColor: "white", minWidth: 200, minHeight: theme.spacing[13], - maxWidth: 265, + maxWidth: "max-content", "&:focus": { outline: "none", }, diff --git a/packages/design-system/src/components/separator.tsx b/packages/design-system/src/components/separator.tsx index 1fc42eddab..7297bdd7a7 100644 --- a/packages/design-system/src/components/separator.tsx +++ b/packages/design-system/src/components/separator.tsx @@ -11,26 +11,15 @@ export const Separator = styled(SeparatorPrimitive.Root, { variants: { size: { - "1": { + 1: { '&[data-orientation="horizontal"]': { height: theme.spacing[1], - width: theme.spacing[9], + my: theme.spacing[5], }, '&[data-orientation="vertical"]': { width: theme.spacing[1], - height: theme.spacing[9], - }, - }, - "2": { - '&[data-orientation="horizontal"]': { - height: theme.spacing[2], - width: theme.spacing[17], - }, - - '&[data-orientation="vertical"]': { - width: theme.spacing[2], - height: theme.spacing[17], + mx: theme.spacing[5], }, }, auto: { diff --git a/packages/design-system/src/components/toast.tsx b/packages/design-system/src/components/toast.tsx index 4fb447ce1a..bcab3884cd 100644 --- a/packages/design-system/src/components/toast.tsx +++ b/packages/design-system/src/components/toast.tsx @@ -4,7 +4,7 @@ import { keyframes, styled } from "../stitches.config"; import { CheckCircledIcon, CrossCircledIcon, - InfoCircledIcon, + InfoIcon, } from "@webstudio-is/icons"; import { Box } from "./box"; import { theme } from "../stitches.config"; @@ -120,7 +120,7 @@ export const Toaster = () => { > {toast.type === "success" && } {toast.type === "error" && } - {toast.type === "blank" && } + {toast.type === "blank" && } {toast.message} diff --git a/packages/design-system/src/components/tooltip.tsx b/packages/design-system/src/components/tooltip.tsx index 643dc2f1ac..0f28579b06 100644 --- a/packages/design-system/src/components/tooltip.tsx +++ b/packages/design-system/src/components/tooltip.tsx @@ -7,10 +7,9 @@ import type { CSS } from "../stitches.config"; import { theme } from "../stitches.config"; export type TooltipProps = ComponentProps & - ComponentProps & { + ComponentProps & { children: React.ReactElement; content: React.ReactNode; - multiline?: boolean; delayDuration?: number; disableHoverableContent?: boolean; css?: CSS; @@ -20,16 +19,18 @@ const Content = styled(TooltipPrimitive.Content, { backgroundColor: theme.colors.hiContrast, color: theme.colors.loContrast, borderRadius: theme.borderRadius[4], - padding: `${theme.spacing[3]} ${theme.spacing[5]}`, + padding: theme.spacing[5], zIndex: theme.zIndices[1], position: "relative", variants: { - multiline: { - true: { - // @todo make this part of the design system - maxWidth: 110, - pb: 7, + variant: { + wrapped: { + maxWidth: theme.spacing["29"], + }, + large: { + maxWidth: theme.spacing["32"], + padding: theme.spacing[9], }, }, }, @@ -46,7 +47,6 @@ export const Tooltip = React.forwardRef( children, content, defaultOpen, - multiline, delayDuration, disableHoverableContent, open, @@ -80,7 +80,6 @@ export const Tooltip = React.forwardRef( align="center" sideOffset={5} {...props} - multiline={multiline} > {content} diff --git a/packages/design-system/src/stitches.config.ts b/packages/design-system/src/stitches.config.ts index cf95fb6d49..dd984d5595 100644 --- a/packages/design-system/src/stitches.config.ts +++ b/packages/design-system/src/stitches.config.ts @@ -159,7 +159,7 @@ const spacing = { 33: "320px", 34: "384px", 35: "448px", -}; +} as const; const { styled, diff --git a/packages/icons/icons/info.svg b/packages/icons/icons/info.svg new file mode 100644 index 0000000000..b42008c6fd --- /dev/null +++ b/packages/icons/icons/info.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/icons/src/__generated__/index.ts b/packages/icons/src/__generated__/index.ts index 245e5d8779..d4c9932442 100644 --- a/packages/icons/src/__generated__/index.ts +++ b/packages/icons/src/__generated__/index.ts @@ -1,12 +1,4 @@ export * from "./auto-scroll"; -export * from "./eyecon-closed"; -export * from "./eyecon-open"; -export * from "./linked"; -export * from "./scroll"; -export * from "./subscript"; -export * from "./superscript"; -export * from "./unlinked"; -export * from "./webstudio"; export * from "./chevron-big-down"; export * from "./chevron-big-left"; export * from "./chevron-big-right"; @@ -16,3 +8,12 @@ export * from "./chevron-filled-left"; export * from "./chevron-filled-right"; export * from "./chevron-filled-up"; export * from "./dot"; +export * from "./eyecon-closed"; +export * from "./eyecon-open"; +export * from "./info"; +export * from "./linked"; +export * from "./scroll"; +export * from "./subscript"; +export * from "./superscript"; +export * from "./unlinked"; +export * from "./webstudio"; diff --git a/packages/icons/src/__generated__/info.tsx b/packages/icons/src/__generated__/info.tsx new file mode 100644 index 0000000000..ce7983d188 --- /dev/null +++ b/packages/icons/src/__generated__/info.tsx @@ -0,0 +1,15 @@ +// Generated from icons/info.svg + +import { forwardRef } from "react"; +import { IconProps } from "../types"; + +// prettier-ignore +export const InfoIcon = forwardRef( + ({ color = "currentColor", ...props }, forwardedRef) => { + return ( + + ); + } +); + +InfoIcon.displayName = "InfoIcon"; diff --git a/packages/icons/src/legacy.ts b/packages/icons/src/legacy.ts index 8d221b609d..dfc160e741 100644 --- a/packages/icons/src/legacy.ts +++ b/packages/icons/src/legacy.ts @@ -24,7 +24,6 @@ export { HeadingIcon, TextIcon, SquareIcon, - InfoCircledIcon, CrossCircledIcon, CheckCircledIcon, TokensIcon,