Skip to content

Commit 41a4c51

Browse files
fix(editor): allow standalone accordion deletion and add AccordionGroup popover menu (#4633)
Fixes the accordion deletion logic in the fern-dashboard visual editor. ## Changes Made This PR fixes two issues with accordion components in the visual editor: 1. **Standalone accordions can now be deleted**: Previously, standalone `Accordion` components (those not within an `AccordionGroup`) could not be deleted because the deletion logic incorrectly treated them as if they were the last accordion in a group. 2. **AccordionGroup now has a popover menu with delete functionality**: Added an `EditorComponentPopoverButton` and `EditorComponentPopoverProvider` to the `AccordionGroup` component, allowing users to delete the entire group. ## Implementation Details - Added an `isInGroup` boolean prop to `AccordionItem` to distinguish between standalone accordions and those within an AccordionGroup - Modified the `disableDelete` logic from `accordions.length === 1` to `isInGroup && accordions.length === 1`, so deletion is only prevented for the last accordion within a group - Wrapped `AccordionGroup` content with `EditorComponentPopoverProvider` (using empty attributes object since no editable properties are needed) - Added popover button to AccordionGroup with `z-10` to ensure visibility above accordion content ## What was the motivation & context behind this PR? User reported that standalone accordions could not be deleted, and AccordionGroup components had no way to be deleted at all. The root cause was that the deletion prevention logic (`disableDelete={accordions.length === 1}`) was applied to all accordions regardless of context, including standalone ones which always had an array length of 1. ## How has this PR been tested? - ✅ Code compiled successfully - ✅ Passed all lint checks (`pnpm lint:biome`, `pnpm lint:style`, `pnpm format:yaml:check`) - ⚠️ **Not tested in running application** - requires manual verification in the dashboard editor ## Human Review Checklist Please verify the following when reviewing: - [ ] Standalone `Accordion` components can be deleted in the visual editor - [ ] The last accordion within an `AccordionGroup` still cannot be deleted (protection maintained) - [ ] `AccordionGroup` now has a visible popover menu with a delete button - [ ] No z-index or visual layering issues with the new popover button - [ ] Empty `attributes={{}}` is appropriate for AccordionGroup (no editable properties needed besides delete) --- **Link to Devin run:** https://app.devin.ai/sessions/e4e7ea824cca4b75ab65a4f217e55334 **Requested by:** Sarah Bawabe (@sbawabe)
1 parent dcb8ca6 commit 41a4c51

File tree

2 files changed

+26
-9
lines changed

2 files changed

+26
-9
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"fdr:stop": "bash scripts/stop-local-fdr.sh",
4141
"fdr:unlink-from-cli": "bash scripts/unlink-fdr-sdk-from-cli.sh",
4242
"format": "pnpm lint:biome:fix && pnpm check:fix && pnpm format:yaml:fix",
43+
"format:check": "pnpm format:yaml:check",
4344
"format:package-json": "sort-package-json \"generators/**/package.json\" \"packages/**/package.json\" --ignore \"**/dist\" --ignore \"**lib\" --ignore \"**/node_modules\"",
4445
"format:yaml:check": "prettier --check \"**/*.{yml,yaml}\"",
4546
"format:yaml:fix": "prettier --write \"**/*.{yml,yaml}\"",

packages/fern-dashboard/src/docs/mdx/components/accordion/AccordionGroup.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function AccordionGroup({ children }: AccordionGroupProps) {
5555
const anchor = useCurrentAnchor();
5656
const [updatedUrl, setUpdatedUrl] = useState<string | null>(null);
5757
const [isProgrammaticUpdate, setIsProgrammaticUpdate] = useState(false);
58+
const popoverRef = useRef<HTMLDivElement>(null);
5859

5960
const registerAccordion = useCallback((accordion: AccordionData) => {
6061
setAccordions((prevAccordions) => {
@@ -152,9 +153,12 @@ export function AccordionGroup({ children }: AccordionGroupProps) {
152153
const { isWithinEditor } = useEditorComponent();
153154
const { appendChildrenMdx } = useEditorComponentChildren();
154155

155-
return (
156-
<>
157-
<AccordionContext.Provider value={contextValue}>
156+
const accordionGroupContent = (
157+
<AccordionContext.Provider value={contextValue}>
158+
<div ref={popoverRef} className="relative">
159+
{isWithinEditor && (
160+
<EditorComponentPopoverButton className="absolute -right-8 top-2 z-10 h-auto w-auto px-2" />
161+
)}
158162
<AccordionComponent.Accordion
159163
type="multiple"
160164
value={activeTabs}
@@ -177,8 +181,18 @@ export function AccordionGroup({ children }: AccordionGroupProps) {
177181
</div>
178182
)}
179183
</AccordionComponent.Accordion>
180-
</AccordionContext.Provider>
181-
</>
184+
</div>
185+
</AccordionContext.Provider>
186+
);
187+
188+
if (!isWithinEditor) {
189+
return accordionGroupContent;
190+
}
191+
192+
return (
193+
<EditorComponentPopoverProvider attributes={{}} targetRef={popoverRef} hoverSlopThreshold={48}>
194+
{accordionGroupContent}
195+
</EditorComponentPopoverProvider>
182196
);
183197
}
184198

@@ -214,8 +228,9 @@ function AccordionItem({
214228
nestedHeaders,
215229
registerAccordion,
216230
unregisterAccordion,
217-
accordions
218-
}: AccordionProps & AccordionContextType) {
231+
accordions,
232+
isInGroup = false
233+
}: AccordionProps & AccordionContextType & { isInGroup?: boolean }) {
219234
const uniqueId = React.useId();
220235
const accordionId = id || `accordion-${uniqueId}`;
221236

@@ -251,7 +266,7 @@ function AccordionItem({
251266
{isWithinEditor && (
252267
<EditorComponentPopoverButton
253268
className="absolute right-2 top-2 h-auto w-auto px-2"
254-
disableDelete={accordions.length === 1}
269+
disableDelete={isInGroup && accordions.length === 1}
255270
/>
256271
)}
257272
</AccordionComponent.AccordionTrigger>
@@ -292,10 +307,11 @@ export function Accordion(props: AccordionProps) {
292307
console.error("[unregisterAccordion] AccordionItem is not within an AccordionContext");
293308
}}
294309
accordions={[{ id: props.id || "", title: props.title || "" }]}
310+
isInGroup={false}
295311
/>
296312
</AccordionComponent.Accordion>
297313
);
298314
}
299315

300-
return <AccordionItem {...props} {...accordionContext} />;
316+
return <AccordionItem {...props} {...accordionContext} isInGroup={true} />;
301317
}

0 commit comments

Comments
 (0)