Files
NODEDC_TASKMANAGER/plane-src/packages/propel/src/collapsible/collapsible.tsx
T
2026-04-18 18:39:25 +03:00

109 lines
2.8 KiB
TypeScript

/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React, { useState, useEffect, useCallback, createContext, useContext } from "react";
import { Collapsible as BaseCollapsible } from "@base-ui-components/react/collapsible";
import clsx from "clsx";
// Types
type CollapsibleContextType = {
isOpen: boolean;
onToggle: () => void;
};
type RootProps = {
children: React.ReactNode;
className?: string;
isOpen?: boolean;
onToggle?: () => void;
defaultOpen?: boolean;
};
type TriggerProps = {
children: React.ReactNode;
className?: string;
buttonRef?: React.RefObject<HTMLButtonElement>;
};
type ContentProps = {
children: React.ReactNode;
className?: string;
};
// Context
const CollapsibleContext = createContext<CollapsibleContextType | undefined>(undefined);
// Hook
const useCollapsible = () => {
const context = useContext(CollapsibleContext);
if (!context) {
throw new Error("Collapsible compound components cannot be rendered outside the Collapsible component");
}
return context;
};
// Components
function Root({ children, className, isOpen: controlledIsOpen, onToggle, defaultOpen }: RootProps) {
const [localIsOpen, setLocalIsOpen] = useState<boolean>(controlledIsOpen || defaultOpen || false);
useEffect(() => {
if (controlledIsOpen !== undefined) {
setLocalIsOpen(controlledIsOpen);
}
}, [controlledIsOpen]);
const handleToggle = useCallback(() => {
if (controlledIsOpen !== undefined) {
onToggle?.();
} else {
setLocalIsOpen((prev) => !prev);
}
}, [controlledIsOpen, onToggle]);
return (
<CollapsibleContext.Provider value={{ isOpen: localIsOpen, onToggle: handleToggle }}>
<BaseCollapsible.Root
className={clsx(className)}
defaultOpen={defaultOpen}
open={localIsOpen}
onOpenChange={handleToggle}
>
{children}
</BaseCollapsible.Root>
</CollapsibleContext.Provider>
);
}
function Trigger({ children, className, buttonRef }: TriggerProps) {
const { isOpen } = useCollapsible();
return (
<BaseCollapsible.Trigger data-panel-open={isOpen} ref={buttonRef} className={className}>
{children}
</BaseCollapsible.Trigger>
);
}
function Content({ children, className }: ContentProps) {
return (
<BaseCollapsible.Panel
className={clsx(
"flex h-[var(--collapsible-panel-height)] flex-col overflow-hidden text-13 transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0",
className
)}
>
{children}
</BaseCollapsible.Panel>
);
}
// Compound Component
export const Collapsible = {
CollapsibleRoot: Root,
CollapsibleTrigger: Trigger,
CollapsibleContent: Content,
};