@@ -31,6 +31,15 @@ type ExpandingCard = {
|
|||||||
/** which side of the container the open card sits on – the rail takes the other */
|
/** which side of the container the open card sits on – the rail takes the other */
|
||||||
type Side = "left" | "right"
|
type Side = "left" | "right"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the minimised cards are allowed to gather. `auto` mirrors the pointer –
|
||||||
|
* the picked card keeps its half, so the rail changes sides as you browse.
|
||||||
|
* Pinning it to one side trades that spatial cue for calm: picking out of the
|
||||||
|
* rail then shifts the column by a single slot instead of sending every card
|
||||||
|
* across the container.
|
||||||
|
*/
|
||||||
|
type RailSide = Side | "auto"
|
||||||
|
|
||||||
/** the face a card currently shows */
|
/** the face a card currently shows */
|
||||||
type Face = "brief" | "rail" | "open"
|
type Face = "brief" | "rail" | "open"
|
||||||
|
|
||||||
@@ -567,9 +576,10 @@ function GridCard({
|
|||||||
/**
|
/**
|
||||||
* Card grid that unpacks in place. Clicking a card grows it to the full height
|
* Card grid that unpacks in place. Clicking a card grows it to the full height
|
||||||
* of the grid it was part of, while the remaining cards gather into a column of
|
* of the grid it was part of, while the remaining cards gather into a column of
|
||||||
* a third of the width beside it – on the side the open card came *from*, so
|
* a third of the width beside it – by default on the side the open card came
|
||||||
* the card you picked stays where you clicked it and everything else moves out
|
* *from*, so the card you picked stays where you clicked it and everything else
|
||||||
* of its way. Opening a card out of the rail therefore flips the composition.
|
* moves out of its way. Opening a card out of the rail therefore flips the
|
||||||
|
* composition; pass `railSide` to pin the column and keep the travel short.
|
||||||
*
|
*
|
||||||
* Every card is absolutely positioned from measured geometry, which keeps a
|
* Every card is absolutely positioned from measured geometry, which keeps a
|
||||||
* state change to a transform plus a size: the faces are pre-laid out at the
|
* state change to a transform plus a size: the faces are pre-laid out at the
|
||||||
@@ -578,9 +588,12 @@ function GridCard({
|
|||||||
*/
|
*/
|
||||||
function ExpandingCardGrid({
|
function ExpandingCardGrid({
|
||||||
items,
|
items,
|
||||||
|
railSide = "auto",
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
items: ExpandingCard[]
|
items: ExpandingCard[]
|
||||||
|
/** side the minimised cards stack on; `auto` follows the card you clicked */
|
||||||
|
railSide?: RailSide
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = React.useState<{ id: string; side: Side } | null>(
|
const [open, setOpen] = React.useState<{ id: string; side: Side } | null>(
|
||||||
@@ -592,13 +605,18 @@ function ExpandingCardGrid({
|
|||||||
const uid = React.useId()
|
const uid = React.useId()
|
||||||
|
|
||||||
const active = open ? items.findIndex((item) => item.id === open.id) : -1
|
const active = open ? items.findIndex((item) => item.id === open.id) : -1
|
||||||
|
/* a pinned rail decides the composition outright; the pointer-driven side
|
||||||
|
stays in state either way, so switching back to `auto` resumes from the
|
||||||
|
half the reader last picked instead of jumping */
|
||||||
|
const pinned =
|
||||||
|
railSide === "auto" ? null : railSide === "left" ? "right" : "left"
|
||||||
const plan = planLayout({
|
const plan = planLayout({
|
||||||
count: items.length,
|
count: items.length,
|
||||||
width: metrics.width,
|
width: metrics.width,
|
||||||
teaserHeights: metrics.teaser,
|
teaserHeights: metrics.teaser,
|
||||||
copyHeights: metrics.copy,
|
copyHeights: metrics.copy,
|
||||||
active,
|
active,
|
||||||
side: open?.side ?? "left",
|
side: pinned ?? open?.side ?? "left",
|
||||||
})
|
})
|
||||||
|
|
||||||
/* transitions only after the measured geometry has been painted once, and
|
/* transitions only after the measured geometry has been painted once, and
|
||||||
@@ -622,7 +640,8 @@ function ExpandingCardGrid({
|
|||||||
}
|
}
|
||||||
const frame = plan.frames[index]
|
const frame = plan.frames[index]
|
||||||
/* the card keeps its side: whichever half it is sitting in when clicked is
|
/* the card keeps its side: whichever half it is sitting in when clicked is
|
||||||
the half it opens into, so the rail always forms on the other one */
|
the half it opens into, so the rail forms on the other one – unless a
|
||||||
|
pinned `railSide` overrules it above */
|
||||||
const onRight = frame
|
const onRight = frame
|
||||||
? frame.x + frame.w / 2 > metrics.width / 2 + 1
|
? frame.x + frame.w / 2 > metrics.width / 2 + 1
|
||||||
: false
|
: false
|
||||||
|
|||||||
@@ -531,7 +531,11 @@ export default function HomePage() {
|
|||||||
title="Alles, was für den laufenden Messbetrieb benötigt wird"
|
title="Alles, was für den laufenden Messbetrieb benötigt wird"
|
||||||
lead="SolenOS verbindet Messtechnik, Datenerfassung und Software zu einem durchgängigen System. Statt einzelne Komponenten selbst zusammenzustellen und miteinander zu verbinden, erhalten Sie eine Lösung, bei der Bestellung, Hardware, Konfiguration und laufender Betrieb zusammenspielen."
|
lead="SolenOS verbindet Messtechnik, Datenerfassung und Software zu einem durchgängigen System. Statt einzelne Komponenten selbst zusammenzustellen und miteinander zu verbinden, erhalten Sie eine Lösung, bei der Bestellung, Hardware, Konfiguration und laufender Betrieb zusammenspielen."
|
||||||
/>
|
/>
|
||||||
<ExpandingCardGrid items={operationsScope} className="mt-12" />
|
<ExpandingCardGrid
|
||||||
|
items={operationsScope}
|
||||||
|
railSide="right"
|
||||||
|
className="mt-12"
|
||||||
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section>
|
<Section>
|
||||||
|
|||||||
Reference in New Issue
Block a user