animation ein/ausklappen eines tenants hinzugefügt
This commit is contained in:
parent
eb0a58aaa7
commit
fa7988b4b6
4 changed files with 126 additions and 15 deletions
|
|
@ -31,9 +31,15 @@
|
|||
|
||||
.chevronIcon {
|
||||
font-size: 11px;
|
||||
transition: transform 0.15s ease;
|
||||
transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.chevronIconExpanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.chevronIcon {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import styles from './FormGeneratorTable.module.css';
|
||||
|
||||
export interface ExpandableDetailRowProps {
|
||||
open: boolean;
|
||||
colSpan: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table detail row with smooth expand/collapse (grid 0fr → 1fr).
|
||||
* Stays mounted until the close animation finishes.
|
||||
*/
|
||||
export function ExpandableDetailRow({ open, colSpan, children }: ExpandableDetailRowProps) {
|
||||
const [mounted, setMounted] = useState(open);
|
||||
const [revealed, setRevealed] = useState(false);
|
||||
const openRef = useRef(open);
|
||||
openRef.current = open;
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setMounted(true);
|
||||
const id = requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (openRef.current) setRevealed(true);
|
||||
});
|
||||
});
|
||||
return () => cancelAnimationFrame(id);
|
||||
}
|
||||
setRevealed(false);
|
||||
const fallback = window.setTimeout(() => {
|
||||
if (!openRef.current) setMounted(false);
|
||||
}, 420);
|
||||
return () => window.clearTimeout(fallback);
|
||||
}, [open]);
|
||||
|
||||
const handleTransitionEnd = useCallback((e: React.TransitionEvent<HTMLDivElement>) => {
|
||||
if (e.target !== e.currentTarget) return;
|
||||
if (e.propertyName !== 'grid-template-rows') return;
|
||||
if (!openRef.current) {
|
||||
setMounted(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<tr className={styles.expandedDetailRow}>
|
||||
<td
|
||||
colSpan={colSpan}
|
||||
className={`${styles.expandedDetailCell} ${revealed ? styles.expandedDetailCellOpen : ''}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
className={`${styles.expandedCollapsible} ${revealed ? styles.expandedCollapsibleOpen : ''}`}
|
||||
onTransitionEnd={handleTransitionEnd}
|
||||
>
|
||||
<div className={styles.expandedCollapsibleInner}>
|
||||
<div className={styles.expandedDetailInner}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExpandableDetailRow;
|
||||
|
|
@ -592,10 +592,37 @@
|
|||
}
|
||||
|
||||
.expandedDetailCell {
|
||||
padding: 12px 16px 16px;
|
||||
padding: 0 16px;
|
||||
vertical-align: top;
|
||||
border-bottom: 2px solid var(--color-border, #e2e8f0);
|
||||
border-bottom: 2px solid transparent;
|
||||
box-sizing: border-box;
|
||||
transition:
|
||||
padding 0.32s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-color 0.32s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.expandedDetailCellOpen {
|
||||
padding: 12px 16px 16px;
|
||||
border-bottom-color: var(--color-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.expandedCollapsible {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
opacity: 0;
|
||||
transition:
|
||||
grid-template-rows 0.36s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.expandedCollapsibleOpen {
|
||||
grid-template-rows: 1fr;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.expandedCollapsibleInner {
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.expandedDetailInner {
|
||||
|
|
@ -604,6 +631,24 @@
|
|||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
transform: translateY(-6px);
|
||||
transition: transform 0.36s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.expandedCollapsibleOpen .expandedDetailInner {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.expandedDetailCell,
|
||||
.expandedCollapsible,
|
||||
.expandedDetailInner {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.expandedCollapsibleOpen .expandedDetailInner {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Items that live inside a group — subtle tint + left connector */
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ import {
|
|||
isNumberType,
|
||||
} from '../../../utils/attributeTypeMapper';
|
||||
import type { AttributeType } from '../../../utils/attributeTypeMapper';
|
||||
import { ExpandableDetailRow } from './ExpandableDetailRow';
|
||||
import { FaFilter } from 'react-icons/fa';
|
||||
import api from '../../../api';
|
||||
import { PeriodPicker } from '../../PeriodPicker';
|
||||
|
|
@ -2760,18 +2761,10 @@ export function FormGeneratorTable<T extends Record<string, any>>({
|
|||
);
|
||||
})}
|
||||
</tr>
|
||||
{isExpanded && renderExpandedRow && (
|
||||
<tr className={styles.expandedDetailRow}>
|
||||
<td
|
||||
colSpan={detailColSpan}
|
||||
className={styles.expandedDetailCell}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className={styles.expandedDetailInner}>
|
||||
{renderExpandedRow(row)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{renderExpandedRow && (
|
||||
<ExpandableDetailRow open={isExpanded} colSpan={detailColSpan}>
|
||||
{renderExpandedRow(row)}
|
||||
</ExpandableDetailRow>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue