Skip to content

fix(modal-v2): compose scroll locks + respect nested Radix layers on Escape - #1675

Open
michaelassraf wants to merge 2 commits into
mainfrom
fix/modal-v2-scroll-lock-and-escape
Open

fix(modal-v2): compose scroll locks + respect nested Radix layers on Escape#1675
michaelassraf wants to merge 2 commits into
mainfrom
fix/modal-v2-scroll-lock-and-escape

Conversation

@michaelassraf

@michaelassraf michaelassraf commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Two ModalV2 defects surfaced by real-input testing on the hub (Radix Select open inside a form modal):

  1. Scroll-lock fight. ModalV2 locked scroll with react-aria's usePreventScroll; a Radix Select opened inside the modal adds its own react-remove-scroll lock on top. The two techniques don't compose — body jumped and the page behind rendered black while the select was open. ModalV2 now locks with react-remove-scroll itself (declared dep), which is ref-counted and composes with the locks Radix primitives add.

  2. Escape closed the whole modal. ModalV2's document-level Escape handler ignored event.defaultPrevented, so the Escape a nested Select/DropdownMenu consumed ALSO closed the modal. Now checked.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added accessible form fields with labels, hints, and validation messaging.
    • Added reusable information hints, JSON detail displays, and unsaved-changes indicators.
    • Expanded dropdowns with grouped items, button styles, loading, disabled, and focus options.
    • Added an admin-rail tabs variant with responsive horizontal scrolling.
    • Added confirmation handling for closing forms with unsaved changes.
  • Bug Fixes
    • Improved modal scroll locking to prevent background scrolling while open.
    • Prevented nested modal interactions from closing unexpectedly on Escape.

…Escape [preview:none]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds reusable frontend UI components, expands DropdownButton, adds guarded modal closing, introduces the admin-rail tabs variant, updates modal scroll handling, and adds Storybook stories for the new behavior.

Changes

Frontend UI updates

Layer / File(s) Summary
Modal scroll locking and Escape guard
openframe-frontend-core/package.json, openframe-frontend-core/src/components/ui/modal-v2.tsx
The modal uses react-remove-scroll while open and ignores default-prevented Escape events.
Dropdown trigger and menu extensions
openframe-frontend-core/src/components/ui/dropdown-button.tsx, openframe-frontend-core/src/stories/DropdownButton.stories.tsx
DropdownButton supports grouped items, Button variants, loading and disabled states, resolved accessibility labels, and close-focus handling.
Shared field, hint, and JSON components
openframe-frontend-core/src/components/ui/field.tsx, openframe-frontend-core/src/components/ui/info-hint.tsx, openframe-frontend-core/src/components/ui/json-detail-list.tsx, openframe-frontend-core/src/components/ui/index.ts, openframe-frontend-core/src/stories/Field.stories.tsx, openframe-frontend-core/src/stories/InfoHint.stories.tsx, openframe-frontend-core/src/stories/JsonDetailList.stories.tsx
The package adds accessible fields, tooltip hints, recursive JSON detail rendering, barrel exports, and Storybook examples.
Guarded modal close flow
openframe-frontend-core/src/components/ui/modal-guarded-close.tsx, openframe-frontend-core/src/components/ui/index.ts, openframe-frontend-core/src/stories/ModalGuardedClose.stories.tsx
useConfirm and useGuardedClose confirm dirty-form closure. UnsavedChangesChip displays unsaved state.
Tabs admin-rail variant
openframe-frontend-core/src/components/ui/tabs.tsx, openframe-frontend-core/src/stories/Tabs.stories.tsx
TabsList and TabsTrigger support default and admin-rail styling, including bordered horizontal overflow and disabled states.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: oleksandr-blip

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DropdownButton
  participant ActionsMenu
  participant Button
  User->>DropdownButton: select trigger
  DropdownButton->>Button: render standard trigger when configured
  DropdownButton->>ActionsMenu: pass grouped or flat menu data
  ActionsMenu-->>User: display menu actions
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the ModalV2 scroll-lock composition and nested Radix Escape-handling fixes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/modal-v2-scroll-lock-and-escape

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openframe-frontend-core/src/components/ui/modal-v2.tsx`:
- Around line 151-157: Update the Escape handling in the ModalV2 document-level
useEffect so onClose is invoked only when the current modal is the topmost entry
in modalStack. Preserve the existing event.key === 'Escape' and
!event.defaultPrevented checks for nested Radix layers, and add the stack check
before calling onClose.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 18c76279-f47e-4736-8259-0a1503d80e30

📥 Commits

Reviewing files that changed from the base of the PR and between f3e4588 and bb59faf.

📒 Files selected for processing (2)
  • openframe-frontend-core/package.json
  • openframe-frontend-core/src/components/ui/modal-v2.tsx

Comment on lines 151 to +157
// Escape key (document-level: top-of-stack semantics for modals)
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
// A nested Radix layer (Select, DropdownMenu) preventDefaults the
// Escape it consumes — without this check, closing a select inside
// the modal closed the WHOLE modal.
if (event.key === 'Escape' && !event.defaultPrevented) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restrict Escape handling to the topmost modal.

Each open ModalV2 instance registers this document listener. The listener does not check modalStack. If two modals are open, one Escape can call onClose for both instances. event.defaultPrevented only filters nested Radix layers.

Add a top-of-stack check before calling onClose.

Proposed fix
-        if (event.key === 'Escape' && !event.defaultPrevented) {
+        if (
+          event.key === 'Escape' &&
+          !event.defaultPrevented &&
+          modalStack[modalStack.length - 1] === stackIdRef.current
+        ) {
           onClose()
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Escape key (document-level: top-of-stack semantics for modals)
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
// A nested Radix layer (Select, DropdownMenu) preventDefaults the
// Escape it consumes — without this check, closing a select inside
// the modal closed the WHOLE modal.
if (event.key === 'Escape' && !event.defaultPrevented) {
// Escape key (document-level: top-of-stack semantics for modals)
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// A nested Radix layer (Select, DropdownMenu) preventDefaults the
// Escape it consumes — without this check, closing a select inside
// the modal closed the WHOLE modal.
if (
event.key === 'Escape' &&
!event.defaultPrevented &&
modalStack[modalStack.length - 1] === stackIdRef.current
) {
onClose()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openframe-frontend-core/src/components/ui/modal-v2.tsx` around lines 151 -
157, Update the Escape handling in the ModalV2 document-level useEffect so
onClose is invoked only when the current modal is the topmost entry in
modalStack. Preserve the existing event.key === 'Escape' and
!event.defaultPrevented checks for nested Radix layers, and add the stack check
before calling onClose.

…oHint, JsonDetailList, DropdownButton groups/variant/loading, Tabs admin-rail variant, useConfirm/useGuardedClose/UnsavedChangesChip + stories [preview:none]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openframe-frontend-core/src/components/ui/dropdown-button.tsx`:
- Around line 81-89: Update handleOpenChange so disabled or loading only
prevents opening the menu, not closing it: guard the setOpen call when next is
true and either state is active, while allowing next === false to close an
already-open menu.

In `@openframe-frontend-core/src/components/ui/field.tsx`:
- Line 24: Update the Field component’s children contract to require a render
function accepting FieldRenderProps, removing support for plain React nodes. In
the Field render logic, invoke this function directly with renderProps so the
child receives controlId and the Label target remains valid.

In `@openframe-frontend-core/src/components/ui/json-detail-list.tsx`:
- Around line 72-92: Update JsonDetailList to track whether a string parse
succeeded, and use that state in the non-object/array branch. Render parsed
arrays and scalar values from obj through Value, while retaining the original
data string only when JSON.parse fails; preserve existing handling for
non-string inputs.

In `@openframe-frontend-core/src/components/ui/modal-guarded-close.tsx`:
- Around line 36-63: Update useConfirm to store the active pending confirmation
request in a ref alongside its state, and add an unmount cleanup effect that
resolves any active request with false and clears the asking state. Keep decide
synchronized with the ref so normal decisions resolve the same request without
later cleanup resolving it again.

In `@openframe-frontend-core/src/stories/DropdownButton.stories.tsx`:
- Line 30: Replace the raw pixel sizing utilities with the appropriate ODS
sizing tokens at all affected sites:
openframe-frontend-core/src/stories/DropdownButton.stories.tsx lines 30-30,
openframe-frontend-core/src/components/ui/info-hint.tsx lines 39-39, and
openframe-frontend-core/src/stories/InfoHint.stories.tsx lines 34-34 and 52-52.
Preserve the existing layout intent while removing min-h-[320px], max-w-[280px],
and both min-h-[160px] utilities.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 160cc0a8-07cf-455b-ae6c-6097b24cc651

📥 Commits

Reviewing files that changed from the base of the PR and between bb59faf and 959bbea.

📒 Files selected for processing (13)
  • openframe-frontend-core/src/components/ui/dropdown-button.tsx
  • openframe-frontend-core/src/components/ui/field.tsx
  • openframe-frontend-core/src/components/ui/index.ts
  • openframe-frontend-core/src/components/ui/info-hint.tsx
  • openframe-frontend-core/src/components/ui/json-detail-list.tsx
  • openframe-frontend-core/src/components/ui/modal-guarded-close.tsx
  • openframe-frontend-core/src/components/ui/tabs.tsx
  • openframe-frontend-core/src/stories/DropdownButton.stories.tsx
  • openframe-frontend-core/src/stories/Field.stories.tsx
  • openframe-frontend-core/src/stories/InfoHint.stories.tsx
  • openframe-frontend-core/src/stories/JsonDetailList.stories.tsx
  • openframe-frontend-core/src/stories/ModalGuardedClose.stories.tsx
  • openframe-frontend-core/src/stories/Tabs.stories.tsx

Comment on lines 81 to +89
const [open, setOpen] = React.useState(false)

const handleOpenChange = React.useCallback(
(next: boolean) => {
if (disabled) return
if (disabled || loading) return
setOpen(next)
},
[disabled]
[disabled, loading]
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Allow the menu to close after the trigger becomes disabled or loading.

Line 85 rejects both open and close transitions. If the menu is open when loading or disabled changes to true, Escape and outside-click dismissal cannot close it.

Close the menu when either state becomes true. Only block next === true.

Proposed fix
   const [open, setOpen] = React.useState(false)

+  React.useEffect(() => {
+    if (disabled || loading) setOpen(false)
+  }, [disabled, loading])
+
   const handleOpenChange = React.useCallback(
     (next: boolean) => {
-      if (disabled || loading) return
+      if (next && (disabled || loading)) return
       setOpen(next)
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [open, setOpen] = React.useState(false)
const handleOpenChange = React.useCallback(
(next: boolean) => {
if (disabled) return
if (disabled || loading) return
setOpen(next)
},
[disabled]
[disabled, loading]
)
const [open, setOpen] = React.useState(false)
React.useEffect(() => {
if (disabled || loading) setOpen(false)
}, [disabled, loading])
const handleOpenChange = React.useCallback(
(next: boolean) => {
if (next && (disabled || loading)) return
setOpen(next)
},
[disabled, loading]
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openframe-frontend-core/src/components/ui/dropdown-button.tsx` around lines
81 - 89, Update handleOpenChange so disabled or loading only prevents opening
the menu, not closing it: guard the setOpen call when next is true and either
state is active, while allowing next === false to close an already-open menu.

/** Definition shown in a hover `InfoHint` next to the label. */
hint?: React.ReactNode
required?: boolean
children: React.ReactNode | ((props: FieldRenderProps) => React.ReactNode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require the render-prop child contract.

Line 24 permits a plain React node. That node cannot receive controlId. The Label at Line 56 then points to an element that does not exist.

Make children a required render function. Render it directly with renderProps.

Proposed fix
 export interface FieldProps {
   label: string
   hint?: React.ReactNode
   required?: boolean
-  children: React.ReactNode | ((props: FieldRenderProps) => React.ReactNode)
+  children: (props: FieldRenderProps) => React.ReactNode
   error?: string | null
 }

-        {typeof children === 'function' ? children(renderProps) : children}
+        {children(renderProps)}

Also applies to: 63-63

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openframe-frontend-core/src/components/ui/field.tsx` at line 24, Update the
Field component’s children contract to require a render function accepting
FieldRenderProps, removing support for plain React nodes. In the Field render
logic, invoke this function directly with renderProps so the child receives
controlId and the Label target remains valid.

Comment on lines +72 to +92
export function JsonDetailList({ data, depth = 0 }: JsonDetailListProps) {
const obj =
typeof data === 'string'
? (() => {
try {
return JSON.parse(data) as unknown
} catch {
return null
}
})()
: data

if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
// Not an object — render whatever it is faithfully.
return typeof data === 'string' ? (
<p className="whitespace-pre-wrap text-h6 text-ods-text-primary">{data}</p>
) : (
<pre className="whitespace-pre-wrap text-code text-ods-text-secondary">
{JSON.stringify(data, null, 2)}
</pre>
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Render parsed JSON arrays and scalar values from obj.

A valid JSON string such as ["a","b"] reaches Line 84 because obj is an array. Line 86 then renders the original source string instead of the parsed array. The same occurs for JSON scalar strings.

Track whether parsing succeeded. For parsed non-object values, render obj through Value. Keep the raw-string fallback only for parse failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openframe-frontend-core/src/components/ui/json-detail-list.tsx` around lines
72 - 92, Update JsonDetailList to track whether a string parse succeeded, and
use that state in the non-object/array branch. Render parsed arrays and scalar
values from obj through Value, while retaining the original data string only
when JSON.parse fails; preserve existing handling for non-string inputs.

Comment on lines +36 to +63
export function useConfirm() {
const [open, setOpen] = React.useState(false)
const [pending, setPending] = React.useState<{
title: string
body: string
resolve: (ok: boolean) => void
} | null>(null)
const askingRef = React.useRef(false)
const ask = React.useCallback((title: string, body: string) => {
return new Promise<boolean>((resolve) => {
if (askingRef.current) {
resolve(false)
return
}
askingRef.current = true
setPending({ title, body, resolve })
setOpen(true)
})
}, [])
const decide = React.useCallback(
(ok: boolean) => {
askingRef.current = false
setOpen(false)
pending?.resolve(ok)
setPending(null)
},
[pending]
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate file:"
git ls-files | rg '^openframe-frontend-core/src/components/ui/modal-guarded-close\.tsx$' || true

echo
echo "File outline and relevant contents:"
if [ -f openframe-frontend-core/src/components/ui/modal-guarded-close.tsx ]; then
  wc -l openframe-frontend-core/src/components/ui/modal-guarded-close.tsx
  sed -n '1,150p' openframe-frontend-core/src/components/ui/modal-guarded-close.tsx | cat -n
fi

echo
echo "Search usages of useConfirm/useGuardedClose:"
rg -n "useConfirm|useGuardedClose|guard.*close|UnsavedChangesChip" openframe-frontend-core/src -g '!components/ui/modal-guarded-close.tsx' || true

echo
echo "Static check of hook call order and cleanup presence in file:"
python3 - <<'PY'
from pathlib import Path
p=Path('openframe-frontend-core/src/components/ui/modal-guarded-close.tsx')
lines=p.read_text().splitlines()
in_use_confirm=False
brace=0
for i,line in enumerate(lines,1):
    if 'export function useConfirm' in line:
        in_use_confirm=True; start=i
    if in_use_confirm:
        print(f"{i}: {line}")
PY

Repository: flamingo-stack/openframe-oss-lib

Length of output: 12002


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate file:"
git ls-files | rg '^openframe-frontend-core/src/components/ui/modal-guarded-close\.tsx$' || true

echo
echo "File outline and relevant contents:"
if [ -f openframe-frontend-core/src/components/ui/modal-guarded-close.tsx ]; then
  wc -l openframe-frontend-core/src/components/ui/modal-guarded-close.tsx
  sed -n '1,150p' openframe-frontend-core/src/components/ui/modal-guarded-close.tsx | cat -n
fi

echo
echo "Search usages of useConfirm/useGuardedClose:"
rg -n "useConfirm|useGuardedClose|guard.*close|UnsavedChangesChip" openframe-frontend-core/src -g '!components/ui/modal-guarded-close.tsx' || true

echo
echo "Static check of hook call order and cleanup presence in file:"
python3 - <<'PY'
from pathlib import Path
p=Path('openframe-frontend-core/src/components/ui/modal-guarded-close.tsx')
lines=p.read_text().splitlines()
in_use_confirm=False
for i,line in enumerate(lines,1):
    if 'export function useConfirm' in line:
        in_use_confirm=True; start=i
    if in_use_confirm:
        print(f"{i}: { line }")
PY

Repository: flamingo-stack/openframe-oss-lib

Length of output: 12002


Resolve an active confirmation during unmount.

If the host unmounts while the confirmation is open, ask() never resolves. This leaves callers waiting on the promise after the only decision UI has been removed.

Store the active request in a ref and resolve it with false from a cleanup effect when the component unmounts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openframe-frontend-core/src/components/ui/modal-guarded-close.tsx` around
lines 36 - 63, Update useConfirm to store the active pending confirmation
request in a ref alongside its state, and add an unmount cleanup effect that
resolves any active request with false and clears the asking state. Keep decide
synchronized with the ref so normal decisions resolve the same request without
later cleanup resolving it again.

},
decorators: [
(Story) => (
<div className="flex min-h-[320px] items-start justify-center p-8">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace raw pixel dimensions with ODS sizing tokens.

These additions use raw pixel dimensions instead of ODS sizing tokens.

  • openframe-frontend-core/src/stories/DropdownButton.stories.tsx#L30-L30: replace min-h-[320px].
  • openframe-frontend-core/src/components/ui/info-hint.tsx#L39-L39: replace max-w-[280px].
  • openframe-frontend-core/src/stories/InfoHint.stories.tsx#L34-L34: replace min-h-[160px].
  • openframe-frontend-core/src/stories/InfoHint.stories.tsx#L52-L52: replace min-h-[160px].

As per coding guidelines, “Do not use hardcoded styles; translate Figma/Tailwind output and all hand-written styling to ODS tokens, raw hex colors, pixel sizes, and font shorthands are not allowed.”

📍 Affects 3 files
  • openframe-frontend-core/src/stories/DropdownButton.stories.tsx#L30-L30 (this comment)
  • openframe-frontend-core/src/components/ui/info-hint.tsx#L39-L39
  • openframe-frontend-core/src/stories/InfoHint.stories.tsx#L34-L34
  • openframe-frontend-core/src/stories/InfoHint.stories.tsx#L52-L52
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openframe-frontend-core/src/stories/DropdownButton.stories.tsx` at line 30,
Replace the raw pixel sizing utilities with the appropriate ODS sizing tokens at
all affected sites:
openframe-frontend-core/src/stories/DropdownButton.stories.tsx lines 30-30,
openframe-frontend-core/src/components/ui/info-hint.tsx lines 39-39, and
openframe-frontend-core/src/stories/InfoHint.stories.tsx lines 34-34 and 52-52.
Preserve the existing layout intent while removing min-h-[320px], max-w-[280px],
and both min-h-[160px] utilities.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant