Files
SkinbaseNova/resources/js/components/artwork/ArtworkDescription.jsx
2026-02-22 17:09:34 +01:00

76 lines
2.1 KiB
JavaScript

import React, { useMemo, useState } from 'react'
const COLLAPSE_AT = 560
function renderMarkdownSafe(text) {
const lines = text.split(/\n{2,}/)
return lines.map((line, lineIndex) => {
const parts = []
let rest = line
let key = 0
const linkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g
let match = linkPattern.exec(rest)
let lastIndex = 0
while (match) {
if (match.index > lastIndex) {
parts.push(<span key={`txt-${lineIndex}-${key++}`}>{rest.slice(lastIndex, match.index)}</span>)
}
parts.push(
<a
key={`lnk-${lineIndex}-${key++}`}
href={match[2]}
target="_blank"
rel="noopener noreferrer nofollow"
className="text-accent hover:underline"
>
{match[1]}
</a>,
)
lastIndex = match.index + match[0].length
match = linkPattern.exec(rest)
}
if (lastIndex < rest.length) {
parts.push(<span key={`txt-${lineIndex}-${key++}`}>{rest.slice(lastIndex)}</span>)
}
return (
<p key={`p-${lineIndex}`} className="text-base leading-7 text-soft">
{parts}
</p>
)
})
}
export default function ArtworkDescription({ artwork }) {
const [expanded, setExpanded] = useState(false)
const content = (artwork?.description || '').trim()
if (content.length === 0) return null
const collapsed = content.length > COLLAPSE_AT && !expanded
const visibleText = collapsed ? `${content.slice(0, COLLAPSE_AT)}` : content
const rendered = useMemo(() => renderMarkdownSafe(visibleText), [visibleText])
return (
<section className="rounded-xl bg-panel p-5 shadow-lg shadow-deep/30">
<h2 className="text-xs font-semibold uppercase tracking-wide text-soft">Description</h2>
<div className="mt-4 max-w-[720px] space-y-4">{rendered}</div>
{content.length > COLLAPSE_AT && (
<button
type="button"
className="mt-4 text-sm font-medium text-accent hover:underline"
onClick={() => setExpanded((value) => !value)}
>
{expanded ? 'Show less' : 'Show more'}
</button>
)}
</section>
)
}