All checks were successful
Deploy Nyla Frontend to Integration / deploy (push) Successful in 1m26s
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
// Copyright (c) 2026 PowerOn AG
|
|
// All rights reserved.
|
|
import { type FC, useState, useEffect, useCallback } from 'react';
|
|
import { useLocation } from 'react-router-dom';
|
|
import { FaChevronDown, FaChevronRight } from 'react-icons/fa';
|
|
import type { PanelProps } from './types';
|
|
import styles from './Panel.module.css';
|
|
|
|
function _loadCollapsed(key: string, fallback: boolean): boolean {
|
|
try {
|
|
const stored = localStorage.getItem(`panel-collapse:${key}`);
|
|
if (stored !== null) return stored === '1';
|
|
} catch { /* noop */ }
|
|
return fallback;
|
|
}
|
|
|
|
function _saveCollapsed(key: string, value: boolean): void {
|
|
try {
|
|
localStorage.setItem(`panel-collapse:${key}`, value ? '1' : '0');
|
|
} catch { /* noop */ }
|
|
}
|
|
|
|
export const Panel: FC<PanelProps> = ({
|
|
variant = 'card',
|
|
title,
|
|
id,
|
|
subtitle,
|
|
actions,
|
|
collapsible = true,
|
|
defaultCollapsed = false,
|
|
collapseKey,
|
|
className = '',
|
|
style,
|
|
fill = false,
|
|
children,
|
|
}) => {
|
|
const { pathname } = useLocation();
|
|
const persistKey = collapseKey ?? `${pathname}:${id}`;
|
|
const [collapsed, setCollapsed] = useState(() => _loadCollapsed(persistKey, defaultCollapsed));
|
|
|
|
useEffect(() => {
|
|
_saveCollapsed(persistKey, collapsed);
|
|
}, [persistKey, collapsed]);
|
|
|
|
const _toggleCollapsed = useCallback(() => {
|
|
if (collapsible) setCollapsed((prev) => !prev);
|
|
}, [collapsible]);
|
|
|
|
return (
|
|
<div
|
|
className={`${styles.panel} ${collapsed ? styles.panelCollapsed : ''} ${className}`}
|
|
data-variant={variant}
|
|
data-fill={fill ? 'true' : undefined}
|
|
data-panel-id={id}
|
|
style={style}
|
|
>
|
|
<div
|
|
className={`${styles.header} ${collapsible ? styles.headerCollapsible : ''}`}
|
|
role={collapsible ? 'button' : undefined}
|
|
tabIndex={collapsible ? 0 : undefined}
|
|
aria-expanded={collapsible ? !collapsed : undefined}
|
|
onClick={_toggleCollapsed}
|
|
onKeyDown={
|
|
collapsible
|
|
? (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
_toggleCollapsed();
|
|
}
|
|
}
|
|
: undefined
|
|
}
|
|
>
|
|
<div className={styles.titles}>
|
|
<span className={styles.title}>{title}</span>
|
|
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
|
|
</div>
|
|
{actions && <div className={styles.actions} onClick={(e) => e.stopPropagation()}>{actions}</div>}
|
|
{collapsible && (
|
|
<span className={styles.chevron} aria-hidden>
|
|
{collapsed ? <FaChevronRight /> : <FaChevronDown />}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className={`${styles.body} ${collapsed ? styles.bodyHidden : ''}`}>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|