@@ -0,0 +1,169 @@
|
||||
import type * as React from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import { ArrowRight } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { IconBadge } from "@/components/gebos/icon-tile"
|
||||
import { PageBreadcrumb } from "@/components/gebos/page-breadcrumb"
|
||||
import { PageHero } from "@/components/gebos/page-hero"
|
||||
import { Container, Section } from "@/components/gebos/section"
|
||||
|
||||
/** The paragraph of the HeizkostenV a property is cited against. */
|
||||
type LegalSource = {
|
||||
cite: string
|
||||
href: string
|
||||
}
|
||||
|
||||
/** One technical property: line icon, title, body, optional legal citation. */
|
||||
type Topic = {
|
||||
icon: React.ReactNode
|
||||
title: string
|
||||
body: string
|
||||
source?: LegalSource
|
||||
}
|
||||
|
||||
const HEIZKOSTENV = "https://www.gesetze-im-internet.de/heizkostenv"
|
||||
|
||||
/** Cited source line, pinned to the card's baseline so rows stay aligned. */
|
||||
function LegalSourceNote({ source }: { source: LegalSource }) {
|
||||
return (
|
||||
<p className="mt-auto pt-4 text-xs text-muted-foreground">
|
||||
Quelle:{" "}
|
||||
<a
|
||||
href={source.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-brand-700 underline-offset-2 transition-colors hover:text-brand-800 hover:underline"
|
||||
>
|
||||
{source.cite}
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
/** Property grid: line icon over a navy title, body copy, optional citation. */
|
||||
function TopicCards({
|
||||
items,
|
||||
className,
|
||||
}: {
|
||||
items: Topic[]
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("mt-10 grid gap-5", className)}>
|
||||
{items.map((item) => (
|
||||
<Card key={item.title}>
|
||||
<CardContent className="flex flex-1 flex-col">
|
||||
<IconBadge variant="outline" shape="squircle" size="lg">
|
||||
{item.icon}
|
||||
</IconBadge>
|
||||
<h3 className="mt-5 text-base font-bold text-balance text-navy">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-pretty text-muted-foreground">
|
||||
{item.body}
|
||||
</p>
|
||||
{item.source ? <LegalSourceNote source={item.source} /> : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared shell for the /wissen articles: split PageHero with the
|
||||
* Startseite → Wissen → article breadcrumb, then the article's own sections.
|
||||
*/
|
||||
function WissenArticle({
|
||||
breadcrumb,
|
||||
eyebrow,
|
||||
title,
|
||||
tagline,
|
||||
lead,
|
||||
visual,
|
||||
children,
|
||||
}: {
|
||||
/** label of the current page, appended to Startseite → Wissen */
|
||||
breadcrumb: string
|
||||
eyebrow?: React.ReactNode
|
||||
title: React.ReactNode
|
||||
/** short bold subline under the H1 (e.g. "Für den Messbetrieb gebaut.") */
|
||||
tagline?: React.ReactNode
|
||||
lead?: React.ReactNode
|
||||
visual?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
breadcrumb={
|
||||
<PageBreadcrumb
|
||||
items={[
|
||||
{ label: "Startseite", to: "/" },
|
||||
{ label: "Wissen", to: "/wissen" },
|
||||
{ label: breadcrumb },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
eyebrow={eyebrow}
|
||||
title={title}
|
||||
lead={
|
||||
<>
|
||||
{tagline ? (
|
||||
<span className="mb-2 block text-lg font-bold text-navy sm:text-xl">
|
||||
{tagline}
|
||||
</span>
|
||||
) : null}
|
||||
{lead}
|
||||
</>
|
||||
}
|
||||
visual={visual}
|
||||
/>
|
||||
<Container>{children}</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** Baseline link to the sibling article, closing an article out. */
|
||||
function WissenRelated({
|
||||
to,
|
||||
title,
|
||||
summary,
|
||||
}: {
|
||||
to: string
|
||||
title: string
|
||||
summary: string
|
||||
}) {
|
||||
return (
|
||||
<Section className="pt-0 sm:pt-0 lg:pt-0">
|
||||
<Link
|
||||
to={to}
|
||||
className="group flex items-center gap-6 rounded-3xl border border-border bg-card p-6 shadow-card transition-colors hover:border-brand-200 sm:p-8"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-xs font-bold tracking-[0.14em] text-primary uppercase">
|
||||
Weiterlesen
|
||||
</p>
|
||||
<h2 className="mt-2 text-xl font-extrabold tracking-tight text-balance text-navy">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm leading-relaxed text-pretty text-muted-foreground">
|
||||
{summary}
|
||||
</p>
|
||||
</div>
|
||||
<IconBadge
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="transition-transform group-hover:translate-x-0.5"
|
||||
>
|
||||
<ArrowRight />
|
||||
</IconBadge>
|
||||
</Link>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
export { HEIZKOSTENV, TopicCards, WissenArticle, WissenRelated }
|
||||
export type { LegalSource, Topic }
|
||||
Reference in New Issue
Block a user