diff --git a/packages/sdk-components-react-radix/src/dialog.template.tsx b/packages/sdk-components-react-radix/src/dialog.template.tsx new file mode 100644 index 0000000000..0dff771581 --- /dev/null +++ b/packages/sdk-components-react-radix/src/dialog.template.tsx @@ -0,0 +1,151 @@ +import { LargeXIcon } from "@webstudio-is/icons/svg"; +import { + type TemplateMeta, + $, + css, + PlaceholderValue, +} from "@webstudio-is/template"; +import { radix } from "./shared/proxy"; +import { + blur, + borderRadius, + borderWidth, + boxShadow, + colors, + fontSize, + fontSizeLineHeight, + height, + letterSpacing, + lineHeight, + maxWidth, + opacity, + spacing, + width, + zIndex, +} from "./shared/theme"; +import { getButtonStyle } from "./shared/styles"; + +export const meta: TemplateMeta = { + category: "radix", + description: + "Displays content with an overlay that covers the window, triggered by a button. Clicking the overlay will close the dialog.", + order: 4, + template: ( + + + <$.Button ws:style={getButtonStyle("outline")}> + {new PlaceholderValue("Button")} + + + + + <$.Box + ws:label="Dialog Header" + ws:style={css` + display: flex; + flex-direction: column; + gap: ${spacing[2]}; + `} + > + + {new PlaceholderValue("Dialog Title you can edit")} + + + {new PlaceholderValue("Dialog description text you can edit")} + + + <$.Text>{new PlaceholderValue("The text you can edit")} + + <$.HtmlEmbed ws:label="Close Icon" code={LargeXIcon} /> + + + + + ), +}; diff --git a/packages/sdk-components-react-radix/src/dialog.ws.ts b/packages/sdk-components-react-radix/src/dialog.ws.ts index 4e9cf7935d..dba3fa1372 100644 --- a/packages/sdk-components-react-radix/src/dialog.ws.ts +++ b/packages/sdk-components-react-radix/src/dialog.ws.ts @@ -6,7 +6,6 @@ import { HeadingIcon, TextIcon, ButtonElementIcon, - LargeXIcon, } from "@webstudio-is/icons/svg"; import { defaultStates, @@ -15,7 +14,6 @@ import { type WsComponentPropsMeta, } from "@webstudio-is/react-sdk"; import { div, button, h2, p } from "@webstudio-is/sdk/normalize.css"; -import * as tc from "./theme/tailwind-classes"; import { propsDialog, propsDialogContent, @@ -25,7 +23,7 @@ import { propsDialogTitle, propsDialogDescription, } from "./__generated__/dialog.props"; -import { buttonReset, getButtonStyles } from "./theme/styles"; +import { buttonReset } from "./theme/styles"; const presetStyle = { div, @@ -41,7 +39,6 @@ const descriptionPresetStyle = { // @todo add [data-state] to button and link export const metaDialogTrigger: WsComponentMeta = { - category: "hidden", type: "container", icon: TriggerIcon, stylable: false, @@ -52,7 +49,6 @@ export const metaDialogTrigger: WsComponentMeta = { }; export const metaDialogOverlay: WsComponentMeta = { - category: "hidden", type: "container", presetStyle, icon: OverlayIcon, @@ -69,7 +65,6 @@ export const metaDialogOverlay: WsComponentMeta = { }; export const metaDialogContent: WsComponentMeta = { - category: "hidden", type: "container", presetStyle, icon: ContentIcon, @@ -98,7 +93,6 @@ export const metaDialogContent: WsComponentMeta = { }; export const metaDialogTitle: WsComponentMeta = { - category: "hidden", type: "container", presetStyle: titlePresetStyle, icon: HeadingIcon, @@ -109,7 +103,6 @@ export const metaDialogTitle: WsComponentMeta = { }; export const metaDialogDescription: WsComponentMeta = { - category: "hidden", type: "container", presetStyle: descriptionPresetStyle, icon: TextIcon, @@ -120,7 +113,6 @@ export const metaDialogDescription: WsComponentMeta = { }; export const metaDialogClose: WsComponentMeta = { - category: "hidden", type: "container", presetStyle: { button: [buttonReset, button].flat(), @@ -143,8 +135,6 @@ export const metaDialogClose: WsComponentMeta = { * Copyright (c) 2023 shadcn **/ export const metaDialog: WsComponentMeta = { - category: "radix", - order: 4, type: "container", constraints: [ { @@ -158,177 +148,6 @@ export const metaDialog: WsComponentMeta = { ], icon: DialogIcon, stylable: false, - description: - "Displays content with an overlay that covers the window, triggered by a button. Clicking the overlay will close the dialog.", - template: [ - { - type: "instance", - component: "Dialog", - children: [ - { - type: "instance", - component: "DialogTrigger", - children: [ - { - type: "instance", - component: "Button", - styles: getButtonStyles("outline"), - children: [{ type: "text", value: "Button", placeholder: true }], - }, - ], - }, - { - type: "instance", - component: "DialogOverlay", - /** - * fixed inset-0 z-50 bg-background/80 backdrop-blur-sm - * flex - **/ - styles: [ - tc.fixed(), - tc.inset(0), - tc.z(50), - tc.bg("background", 80), - tc.backdropBlur("sm"), - // To allow positioning Content - tc.flex(), - tc.overflow("auto"), - ].flat(), - children: [ - { - type: "instance", - component: "DialogContent", - /** - * fixed w-full z-50 - * grid gap-4 max-w-lg - * m-auto - * border bg-background p-6 shadow-lg - **/ - styles: [ - tc.w("full"), - tc.z(50), - tc.flex(), - tc.flex("col"), - tc.gap(4), - tc.m("auto"), - tc.maxW("lg"), - tc.border(), - tc.bg("background"), - tc.p(6), - tc.shadow("lg"), - tc.relative(), - ].flat(), - children: [ - { - type: "instance", - component: "Box", - label: "Dialog Header", - styles: [tc.flex(), tc.flex("col"), tc.gap(1)].flat(), - children: [ - { - type: "instance", - component: "DialogTitle", - /** - * text-lg leading-none tracking-tight - **/ - styles: [ - tc.my(0), - tc.leading("none"), - tc.text("lg"), - tc.tracking("tight"), - ].flat(), - children: [ - { - type: "text", - value: "Dialog Title you can edit", - placeholder: true, - }, - ], - }, - { - type: "instance", - component: "DialogDescription", - /** - * text-sm text-muted-foreground - **/ - styles: [ - tc.my(0), - tc.text("sm"), - tc.text("mutedForeground"), - ].flat(), - children: [ - { - type: "text", - value: "Dialog description text you can edit", - placeholder: true, - }, - ], - }, - ], - }, - - { - type: "instance", - component: "Text", - children: [ - { - type: "text", - value: "The text you can edit", - placeholder: true, - }, - ], - }, - - { - type: "instance", - component: "DialogClose", - /** - * absolute right-4 top-4 - * rounded-sm opacity-70 - * ring-offset-background - * hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 - * flex items-center justify-center h-4 w-4 - **/ - styles: [ - tc.absolute(), - tc.right(4), - tc.top(4), - tc.rounded("sm"), - tc.opacity(70), - tc.flex(), - tc.items("center"), - tc.justify("center"), - tc.h(4), - tc.w(4), - tc.border(0), - tc.bg("transparent"), - tc.outline("none"), - tc.hover(tc.opacity(100)), - tc.focus(tc.ring("ring", 2, "background", 2)), - ].flat(), - children: [ - { - type: "instance", - component: "HtmlEmbed", - label: "Close Icon", - props: [ - { - type: "string", - name: "code", - value: LargeXIcon, - }, - ], - children: [], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], }; export const propsMetaDialog: WsComponentPropsMeta = { diff --git a/packages/sdk-components-react-radix/src/label.template.tsx b/packages/sdk-components-react-radix/src/label.template.tsx index eb976e04bd..c78a7bee7c 100644 --- a/packages/sdk-components-react-radix/src/label.template.tsx +++ b/packages/sdk-components-react-radix/src/label.template.tsx @@ -6,6 +6,14 @@ import { import { radix } from "./shared/proxy"; import { fontSize, lineHeight, weights } from "./shared/theme"; +/** + * Styles source without animations: + * https://github.com/shadcn-ui/ui/blob/main/apps/www/registry/default/ui/label.tsx + * + * Attributions + * MIT License + * Copyright (c) 2023 shadcn + **/ export const meta: TemplateMeta = { category: "radix", description: diff --git a/packages/sdk-components-react-radix/src/metas.ts b/packages/sdk-components-react-radix/src/metas.ts index 187cf80514..26ffef7d38 100644 --- a/packages/sdk-components-react-radix/src/metas.ts +++ b/packages/sdk-components-react-radix/src/metas.ts @@ -12,7 +12,6 @@ export { metaDialogTitle as DialogTitle, metaDialogDescription as DialogDescription, } from "./dialog.ws"; -export { meta as Sheet } from "./sheet.ws"; export { metaPopover as Popover, metaPopoverTrigger as PopoverTrigger, diff --git a/packages/sdk-components-react-radix/src/shared/styles.ts b/packages/sdk-components-react-radix/src/shared/styles.ts new file mode 100644 index 0000000000..2b7c334011 --- /dev/null +++ b/packages/sdk-components-react-radix/src/shared/styles.ts @@ -0,0 +1,93 @@ +import { css, type TemplateStyleDecl } from "@webstudio-is/template"; +import { + borderRadius, + borderWidth, + boxShadow, + colors, + fontSize, + fontSizeLineHeight, + height, + opacity, + spacing, + weights, +} from "./theme"; + +// ghost icon button +// 'inline-flex items-center justify-center rounded-md text-sm font-medium +// ring-offset-background transition-colors +// focus-visible:outline-none focus-visible:ring-2 +// focus-visible:ring-ring focus-visible:ring-offset-2 + +// disabled:pointer-events-none disabled:opacity-50' +const buttonStyle = css` + display: inline-flex; + align-items: center; + justify-content: center; + background-color: transparent; + border: 0 solid ${colors.border}; + border-radius: ${borderRadius.md}; + font-size: ${fontSize.sm}; + line-height: ${fontSizeLineHeight.sm}; + font-weight: ${weights.medium}; + &:focus-visible { + outline: 2px solid transparent; + outline-offset: 2px; + box-shadow: ${boxShadow.ring}; + } + &:disabled { + pointer-events: none; + opacity: ${opacity[50]}; + } +`; + +// hover:bg-accent hover:text-accent-foreground +const ghostButtonStyle = css` + &:hover { + background-color: ${colors.accent}; + color: ${colors.accentForeground}; + } +`; + +// border border-input bg-background hover:bg-accent hover:text-accent-foreground +const outlineButtonStyle = css` + border: ${borderWidth.DEFAULT} solid ${colors.input}; + background-color: ${colors.background}; + &:hover { + background-color: ${colors.accent}; + color: ${colors.accentForeground}; + } +`; + +// h-10 px-4 py-2 +const defaultButtonStyle = css` + height: ${height[10]}; + padding: ${spacing[2]} ${spacing[4]}; +`; + +// Set explicit paddings for IOS Safari to prevent the icon from collapsing +// h-10 w-10 +const iconButtonStyle = css` + height: ${spacing[10]}; + width: ${spacing[10]}; + padding: ${spacing[0]} ${spacing[1.5]}; +`; + +export const getButtonStyle = ( + variant: "outline" | "ghost", + size: "default" | /*"sm" |*/ "icon" = "default" +) => { + const style: TemplateStyleDecl[] = [...buttonStyle]; + if (variant === "ghost") { + style.push(...ghostButtonStyle); + } + if (variant === "outline") { + style.push(...outlineButtonStyle); + } + if (size === "default") { + style.push(...defaultButtonStyle); + } + if (size === "icon") { + style.push(...iconButtonStyle); + } + return style; +}; diff --git a/packages/sdk-components-react-radix/src/shared/theme.ts b/packages/sdk-components-react-radix/src/shared/theme.ts index 235b868f32..5deb02ff65 100644 --- a/packages/sdk-components-react-radix/src/shared/theme.ts +++ b/packages/sdk-components-react-radix/src/shared/theme.ts @@ -59,6 +59,15 @@ export const weights = { black: "900", } as const; +export const letterSpacing = { + tighter: "-0.05em", + tight: "-0.025em", + normal: "0em", + wide: "0.025em", + wider: "0.05em", + widest: "0.1em", +} as const; + export const spacing = { "0": "0px", "1": "0.25rem", @@ -97,6 +106,11 @@ export const spacing = { "3.5": "0.875rem", } as const; +export const width = { + ...spacing, + full: "100%", +} as const; + export const height = { ...spacing, auto: "auto", @@ -122,6 +136,34 @@ export const height = { fit: "fit-content", } as const; +export const maxWidth = { + "0": "0rem", + xs: "20rem", + sm: "24rem", + md: "28rem", + lg: "32rem", + xl: "36rem", + "2xl": "42rem", + "3xl": "48rem", + "4xl": "56rem", + "5xl": "64rem", + "6xl": "72rem", + "7xl": "80rem", + full: "100%", + min: "min-content", + max: "max-content", + fit: "fit-content", + prose: "65ch", +} as const; + +export const borderWidth = { + "0": "0px", + "2": "2px", + "4": "4px", + "8": "8px", + DEFAULT: "1px", +}; + export const borderRadius = { none: "0px", sm: "0.125rem", @@ -212,3 +254,24 @@ export const boxShadow = { `0 0 0 ${ringOffsetWidth[2]} ${colors.background}, ` + `0 0 0 calc(${ringWidth[2]} + ${ringOffsetWidth[2]}) ${colors.ring}`, } as const; + +export const zIndex = { + "0": "0", + "10": "10", + "20": "20", + "30": "30", + "40": "40", + "50": "50", +} as const; + +export const blur = { + sm: "blur(0 1px 2px 0 rgb(0 0 0 / 0.05))", + DEFAULT: + "blur(0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1))", + md: "blur(0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1))", + lg: "blur(0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1))", + xl: "blur(0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1))", + "2xl": "blur(0 25px 50px -12px rgb(0 0 0 / 0.25))", + inner: "blur(inset 0 2px 4px 0 rgb(0 0 0 / 0.05))", + none: "blur(none)", +}; diff --git a/packages/sdk-components-react-radix/src/sheet.template.tsx b/packages/sdk-components-react-radix/src/sheet.template.tsx new file mode 100644 index 0000000000..dc44c7c687 --- /dev/null +++ b/packages/sdk-components-react-radix/src/sheet.template.tsx @@ -0,0 +1,169 @@ +import { LargeXIcon, MenuIcon } from "@webstudio-is/icons/svg"; +import { + type TemplateMeta, + $, + css, + PlaceholderValue, +} from "@webstudio-is/template"; +import { radix } from "./shared/proxy"; +import { + blur, + borderRadius, + borderWidth, + boxShadow, + colors, + fontSize, + fontSizeLineHeight, + height, + letterSpacing, + lineHeight, + maxWidth, + opacity, + spacing, + width, + zIndex, +} from "./shared/theme"; +import { getButtonStyle } from "./shared/styles"; + +/** + * Styles source without animations: + * https://github.com/shadcn-ui/ui/blob/main/apps/www/registry/default/ui/sheet.tsx + * + * Attributions + * MIT License + * Copyright (c) 2023 shadcn + **/ +export const meta: TemplateMeta = { + category: "radix", + icon: MenuIcon, + description: + "Displays content in a menu that slides out from the side of the screen, triggered by a button. Use this component for a typical mobile hamburger menu.", + order: 1, + template: ( + + + <$.Button ws:style={getButtonStyle("ghost", "icon")}> + <$.HtmlEmbed ws:label="Hamburger Menu Svg" code={MenuIcon} /> + + + + + <$.Box ws:label="Navigation" tag="nav" role="navigation"> + <$.Box + ws:label="Sheet Header" + ws:style={css` + display: flex; + flex-direction: column; + gap: ${spacing[2]}; + `} + > + + {new PlaceholderValue("Sheet Title")} + + + {new PlaceholderValue("Sheet description text you can edit")} + + + <$.Text>{new PlaceholderValue("The text you can edit")} + + + <$.HtmlEmbed ws:label="Close Icon" code={LargeXIcon} /> + + + + + ), +}; diff --git a/packages/sdk-components-react-radix/src/sheet.ws.ts b/packages/sdk-components-react-radix/src/sheet.ws.ts deleted file mode 100644 index 6503b59771..0000000000 --- a/packages/sdk-components-react-radix/src/sheet.ws.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { MenuIcon, LargeXIcon } from "@webstudio-is/icons/svg"; -import type { WsComponentMeta } from "@webstudio-is/react-sdk"; -import * as tc from "./theme/tailwind-classes"; -import { getButtonStyles } from "./theme/styles"; - -/** - * Styles source without animations: - * https://github.com/shadcn-ui/ui/blob/main/apps/www/registry/default/ui/sheet.tsx - * - * Attributions - * MIT License - * Copyright (c) 2023 shadcn - **/ -export const meta: WsComponentMeta = { - category: "radix", - order: 1, - type: "container", - icon: MenuIcon, - stylable: false, - description: - "Displays content in a menu that slides out from the side of the screen, triggered by a button. Use this component for a typical mobile hamburger menu.", - template: [ - { - type: "instance", - component: "Dialog", - label: "Sheet", - props: [], - children: [ - { - type: "instance", - component: "DialogTrigger", - label: "Sheet Trigger", - children: [ - { - type: "instance", - component: "Button", - styles: getButtonStyles("ghost", "icon"), - children: [ - { - type: "instance", - component: "HtmlEmbed", - label: "Hamburger Menu Svg", - props: [ - { - type: "string", - name: "code", - value: MenuIcon, - }, - ], - children: [], - }, - ], - }, - ], - }, - { - type: "instance", - component: "DialogOverlay", - label: "Sheet Overlay", - /** - * fixed inset-0 z-50 bg-background/80 backdrop-blur-sm - * flex - **/ - styles: [ - tc.fixed(), - tc.inset(0), - tc.z(50), - tc.bg("background", 80), - tc.backdropBlur("sm"), - // To allow positioning Content - tc.flex(), - tc.flex("col"), - tc.overflow("auto"), - ].flat(), - children: [ - { - type: "instance", - component: "DialogContent", - label: "Sheet Content", - /** - * fixed w-full z-50 - * grid gap-4 max-w-lg - * m-auto - * border bg-background p-6 shadow-lg - **/ - styles: [ - tc.w("full"), - tc.z(50), - tc.flex(), - tc.flex("col"), - tc.gap(4), - tc.border(), - tc.bg("background"), - tc.p(6), - tc.shadow("lg"), - tc.relative(), - // side=left - tc.mr("auto"), - tc.maxW("sm"), - tc.grow(), - ].flat(), - children: [ - { - type: "instance", - component: "Box", - label: "Navigation", - props: [ - { name: "tag", type: "string", value: "nav" }, - { name: "role", type: "string", value: "navigation" }, - ], - children: [ - { - type: "instance", - component: "Box", - label: "Sheet Header", - styles: [tc.flex(), tc.flex("col"), tc.gap(1)].flat(), - children: [ - { - type: "instance", - component: "DialogTitle", - label: "Sheet Title", - /** - * text-lg leading-none tracking-tight - **/ - styles: [ - tc.my(0), - tc.leading("none"), - tc.text("lg"), - tc.tracking("tight"), - ].flat(), - children: [ - { - type: "text", - value: "Sheet Title", - placeholder: true, - }, - ], - }, - { - type: "instance", - component: "DialogDescription", - label: "Sheet Description", - /** - * text-sm text-muted-foreground - **/ - styles: [ - tc.my(0), - tc.text("sm"), - tc.text("mutedForeground"), - ].flat(), - children: [ - { - type: "text", - value: "Sheet description text you can edit", - placeholder: true, - }, - ], - }, - ], - }, - - { - type: "instance", - component: "Text", - children: [ - { - type: "text", - value: "The text you can edit", - placeholder: true, - }, - ], - }, - ], - }, - { - type: "instance", - component: "DialogClose", - label: "Close Button", - /** - * absolute right-4 top-4 - * rounded-sm opacity-70 - * ring-offset-background - * hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 - * flex items-center justify-center h-4 w-4 - **/ - styles: [ - tc.absolute(), - tc.right(4), - tc.top(4), - tc.rounded("sm"), - tc.opacity(70), - tc.flex(), - tc.items("center"), - tc.justify("center"), - tc.h(4), - tc.w(4), - tc.border(0), - tc.bg("transparent"), - tc.outline("none"), - tc.hover(tc.opacity(100)), - tc.focus(tc.ring("ring", 2, "background", 2)), - ].flat(), - children: [ - { - type: "instance", - component: "HtmlEmbed", - label: "Close Icon", - props: [ - { - type: "string", - name: "code", - value: LargeXIcon, - }, - ], - children: [], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], -}; diff --git a/packages/sdk-components-react-radix/src/tabs.template.tsx b/packages/sdk-components-react-radix/src/tabs.template.tsx index 91ae25380b..e54b44c75e 100644 --- a/packages/sdk-components-react-radix/src/tabs.template.tsx +++ b/packages/sdk-components-react-radix/src/tabs.template.tsx @@ -43,7 +43,8 @@ const tabsTriggerStyle = css` font-weight: ${weights.medium}; transition: ${transition.all}; &:focus-visible { - outline: none; + outline: 2px solid transparent; + outline-offset: 2px; box-shadow: ${boxShadow.ring}; } &:disabled { diff --git a/packages/sdk-components-react-radix/src/templates.ts b/packages/sdk-components-react-radix/src/templates.ts index ad138002ec..d1a9f48e33 100644 --- a/packages/sdk-components-react-radix/src/templates.ts +++ b/packages/sdk-components-react-radix/src/templates.ts @@ -1,2 +1,4 @@ export { meta as Label } from "./label.template"; export { meta as Tabs } from "./tabs.template"; +export { meta as Sheet } from "./sheet.template"; +export { meta as Dialog } from "./dialog.template";