diff --git a/apps/desktop/layer/renderer/src/components/ui/collapse/Collapse.tsx b/apps/desktop/layer/renderer/src/components/ui/collapse/Collapse.tsx index 76eefecaf..85a152e32 100644 --- a/apps/desktop/layer/renderer/src/components/ui/collapse/Collapse.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/collapse/Collapse.tsx @@ -3,6 +3,7 @@ import { cn } from "@follow/utils/utils" import { atom, useAtom, useStore } from "jotai" import type { Variants } from "motion/react" import { AnimatePresence, m } from "motion/react" +import type { FC } from "react" import * as React from "react" import { useEffect } from "react" @@ -18,10 +19,12 @@ interface CollapseProps { contentClassName?: string } -export const CollapseGroup: Component<{ - defaultOpenId?: string - onOpenChange?: (state: Record) => void -}> = ({ children, defaultOpenId, onOpenChange }) => { +export const CollapseGroup: FC< + { + defaultOpenId?: string + onOpenChange?: (state: Record) => void + } & React.PropsWithChildren +> = ({ children, defaultOpenId, onOpenChange }) => { const ctxValue = React.useMemo( () => ({ currentOpenCollapseIdAtom: atom(defaultOpenId ?? null), diff --git a/packages/internal/components/src/ui/accordion/index.tsx b/packages/internal/components/src/ui/accordion/index.tsx index 04ff60b30..bde942654 100644 --- a/packages/internal/components/src/ui/accordion/index.tsx +++ b/packages/internal/components/src/ui/accordion/index.tsx @@ -1,49 +1,178 @@ +"use client" + +import { Spring } from "@follow/components/constants/spring" import { cn } from "@follow/utils/utils" import * as AccordionPrimitive from "@radix-ui/react-accordion" +import type { HTMLMotionProps, Transition } from "motion/react" +import { AnimatePresence, m } from "motion/react" import * as React from "react" -const Accordion = AccordionPrimitive.Root +type AccordionItemContextType = { + isOpen: boolean + setIsOpen: (open: boolean) => void +} -const AccordionItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AccordionItem.displayName = "AccordionItem" +const AccordionItemContext = React.createContext(undefined) -const AccordionTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - svg]:rotate-180", - className, +const useAccordionItem = (): AccordionItemContextType => { + const context = React.useContext(AccordionItemContext) + if (!context) { + throw new Error("useAccordionItem must be used within an AccordionItem") + } + return context +} + +type AccordionProps = React.ComponentProps + +function Accordion(props: AccordionProps) { + return +} + +type AccordionItemProps = React.ComponentProps & { + children: React.ReactNode +} + +function AccordionItem({ className, children, ...props }: AccordionItemProps) { + const [isOpen, setIsOpen] = React.useState(false) + + return ( + ({ + isOpen, + setIsOpen, + }), + [isOpen, setIsOpen], )} - {...props} > - {children} - - - -)) -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + + {children} + + + ) +} -const AccordionContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - -
{children}
-
-)) -AccordionContent.displayName = AccordionPrimitive.Content.displayName +type AccordionTriggerProps = React.ComponentProps & { + transition?: Transition + chevron?: boolean +} -export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } +function AccordionTrigger({ + ref, + className, + children, + transition = Spring.presets.snappy, + chevron = true, + ...props +}: AccordionTriggerProps) { + const triggerRef = React.useRef(null) + // @ts-expect-error + React.useImperativeHandle(ref, () => triggerRef.current as HTMLButtonElement) + const { isOpen, setIsOpen } = useAccordionItem() + + React.useEffect(() => { + const node = triggerRef.current + if (!node) return + + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + if (mutation.attributeName === "data-state") { + const currentState = node.dataset.state + setIsOpen(currentState === "open") + } + }) + }) + observer.observe(node, { + attributes: true, + attributeFilter: ["data-state"], + }) + const initialState = node.dataset.state + setIsOpen(initialState === "open") + return () => { + observer.disconnect() + } + }, [setIsOpen]) + + return ( + + + {children} + + {chevron && ( + + + + )} + + + ) +} + +type AccordionContentProps = React.ComponentProps & + HTMLMotionProps<"div"> & { + transition?: Transition + } + +function AccordionContent({ + className, + children, + transition = Spring.presets.snappy, + ...props +}: AccordionContentProps) { + const { isOpen } = useAccordionItem() + + return ( + + {isOpen && ( + + +
{children}
+
+
+ )} +
+ ) +} + +export { + Accordion, + AccordionContent, + type AccordionContentProps, + AccordionItem, + type AccordionItemContextType, + type AccordionItemProps, + type AccordionProps, + AccordionTrigger, + type AccordionTriggerProps, +} diff --git a/packages/internal/components/src/ui/checkbox/index.tsx b/packages/internal/components/src/ui/checkbox/index.tsx index c1e33b171..571651d33 100644 --- a/packages/internal/components/src/ui/checkbox/index.tsx +++ b/packages/internal/components/src/ui/checkbox/index.tsx @@ -28,7 +28,7 @@ function Checkbox({ className, onCheckedChange, ...props }: CheckboxProps) {