84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
import { JSXElement, createMemo, createSignal, onCleanup } from 'solid-js';
|
|
|
|
type SSExpandableProps = {
|
|
title: JSXElement;
|
|
class?: string;
|
|
children?: JSXElement;
|
|
style?: string;
|
|
initiallyExpanded?: boolean;
|
|
};
|
|
|
|
const TRANSITION_MS = 200;
|
|
|
|
function SSExpandable(props: SSExpandableProps) {
|
|
const [height, setHeight] = createSignal<string | number>(props.initiallyExpanded ? 'auto' : 0);
|
|
const isExpanded = createMemo(() => height() !== 0);
|
|
let contentRef: HTMLDivElement | undefined;
|
|
let timeoutId: number | undefined;
|
|
|
|
const toggle = () => {
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
const targetHeight = contentRef?.scrollHeight ?? 0;
|
|
|
|
if (isExpanded()) {
|
|
setHeight(targetHeight);
|
|
timeoutId = window.setTimeout(() => setHeight(0), 1);
|
|
return;
|
|
}
|
|
|
|
setHeight(targetHeight);
|
|
timeoutId = window.setTimeout(() => setHeight('auto'), TRANSITION_MS);
|
|
};
|
|
|
|
onCleanup(() => {
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
});
|
|
|
|
return (
|
|
<div
|
|
class={`ss_expandable ${props.class ?? ''}`}
|
|
style={props.style}
|
|
data-state={isExpanded() ? 'open' : 'closed'}
|
|
>
|
|
<div
|
|
class="ss_expandable__header"
|
|
role="button"
|
|
tabindex="0"
|
|
aria-expanded={isExpanded()}
|
|
onclick={toggle}
|
|
onkeydown={event => {
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
event.preventDefault();
|
|
toggle();
|
|
}
|
|
}}
|
|
>
|
|
<span class="ss_expandable__icon" aria-hidden="true">
|
|
{isExpanded() ? (
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-down"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6 9l6 6l6 -6" /></svg>
|
|
) : (
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l6 6l-6 6" /></svg>
|
|
)}
|
|
</span>
|
|
<span class="ss_expandable__title">{props.title}</span>
|
|
</div>
|
|
|
|
<div
|
|
ref={el => (contentRef = el)}
|
|
class="ss_expandable__content"
|
|
style={{
|
|
height: (typeof height() === 'number' ? `${height()}px` : height()) as any,
|
|
'transition-duration': `${TRANSITION_MS}ms`,
|
|
}}
|
|
>
|
|
<div class='ss_expandable__divider_wrapper'>
|
|
<div class="ss_expandable__divider" />
|
|
{props.children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export { SSExpandable };
|