Skip to content

Commit

Permalink
experimental: Style panel focus mode (#4835)
Browse files Browse the repository at this point in the history
#4816

## Description

- implements accordion pattern
- adds a dropdown switch

## Steps for reproduction

1. click button
2. expect xyz

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
0000)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
  • Loading branch information
kof authored Feb 5, 2025
1 parent 609fdb4 commit a8bfa4d
Show file tree
Hide file tree
Showing 15 changed files with 267 additions and 110 deletions.
39 changes: 26 additions & 13 deletions apps/builder/app/builder/features/inspector/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
Kbd,
FloatingPanelProvider,
} from "@webstudio-is/design-system";
import { StylePanel } from "~/builder/features/style-panel";
import { ModeMenu, StylePanel } from "~/builder/features/style-panel";
import { SettingsPanelContainer } from "~/builder/features/settings-panel";
import {
$registeredComponentMetas,
Expand All @@ -33,6 +33,7 @@ import { getInstanceLabel } from "~/shared/instance-utils";
import { BindingPopoverProvider } from "~/builder/shared/binding-popover";
import { $activeInspectorPanel } from "~/builder/shared/nano-states";
import { $selectedInstance, $selectedPage } from "~/shared/awareness";
import { isFeatureEnabled } from "@webstudio-is/feature-flags";

const InstanceInfo = ({ instance }: { instance: Instance }) => {
const metas = useStore($registeredComponentMetas);
Expand All @@ -42,16 +43,7 @@ const InstanceInfo = ({ instance }: { instance: Instance }) => {
}
const label = getInstanceLabel(instance, componentMeta);
return (
<Flex
shrink="false"
gap="1"
align="center"
css={{
p: theme.panel.padding,
pb: 0,
color: theme.colors.foregroundSubtle,
}}
>
<Flex shrink={false} gap="1" align="center">
<MetaIcon icon={componentMeta.icon} />
<Text truncate variant="labelsSentenceCase">
{label}
Expand Down Expand Up @@ -175,7 +167,18 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
</PanelTabsList>
<Separator />
<PanelTabsContent value="style" css={contentStyle} tabIndex={-1}>
<InstanceInfo instance={selectedInstance} />
<Flex
justify="between"
align="center"
shrink={false}
css={{
paddingInline: theme.panel.paddingInline,
height: theme.spacing[13],
}}
>
<InstanceInfo instance={selectedInstance} />
{isFeatureEnabled("stylePanelModes") && <ModeMenu />}
</Flex>
<StylePanel />
</PanelTabsContent>
<PanelTabsContent
Expand All @@ -184,7 +187,17 @@ export const Inspector = ({ navigatorLayout }: InspectorProps) => {
tabIndex={-1}
>
<ScrollArea>
<InstanceInfo instance={selectedInstance} />
<Flex
justify="between"
align="center"
shrink={false}
css={{
paddingInline: theme.panel.paddingInline,
height: theme.spacing[13],
}}
>
<InstanceInfo instance={selectedInstance} />
</Flex>
<SettingsPanelContainer
// Re-render when instance changes
key={selectedInstance.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,15 @@ const VariablesList = () => {
);
};

const label = "Data Variables";

export const VariablesSection = () => {
const containerRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useOpenState({ label: "variables" });
const [isOpen, setIsOpen] = useOpenState(label);
return (
<VariablePopoverProvider value={{ containerRef }}>
<CollapsibleSectionRoot
label="Data Variables"
label={label}
fullWidth={true}
isOpen={isOpen}
onOpenChange={setIsOpen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export const PropertyInfo = ({
<ResetIcon />
</Flex>
}
suffix={<Kbd value={["option", "click"]} color="moreSubtle" />}
suffix={<Kbd value={["alt", "click"]} color="moreSubtle" />}
css={{ gridTemplateColumns: "1fr max-content 1fr" }}
onClick={onReset}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const AdvancedStyleSection = (props: {
children: ReactNode;
}) => {
const { label, children, properties, onAdd } = props;
const [isOpen, setIsOpen] = useOpenState(props);
const [isOpen, setIsOpen] = useOpenState(label);
const styles = useComputedStyles(properties);
return (
<CollapsibleSectionRoot
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
forwardRef,
useState,
type ElementRef,
type ComponentProps,
} from "react";
import { forwardRef, type ElementRef, type ComponentProps } from "react";
import type { StyleProperty } from "@webstudio-is/css-engine";
import { propertyDescriptions } from "@webstudio-is/css-data";
import {
Expand Down Expand Up @@ -32,7 +27,10 @@ import {
EyeOpenIcon,
EllipsesIcon,
} from "@webstudio-is/icons";
import { CollapsibleSectionRoot } from "~/builder/shared/collapsible-section";
import {
CollapsibleSectionRoot,
useOpenState,
} from "~/builder/shared/collapsible-section";
import {
addDefaultsForTransormSection,
isTransformPanelPropertyUsed,
Expand Down Expand Up @@ -138,7 +136,7 @@ const TransformAdvancedPopover = () => {
};

export const Section = () => {
const [isOpen, setIsOpen] = useState(true);
const [isOpen, setIsOpen] = useOpenState(label);

const styles = useComputedStyles(properties);
const isAnyTransformPropertyAdded = transformPanels.some((panel) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState } from "react";
import { useStore } from "@nanostores/react";
import { PlusIcon } from "@webstudio-is/icons";
import {
Expand All @@ -12,7 +11,10 @@ import {
type LayerValueItem,
type StyleProperty,
} from "@webstudio-is/css-engine";
import { CollapsibleSectionRoot } from "~/builder/shared/collapsible-section";
import {
CollapsibleSectionRoot,
useOpenState,
} from "~/builder/shared/collapsible-section";
import { $selectedOrLastStyleSourceSelector } from "~/shared/nano-states";
import { humanizeString } from "~/shared/string-utils";
import { repeatUntil } from "~/shared/array-utils";
Expand Down Expand Up @@ -83,7 +85,7 @@ const getLayerLabel = ({
};

export const Section = () => {
const [isOpen, setIsOpen] = useState(true);
const [isOpen, setIsOpen] = useOpenState(label);

const selectedOrLastStyleSourceSelector = useStore(
$selectedOrLastStyleSourceSelector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,9 @@ export const StyleSection = (props: {
// @todo remove to keep sections consistent
fullWidth?: boolean;
children: ReactNode;
accordion?: string;
initialOpen?: string;
}) => {
const { label, children, properties, fullWidth } = props;
const [isOpen, setIsOpen] = useOpenState(props);
const [isOpen, setIsOpen] = useOpenState(label);
const styles = useComputedStyles(properties);
return (
<CollapsibleSectionRoot
Expand Down Expand Up @@ -74,7 +72,7 @@ export const RepeatedStyleSection = (props: {
}) => {
const { label, description, children, properties, onAdd, collapsible } =
props;
const [isOpen, setIsOpen] = useOpenState(props);
const [isOpen, setIsOpen] = useOpenState(label);
const styles = useComputedStyles(properties);
const dots = getDots(styles);

Expand Down
93 changes: 85 additions & 8 deletions apps/builder/app/builder/features/style-panel/style-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ import {
Text,
Separator,
ScrollArea,
DropdownMenu,
DropdownMenuTrigger,
IconButton,
DropdownMenuContent,
DropdownMenuRadioItem,
MenuCheckedIcon,
DropdownMenuRadioGroup,
rawTheme,
Kbd,
Flex,
DropdownMenuSeparator,
DropdownMenuItem,
} from "@webstudio-is/design-system";
import { useStore } from "@nanostores/react";
import { computed } from "nanostores";
Expand All @@ -14,8 +26,15 @@ import { sections } from "./sections";
import { toValue } from "@webstudio-is/css-engine";
import { $instanceTags, useParentComputedStyleDecl } from "./shared/model";
import { $selectedInstance } from "~/shared/awareness";
import { CollapsibleSectionContext } from "~/builder/shared/collapsible-section";
import { isFeatureEnabled } from "@webstudio-is/feature-flags";
import { CollapsibleProvider } from "~/builder/shared/collapsible-section";
import { EllipsesIcon } from "@webstudio-is/icons";
import {
$settings,
getSetting,
setSetting,
type Settings,
} from "~/builder/shared/client-settings";
import { useState } from "react";

const $selectedInstanceTag = computed(
[$selectedInstance, $instanceTags],
Expand All @@ -27,7 +46,67 @@ const $selectedInstanceTag = computed(
}
);

export const ModeMenu = () => {
const value = getSetting("stylePanelMode");
const [focusedValue, setFocusedValue] = useState<string>(value);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton>
<EllipsesIcon />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent
sideOffset={Number.parseFloat(rawTheme.spacing[5])}
css={{ width: theme.spacing[25] }}
>
<DropdownMenuRadioGroup
value={value}
onValueChange={(value) => {
setSetting("stylePanelMode", value as Settings["stylePanelMode"]);
}}
>
<DropdownMenuRadioItem
value="default"
icon={<MenuCheckedIcon />}
onFocus={() => setFocusedValue("default")}
>
Default
</DropdownMenuRadioItem>
<DropdownMenuRadioItem
value="focus"
icon={<MenuCheckedIcon />}
onFocus={() => setFocusedValue("focus")}
>
<Flex justify="between" grow>
<Text>Focus mode</Text> <Kbd value={["alt", "shift", "s"]} />
</Flex>
</DropdownMenuRadioItem>
{/*
<DropdownMenuRadioItem value="advanced" icon={<MenuCheckedIcon />}>
Advanced mode
</DropdownMenuRadioItem> */}
</DropdownMenuRadioGroup>
<DropdownMenuSeparator />

{focusedValue === "default" && (
<DropdownMenuItem hint>
All sections are open by default.
</DropdownMenuItem>
)}
{focusedValue === "focus" && (
<DropdownMenuItem hint>
Only one section is open at a time.
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};

export const StylePanel = () => {
const { stylePanelMode } = useStore($settings);
const selectedInstanceRenderState = useStore($selectedInstanceRenderState);
const tag = useStore($selectedInstanceTag);
const display = useParentComputedStyleDecl("display");
Expand Down Expand Up @@ -75,14 +154,12 @@ export const StylePanel = () => {
</Box>
<Separator />
<ScrollArea>
<CollapsibleSectionContext.Provider
value={{
accordion: isFeatureEnabled("stylePanelModes"),
initialOpen: "Layout",
}}
<CollapsibleProvider
accordion={stylePanelMode === "focus"}
initialOpen={stylePanelMode === "focus" ? "Layout" : "*"}
>
{all}
</CollapsibleSectionContext.Provider>
</CollapsibleProvider>
</ScrollArea>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/builder/features/topbar/topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const PagesButton = () => {
content={
<Text>
{"Pages or page settings "}
<Kbd value={["option", "click"]} color="moreSubtle" />
<Kbd value={["alt", "click"]} color="moreSubtle" />
</Text>
}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export const TemplatesMenu = ({
display: hasChildren ? undefined : "none",
}}
>
<Kbd value={["option", "click"]} /> <Text>to add before</Text>
<Kbd value={["alt", "click"]} /> <Text>to add before</Text>
</Flex>
</Grid>
</div>
Expand Down Expand Up @@ -303,7 +303,7 @@ export const BlockChildHoveredInstanceOutline = () => {
gap={1}
css={{ order: 1, display: !hasChildren ? "none" : undefined }}
>
<Kbd value={["option", "click"]} color="contrast" />{" "}
<Kbd value={["alt", "click"]} color="contrast" />{" "}
<Text color="subtle">to delete</Text>
</Flex>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Settings = z.object({
navigatorLayout: z.enum(["docked", "undocked"]).default("undocked"),
isAiMenuOpen: z.boolean().default(true),
isAiCommandBarVisible: z.boolean().default(false),
stylePanelMode: z.enum(["default", "focus", "advanced"]).default("default"),
});

export type Settings = z.infer<typeof Settings>;
Expand Down
Loading

0 comments on commit a8bfa4d

Please sign in to comment.