chore: heygermany customer project
Some checks failed
Main / Setup and Test (push) Has been cancelled
Main / Build Website (push) Has been cancelled

This commit is contained in:
e2e
2026-07-11 10:40:10 +02:00
commit 6e43ee3df9
394 changed files with 38048 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
import { useState, useRef, useEffect, ReactNode } from 'react'
import {
Container,
Stack,
Text,
ThemeIcon,
Box,
rem,
Collapse,
useMantineTheme,
Flex,
} from '@pikku/mantine/core'
import { asI18n } from '@pikku/react'
export interface ExpandableStep {
title: string
icon: ReactNode
content: () => ReactNode
}
interface ExpandableStepsProps {
steps: ExpandableStep[]
defaultExpanded?: number[]
singleExpand?: boolean
containerSize?: string
showTitle?: boolean
title?: string
}
export function ExpandableSteps({
steps,
defaultExpanded = [0],
singleExpand = true,
containerSize = 'xl',
showTitle = false,
title,
}: ExpandableStepsProps) {
const theme = useMantineTheme()
const [expandedSteps, setExpandedSteps] = useState(() => new Set(defaultExpanded))
const contentRefs = useRef<(HTMLDivElement | null)[]>([])
const [heights, setHeights] = useState<number[]>([])
const CIRCLE_SIZE = 38
const GAP = 8
useEffect(() => {
const observers: ResizeObserver[] = []
contentRefs.current.forEach((el, i) => {
if (!el) return
const observer = new ResizeObserver(() => {
setHeights((prev) => {
const copy = [...prev]
copy[i] = el.offsetHeight
return copy
})
})
observer.observe(el)
observers.push(observer)
})
return () => {
observers.forEach((observer) => observer.disconnect())
}
}, [])
const calculateLineHeight = (index: number): number => {
if (index === steps.length - 1) return 0
const contentHeight = heights[index] ?? 0
return contentHeight + GAP * 2
}
const toggleStep = (index: number) => {
if (singleExpand) {
setExpandedSteps(() => new Set([index]))
} else {
setExpandedSteps((prev) => {
const newSet = new Set(prev)
if (newSet.has(index)) {
newSet.delete(index)
} else {
newSet.add(index)
}
return newSet
})
}
}
return (
<Container size='xl' py="xl" mt="xl">
<Stack
gap="lg"
mt="xl"
w="100%"
maw={{ base: rem(300), md: rem(800) }}
mx="auto"
>
{showTitle && title && (
<Text fz="xxll" fw={700} ta='center' mb='xl'>
{asI18n(title)}
</Text>
)}
{steps.map((step, index) => {
const isActive = expandedSteps.has(index)
const lineHeight =
isActive && index !== steps.length - 1
? calculateLineHeight(index)
: 0
return (
<Box
key={step.title}
pos="relative"
display="flex"
pl={{ base: 0, md: 30 }}
mb={8}
mih={40}
c={isActive ? theme.colors.primary[6] : theme.colors.gray[9]}
style={{ cursor: 'pointer' }}
>
<Box
w={CIRCLE_SIZE}
pos="relative"
display="flex"
style={{
flexDirection: 'column',
alignItems: 'center',
userSelect: 'none',
}}
>
<Box
w={CIRCLE_SIZE}
h={CIRCLE_SIZE}
bg="white"
c={
isActive ? theme.colors.primary[6] : theme.colors.gray[9]
}
style={{
borderRadius: '50%',
border: `2px solid ${theme.colors.gray[4]}`,
fontSize: rem(16),
fontWeight: 700,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
transform: `translateY(${rem(4)})`,
zIndex: 2,
}}
onClick={() => toggleStep(index)}
>
{index + 1}
</Box>
{lineHeight > 0 && (
<Box
w={2}
h={lineHeight}
bg={`${theme.colors.gray[4]}`}
mt={GAP}
mb={GAP}
style={{
transition:
'height 300ms cubic-bezier(.4,2,.6,1), background-color 250ms ease',
}}
/>
)}
</Box>
<Box flex={1} me={10} onClick={() => toggleStep(index)}>
<Flex
align="flex-start"
gap="xs"
style={{ wordBreak: 'break-word' }}
mb={6}
>
<ThemeIcon
size={44}
variant="transparent"
color={isActive ? 'primary' : `${theme.colors.gray[8]}`}
radius="xl"
style={{ flexShrink: 0 }}
>
{step.icon}
</ThemeIcon>
<Text
fw={700}
fz={25}
style={{
whiteSpace: 'normal',
wordBreak: 'break-word',
flexGrow: 1,
flexShrink: 1,
}}
ta={'left'}
c={isActive ? 'primary' : `${theme.colors.gray[8]}`}
>
{asI18n(step.title)}
</Text>
</Flex>
<Collapse in={isActive} transitionDuration={250}>
<Container
ref={(el) => {
contentRefs.current[index] = el
}}
id={`step-${index}-content`}
p={0}
mb="md"
>
{step.content()}
</Container>
</Collapse>
</Box>
</Box>
)
})}
</Stack>
</Container>
)
}