This commit is contained in:
83
src/components/SSExpandable.tsx
Normal file
83
src/components/SSExpandable.tsx
Normal 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 };
|
||||
Reference in New Issue
Block a user