Skip to content

Commit

Permalink
impl. add/remove favorites
Browse files Browse the repository at this point in the history
  • Loading branch information
steeeee0223 committed Dec 12, 2024
1 parent 3078047 commit e9717bd
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 40 deletions.
4 changes: 2 additions & 2 deletions apps/storybook/src/stories/playground/liveblocks-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const LayoutWithLiveblocks = ({ children }: LayoutProps) => {
const setActiveWorkspace = usePlatformStore(
(state) => state.setActiveWorkspace,
);
const { pageId, isLoading, fetchPages, selectPage } =
const { pageId, isLoading, fetchPages, selectPage, updatePage } =
usePages(activeWorkspace);

return (
Expand Down Expand Up @@ -72,7 +72,7 @@ export const LayoutWithLiveblocks = ({ children }: LayoutProps) => {
onFetchConnections: () => Promise.resolve(mockConnections),
onFetchMemberships: () => Promise.resolve(mockMemberships),
}}
pageHandlers={{ isLoading, fetchPages }}
pageHandlers={{ isLoading, fetchPages, onUpdate: updatePage }}
WorkspaceSwitcher={
<WorkspaceSwitcher2
user={user!}
Expand Down
9 changes: 8 additions & 1 deletion apps/storybook/src/stories/playground/use-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const usePages = (workspaceId: string | null) => {
const activePage = usePlatformStore((state) => state.activePage);
const setActivePage = usePlatformStore((state) => state.setActivePage);
const setPages = usePlatformStore((state) => state.setPages);
const updatePage = usePlatformStore((state) => state.updatePage);

const { isLoading } = useSWR<Page[], Error>(workspaceId, fetcher, {
onSuccess: (data) => setPages(data),
Expand All @@ -26,5 +27,11 @@ export const usePages = (workspaceId: string | null) => {
setActivePage(id);
};

return { pageId: activePage ?? "#", isLoading, fetchPages, selectPage };
return {
pageId: activePage ?? "#",
isLoading,
fetchPages,
selectPage,
updatePage,
};
};
14 changes: 13 additions & 1 deletion packages/notion/src/common/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const Help = () => {
</svg>
);
};
export const Star = () => {
export const Sparkle = () => {
return (
<svg
role="graphics-symbol"
Expand Down Expand Up @@ -98,3 +98,15 @@ export const Enter = (props: IconProps) => (
<path d="M5.38965 14.1667C5.81812 14.1667 6.10156 13.8767 6.10156 13.468C6.10156 13.2571 6.01587 13.0989 5.89062 12.967L4.18994 11.3125L3.02979 10.3369L4.55908 10.4028H12.7922C14.4402 10.4028 15.1389 9.65796 15.1389 8.04297V4.13403C15.1389 2.48608 14.4402 1.78735 12.7922 1.78735H9.13379C8.70532 1.78735 8.4021 2.11035 8.4021 2.50586C8.4021 2.90137 8.69873 3.22437 9.13379 3.22437H12.7593C13.4316 3.22437 13.7151 3.50781 13.7151 4.17358V7.99683C13.7151 8.67578 13.425 8.95923 12.7593 8.95923H4.55908L3.02979 9.03174L4.18994 8.04956L5.89062 6.39502C6.01587 6.26978 6.10156 6.11157 6.10156 5.89404C6.10156 5.48535 5.81812 5.19531 5.38965 5.19531C5.21167 5.19531 5.01392 5.27441 4.8689 5.41943L1.08521 9.1438C0.933594 9.28882 0.854492 9.48657 0.854492 9.68433C0.854492 9.87549 0.933594 10.0732 1.08521 10.2183L4.8689 13.9492C5.01392 14.0876 5.21167 14.1667 5.38965 14.1667Z"></path>
</svg>
);

export const Star: React.FC<IconProps> = (props) => (
<svg role="graphics-symbol" viewBox="0 0 16 16" {...props}>
<path d="M3.2627 14.7637C3.58398 15.0098 3.98047 14.9277 4.44531 14.5928L8 11.9814L11.5547 14.5928C12.0195 14.9277 12.4229 15.0098 12.7441 14.7637C13.0586 14.5244 13.127 14.1279 12.9424 13.5879L11.541 9.41113L15.1299 6.83398C15.5879 6.50586 15.7861 6.14355 15.6562 5.76074C15.5264 5.38477 15.1641 5.2002 14.5967 5.20703L10.1943 5.23438L8.85449 1.03711C8.68359 0.490234 8.40332 0.196289 8 0.196289C7.60352 0.196289 7.32324 0.490234 7.14551 1.03711L5.80566 5.23438L1.40332 5.20703C0.835938 5.2002 0.480469 5.38477 0.350586 5.76074C0.220703 6.14355 0.412109 6.50586 0.876953 6.83398L4.46582 9.41113L3.05762 13.5879C2.87305 14.1279 2.94141 14.5244 3.2627 14.7637ZM4.45898 13.1162C4.45215 13.1025 4.45215 13.0957 4.45898 13.0615L5.75781 9.39062C5.87402 9.05566 5.83301 8.84375 5.52539 8.63867L2.3125 6.4375C2.28516 6.42383 2.27148 6.41016 2.27832 6.38965C2.29199 6.37598 2.30566 6.36914 2.33984 6.36914L6.22949 6.45801C6.58496 6.47168 6.7627 6.35547 6.86523 6.01367L7.96582 2.28125C7.97266 2.24023 7.98633 2.2334 8 2.2334C8.02051 2.2334 8.02734 2.24023 8.04102 2.28125L9.13477 6.01367C9.2373 6.35547 9.42188 6.47168 9.77051 6.45801L13.6602 6.36914C13.7012 6.36914 13.7148 6.37598 13.7217 6.38965C13.7285 6.41016 13.7217 6.41699 13.6875 6.4375L10.4814 8.63867C10.167 8.85059 10.126 9.05566 10.2422 9.39062L11.541 13.0615C11.5547 13.0957 11.5547 13.1025 11.541 13.1162C11.5273 13.1367 11.5137 13.123 11.4863 13.1094L8.40332 10.7305C8.12305 10.5117 7.87695 10.5117 7.60352 10.7305L4.52051 13.1094C4.49316 13.123 4.47266 13.1367 4.45898 13.1162Z" />
</svg>
);

export const Unstar: React.FC<IconProps> = (props) => (
<svg role="graphics-symbol" viewBox="0 0 16 16" {...props}>
<path d="M11.6641 9.32227L15.1299 6.83398C15.5879 6.50586 15.7861 6.14355 15.6562 5.76074C15.5264 5.38477 15.1641 5.2002 14.5967 5.20703L10.1943 5.23438L8.85449 1.03711C8.68359 0.490234 8.40332 0.196289 8 0.196289C7.60352 0.196289 7.32324 0.490234 7.14551 1.03711L6.22949 3.9082L7.2002 4.87207L7.96582 2.28125C7.97266 2.24023 7.98633 2.2334 8 2.2334C8.02051 2.2334 8.02734 2.24023 8.04102 2.28125L9.13477 6.01367C9.2373 6.35547 9.42188 6.47168 9.77051 6.45801L13.6602 6.36914C13.7012 6.36914 13.7148 6.37598 13.7217 6.38965C13.7285 6.41016 13.7217 6.41699 13.6875 6.4375L10.7754 8.44043L11.6641 9.32227ZM13.9268 14.2783C14.1387 14.4902 14.4941 14.4902 14.6992 14.2783C14.9043 14.0664 14.9111 13.7246 14.6992 13.5127L3.44043 2.28809C3.22852 2.07617 2.87988 2.06934 2.66797 2.28809C2.46289 2.49316 2.46289 2.84863 2.66797 3.05371L13.9268 14.2783ZM3.2627 14.7637C3.58398 15.0098 3.98047 14.9277 4.44531 14.5928L8 11.9814L11.5547 14.5928C12.0195 14.9277 12.4092 14.9961 12.7441 14.7637C12.8193 14.709 12.8672 14.6406 12.9629 14.5107L8.3418 10.7236C8.10254 10.5254 7.84277 10.5391 7.60352 10.7305L4.52051 13.1094C4.49316 13.123 4.47266 13.1367 4.45898 13.1162C4.45215 13.1025 4.45215 13.0957 4.45898 13.0615L5.75781 9.39062C5.87402 9.05566 5.83301 8.84375 5.52539 8.63867L2.3125 6.4375C2.28516 6.42383 2.27148 6.41016 2.27832 6.38965C2.29199 6.37598 2.30566 6.36914 2.33984 6.36914L4.88281 6.42383L3.67969 5.2207L1.40332 5.20703C0.835938 5.2002 0.480469 5.38477 0.350586 5.76074C0.220703 6.14355 0.412109 6.50586 0.876953 6.83398L4.46582 9.41113L3.05762 13.5879C2.87305 14.1279 2.94141 14.5244 3.2627 14.7637Z" />
</svg>
);
2 changes: 1 addition & 1 deletion packages/notion/src/settings-panel/body/plans.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const ActivePlan = ({ activePlan, canUpgrade }: ActivePlanProps) => {
>
<div className="flex flex-col gap-1.5">
<CardTitle className="relative flex items-center gap-1 self-stretch text-sm tracking-[-0.1px]">
<Icon.Star /> {active.ai.title}
<Icon.Sparkle /> {active.ai.title}
</CardTitle>
<div className="text-xs text-[#787774]">
{active.ai.description}
Expand Down
65 changes: 44 additions & 21 deletions packages/notion/src/sidebar-2/_components/action-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
MoreHorizontal,
Plus,
SquarePen,
Star,
Trash,
} from "lucide-react";
import { useCopyToClipboard } from "usehooks-ts";

import {
Button,
Expand All @@ -20,21 +20,33 @@ import {
} from "@swy/ui/shadcn";
import { Hint } from "@swy/ui/shared";

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

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

export const ActionGroup: React.FC<ActionGroupProps> = ({
type,
pageLink,
isFavorite,
lastEditedBy,
lastEditedAt,
onCreate,
onDelete,
onUpdate,
}) => {
const [, copy] = useCopyToClipboard();

return (
<div className="ml-auto flex items-center p-0.5">
<DropdownMenu>
Expand All @@ -59,32 +71,43 @@ export const ActionGroup: React.FC<ActionGroupProps> = ({
forceMount
onClick={(e) => e.stopPropagation()}
>
<DropdownMenuItem>
<Star className="mr-2 size-4" />
Add to Favorites
</DropdownMenuItem>
{isFavorite ? (
<DropdownMenuItem onClick={() => onUpdate({ isFavorite: false })}>
<Icon.Unstar className="mr-2 size-4 flex-shrink-0 fill-icon dark:fill-icon-dark" />
Remove from Favorites
</DropdownMenuItem>
) : (
<DropdownMenuItem onClick={() => onUpdate({ isFavorite: true })}>
<Icon.Star className="mr-2 size-4 flex-shrink-0 fill-icon dark:fill-icon-dark" />
Add to Favorites
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem>
<DropdownMenuItem onClick={() => void copy(pageLink)}>
<Link className="mr-2 size-4" />
Copy link
</DropdownMenuItem>
<DropdownMenuItem>
<Copy className="mr-2 size-4" />
Duplicate
</DropdownMenuItem>
{type === MenuType.Normal && (
<DropdownMenuItem>
<Copy className="mr-2 size-4" />
Duplicate
</DropdownMenuItem>
)}
<DropdownMenuItem>
<SquarePen className="mr-2 size-4" />
Rename
</DropdownMenuItem>
<DropdownMenuItem
onClick={onDelete}
className="group/trash hover:text-red"
>
<Trash className="mr-2 size-4" />
Move to Trash
</DropdownMenuItem>
{type === MenuType.Normal && (
<DropdownMenuItem
onClick={onDelete}
className="group/trash hover:text-red"
>
<Trash className="mr-2 size-4" />
Move to Trash
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem>
<DropdownMenuItem onClick={() => window.open(pageLink)}>
<ArrowUpRight className="mr-2 size-4" />
Open in new tab
</DropdownMenuItem>
Expand Down
10 changes: 10 additions & 0 deletions packages/notion/src/sidebar-2/_components/doc-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,32 @@ import {
} from "@swy/ui/shared";

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

interface DocListProps {
group: string;
title: string;
defaultIcon?: IconInfo;
isLoading?: boolean;
getPageLink?: (group: string, id: string) => string;
onSelect: (group: string, id: string) => void;
onCreate: (parentId?: string) => void;
onArchive: (id: string) => void;
onUpdate: (id: string, data: UpdatePageParams) => void;
}

export const DocList: React.FC<DocListProps> = ({
group,
title,
defaultIcon,
isLoading,
getPageLink,
onSelect,
onCreate,
onArchive,
onUpdate,
}) => {
const pages = usePlatformStore((state) =>
Object.values(state.pages).filter(
Expand Down Expand Up @@ -66,10 +72,14 @@ export const DocList: React.FC<DocListProps> = ({
expandable={group === "document"}
>
<ActionGroup
type={MenuType.Normal}
pageLink={getPageLink?.(node.type, node.id) ?? "/"}
isFavorite={node.isFavorite}
lastEditedBy={node.lastEditedBy}
lastEditedAt={node.lastEditedAt}
onCreate={() => onCreate(node.id)}
onDelete={() => onArchive(node.id)}
onUpdate={(data) => onUpdate(node.id, data)}
/>
</TreeItem>
)}
Expand Down
72 changes: 72 additions & 0 deletions packages/notion/src/sidebar-2/_components/favorite-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from "react";

import {
buildTree,
TreeGroup,
TreeItem,
TreeList,
TreeNode,
} from "@swy/ui/shared";

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

interface FavoriteListProps {
isLoading?: boolean;
getPageLink?: (group: string, id: string) => string;
onSelect: (group: string, id: string) => void;
onUpdate: (id: string, data: UpdatePageParams) => void;
}

export const FavoriteList: React.FC<FavoriteListProps> = ({
isLoading,
getPageLink,
onSelect,
onUpdate,
}) => {
const pages = usePlatformStore((state) => Object.values(state.pages));
const favorites = pages.filter((page) => page.isFavorite);
const activePage = usePlatformStore((state) => state.activePage);
const setActivePage = usePlatformStore((state) => state.setActivePage);

const nodes = favorites.map<TreeNode<Page>>((fav) => ({
...fav,
parentId: null,
children: buildTree(pages, fav.id),
}));

const select = (node: Page) => {
setActivePage(node.id);
onSelect(node.type, node.id);
};

return (
<TreeGroup title="Favorites" isLoading={isLoading}>
<TreeList
nodes={nodes}
defaultIcon={{ type: "lucide", name: "file" }}
selectedId={activePage}
Item={({ node, ...props }) => (
<TreeItem
{...props}
node={node}
className="group"
onSelect={() => select(node)}
expandable={node.type === "document"}
>
<ActionGroup
type={node.isFavorite ? MenuType.Favorites : MenuType.Normal}
pageLink={getPageLink?.(node.type, node.id) ?? "/"}
isFavorite={node.isFavorite}
lastEditedBy={node.lastEditedBy}
lastEditedAt={node.lastEditedAt}
onUpdate={(data) => onUpdate(node.id, data)}
/>
</TreeItem>
)}
/>
</TreeGroup>
);
};
1 change: 1 addition & 0 deletions packages/notion/src/sidebar-2/_components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./doc-list";
export * from "./favorite-list";
export * from "./hint-item";
export * from "./trash-box";
4 changes: 4 additions & 0 deletions packages/notion/src/sidebar-2/_components/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum MenuType {
Normal = "normal",
Favorites = "favorites",
}
19 changes: 17 additions & 2 deletions packages/notion/src/sidebar-2/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import {
} from "lucide-react";
import { useHotkeys } from "react-hotkeys-hook";

import { useOrigin } from "@swy/ui/hooks";
import { cn } from "@swy/ui/lib";
import { Button, useTheme } from "@swy/ui/shadcn";
import { Hint, useModal } from "@swy/ui/shared";

import type { SettingsPanelProps } from "../settings-panel";
import type { Page } from "../types";
import { DocList, HintItem, TrashBox } from "./_components";
import type { Page, UpdatePageParams } from "../types";
import { DocList, FavoriteList, HintItem, TrashBox } from "./_components";
import { SearchCommand, SettingsModal } from "./modals";

interface SidebarProps {
Expand All @@ -32,6 +33,7 @@ interface SidebarProps {
onArchive?: (id: string) => void;
onRestore?: (id: string) => void;
onDelete?: (id: string) => void;
onUpdate?: (id: string, data: UpdatePageParams) => void;
};
WorkspaceSwitcher: React.ReactNode;
}
Expand All @@ -49,6 +51,7 @@ export const Sidebar2 = forwardRef<HTMLElement, SidebarProps>(function Sidebar(
ref,
) {
const { theme: activeTheme, setTheme } = useTheme();
const origin = useOrigin();
/** Modals */
const { setOpen } = useModal();
const [trashOpen, setTrashOpen] = useState(false);
Expand Down Expand Up @@ -131,32 +134,44 @@ export const Sidebar2 = forwardRef<HTMLElement, SidebarProps>(function Sidebar(
/>
</div>
<div className="mt-4 flex w-full flex-col gap-y-4">
<FavoriteList
isLoading={pages.isLoading}
getPageLink={(group, id) => `${origin}/${group}/${id}`}
onSelect={(group, id) => redirect?.(`/${group}/${id}`)}
onUpdate={(id, data) => pages.onUpdate?.(id, data)}
/>
<DocList
isLoading={pages.isLoading}
group="document"
title="Document"
defaultIcon={{ type: "lucide", name: "file-text" }}
getPageLink={(group, id) => `${origin}/${group}/${id}`}
onSelect={(group, id) => redirect?.(`/${group}/${id}`)}
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" }}
getPageLink={(group, id) => `${origin}/${group}/${id}`}
onSelect={(group, id) => redirect?.(`/${group}/${id}`)}
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" }}
getPageLink={(group, id) => `${origin}/${group}/${id}`}
onSelect={(group, id) => redirect?.(`/${group}/${id}`)}
onCreate={(parentId) => pages.onCreate?.("whiteboard", parentId)}
onArchive={(id) => pages.onArchive?.(id)}
onUpdate={(id, data) => pages.onUpdate?.(id, data)}
/>
<TrashBox
isOpen={trashOpen}
Expand Down
Loading

0 comments on commit e9717bd

Please sign in to comment.