[WIKI-307]chore: update page icon placement #6916
This commit is contained in:
parent
1d5b93cebd
commit
915e374485
17 changed files with 225 additions and 83 deletions
|
|
@ -151,11 +151,11 @@
|
|||
/* end font size and style */
|
||||
|
||||
/* layout config */
|
||||
#page-header-container {
|
||||
container-name: page-header-container;
|
||||
#page-toolbar-container {
|
||||
container-name: page-toolbar-container;
|
||||
container-type: inline-size;
|
||||
|
||||
.page-header-content {
|
||||
.page-toolbar-content {
|
||||
--header-width: var(--normal-content-width);
|
||||
|
||||
&.wide-layout {
|
||||
|
|
@ -186,23 +186,23 @@
|
|||
}
|
||||
|
||||
/* keep a static padding of 96px for wide layouts for container width >912px and <1344px */
|
||||
@container page-header-container (min-width: 912px) and (max-width: 1344px) {
|
||||
.page-header-content.wide-layout {
|
||||
@container page-toolbar-container (min-width: 912px) and (max-width: 1344px) {
|
||||
.page-toolbar-content.wide-layout {
|
||||
padding-left: var(--wide-content-margin) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* keep a static padding of 96px for wide layouts for container width <912px */
|
||||
@container page-header-container (max-width: 912) {
|
||||
.page-header-content.wide-layout {
|
||||
@container page-toolbar-container (max-width: 912) {
|
||||
.page-toolbar-content.wide-layout {
|
||||
padding-left: var(--wide-content-margin) !important;
|
||||
}
|
||||
}
|
||||
/* end layout config */
|
||||
|
||||
/* keep a static padding of 20px for wide layouts for container width <760px */
|
||||
@container page-header-container (max-width: 760px) {
|
||||
.page-header-content {
|
||||
@container page-toolbar-container (max-width: 760px) {
|
||||
.page-toolbar-content {
|
||||
padding-left: var(--normal-content-margin) !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@
|
|||
/* keep a static padding of 96px for wide layouts for container width >912px and <1344px */
|
||||
@container page-content-container (min-width: 912px) and (max-width: 1344px) {
|
||||
.editor-container.wide-layout,
|
||||
.page-title-container {
|
||||
.page-header-container {
|
||||
padding-left: var(--wide-content-margin);
|
||||
padding-right: var(--wide-content-margin);
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@
|
|||
/* keep a static padding of 20px for wide layouts for container width <912px */
|
||||
@container page-content-container (max-width: 912px) {
|
||||
.editor-container.wide-layout,
|
||||
.page-title-container {
|
||||
.page-header-container {
|
||||
padding-left: var(--normal-content-margin);
|
||||
padding-right: var(--normal-content-margin);
|
||||
}
|
||||
|
|
@ -229,7 +229,7 @@
|
|||
/* keep a static padding of 20px for normal layouts for container width <760px */
|
||||
@container page-content-container (max-width: 760px) {
|
||||
.editor-container:not(.wide-layout),
|
||||
.page-title-container {
|
||||
.page-header-container {
|
||||
padding-left: var(--normal-content-margin);
|
||||
padding-right: var(--normal-content-margin);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
|||
import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
|
||||
// store
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
// local imports
|
||||
import { PageEditorHeaderRoot } from "./header";
|
||||
|
||||
export type TEditorBodyConfig = {
|
||||
fileHandler: TFileHandler;
|
||||
|
|
@ -161,10 +163,10 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
|
|||
|
||||
return (
|
||||
<Row
|
||||
className="relative size-full flex flex-col pt-[64px] overflow-y-auto overflow-x-hidden vertical-scrollbar scrollbar-md duration-200"
|
||||
className="relative size-full flex flex-col overflow-y-auto overflow-x-hidden vertical-scrollbar scrollbar-md duration-200"
|
||||
variant={ERowVariant.HUGGING}
|
||||
>
|
||||
<div id="page-content-container" className="relative w-full flex-shrink-0 space-y-4">
|
||||
<div id="page-content-container" className="relative w-full flex-shrink-0">
|
||||
{/* table of content */}
|
||||
<div className="page-summary-container absolute h-full right-0 top-[64px] z-[5]">
|
||||
<div className="sticky top-[72px]">
|
||||
|
|
@ -178,13 +180,17 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PageEditorTitle
|
||||
editorRef={editorRef}
|
||||
readOnly={!isContentEditable}
|
||||
title={pageTitle}
|
||||
updateTitle={updateTitle}
|
||||
widthClassName={blockWidthClassName}
|
||||
/>
|
||||
<div className="page-header-container group/page-header">
|
||||
<div className={blockWidthClassName}>
|
||||
<PageEditorHeaderRoot page={page} />
|
||||
<PageEditorTitle
|
||||
editorRef={editorRef}
|
||||
readOnly={!isContentEditable}
|
||||
title={pageTitle}
|
||||
updateTitle={updateTitle}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CollaborativeDocumentEditorWithRef
|
||||
editable={isContentEditable}
|
||||
id={pageId}
|
||||
|
|
|
|||
|
|
@ -1,7 +1 @@
|
|||
export * from "./color-dropdown";
|
||||
export * from "./extra-options";
|
||||
export * from "./info-popover";
|
||||
export * from "./options-dropdown";
|
||||
export * from "./root";
|
||||
export * from "./mobile-root";
|
||||
export * from "./toolbar";
|
||||
|
|
|
|||
53
web/core/components/pages/editor/header/logo-picker.tsx
Normal file
53
web/core/components/pages/editor/header/logo-picker.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { EmojiIconPicker, EmojiIconPickerTypes } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { Logo } from "@/components/common";
|
||||
// store
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
page: TPageInstance;
|
||||
};
|
||||
|
||||
export const PageEditorHeaderLogoPicker: React.FC<Props> = observer((props) => {
|
||||
const { className, page } = props;
|
||||
// states
|
||||
const [isLogoPickerOpen, setIsLogoPickerOpen] = useState(false);
|
||||
// derived values
|
||||
const { logo_props, isContentEditable, updatePageLogo } = page;
|
||||
const isLogoSelected = !!logo_props?.in_use;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(className, "max-h-0 pointer-events-none transition-all ease-linear duration-200", {
|
||||
"max-h-[56px] pointer-events-auto": isLogoSelected,
|
||||
})}
|
||||
>
|
||||
<EmojiIconPicker
|
||||
isOpen={isLogoPickerOpen}
|
||||
handleToggle={(val) => setIsLogoPickerOpen(val)}
|
||||
className="flex items-center justify-center"
|
||||
buttonClassName="flex items-center justify-center"
|
||||
label={
|
||||
<div
|
||||
className={cn("-ml-[8px] size-[56px] grid place-items-center rounded transition-colors", {
|
||||
"hover:bg-custom-background-80": isContentEditable,
|
||||
})}
|
||||
>
|
||||
{isLogoSelected && <Logo logo={logo_props} size={48} type="lucide" />}
|
||||
</div>
|
||||
}
|
||||
onChange={updatePageLogo}
|
||||
defaultIconColor={logo_props?.in_use && logo_props.in_use === "icon" ? logo_props?.icon?.color : undefined}
|
||||
defaultOpen={
|
||||
logo_props?.in_use && logo_props?.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON
|
||||
}
|
||||
disabled={!isContentEditable}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,56 +1,70 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
// components
|
||||
import { PageEditorMobileHeaderRoot, PageExtraOptions, PageToolbar } from "@/components/pages";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { usePageFilters } from "@/hooks/use-page-filters";
|
||||
// plane web hooks
|
||||
import { EPageStoreType } from "@/plane-web/hooks/store";
|
||||
import { SmilePlus } from "lucide-react";
|
||||
// plane imports
|
||||
import { EmojiIconPicker, EmojiIconPickerTypes } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
// store
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
// local imports
|
||||
import { PageEditorHeaderLogoPicker } from "./logo-picker";
|
||||
|
||||
type Props = {
|
||||
editorReady: boolean;
|
||||
editorRef: React.RefObject<EditorRefApi>;
|
||||
page: TPageInstance;
|
||||
storeType: EPageStoreType;
|
||||
};
|
||||
|
||||
export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
|
||||
const { editorReady, editorRef, page, storeType } = props;
|
||||
const { page } = props;
|
||||
// states
|
||||
const [isLogoPickerOpen, setIsLogoPickerOpen] = useState(false);
|
||||
// derived values
|
||||
const { isContentEditable } = page;
|
||||
// page filters
|
||||
const { isFullWidth, isStickyToolbarEnabled } = usePageFilters();
|
||||
// derived values
|
||||
const resolvedEditorRef = editorRef.current;
|
||||
|
||||
if (!resolvedEditorRef) return null;
|
||||
const { isContentEditable, logo_props, name, updatePageLogo } = page;
|
||||
const isLogoSelected = !!logo_props?.in_use;
|
||||
const isTitleEmpty = !name || name.trim() === "";
|
||||
|
||||
return (
|
||||
<div id="page-header-container">
|
||||
<div
|
||||
className={cn(
|
||||
"hidden md:flex items-center relative min-h-[52px] page-header-content border-b border-custom-border-200 px-page-x transition-all duration-200 ease-in-out",
|
||||
{
|
||||
"wide-layout": isFullWidth,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="max-w-full w-full flex items-center justify-between">
|
||||
<div>
|
||||
{isStickyToolbarEnabled && editorReady && isContentEditable && editorRef.current && (
|
||||
<PageToolbar editorRef={editorRef?.current} />
|
||||
)}
|
||||
<>
|
||||
<div className="h-[48px] flex items-end text-left">
|
||||
{!isLogoSelected && (
|
||||
<div
|
||||
className={cn("opacity-0 group-hover/page-header:opacity-100 transition-all duration-200", {
|
||||
"opacity-100": isTitleEmpty,
|
||||
})}
|
||||
>
|
||||
<EmojiIconPicker
|
||||
isOpen={isLogoPickerOpen}
|
||||
handleToggle={(val) => setIsLogoPickerOpen(val)}
|
||||
className="flex items-center justify-center"
|
||||
buttonClassName="flex items-center justify-center"
|
||||
label={
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex items-center gap-1 p-1 rounded font-medium text-sm hover:bg-custom-background-80 text-custom-text-300 outline-none transition-colors",
|
||||
{
|
||||
"bg-custom-background-80": isLogoPickerOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<SmilePlus className="flex-shrink-0 size-4" />
|
||||
Icon
|
||||
</button>
|
||||
}
|
||||
onChange={updatePageLogo}
|
||||
defaultIconColor={
|
||||
logo_props?.in_use && logo_props.in_use === "icon" ? logo_props?.icon?.color : undefined
|
||||
}
|
||||
defaultOpen={
|
||||
logo_props?.in_use && logo_props?.in_use === "emoji"
|
||||
? EmojiIconPickerTypes.EMOJI
|
||||
: EmojiIconPickerTypes.ICON
|
||||
}
|
||||
disabled={!isContentEditable}
|
||||
/>
|
||||
</div>
|
||||
<PageExtraOptions editorRef={resolvedEditorRef} page={page} storeType={storeType} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="md:hidden">
|
||||
<PageEditorMobileHeaderRoot editorRef={resolvedEditorRef} page={page} storeType={storeType} />
|
||||
</div>
|
||||
</div>
|
||||
<PageEditorHeaderLogoPicker className="flex-shrink-0 w-full mt-2 flex" page={page} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export * from "./header";
|
||||
export * from "./summary";
|
||||
export * from "./editor-body";
|
||||
export * from "./title";
|
||||
export * from "./page-root";
|
||||
export * from "./summary";
|
||||
export * from "./title";
|
||||
export * from "./toolbar";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { EditorRefApi } from "@plane/editor";
|
|||
import { TDocumentPayload, TPage, TPageVersion, TWebhookConnectionQueryParams } from "@plane/types";
|
||||
// components
|
||||
import {
|
||||
PageEditorHeaderRoot,
|
||||
PageEditorToolbarRoot,
|
||||
PageEditorBody,
|
||||
PageVersionsOverlay,
|
||||
PagesVersionEditor,
|
||||
|
|
@ -103,7 +103,7 @@ export const PageRoot = observer((props: TPageRootProps) => {
|
|||
pageId={page.id ?? ""}
|
||||
restoreEnabled={isContentEditable}
|
||||
/>
|
||||
<PageEditorHeaderRoot editorReady={editorReady} editorRef={editorRef} page={page} storeType={storeType} />
|
||||
<PageEditorToolbarRoot editorReady={editorReady} editorRef={editorRef} page={page} storeType={storeType} />
|
||||
<PageEditorBody
|
||||
config={config}
|
||||
editorReady={editorReady}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,10 @@ type Props = {
|
|||
readOnly: boolean;
|
||||
title: string | undefined;
|
||||
updateTitle: (title: string) => void;
|
||||
widthClassName: string;
|
||||
};
|
||||
|
||||
export const PageEditorTitle: React.FC<Props> = observer((props) => {
|
||||
const { editorRef, readOnly, title, updateTitle, widthClassName } = props;
|
||||
const { editorRef, readOnly, title, updateTitle } = props;
|
||||
// states
|
||||
const [isLengthVisible, setIsLengthVisible] = useState(false);
|
||||
// page filters
|
||||
|
|
@ -33,12 +32,11 @@ export const PageEditorTitle: React.FC<Props> = observer((props) => {
|
|||
});
|
||||
|
||||
return (
|
||||
<div className="relative w-full flex-shrink-0 py-3 page-title-container">
|
||||
<div className="relative w-full flex-shrink-0 py-3">
|
||||
{readOnly ? (
|
||||
<h6
|
||||
className={cn(
|
||||
titleFontClassName,
|
||||
widthClassName,
|
||||
{
|
||||
"text-custom-text-400": !title,
|
||||
},
|
||||
|
|
@ -48,7 +46,7 @@ export const PageEditorTitle: React.FC<Props> = observer((props) => {
|
|||
{getPageName(title)}
|
||||
</h6>
|
||||
) : (
|
||||
<div className={cn("relative", widthClassName)}>
|
||||
<div className="relative">
|
||||
<TextArea
|
||||
className={cn(titleFontClassName, "block w-full border-none outline-none p-0 resize-none rounded-none")}
|
||||
placeholder="Untitled"
|
||||
|
|
|
|||
7
web/core/components/pages/editor/toolbar/index.ts
Normal file
7
web/core/components/pages/editor/toolbar/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export * from "./color-dropdown";
|
||||
export * from "./extra-options";
|
||||
export * from "./info-popover";
|
||||
export * from "./options-dropdown";
|
||||
export * from "./root";
|
||||
export * from "./mobile-root";
|
||||
export * from "./toolbar";
|
||||
56
web/core/components/pages/editor/toolbar/root.tsx
Normal file
56
web/core/components/pages/editor/toolbar/root.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
// components
|
||||
import { PageEditorMobileHeaderRoot, PageExtraOptions, PageToolbar } from "@/components/pages";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { usePageFilters } from "@/hooks/use-page-filters";
|
||||
// plane web hooks
|
||||
import { EPageStoreType } from "@/plane-web/hooks/store";
|
||||
// store
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
|
||||
type Props = {
|
||||
editorReady: boolean;
|
||||
editorRef: React.RefObject<EditorRefApi>;
|
||||
page: TPageInstance;
|
||||
storeType: EPageStoreType;
|
||||
};
|
||||
|
||||
export const PageEditorToolbarRoot: React.FC<Props> = observer((props) => {
|
||||
const { editorReady, editorRef, page, storeType } = props;
|
||||
// derived values
|
||||
const { isContentEditable } = page;
|
||||
// page filters
|
||||
const { isFullWidth, isStickyToolbarEnabled } = usePageFilters();
|
||||
// derived values
|
||||
const resolvedEditorRef = editorRef.current;
|
||||
|
||||
if (!resolvedEditorRef) return null;
|
||||
|
||||
return (
|
||||
<div id="page-toolbar-container">
|
||||
<div
|
||||
className={cn(
|
||||
"hidden md:flex items-center relative min-h-[52px] page-toolbar-content border-b border-custom-border-200 px-page-x transition-all duration-200 ease-in-out",
|
||||
{
|
||||
"wide-layout": isFullWidth,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="max-w-full w-full flex items-center justify-between">
|
||||
<div>
|
||||
{isStickyToolbarEnabled && editorReady && isContentEditable && editorRef.current && (
|
||||
<PageToolbar editorRef={editorRef?.current} />
|
||||
)}
|
||||
</div>
|
||||
<PageExtraOptions editorRef={resolvedEditorRef} page={page} storeType={storeType} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:hidden">
|
||||
<PageEditorMobileHeaderRoot editorRef={resolvedEditorRef} page={page} storeType={storeType} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import set from "lodash/set";
|
||||
import { action, computed, makeObservable, observable, reaction, runInAction } from "mobx";
|
||||
// constants
|
||||
// plane imports
|
||||
import { EPageAccess } from "@plane/constants";
|
||||
// types
|
||||
import { TDocumentPayload, TLogoProps, TNameDescriptionLoader, TPage } from "@plane/types";
|
||||
import { TChangeHandlerProps } from "@plane/ui";
|
||||
import { convertHexEmojiToDecimal } from "@plane/utils";
|
||||
// plane web store
|
||||
import { RootStore } from "@/plane-web/store/root.store";
|
||||
|
||||
|
|
@ -27,7 +28,7 @@ export type TBasePage = TPage & {
|
|||
unlock: (shouldSync?: boolean) => Promise<void>;
|
||||
archive: (shouldSync?: boolean) => Promise<void>;
|
||||
restore: (shouldSync?: boolean) => Promise<void>;
|
||||
updatePageLogo: (logo_props: TLogoProps) => Promise<void>;
|
||||
updatePageLogo: (value: TChangeHandlerProps) => Promise<void>;
|
||||
addToFavorites: () => Promise<void>;
|
||||
removePageFromFavorites: () => Promise<void>;
|
||||
duplicate: () => Promise<TPage | undefined>;
|
||||
|
|
@ -424,12 +425,25 @@ export class BasePage implements TBasePage {
|
|||
}
|
||||
};
|
||||
|
||||
updatePageLogo = async (logo_props: TLogoProps) => {
|
||||
updatePageLogo = async (value: TChangeHandlerProps) => {
|
||||
let logoValue = {};
|
||||
if (value?.type === "emoji")
|
||||
logoValue = {
|
||||
value: convertHexEmojiToDecimal(value.value.unified),
|
||||
url: value.value.imageUrl,
|
||||
};
|
||||
else if (value?.type === "icon") logoValue = value.value;
|
||||
|
||||
const logoProps: TLogoProps = {
|
||||
in_use: value?.type,
|
||||
[value?.type]: logoValue,
|
||||
};
|
||||
|
||||
await this.services.update({
|
||||
logo_props,
|
||||
logo_props: logoProps,
|
||||
});
|
||||
runInAction(() => {
|
||||
this.logo_props = logo_props;
|
||||
this.logo_props = logoProps;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue