setShow(prev => !prev)}
+ />
+ );
+};
+
+const ColorPickerSelect = () => {
+ const {
+ enableAnimation,
+ colors,
+ color: current,
+ setColor,
+ show,
+ } = useContext(TagColorContext);
+
+ if (!show && !enableAnimation) return null;
+
+ return (
+
+ {colors.map(color => (
+
setColor(color)}
+ key={color}
+ className={styles.colorDot}
+ style={{ color }}
+ />
+ ))}
+
+ );
+};
+
+interface TagRenameContentProps extends Omit
{
+ initialColor?: string;
+ onConfirm?: (name: string, color: string) => void;
+ enableAnimation?: boolean;
+}
+const TagRenameContent = ({
+ initialColor,
+ onConfirm,
+ enableAnimation,
+ ...props
+}: TagRenameContentProps) => {
+ const tagService = useService(TagService);
+ const colors = useMemo(() => {
+ return tagService.tagColors.map(([_, value]) => value);
+ }, [tagService.tagColors]);
+
+ const [color, setColor] = useState(
+ initialColor || tagService.randomTagColor()
+ );
+ const [show, setShow] = useState(false);
+
+ const handleConfirm = useCallback(
+ (name: string) => {
+ onConfirm?.(name, color);
+ },
+ [color, onConfirm]
+ );
+
+ return (
+
+
+
+ );
+};
+
+interface TagRenameDialogProps extends TagRenameContentProps {
+ title?: string;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+}
+export const TagRenameDialog = ({
+ title: propsTitle,
+ confirmText: propsConfirmText,
+ open,
+ onOpenChange,
+ ...props
+}: TagRenameDialogProps) => {
+ const t = useI18n();
+ const title = propsTitle || t['com.affine.m.explorer.tag.new-dialog-title']();
+ const confirmText =
+ propsConfirmText || t['com.affine.m.explorer.tag.rename-confirm']();
+
+ return (
+
+
+
+ );
+};
+
+interface TagRenameSubMenuProps {
+ tagId?: string;
+ title?: string;
+ icon?: ReactNode;
+ text?: string;
+ onConfirm?: (name: string, color: string) => void;
+}
+export const TagRenameSubMenu = ({
+ tagId,
+ title,
+ icon,
+ text,
+ onConfirm,
+}: TagRenameSubMenuProps) => {
+ const t = useI18n();
+ const { close } = useMobileMenuController();
+ const tagService = useService(TagService);
+ const tagRecord = useLiveData(tagService.tagList.tagByTagId$(tagId));
+ const tagName = useLiveData(tagRecord?.value$);
+ const tagColor = useLiveData(tagRecord?.color$);
+
+ const handleCloseAndConfirm = useCallback(
+ (name: string, color: string) => {
+ close();
+ onConfirm?.(name, color);
+ },
+ [close, onConfirm]
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx
index 2d939bf44523..8173b2644e2c 100644
--- a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx
+++ b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/index.tsx
@@ -2,7 +2,6 @@ import type { NodeOperation } from '@affine/core/modules/explorer';
import type { Tag } from '@affine/core/modules/tag';
import { TagService } from '@affine/core/modules/tag';
import { useI18n } from '@affine/i18n';
-import track from '@affine/track';
import {
GlobalContextService,
useLiveData,
@@ -23,10 +22,8 @@ import * as styles from './styles.css';
export const ExplorerTagNode = ({
tagId,
operations: additionalOperations,
- defaultRenaming,
}: {
tagId: string;
- defaultRenaming?: boolean;
operations?: NodeOperation[];
}) => {
const t = useI18n();
@@ -58,18 +55,6 @@ export const ExplorerTagNode = ({
[tagColor]
);
- const handleRename = useCallback(
- (newName: string) => {
- if (tagRecord && tagRecord.value$.value !== newName) {
- tagRecord.rename(newName);
- track.$.navigationPanel.organize.renameOrganizeItem({
- type: 'tag',
- });
- }
- },
- [tagRecord]
- );
-
const option = useMemo(
() => ({
openNodeCollapsed: () => setCollapsed(false),
@@ -94,13 +79,10 @@ export const ExplorerTagNode = ({
diff --git a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx
index 967113ad0670..b7c075aaad02 100644
--- a/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx
+++ b/packages/frontend/core/src/mobile/components/explorer/nodes/tag/operations.tsx
@@ -7,12 +7,7 @@ import { TagService } from '@affine/core/modules/tag';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
-import {
- DeleteIcon,
- OpenInNewIcon,
- PlusIcon,
- SplitViewIcon,
-} from '@blocksuite/icons/rc';
+import { DeleteIcon, PlusIcon, SplitViewIcon } from '@blocksuite/icons/rc';
import {
DocsService,
FeatureFlagService,
@@ -23,6 +18,8 @@ import {
} from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
+import { TagRenameSubMenu } from './dialog';
+
export const useExplorerTagNodeOperations = (
tagId: string,
{
@@ -86,6 +83,37 @@ export const useExplorerTagNodeOperations = (
track.$.navigationPanel.organize.openInNewTab({ type: 'tag' });
}, [tagId, workbenchService]);
+ const handleRename = useCallback(
+ (newName: string) => {
+ if (tagRecord && tagRecord.value$.value !== newName) {
+ tagRecord.rename(newName);
+ track.$.navigationPanel.organize.renameOrganizeItem({
+ type: 'tag',
+ });
+ }
+ },
+ [tagRecord]
+ );
+ const handleChangeColor = useCallback(
+ (color: string) => {
+ if (tagRecord && tagRecord.color$.value !== color) {
+ tagRecord.changeColor(color);
+ }
+ },
+ [tagRecord]
+ );
+ const handleChangeNameOrColor = useCallback(
+ (name?: string, color?: string) => {
+ if (name !== undefined) {
+ handleRename(name);
+ }
+ if (color !== undefined) {
+ handleChangeColor(color);
+ }
+ },
+ [handleChangeColor, handleRename]
+ );
+
return useMemo(
() => ({
favorite,
@@ -94,13 +122,19 @@ export const useExplorerTagNodeOperations = (
handleOpenInSplitView,
handleToggleFavoriteTag,
handleOpenInNewTab,
+ handleRename,
+ handleChangeColor,
+ handleChangeNameOrColor,
}),
[
favorite,
+ handleChangeColor,
+ handleChangeNameOrColor,
handleMoveToTrash,
handleNewDoc,
handleOpenInNewTab,
handleOpenInSplitView,
+ handleRename,
handleToggleFavoriteTag,
]
);
@@ -122,7 +156,7 @@ export const useExplorerTagNodeOperationsMenu = (
handleMoveToTrash,
handleOpenInSplitView,
handleToggleFavoriteTag,
- handleOpenInNewTab,
+ handleChangeNameOrColor,
} = useExplorerTagNodeOperations(tagId, option);
return useMemo(
@@ -131,21 +165,15 @@ export const useExplorerTagNodeOperationsMenu = (
index: 0,
inline: true,
view: (
-
+
),
},
{
- index: 50,
+ index: 10,
view: (
- } onClick={handleOpenInNewTab}>
- {t['com.affine.workbench.tab.page-menu-open']()}
-
+
),
},
...(BUILD_CONFIG.isElectron && enableMultiView
@@ -196,12 +224,13 @@ export const useExplorerTagNodeOperationsMenu = (
[
enableMultiView,
favorite,
+ handleChangeNameOrColor,
handleMoveToTrash,
handleNewDoc,
- handleOpenInNewTab,
handleOpenInSplitView,
handleToggleFavoriteTag,
t,
+ tagId,
]
);
};
diff --git a/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx b/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx
index d2bf33fa7461..186ed64e5256 100644
--- a/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx
+++ b/packages/frontend/core/src/mobile/components/explorer/sections/collections/index.tsx
@@ -1,4 +1,3 @@
-import { useEditCollectionName } from '@affine/core/components/page-list';
import { createEmptyCollection } from '@affine/core/components/page-list/use-collection-manager';
import { CollectionService } from '@affine/core/modules/collection';
import { ExplorerService } from '@affine/core/modules/explorer';
@@ -8,11 +7,12 @@ import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useLiveData, useServices } from '@toeverything/infra';
import { nanoid } from 'nanoid';
-import { useCallback } from 'react';
+import { useCallback, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerCollectionNode } from '../../nodes/collection';
+import { CollectionRenameDialog } from '../../nodes/collection/dialog';
export const ExplorerCollections = () => {
const t = useI18n();
@@ -23,31 +23,22 @@ export const ExplorerCollections = () => {
});
const explorerSection = explorerService.sections.collections;
const collections = useLiveData(collectionService.collections$);
- const { open: openCreateCollectionModel } = useEditCollectionName({
- title: t['com.affine.editCollection.createCollection'](),
- showTips: true,
- });
+ const [showCreateCollectionModal, setShowCreateCollectionModal] =
+ useState(false);
- const handleCreateCollection = useCallback(() => {
- openCreateCollectionModel('')
- .then(name => {
- const id = nanoid();
- collectionService.addCollection(createEmptyCollection(id, { name }));
- track.$.navigationPanel.organize.createOrganizeItem({
- type: 'collection',
- });
- workbenchService.workbench.openCollection(id);
- explorerSection.setCollapsed(false);
- })
- .catch(err => {
- console.error(err);
+ const handleCreateCollection = useCallback(
+ (name: string) => {
+ setShowCreateCollectionModal(false);
+ const id = nanoid();
+ collectionService.addCollection(createEmptyCollection(id, { name }));
+ track.$.navigationPanel.organize.createOrganizeItem({
+ type: 'collection',
});
- }, [
- collectionService,
- explorerSection,
- openCreateCollectionModel,
- workbenchService.workbench,
- ]);
+ workbenchService.workbench.openCollection(id);
+ explorerSection.setCollapsed(false);
+ },
+ [collectionService, explorerSection, workbenchService.workbench]
+ );
return (
{
setShowCreateCollectionModal(true)}
+ />
+
diff --git a/packages/frontend/core/src/mobile/components/explorer/sections/organize/index.tsx b/packages/frontend/core/src/mobile/components/explorer/sections/organize/index.tsx
index 601e2a6b228b..60fd4592f794 100644
--- a/packages/frontend/core/src/mobile/components/explorer/sections/organize/index.tsx
+++ b/packages/frontend/core/src/mobile/components/explorer/sections/organize/index.tsx
@@ -7,11 +7,12 @@ import { OrganizeService } from '@affine/core/modules/organize';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { useLiveData, useServices } from '@toeverything/infra';
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerFolderNode } from '../../nodes/folder';
+import { FolderCreateTip, FolderRenameDialog } from '../../nodes/folder/dialog';
export const ExplorerOrganize = () => {
const { organizeService, explorerService } = useServices({
@@ -19,8 +20,7 @@ export const ExplorerOrganize = () => {
ExplorerService,
});
const explorerSection = explorerService.sections.organize;
- const collapsed = useLiveData(explorerSection.collapsed$);
- const [newFolderId, setNewFolderId] = useState(null);
+ const [openNewFolderDialog, setOpenNewFolderDialog] = useState(false);
const t = useI18n();
@@ -30,20 +30,18 @@ export const ExplorerOrganize = () => {
const folders = useLiveData(rootFolder.sortedChildren$);
const isLoading = useLiveData(folderTree.isLoading$);
- const handleCreateFolder = useCallback(() => {
- const newFolderId = rootFolder.createFolder(
- 'New Folder',
- rootFolder.indexAt('before')
- );
- track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
- setNewFolderId(newFolderId);
- explorerSection.setCollapsed(false);
- return newFolderId;
- }, [explorerSection, rootFolder]);
-
- useEffect(() => {
- if (collapsed) setNewFolderId(null); // reset new folder id to clear the renaming state
- }, [collapsed]);
+ const handleCreateFolder = useCallback(
+ (name: string) => {
+ const newFolderId = rootFolder.createFolder(
+ name,
+ rootFolder.indexAt('before')
+ );
+ track.$.navigationPanel.organize.createOrganizeItem({ type: 'folder' });
+ explorerSection.setCollapsed(false);
+ return newFolderId;
+ },
+ [explorerSection, rootFolder]
+ );
return (
{
{/* TODO(@CatsJuice): Organize loading UI */}
: null}>
{folders.map(child => (
-
+
))}
setOpenNewFolderDialog(true)}
/>
+
);
};
diff --git a/packages/frontend/core/src/mobile/components/explorer/sections/tags/index.tsx b/packages/frontend/core/src/mobile/components/explorer/sections/tags/index.tsx
index be9a77fa9671..6c0bf1db442f 100644
--- a/packages/frontend/core/src/mobile/components/explorer/sections/tags/index.tsx
+++ b/packages/frontend/core/src/mobile/components/explorer/sections/tags/index.tsx
@@ -1,15 +1,23 @@
import { ExplorerService } from '@affine/core/modules/explorer';
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
-import type { Tag } from '@affine/core/modules/tag';
import { TagService } from '@affine/core/modules/tag';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useLiveData, useServices } from '@toeverything/infra';
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useState } from 'react';
import { AddItemPlaceholder } from '../../layouts/add-item-placeholder';
import { CollapsibleSection } from '../../layouts/collapsible-section';
import { ExplorerTagNode } from '../../nodes/tag';
+import { TagRenameDialog } from '../../nodes/tag/dialog';
+
+export const TagDesc = ({ input }: { input: string }) => {
+ const t = useI18n();
+
+ return input
+ ? t['com.affine.m.explorer.tag.new-tip-not-empty']({ value: input })
+ : t['com.affine.m.explorer.tag.new-tip-empty']();
+};
export const ExplorerTags = () => {
const { tagService, explorerService } = useServices({
@@ -17,25 +25,20 @@ export const ExplorerTags = () => {
ExplorerService,
});
const explorerSection = explorerService.sections.tags;
- const collapsed = useLiveData(explorerSection.collapsed$);
- const [createdTag, setCreatedTag] = useState(null);
const tags = useLiveData(tagService.tagList.tags$);
+ const [showNewTagDialog, setShowNewTagDialog] = useState(false);
const t = useI18n();
- const handleCreateNewFavoriteDoc = useCallback(() => {
- const newTags = tagService.tagList.createTag(
- t['com.affine.rootAppSidebar.tags.new-tag'](),
- tagService.randomTagColor()
- );
- setCreatedTag(newTags);
- track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
- explorerSection.setCollapsed(false);
- }, [explorerSection, t, tagService]);
-
- useEffect(() => {
- if (collapsed) setCreatedTag(null); // reset created tag to clear the renaming state
- }, [collapsed]);
+ const handleNewTag = useCallback(
+ (name: string, color: string) => {
+ setShowNewTagDialog(false);
+ tagService.tagList.createTag(name, color);
+ track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
+ explorerSection.setCollapsed(false);
+ },
+ [explorerSection, tagService]
+ );
return (
{
>
{tags.map(tag => (
-
+
))}
setShowNewTagDialog(true)}
label={t[
'com.affine.rootAppSidebar.explorer.tag-section-add-tooltip'
]()}
/>
+
);
diff --git a/packages/frontend/core/src/mobile/components/explorer/tree/node.tsx b/packages/frontend/core/src/mobile/components/explorer/tree/node.tsx
index 495eb6ad2461..b8a652c1fbb8 100644
--- a/packages/frontend/core/src/mobile/components/explorer/tree/node.tsx
+++ b/packages/frontend/core/src/mobile/components/explorer/tree/node.tsx
@@ -1,17 +1,10 @@
-import { MenuItem, MobileMenu } from '@affine/component';
-import { RenameModal } from '@affine/component/rename-modal';
-import { AppSidebarService } from '@affine/core/modules/app-sidebar';
-import type {
- BaseExplorerTreeNodeProps,
- NodeOperation,
-} from '@affine/core/modules/explorer';
+import { MobileMenu } from '@affine/component';
+import type { BaseExplorerTreeNodeProps } from '@affine/core/modules/explorer';
import { ExplorerTreeContext } from '@affine/core/modules/explorer';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import { extractEmojiIcon } from '@affine/core/utils';
-import { useI18n } from '@affine/i18n';
-import { ArrowDownSmallIcon, EditIcon } from '@blocksuite/icons/rc';
+import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
-import { useLiveData, useService } from '@toeverything/infra';
import { assignInlineVars } from '@vanilla-extract/dynamic';
import {
Fragment,
@@ -33,9 +26,6 @@ export const ExplorerTreeNode = ({
onClick,
to,
active,
- defaultRenaming,
- renameable,
- onRename,
disabled,
collapsed,
extractEmojiAsIcon,
@@ -47,18 +37,13 @@ export const ExplorerTreeNode = ({
linkComponent: LinkComponent = WorkbenchLink,
...otherProps
}: ExplorerTreeNodeProps) => {
- const t = useI18n();
const context = useContext(ExplorerTreeContext);
const level = context?.level ?? 0;
// If no onClick or to is provided, clicking on the node will toggle the collapse state
const clickForCollapse = !onClick && !to && !disabled;
const [childCount, setChildCount] = useState(0);
- const [renaming, setRenaming] = useState(defaultRenaming);
const rootRef = useRef(null);
- const appSidebarService = useService(AppSidebarService).sidebar;
- const sidebarWidth = useLiveData(appSidebarService.width$);
-
const { emoji, name } = useMemo(() => {
if (!extractEmojiAsIcon || !rawName) {
return {
@@ -73,39 +58,13 @@ export const ExplorerTreeNode = ({
};
}, [extractEmojiAsIcon, rawName]);
- const presetOperations = useMemo(
- () =>
- (
- [
- renameable
- ? {
- index: 0,
- view: (
- }
- onClick={() => setRenaming(true)}
- >
- {t['com.affine.menu.rename']()}
-
- ),
- }
- : null,
- ] as (NodeOperation | null)[]
- ).filter((t): t is NodeOperation => t !== null),
- [renameable, t]
- );
-
const { menuOperations } = useMemo(() => {
- const sorted = [...presetOperations, ...operations].sort(
- (a, b) => a.index - b.index
- );
+ const sorted = [...operations].sort((a, b) => a.index - b.index);
return {
menuOperations: sorted.filter(({ inline }) => !inline),
inlineOperations: sorted.filter(({ inline }) => !!inline),
};
- }, [presetOperations, operations]);
+ }, [operations]);
const contextValue = useMemo(() => {
return {
@@ -127,11 +86,6 @@ export const ExplorerTreeNode = ({
[collapsed, setCollapsed]
);
- const handleRename = useCallback(
- (newName: string) => onRename?.(newName),
- [onRename]
- );
-
const handleClick = useCallback(
(e: React.MouseEvent) => {
if (e.defaultPrevented) {
@@ -193,18 +147,6 @@ export const ExplorerTreeNode = ({
data-collapsed={collapsed !== false}
/>
-
- {renameable && (
-
-
-
- )}
);
diff --git a/packages/frontend/core/src/mobile/components/index.ts b/packages/frontend/core/src/mobile/components/index.ts
index c6d272437286..18dd7f3f6561 100644
--- a/packages/frontend/core/src/mobile/components/index.ts
+++ b/packages/frontend/core/src/mobile/components/index.ts
@@ -1,6 +1,7 @@
export * from './app-tabs';
export * from './doc-card';
export * from './page-header';
+export * from './rename';
export * from './search-input';
export * from './search-result';
export * from './user-plan-tag';
diff --git a/packages/frontend/core/src/mobile/components/rename/content.css.ts b/packages/frontend/core/src/mobile/components/rename/content.css.ts
new file mode 100644
index 000000000000..1e7de0ff0e70
--- /dev/null
+++ b/packages/frontend/core/src/mobile/components/rename/content.css.ts
@@ -0,0 +1,35 @@
+import { cssVarV2 } from '@toeverything/theme/v2';
+import { style } from '@vanilla-extract/css';
+
+export const inputWrapper = style({
+ padding: '4px 12px',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 10,
+});
+export const input = style({
+ width: '100%',
+ height: 42,
+ border: '1px solid ' + cssVarV2('input/border/active'),
+ borderRadius: 8,
+ padding: '0 4px',
+});
+export const desc = style({
+ padding: '11px 16px',
+ fontSize: 17,
+ fontWeight: 400,
+ lineHeight: '22px',
+ letterSpacing: -0.43,
+ color: cssVarV2('text/secondary'),
+});
+export const doneWrapper = style({
+ width: '100%',
+ padding: '8px 16px',
+});
+export const done = style({
+ width: '100%',
+ height: 44,
+ borderRadius: 8,
+ fontSize: 17,
+ fontWeight: 400,
+});
diff --git a/packages/frontend/core/src/mobile/components/rename/content.tsx b/packages/frontend/core/src/mobile/components/rename/content.tsx
new file mode 100644
index 000000000000..7f075e1f6d66
--- /dev/null
+++ b/packages/frontend/core/src/mobile/components/rename/content.tsx
@@ -0,0 +1,56 @@
+import { Button, RowInput } from '@affine/component';
+import { useI18n } from '@affine/i18n';
+import { useCallback, useState } from 'react';
+
+import * as styles from './content.css';
+import type { RenameContentProps } from './type';
+
+export const RenameContent = ({
+ initialName = '',
+ inputPrefixRenderer: InputPrefixRenderer,
+ inputBelowRenderer: InputBelowRenderer,
+ descRenderer: DescRenderer,
+ confirmText = 'Done',
+ onConfirm,
+}: RenameContentProps) => {
+ const t = useI18n();
+ const [value, setValue] = useState(initialName);
+
+ const handleDone = useCallback(() => {
+ onConfirm?.(value);
+ }, [onConfirm, value]);
+
+ return (
+
+
+ {InputPrefixRenderer ? : null}
+
+
+ {}
+ {InputBelowRenderer ?
: null}
+
+ {DescRenderer ? (
+
+ ) : (
+ t['com.affine.m.rename-to']({ name: value })
+ )}
+
+
+
+
+
+ );
+};
diff --git a/packages/frontend/core/src/mobile/components/rename/dialog.css.ts b/packages/frontend/core/src/mobile/components/rename/dialog.css.ts
new file mode 100644
index 000000000000..fe0994c039a9
--- /dev/null
+++ b/packages/frontend/core/src/mobile/components/rename/dialog.css.ts
@@ -0,0 +1,17 @@
+import { cssVarV2 } from '@toeverything/theme/v2';
+import { style } from '@vanilla-extract/css';
+
+export const header = style({
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ gap: 8,
+ padding: '10px 16px',
+});
+export const title = style({
+ fontSize: 17,
+ fontWeight: 600,
+ lineHeight: '22px',
+ letterSpacing: -0.43,
+ color: cssVarV2('text/primary'),
+});
diff --git a/packages/frontend/core/src/mobile/components/rename/dialog.tsx b/packages/frontend/core/src/mobile/components/rename/dialog.tsx
new file mode 100644
index 000000000000..8932aae91209
--- /dev/null
+++ b/packages/frontend/core/src/mobile/components/rename/dialog.tsx
@@ -0,0 +1,49 @@
+import { IconButton, Modal } from '@affine/component';
+import { CloseIcon } from '@blocksuite/icons/rc';
+import { useCallback } from 'react';
+
+import { RenameContent } from './content';
+import * as styles from './dialog.css';
+import type { RenameDialogProps } from './type';
+
+export const RenameDialog = ({
+ open,
+ title,
+ onOpenChange,
+ onConfirm,
+ children,
+ ...props
+}: RenameDialogProps & {
+ open?: boolean;
+ onOpenChange?: (v: boolean) => void;
+}) => {
+ const handleRename = useCallback(
+ (value: string) => {
+ onConfirm?.(value);
+ onOpenChange?.(false);
+ },
+ [onOpenChange, onConfirm]
+ );
+
+ const close = useCallback(() => {
+ onOpenChange?.(false);
+ }, [onOpenChange]);
+
+ return (
+