initial commit
Some checks failed
CodeQL / Analyze (push) Failing after 1m32s

This commit is contained in:
Ludwig Lehnert
2026-01-11 11:08:48 +01:00
commit 0efd3d954b
58 changed files with 19390 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
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 };