Skip to content

Commit

Permalink
experimental: add tabbable command actions
Browse files Browse the repository at this point in the history
Ref #1696

Here adding new idea into command search.
User searches for an object and can navigate actions over this object.
In case of pages user can open page or open pangel settings panel.
  • Loading branch information
TrySound committed Nov 12, 2024
1 parent 8987281 commit b955a46
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 54 deletions.
79 changes: 55 additions & 24 deletions apps/builder/app/builder/features/command-panel/command-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { atom, computed } from "nanostores";
import { useStore } from "@nanostores/react";
import { useState } from "react";
import { matchSorter } from "match-sorter";
import {
collectionComponent,
componentCategories,
Expand All @@ -19,11 +21,14 @@ import {
Flex,
Kbd,
Text,
CommandActions,
useSelectedAction,
} from "@webstudio-is/design-system";
import { compareMedia } from "@webstudio-is/css-engine";
import type { Breakpoint, Page } from "@webstudio-is/sdk";
import {
$breakpoints,
$editingPageId,
$pages,
$registeredComponentMetas,
$selectedBreakpoint,
Expand All @@ -34,9 +39,8 @@ import { humanizeString } from "~/shared/string-utils";
import { setCanvasWidth } from "~/builder/features/breakpoints";
import { insert as insertComponent } from "~/builder/features/components/insert";
import { $selectedPage, selectPage } from "~/shared/awareness";
import { useState } from "react";
import { matchSorter } from "match-sorter";
import { mapGroupBy } from "~/shared/shim";
import { setActiveSidebarPanel } from "~/builder/shared/nano-states";

const $commandPanel = atom<
| undefined
Expand Down Expand Up @@ -124,30 +128,35 @@ const $componentOptions = computed(
}
);

const ComponentsGroup = ({ options }: { options: ComponentOption[] }) => {
const ComponentGroup = ({ options }: { options: ComponentOption[] }) => {
return (
<CommandGroup
value="component"
heading={<CommandGroupHeading>Components</CommandGroupHeading>}
actions={["add"]}
>
{options.map(({ component, label, meta }) => {
return (
<CommandItem
key={component}
keywords={["Components"]}
// preserve selected state when rerender
value={component}
onSelect={() => {
closeCommandPanel();
insertComponent(component);
}}
>
<CommandIcon
dangerouslySetInnerHTML={{ __html: meta.icon }}
></CommandIcon>
<Text variant="labelsTitleCase">
{label}{" "}
<Text as="span" color="moreSubtle">
{humanizeString(meta.category ?? "")}
<Flex gap={2}>
<CommandIcon
dangerouslySetInnerHTML={{ __html: meta.icon }}
></CommandIcon>
<Text variant="labelsTitleCase">
{label}{" "}
<Text as="span" color="moreSubtle">
{humanizeString(meta.category ?? "")}
</Text>
</Text>
</Text>
</Flex>
</CommandItem>
);
})}
Expand Down Expand Up @@ -198,21 +207,24 @@ const getBreakpointLabel = (breakpoint: Breakpoint) => {
return `${breakpoint.label}: ${label}`;
};

const BreakpointsGroup = ({ options }: { options: BreakpointOption[] }) => {
const BreakpointGroup = ({ options }: { options: BreakpointOption[] }) => {
return (
<CommandGroup
value="breakpoint"
heading={<CommandGroupHeading>Breakpoints</CommandGroupHeading>}
actions={["select"]}
>
{options.map(({ breakpoint, shortcut }) => (
<CommandItem
key={breakpoint.id}
// preserve selected state when rerender
value={breakpoint.id}
onSelect={() => {
closeCommandPanel({ restoreFocus: true });
$selectedBreakpointId.set(breakpoint.id);
setCanvasWidth(breakpoint.id);
}}
>
<CommandIcon></CommandIcon>
<Text variant="labelsTitleCase">
{getBreakpointLabel(breakpoint)}
</Text>
Expand Down Expand Up @@ -249,18 +261,33 @@ const $pageOptions = computed(
}
);

const PagesGroup = ({ options }: { options: PageOption[] }) => {
const PageGroup = ({ options }: { options: PageOption[] }) => {
const action = useSelectedAction();
return (
<CommandGroup heading={<CommandGroupHeading>Pages</CommandGroupHeading>}>
<CommandGroup
value="page"
heading={<CommandGroupHeading>Pages</CommandGroupHeading>}
actions={["select", "settings"]}
>
{options.map(({ page }) => (
<CommandItem
key={page.id}
// preserve selected state when rerender
value={page.id}
onSelect={() => {
closeCommandPanel();
selectPage(page.id);
if (action === "select") {
selectPage(page.id);
setActiveSidebarPanel("auto");
$editingPageId.set(undefined);
}
if (action === "settings") {
selectPage(page.id);
setActiveSidebarPanel("pages");
$editingPageId.set(page.id);
}
}}
>
<CommandIcon></CommandIcon>
<Text variant="labelsTitleCase">{page.name}</Text>
</CommandItem>
))}
Expand Down Expand Up @@ -288,38 +315,40 @@ const CommandDialogContent = () => {
}
const groups = mapGroupBy(matches, (match) => match.type);
return (
<Command shouldFilter={false}>
<>
<CommandInput value={search} onValueChange={setSearch} />
<CommandActions />
<Flex direction="column" css={{ maxHeight: 300 }}>
<ScrollArea>
<CommandList>
{Array.from(groups).map(([group, matches]) => {
if (group === "component") {
return (
<ComponentsGroup
<ComponentGroup
key={group}
options={matches as ComponentOption[]}
/>
);
}
if (group === "breakpoint") {
return (
<BreakpointsGroup
<BreakpointGroup
key={group}
options={matches as BreakpointOption[]}
/>
);
}
if (group === "page") {
return (
<PagesGroup key={group} options={matches as PageOption[]} />
<PageGroup key={group} options={matches as PageOption[]} />
);
}
group satisfies never;
})}
</CommandList>
</ScrollArea>
</Flex>
</Command>
</>
);
};

Expand All @@ -330,7 +359,9 @@ export const CommandPanel = () => {
open={isOpen}
onOpenChange={() => closeCommandPanel({ restoreFocus: true })}
>
<CommandDialogContent />
<Command shouldFilter={false}>
<CommandDialogContent />
</Command>
</CommandDialog>
);
};
65 changes: 41 additions & 24 deletions packages/design-system/src/components/command.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Meta, StoryFn } from "@storybook/react";
import {
CommandActions,
Command as CommandComponent,
CommandGroup,
CommandGroupHeading,
Expand All @@ -11,6 +12,7 @@ import {
import { Text } from "./text";
import { InfoCircleIcon } from "@webstudio-is/icons";
import { Kbd } from "./kbd";
import { Flex } from "./flex";

const meta: Meta = {
title: "Library/Command",
Expand All @@ -21,51 +23,66 @@ export const Command: StoryFn = () => {
return (
<CommandComponent>
<CommandInput />
<CommandActions />
<CommandList>
<CommandGroup
heading={<CommandGroupHeading>Suggestions</CommandGroupHeading>}
actions={["select", "edit", "delete"]}
>
<CommandItem>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Calendar</Text>
<Flex gap={2}>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Calendar</Text>
</Flex>
</CommandItem>
<CommandItem>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Search Emoji</Text>
<Flex gap={2}>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Search Emoji</Text>
</Flex>
</CommandItem>
<CommandItem>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Calculator</Text>
<Flex gap={2}>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Calculator</Text>
</Flex>
</CommandItem>
</CommandGroup>
<CommandGroup
heading={<CommandGroupHeading>Settings</CommandGroupHeading>}
actions={["open"]}
>
<CommandItem>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Profile</Text>
<Flex gap={2}>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Profile</Text>
</Flex>
<Kbd value={["cmd", "p"]} />
</CommandItem>
<CommandItem>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Billing</Text>
<Flex gap={2}>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Billing</Text>
</Flex>
<Kbd value={["cmd", "b"]} />
</CommandItem>
<CommandItem>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Settings</Text>
<Flex gap={2}>
<CommandIcon>
<InfoCircleIcon />
</CommandIcon>
<Text variant="labelsTitleCase">Settings</Text>
</Flex>
<Kbd value={["cmd", "s"]} />
</CommandItem>
</CommandGroup>
Expand Down
Loading

0 comments on commit b955a46

Please sign in to comment.