From 239c5277a3775470a87e1a610f722d7ab7611eee Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 10 Mar 2025 17:52:12 +0800 Subject: [PATCH 01/31] Add experiment --- .../experiments/collapsible/plain.module.css | 71 +++++++++++++ .../experiments/collapsible/plain.tsx | 99 +++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 docs/src/app/(private)/experiments/collapsible/plain.module.css create mode 100644 docs/src/app/(private)/experiments/collapsible/plain.tsx diff --git a/docs/src/app/(private)/experiments/collapsible/plain.module.css b/docs/src/app/(private)/experiments/collapsible/plain.module.css new file mode 100644 index 0000000000..9f65d55e86 --- /dev/null +++ b/docs/src/app/(private)/experiments/collapsible/plain.module.css @@ -0,0 +1,71 @@ +.wrapper { + --width: 320px; + --duration: 2000ms; + + font-family: system-ui, sans-serif; + line-height: 1.4; + display: flex; + flex-flow: column nowrap; + align-items: stretch; + gap: 3rem; + align-self: flex-start; +} + +.Root { + width: var(--width); +} + +.Trigger { + display: flex; + width: 100%; + align-items: center; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + background-color: var(--color-gray-200); + color: var(--color-gray-900); + + & svg { + transform: rotate(-90deg); + transition: transform var(--duration) ease-in; + } + + &[data-panel-open] svg { + transform: rotate(0); + transition: transform var(--duration) ease-out; + } +} + +.Panel { + overflow: hidden; + box-sizing: border-box; + width: var(--width); + + transition: all var(--duration) ease-out; + + &[data-open] { + height: var(--collapsible-panel-height); + /* height: auto;*/ + opacity: 1; + } + + &[data-closed] { + display: none; + height: 0; + opacity: 0; + } +} + +.Content { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-top: 0.25rem; + padding: 0.5rem; + border-radius: 0.25rem; + background-color: var(--color-gray-200); + cursor: text; + + & p { + overflow-wrap: break-word; + } +} diff --git a/docs/src/app/(private)/experiments/collapsible/plain.tsx b/docs/src/app/(private)/experiments/collapsible/plain.tsx new file mode 100644 index 0000000000..6907acce45 --- /dev/null +++ b/docs/src/app/(private)/experiments/collapsible/plain.tsx @@ -0,0 +1,99 @@ +'use client'; +import * as React from 'react'; +import classes from './plain.module.css'; + +export default function DomCollapsible() { + const [open, setOpen] = React.useState(false); + + const panelRef: React.RefObject = React.useRef(null); + + const [height, setHeight] = React.useState(0); + + const handleTrigger = () => { + const panel = panelRef.current; + if (!panel) { + return; + } + + const nextOpen = !open; + + panel.style.display = 'block'; + // const targetHeight = panel.clientHeight; + + if (nextOpen) { + /* opening */ + panel.style.opacity = '0'; + panel.style.height = '0px'; + + requestAnimationFrame(() => { + panel.style.opacity = '1'; + panel.style.height = ''; + setHeight(panel.scrollHeight); + }); + } else { + /* closing */ + requestAnimationFrame(() => { + console.log('closing, scrollHeight', panel.scrollHeight); + panel.style.opacity = '0'; + setHeight(0); + }); + } + + setOpen(!open); + }; + + return ( +
+
+ + + +
+
+ ); +} + +function ExpandMoreIcon(props: React.SVGProps) { + return ( + + + + ); +} From d69830f62bcb2806fcae52e94f35d6fcbb793365 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 10 Mar 2025 19:57:10 +0800 Subject: [PATCH 02/31] Support keepMounted false --- .../experiments/collapsible/plain.module.css | 15 +-- .../experiments/collapsible/plain.tsx | 110 ++++++++++++++---- 2 files changed, 94 insertions(+), 31 deletions(-) diff --git a/docs/src/app/(private)/experiments/collapsible/plain.module.css b/docs/src/app/(private)/experiments/collapsible/plain.module.css index 9f65d55e86..c523d4cb37 100644 --- a/docs/src/app/(private)/experiments/collapsible/plain.module.css +++ b/docs/src/app/(private)/experiments/collapsible/plain.module.css @@ -1,17 +1,17 @@ .wrapper { - --width: 320px; - --duration: 2000ms; - font-family: system-ui, sans-serif; line-height: 1.4; display: flex; flex-flow: column nowrap; align-items: stretch; - gap: 3rem; + gap: 1rem; align-self: flex-start; } .Root { + --width: 320px; + --duration: 1000ms; + width: var(--width); } @@ -38,13 +38,14 @@ .Panel { overflow: hidden; box-sizing: border-box; - width: var(--width); + width: 100%; - transition: all var(--duration) ease-out; + transition: + height var(--duration) ease-out, + opacity var(--duration) ease-out; &[data-open] { height: var(--collapsible-panel-height); - /* height: auto;*/ opacity: 1; } diff --git a/docs/src/app/(private)/experiments/collapsible/plain.tsx b/docs/src/app/(private)/experiments/collapsible/plain.tsx index 6907acce45..9dce2853b8 100644 --- a/docs/src/app/(private)/experiments/collapsible/plain.tsx +++ b/docs/src/app/(private)/experiments/collapsible/plain.tsx @@ -1,22 +1,38 @@ 'use client'; import * as React from 'react'; +import { useEnhancedEffect } from '@base-ui-components/react/utils'; import classes from './plain.module.css'; -export default function DomCollapsible() { +import { useAnimationsFinished } from '../../../../../../packages/react/src/utils/useAnimationsFinished'; +import { useEventCallback } from '../../../../../../packages/react/src/utils/useEventCallback'; + +function PlainCollapsible(props: { keepMounted?: boolean }) { + const { keepMounted = true } = props; + const [open, setOpen] = React.useState(false); + const [mounted, setMounted] = React.useState(open); + const panelRef: React.RefObject = React.useRef(null); const [height, setHeight] = React.useState(0); - const handleTrigger = () => { + const handleTrigger = useEventCallback(() => { + const nextOpen = !open; + + if (!keepMounted) { + // mount only + if (!mounted) { + setMounted(true); + } + } + setOpen(nextOpen); + const panel = panelRef.current; if (!panel) { return; } - const nextOpen = !open; - panel.style.display = 'block'; // const targetHeight = panel.clientHeight; @@ -33,34 +49,70 @@ export default function DomCollapsible() { } else { /* closing */ requestAnimationFrame(() => { - console.log('closing, scrollHeight', panel.scrollHeight); + // console.log('closing, scrollHeight', panel.scrollHeight); panel.style.opacity = '0'; setHeight(0); }); } + }); - setOpen(!open); - }; + const runOnceAnimationsFinish = useAnimationsFinished(panelRef); + + useEnhancedEffect(() => { + // This only matters when `keepMounted={false}` + if (keepMounted) { + return; + } + // console.log('useEnhancedEffect open', open, 'mounted', mounted); + + const panel = panelRef.current; + if (!panel) { + return; + } + + if (open) { + /* opening */ + panel.style.opacity = '0'; + panel.style.height = '0px'; + + requestAnimationFrame(() => { + panel.style.opacity = '1'; + panel.style.height = ''; + setHeight(panel.scrollHeight); + }); + } else { + /* closing */ + requestAnimationFrame(() => { + console.log('closing, scrollHeight', panel.scrollHeight); + panel.style.opacity = '0'; + setHeight(0); + }); + + runOnceAnimationsFinish(() => { + setMounted(false); + }); + } + }, [keepMounted, open, mounted, setMounted, runOnceAnimationsFinish]); return ( -
-
+ + + Trigger (keepMounted {String(keepMounted)}) + + {(keepMounted || (!keepMounted && mounted)) && (
-
+ )} + + ); +} + +export default function App() { + return ( +
+ + +
); } From 96644b6bbbc27cac371aaf7ae8ae933c15efee2a Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Mon, 10 Mar 2025 20:10:18 +0800 Subject: [PATCH 03/31] Support hidden attribute --- .../experiments/collapsible/plain.module.css | 1 - .../app/(private)/experiments/collapsible/plain.tsx | 13 +++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/src/app/(private)/experiments/collapsible/plain.module.css b/docs/src/app/(private)/experiments/collapsible/plain.module.css index c523d4cb37..023ad40237 100644 --- a/docs/src/app/(private)/experiments/collapsible/plain.module.css +++ b/docs/src/app/(private)/experiments/collapsible/plain.module.css @@ -50,7 +50,6 @@ } &[data-closed] { - display: none; height: 0; opacity: 0; } diff --git a/docs/src/app/(private)/experiments/collapsible/plain.tsx b/docs/src/app/(private)/experiments/collapsible/plain.tsx index 9dce2853b8..d00916602d 100644 --- a/docs/src/app/(private)/experiments/collapsible/plain.tsx +++ b/docs/src/app/(private)/experiments/collapsible/plain.tsx @@ -33,7 +33,8 @@ function PlainCollapsible(props: { keepMounted?: boolean }) { return; } - panel.style.display = 'block'; + // panel.style.display = 'block'; + // const targetHeight = panel.clientHeight; if (nextOpen) { @@ -94,6 +95,14 @@ function PlainCollapsible(props: { keepMounted?: boolean }) { } }, [keepMounted, open, mounted, setMounted, runOnceAnimationsFinish]); + const isHidden = React.useMemo(() => { + if (keepMounted) { + return !open; + } + + return !open && !mounted; + }, [keepMounted, open, mounted]); + return (