mirror of
https://github.com/linsa-io/linsa.git
synced 2026-05-27 17:19:16 +02:00
Merge branch 'main' of github.com:learn-anything/learn-anything.xyz
This commit is contained in:
@@ -10,18 +10,21 @@ import { useAccount } from "@/lib/providers/jazz-provider"
|
|||||||
import { searchSafeRegExp, toTitleCase } from "@/lib/utils"
|
import { searchSafeRegExp, toTitleCase } from "@/lib/utils"
|
||||||
import { GraphNode } from "@/components/routes/public/PublicHomeRoute"
|
import { GraphNode } from "@/components/routes/public/PublicHomeRoute"
|
||||||
import { useCommandActions } from "./hooks/use-command-actions"
|
import { useCommandActions } from "./hooks/use-command-actions"
|
||||||
|
import { atom, useAtom } from "jotai"
|
||||||
|
|
||||||
let graph_data_promise = import("@/components/routes/public/graph-data.json").then(a => a.default)
|
let graph_data_promise = import("@/components/routes/public/graph-data.json").then(a => a.default)
|
||||||
|
|
||||||
const filterItems = (items: CommandItemType[], searchRegex: RegExp) =>
|
const filterItems = (items: CommandItemType[], searchRegex: RegExp) =>
|
||||||
items.filter(item => searchRegex.test(item.value)).slice(0, 6)
|
items.filter(item => searchRegex.test(item.value)).slice(0, 6)
|
||||||
|
|
||||||
|
export const commandPaletteOpenAtom = atom(false)
|
||||||
|
|
||||||
export function CommandPalette() {
|
export function CommandPalette() {
|
||||||
const { me } = useAccount({ root: { personalLinks: [], personalPages: [] } })
|
const { me } = useAccount({ root: { personalLinks: [], personalPages: [] } })
|
||||||
const dialogRef = React.useRef<HTMLDivElement | null>(null)
|
const dialogRef = React.useRef<HTMLDivElement | null>(null)
|
||||||
const [inputValue, setInputValue] = React.useState("")
|
const [inputValue, setInputValue] = React.useState("")
|
||||||
const [activePage, setActivePage] = React.useState("home")
|
const [activePage, setActivePage] = React.useState("home")
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = useAtom(commandPaletteOpenAtom)
|
||||||
|
|
||||||
const actions = useCommandActions()
|
const actions = useCommandActions()
|
||||||
const commandGroups = React.useMemo(() => me && createCommandGroups(actions, me), [actions, me])
|
const commandGroups = React.useMemo(() => me && createCommandGroups(actions, me), [actions, me])
|
||||||
@@ -38,7 +41,7 @@ export function CommandPalette() {
|
|||||||
|
|
||||||
document.addEventListener("keydown", down)
|
document.addEventListener("keydown", down)
|
||||||
return () => document.removeEventListener("keydown", down)
|
return () => document.removeEventListener("keydown", down)
|
||||||
}, [])
|
}, [setOpen])
|
||||||
|
|
||||||
const bounce = React.useCallback(() => {
|
const bounce = React.useCallback(() => {
|
||||||
if (dialogRef.current) {
|
if (dialogRef.current) {
|
||||||
@@ -177,7 +180,7 @@ export function CommandPalette() {
|
|||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[bounce]
|
[bounce, setOpen]
|
||||||
)
|
)
|
||||||
|
|
||||||
const filteredCommands = React.useMemo(() => getFilteredCommands(), [getFilteredCommands])
|
const filteredCommands = React.useMemo(() => getFilteredCommands(), [getFilteredCommands])
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { LinkHeader } from "@/components/routes/link/header"
|
|||||||
import { LinkList } from "@/components/routes/link/list"
|
import { LinkList } from "@/components/routes/link/list"
|
||||||
import { LinkManage } from "@/components/routes/link/manage"
|
import { LinkManage } from "@/components/routes/link/manage"
|
||||||
import { useQueryState } from "nuqs"
|
import { useQueryState } from "nuqs"
|
||||||
import { useEffect } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import { linkEditIdAtom } from "@/store/link"
|
import { linkEditIdAtom } from "@/store/link"
|
||||||
import { LinkBottomBar } from "./bottom-bar"
|
import { LinkBottomBar } from "./bottom-bar"
|
||||||
@@ -12,6 +12,7 @@ import { LinkBottomBar } from "./bottom-bar"
|
|||||||
export function LinkRoute() {
|
export function LinkRoute() {
|
||||||
const [, setEditId] = useAtom(linkEditIdAtom)
|
const [, setEditId] = useAtom(linkEditIdAtom)
|
||||||
const [nuqsEditId] = useQueryState("editId")
|
const [nuqsEditId] = useQueryState("editId")
|
||||||
|
const [activeItemIndex, setActiveItemIndex] = useState<number | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditId(nuqsEditId)
|
setEditId(nuqsEditId)
|
||||||
@@ -21,7 +22,7 @@ export function LinkRoute() {
|
|||||||
<div className="flex h-full flex-auto flex-col overflow-hidden">
|
<div className="flex h-full flex-auto flex-col overflow-hidden">
|
||||||
<LinkHeader />
|
<LinkHeader />
|
||||||
<LinkManage />
|
<LinkManage />
|
||||||
<LinkList />
|
<LinkList key={nuqsEditId} activeItemIndex={activeItemIndex} setActiveItemIndex={setActiveItemIndex} />
|
||||||
<LinkBottomBar />
|
<LinkBottomBar />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"use client"
|
import React, { useCallback, useEffect, useMemo } from "react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
closestCenter,
|
closestCenter,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
DragStartEvent,
|
DragStartEvent,
|
||||||
UniqueIdentifier
|
UniqueIdentifier
|
||||||
} from "@dnd-kit/core"
|
} from "@dnd-kit/core"
|
||||||
|
import { Primitive } from "@radix-ui/react-primitive"
|
||||||
import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from "@dnd-kit/sortable"
|
import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from "@dnd-kit/sortable"
|
||||||
import { useAccount } from "@/lib/providers/jazz-provider"
|
import { useAccount } from "@/lib/providers/jazz-provider"
|
||||||
import { PersonalLinkLists } from "@/lib/schema/personal-link"
|
import { PersonalLinkLists } from "@/lib/schema/personal-link"
|
||||||
@@ -18,15 +18,20 @@ import { useAtom } from "jotai"
|
|||||||
import { linkSortAtom } from "@/store/link"
|
import { linkSortAtom } from "@/store/link"
|
||||||
import { useKey } from "react-use"
|
import { useKey } from "react-use"
|
||||||
import { LinkItem } from "./partials/link-item"
|
import { LinkItem } from "./partials/link-item"
|
||||||
import { useRef, useState, useCallback, useEffect, useMemo } from "react"
|
|
||||||
import { learningStateAtom } from "./header"
|
|
||||||
import { useQueryState } from "nuqs"
|
import { useQueryState } from "nuqs"
|
||||||
|
import { learningStateAtom } from "./header"
|
||||||
|
import { commandPaletteOpenAtom } from "@/components/custom/command-palette/command-palette"
|
||||||
|
|
||||||
interface LinkListProps {}
|
interface LinkListProps {
|
||||||
|
activeItemIndex: number | null
|
||||||
|
setActiveItemIndex: React.Dispatch<React.SetStateAction<number | null>>
|
||||||
|
}
|
||||||
|
|
||||||
const LinkList: React.FC<LinkListProps> = () => {
|
const LinkList: React.FC<LinkListProps> = ({ activeItemIndex, setActiveItemIndex }) => {
|
||||||
|
const [isCommandPalettePpen] = useAtom(commandPaletteOpenAtom)
|
||||||
const [editId, setEditId] = useQueryState("editId")
|
const [editId, setEditId] = useQueryState("editId")
|
||||||
const [activeLearningState] = useAtom(learningStateAtom)
|
const [activeLearningState] = useAtom(learningStateAtom)
|
||||||
|
const [draggingId, setDraggingId] = React.useState<UniqueIdentifier | null>(null)
|
||||||
|
|
||||||
const { me } = useAccount({
|
const { me } = useAccount({
|
||||||
root: { personalLinks: [] }
|
root: { personalLinks: [] }
|
||||||
@@ -34,9 +39,6 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
const personalLinks = useMemo(() => me?.root?.personalLinks || [], [me?.root?.personalLinks])
|
const personalLinks = useMemo(() => me?.root?.personalLinks || [], [me?.root?.personalLinks])
|
||||||
|
|
||||||
const [sort] = useAtom(linkSortAtom)
|
const [sort] = useAtom(linkSortAtom)
|
||||||
const [focusedId, setFocusedId] = useState<string | null>(null)
|
|
||||||
const [draggingId, setDraggingId] = useState<UniqueIdentifier | null>(null)
|
|
||||||
const linkRefs = useRef<{ [key: string]: HTMLLIElement | null }>({})
|
|
||||||
|
|
||||||
const filteredLinks = useMemo(
|
const filteredLinks = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -67,16 +69,22 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const registerRef = useCallback((id: string, ref: HTMLLIElement | null) => {
|
|
||||||
linkRefs.current[id] = ref
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useKey("Escape", () => {
|
useKey("Escape", () => {
|
||||||
if (editId) {
|
if (editId) {
|
||||||
setEditId(null)
|
setEditId(null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// on mounted, if editId is set, set activeItemIndex to the index of the item with the editId
|
||||||
|
useEffect(() => {
|
||||||
|
if (editId) {
|
||||||
|
const index = sortedLinks.findIndex(link => link?.id === editId)
|
||||||
|
if (index !== -1) {
|
||||||
|
setActiveItemIndex(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [editId, sortedLinks, setActiveItemIndex])
|
||||||
|
|
||||||
const updateSequences = useCallback((links: PersonalLinkLists) => {
|
const updateSequences = useCallback((links: PersonalLinkLists) => {
|
||||||
links.forEach((link, index) => {
|
links.forEach((link, index) => {
|
||||||
if (link) {
|
if (link) {
|
||||||
@@ -87,21 +95,18 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (!me?.root?.personalLinks || sortedLinks.length === 0 || editId !== null) return
|
if (isCommandPalettePpen || !me?.root?.personalLinks || sortedLinks.length === 0 || editId !== null) return
|
||||||
|
|
||||||
const currentIndex = sortedLinks.findIndex(link => link?.id === focusedId)
|
|
||||||
|
|
||||||
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
setActiveItemIndex(prevIndex => {
|
||||||
|
if (prevIndex === null) return 0
|
||||||
const newIndex =
|
const newIndex =
|
||||||
e.key === "ArrowUp" ? Math.max(0, currentIndex - 1) : Math.min(sortedLinks.length - 1, currentIndex + 1)
|
e.key === "ArrowUp" ? Math.max(0, prevIndex - 1) : Math.min(sortedLinks.length - 1, prevIndex + 1)
|
||||||
|
|
||||||
if (e.metaKey && sort === "manual") {
|
if (e.metaKey && sort === "manual") {
|
||||||
const currentLink = me.root.personalLinks[currentIndex]
|
|
||||||
if (!currentLink) return
|
|
||||||
|
|
||||||
const linksArray = [...me.root.personalLinks]
|
const linksArray = [...me.root.personalLinks]
|
||||||
const newLinks = arrayMove(linksArray, currentIndex, newIndex)
|
const newLinks = arrayMove(linksArray, prevIndex, newIndex)
|
||||||
|
|
||||||
while (me.root.personalLinks.length > 0) {
|
while (me.root.personalLinks.length > 0) {
|
||||||
me.root.personalLinks.pop()
|
me.root.personalLinks.pop()
|
||||||
@@ -114,30 +119,32 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
updateSequences(me.root.personalLinks)
|
updateSequences(me.root.personalLinks)
|
||||||
|
|
||||||
const newFocusedLink = me.root.personalLinks[newIndex]
|
|
||||||
if (newFocusedLink) {
|
|
||||||
setFocusedId(newFocusedLink.id)
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
linkRefs.current[newFocusedLink.id]?.focus()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const newFocusedLink = sortedLinks[newIndex]
|
return newIndex
|
||||||
if (newFocusedLink) {
|
|
||||||
setFocusedId(newFocusedLink.id)
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
linkRefs.current[newFocusedLink.id]?.focus()
|
|
||||||
})
|
})
|
||||||
}
|
} else if (e.key === "Enter" && activeItemIndex !== null) {
|
||||||
|
e.preventDefault()
|
||||||
|
const activeLink = sortedLinks[activeItemIndex]
|
||||||
|
if (activeLink) {
|
||||||
|
setEditId(activeLink.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("keydown", handleKeyDown)
|
window.addEventListener("keydown", handleKeyDown)
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown)
|
return () => window.removeEventListener("keydown", handleKeyDown)
|
||||||
}, [me?.root?.personalLinks, sortedLinks, focusedId, editId, sort, updateSequences])
|
}, [
|
||||||
|
me?.root?.personalLinks,
|
||||||
|
sortedLinks,
|
||||||
|
editId,
|
||||||
|
sort,
|
||||||
|
updateSequences,
|
||||||
|
isCommandPalettePpen,
|
||||||
|
activeItemIndex,
|
||||||
|
setEditId,
|
||||||
|
setActiveItemIndex
|
||||||
|
])
|
||||||
|
|
||||||
const handleDragStart = useCallback(
|
const handleDragStart = useCallback(
|
||||||
(event: DragStartEvent) => {
|
(event: DragStartEvent) => {
|
||||||
@@ -185,6 +192,7 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
updateSequences(me.root.personalLinks)
|
updateSequences(me.root.personalLinks)
|
||||||
|
setActiveItemIndex(newIndex)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error during link reordering:", error)
|
console.error("Error during link reordering:", error)
|
||||||
}
|
}
|
||||||
@@ -194,7 +202,10 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-14 flex w-full flex-1 flex-col overflow-y-auto [scrollbar-gutter:stable]">
|
<Primitive.div
|
||||||
|
className="mb-14 flex w-full flex-1 flex-col overflow-y-auto outline-none [scrollbar-gutter:stable]"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
@@ -204,7 +215,7 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
<SortableContext items={sortedLinks.map(item => item?.id || "") || []} strategy={verticalListSortingStrategy}>
|
<SortableContext items={sortedLinks.map(item => item?.id || "") || []} strategy={verticalListSortingStrategy}>
|
||||||
<ul role="list" className="divide-primary/5 divide-y">
|
<ul role="list" className="divide-primary/5 divide-y">
|
||||||
{sortedLinks.map(
|
{sortedLinks.map(
|
||||||
linkItem =>
|
(linkItem, index) =>
|
||||||
linkItem && (
|
linkItem && (
|
||||||
<LinkItem
|
<LinkItem
|
||||||
key={linkItem.id}
|
key={linkItem.id}
|
||||||
@@ -212,17 +223,17 @@ const LinkList: React.FC<LinkListProps> = () => {
|
|||||||
setEditId={setEditId}
|
setEditId={setEditId}
|
||||||
personalLink={linkItem}
|
personalLink={linkItem}
|
||||||
disabled={sort !== "manual" || editId !== null}
|
disabled={sort !== "manual" || editId !== null}
|
||||||
registerRef={registerRef}
|
|
||||||
isDragging={draggingId === linkItem.id}
|
isDragging={draggingId === linkItem.id}
|
||||||
isFocused={focusedId === linkItem.id}
|
isActive={activeItemIndex === index}
|
||||||
setFocusedId={setFocusedId}
|
setActiveItemIndex={setActiveItemIndex}
|
||||||
|
index={index}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
</div>
|
</Primitive.div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import React, { useCallback, useMemo } from "react"
|
import React, { useCallback, useMemo } from "react"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
@@ -15,7 +13,7 @@ import { PersonalLink } from "@/lib/schema/personal-link"
|
|||||||
import { LinkForm } from "./form/link-form"
|
import { LinkForm } from "./form/link-form"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { LEARNING_STATES, LearningStateValue } from "@/lib/constants"
|
import { LEARNING_STATES, LearningStateValue } from "@/lib/constants"
|
||||||
import { linkOpenPopoverForIdAtom, linkShowCreateAtom } from "@/store/link"
|
import { linkOpenPopoverForIdAtom } from "@/store/link"
|
||||||
|
|
||||||
interface LinkItemProps {
|
interface LinkItemProps {
|
||||||
personalLink: PersonalLink
|
personalLink: PersonalLink
|
||||||
@@ -23,9 +21,9 @@ interface LinkItemProps {
|
|||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
setEditId: (id: string | null) => void
|
setEditId: (id: string | null) => void
|
||||||
isDragging: boolean
|
isDragging: boolean
|
||||||
isFocused: boolean
|
isActive: boolean
|
||||||
setFocusedId: (id: string | null) => void
|
setActiveItemIndex: (index: number | null) => void
|
||||||
registerRef: (id: string, ref: HTMLLIElement | null) => void
|
index: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinkItem: React.FC<LinkItemProps> = ({
|
export const LinkItem: React.FC<LinkItemProps> = ({
|
||||||
@@ -34,9 +32,9 @@ export const LinkItem: React.FC<LinkItemProps> = ({
|
|||||||
personalLink,
|
personalLink,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
isDragging,
|
isDragging,
|
||||||
isFocused,
|
isActive,
|
||||||
setFocusedId,
|
setActiveItemIndex,
|
||||||
registerRef
|
index
|
||||||
}) => {
|
}) => {
|
||||||
const [openPopoverForId, setOpenPopoverForId] = useAtom(linkOpenPopoverForIdAtom)
|
const [openPopoverForId, setOpenPopoverForId] = useAtom(linkOpenPopoverForIdAtom)
|
||||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: personalLink.id, disabled })
|
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: personalLink.id, disabled })
|
||||||
@@ -50,14 +48,6 @@ export const LinkItem: React.FC<LinkItemProps> = ({
|
|||||||
[transform, transition, isDragging]
|
[transform, transition, isDragging]
|
||||||
)
|
)
|
||||||
|
|
||||||
const refCallback = useCallback(
|
|
||||||
(node: HTMLLIElement | null) => {
|
|
||||||
setNodeRef(node)
|
|
||||||
registerRef(personalLink.id, node)
|
|
||||||
},
|
|
||||||
[setNodeRef, registerRef, personalLink.id]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
@@ -92,22 +82,24 @@ export const LinkItem: React.FC<LinkItemProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
ref={refCallback}
|
ref={setNodeRef}
|
||||||
style={style as React.CSSProperties}
|
style={style as React.CSSProperties}
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onFocus={() => setFocusedId(personalLink.id)}
|
onFocus={() => setActiveItemIndex(index)}
|
||||||
onBlur={() => setFocusedId(null)}
|
onBlur={() => setActiveItemIndex(null)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
className={cn("relative flex h-14 cursor-default items-center outline-none xl:h-11", {
|
className={cn(
|
||||||
"bg-muted-foreground/10": isFocused,
|
"relative cursor-default outline-none",
|
||||||
"hover:bg-muted/50": !isFocused
|
"grid grid-cols-[auto_1fr_auto] items-center gap-x-2 px-2 py-2 sm:px-4 sm:py-2",
|
||||||
})}
|
{
|
||||||
|
"bg-muted-foreground/10": isActive,
|
||||||
|
"hover:bg-muted/50": !isActive
|
||||||
|
}
|
||||||
|
)}
|
||||||
onDoubleClick={handleRowDoubleClick}
|
onDoubleClick={handleRowDoubleClick}
|
||||||
>
|
>
|
||||||
<div className="flex grow justify-between gap-x-6 px-6 max-lg:px-4">
|
|
||||||
<div className="flex min-w-0 items-center gap-x-4">
|
|
||||||
<Popover
|
<Popover
|
||||||
open={openPopoverForId === personalLink.id}
|
open={openPopoverForId === personalLink.id}
|
||||||
onOpenChange={(open: boolean) => setOpenPopoverForId(open ? personalLink.id : null)}
|
onOpenChange={(open: boolean) => setOpenPopoverForId(open ? personalLink.id : null)}
|
||||||
@@ -136,47 +128,39 @@ export const LinkItem: React.FC<LinkItemProps> = ({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
<div className="flex min-w-0 flex-col items-start gap-y-1 overflow-hidden md:flex-row md:items-center md:gap-x-2">
|
||||||
{personalLink.icon && (
|
{personalLink.icon && (
|
||||||
<Image
|
<Image
|
||||||
src={personalLink.icon}
|
src={personalLink.icon}
|
||||||
alt={personalLink.title}
|
alt={personalLink.title}
|
||||||
className="size-5 rounded-full"
|
className="size-5 shrink-0 rounded-full"
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="w-full min-w-0 flex-auto">
|
<div className="flex min-w-0 flex-col items-start gap-y-1 overflow-hidden md:flex-row md:items-center md:gap-x-2">
|
||||||
<div className="gap-x-2 space-y-0.5 xl:flex xl:flex-row">
|
<p className="text-primary hover:text-primary truncate text-sm font-medium">{personalLink.title}</p>
|
||||||
<p className="text-primary hover:text-primary line-clamp-1 text-sm font-medium xl:truncate">
|
|
||||||
{personalLink.title}
|
|
||||||
</p>
|
|
||||||
{personalLink.url && (
|
{personalLink.url && (
|
||||||
<div className="group flex items-center gap-x-1">
|
<div className="text-muted-foreground flex min-w-0 shrink items-center gap-x-1">
|
||||||
<LaIcon
|
<LaIcon name="Link" aria-hidden="true" className="size-3 flex-none" />
|
||||||
name="Link"
|
|
||||||
aria-hidden="true"
|
|
||||||
className="text-muted-foreground group-hover:text-primary flex-none"
|
|
||||||
/>
|
|
||||||
<Link
|
<Link
|
||||||
href={personalLink.url}
|
href={personalLink.url}
|
||||||
passHref
|
passHref
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
className="text-muted-foreground hover:text-primary text-xs"
|
className="hover:text-primary truncate text-xs"
|
||||||
>
|
>
|
||||||
<span className="xl:truncate">{personalLink.url}</span>
|
{personalLink.url}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex shrink-0 items-center gap-x-4">
|
<div className="flex shrink-0 items-center justify-end">
|
||||||
{personalLink.topic && <Badge variant="secondary">{personalLink.topic.prettyName}</Badge>}
|
{personalLink.topic && <Badge variant="secondary">{personalLink.topic.prettyName}</Badge>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user