Add js web stuff to landing page + documentation
This commit is contained in:
127
go/jsruntime/uikit/Accordion.tsx
Normal file
127
go/jsruntime/uikit/Accordion.tsx
Normal 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-line rounded-default overflow-hidden";
|
||||
const ITEM = "ui-accordion-item border-b border-line 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-ink bg-surface-muted cursor-pointer border-none transition-colors hover:bg-surface-raised active:bg-surface-strong focus:outline-hidden disabled:text-ink-faint disabled:cursor-not-allowed disabled:bg-surface-muted";
|
||||
const TITLE = "ui-accordion-title flex-1";
|
||||
const CONTENT = "ui-accordion-content px-4 pb-4 text-ink";
|
||||
|
||||
function iconCls(open: boolean): string {
|
||||
return "ui-accordion-icon text-ink-muted 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user