mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-07 12:33:36 +09:00
Pages small improvements (#428)
- Fixes #424 - Fixes #405 - Removed the "pages" feature-flag — enabled it without a flag
This commit is contained in:
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;
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user