mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
* wip * wip * wip3 * chore: utils * feat: add command * wip * fix: key duplicate * fix: move and check * fix: use react-use instead * fix: sidebar * chore: make dynamic * chore: tablet mode * chore: fix padding * chore: link instead of inbox * fix: use dnd kit * feat: add select component * chore: use atom * refactor: remove dnd provider * feat: disabled drag when sort is not manual * search route * . * feat: accessibility * fix: search * . * . * . * fix: sidebar collapsed * ai search layout * . * . * . * . * ai responsible content * . * . * . * . * . * global topic route * global topic correct route * topic buttons * sidebar search navigation * ai * Update jazz * . * . * . * . * . * learning status * . * . * chore: content header * fix: pointer none when dragging. prevent auto click after drag end * fix: confirm * fix: prevent drag when editing * chore: remove unused fn * fix: check propagation * chore: list * chore: tweak sonner * chore: update stuff * feat: add badge * chore: close edit when create * chore: escape on manage form * refactor: remove learn path * css: responsive item * chore: separate pages and topic * reafactor: remove new-schema * feat(types): extend jazz type so it can be nullable * chore: use new types * fix: missing deps * fix: link * fix: sidebar in layout * fix: quotes * css: use medium instead semi * Actual streaming and rendering markdown response * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * chore: metadata * feat: la-editor * . * fix: editor and page * . * . * . * . * . * . * fix: remove link * chore: page sidebar * fix: remove 'replace with learning status' * fix: link * fix: link * chore: update schema * chore: use new schema * fix: instead of showing error, just do unique slug * feat: create slug * refactor apply * update package json * fix: schema personal page * chore: editor * feat: pages * fix: metadata * fix: jazz provider * feat: handling data * feat: page detail * chore: server page to id * chore: use id instead of slug * chore: update content header * chore: update link header implementation * refactor: global.css * fix: la editor use animation frame * fix: editor export ref * refactor: page detail * chore: tidy up schema * chore: adapt to new schema * fix: wrap using settimeout * fix: wrap using settimeout * . * . --------- Co-authored-by: marshennikovaolga <marshennikova@gmail.com> Co-authored-by: Nikita <github@nikiv.dev> Co-authored-by: Anselm <anselm.eickhoff@gmail.com> Co-authored-by: Damian Tarnawski <gthetarnav@gmail.com>
159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
"use client"
|
|
import React, { useState } from "react"
|
|
import { ContentHeader } from "@/components/custom/content-header"
|
|
import { PiLinkSimple } from "react-icons/pi"
|
|
import { Bookmark, GraduationCap, Check } from "lucide-react"
|
|
|
|
interface LinkProps {
|
|
title: string
|
|
url: string
|
|
}
|
|
|
|
const links = [
|
|
{ title: "JavaScript", url: "https://justjavascript.com" },
|
|
{ title: "TypeScript", url: "https://www.typescriptlang.org/" },
|
|
{ title: "React", url: "https://reactjs.org/" }
|
|
]
|
|
|
|
const LinkItem: React.FC<LinkProps> = ({ title, url }) => (
|
|
<div className="mb-1 flex flex-row items-center justify-between rounded-xl bg-[#121212] px-2 py-4 hover:cursor-pointer">
|
|
<div className="flex items-center space-x-4">
|
|
<p>{title}</p>
|
|
<span className="text-md flex flex-row items-center space-x-1 font-medium tracking-wide text-white/20 hover:opacity-50">
|
|
<PiLinkSimple size={20} className="text-white/20" />
|
|
<a href={url} target="_blank" rel="noopener noreferrer">
|
|
{new URL(url).hostname}
|
|
</a>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
interface ButtonProps {
|
|
children: React.ReactNode
|
|
onClick: () => void
|
|
className?: string
|
|
color?: string
|
|
icon?: React.ReactNode
|
|
fullWidth?: boolean
|
|
}
|
|
|
|
const Button: React.FC<ButtonProps> = ({ children, onClick, className = "", color = "", icon, fullWidth = false }) => {
|
|
return (
|
|
<button
|
|
className={`flex items-center justify-start rounded px-3 py-1 text-sm font-medium ${
|
|
fullWidth ? "w-full" : ""
|
|
} ${className} ${color}`}
|
|
onClick={onClick}
|
|
>
|
|
{icon && <span className="mr-2 flex items-center">{icon}</span>}
|
|
<span>{children}</span>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export default function GlobalTopic({ topic }: { topic: string }) {
|
|
const [showOptions, setShowOptions] = useState(false)
|
|
const [selectedOption, setSelectedOption] = useState<string | null>(null)
|
|
const [activeTab, setActiveTab] = useState("Guide")
|
|
|
|
const decodedTopic = decodeURIComponent(topic)
|
|
|
|
const learningOptions = [
|
|
{ text: "To Learn", icon: <Bookmark size={18} /> },
|
|
{ text: "Learning", icon: <GraduationCap size={18} /> },
|
|
{ text: "Learned", icon: <Check size={18} /> }
|
|
]
|
|
|
|
const learningStatusColor = (option: string) => {
|
|
switch (option) {
|
|
case "To Learn":
|
|
return "text-white/70"
|
|
case "Learning":
|
|
return "text-[#D29752]"
|
|
case "Learned":
|
|
return "text-[#708F51]"
|
|
default:
|
|
return "text-white/70"
|
|
}
|
|
}
|
|
|
|
const selectedStatus = (option: string) => {
|
|
setSelectedOption(option)
|
|
setShowOptions(false)
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-full flex-auto flex-col overflow-hidden">
|
|
<ContentHeader>
|
|
<div className="flex w-full items-center justify-between">
|
|
<h1 className="text-2xl font-bold">{decodedTopic}</h1>
|
|
<div className="flex items-center space-x-4">
|
|
<div className="flex rounded-lg bg-neutral-800 bg-opacity-60">
|
|
<button
|
|
onClick={() => setActiveTab("Guide")}
|
|
className={`px-4 py-2 text-[16px] font-semibold transition-colors ${
|
|
activeTab === "Guide"
|
|
? "rounded-lg bg-neutral-800 shadow-inner shadow-neutral-700/70"
|
|
: "text-white/70"
|
|
}`}
|
|
>
|
|
Guide
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab("All links")}
|
|
className={`px-4 py-2 text-[16px] font-semibold transition-colors ${
|
|
activeTab === "All links"
|
|
? "rounded-lg bg-neutral-800 shadow-inner shadow-neutral-700/70"
|
|
: "text-white/70"
|
|
}`}
|
|
>
|
|
All links
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative">
|
|
<Button
|
|
onClick={() => setShowOptions(!showOptions)}
|
|
className="w-[150px] whitespace-nowrap rounded-[7px] bg-neutral-800 px-4 py-2 text-[17px] font-semibold shadow-inner shadow-neutral-700/50 transition-colors hover:bg-neutral-700"
|
|
color={learningStatusColor(selectedOption || "")}
|
|
icon={selectedOption && learningOptions.find(opt => opt.text === selectedOption)?.icon}
|
|
>
|
|
{selectedOption || "Add to my profile"}
|
|
</Button>
|
|
{showOptions && (
|
|
<div className="absolute left-1/2 mt-1 w-40 -translate-x-1/2 rounded-lg bg-neutral-800 shadow-lg">
|
|
{learningOptions.map(option => (
|
|
<Button
|
|
key={option.text}
|
|
onClick={() => selectedStatus(option.text)}
|
|
className="space-x-1 px-2 py-2 text-left text-[14px] font-semibold hover:bg-neutral-700"
|
|
color={learningStatusColor(option.text)}
|
|
icon={option.icon}
|
|
fullWidth
|
|
>
|
|
{option.text}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</ContentHeader>
|
|
<div className="px-5 py-3">
|
|
<h2 className="mb-3 text-white/60">Intro</h2>
|
|
{links.map((link, index) => (
|
|
<LinkItem key={index} title={link.title} url={link.url} />
|
|
))}
|
|
</div>
|
|
<div className="px-5 py-3">
|
|
<h2 className="mb-3 text-opacity-60">Other</h2>
|
|
{links.map((link, index) => (
|
|
<LinkItem key={index} title={link.title} url={link.url} />
|
|
))}
|
|
</div>
|
|
<div className="flex-1 overflow-auto p-4"></div>
|
|
</div>
|
|
)
|
|
}
|