mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
active path sidebar
This commit is contained in:
@@ -3,11 +3,10 @@ import Link from "next/link"
|
|||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { useAccount } from "@/lib/providers/jazz-provider"
|
import { useAccount } from "@/lib/providers/jazz-provider"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { LaIcon } from "@/components/custom/la-icon"
|
import { LaIcon } from "@/components/custom/la-icon"
|
||||||
import { PersonalLinkLists } from "@/lib/schema/personal-link"
|
import { PersonalLinkLists } from "@/lib/schema/personal-link"
|
||||||
|
|
||||||
export const LinkSection: React.FC = () => {
|
export const LinkSection: React.FC<{ pathname: string }> = ({ pathname }) => {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: {
|
root: {
|
||||||
personalLinks: []
|
personalLinks: []
|
||||||
@@ -15,12 +14,13 @@ export const LinkSection: React.FC = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const linkCount = me?.root.personalLinks?.length || 0
|
const linkCount = me?.root.personalLinks?.length || 0
|
||||||
|
const isActive = pathname === "/"
|
||||||
|
|
||||||
if (!me) return null
|
if (!me) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group/pages flex flex-col gap-px py-2">
|
<div className="group/pages flex flex-col gap-px py-2">
|
||||||
<LinkSectionHeader linkCount={linkCount} />
|
<LinkSectionHeader linkCount={linkCount} isActive={isActive} />
|
||||||
<List personalLinks={me.root.personalLinks} />
|
<List personalLinks={me.root.personalLinks} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -28,18 +28,22 @@ export const LinkSection: React.FC = () => {
|
|||||||
|
|
||||||
interface LinkSectionHeaderProps {
|
interface LinkSectionHeaderProps {
|
||||||
linkCount: number
|
linkCount: number
|
||||||
|
isActive: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkSectionHeader: React.FC<LinkSectionHeaderProps> = ({ linkCount }) => (
|
const LinkSectionHeader: React.FC<LinkSectionHeaderProps> = ({ linkCount, isActive }) => (
|
||||||
<div
|
<div
|
||||||
className={cn("flex min-h-[30px] items-center gap-px rounded-md", "hover:bg-accent hover:text-accent-foreground")}
|
className={cn(
|
||||||
|
"flex min-h-[30px] items-center gap-px rounded-md",
|
||||||
|
isActive ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-6 flex-1 items-center justify-start rounded-md px-2 py-1",
|
"size-6 flex-1 items-center justify-start rounded-md px-2 py-1",
|
||||||
"focus-visible:outline-none focus-visible:ring-0",
|
"focus-visible:outline-none focus-visible:ring-0",
|
||||||
"hover:bg-accent hover:text-accent-foreground"
|
isActive ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p className="flex items-center text-xs font-medium">
|
<p className="flex items-center text-xs font-medium">
|
||||||
@@ -83,7 +87,6 @@ const ListItem: React.FC<ListItemProps> = ({ label, href, count, isActive }) =>
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate text-sm">
|
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate text-sm">
|
||||||
<LaIcon name="Link" className="flex-shrink-0 opacity-60" />
|
|
||||||
<p className={cn("truncate opacity-95 group-hover/topic-link:opacity-100")}>{label}</p>
|
<p className={cn("truncate opacity-95 group-hover/topic-link:opacity-100")}>{label}</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -46,20 +46,32 @@ const SHOWS: Option<ShowOption>[] = [
|
|||||||
const pageSortAtom = atomWithStorage<SortOption>("pageSort", "title")
|
const pageSortAtom = atomWithStorage<SortOption>("pageSort", "title")
|
||||||
const pageShowAtom = atomWithStorage<ShowOption>("pageShow", 5)
|
const pageShowAtom = atomWithStorage<ShowOption>("pageShow", 5)
|
||||||
|
|
||||||
export const PageSection: React.FC = () => {
|
export const PageSection: React.FC<{ pathname?: string }> = ({ pathname }) => {
|
||||||
const { me } = useAccount({ root: { personalPages: [] } })
|
const { me } = useAccount({
|
||||||
|
root: {
|
||||||
|
personalPages: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [sort, setSort] = useAtom(pageSortAtom)
|
||||||
|
const [show, setShow] = useAtom(pageShowAtom)
|
||||||
|
|
||||||
const pageCount = me?.root.personalPages?.length || 0
|
const pageCount = me?.root.personalPages?.length || 0
|
||||||
|
const isActive = pathname ? pathname.startsWith("/pages") : false
|
||||||
|
|
||||||
|
if (!me) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group/pages flex flex-col gap-px py-2">
|
<div className="group/pages flex flex-col gap-px py-2">
|
||||||
<PageSectionHeader pageCount={pageCount} />
|
<PageSectionHeader pageCount={pageCount} isActive={isActive} />
|
||||||
{me?.root.personalPages && <PageList personalPages={me.root.personalPages} />}
|
<PageList personalPages={me.root.personalPages} sort={sort} show={show} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PageSectionHeaderProps {
|
interface PageSectionHeaderProps {
|
||||||
pageCount: number
|
pageCount: number
|
||||||
|
isActive: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageSectionHeader: React.FC<PageSectionHeaderProps> = ({ pageCount }) => (
|
const PageSectionHeader: React.FC<PageSectionHeaderProps> = ({ pageCount }) => (
|
||||||
@@ -121,6 +133,8 @@ const NewPageButton: React.FC = () => {
|
|||||||
|
|
||||||
interface PageListProps {
|
interface PageListProps {
|
||||||
personalPages: PersonalPageLists
|
personalPages: PersonalPageLists
|
||||||
|
sort: SortOption
|
||||||
|
show: ShowOption
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageList: React.FC<PageListProps> = ({ personalPages }) => {
|
const PageList: React.FC<PageListProps> = ({ personalPages }) => {
|
||||||
@@ -250,4 +264,4 @@ const ShowAllForm: React.FC = () => {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { LaIcon } from "@/components/custom/la-icon"
|
|||||||
import { ListOfTopics } from "@/lib/schema"
|
import { ListOfTopics } from "@/lib/schema"
|
||||||
import { LEARNING_STATES, LearningStateValue } from "@/lib/constants"
|
import { LEARNING_STATES, LearningStateValue } from "@/lib/constants"
|
||||||
|
|
||||||
export const TopicSection: React.FC = () => {
|
export const TopicSection: React.FC<{ pathname: string }> = ({ pathname }) => {
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: {
|
root: {
|
||||||
topicsWantToLearn: [],
|
topicsWantToLearn: [],
|
||||||
@@ -22,11 +22,13 @@ export const TopicSection: React.FC = () => {
|
|||||||
(me?.root.topicsLearning?.length || 0) +
|
(me?.root.topicsLearning?.length || 0) +
|
||||||
(me?.root.topicsLearned?.length || 0)
|
(me?.root.topicsLearned?.length || 0)
|
||||||
|
|
||||||
|
const isActive = pathname.startsWith("/topics")
|
||||||
|
|
||||||
if (!me) return null
|
if (!me) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group/pages flex flex-col gap-px py-2">
|
<div className="group/pages flex flex-col gap-px py-2">
|
||||||
<TopicSectionHeader topicCount={topicCount} />
|
<TopicSectionHeader topicCount={topicCount} isActive={isActive} />
|
||||||
<List
|
<List
|
||||||
topicsWantToLearn={me.root.topicsWantToLearn}
|
topicsWantToLearn={me.root.topicsWantToLearn}
|
||||||
topicsLearning={me.root.topicsLearning}
|
topicsLearning={me.root.topicsLearning}
|
||||||
@@ -38,11 +40,15 @@ export const TopicSection: React.FC = () => {
|
|||||||
|
|
||||||
interface TopicSectionHeaderProps {
|
interface TopicSectionHeaderProps {
|
||||||
topicCount: number
|
topicCount: number
|
||||||
|
isActive: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopicSectionHeader: React.FC<TopicSectionHeaderProps> = ({ topicCount }) => (
|
const TopicSectionHeader: React.FC<TopicSectionHeaderProps> = ({ topicCount, isActive }) => (
|
||||||
<div
|
<div
|
||||||
className={cn("flex min-h-[30px] items-center gap-px rounded-md", "hover:bg-accent hover:text-accent-foreground")}
|
className={cn(
|
||||||
|
"flex min-h-[30px] items-center gap-px rounded-md",
|
||||||
|
isActive ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -131,4 +137,4 @@ const ListItem: React.FC<ListItemProps> = ({ label, value, href, count, isActive
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ const LogoAndSearch: React.FC = React.memo(() => {
|
|||||||
LogoAndSearch.displayName = "LogoAndSearch"
|
LogoAndSearch.displayName = "LogoAndSearch"
|
||||||
|
|
||||||
const SidebarContent: React.FC = React.memo(() => {
|
const SidebarContent: React.FC = React.memo(() => {
|
||||||
|
const pathname = usePathname()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav className="bg-background relative flex h-full w-full shrink-0 flex-col">
|
<nav className="bg-background relative flex h-full w-full shrink-0 flex-col">
|
||||||
@@ -117,9 +118,9 @@ const SidebarContent: React.FC = React.memo(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div tabIndex={-1} className="relative mb-0.5 mt-1.5 flex grow flex-col overflow-y-auto rounded-md px-3">
|
<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" />
|
<div className="h-2 shrink-0" />
|
||||||
<LinkSection />
|
<LinkSection pathname={pathname} />
|
||||||
<PageSection />
|
<PageSection pathname={pathname} />
|
||||||
<TopicSection />
|
<TopicSection pathname={pathname} />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<ProfileSection />
|
<ProfileSection />
|
||||||
|
|||||||
Reference in New Issue
Block a user