0e626451e7
- CSS foundation: theme-aware scrollbars, section glows, glass cards with gold shadows, stronger animated borders and glow effects for light mode - Hero: consistent dark-video treatment for both themes, brighter gold gradient text, glowing CTA button - Gradient text: auto-switch to warm gold tones on light backgrounds via html:not(.dark) selector - Team profile: inverted ambient photo bg with white overlay for light, dark text/borders, gold-dark labels for contrast - All sections: text-neutral-500→600 upgrades for WCAG AA contrast, gold shadow accents on cards (About, Pricing, FAQ, DayCard, News) - Admin: replaced hardcoded #c9a96e with theme tokens, fixed select options, array editor borders, booking badges contrast - Header: white text on transparent hero, dark text after scroll - UI components: BackToTop, FloatingHearts, ShowcaseLayout tabs, SignupModal, NewsModal, GroupCard adapted for light backgrounds - Updated CLAUDE.md to reflect dual theme support
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { ChevronDown } from "lucide-react";
|
|
|
|
interface CollapsibleSectionProps {
|
|
title: string;
|
|
count?: number;
|
|
defaultOpen?: boolean;
|
|
isOpen?: boolean;
|
|
onToggle?: () => void;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Shared collapsible section for admin pages.
|
|
* Supports both controlled (isOpen/onToggle) and uncontrolled (defaultOpen) modes.
|
|
*/
|
|
export function CollapsibleSection({
|
|
title,
|
|
count,
|
|
defaultOpen = true,
|
|
isOpen: controlledOpen,
|
|
onToggle,
|
|
children,
|
|
}: CollapsibleSectionProps) {
|
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
|
const toggle = onToggle ?? (() => setInternalOpen((v) => !v));
|
|
|
|
return (
|
|
<div className="rounded-xl border border-neutral-200 bg-neutral-100/50 overflow-hidden dark:border-white/10 dark:bg-neutral-800/50">
|
|
<button
|
|
type="button"
|
|
onClick={toggle}
|
|
aria-expanded={open}
|
|
className="flex items-center justify-between w-full px-5 py-3.5 text-left cursor-pointer group hover:bg-neutral-100 transition-colors dark:hover:bg-white/[0.02]"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="text-sm font-semibold text-neutral-700 group-hover:text-neutral-900 transition-colors dark:text-neutral-200 dark:group-hover:text-white">
|
|
{title}
|
|
</h3>
|
|
{count !== undefined && (
|
|
<span className="text-xs text-neutral-500">{count}</span>
|
|
)}
|
|
</div>
|
|
<ChevronDown
|
|
size={16}
|
|
className={`text-neutral-500 transition-transform duration-200 ${open ? "rotate-180" : ""}`}
|
|
/>
|
|
</button>
|
|
<div
|
|
className="grid transition-[grid-template-rows] duration-300 ease-out"
|
|
style={{ gridTemplateRows: open ? "1fr" : "0fr" }}
|
|
>
|
|
<div className="overflow-hidden">
|
|
<div className="px-5 pb-5 space-y-4">{children}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|