Pages small improvements (#428)

- Fixes #424
- Fixes #405
- Removed the "pages" feature-flag — enabled it without a flag
This commit is contained in:
Roman Pominov authored and GitHub committed 2022-11-09 12:06:34 +03:00
1 parent 940f7a87b5
commit b80165e6a4
8 files changed
+125 -51

No files matched your search

@@ -1,4 +1,4 @@
import { Flex, IconButton, Text } from "@webstudio-is/design-system";
import { Flex, IconButton, Text, Tooltip } from "@webstudio-is/design-system";
import { Separator } from "@webstudio-is/design-system";
import { CrossIcon } from "@webstudio-is/icons";
@@ -24,7 +24,9 @@ export const Header = ({ title, suffix }: HeaderProps) => {
};
export const CloseButton = ({ onClick }: { onClick: () => void }) => (
<IconButton onClick={onClick} size="2" aria-label="Close">
<CrossIcon />
</IconButton>
<Tooltip content="Close panel" side="bottom" align="end">
<IconButton onClick={onClick} size="2" aria-label="Close panel">
<CrossIcon />
</IconButton>
</Tooltip>
);
@@ -6,6 +6,7 @@ import {
type TreeItemRenderProps,
styled,
Flex,
Tooltip,
} from "@webstudio-is/design-system";
import { type Publish } from "~/shared/pubsub";
import {
@@ -119,13 +120,19 @@ const PagesPanel = ({
{isEditing ? (
<ChevronRightIcon />
) : (
<MenuButton
aria-label="Page settings"
isParentSelected={isSelected}
onClick={() => onEdit(props.itemData.id)}
<Tooltip
content="Open page settings"
side="left"
disableHoverableContent
>
<MenuIcon />
</MenuButton>
<MenuButton
aria-label="Open page settings"
isParentSelected={isSelected}
onClick={() => onEdit(props.itemData.id)}
>
<MenuIcon />
</MenuButton>
</Tooltip>
)}
</Flex>
)
@@ -153,13 +160,15 @@ const PagesPanel = ({
suffix={
<>
{onCreateNewPage && (
<IconButton
size="2"
onClick={() => onCreateNewPage()}
aria-label="New Page"
>
<NewPageIcon />
</IconButton>
<Tooltip content="New page" side="bottom">
<IconButton
size="2"
onClick={() => onCreateNewPage()}
aria-label="New page"
>
<NewPageIcon />
</IconButton>
</Tooltip>
)}
{onClose && <CloseButton onClick={onClose} />}
</>
@@ -3,10 +3,12 @@ import {
Button,
Box,
Label,
TextArea,
TextField,
styled,
Flex,
InputErrorsTooltip,
Tooltip,
} from "@webstudio-is/design-system";
import { useFetcher } from "@remix-run/react";
import { ChevronDoubleLeftIcon, TrashIcon } from "@webstudio-is/icons";
@@ -37,9 +39,20 @@ const Group = styled(Flex, {
defaultVariants: { direction: "column" },
});
const fieldNames = ["name", "path"] as const;
const fieldNames = ["name", "path", "title", "description"] as const;
type FieldName = typeof fieldNames[number];
type EditablePage = Pick<Page, FieldName>;
type FormPage = Pick<Page, "name" | "path" | "title"> & {
description: string;
};
const toFormPage = (page: Page): FormPage => {
return {
name: page.name,
path: page.path,
title: page.title,
description: page.meta.description ?? "",
};
};
const FormFields = ({
disabled,
@@ -48,10 +61,10 @@ const FormFields = ({
fieldErrors,
}: {
disabled?: boolean;
values: EditablePage;
values: FormPage;
onChange: <Name extends FieldName>(event: {
field: Name;
value: EditablePage[Name];
value: FormPage[Name];
}) => void;
fieldErrors: ZodError["formErrors"]["fieldErrors"];
}) => {
@@ -89,6 +102,36 @@ const FormFields = ({
/>
</InputErrorsTooltip>
</Group>
<Group>
<Label htmlFor={fieldIds.title}>Title</Label>
<InputErrorsTooltip errors={fieldErrors.title}>
<TextField
state={fieldErrors.title && "invalid"}
id={fieldIds.title}
name="title"
disabled={disabled}
value={values?.title}
onChange={(event) => {
onChange({ field: "title", value: event.target.value });
}}
/>
</InputErrorsTooltip>
</Group>
<Group>
<Label htmlFor={fieldIds.description}>Description</Label>
<InputErrorsTooltip errors={fieldErrors.description}>
<TextArea
state={fieldErrors.description && "invalid"}
id={fieldIds.description}
name="description"
disabled={disabled}
value={values?.description}
onChange={(event) => {
onChange({ field: "description", value: event.target.value });
}}
/>
</InputErrorsTooltip>
</Group>
</>
);
};
@@ -117,7 +160,12 @@ export const NewPageSettings = ({
fieldNames,
});
const [values, setValues] = useState<EditablePage>({ name: "", path: "" });
const [values, setValues] = useState<FormPage>({
name: "",
path: "",
title: "",
description: "",
});
const handleSubmit = () => {
fetcher.submit(values, {
@@ -158,9 +206,11 @@ const NewPageSettingsView = ({
title="New Page Settings"
suffix={
onClose && (
<IconButton size="2" onClick={onClose} aria-label="Cancel">
<ChevronDoubleLeftIcon />
</IconButton>
<Tooltip content="Cancel" side="bottom" align="end">
<IconButton size="2" onClick={onClose} aria-label="Cancel">
<ChevronDoubleLeftIcon />
</IconButton>
</Tooltip>
)
}
/>
@@ -183,7 +233,7 @@ const NewPageSettingsView = ({
);
};
const toFormData = (page: Partial<EditablePage> & { id: string }): FormData => {
const toFormData = (page: Partial<FormPage> & { id: string }): FormData => {
const formData = new FormData();
for (const [key, value] of Object.entries(page)) {
// @todo: handle "meta"
@@ -212,10 +262,8 @@ export const PageSettings = ({
const [pages] = usePages();
const page = pages && projectUtils.pages.findById(pages, pageId);
const [unsavedValues, setUnsavedValues] = useState<Partial<EditablePage>>({});
const [submittedValues, setSubmittedValues] = useState<Partial<EditablePage>>(
{}
);
const [unsavedValues, setUnsavedValues] = useState<Partial<FormPage>>({});
const [submittedValues, setSubmittedValues] = useState<Partial<FormPage>>({});
const { fieldErrors, resetFieldError } = useFetcherErrors({
fetcher,
@@ -223,10 +271,7 @@ export const PageSettings = ({
});
const handleChange = useCallback(
<Name extends FieldName>(event: {
field: Name;
value: EditablePage[Name];
}) => {
<Name extends FieldName>(event: { field: Name; value: FormPage[Name] }) => {
resetFieldError(event.field);
setUnsavedValues((values) => ({ ...values, [event.field]: event.value }));
},
@@ -304,7 +349,7 @@ export const PageSettings = ({
onClose={onClose}
onDelete={hanldeDelete}
fieldErrors={fieldErrors}
values={{ ...page, ...submittedValues, ...unsavedValues }}
values={{ ...toFormPage(page), ...submittedValues, ...unsavedValues }}
onChange={handleChange}
/>
);
@@ -327,18 +372,26 @@ const PageSettingsView = ({
suffix={
<>
{isHomePage === false && (
<IconButton size="2" onClick={onDelete} aria-label="Delete page">
<TrashIcon />
</IconButton>
<Tooltip content="Delete page" side="bottom">
<IconButton
size="2"
onClick={onDelete}
aria-label="Delete page"
>
<TrashIcon />
</IconButton>
</Tooltip>
)}
{onClose && (
<IconButton
size="2"
onClick={onClose}
aria-label="Close page settings"
>
<ChevronDoubleLeftIcon />
</IconButton>
<Tooltip content="Close page settings" side="bottom" align="end">
<IconButton
size="2"
onClick={onClose}
aria-label="Close page settings"
>
<ChevronDoubleLeftIcon />
</IconButton>
</Tooltip>
)}
</>
}
@@ -38,8 +38,6 @@ export const SidebarLeft = ({ publish }: SidebarLeftProps) => {
switch (panel) {
case "assets":
return isFeatureEnabled("assets");
case "pages":
return isFeatureEnabled("pages");
case "navigator":
return clientSettings.navigatorLayout === "docked";
}
@@ -33,6 +33,8 @@ const commonPageInput = {
(path) => /^[-_a-z0-9\\/]*$/.test(path),
"Only a-z, 0-9, -, _ and / are allowed"
),
title: z.string().optional(),
description: z.string().optional(),
} as const;
const CreatePageInput = zfd.formData(commonPageInput);
@@ -45,7 +47,11 @@ const handlePut = async (
if (result.success === false) {
return { status: "error", errors: result.error.formErrors };
}
const data = result.data;
const { description, ...restData } = result.data;
const data = {
...restData,
meta: description !== undefined ? { description } : undefined,
};
const devBuild = await db.build.loadByProjectId(projectId, "dev");
@@ -69,6 +75,8 @@ const EditPageInput = zfd.formData({
id: z.string(),
name: z.optional(commonPageInput.name),
path: z.optional(commonPageInput.path),
title: z.optional(commonPageInput.title),
description: z.optional(commonPageInput.description),
});
const handlePost = async (
@@ -79,7 +87,11 @@ const handlePost = async (
if (result.success === false) {
return { status: "error", errors: result.error.formErrors };
}
const { id, ...data } = result.data;
const { id, description, ...restData } = result.data;
const data = {
...restData,
meta: description !== undefined ? { description } : undefined,
};
const devBuild = await db.build.loadByProjectId(projectId, "dev");
@@ -1,4 +1,3 @@
export const assets = false;
export const fonts = false;
export const example = false;
export const pages = false;
+1
View File
@@ -11,6 +11,7 @@ export * as ToggleGroup from "./components/toggle-group";
export * from "./components/progress-radial";
export * from "./components/select";
export * from "./components/text-field";
export * from "./components/text-area";
export { SearchField } from "./components/search-field";
export { Flex } from "./components/flex";
export { Image } from "./components/image";
+1 -1
View File
@@ -120,7 +120,7 @@ export const editPage = async (
name: data.name ?? currentPage.name,
path: data.path ?? currentPage.path,
title: data.title ?? currentPage.title,
meta: data.meta ?? currentPage.meta,
meta: { ...currentPage.meta, ...data.meta },
};
return {