Skip to content

feat: Add Settings menu to scroll info panel to top when browsing through rows #2937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d4bce76
fix: AggregationPanel scroll position persists when using cache
mtrezza Jul 17, 2025
f7101ce
fix: reset aggregation panel scroll when loading cached data
mtrezza Jul 18, 2025
e9693c1
fix: aggregation panel retains scroll when using cached data
mtrezza Jul 18, 2025
8af69bc
fix: momentum scroll persists on cached panel
mtrezza Jul 19, 2025
e4dbc17
Merge branch 'alpha' into codex/fix-scroll-position-in-aggregationpanel
mtrezza Jul 19, 2025
3248cc6
Revert "fix: momentum scroll persists on cached panel"
mtrezza Jul 20, 2025
3fc8714
feat
mtrezza Jul 20, 2025
71dfd92
add script icon
mtrezza Jul 20, 2025
f100868
feat: add nested info panel menu in settings
mtrezza Jul 20, 2025
3858279
fix: submenu hover and toggle bug
mtrezza Jul 20, 2025
ad92515
fix: nested menu closes on hover and show chevron
mtrezza Jul 21, 2025
30974be
fix: nested submenu overflowing off screen
mtrezza Jul 23, 2025
4f94157
fix: submenu shows duplicate title
mtrezza Jul 23, 2025
60c46e8
fix: submenu offset from parent
mtrezza Jul 23, 2025
f435cfb
fix: submenu overlays parent item
mtrezza Jul 23, 2025
c2fc2da
fix: submenu opens too far left
mtrezza Jul 23, 2025
58b97b7
fix: submenu action not executed
mtrezza Jul 23, 2025
5ab0681
fix: submenu item action not triggered
mtrezza Jul 23, 2025
4858d43
fix: run menu item action before closing
mtrezza Jul 23, 2025
a192947
Revert "fix: run menu item action before closing"
mtrezza Jul 24, 2025
3c3e112
Update Views.react.js
mtrezza Jul 24, 2025
5d8cd0b
Update BrowserMenu.react.js
mtrezza Jul 24, 2025
e4eb0a4
Merge branch 'alpha' into feat/add-scoll-option
mtrezza Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 79 additions & 29 deletions src/components/BrowserMenu/BrowserMenu.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
import Popover from 'components/Popover/Popover.react';
import styles from 'components/BrowserMenu/BrowserMenu.scss';
import Icon from 'components/Icon/Icon.react';
import Popover from 'components/Popover/Popover.react';
import Position from 'lib/Position';
import PropTypes from 'lib/PropTypes';
import React from 'react';
import styles from 'components/BrowserMenu/BrowserMenu.scss';

export default class BrowserMenu extends React.Component {
constructor() {
super();

this.state = { open: false };
this.state = { open: false, openToLeft: false };
this.wrapRef = React.createRef();
}

render() {
let menu = null;
const isSubmenu = !!this.props.parentClose;
if (this.state.open) {
const position = Position.inDocument(this.wrapRef.current);
const titleStyle = [styles.title];
Expand All @@ -35,20 +36,50 @@ export default class BrowserMenu extends React.Component {
onExternalClick={() => this.setState({ open: false })}
>
<div className={styles.menu}>
<div className={titleStyle.join(' ')} onClick={() => this.setState({ open: false })}>
<Icon name={this.props.icon} width={14} height={14} />
<span>{this.props.title}</span>
</div>
<div className={styles.body} style={{ minWidth: this.wrapRef.current.clientWidth }}>
{React.Children.map(this.props.children, child =>
React.cloneElement(child, {
...child.props,
onClick: () => {
this.setState({ open: false });
child.props.onClick();
},
})
)}
{!isSubmenu && (
<div
className={titleStyle.join(' ')}
onClick={() => this.setState({ open: false })}
>
{this.props.icon && <Icon name={this.props.icon} width={14} height={14} />}
<span>{this.props.title}</span>
</div>
)}
<div
className={
isSubmenu
? this.state.openToLeft
? styles.subMenuBodyLeft
: styles.subMenuBody
: styles.body
}
style={{
minWidth: this.wrapRef.current.clientWidth,
...(isSubmenu
? {
top: 0,
left: this.state.openToLeft
? 0
: `${this.wrapRef.current.clientWidth - 3}px`,
transform: this.state.openToLeft
? 'translateX(calc(-100% + 3px))'
: undefined,
}
: {}),
}}
>
{React.Children.map(this.props.children, (child) => {
if (React.isValidElement(child) && child.type === BrowserMenu) {
return React.cloneElement(child, {
...child.props,
parentClose: () => {
this.setState({ open: false });
this.props.parentClose?.();
},
});
}
return child;
})}
</div>
</div>
</Popover>
Expand All @@ -61,18 +92,37 @@ export default class BrowserMenu extends React.Component {
if (this.props.disabled) {
classes.push(styles.disabled);
}
let onClick = null;
const entryEvents = {};
if (!this.props.disabled) {
onClick = () => {
this.setState({ open: true });
this.props.setCurrent(null);
};
if (isSubmenu) {
entryEvents.onMouseEnter = () => {
const rect = this.wrapRef.current.getBoundingClientRect();
const width = this.wrapRef.current.clientWidth;
const openToLeft = rect.right + width > window.innerWidth;
this.setState({ open: true, openToLeft });
this.props.setCurrent?.(null);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing setCurrent to PropTypes

The setCurrent prop is used in the component but not defined in PropTypes. This should be documented for better type safety and clarity.

 BrowserMenu.propTypes = {
   icon: PropTypes.string.describe('The name of the icon to place in the menu.'),
   title: PropTypes.string.isRequired.describe('The title text of the menu.'),
   children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).describe(
     'The contents of the menu when open. It should be a set of MenuItem and Separator components.'
   ),
   parentClose: PropTypes.func.describe(
     'Closes the parent menu when a nested menu item is selected.'
   ),
+  setCurrent: PropTypes.func.describe(
+    'Function to set the current menu state in the parent component.'
+  ),
+  active: PropTypes.bool.describe(
+    'Whether the menu item is currently active.'
+  ),
+  disabled: PropTypes.bool.describe(
+    'Whether the menu item is disabled.'
+  ),
 };

Also applies to: 108-108, 133-142

🤖 Prompt for AI Agents
In src/components/BrowserMenu/BrowserMenu.react.js around lines 103, 108, and
133-142, the setCurrent prop is used but missing from the component's PropTypes
definition. Add setCurrent to the PropTypes object, specifying its expected type
(likely a function), to improve type safety and documentation of the component's
props.

};
} else {
entryEvents.onClick = () => {
this.setState({ open: true, openToLeft: false });
this.props.setCurrent(null);
};
}
}
return (
<div className={styles.wrap} ref={this.wrapRef}>
<div className={classes.join(' ')} onClick={onClick}>
<Icon name={this.props.icon} width={14} height={14} />
<div className={classes.join(' ')} {...entryEvents}>
{this.props.icon && <Icon name={this.props.icon} width={14} height={14} />}
<span>{this.props.title}</span>
{isSubmenu &&
React.Children.toArray(this.props.children).some(c => React.isValidElement(c) && c.type === BrowserMenu) && (
<Icon
name="right-outline"
width={12}
height={12}
className={styles.submenuArrow}
/>
)}
</div>
{menu}
</div>
Expand All @@ -81,12 +131,12 @@ export default class BrowserMenu extends React.Component {
}

BrowserMenu.propTypes = {
icon: PropTypes.string.isRequired.describe('The name of the icon to place in the menu.'),
icon: PropTypes.string.describe('The name of the icon to place in the menu.'),
title: PropTypes.string.isRequired.describe('The title text of the menu.'),
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).describe(
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).describe(
'The contents of the menu when open. It should be a set of MenuItem and Separator components.'
),
parentClose: PropTypes.func.describe(
'Closes the parent menu when a nested menu item is selected.'
),
};
32 changes: 32 additions & 0 deletions src/components/BrowserMenu/BrowserMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,38 @@
font-size: 14px;
}


.subMenuBody {
position: absolute;
top: 0;
border-radius: 0 5px 5px 5px;
background: #797592;
padding: 8px 0;
font-size: 14px;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
}

.subMenuBodyLeft {
position: absolute;
top: 0;
border-radius: 5px 0 5px 5px;
background: #797592;
padding: 8px 0;
font-size: 14px;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
}

.submenuArrow {
margin-left: 4px;
fill: #66637A;
}

.entry:hover .submenuArrow {
fill: white;
}

.item {
padding: 4px 14px;
white-space: nowrap;
Expand Down
18 changes: 17 additions & 1 deletion src/components/BrowserMenu/MenuItem.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,24 @@ const MenuItem = ({ text, disabled, active, greenActive, onClick }) => {
if (greenActive) {
classes.push(styles.greenActive);
}

const handleClick = (e) => {
if (!disabled && onClick) {
onClick(e);
}
};

return (
<div className={classes.join(' ')} onClick={disabled ? undefined : onClick}>
<div
className={classes.join(' ')}
onClick={handleClick}
onMouseDown={handleClick} // This is needed - onClick alone doesn't work in this context
Comment on lines +32 to +33
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential double execution of click handler

Setting both onClick and onMouseDown to the same handler could cause the function to execute twice in some scenarios. If onClick alone doesn't work due to event propagation issues, consider using only onMouseDown or investigating the root cause.

-      onClick={handleClick}
-      onMouseDown={handleClick} // This is needed - onClick alone doesn't work in this context
+      onMouseDown={handleClick} // Using onMouseDown due to event propagation requirements
📝 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
onClick={handleClick}
onMouseDown={handleClick} // This is needed - onClick alone doesn't work in this context
onMouseDown={handleClick} // Using onMouseDown due to event propagation requirements
🤖 Prompt for AI Agents
In src/components/BrowserMenu/MenuItem.react.js at lines 32-33, the click
handler is assigned to both onClick and onMouseDown, which may cause it to
execute twice. To fix this, remove the onClick handler and keep only onMouseDown
if onClick alone doesn't work, or investigate event propagation issues to ensure
the handler runs only once per user interaction.

style={{
position: 'relative',
zIndex: 9999,
cursor: 'pointer'
}}
Comment on lines +34 to +38
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Move inline styles to CSS for better maintainability

The inline styles should be moved to the BrowserMenu.scss file to maintain consistency and improve maintainability. The high z-index value (9999) could also cause stacking issues with other UI elements.

Add to BrowserMenu.scss:

.item {
  position: relative;
  z-index: 10; // Use a more reasonable z-index
  cursor: pointer;
}

Then update the component:

-      style={{
-        position: 'relative',
-        zIndex: 9999,
-        cursor: 'pointer'
-      }}
🤖 Prompt for AI Agents
In src/components/BrowserMenu/MenuItem.react.js around lines 34 to 38, move the
inline styles for position, zIndex, and cursor into the BrowserMenu.scss file by
creating a CSS class named "item" with position relative, z-index 10, and cursor
pointer. Then update the component to use this CSS class instead of inline
styles to improve maintainability and avoid potential stacking issues from the
high z-index value.

>
{text}
</div>
);
Expand Down
12 changes: 9 additions & 3 deletions src/components/Icon/Icon.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
*/
import PropTypes from 'lib/PropTypes';
import React from 'react';
import styles from 'components/Icon/Icon.scss';

const Icon = ({ name, fill, width, height }) => {
const Icon = ({ name, fill, width, height, style, className }) => {
const props = {
width: width,
height: height,
width,
height,
style,
};
if (className) {
props.className = styles[className] || className;
}
if (fill) {
props.fill = fill;
}
Expand All @@ -30,4 +35,5 @@ Icon.propTypes = {
width: PropTypes.number.isRequired.describe('The icon width, in pixels.'),
height: PropTypes.number.isRequired.describe('The icon height, in pixels.'),
fill: PropTypes.string.describe('A valid color, used as the fill property for the SVG.'),
style: PropTypes.object.describe('An object containing CSS styles to apply to the icon.'),
};
14 changes: 14 additions & 0 deletions src/components/Icon/Icon.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2016-present, Parse, LLC
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
@import 'stylesheets/globals.scss';

.menuCheck {
margin-right: 6px;
margin-bottom: -1px;
padding-top: 0;
}
44 changes: 35 additions & 9 deletions src/dashboard/Data/Browser/BrowserToolbar.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
*/
import BrowserFilter from 'components/BrowserFilter/BrowserFilter.react';
import BrowserMenu from 'components/BrowserMenu/BrowserMenu.react';
import Icon from 'components/Icon/Icon.react';
import MenuItem from 'components/BrowserMenu/MenuItem.react';
import prettyNumber from 'lib/prettyNumber';
import React, { useRef } from 'react';
import Separator from 'components/BrowserMenu/Separator.react';
import styles from 'dashboard/Data/Browser/Browser.scss';
import Toolbar from 'components/Toolbar/Toolbar.react';
import SecurityDialog from 'dashboard/Data/Browser/SecurityDialog.react';
import ColumnsConfiguration from 'components/ColumnsConfiguration/ColumnsConfiguration.react';
import SecureFieldsDialog from 'dashboard/Data/Browser/SecureFieldsDialog.react';
import LoginDialog from 'dashboard/Data/Browser/LoginDialog.react';
import Icon from 'components/Icon/Icon.react';
import Toggle from 'components/Toggle/Toggle.react';
import Toolbar from 'components/Toolbar/Toolbar.react';
import styles from 'dashboard/Data/Browser/Browser.scss';
import LoginDialog from 'dashboard/Data/Browser/LoginDialog.react';
import SecureFieldsDialog from 'dashboard/Data/Browser/SecureFieldsDialog.react';
import SecurityDialog from 'dashboard/Data/Browser/SecurityDialog.react';
import prettyNumber from 'lib/prettyNumber';
import React, { useRef } from 'react';

const BrowserToolbar = ({
className,
Expand Down Expand Up @@ -82,6 +82,8 @@ const BrowserToolbar = ({
classwiseCloudFunctions,
appId,
appName,
scrollToTop,
toggleScrollToTop,
}) => {
const selectionLength = Object.keys(selection).length;
const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0;
Expand Down Expand Up @@ -424,7 +426,7 @@ const BrowserToolbar = ({
<noscript />
)}
{enableSecurityDialog ? <div className={styles.toolbarSeparator} /> : <noscript />}
<BrowserMenu setCurrent={setCurrent} title="Script" icon="gear-solid">
<BrowserMenu setCurrent={setCurrent} title="Script" icon="script-solid">
<MenuItem
disabled={selectionLength === 0}
text={
Expand All @@ -443,6 +445,30 @@ const BrowserToolbar = ({
<MenuItem text={'Cancel all pending rows'} onClick={onCancelPendingEditRows} />
</BrowserMenu>
)}
<div className={styles.toolbarSeparator} />
<BrowserMenu setCurrent={setCurrent} title="Settings" icon="gear-solid">
<BrowserMenu title="Info Panel" setCurrent={setCurrent}>
<MenuItem
text={
<span>
{scrollToTop && (
<Icon
name="check"
width={12}
height={12}
fill="#ffffffff"
className="menuCheck"
/>
)}
Scroll to top
</span>
}
onClick={() => {
toggleScrollToTop();
}}
/>
</BrowserMenu>
</BrowserMenu>
</Toolbar>
);
};
Expand Down
Loading
Loading