Skip to content

[WEB-4688]chore: added collapsible to propel#7643

Merged
sriramveeraghanta merged 7 commits intopreviewfrom
chore-collapsible_migration_propel
Sep 9, 2025
Merged

[WEB-4688]chore: added collapsible to propel#7643
sriramveeraghanta merged 7 commits intopreviewfrom
chore-collapsible_migration_propel

Conversation

@vamsikrishnamathala
Copy link
Member

@vamsikrishnamathala vamsikrishnamathala commented Aug 25, 2025

Description

This update adds collapsible to propel.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

References

Summary by CodeRabbit

  • New Features

    • Added a Collapsible component set with Root, Trigger, and Content for building expandable sections.
    • Supports both controlled and uncontrolled usage with toggle callbacks.
    • Includes smooth transition styles and open-state indicators for better UX.
  • Chores

    • Exposed the new component via a public export for easier consumption.
    • Updated build configuration to include the new component in distributed outputs.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Adds a new Collapsible compound component (Root/Trigger/Content) under packages/propel/src/collapsible, wires it into the build (tsdown) and public exports (package.json), and creates a barrel index for re-exports. No changes to existing modules.

Changes

Cohort / File(s) Summary
Collapsible component (new)
packages/propel/src/collapsible/collapsible.tsx, packages/propel/src/collapsible/index.ts
Introduces React Collapsible compound component using @base-ui-components/react/collapsible, with context-managed open state; adds barrel export.
Build and exports wiring
packages/propel/package.json, packages/propel/tsdown.config.ts
Adds subpath export "./collapsible" → "./dist/collapsible/index.js" and includes "src/collapsible/index.ts" in tsdown entry for building.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant T as Collapsible.Trigger
  participant Ctx as CollapsibleContext
  participant R as Collapsible.Root
  participant Base as BaseCollapsible

  U->>T: Click
  T->>Ctx: onToggle()
  alt Uncontrolled
    Ctx->>R: toggle local isOpen
  else Controlled
    Ctx->>R: call onToggle (delegates to parent)
  end
  R->>Base: open / onOpenChange
  Note over Base,R: Base primitives render Panel visibility accordingly
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

🌟enhancement

Suggested reviewers

  • anmolsinghbhatia
  • sriramveeraghanta

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The current description is insufficiently detailed and only states that collapsible was added without explaining the specific code changes, usage, or API impact. It also leaves all change type checkboxes unchecked, lacks test scenarios, and provides no references, making it difficult for reviewers to assess functionality or verify coverage. Please expand the Description section with details on the new collapsible component implementation and usage, select the appropriate change type (such as Feature), and include a Test Scenarios section outlining how to verify the changes as well as any relevant references or linked issues.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The current title succinctly describes the primary change—adding the collapsible feature to the propel package—and includes the ticket reference for context, making it clear and relevant to the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I tap the Trigger—flop! it folds—
A panel hides its secret holds.
Context whispers, open—close—
State like clover comes and goes.
Built and shipped, a tidy trick,
Collapsible dreams, deployed quick.
(_/)\n> ( •_•)\n> /︵🍃

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore-collapsible_migration_propel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@makeplane
Copy link

makeplane bot commented Aug 25, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
packages/propel/src/collapsible/collapsible.tsx (4)

19-23: Forward ref and pass through native props on Trigger.

Use forwardRef and widen props to the underlying trigger for compatibility and a11y extensions.

-type TriggerProps = {
-  children: React.ReactNode;
-  className?: string;
-  buttonRef?: React.RefObject<HTMLButtonElement>;
-};
+type TriggerProps = React.ComponentPropsWithoutRef<typeof BaseCollapsible.Trigger>;
@@
-const Trigger: React.FC<TriggerProps> = ({ children, className, buttonRef }) => {
+const Trigger = React.forwardRef<HTMLButtonElement, TriggerProps>(({ children, className, ...props }, ref) => {
   const { isOpen } = useCollapsible();
 
-  return (
-    <BaseCollapsible.Trigger data-panel-open={isOpen} ref={buttonRef} className={className}>
-      {children}
-    </BaseCollapsible.Trigger>
-  );
-};
+  return (
+    <BaseCollapsible.Trigger
+      data-panel-open={isOpen}
+      ref={ref}
+      className={className}
+      {...props}
+    >
+      {children}
+    </BaseCollapsible.Trigger>
+  );
+});

Also update imports to include forwardRef and ComponentPropsWithoutRef (see a separate comment).

Also applies to: 74-82


25-28: Forward ref and pass through native props on Content.

Align with Trigger for consistency and flexibility.

-type ContentProps = {
-  children: React.ReactNode;
-  className?: string;
-};
+type ContentProps = React.ComponentPropsWithoutRef<typeof BaseCollapsible.Panel>;
@@
-const Content: React.FC<ContentProps> = ({ children, className }) => {
-  return (
-    <BaseCollapsible.Panel
+const Content = React.forwardRef<HTMLDivElement, ContentProps>(({ children, className, ...props }, ref) => {
+  return (
+    <BaseCollapsible.Panel
+      ref={ref}
       className={clsx(
         "flex h-[var(--collapsible-panel-height)] flex-col overflow-hidden text-sm transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0",
         className
       )}
+      {...props}
     >
       {children}
     </BaseCollapsible.Panel>
-  );
-};
+  );
+});

Also applies to: 84-95


1-1: Add missing imports for forwardRef and widened prop types.

Required by the previous two refactors.

-import React, { useState, useEffect, useCallback, createContext, useContext } from "react";
+import React, { useState, useEffect, useCallback, createContext, useContext, forwardRef } from "react";
+import type { ComponentPropsWithoutRef } from "react";

98-102: Add short aliases: Collapsible.Root/Trigger/Content.

Keeps API familiar while preserving current names.

 export const Collapsible = {
-  CollapsibleRoot: Root,
-  CollapsibleTrigger: Trigger,
-  CollapsibleContent: Content,
+  // Short aliases
+  Root: Root,
+  Trigger: Trigger,
+  Content: Content,
+  // Existing names
+  CollapsibleRoot: Root,
+  CollapsibleTrigger: Trigger,
+  CollapsibleContent: Content,
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bbc465a and 972acdc.

📒 Files selected for processing (3)
  • packages/propel/package.json (1 hunks)
  • packages/propel/src/collapsible/collapsible.tsx (1 hunks)
  • packages/propel/src/collapsible/index.ts (1 hunks)
🔇 Additional comments (2)
packages/propel/src/collapsible/collapsible.tsx (1)

52-58: Use nextOpen from onOpenChange to avoid state desync

Derive state from the callback argument instead of blindly inverting.

Confirm BaseCollapsible.Root’s onOpenChange signature (does it pass nextOpen: boolean or (event, data)?), then map accordingly:

-  const handleToggle = useCallback(() => {
-    if (controlledIsOpen !== undefined) {
-      onToggle?.();
-    } else {
-      setLocalIsOpen((prev) => !prev);
-    }
-  }, [controlledIsOpen, onToggle]);
+  const handleToggle = useCallback((nextOpen?: boolean) => {
+    if (controlledIsOpen !== undefined) {
+      onToggle?.(nextOpen ?? !controlledIsOpen);
+    } else {
+      setLocalIsOpen((prev) => (nextOpen ?? !prev));
+    }
+  }, [controlledIsOpen, onToggle]);

Also apply the same change at line 66.

packages/propel/src/collapsible/index.ts (1)

1-1: LGTM: Barrel re-export is correct.

Keeps the public surface tidy and consistent with other modules.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/propel/package.json (2)

28-30: Verify the new export resolves correctly.

Confirm the export maps and source barrel exist to prevent runtime resolution issues.

#!/bin/bash
# 1) Check export mapping
jq -r '.exports["./collapsible"]' packages/propel/package.json

# 2) Ensure the barrel file exists
fd -p 'src/collapsible/index.ts' packages/propel

# 3) Ensure barrel re-exports the component(s)
rg -n 'export \* from "./collapsible";' packages/propel/src/collapsible/index.ts -C0

28-30: Document usage in README/Storybook.

Add a minimal import/usage snippet and a basic Storybook story for Collapsible so consumers see API and import path.

Happy to draft the README snippet and a Storybook story if you want.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 972acdc and 1d70774.

📒 Files selected for processing (1)
  • packages/propel/package.json (1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
packages/propel/package.json

[error] 30-30: expected , but instead found "./card"

Remove "./card"

(parse)

🪛 GitHub Actions: Build and lint web apps
packages/propel/package.json

[error] 30-30: Command failed: pnpm install --frozen-lockfile. PNPM JSON parse error in '/home/runner/work/plane/plane/packages/propel/package.json': ERR_PNPM_JSON_PARSE: Expected ',' or '}' after property value in JSON at position 1125 (line 30 column 5).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (5)
packages/propel/package.json (2)

28-30: New public export looks correct.

The mapping to "./src/collapsible/index.ts" is valid and the JSON is well-formed.


16-31: Add README/Storybook usage snippet for Collapsible (import path).

Document a minimal example in Propel README and add a Storybook story for discoverability.

packages/propel/src/collapsible/collapsible.tsx (3)

6-9: Pass next state through callbacks (types and API).

Expose the next open state for controlled usage and parity with Base UI’s onOpenChange.

Apply:

 type CollapsibleContextType = {
   isOpen: boolean;
-  onToggle: () => void;
+  onToggle: (nextOpen: boolean) => void;
 };

 type RootProps = {
   children: React.ReactNode;
   className?: string;
   isOpen?: boolean;
-  onToggle?: () => void;
+  onToggle?: (nextOpen: boolean) => void;
   defaultOpen?: boolean;
 };

Also applies to: 11-17


44-44: Fix initial state: false incorrectly falls back to defaultOpen.

Use nullish coalescing to preserve explicit false.

-  const [localIsOpen, setLocalIsOpen] = useState<boolean>(controlledIsOpen || defaultOpen || false);
+  const [localIsOpen, setLocalIsOpen] = useState<boolean>(controlledIsOpen ?? defaultOpen ?? false);

52-58: onOpenChange handler ignores nextOpen; gate defaultOpen when controlled.

Current handler toggles blindly and passes a no-arg function to Base UI. Use the provided nextOpen boolean and avoid mixing defaultOpen with controlled mode.

-  const handleToggle = useCallback(() => {
-    if (controlledIsOpen !== undefined) {
-      onToggle?.();
-    } else {
-      setLocalIsOpen((prev) => !prev);
-    }
-  }, [controlledIsOpen, onToggle]);
+  const handleOpenChange = useCallback(
+    (nextOpen: boolean) => {
+      if (controlledIsOpen === undefined) {
+        setLocalIsOpen(nextOpen);
+      }
+      onToggle?.(nextOpen);
+    },
+    [controlledIsOpen, onToggle]
+  );
 
   return (
-    <CollapsibleContext.Provider value={{ isOpen: localIsOpen, onToggle: handleToggle }}>
+    <CollapsibleContext.Provider value={{ isOpen: localIsOpen, onToggle: handleOpenChange }}>
       <BaseCollapsible.Root
         className={clsx(className)}
-        defaultOpen={defaultOpen}
+        {...(controlledIsOpen === undefined ? { defaultOpen } : {})}
         open={localIsOpen}
-        onOpenChange={handleToggle}
+        onOpenChange={handleOpenChange}
       >

Also applies to: 60-67

🧹 Nitpick comments (3)
packages/propel/src/collapsible/collapsible.tsx (3)

19-23: Forward refs and pass-through props on Trigger.

Improve ergonomics and interop by using forwardRef and allowing standard button/trigger props.

-type TriggerProps = {
-  children: React.ReactNode;
-  className?: string;
-  buttonRef?: React.RefObject<HTMLButtonElement>;
-};
+type TriggerProps = React.ComponentPropsWithoutRef<typeof BaseCollapsible.Trigger>;
 
-const Trigger: React.FC<TriggerProps> = ({ children, className, buttonRef }) => {
-  const { isOpen } = useCollapsible();
-
-  return (
-    <BaseCollapsible.Trigger data-panel-open={isOpen} ref={buttonRef} className={className}>
-      {children}
-    </BaseCollapsible.Trigger>
-  );
-};
+const Trigger = React.forwardRef<HTMLButtonElement, TriggerProps>(({ className, ...props }, ref) => {
+  const { isOpen } = useCollapsible();
+  return (
+    <BaseCollapsible.Trigger
+      data-panel-open={isOpen}
+      ref={ref}
+      className={clsx(className)}
+      {...props}
+    />
+  );
+});
+Trigger.displayName = "CollapsibleTrigger";

Also applies to: 74-82


25-29: Forward refs and pass-through props on Content.

Allow standard div/panel props and external refs.

-type ContentProps = {
-  children: React.ReactNode;
-  className?: string;
-};
+type ContentProps = React.ComponentPropsWithoutRef<typeof BaseCollapsible.Panel>;
 
-const Content: React.FC<ContentProps> = ({ children, className }) => (
-  <BaseCollapsible.Panel
+const Content = React.forwardRef<HTMLDivElement, ContentProps>(({ className, ...props }, ref) => (
+  <BaseCollapsible.Panel
+    ref={ref}
     className={clsx(
       "flex h-[var(--collapsible-panel-height)] flex-col overflow-hidden text-sm transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0",
       className
     )}
+    {...props}
   >
-    {children}
+    {props.children}
   </BaseCollapsible.Panel>
-);
+));
+Content.displayName = "CollapsibleContent";

Also applies to: 84-93


95-100: Export prop types and freeze compound object.

Expose types for consumers and mark the compound export as readonly.

-export const Collapsible = {
+export const Collapsible = {
   CollapsibleRoot: Root,
   CollapsibleTrigger: Trigger,
   CollapsibleContent: Content,
-};
+} as const;
+
+export type {
+  RootProps as CollapsibleRootProps,
+  TriggerProps as CollapsibleTriggerProps,
+  ContentProps as CollapsibleContentProps,
+};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d70774 and 1d2e06d.

📒 Files selected for processing (2)
  • packages/propel/package.json (1 hunks)
  • packages/propel/src/collapsible/collapsible.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/propel/package.json (1)

28-30: Exports for “./collapsible” are correctly configured; consumers can import from "@plane/propel/collapsible".

packages/propel/src/collapsible/collapsible.tsx (1)

86-89: No changes needed: data-starting-style and data-ending-style match Base UI

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
packages/propel/src/collapsible/collapsible.tsx (3)

6-9: Make onToggle convey next state; wire through onOpenChange.

Controlled consumers need the next open value. Also, BaseCollapsible’s onOpenChange provides the next state; use it instead of toggling locally.

Apply:

 type CollapsibleContextType = {
   isOpen: boolean;
-  onToggle: () => void;
+  onToggle: (nextOpen: boolean) => void;
 };

 type RootProps = {
   children: React.ReactNode;
   className?: string;
   isOpen?: boolean;
-  onToggle?: () => void;
+  onToggle?: (nextOpen: boolean) => void;
   defaultOpen?: boolean;
 };

-  const handleToggle = useCallback(() => {
-    if (controlledIsOpen !== undefined) {
-      onToggle?.();
-    } else {
-      setLocalIsOpen((prev) => !prev);
-    }
-  }, [controlledIsOpen, onToggle]);
+  const handleToggle = useCallback(
+    (nextOpen: boolean) => {
+      if (controlledIsOpen !== undefined) {
+        onToggle?.(nextOpen);
+      } else {
+        setLocalIsOpen(nextOpen);
+      }
+    },
+    [controlledIsOpen, onToggle]
+  );

To confirm the callback shape, please check the latest API:

What is the onOpenChange callback signature for @base-ui-components/react/collapsible (v1.0.0-beta.2)? Does it pass the next open boolean?

Also applies to: 11-17, 52-58


44-44: Fix initial-state flicker (false treated as fallback).

Preserve explicit false via nullish coalescing.

-  const [localIsOpen, setLocalIsOpen] = useState<boolean>(controlledIsOpen || defaultOpen || false);
+  const [localIsOpen, setLocalIsOpen] = useState<boolean>(controlledIsOpen ?? defaultOpen ?? false);

64-66: Avoid mixing controlled and uncontrolled props.

Gate defaultOpen when controlled to prevent confusing semantics.

-        defaultOpen={defaultOpen}
+        defaultOpen={controlledIsOpen === undefined ? defaultOpen : undefined}
         open={localIsOpen}
-        onOpenChange={handleToggle}
+        onOpenChange={handleToggle}
packages/propel/package.json (1)

23-23: Expose usage in docs/Storybook for the new subpath.

Add a brief README and Storybook example showing:
import { Collapsible } from "@plane/propel/collapsible".

🧹 Nitpick comments (2)
packages/propel/src/collapsible/collapsible.tsx (2)

19-23: Prefer forwardRef over custom buttonRef prop.

Forwarding refs is the conventional ergonomics for host elements.

-type TriggerProps = {
-  children: React.ReactNode;
-  className?: string;
-  buttonRef?: React.RefObject<HTMLButtonElement>;
-};
+type TriggerProps = {
+  children: React.ReactNode;
+  className?: string;
+};

-const Trigger: React.FC<TriggerProps> = ({ children, className, buttonRef }) => {
+const Trigger = React.forwardRef<HTMLButtonElement, TriggerProps>(({ children, className }, ref) => {
   const { isOpen } = useCollapsible();

   return (
-    <BaseCollapsible.Trigger data-panel-open={isOpen} ref={buttonRef} className={className}>
+    <BaseCollapsible.Trigger data-panel-open={isOpen} ref={ref} className={className}>
       {children}
     </BaseCollapsible.Trigger>
   );
-};
+});
+Trigger.displayName = "CollapsibleTrigger";

Also applies to: 74-82


6-9: Context surface: drop onToggle or export a hook if needed.

onToggle in context isn’t consumed by Trigger/Content and useCollapsible isn’t exported; consider removing it from context to reduce API surface, or export the hook for custom triggers.

Also applies to: 30-41

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20d139c and ab10fa1.

📒 Files selected for processing (4)
  • packages/propel/package.json (1 hunks)
  • packages/propel/src/collapsible/collapsible.tsx (1 hunks)
  • packages/propel/src/collapsible/index.ts (1 hunks)
  • packages/propel/tsdown.config.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/propel/tsdown.config.ts (1)

9-9: LGTM: entry added and aligned with exports.

packages/propel/src/collapsible/index.ts (1)

1-1: LGTM: barrel export is correct.

@sriramveeraghanta sriramveeraghanta merged commit a8a6536 into preview Sep 9, 2025
6 of 7 checks passed
@sriramveeraghanta sriramveeraghanta deleted the chore-collapsible_migration_propel branch September 9, 2025 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants