# @f0rbit/ui v0.1.8 - Full Documentation > A minimal, composable UI component library for SolidJS This document contains the complete source code and documentation for LLM consumption. ## Installation ```bash bun add @f0rbit/ui ``` ## Setup ```tsx import "@f0rbit/ui/styles.css"; import { Button, Card, Modal } from "@f0rbit/ui"; ``` --- ## Components ### Badge ```tsx import { JSX, splitProps } from "solid-js"; export type BadgeVariant = "default" | "success" | "error" | "warning" | "info" | "accent"; export interface BadgeProps extends JSX.HTMLAttributes { variant?: BadgeVariant; } const variantClasses: Record = { default: "", success: "badge-success", error: "badge-error", warning: "badge-warning", info: "badge-info", accent: "badge-accent", }; export function Badge(props: BadgeProps) { const [local, rest] = splitProps(props, ["variant", "class", "children"]); const classes = () => { const parts = ["badge"]; const variant = local.variant ?? "default"; if (variant !== "default") { parts.push(variantClasses[variant]); } if (local.class) { parts.push(local.class); } return parts.join(" "); }; return ( {local.children} ); } ``` --- ### Button ```tsx import { JSX, splitProps } from "solid-js"; export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger"; export type ButtonSize = "sm" | "md" | "lg"; export interface ButtonProps extends JSX.ButtonHTMLAttributes { variant?: ButtonVariant; size?: ButtonSize; icon?: boolean; label?: string; loading?: boolean; } const variantClasses: Record = { primary: "", secondary: "btn-secondary", ghost: "btn-ghost", danger: "btn-danger", }; const sizeClasses: Record = { sm: "btn-sm", md: "", lg: "btn-lg", }; export function Button(props: ButtonProps) { const [local, rest] = splitProps(props, ["variant", "size", "icon", "label", "loading", "class", "disabled", "children"]); const classes = () => { const parts = ["btn"]; parts.push(variantClasses[local.variant ?? "primary"]); if (local.size && local.size !== "md") { parts.push(sizeClasses[local.size]); } if (local.icon) { parts.push("btn-icon"); } if (local.class) { parts.push(local.class); } return parts.join(" "); }; return ( ); } ``` --- ### Card ```tsx import { JSX, splitProps } from "solid-js"; export interface CardProps extends JSX.HTMLAttributes { interactive?: boolean; } export interface CardHeaderProps extends JSX.HTMLAttributes {} export interface CardTitleProps extends JSX.HTMLAttributes {} export interface CardDescriptionProps extends JSX.HTMLAttributes {} export interface CardContentProps extends JSX.HTMLAttributes {} export interface CardFooterProps extends JSX.HTMLAttributes {} export function Card(props: CardProps) { const [local, rest] = splitProps(props, ["interactive", "class", "children"]); const classes = () => { const parts = ["card"]; if (local.interactive) { parts.push("card-interactive"); } if (local.class) { parts.push(local.class); } return parts.join(" "); }; return (
{local.children}
); } export function CardHeader(props: CardHeaderProps) { const [local, rest] = splitProps(props, ["class", "children"]); return (
{local.children}
); } export function CardTitle(props: CardTitleProps) { const [local, rest] = splitProps(props, ["class", "children"]); return (

{local.children}

); } export function CardDescription(props: CardDescriptionProps) { const [local, rest] = splitProps(props, ["class", "children"]); return (

{local.children}

); } export function CardContent(props: CardContentProps) { const [local, rest] = splitProps(props, ["class", "children"]); return (
{local.children}
); } export function CardFooter(props: CardFooterProps) { const [local, rest] = splitProps(props, ["class", "children"]); return ( ); } ``` --- ### Checkbox ```tsx import { type JSX, splitProps, createEffect, onMount } from "solid-js"; export interface CheckboxProps extends Omit, "type"> { label?: string; description?: string; indeterminate?: boolean; } export function Checkbox(props: CheckboxProps) { const [local, rest] = splitProps(props, ["label", "description", "indeterminate", "class", "disabled", "id"]); let inputRef: HTMLInputElement | undefined; const setIndeterminate = () => { if (inputRef) inputRef.indeterminate = local.indeterminate ?? false; }; onMount(setIndeterminate); createEffect(setIndeterminate); const classes = () => `checkbox ${local.disabled ? "checkbox-disabled" : ""} ${local.class ?? ""}`.trim(); const inputId = () => local.id ?? (local.label ? `checkbox-${local.label.toLowerCase().replace(/\s+/g, "-")}` : undefined); return ( ); } ``` --- ### Chevron ```tsx import { type JSX, splitProps } from "solid-js"; export type ChevronFacing = "right" | "down"; export interface ChevronProps extends JSX.SvgSVGAttributes { expanded?: boolean; /** Which way the chevron points. Default: "right" */ facing?: ChevronFacing; size?: number | string; } export function Chevron(props: ChevronProps) { const [local, rest] = splitProps(props, ["expanded", "facing", "size", "class"]); const classes = () => { const parts = ["chevron"]; if (local.facing === "down") { parts.push("chevron-down"); } if (local.expanded) { parts.push("active"); } if (local.class) { parts.push(local.class); } return parts.join(" "); }; const size = () => local.size ?? "1em"; return ( ); } ``` --- ### Clamp ```tsx import { type JSX, splitProps, createSignal, Show } from "solid-js"; export interface ClampProps extends JSX.HTMLAttributes { lines?: number; showMoreText?: string; showLessText?: string; } export function Clamp(props: ClampProps) { const [local, rest] = splitProps(props, ["lines", "showMoreText", "showLessText", "class", "children"]); const lines = () => local.lines ?? 3; const showMoreText = () => local.showMoreText ?? "show more"; const showLessText = () => local.showLessText ?? "show less"; const [expanded, setExpanded] = createSignal(false); const [overflows, setOverflows] = createSignal(false); let contentRef: HTMLDivElement | undefined; const checkOverflow = () => { if (!contentRef) return; setOverflows(contentRef.scrollHeight > contentRef.clientHeight); }; const toggle = () => setExpanded(prev => !prev); const wrapperClasses = () => `clamp ${local.class ?? ""}`.trim(); const contentClasses = () => (expanded() ? "clamp-content" : "clamp-content clamp-clamped"); return (
{ contentRef = el; requestAnimationFrame(checkOverflow); }} class={contentClasses()} style={`--clamp-lines: ${lines()}`} > {local.children}
); } ``` --- ### Collapsible ```tsx import { type JSX, splitProps, createSignal, Show } from "solid-js"; import { Chevron } from "./Chevron"; export interface CollapsibleProps { defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; children: JSX.Element; trigger: JSX.Element | string; } export function Collapsible(props: CollapsibleProps) { const [local, rest] = splitProps(props, ["defaultOpen", "open", "onOpenChange", "children", "trigger"]); const [internalOpen, setInternalOpen] = createSignal(local.defaultOpen ?? false); const isControlled = () => local.open !== undefined; const isOpen = () => (isControlled() ? local.open! : internalOpen()); const toggle = () => { const next = !isOpen(); if (!isControlled()) { setInternalOpen(next); } local.onOpenChange?.(next); }; return (
{local.children}
); } ``` --- ### Dropdown ```tsx import { type JSX, splitProps, createSignal, Show, onMount, onCleanup, createContext, useContext } from "solid-js"; import { isServer } from "solid-js/web"; type DropdownContextValue = { open: () => boolean; setOpen: (value: boolean) => void; close: () => void; }; const DropdownContext = createContext(); export interface DropdownProps { children: JSX.Element; } export interface DropdownTriggerProps { children: JSX.Element; } export interface DropdownMenuProps { children: JSX.Element; } export interface DropdownItemProps extends JSX.ButtonHTMLAttributes { onClick?: () => void; active?: boolean; children: JSX.Element; } export function Dropdown(props: DropdownProps) { const [open, setOpen] = createSignal(false); let containerRef: HTMLDivElement | undefined; const close = () => setOpen(false); const handleClickOutside = (e: MouseEvent) => { if (!containerRef?.contains(e.target as Node)) { close(); } }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" && open()) { close(); } }; onMount(() => { if (isServer) return; document.addEventListener("click", handleClickOutside); document.addEventListener("keydown", handleKeyDown); }); onCleanup(() => { if (isServer) return; document.removeEventListener("click", handleClickOutside); document.removeEventListener("keydown", handleKeyDown); }); return (
(containerRef = el)} class="dropdown"> {props.children}
); } export function DropdownTrigger(props: DropdownTriggerProps) { const ctx = useContext(DropdownContext); const handleClick = (e: MouseEvent) => { e.stopPropagation(); ctx?.setOpen(!ctx.open()); }; return ( ); } export function DropdownMenu(props: DropdownMenuProps) { const ctx = useContext(DropdownContext); return ( ); } export function DropdownItem(props: DropdownItemProps) { const [local, rest] = splitProps(props, ["onClick", "active", "children"]); const ctx = useContext(DropdownContext); const handleClick = () => { local.onClick?.(); ctx?.close(); }; const classes = () => `dropdown-item ${local.active ? "active" : ""}`.trim(); return ( ); } export function DropdownDivider() { return