move tsx kit -> uikit

This commit is contained in:
2026-07-13 09:17:11 -04:00
parent 98978e4930
commit 9243d142e9
37 changed files with 20 additions and 19 deletions

127
web/uikit/Accordion.tsx Normal file
View File

@@ -0,0 +1,127 @@
import { createSignal, createEffect, For, Show, JSXElement } from "solid-js";
import { Icon } from "./Icons.tsx";
// Baseline Tailwind for the scoped .ui-accordion stack. The ui-* class
// names are kept so page-specific CSS overrides (e.g. sale.css,
// licensing.css) can continue to layer on top.
const ROOT = "ui-accordion border border-neutral-200 rounded-default overflow-hidden";
const ITEM = "ui-accordion-item border-b border-neutral-200 last:border-b-0";
const TRIGGER = "ui-accordion-trigger flex items-center justify-between w-full py-3 px-4 text-left font-medium text-neutral-900 bg-neutral-50 cursor-pointer border-none transition-colors hover:bg-neutral-100 active:bg-neutral-200 focus:outline-hidden disabled:text-neutral-400 disabled:cursor-not-allowed disabled:bg-neutral-50";
const TITLE = "ui-accordion-title flex-1";
const CONTENT = "ui-accordion-content px-4 pb-4 text-neutral-700";
function iconCls(open: boolean): string {
return "ui-accordion-icon text-neutral-500 leading-none transition-transform duration-200" +
(open ? " rotate-180" : "");
}
interface AccordionItemProps {
startOpen?: boolean;
isOpen?: boolean;
disabled?: boolean;
title?: string;
children?: JSXElement;
}
export function AccordionItem(props: AccordionItemProps) {
const getStartOpen = () => typeof props.startOpen === "function" ? (props.startOpen as () => boolean)() : !!props.startOpen;
const [isOpen, setIsOpen] = createSignal(getStartOpen());
createEffect(() => {
if (props.isOpen === undefined) return;
const v = typeof props.isOpen === "function" ? (props.isOpen as () => boolean)() : !!props.isOpen;
setIsOpen(v);
});
const isDisabled = () => typeof props.disabled === "function" ? (props.disabled as () => boolean)() : !!props.disabled;
const title = () => typeof props.title === "function" ? (props.title as () => string)() : props.title;
const toggle = () => {
if (isDisabled()) return;
setIsOpen(!isOpen());
};
return (
<div class={ITEM}>
<button type="button" class={TRIGGER} disabled={isDisabled()} onclick={toggle} aria-expanded={isOpen()}>
<span class={TITLE}>{title()}</span>
<Show when={!isDisabled()}>
<span class={iconCls(isOpen())}>
<Icon icon={isOpen() ? "chevron-up" : "chevron-down"} size={18}/>
</span>
</Show>
</button>
<Show when={isOpen() && !isDisabled()}>
<div class={CONTENT}>
{props.children}
</div>
</Show>
</div>
);
}
interface AccordionItemData {
title: string;
content: JSXElement;
disabled?: boolean;
}
interface AccordionProps {
items?: AccordionItemData[];
}
export function Accordion(props: AccordionProps) {
const items = () => props.items || [];
return (
<div class={ROOT}>
<For each={items()}>{(item) => (
<AccordionItem title={(() => {
const t = item.title;
return typeof t === "function" ? (t as () => string)() : t;
})()} disabled={(() => {
const d = item.disabled;
return typeof d === "function" ? (d as () => boolean)() : !!d;
})()}>
{item.content}
</AccordionItem>
)}</For>
</div>
);
}
interface SingleAccordionProps {
items?: AccordionItemData[];
startOpen?: number;
}
export function SingleAccordion(props: SingleAccordionProps) {
const items = () => props.items || [];
const [openIndex, setOpenIndex] = createSignal(props.startOpen ?? 0);
const toggleItem = (index: number) => {
setOpenIndex(openIndex() === index ? -1 : index);
};
return (
<div class={ROOT}>
<For each={items()}>{(item, index) => (
<div class={ITEM}>
<button type="button" class={TRIGGER} disabled={!!item.disabled} onclick={() => toggleItem(index())} aria-expanded={openIndex() === index()}>
<span class={TITLE}>{item.title}</span>
<Show when={!item.disabled}>
<span class={iconCls(openIndex() === index())}>
<Icon icon={openIndex() === index() ? "chevron-up" : "chevron-down"} size={18}/>
</span>
</Show>
</button>
<Show when={openIndex() === index() && !item.disabled}>
<div class={CONTENT}>
{item.content}
</div>
</Show>
</div>
)}</For>
</div>
);
}