fix: link (#115)

* start

* .

* seeding connections

* .

* wip

* wip: learning state

* wip: notes section

* wip: many

* topics

* chore: update schema

* update package

* update sidebar

* update page section

* feat: profile

* fix: remove z index

* fix: wrong type

* add avatar

* add avatar

* wip

* .

* store page section key

* remove atom page section

* fix rerender

* fix rerender

* fix rerender

* fix rerender

* fix link

* search light/dark mode

* bubble menu ui

* .

* fix: remove unecessary code

* chore: mark as old for old schema

* chore: adapt new schema

* fix: add topic schema but null for now

* fix: add icon on personal link

* fix: list item

* fix: set url fetched when editing

* fix: remove image

* feat: add icon to link

* feat: custom url zod validation

* fix: metadata test

* chore: update utils

* fix: link

* fix: url fetcher

* .

* .

* fix: add link, section

* chore: seeder

* .

* .

* .

* .

* fix: change checkbox to learning state

* fix: click outside editing form

* feat: constant

* chore: move to master folder

* chore: adapt new schema

* chore: cli for new schema

* fix: new schema for dev seed

* fix: seeding

* update package

* chore: forcegraph seed

* bottombar

* if isEdit delete icon

* showCreate X button

* .

* options

* chore: implement topic from public global group

* chore: update learning state

* fix: change implementation for outside click

* chore: implement new form param

* chore: update env example

* feat: link form refs exception

* new page button layout, link topic search fixed

* chore: enable topic

* chore: update seed

* profile

* chore: move framer motion package from root to web and add nuqs

* chore: add LearningStateValue

* chore: implement active state

* profile

* chore: use fancy switch and update const

* feat: filter implementation

* dropdown menu

* .

* sidebar topics

* topic selected color

* feat: topic detail

* fix: collapsible page

* pages - sorted by, layout, visible mode

* .

* .

* .

* topic status sidebar

* topic button and count

* fix: topic

* page delete/topic buttons

* search ui

* selected topic for page

* selected topic status sidebar

* removed footer

* update package

* .

---------

Co-authored-by: Nikita <github@nikiv.dev>
Co-authored-by: marshennikovaolga <marshennikova@gmail.com>
Co-authored-by: Kisuyo <ig.intr3st@gmail.com>
This commit is contained in:
Aslam
2024-08-26 19:35:00 +07:00
committed by GitHub
parent 7cbfcc705b
commit 2d270706a5
77 changed files with 3002 additions and 1327 deletions

View File

@@ -71,14 +71,14 @@ const AiSearch: React.FC<AiSearchProps> = (props: { searchQuery: string }) => {
return (
<div className="mx-auto flex max-w-3xl flex-col items-center">
<div className="w-full rounded-lg bg-inherit p-6 text-white">
<div className="mb-6 rounded-lg bg-blue-700 p-4">
<div className="w-full rounded-lg bg-inherit p-6 text-black dark:text-white">
<div className="mb-6 rounded-lg bg-blue-700 p-4 text-white">
<h2 className="text-lg font-medium"> This is what I have found:</h2>
</div>
<div className="rounded-xl bg-[#121212] p-4" ref={root_el}></div>
<div className="rounded-xl bg-neutral-100 p-4 dark:bg-[#121212]" ref={root_el}></div>
</div>
<p className="text-md pb-5 font-semibold opacity-50">{error}</p>
<button className="text-md rounded-2xl bg-neutral-800 px-6 py-3 font-semibold text-opacity-50 shadow-inner shadow-neutral-700/50 transition-colors hover:bg-neutral-700">
<button className="text-md rounded-2xl bg-neutral-300 px-6 py-3 font-semibold text-opacity-50 shadow-inner shadow-neutral-400/50 transition-colors hover:bg-neutral-700 dark:bg-neutral-800 dark:shadow-neutral-700/50">
Ask Community
</button>
</div>

View File

@@ -16,7 +16,7 @@ export const ContentHeader = React.forwardRef<HTMLDivElement, ContentHeaderProps
return (
<header
className={cn(
"flex min-h-20 min-w-0 max-w-[100vw] shrink-0 items-center gap-3 pl-8 pr-6 transition-opacity max-lg:pl-4 max-lg:pr-5",
"flex min-h-10 min-w-0 max-w-[100vw] shrink-0 items-center gap-3 pl-8 pr-6 transition-opacity max-lg:pl-4 max-lg:pr-5",
className
)}
ref={ref}
@@ -50,7 +50,7 @@ export const SidebarToggleButton: React.FC = () => {
size="icon"
variant="ghost"
aria-label="Menu"
className="text-primary/60 z-50"
className="text-primary/60"
onClick={handleClick}
>
<PanelLeftIcon size={16} />

View File

@@ -0,0 +1,37 @@
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from "@/components/ui/dialog"
interface DeleteModalProps {
isOpen: boolean
onClose: () => void
onConfirm: () => void
title: string
}
export default function DeletePageModal({ isOpen, onClose, onConfirm, title }: DeleteModalProps) {
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete "{title}"?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button variant="destructive" className="bg-red-700" onClick={onConfirm}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { icons } from "lucide-react"
export type IconProps = {
name: keyof typeof icons
className?: string
strokeWidth?: number
[key: string]: any
}
export const LaIcon = React.memo(({ name, className, size, strokeWidth, ...props }: IconProps) => {
const IconComponent = icons[name]
if (!IconComponent) {
return null
}
return <IconComponent className={cn(!size ? "size-4" : size, className)} strokeWidth={strokeWidth || 2} {...props} />
})
LaIcon.displayName = "LaIcon"

View File

@@ -0,0 +1,14 @@
export const PageLoader = () => {
return (
<div className="relative top-[-60px] flex h-full flex-col justify-center">
<div className="mx-auto w-full max-w-[220px] py-6">
<div className="text-center">
<div className="mb-4 text-base font-medium">Preparing application</div>
<div className="bg-muted relative flex h-1 w-full appearance-none overflow-hidden rounded leading-3">
<div className="progress-bar-indeterminate bg-primary flex h-full flex-col justify-center overflow-hidden whitespace-nowrap text-center text-white"></div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,19 +1,23 @@
import { SidebarItem } from "../sidebar"
import { z } from "zod"
import { useAtom } from "jotai"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { usePathname, useRouter } from "next/navigation"
import { useAccount } from "@/lib/providers/jazz-provider"
import { Input } from "@/components/ui/input"
import { cn, generateUniqueSlug } from "@/lib/utils"
import { atomWithStorage } from "jotai/utils"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form"
import { PlusIcon } from "lucide-react"
import { generateUniqueSlug } from "@/lib/utils"
import { PersonalPage } from "@/lib/schema/personal-page"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { useForm } from "react-hook-form"
import { PersonalPage, PersonalPageLists } from "@/lib/schema/personal-page"
import { zodResolver } from "@hookform/resolvers/zod"
import { useState, useEffect, useCallback } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { LaIcon } from "../../la-icon"
import { toast } from "sonner"
import Link from "next/link"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
const pageSortAtom = atomWithStorage("pageSort", "title")
const createPageSchema = z.object({
title: z.string({ message: "Please enter a valid title" }).min(1, { message: "Please enter a valid title" })
})
@@ -21,47 +25,114 @@ const createPageSchema = z.object({
type PageFormValues = z.infer<typeof createPageSchema>
export const PageSection: React.FC = () => {
const { me } = useAccount()
const [personalPages, setPersonalPages] = useState<PersonalPage[]>([])
const [pagesSorted, setPagesSorted] = useAtom(pageSortAtom)
useEffect(() => {
if (me.root?.personalPages) {
setPersonalPages(prevPages => {
const newPages = Array.from(me.root?.personalPages ?? []).filter((page): page is PersonalPage => page !== null)
return [...prevPages, ...newPages.filter(newPage => !prevPages.some(prevPage => prevPage.id === newPage.id))]
})
}
}, [me.root?.personalPages])
const { me } = useAccount({
root: { personalPages: [] }
})
const onPageCreated = useCallback((newPage: PersonalPage) => {
setPersonalPages(prevPages => [...prevPages, newPage])
}, [])
const pageCount = me?.root.personalPages?.length || 0
const sortedPages = (filter: string) => {
setPagesSorted(filter)
}
return (
<div className="-ml-2">
<div className="group mb-0.5 ml-2 mt-2 flex flex-row items-center justify-between rounded-md">
<div
role="button"
tabIndex={0}
className="text-muted-foreground hover:bg-muted/50 flex h-6 grow cursor-default items-center justify-between gap-x-0.5 self-start rounded-md px-1 text-xs font-medium"
<div className="flex flex-col gap-px py-2">
<div className="hover:bg-accent group/pages flex items-center gap-px rounded-md">
<Button
variant="ghost"
className="size-6 flex-1 items-center justify-start rounded-md px-2 py-1 focus:outline-0 focus:ring-0"
>
<span className="group-hover:text-muted-foreground">Pages</span>
<CreatePageForm onPageCreated={onPageCreated} />
<p className="flex items-center text-xs font-medium">
Pages <span className="text-muted-foreground ml-1">{pageCount}</span>
</p>
</Button>
<div className="flex items-center opacity-0 transition-opacity duration-200 group-hover/pages:opacity-100">
<ShowAllForm filteredPages={sortedPages} />
<CreatePageForm />
</div>
</div>
<div className="relative shrink-0">
<div aria-hidden="false" className="ml-2 flex shrink-0 flex-col space-y-1 pb-2">
{personalPages.map(page => (
<SidebarItem key={page.id} url={`/pages/${page.id}`} label={page.title} />
))}
</div>
</div>
{me?.root.personalPages && <PageList personalPages={me.root.personalPages} sortBy={pagesSorted} />}
</div>
)
}
const CreatePageForm: React.FC<{ onPageCreated: (page: PersonalPage) => void }> = ({ onPageCreated }) => {
const PageList: React.FC<{ personalPages: PersonalPageLists; sortBy: string }> = ({ personalPages, sortBy }) => {
const pathname = usePathname()
const sortedPages = [...personalPages]
.sort((a, b) => {
if (sortBy === "title") {
return (a?.title || "").localeCompare(b?.title || "")
} else if (sortBy === "latest") {
return ((b as any)?.createdAt?.getTime?.() ?? 0) - ((a as any)?.createdAt?.getTime?.() ?? 0)
}
return 0
})
.slice(0, 6)
return (
<div className="flex flex-col gap-1">
{sortedPages.map(
page =>
page?.id && (
<div key={page.id} className="group/reorder-page relative">
<div className="group/sidebar-link relative flex min-w-0 flex-1">
<Link
href={`/pages/${page.id}`}
className={cn(
"group-hover/sidebar-link:bg-accent group-hover/sidebar-link:text-accent-foreground relative flex h-8 w-full items-center gap-2 rounded-md p-1.5 font-medium",
{ "bg-accent text-accent-foreground": pathname === `/pages/${page.id}` }
)}
>
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate text-sm">
<LaIcon name="FileText" className="size-3 flex-shrink-0 opacity-60" />
<p className="truncate opacity-95 group-hover/sidebar-link:opacity-100">{page.title}</p>
</div>
</Link>
</div>
</div>
)
)}
</div>
)
}
interface ShowAllFormProps {
filteredPages: (filter: string) => void
}
const ShowAllForm: React.FC<ShowAllFormProps> = ({ filteredPages }) => {
const [pagesSorted, setPagesSorted] = useAtom(pageSortAtom)
const handleSort = (newSort: string) => {
setPagesSorted(newSort.toLowerCase())
filteredPages(newSort.toLowerCase())
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 px-2 text-xs font-medium">
<LaIcon name="Ellipsis" className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-[100px]">
<DropdownMenuItem onClick={() => handleSort("title")}>
Title
{pagesSorted === "title" && <LaIcon name="Check" className="ml-auto h-4 w-4" />}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleSort("manual")}>
Manual
{pagesSorted === "manual" && <LaIcon name="Check" className="ml-auto h-4 w-4" />}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
const CreatePageForm: React.FC = () => {
const [open, setOpen] = useState(false)
const { me } = useAccount()
const route = useRouter()
@@ -88,7 +159,6 @@ const CreatePageForm: React.FC<{ onPageCreated: (page: PersonalPage) => void }>
)
me.root?.personalPages?.push(newPersonalPage)
onPageCreated(newPersonalPage)
form.reset()
setOpen(false)
@@ -103,9 +173,16 @@ const CreatePageForm: React.FC<{ onPageCreated: (page: PersonalPage) => void }>
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button type="button" size="icon" variant="ghost" aria-label="New Page" className="size-6">
<PlusIcon size={16} />
</Button>
<button
type="button"
aria-label="New Page"
className={cn(
"flex size-6 cursor-pointer items-center justify-center rounded-lg bg-inherit p-0.5 shadow-none focus:outline-0 focus:ring-0",
'opacity-0 transition-opacity duration-200 group-hover/pages:opacity-100 data-[state="open"]:opacity-100'
)}
>
<LaIcon name="Plus" className="text-black dark:text-white" />
</button>
</PopoverTrigger>
<PopoverContent align="start">
<Form {...form}>

View File

@@ -0,0 +1,108 @@
import { LaIcon } from "../../la-icon"
import { useState } from "react"
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from "@/components/ui/dropdown-menu"
import { useAccount } from "@/lib/providers/jazz-provider"
import Link from "next/link"
const MenuItem = ({
icon,
text,
href,
onClick,
onClose
}: {
icon: string
text: string
href?: string
onClick?: () => void
onClose: () => void
}) => {
const handleClick = () => {
onClose()
if (onClick) {
onClick()
}
}
return (
<div className="relative flex flex-1 items-center gap-2">
<LaIcon name={icon as any} />
{href ? (
<Link href={href} onClick={onClose}>
<span className="line-clamp-1 flex-1">{text}</span>
</Link>
) : (
<span className="line-clamp-1 flex-1" onClick={handleClick}>
{text}
</span>
)}
</div>
)
}
export const ProfileSection: React.FC = () => {
const { me, logOut } = useAccount({
profile: true
})
const [menuOpen, setMenuOpen] = useState(false)
const closeMenu = () => setMenuOpen(false)
return (
<div className="visible absolute inset-x-0 bottom-0 z-10 flex gap-8 p-2.5">
<div className="flex h-10 min-w-full items-center">
<div className="flex min-w-0">
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
<DropdownMenuTrigger asChild>
<button
aria-label="Profile"
className="hover:bg-accent focus-visible:ring-ring hover:text-accent-foreground flex items-center gap-1.5 truncate rounded pl-1 pr-1.5 focus-visible:outline-none focus-visible:ring-1"
>
<Avatar className="size-6">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
{/* <AvatarFallback>CN</AvatarFallback> */}
</Avatar>
<span className="truncate text-left text-sm font-medium -tracking-wider">{me?.profile?.name}</span>
<LaIcon
name="ChevronDown"
className={`size-4 shrink-0 transition-transform duration-300 ${menuOpen ? "rotate-180" : ""}`}
/>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="start" side="top">
<DropdownMenuItem>
<MenuItem icon="CircleUser" text="My profile" href="/profile" onClose={closeMenu} />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<MenuItem icon="Settings" text="Settings" href="/settings" onClose={closeMenu} />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<MenuItem icon="LogOut" text="Log out" onClick={logOut} onClose={closeMenu} />
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* <div className="flex min-w-2 grow flex-row"></div>
<div className="flex flex-row items-center gap-2">
<Button size="icon" variant="ghost" aria-label="Settings" className="size-7 p-0">
<LaIcon name="Settings" />
</Button>
<Link href="/">
<Button size="icon" variant="ghost" aria-label="Settings" className="size-7 p-0">
<LaIcon name="House" />
</Button>
</Link>
</div> */}
</div>
</div>
)
}

View File

@@ -1,100 +1,66 @@
import { useState, useEffect, useRef } from "react"
import { usePathname } from "next/navigation"
import Link from "next/link"
import { useState, useRef } from "react"
import { Button } from "@/components/ui/button"
import { ChevronDown, BookOpen, Bookmark, GraduationCap, Check } from "lucide-react"
import { LaIcon } from "@/components/custom/la-icon"
import { SidebarItem } from "../sidebar"
const TOPICS = ["Nix", "Javascript", "Kubernetes", "Figma", "Hiring", "Java", "IOS", "Design"]
// const TOPICS = ["Nix", "Javascript", "Kubernetes", "Figma", "Hiring", "Java", "IOS", "Design"]
export const TopicSection = () => {
const [showOptions, setShowOptions] = useState(false)
const [selectedStatus, setSelectedStatus] = useState<string | null>(null)
const sectionRef = useRef<HTMLDivElement>(null)
const learningOptions = [
{ text: "To Learn", icon: <Bookmark size={16} />, color: "text-white/70" },
{
text: "Learning",
icon: <GraduationCap size={16} />,
color: "text-[#D29752]"
text: "To Learn",
icon: <LaIcon name="NotebookPen" className="size-3 flex-shrink-0" />,
color: "text-black dark:text-white"
},
{
text: "Learned",
icon: <Check size={16} />,
color: "text-[#708F51]"
}
text: "Learning",
icon: <LaIcon name="GraduationCap" className="size-4 flex-shrink-0" />,
color: "text-[#D29752]"
},
{ text: "Learned", icon: <LaIcon name="Check" className="size-4 flex-shrink-0" />, color: "text-[#708F51]" }
]
const statusSelect = (status: string) => {
setSelectedStatus(status === "Show All" ? null : status)
setShowOptions(false)
setSelectedStatus(prevStatus => (prevStatus === status ? null : status))
}
useEffect(() => {
const overlayClick = (event: MouseEvent) => {
if (sectionRef.current && !sectionRef.current.contains(event.target as Node)) {
setShowOptions(false)
}
const topicCounts = {
"To Learn": 2,
Learning: 5,
Learned: 3,
get total() {
return this["To Learn"] + this.Learning + this.Learned
}
document.addEventListener("mousedown", overlayClick)
return () => {
document.removeEventListener("mousedown", overlayClick)
}
}, [])
const availableOptions = selectedStatus
? [
{
text: "Show All",
icon: <BookOpen size={16} />,
color: "text-white"
},
...learningOptions.filter(option => option.text !== selectedStatus)
]
: learningOptions
// const topicClick = (topic: string) => {
// router.push(`/${topic.toLowerCase()}`)
// }
}
return (
<div className="space-y-1 overflow-hidden" ref={sectionRef}>
<Button
onClick={() => setShowOptions(!showOptions)}
className="bg-accent text-foreground hover:bg-accent/50 flex w-full items-center justify-between rounded-md px-3 py-2 text-sm font-medium"
>
<span>{selectedStatus ? `Topics: ${selectedStatus}` : "Topics"}</span>
<ChevronDown
size={16}
className={`transform transition-transform duration-200 ease-in-out ${
showOptions ? "rotate-0" : "rotate-[-90deg]"
}`}
/>
</Button>
{showOptions && (
<div className="rounded-md bg-neutral-800">
{availableOptions.map(option => (
<Button
key={option.text}
onClick={() => statusSelect(option.text)}
className={`flex w-full items-center justify-start space-x-2 rounded-md px-3 py-2 text-sm font-medium hover:bg-neutral-700 ${option.color} bg-inherit`}
>
<div className="text-foreground group/topics hover:bg-accent flex w-full items-center justify-between rounded-md px-2 py-2 text-xs font-medium">
<span className="text-black dark:text-white">Topics {topicCounts.total}</span>
<button className="opacity-0 transition-opacity duration-200 group-hover/topics:opacity-100">
<LaIcon name="Ellipsis" className="size-4 flex-shrink-0" />
</button>
</div>
<div>
{learningOptions.map(option => (
<Button
key={option.text}
onClick={() => statusSelect(option.text)}
className={`flex w-full items-center justify-between rounded-md py-1 pl-1 text-sm font-medium hover:bg-neutral-100 dark:hover:bg-neutral-100/20 ${option.color} ${
selectedStatus === option.text ? "bg-accent" : "bg-inherit"
} shadow-none`}
>
<div className="flex items-center gap-2">
{option.icon && <span className={option.color}>{option.icon}</span>}
<span>{option.text}</span>
</Button>
))}
</div>
)}
<div className="scrollbar-hide space-y-1 overflow-y-auto" style={{ maxHeight: "calc(100vh - 200px)" }}>
{TOPICS.map(topic => (
<SidebarItem key={topic} label={topic} url={`/${topic}`} />
</div>
<span className={`${option.color} mr-2`}>{topicCounts[option.text as keyof typeof topicCounts]}</span>
</Button>
))}
</div>
</div>
)
}
export default TopicSection

View File

@@ -5,14 +5,14 @@ import Link from "next/link"
import { usePathname } from "next/navigation"
import { useMedia } from "react-use"
import { useAtom } from "jotai"
import { LinkIcon, SearchIcon } from "lucide-react"
import { SearchIcon } from "lucide-react"
import { Logo } from "@/components/custom/logo"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { isCollapseAtom } from "@/store/sidebar"
import { PageSection } from "./partial/page-section"
import { TopicSection } from "./partial/topic-section"
import { ProfileSection } from "./partial/profile-section"
interface SidebarContextType {
isCollapsed: boolean
@@ -73,14 +73,14 @@ export const SidebarItem: React.FC<SidebarItemProps> = React.memo(({ label, url,
const LogoAndSearch: React.FC = React.memo(() => {
const pathname = usePathname()
return (
<div className="px-3.5">
<div className="mb-1 mt-2 flex h-10 max-w-full items-center">
<Link href="/links" className="px-2">
<div className="px-3">
<div className="mt-2 flex h-10 max-w-full items-center">
<Link href="/" className="px-2">
<Logo className="size-7" />
</Link>
<div className="flex min-w-2 grow flex-row" />
{pathname === "/search" ? (
<Link href="/links">
<Link href="/">
<Button size="sm" variant="secondary" type="button" className="text-md text-primary/60 font-medium">
Back
</Button>
@@ -104,21 +104,20 @@ const LogoAndSearch: React.FC = React.memo(() => {
})
const SidebarContent: React.FC = React.memo(() => {
const { isCollapsed } = React.useContext(SidebarContext)
const isTablet = useMedia("(max-width: 1024px)")
return (
<nav className="bg-background relative flex h-full w-full shrink-0 flex-col">
<div className={cn({ "pt-12": !isCollapsed && isTablet })}>
<LogoAndSearch />
</div>
<div tabIndex={-1} className="relative mb-0.5 mt-1.5 flex grow flex-col overflow-y-auto rounded-md px-3.5">
<SidebarItem url="/links" label="Links" icon={<LinkIcon size={16} />} />
<div className="h-2 shrink-0" />
<PageSection />
<TopicSection />
</div>
</nav>
<>
<nav className="bg-background relative flex h-full w-full shrink-0 flex-col">
<div>
<LogoAndSearch />
</div>
<div tabIndex={-1} className="relative mb-0.5 mt-1.5 flex grow flex-col overflow-y-auto rounded-md px-3">
<div className="h-2 shrink-0" />
<PageSection />
<TopicSection />
</div>
</nav>
<ProfileSection />
</>
)
})
@@ -132,7 +131,7 @@ export const Sidebar: React.FC = () => {
)
const sidebarInnerClasses = cn(
"h-full w-auto min-w-56 transition-transform duration-300 ease-in-out",
"h-full w-56 min-w-56 transition-transform duration-300 ease-in-out",
isCollapsed ? "-translate-x-full" : "translate-x-0"
)

View File

@@ -0,0 +1,23 @@
import * as React from "react"
import BaseTextareaAutosize from "react-textarea-autosize"
import { TextareaAutosizeProps as BaseTextareaAutosizeProps } from "react-textarea-autosize"
import { cn } from "@/lib/utils"
export interface TextareaProps extends Omit<BaseTextareaAutosizeProps, "ref"> {}
const TextareaAutosize = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, style, ...props }, ref) => {
return (
<BaseTextareaAutosize
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[60px] w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
})
TextareaAutosize.displayName = "TextareaAutosize"
export { TextareaAutosize }