chore: Enhancement + New Feature (#185)

* wip

* wip page

* chore: style

* wip pages

* wip pages

* chore: toggle

* chore: link

* feat: topic search

* chore: page section

* refactor: apply tailwind class ordering

* fix: handle loggedIn user for guest route

* feat: folder & image schema

* chore: move utils to shared

* refactor: tailwind class ordering

* feat: img ext for editor

* refactor: remove qa

* fix: tanstack start

* fix: wrong import

* chore: use toast

* chore: schema
This commit is contained in:
Aslam
2024-10-18 21:18:20 +07:00
committed by GitHub
parent c93c634a77
commit a440828f8c
158 changed files with 2808 additions and 1064 deletions

View File

@@ -17,28 +17,28 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
console.error(error)
return (
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
<div className="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 p-4">
<ErrorComponent error={error} />
<div className="flex gap-2 items-center flex-wrap">
<div className="flex flex-wrap items-center gap-2">
<button
onClick={() => {
router.invalidate()
}}
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
className={`rounded bg-gray-600 px-2 py-1 font-extrabold uppercase text-white dark:bg-gray-700`}
>
Try Again
</button>
{isRoot ? (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
className={`rounded bg-gray-600 px-2 py-1 font-extrabold uppercase text-white dark:bg-gray-700`}
>
Home
</Link>
) : (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
className={`rounded bg-gray-600 px-2 py-1 font-extrabold uppercase text-white dark:bg-gray-700`}
onClick={(e) => {
e.preventDefault()
window.history.back()

View File

@@ -1,13 +1,13 @@
import * as React from "react"
import { useKeyDown, KeyFilter, Options } from "@/hooks/use-key-down"
import { useAccountOrGuest } from "@/lib/providers/jazz-provider"
import { isModKey, isServer } from "@/lib/utils"
import { useAtom } from "jotai"
import { usePageActions } from "~/hooks/actions/use-page-actions"
import { useAuth } from "@clerk/tanstack-start"
import { useNavigate } from "@tanstack/react-router"
import queryString from "query-string"
import { commandPaletteOpenAtom } from "~/store/any-store"
import { isModKey, isServer } from "@shared/utils"
type RegisterKeyDownProps = {
trigger: KeyFilter

View File

@@ -6,16 +6,16 @@ export function NotFound({ children }: { children?: any }) {
<div className="text-gray-600 dark:text-gray-400">
{children || <p>The page you are looking for does not exist.</p>}
</div>
<p className="flex items-center gap-2 flex-wrap">
<p className="flex flex-wrap items-center gap-2">
<button
onClick={() => window.history.back()}
className="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm"
className="rounded bg-emerald-500 px-2 py-1 text-sm font-black uppercase text-white"
>
Go back
</button>
<Link
to="/"
className="bg-cyan-600 text-white px-2 py-1 rounded uppercase font-black text-sm"
className="rounded bg-cyan-600 px-2 py-1 text-sm font-black uppercase text-white"
>
Start Over
</Link>

View File

@@ -42,7 +42,7 @@ export function Onboarding() {
</AlertDialogHeader>
<AlertDialogDescription asChild>
<div className="text-foreground/70 space-y-4 text-base leading-5">
<div className="space-y-4 text-base leading-5 text-foreground/70">
<p>
Learn Anything is a learning platform that organizes knowledge in
a social way. You can create pages, add links, track learning

View File

@@ -1,7 +1,7 @@
import { icons } from "lucide-react"
import { LaAccount } from "@/lib/schema"
import { HTMLLikeElement } from "@/lib/utils"
import { useCommandActions } from "~/hooks/use-command-actions"
import { useCommandActions } from "~/hooks/actions/use-command-actions"
export type CommandAction = string | (() => void)

View File

@@ -12,7 +12,7 @@ import { CommandGroup } from "./command-group"
import { CommandAction, createCommandGroups } from "./command-data"
import { useAccount, useAccountOrGuest } from "@/lib/providers/jazz-provider"
import { useAtom } from "jotai"
import { useCommandActions } from "~/hooks/use-command-actions"
import { useCommandActions } from "~/hooks/actions/use-command-actions"
import {
filterItems,
getTopics,

View File

@@ -15,7 +15,7 @@ export const ContentHeader = React.forwardRef<
return (
<header
className={cn(
"flex min-h-10 min-w-0 shrink-0 items-center gap-3 pl-8 pr-6 transition-opacity max-lg:px-4",
"flex min-h-10 min-w-0 shrink-0 items-center gap-3 border-b border-b-[var(--la-border-new)] px-6 py-3 transition-opacity max-lg:px-4",
className,
)}
ref={ref}
@@ -48,7 +48,7 @@ export const SidebarToggleButton: React.FC = () => {
size="icon"
variant="ghost"
aria-label="Menu"
className="text-primary/60"
className="-ml-2 cursor-default text-muted-foreground hover:bg-transparent"
onClick={handleClick}
>
<LaIcon name="PanelLeft" />

View File

@@ -66,7 +66,7 @@ export const LearningStateSelector: React.FC<LearningStateSelectorProps> = ({
type="button"
role="combobox"
variant="secondary"
className={cn("gap-x-2 text-sm", className)}
className={cn("h-7 gap-x-2 text-sm", className)}
>
{iconName && (
<LaIcon

View File

@@ -0,0 +1,71 @@
import { Link } from "@tanstack/react-router"
import { cn } from "@/lib/utils"
import { LaIcon } from "~/components/custom/la-icon"
import { icons } from "lucide-react"
import type { NavigateOptions } from "@tanstack/react-router"
interface NavItemProps extends NavigateOptions {
title: string
count: number
icon: keyof typeof icons
className?: string
}
export function NavItem({
title,
count,
icon,
className,
...linkProps
}: NavItemProps) {
return (
<Link
className={cn(
"group/p",
"flex h-[30px] cursor-default items-center gap-px rounded-md px-2 text-sm font-medium",
"text-[var(--less-foreground)] hover:bg-[var(--item-hover)] focus-visible:outline-none focus-visible:ring-0",
className,
)}
activeProps={{
className:
'bg-[var(--item-active)] data-[status="active"]:hover:bg-[var(--item-active)]',
}}
{...linkProps}
>
{({ isActive }) => (
<>
<div className="flex items-center gap-1.5">
<LaIcon
name={icon}
className={cn("group-hover/p:text-foreground", {
"text-foreground": isActive,
"text-muted-foreground": !isActive,
})}
/>
<span>{title}</span>
</div>
<span className="flex-grow" />
{count > 0 && <BadgeCount count={count} isActive={isActive} />}
</>
)}
</Link>
)
}
interface BadgeCountProps {
count: number
isActive: boolean
}
function BadgeCount({ count, isActive }: BadgeCountProps) {
return (
<span
className={cn("font-mono", {
"text-muted-foreground": !isActive,
"text-foreground": isActive,
})}
>
{count}
</span>
)
}

View File

@@ -10,7 +10,7 @@ const TextareaAutosize = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
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",
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}

View File

@@ -0,0 +1,27 @@
import * as React from "react"
import { cn } from "~/lib/utils"
export const ArrowIcon = ({
className,
...props
}: React.SVGProps<SVGSVGElement>) => {
return (
<svg
width="12"
height="12"
fill="none"
viewBox="0 0 24 24"
className={cn(className)}
{...props}
>
<path
d="M18.1125 12.4381C18.4579 12.2481 18.4579 11.7519 18.1125 11.5619L8.74096 6.40753C8.40773 6.22425 8 6.46533 8 6.84564V17.1544C8 17.5347 8.40773 17.7757 8.74096 17.5925L18.1125 12.4381Z"
fill="currentColor"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</svg>
)
}

View File

@@ -66,7 +66,7 @@ const ShortcutKey: React.FC<{ keyChar: string }> = ({ keyChar }) => (
const ShortcutItem: React.FC<ShortcutItem> = ({ label, keys, then }) => (
<div className="flex flex-row items-center gap-2">
<dt className="flex grow items-center">
<span className="text-muted-foreground text-left text-sm">{label}</span>
<span className="text-left text-sm text-muted-foreground">{label}</span>
</dt>
<dd className="flex items-end">
<span className="text-left">
@@ -79,7 +79,7 @@ const ShortcutItem: React.FC<ShortcutItem> = ({ label, keys, then }) => (
))}
{then && (
<>
<span className="text-muted-foreground text-xs">then</span>
<span className="text-xs text-muted-foreground">then</span>
{then.map((key, index) => (
<ShortcutKey key={`then-${index}`} keyChar={key} />
))}
@@ -149,7 +149,7 @@ export function Shortcut() {
"size-6 p-0",
)}
>
<LaIcon name="X" className="text-muted-foreground size-5" />
<LaIcon name="X" className="size-5 text-muted-foreground" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</header>
@@ -158,12 +158,12 @@ export function Shortcut() {
<form className="relative flex items-center">
<LaIcon
name="Search"
className="text-muted-foreground absolute left-3 size-4"
className="absolute left-3 size-4 text-muted-foreground"
/>
<Input
autoFocus
placeholder="Search shortcuts"
className="border-muted-foreground/50 focus-visible:border-muted-foreground h-10 pl-10 focus-visible:ring-0"
className="h-10 border-muted-foreground/50 pl-10 focus-visible:border-muted-foreground focus-visible:ring-0"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>

View File

@@ -109,7 +109,7 @@ export function Feedback() {
<DialogOverlay />
<DialogPrimitive.Content
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
"flex flex-col p-4 sm:max-w-2xl",
)}
>
@@ -134,7 +134,7 @@ export function Feedback() {
{...field}
throttleDelay={500}
className={cn(
"border-muted-foreground/40 focus-within:border-muted-foreground/80 min-h-52 rounded-lg",
"min-h-52 rounded-lg border-muted-foreground/40 focus-within:border-muted-foreground/80",
{
"border-destructive focus-within:border-destructive":
form.formState.errors.content,

View File

@@ -75,7 +75,7 @@ const JournalSectionHeader: React.FC<JournalHeaderProps> = ({
<p className="text-xs">
Journal
{entriesCount > 0 && (
<span className="text-muted-foreground ml-1">({entriesCount})</span>
<span className="ml-1 text-muted-foreground">({entriesCount})</span>
)}
</p>
</Link>
@@ -104,7 +104,7 @@ const JournalEntryItem: React.FC<JournalEntryItemProps> = ({ entry }) => (
href={`/journal/${entry.id}`}
className="group/journal-entry relative flex min-w-0 flex-1"
>
<div className="relative flex h-8 w-full items-center gap-2 rounded-md p-1.5 font-medium">
<div className="relative flex h-[30px] w-full items-center gap-2 rounded-md p-1.5 font-medium">
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate text-sm">
<LaIcon name="FileText" className="opacity-60" />
<p

View File

@@ -0,0 +1,28 @@
import * as React from "react"
import { useAccount } from "@/lib/providers/jazz-provider"
import { NavItem } from "~/components/custom/nav-item"
export const LinkCollection: React.FC = () => {
const { me } = useAccount({
root: {
personalLinks: [],
topicsWantToLearn: [],
topicsLearning: [],
topicsLearned: [],
},
})
const linkCount = me?.root.personalLinks?.length || 0
const topicCount =
(me?.root.topicsWantToLearn?.length || 0) +
(me?.root.topicsLearning?.length || 0) +
(me?.root.topicsLearned?.length || 0)
return (
<div className="flex flex-col gap-px py-2">
<NavItem to="/links" title="Links" icon="Link" count={linkCount} />
<NavItem to="/topics" title="Topics" icon="Hash" count={topicCount} />
</div>
)
}

View File

@@ -4,6 +4,7 @@ import { useAccount } from "@/lib/providers/jazz-provider"
import { cn } from "@/lib/utils"
import { PersonalLinkLists } from "@/lib/schema/personal-link"
import { LearningStateValue } from "~/lib/constants"
import { LaIcon } from "~/components/custom/la-icon"
export const LinkSection: React.FC = () => {
const { me } = useAccount({ root: { personalLinks: [] } })
@@ -13,7 +14,7 @@ export const LinkSection: React.FC = () => {
const linkCount = me.root.personalLinks?.length || 0
return (
<div className="group/pages flex flex-col gap-px py-2">
<div className="flex flex-col gap-px py-2">
<LinkSectionHeader linkCount={linkCount} />
<LinkList personalLinks={me.root.personalLinks} />
</div>
@@ -24,22 +25,41 @@ interface LinkSectionHeaderProps {
linkCount: number
}
const LinkSectionHeader: React.FC<LinkSectionHeaderProps> = ({ linkCount }) => (
<Link
to="/links"
className={cn(
"flex h-9 items-center gap-px rounded-md px-2 py-1 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 sm:h-[30px] sm:text-xs",
)}
activeProps={{
className: "bg-accent text-accent-foreground",
}}
>
Links
{linkCount > 0 && (
<span className="text-muted-foreground ml-1">{linkCount}</span>
)}
</Link>
)
const LinkSectionHeader: React.FC<LinkSectionHeaderProps> = ({ linkCount }) => {
return (
<Link
to="/links"
className={cn(
"flex h-[30px] items-center gap-px rounded-md px-2 text-sm font-medium hover:bg-[var(--item-hover)] focus-visible:outline-none focus-visible:ring-0",
)}
activeProps={{
className:
"bg-[var(--item-active)] data-[status='active']:hover:bg-[var(--item-active)]",
}}
>
{({ isActive }) => {
return (
<>
<div className="flex items-center gap-1.5">
<LaIcon name="Link" className="" />
<span>Links</span>
</div>
<span className="flex flex-auto"></span>
{linkCount > 0 && (
<span
className={cn("font-mono text-muted-foreground", {
"text-foreground": isActive,
})}
>
{linkCount}
</span>
)}
</>
)
}}
</Link>
)
}
interface LinkListProps {
personalLinks: PersonalLinkLists
@@ -87,29 +107,34 @@ interface LinkListItemProps {
}
const LinkListItem: React.FC<LinkListItemProps> = ({ label, state, count }) => (
<div className="group/reorder-page relative">
<div className="group/topic-link relative flex min-w-0 flex-1">
<Link
to="/links"
search={{ state }}
className={cn(
"relative flex h-9 w-full items-center gap-2 rounded-md p-1.5 font-medium hover:bg-accent hover:text-accent-foreground sm:h-8",
)}
activeProps={{
className: "bg-accent text-accent-foreground",
}}
>
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate text-sm">
<p className="truncate opacity-95 group-hover/topic-link:opacity-100">
{label}
</p>
</div>
</Link>
{count > 0 && (
<span className="absolute right-2 top-1/2 z-[1] -translate-y-1/2 rounded p-1 text-sm">
{count}
</span>
<div className="relative flex min-w-0 flex-1">
<Link
to="/links"
search={{ state }}
className={cn(
"relative flex h-[30px] w-full items-center gap-2 rounded-md px-1.5 text-sm font-medium hover:bg-[var(--item-hover)]",
)}
</div>
activeProps={{
className:
"bg-[var(--item-active)] data-[status='active']:hover:bg-[var(--item-active)]",
}}
>
{({ isActive }) => (
<>
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate">
<p className="truncate">{label}</p>
</div>
{count > 0 && (
<span
className={cn("font-mono text-muted-foreground", {
"text-foreground": isActive,
})}
>
{count}
</span>
)}
</>
)}
</Link>
</div>
)

View File

@@ -20,6 +20,7 @@ import {
} from "@/components/ui/dropdown-menu"
import { usePageActions } from "~/hooks/actions/use-page-actions"
import { icons } from "lucide-react"
import { ArrowIcon } from "~/components/icons/arrow-icon"
type SortOption = "title" | "recent"
type ShowOption = 5 | 10 | 15 | 20 | 0
@@ -44,56 +45,82 @@ const SHOWS: Option<ShowOption>[] = [
const pageSortAtom = atomWithStorage<SortOption>("pageSort", "title")
const pageShowAtom = atomWithStorage<ShowOption>("pageShow", 5)
const isExpandedAtom = atomWithStorage("isPageSectionExpanded", true)
export const PageSection: React.FC = () => {
const { me } = useAccount({
root: {
personalPages: [],
},
})
const { me } = useAccount({ root: { personalPages: [] } })
const [sort] = useAtom(pageSortAtom)
const [show] = useAtom(pageShowAtom)
const [isExpanded, setIsExpanded] = useAtom(isExpandedAtom)
if (!me) return null
const pageCount = me.root.personalPages?.length || 0
return (
<div className="group/pages flex flex-col gap-px py-2">
<PageSectionHeader pageCount={pageCount} />
<PageList personalPages={me.root.personalPages} sort={sort} show={show} />
<div className="flex flex-col gap-px py-2">
<PageSectionHeader
pageCount={pageCount}
isExpanded={isExpanded}
onToggle={() => setIsExpanded(!isExpanded)}
/>
{isExpanded && (
<PageList
personalPages={me.root.personalPages}
sort={sort}
show={show}
/>
)}
</div>
)
}
interface PageSectionHeaderProps {
pageCount: number
isExpanded: boolean
onToggle: () => void
}
const PageSectionHeader: React.FC<PageSectionHeaderProps> = ({ pageCount }) => (
<Link
to="/pages"
const PageSectionHeader: React.FC<PageSectionHeaderProps> = ({
pageCount,
isExpanded,
onToggle,
}) => (
<div
className={cn(
"flex h-9 flex-1 items-center justify-start gap-px rounded-md px-2 py-1",
"hover:bg-accent hover:text-accent-foreground sm:h-[30px]",
"group/pages",
"relative flex h-7 cursor-default items-center gap-px rounded-md py-0 font-medium text-muted-foreground hover:bg-[var(--item-hover)] focus-visible:outline-none focus-visible:ring-0",
)}
activeProps={{
className: "bg-accent text-accent-foreground",
}}
>
<div className="flex grow items-center justify-between">
<p className="text-sm sm:text-xs">
Pages
{pageCount > 0 && (
<span className="text-muted-foreground ml-1">{pageCount}</span>
)}
</p>
<Button
variant="ghost"
className="h-7 w-full justify-start gap-1 px-2 py-0 text-xs hover:bg-inherit"
onClick={onToggle}
>
<span>Pages</span>
{pageCount > 0 && <span className="text-xs">({pageCount})</span>}
<ArrowIcon
className={cn("size-3 transition-transform duration-200 ease-in-out", {
"rotate-90": isExpanded,
"opacity-0 group-hover/pages:opacity-100": !isExpanded,
})}
/>
</Button>
<div
className={cn(
"absolute right-1 top-1/2 -translate-y-1/2",
"transition-all duration-200 ease-in-out",
{
"opacity-100": isExpanded,
},
)}
>
<div className="flex items-center gap-px">
<ShowAllForm />
<NewPageButton />
</div>
</div>
</Link>
</div>
)
const NewPageButton: React.FC = () => {
@@ -122,11 +149,11 @@ const NewPageButton: React.FC = () => {
variant="ghost"
aria-label="New Page"
className={cn(
"flex size-5 items-center justify-center p-0.5 shadow-none",
"hover:bg-accent-foreground/10",
"flex size-5 cursor-default items-center justify-center p-0.5 shadow-none",
"text-muted-foreground hover:bg-inherit hover:text-foreground",
"opacity-0 transition-opacity duration-200",
"group-hover/pages:opacity-100 group-has-[[data-state='open']]/pages:opacity-100",
"data-[state='open']:opacity-100 focus-visible:outline-none focus-visible:ring-0",
"focus-visible:outline-none focus-visible:ring-0 data-[state='open']:opacity-100",
)}
onClick={handleClick}
>
@@ -168,29 +195,31 @@ interface PageListItemProps {
const PageListItem: React.FC<PageListItemProps> = ({ page }) => {
return (
<div className="group/reorder-page relative">
<div className="group/sidebar-link relative flex min-w-0 flex-1">
<Link
to="/pages/$pageId"
params={{ pageId: page.id }}
className={cn(
"relative flex h-9 w-full items-center gap-2 rounded-md p-1.5 font-medium sm:h-8",
"group-hover/sidebar-link:bg-accent group-hover/sidebar-link:text-accent-foreground",
)}
activeOptions={{ exact: true }}
activeProps={{
className: "bg-accent text-accent-foreground",
}}
>
<div className="flex max-w-[calc(100%-1rem)] flex-1 items-center gap-1.5 truncate text-sm">
<LaIcon name="FileText" className="flex-shrink-0 opacity-60" />
<p className="truncate opacity-95 group-hover/sidebar-link:opacity-100">
{page.title || "Untitled"}
</p>
</div>
</Link>
</div>
</div>
<Link
to="/pages/$pageId"
params={{ pageId: page.id }}
className={cn(
"group/p cursor-default text-[var(--less-foreground)]",
"relative flex h-[30px] w-full items-center gap-2 rounded-md px-1.5 text-sm font-medium hover:bg-[var(--item-hover)]",
)}
activeProps={{
className:
"bg-[var(--item-active)] data-[status='active']:hover:bg-[var(--item-active)]",
}}
>
{({ isActive }) => (
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate">
<LaIcon
name="File"
className={cn("flex-shrink-0 group-hover/p:text-foreground", {
"text-foreground": isActive,
"text-muted-foreground": !isActive,
})}
/>
<p className="truncate">{page.title || "Untitled"}</p>
</div>
)}
</Link>
)
}
@@ -212,11 +241,11 @@ const SubMenu = <T extends string | number>({
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<span className="flex items-center gap-2">
<LaIcon name={icon} />
<LaIcon name={icon} className="" />
<span>{label}</span>
</span>
<span className="ml-auto flex items-center gap-1">
<span className="text-muted-foreground text-xs">
<span className="text-sm text-muted-foreground">
{options.find((option) => option.value === currentValue)?.label}
</span>
<LaIcon name="ChevronRight" />
@@ -251,11 +280,11 @@ const ShowAllForm: React.FC = () => {
variant="ghost"
size="sm"
className={cn(
"flex size-5 items-center justify-center p-0.5 shadow-none",
"hover:bg-accent-foreground/10",
"flex size-5 cursor-default items-center justify-center p-0.5 shadow-none",
"text-muted-foreground hover:bg-inherit hover:text-foreground",
"opacity-0 transition-opacity duration-200",
"group-hover/pages:opacity-100 group-has-[[data-state='open']]/pages:opacity-100",
"data-[state='open']:opacity-100 focus-visible:outline-none focus-visible:ring-0",
"focus-visible:outline-none focus-visible:ring-0 data-[state='open']:opacity-100",
)}
>
<LaIcon name="Ellipsis" />

View File

@@ -56,6 +56,7 @@ export const ProfileSection: React.FC = () => {
signOut={signOut}
setShowShortcut={setShowShortcut}
/>
<span className="flex flex-auto"></span>
<Feedback />
</div>
</div>
@@ -83,12 +84,12 @@ const ProfileDropdown: React.FC<ProfileDropdownProps> = ({
<Button
variant="ghost"
aria-label="Profile"
className="hover:bg-accent focus-visible:ring-ring hover:text-accent-foreground flex h-auto items-center gap-1.5 truncate rounded py-1 pl-1 pr-1.5 focus-visible:outline-none focus-visible:ring-1"
className="flex h-auto items-center gap-1.5 truncate rounded py-1 pl-1 pr-1.5 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
<Avatar className="size-6">
<AvatarImage src={user.imageUrl} alt={user.fullName || ""} />
</Avatar>
<span className="truncate text-left text-sm font-medium -tracking-wider">
<span className="truncate text-left text-sm font-medium -tracking-wide">
{user.fullName}
</span>
<LaIcon

View File

@@ -93,7 +93,7 @@ const TaskSectionHeader: React.FC<TaskSectionHeaderProps> = ({
<Link
to="/tasks"
className={cn(
"flex flex-1 min-h-[30px] gap-px items-center justify-start hover:bg-accent hover:text-accent-foreground rounded-md px-2 py-1 focus-visible:outline-none focus-visible:ring-0",
"flex min-h-[30px] flex-1 items-center justify-start gap-px rounded-md px-2 py-1 hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0",
)}
search={{ filter }}
activeProps={{
@@ -104,7 +104,7 @@ const TaskSectionHeader: React.FC<TaskSectionHeaderProps> = ({
<p className="text-sm">
{title}
{count > 0 && <span className="text-muted-foreground ml-1">{count}</span>}
{count > 0 && <span className="ml-1 text-muted-foreground">{count}</span>}
</p>
</Link>
)

View File

@@ -1,142 +0,0 @@
import * as React from "react"
import { useAccount } from "@/lib/providers/jazz-provider"
import { cn } from "@/lib/utils"
import { LaIcon } from "@/components/custom/la-icon"
import { ListOfTopics } from "@/lib/schema"
import { LEARNING_STATES, LearningStateValue } from "@/lib/constants"
import { Link } from "@tanstack/react-router"
export const TopicSection: React.FC = () => {
const { me } = useAccount({
root: {
topicsWantToLearn: [],
topicsLearning: [],
topicsLearned: [],
},
})
const topicCount =
(me?.root.topicsWantToLearn?.length || 0) +
(me?.root.topicsLearning?.length || 0) +
(me?.root.topicsLearned?.length || 0)
if (!me) return null
return (
<div className="group/topics flex flex-col gap-px py-2">
<TopicSectionHeader topicCount={topicCount} />
<List
topicsWantToLearn={me.root.topicsWantToLearn}
topicsLearning={me.root.topicsLearning}
topicsLearned={me.root.topicsLearned}
/>
</div>
)
}
interface TopicSectionHeaderProps {
topicCount: number
}
const TopicSectionHeader: React.FC<TopicSectionHeaderProps> = ({
topicCount,
}) => (
<Link
to="/topics"
className="flex h-9 items-center gap-px rounded-md px-2 py-1 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 sm:h-[30px] sm:text-xs"
activeProps={{
className: "bg-accent text-accent-foreground",
}}
>
<p className="text-sm sm:text-xs">
Topics
{topicCount > 0 && (
<span className="text-muted-foreground ml-1">{topicCount}</span>
)}
</p>
</Link>
)
interface ListProps {
topicsWantToLearn: ListOfTopics
topicsLearning: ListOfTopics
topicsLearned: ListOfTopics
}
const List: React.FC<ListProps> = ({
topicsWantToLearn,
topicsLearning,
topicsLearned,
}) => {
return (
<div className="flex flex-col gap-px">
<ListItem
key={topicsWantToLearn.id}
count={topicsWantToLearn.length}
label="To Learn"
value="wantToLearn"
/>
<ListItem
key={topicsLearning.id}
label="Learning"
value="learning"
count={topicsLearning.length}
/>
<ListItem
key={topicsLearned.id}
label="Learned"
value="learned"
count={topicsLearned.length}
/>
</div>
)
}
interface ListItemProps {
label: string
value: LearningStateValue
count: number
}
const ListItem: React.FC<ListItemProps> = ({ label, value, count }) => {
const le = LEARNING_STATES.find((l) => l.value === value)
if (!le) return null
return (
<div className="group/reorder-page relative">
<div className="group/topic-link relative flex min-w-0 flex-1">
<Link
to="/topics"
search={{ learningState: value }}
className={cn(
"group-hover/topic-link:bg-accent relative flex h-9 w-full items-center gap-2 rounded-md p-1.5 font-medium sm:h-8",
le.className,
)}
activeOptions={{ exact: true }}
activeProps={{
className: "bg-accent text-accent-foreground",
}}
>
<div className="flex max-w-full flex-1 items-center gap-1.5 truncate text-sm">
<LaIcon name={le.icon} className="flex-shrink-0 opacity-60" />
<p
className={cn(
"truncate opacity-95 group-hover/topic-link:opacity-100",
le.className,
)}
>
{label}
</p>
</div>
</Link>
{count > 0 && (
<span className="absolute right-2 top-1/2 z-[1] -translate-y-1/2 rounded p-1 text-sm">
{count}
</span>
)}
</div>
</div>
)
}

View File

@@ -1,7 +1,6 @@
import * as React from "react"
import { useMedia } from "@/hooks/use-media"
import { useAtom } from "jotai"
import { LogoIcon } from "@/components/icons/logo-icon"
import { buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { isCollapseAtom } from "@/store/sidebar"
@@ -9,12 +8,12 @@ import { useAccountOrGuest } from "@/lib/providers/jazz-provider"
import { LaIcon } from "@/components/custom/la-icon"
import { Link, useLocation } from "@tanstack/react-router"
import { LinkSection } from "./partials/link-section"
// import { LinkSection } from "./partials/link-section"
import { PageSection } from "./partials/page-section"
import { TopicSection } from "./partials/topic-section"
import { ProfileSection } from "./partials/profile-section"
import { JournalSection } from "./partials/journal-section"
import { TaskSection } from "./partials/task-section"
import { LinkCollection } from "./partials/link-collection"
interface SidebarContextType {
isCollapsed: boolean
@@ -64,14 +63,14 @@ const SidebarItem: React.FC<SidebarItemProps> = React.memo(
)}
>
<Link
className="text-secondary-foreground flex h-8 grow items-center truncate rounded-md pl-1.5 pr-1 text-sm font-medium"
className="flex h-8 grow items-center truncate rounded-md pl-1.5 pr-1 text-sm font-medium text-secondary-foreground"
to={url}
onClick={onClick}
>
{icon && (
<span
className={cn(
"text-primary/60 group-hover:text-primary mr-2 size-4",
"mr-2 size-4 text-primary/60 group-hover:text-primary",
{ "text-primary": isActive },
)}
>
@@ -102,7 +101,7 @@ const LogoAndSearch: React.FC = React.memo(() => {
to={pathname === "/search" ? "/" : "/search"}
className={cn(
buttonVariants({ size: "sm", variant: "secondary" }),
"text-primary/60 flex w-20 items-center justify-start py-4 pl-2",
"flex w-20 items-center justify-start py-4 pl-2 text-primary/60",
)}
activeProps={{
className: "text-md font-medium",
@@ -126,14 +125,14 @@ const SidebarContent: React.FC = React.memo(() => {
const { me } = useAccountOrGuest()
return (
<nav className="bg-background relative flex h-full w-full shrink-0 flex-col">
<nav className="relative flex h-full w-full shrink-0 flex-col bg-[var(--body-background)]">
<div>
<LogoAndSearch />
</div>
<div className="relative mb-0.5 mt-1.5 flex grow flex-col overflow-y-auto rounded-md px-3 outline-none">
<div className="h-2 shrink-0" />
{me._type === "Account" && <LinkSection />}
{me._type === "Account" && <TopicSection />}
{me._type === "Account" && <LinkCollection />}
{/* {me._type === "Account" && <LinkSection />} */}
{me._type === "Account" && <JournalSection />}
{me._type === "Account" && <TaskSection />}
{me._type === "Account" && <PageSection />}
@@ -156,7 +155,7 @@ const Sidebar: React.FC = () => {
)
const sidebarInnerClasses = cn(
"h-full w-56 min-w-56 transition-transform duration-300 ease-in-out",
"h-full w-60 min-w-60 transition-transform duration-300 ease-in-out",
isCollapsed ? "-translate-x-full" : "translate-x-0",
)
@@ -184,7 +183,7 @@ const Sidebar: React.FC = () => {
)}
>
<div
className={cn(sidebarInnerClasses, "border-r-primary/5 border-r")}
className={cn(sidebarInnerClasses, "border-r border-r-primary/5")}
>
<SidebarContext.Provider value={contextValue}>
<SidebarContent />

View File

@@ -19,7 +19,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}

View File

@@ -19,7 +19,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}