[WIKI-307]chore: update page icon placement #6916

This commit is contained in:
Aaryan Khandelwal 2025-04-11 18:07:03 +05:30 committed by GitHub
parent 1d5b93cebd
commit 915e374485
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 225 additions and 83 deletions

View file

@ -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);
}

View file

@ -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}

View file

@ -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";

View 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>
);
});

View file

@ -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} />
</>
);
});

View file

@ -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";

View file

@ -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}

View file

@ -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"

View 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";

View 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>
);
});

View file

@ -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;
});
};