import { useState } from "react"; // --------------------------------------------------------------------------- // Accordion primitives // --------------------------------------------------------------------------- interface AccordionItemProps { title: string; children: React.ReactNode; open: boolean; onToggle: () => void; disabled?: boolean; isLast?: boolean; } function AccordionItem({ title, children, open, onToggle, disabled = false, isLast = false }: AccordionItemProps) { return (
{/* Height animation via grid-template-rows */}
{children}
); } function ChevronIcon({ open }: { open: boolean }) { return ( ); } // Single-select accordion: only one item open at a time function ExclusiveAccordion({ items }: { items: { title: string; body: string }[] }) { const [open, setOpen] = useState(0); return (
{items.map((item, i) => ( setOpen(open === i ? null : i)} isLast={i === items.length - 1} > {item.body} ))}
); } // Multi-select accordion: multiple items can be open simultaneously. // open / onToggle / onExpandAll / onCollapseAll are controlled externally // so a parent can wire up "Expand all / Collapse all". interface MultiAccordionProps { items: { title: string; body: string }[]; open: Set; onToggle: (i: number) => void; } function MultiAccordion({ items, open, onToggle }: MultiAccordionProps) { return (
{items.map((item, i) => ( onToggle(i)} isLast={i === items.length - 1} > {item.body} ))}
); } // --------------------------------------------------------------------------- // Demo // --------------------------------------------------------------------------- function Section({ title, action, children, }: { title: string; action?: React.ReactNode; children: React.ReactNode; }) { return (

{title}

{action}
{children}
); } const BOOKING_ITEMS = [ { title: "What is included in the booking?", body: "Each booking includes the core service, all applicable taxes, and standard support. Optional add-ons such as seat upgrades and travel insurance are available at checkout.", }, { title: "How do I modify or cancel a reservation?", body: "Reservations can be modified or cancelled from the booking management screen up to 24 hours before departure. Cancellation policies vary by supplier and fare class.", }, { title: "When will I receive my confirmation?", body: "Confirmations are sent to the email address on file within a few minutes of successful payment. Check your spam folder if you have not received it after 10 minutes.", }, { title: "Is dynamic pricing applied at checkout?", body: "Yes. Prices shown reflect live inventory rates at the time of search. The final amount is confirmed at the point of payment and does not change after booking.", }, ]; const SETTINGS_ITEMS = [ { title: "Notifications", body: "Configure email and in-app notification preferences per event type. Changes take effect immediately and apply to all connected channels.", }, { title: "Data retention", body: "Booking records are retained for 7 years by default in accordance with financial regulations. Contact your account manager to discuss custom retention policies.", }, { title: "Integrations", body: "Connect third-party tools via the Integrations panel. OAuth tokens are stored encrypted and can be revoked at any time from the API settings page.", }, ]; // --------------------------------------------------------------------------- // Complex content demo // --------------------------------------------------------------------------- const BADGE: Record = { confirmed: { bg: "#BFE5B8" }, pending: { bg: "#FFD78E" }, cancelled: { bg: "#FFA99B" }, "on request": { bg: "#FFD78E" }, }; function StatusBadge({ label }: { label: string }) { const { bg } = BADGE[label.toLowerCase()] ?? { bg: "#E5E7EB" }; return ( {label} ); } const BOOKING_LINES = [ { ref: "KT-00421", service: "Hotel — Reykjavik Natura", nights: 3, pax: 2, unit: 189, status: "Confirmed" }, { ref: "KT-00422", service: "Northern Lights Tour", nights: 1, pax: 2, unit: 95, status: "Confirmed" }, { ref: "KT-00423", service: "Airport Transfer × 2", nights: 0, pax: 2, unit: 45, status: "Pending" }, { ref: "KT-00424", service: "Blue Lagoon Entry", nights: 0, pax: 2, unit: 79, status: "On Request" }, ]; function HoverButton({ variant, children }: { variant: "primary" | "ghost"; children: React.ReactNode }) { const [hovered, setHovered] = useState(false); const isPrimary = variant === "primary"; return ( ); } function BookingRow({ line: l, total, isLast }: { line: typeof BOOKING_LINES[0]; total: number; isLast?: boolean }) { const [hovered, setHovered] = useState(false); const [removed, setRemoved] = useState(false); if (removed) return null; return ( setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ borderBottom: isLast ? "none" : "1px solid var(--flux-grey-100)", backgroundColor: hovered ? "var(--flux-grey-50, #F8FAFB)" : "transparent", transition: "background-color 120ms ease", }} > {l.ref} {l.service} {l.nights || "—"} {l.pax} €{l.unit} €{total} {/* Inline actions — fade in on row hover */} alert(`Edit ${l.ref}`)}> alert(`Duplicate ${l.ref}`)}> setRemoved(true)}> ); } function IconBtn({ title, danger, onClick, children }: { title: string; danger?: boolean; onClick: () => void; children: React.ReactNode; }) { const [hovered, setHovered] = useState(false); return ( ); } const iconBtnStyle: React.CSSProperties = {}; function BookingBreakdown() { const subtotal = BOOKING_LINES.reduce((s, l) => s + l.unit * l.pax * Math.max(l.nights, 1), 0); const tax = Math.round(subtotal * 0.2); return (
{/* Line items */}
{["Ref", "Service", "Nights", "Pax", "Unit price", "Total", "Status", ""].map(h => ( ))} {BOOKING_LINES.map((l, i) => { const total = l.unit * l.pax * Math.max(l.nights, 1); return ( ); })}
{h}
{/* Totals + actions */}
Export PDF Add service
Subtotal €{subtotal}
VAT 20% €{tax}
Total  €{subtotal + tax}
); } function ComplexAccordion() { const [open, setOpen] = useState(0); const items = [ { title: "Booking BK-2024-03817 — Iceland Self-Drive, 5 nights", content: }, { title: "Booking BK-2024-03651 — Lofoten Highlights, 7 nights", content:

Full breakdown not yet loaded — click to fetch from supplier.

}, { title: "Booking BK-2024-03420 — Scottish Highlands Tour, 4 nights", content:

Full breakdown not yet loaded — click to fetch from supplier.

}, ]; return (
{items.map((item, i) => ( setOpen(open === i ? null : i)} isLast={i === items.length - 1}> {item.content} ))}
); } export default function AccordionDemo() { const [singleOpen, setSingleOpen] = useState(true); // Controlled state for the "Multiple" section so the expand/collapse all // button can act on all items at once. const [multiOpen, setMultiOpen] = useState>(new Set([0])); const allExpanded = multiOpen.size === SETTINGS_ITEMS.length; const toggleMulti = (i: number) => { const next = new Set(multiOpen); next.has(i) ? next.delete(i) : next.add(i); setMultiOpen(next); }; const expandAll = () => setMultiOpen(new Set(SETTINGS_ITEMS.map((_, i) => i))); const collapseAll = () => setMultiOpen(new Set()); return (
setSingleOpen(!singleOpen)} isLast > Kaptio Travel is an end-to-end travel management platform built for tour operators, wholesalers, and travel businesses that need to manage complex itineraries, inventory, and bookings at scale.
{allExpanded ? ( <> Collapse all ) : ( <> Expand all )} } >
{}} > These settings apply across the entire account and are inherited by all sub-users unless overridden. {}} disabled > Not reachable. {}} isLast > Contact your account manager or open a ticket via the help centre.
); }