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

@@ -124,7 +124,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<React.Suspense>
<TanStackRouterDevtools position="bottom-right" />
<ReactQueryDevtools buttonPosition="bottom-left" />
<ReactQueryDevtools buttonPosition="bottom-right" />
</React.Suspense>
<ScrollRestoration />

View File

@@ -8,7 +8,7 @@ export const Route = createFileRoute("/_layout")({
function LayoutComponent() {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
<ClerkProvider>
<Outlet />
</ClerkProvider>

View File

@@ -1,6 +1,11 @@
import { createFileRoute, Outlet } from "@tanstack/react-router"
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
export const Route = createFileRoute("/_layout/(auth)/_auth")({
beforeLoad({ context }) {
if (context.auth) {
throw redirect({ to: "/links", replace: true })
}
},
component: () => (
<main className="h-full">
<Outlet />

View File

@@ -104,10 +104,13 @@ export function Autocomplete({
return (
<Command
className={cn("relative mx-auto max-w-md overflow-visible shadow-md", {
"rounded-lg border": !open,
"rounded-none rounded-t-lg border-l border-r border-t": open,
})}
className={cn(
"relative mx-auto max-w-md overflow-visible bg-background shadow-md",
{
"rounded-lg border": !open,
"rounded-none rounded-t-lg border-l border-r border-t": open,
},
)}
>
<div className="relative flex items-center">
<CommandPrimitive.Input
@@ -125,7 +128,7 @@ export function Autocomplete({
}}
placeholder={filteredTopics[0]?.prettyName}
className={cn(
"placeholder:text-muted-foreground flex-1 bg-transparent min-h-10 px-3 py-1 sm:py-3 sm:px-4 outline-none",
"min-h-10 flex-1 bg-transparent px-3 py-1 outline-none placeholder:text-muted-foreground sm:px-4 sm:py-3",
)}
autoFocus
/>
@@ -138,7 +141,7 @@ export function Autocomplete({
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.1 }}
className="bg-background absolute left-0 right-0 z-10 -mx-px rounded-b-lg border-b border-l border-r border-t shadow-lg"
className="absolute left-0 right-0 z-10 -mx-px rounded-b-lg border-b border-l border-r border-t bg-background shadow-lg"
>
<CommandList className="max-h-56">
<CommandGroup className="my-2">
@@ -150,7 +153,7 @@ export function Autocomplete({
className="min-h-10 rounded-none px-3 py-1.5"
>
<span>{topic.prettyName}</span>
<span className="text-muted-foreground/80 ml-auto text-xs">
<span className="ml-auto text-xs text-muted-foreground/80">
{topic.connectedTopics.length > 0 &&
topic.connectedTopics.join(", ")}
</span>

View File

@@ -25,7 +25,7 @@ function LandingComponent() {
}
return (
<div className="relative h-full w-screen">
<div className="relative h-full w-screen bg-background">
<ForceGraphClient
raw_nodes={GraphData}
onNodeClick={handleTopicSelect}
@@ -40,7 +40,7 @@ function LandingComponent() {
>
<motion.h1
className={cn(
"mb-2 text-center text-5xl font-bold tracking-tight sm:mb-4 md:text-7xl font-raleway",
"mb-2 text-center font-raleway text-5xl font-bold tracking-tight sm:mb-4 md:text-7xl",
)}
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}

View File

@@ -33,7 +33,7 @@ function LayoutContent() {
return (
<>
<Toaster expand={false} />
<div className="flex min-h-full size-full flex-row items-stretch overflow-hidden">
<div className="flex size-full min-h-full flex-row items-stretch overflow-hidden">
<Sidebar />
<Shortcut />
<GlobalKeyboardHandler />
@@ -49,7 +49,7 @@ function LayoutContent() {
function MainContent() {
return (
<div className="relative flex min-w-0 flex-1 flex-col">
<main className="relative flex flex-auto flex-col place-items-stretch overflow-auto lg:my-2 lg:mr-2 lg:rounded-md lg:border">
<main className="relative flex flex-auto flex-col place-items-stretch overflow-auto bg-[var(--container-background)] lg:my-2 lg:mr-2 lg:rounded-md lg:border">
<Outlet />
</main>
</div>

View File

@@ -16,7 +16,6 @@ export const Route = createFileRoute("/_layout/_pages/(topic)/$")({
export const openPopoverForIdAtom = atom<string | null>(null)
export function TopicDetailComponent() {
console.log("TopicDetailComponent")
const params = useParams({ from: "/_layout/_pages/$" })
const { me } = useAccountOrGuest({ root: { personalLinks: [] } })
@@ -30,34 +29,65 @@ export function TopicDetailComponent() {
latestGlobalGuide: { sections: [] },
})
const [activeIndex, setActiveIndex] = React.useState(-1)
const [searchQuery, setSearchQuery] = React.useState("")
const topicExists = GraphData.find((node) => {
return node.name === params._splat
})
const topicExists = React.useMemo(
() => GraphData.find((node) => node.name === params._splat),
[params._splat],
)
const latestGlobalGuide = React.useMemo(
() => topic?.latestGlobalGuide,
[topic?.latestGlobalGuide],
)
const flattenedItems = React.useMemo(
() =>
latestGlobalGuide?.sections.flatMap((section) => [
{ type: "section" as const, data: section },
...(section?.links?.map((link) => ({
type: "link" as const,
data: link,
})) || []),
]) || [],
[latestGlobalGuide],
)
const filteredItems = React.useMemo(() => {
if (!searchQuery) return flattenedItems
return flattenedItems.filter((item) => {
if (item.type === "section") {
return item.data?.title
.toLowerCase()
.includes(searchQuery.toLowerCase())
}
if (item.type === "link") {
return (
item.data?.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.data?.url.toLowerCase().includes(searchQuery.toLowerCase())
)
}
return false
})
}, [flattenedItems, searchQuery])
if (!topicExists) {
return <NotFoundPlaceholder />
}
const flattenedItems = topic?.latestGlobalGuide?.sections.flatMap(
(section) => [
{ type: "section" as const, data: section },
...(section?.links?.map((link) => ({
type: "link" as const,
data: link,
})) || []),
],
)
if (!topic || !me || !flattenedItems) {
if (!topic || !me) {
return <TopicDetailSkeleton />
}
return (
<>
<TopicDetailHeader topic={topic} />
<TopicDetailHeader
topic={topic}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
/>
<TopicDetailList
items={flattenedItems}
items={filteredItems}
topic={topic}
activeIndex={activeIndex}
setActiveIndex={setActiveIndex}
@@ -88,7 +118,7 @@ function TopicDetailSkeleton() {
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-6 w-48" />
</div>
<Skeleton className="h-9 w-36" />
<Skeleton className="h-7 w-28" />
</div>
<div className="space-y-4 p-6 max-lg:px-4">

View File

@@ -10,13 +10,19 @@ import { LearningStateValue } from "@/lib/constants"
import { useMedia } from "@/hooks/use-media"
import { useClerk } from "@clerk/tanstack-start"
import { useLocation } from "@tanstack/react-router"
import { Input } from "~/components/ui/input"
import { LaIcon } from "~/components/custom/la-icon"
interface TopicDetailHeaderProps {
topic: Topic
searchQuery: string
setSearchQuery: (query: string) => void
}
export const TopicDetailHeader = React.memo(function TopicDetailHeader({
topic,
searchQuery,
setSearchQuery,
}: TopicDetailHeaderProps) {
const clerk = useClerk()
const { pathname } = useLocation()
@@ -111,28 +117,51 @@ export const TopicDetailHeader = React.memo(function TopicDetailHeader({
topicLists[learningState]?.push(topic)
}
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value)
}
return (
<ContentHeader className="px-6 py-5 max-lg:px-4">
<div className="flex min-w-0 flex-1 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 min-w-0 flex-1 items-center">
<h1 className="truncate text-left font-bold lg:text-xl">
{topic.prettyName}
</h1>
<>
<ContentHeader>
<div className="flex min-w-0 flex-1 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 min-w-0 flex-1 items-center">
<h1 className="truncate text-left font-semibold lg:text-lg">
{topic.prettyName}
</h1>
</div>
</div>
<div className="flex flex-auto"></div>
{/* <GuideCommunityToggle topicName={topic.name} /> */}
<LearningStateSelector
showSearch={false}
value={p?.learningState || ""}
onChange={handleAddToProfile}
defaultLabel={isMobile ? "" : "Add to profile"}
defaultIcon="Circle"
/>
</ContentHeader>
<div className="flex min-h-10 flex-row items-center justify-between border-b border-b-[var(--la-border-new)] px-6 py-2 max-lg:px-4">
<div className="flex flex-1 flex-row items-center gap-2">
<span className="text-tertiary flex h-5 w-5 items-center justify-center">
<LaIcon name="Search" className="text-muted-foreground" />
</span>
<Input
className="h-6 flex-1 border-none bg-transparent p-0 focus-visible:ring-0"
placeholder="Search..."
role="searchbox"
autoComplete="off"
autoCorrect="off"
spellCheck={false}
value={searchQuery}
onChange={handleSearchChange}
/>
</div>
</div>
<div className="flex flex-auto"></div>
{/* <GuideCommunityToggle topicName={topic.name} /> */}
<LearningStateSelector
showSearch={false}
value={p?.learningState || ""}
onChange={handleAddToProfile}
defaultLabel={isMobile ? "" : "Add to profile"}
defaultIcon="Circle"
/>
</ContentHeader>
</>
)
})

View File

@@ -178,11 +178,8 @@ export const LinkItem = React.memo(
size="sm"
type="button"
role="combobox"
variant="secondary"
className={cn(
"size-7 shrink-0 p-0",
"hover:bg-accent-foreground/10",
)}
variant="ghost"
className="h-auto shrink-0 cursor-default p-0 text-muted-foreground/75 hover:bg-inherit hover:text-foreground"
onClick={(e) => e.stopPropagation()}
>
{selectedLearningState?.icon ? (
@@ -215,7 +212,7 @@ export const LinkItem = React.memo(
<div className="gap-x-2 space-y-0.5 xl:flex xl:flex-row">
<p
className={cn(
"text-primary hover:text-primary line-clamp-1 text-sm font-medium",
"line-clamp-1 text-sm font-medium text-primary hover:text-primary",
isActive && "font-bold",
)}
>
@@ -226,14 +223,14 @@ export const LinkItem = React.memo(
<LaIcon
name="Link"
aria-hidden="true"
className="text-muted-foreground group-hover:text-primary size-3.5 flex-none"
className="flex-none text-muted-foreground group-hover:text-primary"
/>
<Link
to={ensureUrlProtocol(link.url)}
target="_blank"
onClick={(e) => e.stopPropagation()}
className="text-muted-foreground hover:text-primary text-xs"
className="text-xs text-muted-foreground hover:text-primary"
>
<span className="line-clamp-1">{link.url}</span>
</Link>

View File

@@ -51,10 +51,10 @@ export function TopicDetailList({
className="flex flex-col"
>
<div className="flex items-center gap-4 px-6 py-2 max-lg:px-4">
<p className="text-foreground text-sm font-medium">
<p className="text-[13px] font-medium text-muted-foreground">
{item.data?.title}
</p>
<div className="flex-1 border-b" />
<div className="flex-1 border-b border-[var(--la-border-new)]" />
</div>
</div>
)
@@ -82,7 +82,7 @@ export function TopicDetailList({
)
return (
<div ref={parentRef} className="flex-1 overflow-auto">
<div ref={parentRef} className="flex-1 overflow-auto py-4">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,

View File

@@ -1,7 +1,7 @@
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
export const Route = createFileRoute("/_layout/_pages/_protected")({
beforeLoad: async ({ context, location, cause }) => {
beforeLoad: async ({ context, location }) => {
if (!context?.auth?.userId) {
throw redirect({
to: "/sign-in/$",

View File

@@ -106,7 +106,7 @@ export function QuestionThread({ question, onClose }: QuestionThreadProps) {
>
<div className="flex items-center justify-between pb-1">
<div className="flex items-center">
<div className="bg-accent mr-2 h-6 w-6 rounded-full"></div>
<div className="mr-2 h-6 w-6 rounded-full bg-accent"></div>
<span className="text-sm">{answer.author}</span>
</div>
<div className="flex items-center">
@@ -147,16 +147,16 @@ export function QuestionThread({ question, onClose }: QuestionThreadProps) {
)
return (
<div className="border-accent bg-background fixed bottom-0 right-0 top-0 z-50 flex h-full w-[40%] flex-col border-l">
<div className="border-accent flex w-full justify-between border-b p-4">
<div className="fixed bottom-0 right-0 top-0 z-50 flex h-full w-[40%] flex-col border-l border-accent bg-background">
<div className="flex w-full justify-between border-b border-accent p-4">
<div className="flex w-full flex-col">
<div className="mb-2 flex w-full items-center justify-between">
<div className="flex items-center gap-3">
<div className="bg-accent h-8 w-8 rounded-full"></div>
<div className="h-8 w-8 rounded-full bg-accent"></div>
<h2 className="opacity-70">{question.author}</h2>
</div>
<button
className="bg-accent rounded-full p-1.5 opacity-50 hover:opacity-80"
className="rounded-full bg-accent p-1.5 opacity-50 hover:opacity-80"
onClick={onClose}
>
<LaIcon name="X" className="text-primary" />
@@ -167,7 +167,7 @@ export function QuestionThread({ question, onClose }: QuestionThreadProps) {
</div>
</div>
<div className="flex-grow overflow-y-auto">{renderAnswers(answers)}</div>
<div className="border-accent border-t p-4">
<div className="border-t border-accent p-4">
<form className="relative" onSubmit={sendAnswer}>
<div className="relative flex items-center">
<input
@@ -176,7 +176,7 @@ export function QuestionThread({ question, onClose }: QuestionThreadProps) {
value={newAnswer}
onChange={changeInput}
placeholder="Answer the question..."
className="bg-input w-full rounded p-2 text-opacity-70 placeholder:text-opacity-50 focus:outline-none focus:ring-0"
className="w-full rounded bg-input p-2 text-opacity-70 placeholder:text-opacity-50 focus:outline-none focus:ring-0"
/>
</div>
<button className="absolute right-2 top-1/2 -translate-y-1/2 transform opacity-50 hover:opacity-90">

View File

@@ -28,7 +28,7 @@ export const GuideCommunityToggle: React.FC<GuideCommunityToggleProps> = ({
}
return (
<div className="bg-accent/70 relative flex h-8 w-48 items-center rounded-md">
<div className="relative flex h-8 w-48 items-center rounded-md bg-accent/70">
<div
className="absolute h-8 w-[calc(50%-4px)] rounded-md transition-all duration-300 ease-in-out"
style={{ left: view === "guide" ? "2px" : "calc(50% + 2px)" }}
@@ -36,7 +36,7 @@ export const GuideCommunityToggle: React.FC<GuideCommunityToggleProps> = ({
<button
className={cn(
"relative z-10 h-full flex-1 rounded-md text-sm font-medium transition-colors",
view === "guide" ? "text-primary bg-accent" : "text-primary/50",
view === "guide" ? "bg-accent text-primary" : "text-primary/50",
)}
onClick={() => handleToggle("guide")}
>
@@ -45,7 +45,7 @@ export const GuideCommunityToggle: React.FC<GuideCommunityToggleProps> = ({
<button
className={cn(
"relative z-10 h-full flex-1 rounded-md text-sm font-medium transition-colors",
view === "community" ? "text-primary bg-accent" : "text-primary/50",
view === "community" ? "bg-accent text-primary" : "text-primary/50",
)}
onClick={() => handleToggle("community")}
>

View File

@@ -45,7 +45,7 @@ function CommunityTopicComponent() {
return (
<div className="flex h-full flex-auto flex-col">
<ContentHeader className="px-6 py-4">
<ContentHeader>
<div className="flex min-w-0 shrink-0 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 flex-col items-start">

View File

@@ -7,7 +7,7 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { cn, getShortcutKeys } from "@/lib/utils"
import { cn } from "@/lib/utils"
import { LaIcon } from "@/components/custom/la-icon"
import { useAtom } from "jotai"
import { useConfirm } from "@omit/react-confirm-dialog"
@@ -17,6 +17,7 @@ import { ID } from "jazz-tools"
import { globalLinkFormExceptionRefsAtom } from "./-link-form"
import { useLinkActions } from "~/hooks/actions/use-link-actions"
import { useNavigate, useSearch } from "@tanstack/react-router"
import { getShortcutKeys } from "@shared/utils"
interface ToolbarButtonProps
extends React.ComponentPropsWithoutRef<typeof Button> {
@@ -146,7 +147,7 @@ export const LinkBottomBar: React.FC = () => {
const shortcutText = getShortcutKeys(["c"])
return (
<div className="bg-background min-h-11 border-t">
<div className="min-h-11 border-t">
<AnimatePresence mode="wait">
{editId && (
<motion.div

View File

@@ -27,7 +27,7 @@ export const DescriptionInput: React.FC<DescriptionInputProps> = () => {
{...field}
autoComplete="off"
placeholder="Description"
className="placeholder:text-muted-foreground/70 resize-none overflow-y-auto border-none p-1.5 text-[13px] font-medium shadow-none focus-visible:ring-0"
className="resize-none overflow-y-auto border-none p-1.5 text-sm font-medium shadow-none placeholder:text-muted-foreground/70 focus-visible:ring-0"
/>
</FormControl>
</FormItem>

View File

@@ -1,77 +1,37 @@
import * as React from "react"
import { Button } from "@/components/ui/button"
import { useMedia } from "@/hooks/use-media"
import {
ContentHeader,
SidebarToggleButton,
} from "@/components/custom/content-header"
import { useMedia } from "@/hooks/use-media"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { useNavigate, useSearch } from "@tanstack/react-router"
import { FancySwitch, OptionValue } from "@omit/react-fancy-switch"
import { cn } from "@/lib/utils"
import { LEARNING_STATES, LearningStateValue } from "@/lib/constants"
import { useAtom } from "jotai"
import { linkSortAtom } from "@/store/link"
import { LEARNING_STATES, LearningStateValue } from "@/lib/constants"
import { FancySwitch } from "@omit/react-fancy-switch"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { LaIcon } from "@/components/custom/la-icon"
import { useNavigate, useSearch } from "@tanstack/react-router"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
const ALL_STATES = [
{ label: "All", value: "all", icon: "List", className: "text-foreground" },
...LEARNING_STATES,
]
export const LinkHeader = React.memo(() => {
const isTablet = useMedia("(max-width: 1024px)")
return (
<>
<ContentHeader className="px-6 max-lg:px-4 lg:py-4">
<div className="flex min-w-0 shrink-0 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 items-center">
<span className="truncate text-left font-bold lg:text-xl">
Links
</span>
</div>
</div>
{!isTablet && <LearningTab />}
<div className="flex flex-auto"></div>
<FilterAndSort />
</ContentHeader>
{isTablet && (
<div className="flex flex-row items-start justify-between border-b px-6 pb-4 pt-2 max-lg:pl-4">
<LearningTab />
</div>
)}
</>
)
})
LinkHeader.displayName = "LinkHeader"
const LearningTab = React.memo(() => {
const LearningTab: React.FC = React.memo(() => {
const navigate = useNavigate()
const { state } = useSearch({
from: "/_layout/_pages/_protected/links/",
})
const { state } = useSearch({ from: "/_layout/_pages/_protected/links/" })
const handleTabChange = React.useCallback(
async (value: string) => {
(value: OptionValue) => {
if (value !== state) {
navigate({
to: "/links",
@@ -85,30 +45,22 @@ const LearningTab = React.memo(() => {
return (
<FancySwitch
value={state}
onChange={(value) => {
handleTabChange(value as string)
}}
onChange={handleTabChange}
options={ALL_STATES}
className="bg-muted flex rounded-lg"
className="flex rounded-md"
highlighterClassName="bg-muted-foreground/10 rounded-md"
radioClassName={cn(
"relative mx-2 flex h-8 cursor-pointer items-center justify-center rounded-full px-1 text-sm text-secondary-foreground/60 data-[checked]:text-secondary-foreground font-medium transition-colors focus:outline-none",
"relative mx-2 flex h-6 cursor-pointer items-center justify-center rounded-full px-1 text-sm text-muted-foreground data-[checked]:text-foreground data-[checked]:font-medium transition-colors focus:outline-none",
)}
highlighterIncludeMargin={true}
/>
)
})
LearningTab.displayName = "LearningTab"
const FilterAndSort = React.memo(() => {
const FilterAndSort: React.FC = React.memo(() => {
const [sort, setSort] = useAtom(linkSortAtom)
const [sortOpen, setSortOpen] = React.useState(false)
const getFilterText = React.useCallback(() => {
return sort.charAt(0).toUpperCase() + sort.slice(1)
}, [sort])
const handleSortChange = React.useCallback(
(value: string) => {
setSort(value)
@@ -120,40 +72,80 @@ const FilterAndSort = React.memo(() => {
return (
<div className="flex w-auto items-center justify-end">
<div className="flex items-center gap-2">
<Popover open={sortOpen} onOpenChange={setSortOpen}>
<PopoverTrigger asChild>
<DropdownMenu open={sortOpen} onOpenChange={setSortOpen}>
<DropdownMenuTrigger asChild>
<Button
size="sm"
type="button"
variant="secondary"
className="min-w-8 gap-x-2 text-sm max-sm:p-0"
variant="ghost"
className="h-7 gap-x-2 text-sm max-sm:p-0"
>
<LaIcon name="ListFilter" className="text-primary/60" />
<span className="hidden md:block">Filter: {getFilterText()}</span>
<LaIcon name="ChevronDown" />
<span className="hidden md:block">Display</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-72" align="end">
<div className="flex flex-col">
<div className="flex min-w-8 flex-row items-center">
<Label>Sort by</Label>
<div className="flex flex-auto flex-row items-center justify-end">
<Select value={sort} onValueChange={handleSortChange}>
<SelectTrigger className="h-6 w-auto">
<SelectValue placeholder="Select"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="title">Title</SelectItem>
<SelectItem value="manual">Manual</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</PopoverContent>
</Popover>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" side="top">
<DropdownMenuLabel className="text-xs font-medium text-muted-foreground">
Display
</DropdownMenuLabel>
<DropdownMenuItem>
<LaIcon name="List" className="mr-2 h-4 w-4" />
<span>List</span>
<LaIcon name="Check" className="ml-auto h-4 w-4" />
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel className="text-xs font-medium text-muted-foreground">
Ordering
</DropdownMenuLabel>
<DropdownMenuItem onClick={() => handleSortChange("title")}>
<span>Title</span>
{sort === "title" && (
<LaIcon name="Check" className="ml-auto h-4 w-4" />
)}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleSortChange("manual")}>
<span>Manual</span>
{sort === "manual" && (
<LaIcon name="Check" className="ml-auto h-4 w-4" />
)}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
)
})
export const LinkHeader: React.FC = React.memo(() => {
const isTablet = useMedia("(max-width: 1024px)")
return (
<>
<ContentHeader>
<div className="flex min-w-0 shrink-0 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 items-center">
<span className="truncate text-left font-semibold lg:text-lg">
Links
</span>
</div>
</div>
{!isTablet && <LearningTab />}
<div className="flex flex-auto" />
<FilterAndSort />
</ContentHeader>
{isTablet && (
<div className="flex flex-row items-start justify-between border-b px-6 py-2 max-lg:pl-4">
<LearningTab />
</div>
)}
</>
)
})
LinkHeader.displayName = "LinkHeader"
LearningTab.displayName = "LearningTab"
FilterAndSort.displayName = "FilterAndSort"

View File

@@ -115,15 +115,15 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
data-disabled={disabled}
data-active={isActive}
className={cn(
"w-full overflow-visible border-b-[0.5px] border-transparent outline-none",
"data-[active='true']:bg-[var(--link-background-muted)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]",
"w-full cursor-default overflow-visible border-b-[0.5px] border-transparent outline-none",
"data-[active='true']:bg-[var(--link-background-muted-new)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]",
)}
onKeyDown={handleKeyDown}
>
<div
className={cn(
"w-full grow overflow-visible outline-none",
"flex items-center gap-x-2 py-2 max-lg:px-4 sm:px-5 sm:py-2",
"flex items-center gap-x-2 py-2 sm:px-5 sm:py-2 max-lg:px-4",
)}
>
<Popover
@@ -137,8 +137,8 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
size="sm"
type="button"
role="combobox"
variant="secondary"
className="size-7 shrink-0 p-0"
variant="ghost"
className="size-7 shrink-0 cursor-default p-0 text-muted-foreground/75 hover:bg-inherit hover:text-foreground"
onClick={(e) => e.stopPropagation()}
onDoubleClick={(e) => e.stopPropagation()}
>
@@ -148,7 +148,7 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
className={cn(selectedLearningState.className)}
/>
) : (
<LaIcon name="Circle" />
<LaIcon name="Circle" strokeWidth={2.5} />
)}
</Button>
</PopoverTrigger>
@@ -167,22 +167,22 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
</Popover>
<div className="flex min-w-0 flex-col items-start gap-y-1.5 overflow-hidden md:flex-row md:items-center md:gap-x-2">
<div className="flex items-center gap-x-1">
<div className="flex items-center gap-x-1.5">
{personalLink.icon && (
<img
src={personalLink.icon as string}
alt={personalLink.title}
className="size-5 shrink-0 rounded-full"
className="size-4 shrink-0 rounded-full"
width={16}
height={16}
/>
)}
<p className="text-primary hover:text-primary line-clamp-1 text-sm font-medium">
<p className="line-clamp-1 text-sm font-medium text-primary hover:text-primary">
{personalLink.title}
</p>
</div>
{personalLink.url && (
<div className="text-muted-foreground flex min-w-0 shrink items-center gap-x-1">
<div className="flex min-w-0 shrink items-center gap-x-1 text-muted-foreground">
<LaIcon
name="Link"
aria-hidden="true"
@@ -192,7 +192,7 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
to={ensureUrlProtocol(personalLink.url)}
target="_blank"
onClick={(e) => e.stopPropagation()}
className="hover:text-primary mr-1 truncate text-xs"
className="mr-1 truncate text-xs hover:text-primary"
>
{personalLink.url}
</Link>
@@ -204,14 +204,17 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
<div className="flex shrink-0 items-center justify-end">
{personalLink.topic && (
<Badge variant="secondary" className="border-muted-foreground/25">
<Badge
variant="secondary"
className="border-muted-foreground/25 font-medium"
>
{personalLink.topic.prettyName}
</Badge>
)}
</div>
</div>
<div className="relative h-[0.5px] w-full after:absolute after:left-0 after:right-0 after:block after:h-full after:bg-[var(--link-border-after)]"></div>
<div className="relative h-[0.5px] w-full after:absolute after:left-0 after:right-0 after:block after:h-full after:bg-[var(--la-border-new)]"></div>
</div>
)
},

View File

@@ -19,7 +19,8 @@ import { DescriptionInput } from "./-description-input"
import { UrlBadge } from "./-url-badge"
import { NotesSection } from "./-notes-section"
import { useOnClickOutside } from "~/hooks/use-on-click-outside"
import TopicSelector, {
import {
TopicSelector,
topicSelectorAtom,
} from "~/components/custom/topic-selector"
import { createServerFn } from "@tanstack/start"
@@ -291,7 +292,7 @@ export const LinkForm: React.FC<LinkFormProps> = ({
>
<div
className={cn(
"bg-muted/30 relative rounded-md border",
"relative rounded-md border bg-muted/30",
isFetching && "opacity-50",
)}
>
@@ -369,7 +370,7 @@ export const LinkForm: React.FC<LinkFormProps> = ({
{isFetching ? (
<div className="flex w-auto items-center justify-end gap-x-2">
<span className="text-muted-foreground flex items-center text-sm">
<span className="flex items-center text-sm text-muted-foreground">
<svg
className="mr-2 h-4 w-4 animate-spin"
viewBox="0 0 24 24"

View File

@@ -27,12 +27,12 @@ import { linkSortAtom } from "@/store/link"
import { LinkItem } from "./-item"
import { useConfirm } from "@omit/react-confirm-dialog"
import { useKeyDown } from "@/hooks/use-key-down"
import { isModKey } from "@/lib/utils"
import { useTouchSensor } from "~/hooks/use-touch-sensor"
import { useActiveItemScroll } from "~/hooks/use-active-item-scroll"
import { isDeleteConfirmShownAtom } from "."
import { useLinkActions } from "~/hooks/actions/use-link-actions"
import { useNavigate, useSearch } from "@tanstack/react-router"
import { isModKey } from "@shared/utils"
interface LinkListProps {}

View File

@@ -35,7 +35,7 @@ export const NotesSection: React.FC = () => {
autoComplete="off"
placeholder="Notes"
className={cn(
"placeholder:text-muted-foreground/70 border-none pl-8 shadow-none focus-visible:ring-0",
"border-none pl-8 shadow-none placeholder:text-muted-foreground/70 focus-visible:ring-0",
)}
/>
</>

View File

@@ -31,7 +31,7 @@ export const TitleInput: React.FC<TitleInputProps> = ({ urlFetched }) => {
maxLength={100}
autoFocus
placeholder="Title"
className="placeholder:text-muted-foreground/70 h-8 border-none p-1.5 text-[15px] font-semibold shadow-none focus-visible:ring-0"
className="h-8 border-none p-1.5 text-[15px] font-semibold shadow-none placeholder:text-muted-foreground/70 focus-visible:ring-0"
/>
</FormControl>
</FormItem>

View File

@@ -27,9 +27,9 @@ export const UrlBadge: React.FC<UrlBadgeProps> = ({
size="icon"
type="button"
onClick={handleResetUrl}
className="text-muted-foreground hover:text-foreground ml-2 size-4 rounded-full bg-transparent hover:bg-transparent"
className="ml-2 size-4 rounded-full bg-transparent text-muted-foreground hover:bg-transparent hover:text-foreground"
>
<LaIcon name="X" className="size-3.5" />
<LaIcon name="X" className="" />
</Button>
</Badge>
</div>

View File

@@ -65,14 +65,14 @@ export const UrlInput: React.FC<UrlInputProps> = ({
maxLength={100}
autoFocus
placeholder="Paste a link or write a link"
className="placeholder:text-muted-foreground/70 h-8 border-none p-1.5 text-[15px] font-semibold shadow-none focus-visible:ring-0"
className="h-8 border-none p-1.5 text-[15px] font-semibold shadow-none placeholder:text-muted-foreground/70 focus-visible:ring-0"
onKeyDown={handleKeyDown}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
</TooltipTrigger>
<TooltipContent align="center" side="top">
<TooltipArrow className="text-primary fill-current" />
<TooltipArrow className="fill-current text-primary" />
<span>
Press <kbd className="px-1.5">Enter</kbd> to fetch metadata
</span>

View File

@@ -60,7 +60,7 @@ const StepItem = ({
done: boolean
}) => (
<div className="flex items-start space-x-4 py-4">
<div className="border-foreground/20 w-6 flex-shrink-0 items-center justify-center rounded-3xl border text-center opacity-70">
<div className="w-6 flex-shrink-0 items-center justify-center rounded-3xl border border-foreground/20 text-center opacity-70">
{number}
</div>
<div className="flex-grow space-y-2">

View File

@@ -23,7 +23,7 @@ export const DetailPageHeader: React.FC<DetailPageHeaderProps> = ({
return (
<>
<ContentHeader className="lg:min-h-0">
<ContentHeader>
<div className="flex min-w-0 gap-2">
<SidebarToggleButton />
</div>
@@ -45,7 +45,7 @@ export const DetailPageHeader: React.FC<DetailPageHeaderProps> = ({
)}
/>
<Button size="sm" variant="outline" onClick={handleDelete}>
<LaIcon name="Trash" className="mr-2 size-3.5" />
<LaIcon name="Trash" className="mr-2" />
Delete
</Button>
</div>

View File

@@ -15,14 +15,14 @@ import { Button } from "@/components/ui/button"
import { LaIcon } from "@/components/custom/la-icon"
import { useConfirm } from "@omit/react-confirm-dialog"
import { usePageActions } from "~/hooks/actions/use-page-actions"
import { Paragraph } from "@shared/la-editor/extensions/paragraph"
import { StarterKit } from "@shared/la-editor/extensions/starter-kit"
import { LAEditor, LAEditorRef } from "@shared/la-editor"
import { Paragraph } from "@shared/editor/extensions/paragraph"
import { StarterKit } from "@shared/editor/extensions/starter-kit"
import { LaEditor } from "@shared/editor"
export const Route = createFileRoute(
"/_layout/_pages/_protected/pages/$pageId/",
)({
component: () => <PageDetailComponent />,
component: PageDetailComponent,
})
const TITLE_PLACEHOLDER = "Untitled"
@@ -73,20 +73,22 @@ function PageDetailComponent() {
)
}
const SidebarActions = ({
page,
handleDelete,
}: {
page: PersonalPage
handleDelete: () => void
}) => (
<div className="relative min-w-56 max-w-72 border-l">
<div className="flex">
<div className="flex h-10 flex-auto flex-row items-center justify-between px-5">
<span className="text-left text-[13px] font-medium">Page actions</span>
</div>
<div className="absolute bottom-0 left-0 right-0 top-10 space-y-3 overflow-y-auto px-4 py-1.5">
<div className="flex flex-row">
const SidebarActions = React.memo(
({
page,
handleDelete,
}: {
page: PersonalPage
handleDelete: () => void
}) => (
<div className="relative min-w-56 max-w-72 border-l bg-[var(--body-background)]">
<div className="flex">
<div className="flex h-10 flex-auto flex-row items-center justify-between px-5">
<span className="text-left text-[13px] font-medium text-muted-foreground">
Page actions
</span>
</div>
<div className="absolute bottom-0 left-0 right-0 top-10 space-y-3 overflow-y-auto px-4 py-1.5">
<TopicSelector
value={page.topic?.name}
onTopicChange={(topic) => {
@@ -101,52 +103,40 @@ const SidebarActions = ({
</span>
)}
/>
</div>
<div className="flex flex-row">
<Button
size="sm"
variant="ghost"
onClick={handleDelete}
className="-ml-1.5"
>
<LaIcon name="Trash" className="mr-2 size-3.5" />
<LaIcon name="Trash" className="mr-2" />
<span className="text-sm">Delete</span>
</Button>
</div>
</div>
</div>
</div>
),
)
const DetailPageForm = ({ page }: { page: PersonalPage }) => {
SidebarActions.displayName = "SidebarActions"
const DetailPageForm = React.memo(({ page }: { page: PersonalPage }) => {
const titleEditorRef = React.useRef<Editor | null>(null)
const contentEditorRef = React.useRef<LAEditorRef>(null)
const isTitleInitialMount = React.useRef(true)
const isContentInitialMount = React.useRef(true)
const isInitialFocusApplied = React.useRef(false)
const contentEditorRef = React.useRef<Editor | null>(null)
const updatePageContent = React.useCallback(
(content: Content, model: PersonalPage) => {
if (isContentInitialMount.current) {
isContentInitialMount.current = false
return
}
model.content = content
model.updatedAt = new Date()
(content: Content) => {
page.content = content
page.updatedAt = new Date()
},
[],
[page],
)
const handleUpdateTitle = React.useCallback(
(editor: Editor) => {
if (isTitleInitialMount.current) {
isTitleInitialMount.current = false
return
}
const newTitle = editor.getText()
if (newTitle !== page.title) {
const slug = generateUniqueSlug(page.title?.toString() || "")
const slug = generateUniqueSlug(newTitle || "")
page.title = newTitle
page.slug = slug
page.updatedAt = new Date()
@@ -164,22 +154,18 @@ const DetailPageForm = ({ page }: { page: PersonalPage }) => {
const { selection } = state
const { $anchor } = selection
switch (event.key) {
case "ArrowRight":
case "ArrowDown":
if ($anchor.pos === state.doc.content.size - 1) {
event.preventDefault()
contentEditorRef.current?.editor?.commands.focus("start")
return true
}
break
case "Enter":
if (!event.shiftKey) {
event.preventDefault()
contentEditorRef.current?.editor?.commands.focus("start")
return true
}
break
if (
(event.key === "ArrowRight" || event.key === "ArrowDown") &&
$anchor.pos === state.doc.content.size - 1
) {
event.preventDefault()
contentEditorRef.current?.commands.focus("start")
return true
}
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault()
contentEditorRef.current?.commands.focus("start")
return true
}
return false
},
@@ -188,7 +174,7 @@ const DetailPageForm = ({ page }: { page: PersonalPage }) => {
const handleContentKeyDown = React.useCallback(
(view: EditorView, event: KeyboardEvent) => {
const editor = contentEditorRef.current?.editor
const editor = contentEditorRef.current
if (!editor) return false
const { state } = editor
@@ -239,34 +225,21 @@ const DetailPageForm = ({ page }: { page: PersonalPage }) => {
},
onCreate: ({ editor }) => {
if (page.title) editor.commands.setContent(`<p>${page.title}</p>`)
titleEditorRef.current = editor
},
onBlur: ({ editor }) => handleUpdateTitle(editor),
onUpdate: ({ editor }) => handleUpdateTitle(editor),
})
React.useEffect(() => {
if (titleEditor) {
titleEditorRef.current = titleEditor
}
}, [titleEditor])
React.useEffect(() => {
isTitleInitialMount.current = true
isContentInitialMount.current = true
if (
!isInitialFocusApplied.current &&
titleEditor &&
contentEditorRef.current?.editor
) {
isInitialFocusApplied.current = true
if (!page.title) {
titleEditor?.commands.focus()
} else {
contentEditorRef.current.editor.commands.focus()
const handleCreate = React.useCallback(
({ editor }: { editor: Editor }) => {
if (page.content) {
editor.commands.setContent(page.content as Content)
}
}
}, [page.title, titleEditor])
contentEditorRef.current = editor
},
[page.content],
)
return (
<div className="relative flex grow flex-col overflow-y-auto [scrollbar-gutter:stable]">
@@ -275,21 +248,21 @@ const DetailPageForm = ({ page }: { page: PersonalPage }) => {
<div className="mb-2 mt-8 py-1.5">
<EditorContent
editor={titleEditor}
className="la-editor no-command grow cursor-text select-text text-2xl font-semibold leading-[calc(1.33333)] tracking-[-0.00625rem]"
className="title-editor no-command grow cursor-text select-text text-2xl font-semibold leading-[calc(1.33333)] tracking-[-0.00625rem]"
/>
</div>
<div className="flex flex-auto flex-col">
<div className="relative flex h-full max-w-full grow flex-col items-stretch p-0">
<LAEditor
ref={contentEditorRef}
<LaEditor
editorClassName="-mx-3.5 px-3.5 py-2.5 flex-auto focus:outline-none"
value={page.content}
value={page.content as Content}
placeholder="Add content..."
output="json"
throttleDelay={3000}
onUpdate={(c) => updatePageContent(c, page)}
handleKeyDown={handleContentKeyDown}
onBlur={(c) => updatePageContent(c, page)}
editorProps={{ handleKeyDown: handleContentKeyDown }}
onCreate={handleCreate}
onUpdate={updatePageContent}
onBlur={updatePageContent}
/>
</div>
</div>
@@ -297,4 +270,6 @@ const DetailPageForm = ({ page }: { page: PersonalPage }) => {
</div>
</div>
)
}
})
DetailPageForm.displayName = "DetailPageForm"

View File

@@ -24,7 +24,7 @@ export const PageHeader: React.FC<PageHeaderProps> = React.memo(() => {
}
return (
<ContentHeader className="px-6 py-4 max-lg:px-4">
<ContentHeader>
<HeaderTitle />
<div className="flex flex-auto" />
<NewPageButton onClick={handleNewPageClick} />

View File

@@ -36,8 +36,8 @@ export const PageItem = React.forwardRef<HTMLAnchorElement, PageItemProps>(
tabIndex={isActive ? 0 : -1}
className={cn(
"relative block cursor-default outline-none",
"min-h-12 py-2 max-lg:px-4 sm:px-6",
"data-[active='true']:bg-[var(--link-background-muted)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]",
"min-h-12 py-2 sm:px-6 max-lg:px-4",
"data-[active='true']:bg-[var(--link-background-muted-new)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]",
)}
to={`/pages/${page.id}`}
aria-selected={isActive}
@@ -47,7 +47,7 @@ export const PageItem = React.forwardRef<HTMLAnchorElement, PageItemProps>(
>
<div className="flex h-full items-center gap-4">
<Column.Wrapper style={columnStyles.title}>
<Column.Text className="truncate text-[13px] font-medium">
<Column.Text className="truncate text-sm font-medium">
{page.title || "Untitled"}
</Column.Text>
</Column.Wrapper>
@@ -64,7 +64,7 @@ export const PageItem = React.forwardRef<HTMLAnchorElement, PageItemProps>(
style={columnStyles.updated}
className="flex justify-end"
>
<Column.Text className="text-[13px]">
<Column.Text className="text-sm">
{format(new Date(page.updatedAt), "d MMM yyyy")}
</Column.Text>
</Column.Wrapper>

View File

@@ -54,7 +54,7 @@ export const PageList: React.FC<PageListProps> = () => {
<div className="flex h-full w-full flex-col overflow-hidden border-t">
{!isTablet && <ColumnHeader />}
<Primitive.div
className="divide-primary/5 flex flex-1 flex-col divide-y overflow-y-auto outline-none [scrollbar-gutter:stable]"
className="flex flex-1 flex-col divide-y divide-primary/5 overflow-y-auto outline-none [scrollbar-gutter:stable]"
tabIndex={-1}
role="list"
>
@@ -110,7 +110,7 @@ export const ColumnHeader: React.FC = () => {
const columnStyles = useColumnStyles()
return (
<div className="flex h-8 shrink-0 grow-0 flex-row gap-4 border-b max-lg:px-4 sm:px-6">
<div className="flex h-8 shrink-0 grow-0 flex-row gap-4 border-b sm:px-6 max-lg:px-4">
<Column.Wrapper style={columnStyles.title}>
<Column.Text>Title</Column.Text>
</Column.Wrapper>

View File

@@ -137,7 +137,7 @@ function ProfileComponent() {
<Input
value={newName}
onChange={changeName}
className="border-result mb-3 mr-3 text-[25px] font-semibold"
className="mb-3 mr-3 border-result text-[25px] font-semibold"
/>
{error && (
<p className="text-red-500 text-opacity-70">{error}</p>

View File

@@ -28,7 +28,7 @@ const SearchTitle: React.FC<SearchTitleProps> = ({ title, count }) => (
<div className="flex w-full items-center">
<h2 className="text-md font-semibold">{title}</h2>
<div className="mx-4 flex-grow">
<div className="bg-result h-px"></div>
<div className="h-px bg-result"></div>
</div>
<span className="text-base font-light text-opacity-55">{count}</span>
</div>
@@ -41,7 +41,7 @@ const SearchItem: React.FC<SearchItemProps> = ({
subtitle,
topic,
}) => (
<div className="hover:bg-result group flex min-w-0 items-center gap-x-4 rounded-md p-2">
<div className="group flex min-w-0 items-center gap-x-4 rounded-md p-2 hover:bg-result">
<LaIcon
name={icon as "Square"}
className="size-4 flex-shrink-0 opacity-0 transition-opacity duration-200 group-hover:opacity-50"
@@ -50,7 +50,7 @@ const SearchItem: React.FC<SearchItemProps> = ({
<Link
to={href}
onClick={(e) => e.stopPropagation()}
className="hover:text-primary text-sm font-medium hover:opacity-70"
className="text-sm font-medium hover:text-primary hover:opacity-70"
>
{title}
</Link>
@@ -58,7 +58,7 @@ const SearchItem: React.FC<SearchItemProps> = ({
<Link
to={href}
onClick={(e) => e.stopPropagation()}
className="text-muted-foreground ml-2 truncate text-xs hover:underline"
className="ml-2 truncate text-xs text-muted-foreground hover:underline"
>
{subtitle}
</Link>
@@ -138,7 +138,7 @@ const SearchComponent = () => {
<div className="relative my-5 flex w-full items-center space-x-2">
<LaIcon
name="Search"
className="text-foreground absolute left-4 size-4 flex-shrink-0"
className="absolute left-4 size-4 flex-shrink-0 text-foreground"
/>
<input
autoFocus
@@ -146,12 +146,12 @@ const SearchComponent = () => {
value={searchText}
onChange={handleSearch}
placeholder="Search topics, links, pages"
className="dark:bg-input w-full rounded-lg border border-neutral-300 p-2 pl-8 focus:outline-none dark:border-neutral-600"
className="w-full rounded-lg border border-neutral-300 p-2 pl-8 focus:outline-none dark:border-neutral-600 dark:bg-input"
/>
{searchText && (
<LaIcon
name="X"
className="text-foreground/50 absolute right-3 size-4 flex-shrink-0 cursor-pointer"
className="absolute right-3 size-4 flex-shrink-0 cursor-pointer text-foreground/50"
onClick={clearSearch}
/>
)}

View File

@@ -115,7 +115,7 @@ export const TaskForm: React.FC = () => {
<div
ref={formRef}
onSubmit={handleSubmit}
className="bg-result flex w-full items-center justify-between rounded-lg px-2 py-1"
className="flex w-full items-center justify-between rounded-lg bg-result px-2 py-1"
>
<div className="flex min-w-0 flex-1 items-center">
<Checkbox

View File

@@ -58,7 +58,7 @@ export const TaskItem: React.FC<TaskItemProps> = ({
: "No due date"
return (
<li className="bg-result transitiion-opacity flex items-center justify-between rounded-lg p-2 px-3 hover:opacity-60">
<li className="transitiion-opacity flex items-center justify-between rounded-lg bg-result p-2 px-3 hover:opacity-60">
<div className="flex flex-grow flex-row items-center gap-3">
<Checkbox
checked={task.status === "done"}
@@ -77,7 +77,7 @@ export const TaskItem: React.FC<TaskItemProps> = ({
<p
className={
task.status === "done"
? "text-foreground flex-grow line-through"
? "flex-grow text-foreground line-through"
: "flex-grow"
}
onClick={clickTitle}
@@ -86,7 +86,7 @@ export const TaskItem: React.FC<TaskItemProps> = ({
</p>
)}
</div>
<span className="text-muted-foreground text-xs">{formattedDate}</span>
<span className="text-xs text-muted-foreground">{formattedDate}</span>
</li>
)
}

View File

@@ -13,7 +13,7 @@ export const TopicHeader: React.FC<TopicHeaderProps> = React.memo(() => {
if (!me) return null
return (
<ContentHeader className="px-6 py-4 max-lg:px-4">
<ContentHeader>
<HeaderTitle />
<div className="flex flex-auto" />
</ContentHeader>
@@ -26,7 +26,9 @@ const HeaderTitle: React.FC = () => (
<div className="flex min-w-0 shrink-0 items-center gap-1.5">
<SidebarToggleButton />
<div className="flex min-h-0 items-center">
<span className="truncate text-left font-bold lg:text-xl">Topics</span>
<span className="truncate text-left font-semibold lg:text-lg">
Topics
</span>
</div>
</div>
)

View File

@@ -142,8 +142,8 @@ export const TopicItem = React.forwardRef<HTMLAnchorElement, TopicItemProps>(
tabIndex={isActive ? 0 : -1}
className={cn(
"relative block cursor-default outline-none",
"min-h-12 py-2 max-lg:px-4 sm:px-6",
"data-[active='true']:bg-[var(--link-background-muted)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]",
"min-h-12 py-2 sm:px-6 max-lg:px-4",
"data-[active='true']:bg-[var(--link-background-muted-new)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]",
)}
aria-selected={isActive}
data-active={isActive}
@@ -155,7 +155,7 @@ export const TopicItem = React.forwardRef<HTMLAnchorElement, TopicItemProps>(
tabIndex={isActive ? 0 : -1}
>
<Column.Wrapper style={columnStyles.title}>
<Column.Text className="truncate text-[13px] font-medium">
<Column.Text className="truncate text-sm font-medium">
{topic.prettyName}
</Column.Text>
</Column.Wrapper>

View File

@@ -94,10 +94,10 @@ export const MainTopicList: React.FC<MainTopicListProps> = ({ me }) => {
})
return (
<div className="flex h-full w-full flex-col overflow-hidden border-t">
<div className="flex h-full w-full flex-col overflow-hidden">
{!isTablet && <ColumnHeader />}
<Primitive.div
className="divide-primary/5 flex flex-1 flex-col divide-y overflow-y-auto outline-none [scrollbar-gutter:stable]"
className="flex flex-1 flex-col divide-y divide-primary/5 overflow-y-auto outline-none [scrollbar-gutter:stable]"
tabIndex={-1}
role="list"
>
@@ -144,7 +144,7 @@ export const ColumnHeader: React.FC = () => {
const columnStyles = useColumnStyles()
return (
<div className="flex h-8 shrink-0 grow-0 flex-row gap-4 border-b max-lg:px-4 sm:px-6">
<div className="flex h-8 shrink-0 grow-0 flex-row gap-4 border-b sm:px-6 max-lg:px-4">
<Column.Wrapper style={columnStyles.title}>
<Column.Text>Name</Column.Text>
</Column.Wrapper>