Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mobile): explorer create/rename operation #8628

Open
wants to merge 1 commit into
base: canary
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const InviteModal = ({
confirmButtonOptions={{
loading: isMutating,
variant: 'primary',
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
'data-testid': 'confirm-enable-affine-cloud-button',
}}
onConfirm={handleConfirm}
>
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/component/src/ui/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface ButtonProps
tooltip?: TooltipProps['content'];
tooltipShortcut?: TooltipProps['shortcut'];
tooltipOptions?: Partial<Omit<TooltipProps, 'content' | 'shortcut'>>;
[key: `data-${string}`]: string;
}

const IconSlot = ({
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/component/src/ui/input/row-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type RowInputProps = {
type?: HTMLInputElement['type'];
style?: CSSProperties;
onEnter?: () => void;
[key: `data-${string}`]: string;
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size' | 'onBlur'>;

// RowInput component that is used in the selector layout for search input
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/component/src/ui/menu/desktop/sub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ export const DesktopMenuSub = ({
} = {},
}: MenuSubProps) => {
const { className, children, otherProps } = useMenuItem({
...triggerOptions,
children: propsChildren,
suffixIcon: <ArrowRightSmallIcon />,
...triggerOptions,
});

return (
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/component/src/ui/menu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ export {
};

export { Menu, MenuItem, MenuSeparator, MenuSub, MenuTrigger };
export * from './mobile/hook';
4 changes: 3 additions & 1 deletion packages/frontend/component/src/ui/menu/menu.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export interface MenuItemProps
export interface MenuSubProps {
children: ReactNode;
items: ReactNode;
triggerOptions?: Omit<MenuItemProps, 'onSelect' | 'children' | 'suffixIcon'>;
triggerOptions?: Omit<MenuItemProps, 'onSelect' | 'children'> & {
[key: `data-${string}`]: string;
};
portalOptions?: Omit<DropdownMenuPortalProps, 'children'>;
subOptions?: Omit<DropdownMenuSubProps, 'children'>;
subContentOptions?: Omit<DropdownMenuSubContentProps, 'children'>;
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend/component/src/ui/menu/mobile/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import {
import type { MenuSubProps } from '../menu.types';

export type SubMenuContent = {
/**
* Customize submenu's title
* @default "Back"
*/
title?: string;
items: ReactNode;
contentOptions?: MenuSubProps['subContentOptions'];
};
Expand Down
18 changes: 18 additions & 0 deletions packages/frontend/component/src/ui/menu/mobile/hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useCallback, useContext } from 'react';

import { MobileMenuContext } from './context';

export const useMobileMenuController = () => {
const context = useContext(MobileMenuContext);

/**
* **A workaround to close mobile menu manually**
* By default, it will close automatically when `MenuItem` clicked.
* For custom menu content, you can use this method to close the menu.
*/
const close = useCallback(() => {
context.setOpen?.(false);
}, [context]);

return { close };
};
4 changes: 2 additions & 2 deletions packages/frontend/component/src/ui/menu/mobile/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ export const MobileMenu = ({
className={styles.backButton}
prefix={<ArrowLeftSmallIcon />}
onClick={() => setSubMenus(prev => prev.slice(0, index))}
prefixStyle={{ width: 20, height: 20 }}
prefixStyle={{ width: 24, height: 24 }}
>
{t['com.affine.backButton']()}
{sub.title || t['com.affine.backButton']()}
</Button>

{sub.items}
Expand Down
16 changes: 11 additions & 5 deletions packages/frontend/component/src/ui/menu/mobile/sub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,28 @@ import { useMenuItem } from '../use-menu-item';
import { MobileMenuContext } from './context';

export const MobileMenuSub = ({
title,
children: propsChildren,
items,
triggerOptions,
subContentOptions: contentOptions = {},
}: MenuSubProps) => {
}: MenuSubProps & { title?: string }) => {
const {
className,
children,
otherProps: { onClick, ...otherTriggerOptions },
} = useMenuItem({
...triggerOptions,
children: propsChildren,
suffixIcon: <ArrowRightSmallPlusIcon />,
...triggerOptions,
});

return (
<MobileMenuSubRaw
onClick={onClick}
items={items}
subContentOptions={contentOptions}
title={title}
>
<div className={className} {...otherTriggerOptions}>
{children}
Expand All @@ -36,19 +38,23 @@ export const MobileMenuSub = ({
};

export const MobileMenuSubRaw = ({
title,
onClick,
children,
items,
subContentOptions: contentOptions = {},
}: MenuSubProps & { onClick?: (e: MouseEvent<HTMLDivElement>) => void }) => {
}: MenuSubProps & {
onClick?: (e: MouseEvent<HTMLDivElement>) => void;
title?: string;
}) => {
const { setSubMenus } = useContext(MobileMenuContext);

const onItemClick = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
onClick?.(e);
setSubMenus(prev => [...prev, { items, contentOptions }]);
setSubMenus(prev => [...prev, { items, contentOptions, title }]);
},
[contentOptions, items, onClick, setSubMenus]
[contentOptions, items, onClick, setSubMenus, title]
);

return <Slot onClick={onItemClick}>{children}</Slot>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const WorkspaceDeleteModal = ({
confirmButtonOptions={{
variant: 'error',
disabled: !allowDelete,
['data-testid' as string]: 'delete-workspace-confirm-button',
'data-testid': 'delete-workspace-confirm-button',
}}
{...props}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const Export = ({ exportHandler, className, pageMode }: ExportProps) => {
triggerOptions={{
className: transitionStyle,
prefixIcon: <ExportIcon />,
['data-testid' as string]: 'export-menu',
'data-testid': 'export-menu',
}}
subOptions={{
onOpenChange: handleExportMenuOpenChange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export const Snapshot = ({ className }: SnapshotProps) => {
triggerOptions={{
className: transitionStyle,
prefixIcon: <ToneIcon />,
['data-testid' as string]: 'snapshot-menu',
'data-testid': 'snapshot-menu',
}}
subOptions={{}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useI18n } from '@affine/i18n';

import {
RenameDialog,
type RenameDialogProps,
RenameSubMenu,
type RenameSubMenuProps,
} from '../../../rename';

export const CollectionRenameSubMenu = ({
title,
text,
...props
}: RenameSubMenuProps) => {
const t = useI18n();
return (
<RenameSubMenu
title={title || t['com.affine.m.explorer.collection.rename-menu-title']()}
text={text || t['com.affine.m.explorer.collection.rename']()}
{...props}
/>
);
};

const CollectionDesc = () => {
const t = useI18n();
return t['com.affine.collection.emptyCollectionDescription']();
};

export const CollectionRenameDialog = ({
title,
confirmText,
...props
}: RenameDialogProps) => {
return (
<RenameDialog
title={title}
confirmText={confirmText}
{...props}
descRenderer={CollectionDesc}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,6 @@ export const ExplorerCollectionNode = ({

const collection = useLiveData(collectionService.collection$(collectionId));

const handleRename = useCallback(
(name: string) => {
if (collection && collection.name !== name) {
collectionService.updateCollection(collectionId, () => ({
...collection,
name,
}));

track.$.navigationPanel.organize.renameOrganizeItem({
type: 'collection',
});
notify.success({ message: t['com.affine.toastMessage.rename']() });
}
},
[collection, collectionId, collectionService, t]
);

const handleOpenCollapsed = useCallback(() => {
setCollapsed(false);
}, []);
Expand Down Expand Up @@ -105,7 +88,7 @@ export const ExplorerCollectionNode = ({
return [...additionalOperations, ...collectionOperations];
}
return collectionOperations;
}, [collectionOperations, additionalOperations]);
}, [additionalOperations, collectionOperations]);

if (!collection) {
return null;
Expand All @@ -115,12 +98,10 @@ export const ExplorerCollectionNode = ({
<ExplorerTreeNode
icon={CollectionIcon}
name={collection.name || t['Untitled']()}
renameable
collapsed={collapsed}
setCollapsed={setCollapsed}
to={`/collection/${collection.id}`}
active={active}
onRename={handleRename}
operations={finalOperations}
data-testid={`explorer-collection-${collectionId}`}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
IconButton,
MenuItem,
MenuSeparator,
notify,
useConfirmModal,
} from '@affine/component';
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
Expand All @@ -28,6 +29,8 @@ import {
} from '@toeverything/infra';
import { useCallback, useMemo } from 'react';

import { CollectionRenameSubMenu } from './dialog';

export const useExplorerCollectionNodeOperations = (
collectionId: string,
onOpenCollapsed: () => void,
Expand Down Expand Up @@ -113,6 +116,24 @@ export const useExplorerCollectionNodeOperations = (
onOpenEdit();
}, [onOpenEdit]);

const handleRename = useCallback(
(name: string) => {
const collection = collectionService.collection$(collectionId).value;
if (collection && collection.name !== name) {
collectionService.updateCollection(collectionId, () => ({
...collection,
name,
}));

track.$.navigationPanel.organize.renameOrganizeItem({
type: 'collection',
});
notify.success({ message: t['com.affine.toastMessage.rename']() });
}
},
[collectionId, collectionService, t]
);

return useMemo(
() => ({
favorite,
Expand All @@ -122,13 +143,15 @@ export const useExplorerCollectionNodeOperations = (
handleOpenInSplitView,
handleShowEdit,
handleToggleFavoriteCollection,
handleRename,
}),
[
favorite,
handleAddDocToCollection,
handleDeleteCollection,
handleOpenInNewTab,
handleOpenInSplitView,
handleRename,
handleShowEdit,
handleToggleFavoriteCollection,
]
Expand All @@ -154,6 +177,7 @@ export const useExplorerCollectionNodeOperationsMenu = (
handleOpenInSplitView,
handleShowEdit,
handleToggleFavoriteCollection,
handleRename,
} = useExplorerCollectionNodeOperations(
collectionId,
onOpenCollapsed,
Expand All @@ -177,6 +201,14 @@ export const useExplorerCollectionNodeOperationsMenu = (
</IconButton>
),
},
{
index: 10,
view: <CollectionRenameSubMenu onConfirm={handleRename} />,
},
{
index: 11,
view: <MenuSeparator />,
},
{
index: 99,
view: (
Expand Down Expand Up @@ -256,6 +288,7 @@ export const useExplorerCollectionNodeOperationsMenu = (
handleDeleteCollection,
handleOpenInNewTab,
handleOpenInSplitView,
handleRename,
handleShowEdit,
handleToggleFavoriteCollection,
t,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useI18n } from '@affine/i18n';

import { RenameSubMenu, type RenameSubMenuProps } from '../../../rename';

export const DocRenameSubMenu = ({
title,
text,
...props
}: RenameSubMenuProps) => {
const t = useI18n();
return (
<RenameSubMenu
title={title || t['com.affine.m.explorer.doc.rename']()}
text={text || t['com.affine.m.explorer.doc.rename']()}
{...props}
/>
);
};
Loading
Loading