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

Space 화면 Header UI 구현 #144

Merged
merged 8 commits into from
Nov 28, 2024
Merged
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
1 change: 1 addition & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@milkdown/theme-nord": "^7.5.0",
"@prosemirror-adapter/react": "^0.2.6",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function App() {
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/space/:entrySpaceId" element={<SpacePage />} />
<Route path="/space/:spaceId" element={<SpacePage />} />
<Route path="/note/:noteId" element={<Editor />} />
</Routes>
</BrowserRouter>
Expand Down
124 changes: 124 additions & 0 deletions packages/frontend/src/components/SpaceBreadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Link } from "react-router-dom";

import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "./ui/breadcrumb";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "./ui/dropdown-menu";

type SpacePath = {
name: string;
urlPath: string;
};

function splitSpacePaths(spacePaths: SpacePath[], itemCountToDisplay: number) {
// 처음 스페이스는 무조건 보여준다.
const firstSpacePath = spacePaths[0];

// 중간 스페이스들은 ...으로 표시하고, 클릭 시 드롭다운 메뉴로 보여준다.
const hiddenSpacePaths = spacePaths.slice(1, -2);

// 마지막 (n-1)개 스페이스는 무조건 보여준다.
const lastItemCount = Math.min(spacePaths.length, itemCountToDisplay - 1);
const shownSpacePaths = spacePaths.slice(-lastItemCount);

return [firstSpacePath, hiddenSpacePaths, shownSpacePaths] as const;
}

type HiddenItemsProps = {
spacePaths: SpacePath[];
};

function HiddenItems({ spacePaths }: HiddenItemsProps) {
return (
<>
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger>
<BreadcrumbEllipsis />
</DropdownMenuTrigger>
<DropdownMenuContent>
{spacePaths.map(({ name, urlPath }) => (
<DropdownMenuItem key={urlPath} asChild>
<Link to={`/space/${urlPath}`}>{name}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
<BreadcrumbSeparator />
</>
);
}

type SpaceBreadcrumbItemProps = {
spacePath: SpacePath;
isPage?: boolean;
};

function SpaceBreadcrumbItem({ spacePath, isPage }: SpaceBreadcrumbItemProps) {
if (isPage) {
return (
<BreadcrumbItem>
<BreadcrumbPage className="truncate max-w-20">
{spacePath.name}
</BreadcrumbPage>
</BreadcrumbItem>
);
}
hoqn marked this conversation as resolved.
Show resolved Hide resolved

return (
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link className="truncate max-w-20" to={`/space/${spacePath.urlPath}`}>
{spacePath.name}
</Link>
</BreadcrumbLink>
<BreadcrumbSeparator />
</BreadcrumbItem>
);
}

type SpaceBreadcrumbProps = {
spacePaths: SpacePath[];
itemCountToDisplay?: number;
};

export default function SpaceBreadcrumb({
spacePaths,
itemCountToDisplay = 3,
}: SpaceBreadcrumbProps) {
// [처음, (...중간...), 직전, 현재]
const [firstSpacePath, hiddenSpacePaths, shownSpacePaths] = splitSpacePaths(
spacePaths,
itemCountToDisplay,
);
hoqn marked this conversation as resolved.
Show resolved Hide resolved

return (
<Breadcrumb>
<BreadcrumbList>
{firstSpacePath && <SpaceBreadcrumbItem spacePath={firstSpacePath} />}
{hiddenSpacePaths.length > 0 && (
<HiddenItems spacePaths={hiddenSpacePaths} />
)}
{shownSpacePaths.map((spacePath, index) => (
<SpaceBreadcrumbItem
key={spacePath.urlPath}
spacePath={spacePath}
isPage={index === shownSpacePaths.length - 1}
/>
))}
</BreadcrumbList>
</Breadcrumb>
);
}
33 changes: 33 additions & 0 deletions packages/frontend/src/components/space/SpacePageHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import SpaceBreadcrumb from "../SpaceBreadcrumb";
import { Button } from "../ui/button";

export default function SpacePageHeader() {
return (
<header className="fixed z-20 top-0 inset-x-0 h-16 bg-background/50 backdrop-blur-lg">
<div className="container mx-auto px-6 h-full flex flex-row items-center justify-between">
<div className="flex-1">
<SpaceBreadcrumb
spacePaths={[
{ name: "하나", urlPath: "1" },
{ name: "셋", urlPath: "3" },
{ name: "넷", urlPath: "4" },
{ name: "다섯", urlPath: "5" },
{ name: "여섯", urlPath: "6" },
{ name: "일곱", urlPath: "7" },
{ name: "여덟", urlPath: "8" },
{ name: "아홉", urlPath: "9" },
{
name: "엄청 긴 제목을 가진 스페이스다아아아아아아아아아아아아아",
urlPath: "2",
},
{ name: "열", urlPath: "10" },
]}
/>
</div>
<div className="flex-0">
<Button variant="outline">공유</Button>
</div>
</div>
</header>
);
}
24 changes: 0 additions & 24 deletions packages/frontend/src/components/space/YjsSpaceView.tsx

This file was deleted.

115 changes: 115 additions & 0 deletions packages/frontend/src/components/ui/breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"

import { cn } from "@/lib/utils"

const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
Breadcrumb.displayName = "Breadcrumb"

const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className
)}
{...props}
/>
))
BreadcrumbList.displayName = "BreadcrumbList"

const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
))
BreadcrumbItem.displayName = "BreadcrumbItem"

const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"

return (
<Comp
ref={ref}
className={cn("transition-colors hover:text-foreground", className)}
{...props}
/>
)
})
BreadcrumbLink.displayName = "BreadcrumbLink"

const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
/>
))
BreadcrumbPage.displayName = "BreadcrumbPage"

const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<"li">) => (
<li
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
)
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"

const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
)
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"

export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}
Loading