Skip to content

Commit

Permalink
rename, duplicate & polish
Browse files Browse the repository at this point in the history
  • Loading branch information
steeeee0223 committed Dec 16, 2024
1 parent 1df1216 commit 02a6e13
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 62 deletions.
1 change: 1 addition & 0 deletions packages/notion/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./base-modal";
export * as Icon from "./icons";
export * from "./rename-popover";
export * from "./utils";
66 changes: 66 additions & 0 deletions packages/notion/src/common/rename-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client";

import React, { useRef, useState } from "react";

import { Input, Popover, PopoverContent, PopoverTrigger } from "@swy/ui/shadcn";
import { IconBlock, IconInfo, IconMenu } from "@swy/ui/shared";

interface RenamePopoverProps extends React.PropsWithChildren {
title: string;
icon: IconInfo;
onChange: (value: { title: string; icon: IconInfo }) => void;
}

export const RenamePopover: React.FC<RenamePopoverProps> = ({
children,
title,
icon,
onChange,
}) => {
const inputRef = useRef<HTMLInputElement>(null);

const [open, setOpen] = useState(false);
const onOpenChange = (isOpen: boolean) => {
setOpen(isOpen);
if (isOpen)
setTimeout(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, 0);
};
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.stopPropagation();
if (e.key === "Enter") {
const value = inputRef.current?.value;
if (value && value !== title) onChange({ title: value, icon });
setOpen(false);
}
};

return (
<Popover open={open} onOpenChange={onOpenChange} modal>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent
className="flex w-[380px] items-center gap-1.5 px-2 py-1"
onClick={(e) => e.stopPropagation()}
>
<IconMenu
className="size-7 shrink-0 border border-border-button hover:bg-primary/5"
onSelect={(icon) => onChange({ title, icon })}
onRemove={() =>
onChange({ title, icon: { type: "lucide", name: "file" } })
}
>
<IconBlock icon={icon} className="p-0 text-lg/[22px]" />
</IconMenu>
<Input
ref={inputRef}
className="whitespace-pre-wrap break-words"
value={title}
onChange={(e) => onChange({ title: e.target.value, icon })}
onKeyDown={onKeyDown}
/>
</PopoverContent>
</Popover>
);
};
22 changes: 15 additions & 7 deletions packages/notion/src/sidebar-2/_components/action-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,37 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@swy/ui/shadcn";
import { Hint } from "@swy/ui/shared";
import { Hint, IconInfo } from "@swy/ui/shared";

import { Icon, toDateString } from "../../common";
import { Icon, RenamePopover, toDateString } from "../../common";
import type { UpdatePageParams } from "../../types";
import { MenuType } from "./types";

interface ActionGroupProps {
type: MenuType;
title: string;
icon: IconInfo;
pageLink: string;
isFavorite: boolean;
lastEditedBy: string;
lastEditedAt: number;
onCreate?: () => void;
onDelete?: () => void;
onDuplicate?: () => void;
onUpdate: (data: UpdatePageParams) => void;
}

export const ActionGroup: React.FC<ActionGroupProps> = ({
type,
title,
icon,
pageLink,
isFavorite,
lastEditedBy,
lastEditedAt,
onCreate,
onDelete,
onDuplicate,
onUpdate,
}) => {
const [, copy] = useCopyToClipboard();
Expand Down Expand Up @@ -89,15 +95,17 @@ export const ActionGroup: React.FC<ActionGroupProps> = ({
Copy link
</DropdownMenuItem>
{type === MenuType.Normal && (
<DropdownMenuItem>
<DropdownMenuItem onSelect={onDuplicate}>
<Copy className="mr-2 size-4" />
Duplicate
</DropdownMenuItem>
)}
<DropdownMenuItem>
<SquarePen className="mr-2 size-4" />
Rename
</DropdownMenuItem>
<RenamePopover title={title} icon={icon} onChange={onUpdate}>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
<SquarePen className="mr-2 size-4" />
Rename
</DropdownMenuItem>
</RenamePopover>
{type === MenuType.Normal && (
<DropdownMenuItem variant="warning" onSelect={onDelete}>
<Trash className="mr-2 size-4" />
Expand Down
35 changes: 17 additions & 18 deletions packages/notion/src/sidebar-2/_components/doc-list.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import React from "react";

import { useOrigin } from "@swy/ui/hooks";
import {
buildTree,
TreeGroup,
TreeItem,
TreeList,
type IconInfo,
} from "@swy/ui/shared";
import { buildTree, TreeGroup, TreeItem, TreeList } from "@swy/ui/shared";

import { generateDefaultIcon } from "../../common";
import { usePlatformStore } from "../../slices";
import type { UpdatePageParams } from "../../types";
import { ActionGroup } from "./action-group";
Expand All @@ -17,25 +12,26 @@ import { MenuType } from "./types";
interface DocListProps {
group: string;
title: string;
defaultIcon?: IconInfo;
isLoading?: boolean;
redirect: (url: string) => void;
onCreate: (parentId?: string) => void;
onArchive: (id: string) => void;
onUpdate: (id: string, data: UpdatePageParams) => void;
redirect?: (url: string) => void;
onCreate?: (group: string, parentId?: string) => void;
onArchive?: (id: string) => void;
onDuplicate?: (id: string) => void;
onUpdate?: (id: string, data: UpdatePageParams) => void;
}

export const DocList: React.FC<DocListProps> = ({
group,
title,
defaultIcon,
isLoading,
redirect,
onCreate,
onArchive,
onDuplicate,
onUpdate,
}) => {
const origin = useOrigin();
const defaultIcon = generateDefaultIcon(group);

const pages = usePlatformStore((state) =>
Object.values(state.pages).filter(
Expand All @@ -49,15 +45,15 @@ export const DocList: React.FC<DocListProps> = ({

const select = (id: string, url?: string) => {
setActivePage(id);
redirect(url ?? "#");
redirect?.(url ?? "#");
};

return (
<TreeGroup
title={title}
description={`Add a ${group}`}
isLoading={isLoading}
onCreate={() => onCreate()}
onCreate={() => onCreate?.(group)}
>
<TreeList
nodes={nodes}
Expand All @@ -74,13 +70,16 @@ export const DocList: React.FC<DocListProps> = ({
>
<ActionGroup
type={MenuType.Normal}
title={node.title}
icon={node.icon ?? defaultIcon}
pageLink={node.url ? `${origin}/${node.url}` : "#"}
isFavorite={node.isFavorite}
lastEditedBy={node.lastEditedBy}
lastEditedAt={node.lastEditedAt}
onCreate={() => onCreate(node.id)}
onDelete={() => onArchive(node.id)}
onUpdate={(data) => onUpdate(node.id, data)}
onCreate={() => onCreate?.(group, node.id)}
onDelete={() => onArchive?.(node.id)}
onDuplicate={() => onDuplicate?.(node.id)}
onUpdate={(data) => onUpdate?.(node.id, data)}
/>
</TreeItem>
)}
Expand Down
20 changes: 16 additions & 4 deletions packages/notion/src/sidebar-2/_components/favorite-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,27 @@ import {
TreeNode,
} from "@swy/ui/shared";

import { generateDefaultIcon } from "../../common";
import { usePlatformStore } from "../../slices";
import type { Page, UpdatePageParams } from "../../types";
import { ActionGroup } from "./action-group";
import { MenuType } from "./types";

interface FavoriteListProps {
isLoading?: boolean;
redirect: (url: string) => void;
onUpdate: (id: string, data: UpdatePageParams) => void;
redirect?: (url: string) => void;
onCreate?: (group: string, parentId: string) => void;
onArchive?: (id: string) => void;
onDuplicate?: (id: string) => void;
onUpdate?: (id: string, data: UpdatePageParams) => void;
}

export const FavoriteList: React.FC<FavoriteListProps> = ({
isLoading,
redirect,
onCreate,
onArchive,
onDuplicate,
onUpdate,
}) => {
const origin = useOrigin();
Expand All @@ -40,7 +47,7 @@ export const FavoriteList: React.FC<FavoriteListProps> = ({

const select = (node: Page) => {
setActivePage(node.id);
redirect(node.url ?? "#");
redirect?.(node.url ?? "#");
};

return (
Expand All @@ -59,11 +66,16 @@ export const FavoriteList: React.FC<FavoriteListProps> = ({
>
<ActionGroup
type={node.isFavorite ? MenuType.Favorites : MenuType.Normal}
title={node.title}
icon={node.icon ?? generateDefaultIcon(node.type)}
pageLink={node.url ? `${origin}/${node.url}` : "#"}
isFavorite={node.isFavorite}
lastEditedBy={node.lastEditedBy}
lastEditedAt={node.lastEditedAt}
onUpdate={(data) => onUpdate(node.id, data)}
onCreate={() => onCreate?.(node.type, node.id)}
onDelete={() => onArchive?.(node.id)}
onDuplicate={() => onDuplicate?.(node.id)}
onUpdate={(data) => onUpdate?.(node.id, data)}
/>
</TreeItem>
)}
Expand Down
57 changes: 25 additions & 32 deletions packages/notion/src/sidebar-2/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import type { Page, UpdatePageParams } from "../types";
import { DocList, FavoriteList, HintItem, TrashBox } from "./_components";
import { SearchCommand, SettingsModal } from "./modals";

const GROUPS = {
document: "Document",
kanban: "Kanban",
whiteboard: "Whiteboard",
} as const;

interface SidebarProps {
className?: string;
isMobile?: boolean;
Expand All @@ -31,6 +37,7 @@ interface SidebarProps {
onCreate?: (type: string, parentId?: string) => void;
onArchive?: (id: string) => void;
onRestore?: (id: string) => void;
onDuplicate?: (id: string) => void;
onDelete?: (id: string) => void;
onUpdate?: (id: string, data: UpdatePageParams) => void;
};
Expand Down Expand Up @@ -134,39 +141,25 @@ export const Sidebar2 = forwardRef<HTMLElement, SidebarProps>(function Sidebar(
<div className="mt-4 flex w-full flex-col gap-y-4">
<FavoriteList
isLoading={pages.isLoading}
redirect={(url) => redirect?.(url)}
onUpdate={(id, data) => pages.onUpdate?.(id, data)}
/>
<DocList
isLoading={pages.isLoading}
group="document"
title="Document"
defaultIcon={{ type: "lucide", name: "file-text" }}
redirect={(url) => redirect?.(url)}
onCreate={(parentId) => pages.onCreate?.("document", parentId)}
onArchive={(id) => pages.onArchive?.(id)}
onUpdate={(id, data) => pages.onUpdate?.(id, data)}
/>
<DocList
isLoading={pages.isLoading}
group="kanban"
title="Kanban"
defaultIcon={{ type: "lucide", name: "columns-3" }}
redirect={(url) => redirect?.(url)}
onCreate={(parentId) => pages.onCreate?.("kanban", parentId)}
onArchive={(id) => pages.onArchive?.(id)}
onUpdate={(id, data) => pages.onUpdate?.(id, data)}
/>
<DocList
isLoading={pages.isLoading}
group="whiteboard"
title="Whiteboard"
defaultIcon={{ type: "lucide", name: "presentation" }}
redirect={(url) => redirect?.(url)}
onCreate={(parentId) => pages.onCreate?.("whiteboard", parentId)}
onArchive={(id) => pages.onArchive?.(id)}
onUpdate={(id, data) => pages.onUpdate?.(id, data)}
redirect={redirect}
onCreate={pages.onCreate}
onArchive={pages.onArchive}
onDuplicate={pages.onDuplicate}
onUpdate={pages.onUpdate}
/>
{Object.entries(GROUPS).map(([group, title]) => (
<DocList
key={group}
isLoading={pages.isLoading}
group={group}
title={title}
redirect={redirect}
onCreate={pages.onCreate}
onArchive={pages.onArchive}
onDuplicate={pages.onDuplicate}
onUpdate={pages.onUpdate}
/>
))}
<TrashBox
isOpen={trashOpen}
onOpenChange={setTrashOpen}
Expand Down
4 changes: 3 additions & 1 deletion packages/notion/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,6 @@ export interface Page {
lastEditedBy: string;
}

export type UpdatePageParams = Partial<Pick<Page, "isFavorite">>;
export type UpdatePageParams = Partial<
Pick<Page, "title" | "icon" | "isFavorite">
>;

0 comments on commit 02a6e13

Please sign in to comment.