diff --git a/apps/builder/app/builder/features/sidebar-left/panels/pages/page-settings.tsx b/apps/builder/app/builder/features/sidebar-left/panels/pages/page-settings.tsx index bd1ff24db052..4e47ce0d8fdb 100644 --- a/apps/builder/app/builder/features/sidebar-left/panels/pages/page-settings.tsx +++ b/apps/builder/app/builder/features/sidebar-left/panels/pages/page-settings.tsx @@ -112,7 +112,7 @@ const fieldDefaultValues = { isHomePage: false, title: `"Untitled"`, description: `""`, - excludePageFromSearch: `false`, + excludePageFromSearch: `true`, language: `""`, socialImageUrl: `""`, socialImageAssetId: "", diff --git a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx index b34e2df7ee20..1d89a74c2b66 100644 --- a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx @@ -160,12 +160,12 @@ export const Section = ({ styleSource={getStyleSource(currentStyle[property])} keywords={keywords} value={currentStyle[property]?.value} - setValue={(styleValue, options) => { + setValue={(styleValue, options) => setProperty(property)(styleValue, { ...options, listed: true, - }); - }} + }) + } deleteProperty={deleteProperty} /> diff --git a/apps/builder/app/builder/features/style-panel/sections/backdrop-filter/backdrop-filter.tsx b/apps/builder/app/builder/features/style-panel/sections/backdrop-filter/backdrop-filter.tsx new file mode 100644 index 000000000000..17226abed734 --- /dev/null +++ b/apps/builder/app/builder/features/style-panel/sections/backdrop-filter/backdrop-filter.tsx @@ -0,0 +1,115 @@ +import { CollapsibleSectionRoot } from "~/builder/shared/collapsible-section"; +import type { SectionProps } from "../shared/section"; +import type { + FunctionValue, + StyleProperty, + TupleValue, +} from "@webstudio-is/css-engine"; +import { useState } from "react"; +import { + SectionTitle, + SectionTitleButton, + SectionTitleLabel, + Tooltip, + Flex, + Text, +} from "@webstudio-is/design-system"; +import { PropertyName } from "../../shared/property-name"; +import { getStyleSource } from "../../shared/style-info"; +import { getDots } from "../../shared/collapsible-section"; +import { InfoCircleIcon, PlusIcon } from "@webstudio-is/icons"; +import { addLayer } from "../../style-layer-utils"; +import { parseFilter } from "@webstudio-is/css-data"; +import { LayersList } from "../../style-layers-list"; +import { FilterLayer } from "../filter/filter-layer"; + +export const properties = ["backdropFilter"] satisfies Array; + +const property: StyleProperty = properties[0]; +const label = "Backdrop Filters"; +const INITIAL_BACKDROP_FILTER = "blur(0px)"; + +export const Section = (props: SectionProps) => { + const { currentStyle, deleteProperty } = props; + const [isOpen, setIsOpen] = useState(false); + const value = currentStyle[property]?.value; + const sectionStyleSource = + value?.type === "unparsed" || value?.type === "guaranteedInvalid" + ? undefined + : getStyleSource(currentStyle[property]); + + return ( + + } + onClick={() => { + addLayer( + property, + parseFilter(INITIAL_BACKDROP_FILTER), + currentStyle, + props.createBatchUpdate + ); + setIsOpen(true); + }} + /> + + } + > + + {label} + + } + onReset={() => deleteProperty(property)} + /> + + } + > + {value?.type === "tuple" && value.value.length > 0 && ( + + {...props} + property={property} + layers={value} + renderLayer={(layerProps) => ( + + {label} + backdrop-filter + + Applies graphical effects like blur or color shift to + the area behind an element + + + } + > + + + } + /> + )} + /> + )} + + ); +}; diff --git a/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx b/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx index cd16ac160640..6283cb31d6de 100644 --- a/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx @@ -29,8 +29,11 @@ const INITIAL_BOX_SHADOW = "0px 2px 5px 0px rgba(0, 0, 0, 0.2)"; export const Section = (props: SectionProps) => { const { currentStyle, deleteProperty } = props; const [isOpen, setIsOpen] = useState(true); - const layersStyleSource = getStyleSource(currentStyle[property]); const value = currentStyle[property]?.value; + const sectionStyleSource = + value?.type === "unparsed" || value?.type === "guaranteedInvalid" + ? undefined + : getStyleSource(currentStyle[property]); return ( { properties={properties} description="Adds shadow effects around an element's frame." label={ - + {label} } diff --git a/apps/builder/app/builder/features/style-panel/sections/filter/filter-content.tsx b/apps/builder/app/builder/features/style-panel/sections/filter/filter-content.tsx index 90c61a455c77..6ce4df8a61cd 100644 --- a/apps/builder/app/builder/features/style-panel/sections/filter/filter-content.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/filter/filter-content.tsx @@ -7,12 +7,9 @@ import { Flex, theme, Label, - Tooltip, - Text, TextArea, textVariants, } from "@webstudio-is/design-system"; -import { InfoCircleIcon } from "@webstudio-is/icons"; import { useState } from "react"; import type { IntermediateStyleValue } from "../../shared/css-value-input"; import { parseFilter } from "@webstudio-is/css-data"; @@ -23,6 +20,7 @@ type FilterContentProps = { filter: string; onEditLayer: (index: number, layers: LayersValue | TupleValue) => void; deleteProperty: DeleteProperty; + tooltip: JSX.Element; }; export const FilterSectionContent = ({ @@ -30,6 +28,7 @@ export const FilterSectionContent = ({ filter, onEditLayer, deleteProperty, + tooltip, }: FilterContentProps) => { const [intermediateValue, setIntermediateValue] = useState< IntermediateStyleValue | InvalidValue | undefined @@ -73,21 +72,7 @@ export const FilterSectionContent = ({ { diff --git a/apps/builder/app/builder/features/style-panel/sections/filter/filter-layer.tsx b/apps/builder/app/builder/features/style-panel/sections/filter/filter-layer.tsx index 61412a5132fb..cab1e36dd9c0 100644 --- a/apps/builder/app/builder/features/style-panel/sections/filter/filter-layer.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/filter/filter-layer.tsx @@ -11,18 +11,19 @@ import { useMemo } from "react"; import { FunctionValue, toValue } from "@webstudio-is/css-engine"; export const FilterLayer = (props: LayerProps) => { - const { index, id, layer, isHighlighted, onDeleteLayer } = props; + const { index, id, layer, isHighlighted, onDeleteLayer, label } = props; const filter = useMemo(() => toValue(layer), [layer]); return ( } > diff --git a/apps/builder/app/builder/features/style-panel/sections/filter/filter.tsx b/apps/builder/app/builder/features/style-panel/sections/filter/filter.tsx index 9db09ede1cab..36f75c9eae38 100644 --- a/apps/builder/app/builder/features/style-panel/sections/filter/filter.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/filter/filter.tsx @@ -2,14 +2,16 @@ import { CollapsibleSectionRoot } from "~/builder/shared/collapsible-section"; import type { SectionProps } from "../shared/section"; import { useState } from "react"; import { + Flex, SectionTitle, SectionTitleButton, SectionTitleLabel, Tooltip, + Text, } from "@webstudio-is/design-system"; import { getDots } from "../../shared/collapsible-section"; import { PropertyName } from "../../shared/property-name"; -import { PlusIcon } from "@webstudio-is/icons"; +import { InfoCircleIcon, PlusIcon } from "@webstudio-is/icons"; import { FunctionValue, TupleValue, @@ -30,8 +32,11 @@ const INITIAL_FILTER = "blur(0px)"; export const Section = (props: SectionProps) => { const { currentStyle, deleteProperty } = props; const [isOpen, setIsOpen] = useState(true); - const layerStyleSource = getStyleSource(currentStyle[property]); const value = currentStyle[property]?.value; + const sectionStyleSource = + value?.type === "unparsed" || value?.type === "guaranteedInvalid" + ? undefined + : getStyleSource(currentStyle[property]); return ( { onOpenChange={setIsOpen} trigger={ { properties={properties} description="Filter effects allow you to apply graphical effects like blurring, color shifting, and more to elements." label={ - + {label} } @@ -80,7 +85,28 @@ export const Section = (props: SectionProps) => { property={property} layers={value} renderLayer={(layerProps) => ( - + + {label} + filter + + Applies graphical effects like blur or color shift to an + element + + + } + > + + + } + /> )} /> )} diff --git a/apps/builder/app/builder/features/style-panel/sections/sections.ts b/apps/builder/app/builder/features/style-panel/sections/sections.ts index 75ea996206b0..e6a6d657a465 100644 --- a/apps/builder/app/builder/features/style-panel/sections/sections.ts +++ b/apps/builder/app/builder/features/style-panel/sections/sections.ts @@ -13,6 +13,7 @@ import * as filter from "./filter/filter"; import * as transitions from "./transitions/transitions"; import * as outline from "./outline/outline"; import * as advanced from "./advanced/advanced"; +import * as backdropFilter from "./backdrop-filter/backdrop-filter"; import type { StyleProperty } from "@webstudio-is/css-engine"; import type { SectionProps } from "./shared/section"; @@ -34,6 +35,7 @@ export const sections = new Map< ["borders", borders], ["boxShadows", boxShadows], ["filter", filter], + ["backdropFilters", backdropFilter], ["transitions", transitions], ["outline", outline], ["advanced", advanced], diff --git a/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.tsx b/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.tsx index d9829b732db3..991f572b7639 100644 --- a/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/transitions/transitions.tsx @@ -32,8 +32,11 @@ const INITIAL_TRANSITION = "opacity 200ms ease"; export const Section = (props: SectionProps) => { const { currentStyle, deleteProperty } = props; const [isOpen, setIsOpen] = useState(true); - const layersStyleSource = getStyleSource(currentStyle[property]); const value = currentStyle[property]?.value; + const sectionStyleSource = + value?.type === "unparsed" || value?.type === "guaranteedInvalid" + ? undefined + : getStyleSource(currentStyle[property]); const selectedOrLastStyleSourceSelector = useStore( $selectedOrLastStyleSourceSelector @@ -82,7 +85,7 @@ export const Section = (props: SectionProps) => { description="Animate the transition between states on this instance." properties={properties} label={ - + {label} } diff --git a/apps/builder/app/builder/features/style-panel/shared/collapsible-section.tsx b/apps/builder/app/builder/features/style-panel/shared/collapsible-section.tsx index 20323b85700e..d269a672c903 100644 --- a/apps/builder/app/builder/features/style-panel/shared/collapsible-section.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/collapsible-section.tsx @@ -19,7 +19,17 @@ export const getDots = ( const dots = new Set<"local" | "overwritten" | "remote">(); for (const property of properties) { - const source = getStyleSource(currentStyle[property]); + const style = currentStyle[property]; + + // Unparsed values are not editable directly in the section, so we don't show the dot + if ( + style?.value.type === "unparsed" || + style?.value.type === "guaranteedInvalid" + ) { + return; + } + + const source = getStyleSource(style); if (source === "local" || source === "overwritten" || source === "remote") { dots.add(source); } diff --git a/apps/builder/app/builder/features/style-panel/style-layer-utils.ts b/apps/builder/app/builder/features/style-panel/style-layer-utils.ts index 3320360cdec7..58d3ebe90013 100644 --- a/apps/builder/app/builder/features/style-panel/style-layer-utils.ts +++ b/apps/builder/app/builder/features/style-panel/style-layer-utils.ts @@ -87,7 +87,10 @@ export const addLayer = ( value.value = [...value.value, ...existingValues.value] as T["value"]; } - if (property === "filter" && existingValues?.type === "tuple") { + if ( + (property === "filter" || property === "backdropFilter") && + existingValues?.type === "tuple" + ) { value.value = [ ...value.value, ...(existingValues?.value || []), diff --git a/apps/builder/app/builder/features/style-panel/style-layers-list.tsx b/apps/builder/app/builder/features/style-panel/style-layers-list.tsx index 3c0fa5dfcfee..9dbd0bb95c41 100644 --- a/apps/builder/app/builder/features/style-panel/style-layers-list.tsx +++ b/apps/builder/app/builder/features/style-panel/style-layers-list.tsx @@ -26,6 +26,8 @@ export type LayerProps = { id: string; index: number; layer: LayerType; + label: string; + tooltip: JSX.Element; isHighlighted: boolean; disabled?: boolean; onLayerHide: (index: number) => void; diff --git a/apps/builder/app/builder/shared/binding-popover.tsx b/apps/builder/app/builder/shared/binding-popover.tsx index ec59a65afffb..7b530f510cb2 100644 --- a/apps/builder/app/builder/shared/binding-popover.tsx +++ b/apps/builder/app/builder/shared/binding-popover.tsx @@ -282,6 +282,9 @@ const BindingButton = forwardRef< "--dot-display": "none", "--plus-display": "block", }, + "&:disabled": { + display: "none", + }, }} {...props} icon={ diff --git a/package.json b/package.json index cdbafefa2697..4ba7d13eb66d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "pnpm -r --filter='!./fixtures/*' build", "dts": "pnpm -r dts", "checks": "pnpm -r checks", - "dev": "pnpm --filter='!./fixtures/*' --parallel --recursive dev", + "dev": "pnpm --filter='@webstudio-is/builder' --filter='@webstudio-is/prisma-client' --parallel --recursive dev", "lint": "eslint . --ext .ts,.tsx --max-warnings 0", "format": "prettier --write \"**/*.{ts,tsx,md}\"", "storybook": "pnpm --parallel --recursive storybook:dev", diff --git a/packages/css-data/src/parse-css-value.ts b/packages/css-data/src/parse-css-value.ts index 0c5523755d17..f96565d649b0 100644 --- a/packages/css-data/src/parse-css-value.ts +++ b/packages/css-data/src/parse-css-value.ts @@ -10,6 +10,7 @@ import { } from "@webstudio-is/css-engine"; import { keywordValues } from "./__generated__/keyword-values"; import { units } from "./__generated__/units"; +import { parseFilter, parseShadow, parseTransition } from "./property-parsers"; export const cssTryParseValue = (input: string) => { try { @@ -78,6 +79,18 @@ export const parseCssValue = ( return invalidValue; } + if (property === "filter" || property === "backdropFilter") { + return parseFilter(input); + } + + if (property === "boxShadow" || property === "textShadow") { + return parseShadow(property, input); + } + + if (property === "transition") { + return parseTransition(input); + } + if ( ast != null && ast.type === "Value" && diff --git a/packages/design-system/src/components/button.stories.tsx b/packages/design-system/src/components/button.stories.tsx index bc444aec62e7..71e639664490 100644 --- a/packages/design-system/src/components/button.stories.tsx +++ b/packages/design-system/src/components/button.stories.tsx @@ -83,6 +83,11 @@ export const Button = ({ } color={color} disabled> {color} disabled +
+ } color={color}> + {color} disabled by fieldset + +
))} diff --git a/packages/design-system/src/components/button.tsx b/packages/design-system/src/components/button.tsx index 401dc2b09686..c86d4be415cf 100644 --- a/packages/design-system/src/components/button.tsx +++ b/packages/design-system/src/components/button.tsx @@ -96,7 +96,7 @@ const perColorStyle = (variant: ButtonColor) => ({ ), }, - "&[data-state=disabled]": { + "&:disabled:not([data-state=pending]), &[data-state=disabled]": { background: theme.colors.backgroundButtonDisabled, color: theme.colors.foregroundDisabled, }, diff --git a/packages/sdk-components-react-remix/src/remix-form.tsx b/packages/sdk-components-react-remix/src/remix-form.tsx index bd2d9ac7347a..960093d18b94 100644 --- a/packages/sdk-components-react-remix/src/remix-form.tsx +++ b/packages/sdk-components-react-remix/src/remix-form.tsx @@ -13,7 +13,11 @@ export const RemixForm = forwardRef< } >((props, ref) => { // remix casts action to relative url - if (props.action === "" || props.action?.startsWith("/")) { + if ( + props.action === undefined || + props.action === "" || + props.action?.startsWith("/") + ) { return
; } return ;