Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 60 additions & 16 deletions src/components/inputs/company-input-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@

import React from "react";
import PropTypes from "prop-types";
import T from "i18n-react/dist/i18n-react";
import { TextField, Autocomplete, Typography } from "@mui/material";
import { queryRegistrationCompanies } from "../../utils/query-actions";

const CompanyInputV2 = ({ summitId, isRequired, sx, onChange, id, name, label, value, error, helperText, onBlur, placeholder, options2Show, disableShrink, ...rest }) => {
const [inputValue, setInputValue] = React.useState("");
const [options, setOptions] = React.useState([]);

const noCompanyMatchText = T.translate("request_modal.no_company_match");
const createAccessRequestText = (companyName) =>
T.translate("request_modal.create_company_access_request", {
companyName: `"${companyName}"`
});

React.useEffect(() => {
if (inputValue === "") {
setOptions(value ? [value] : []);
Expand All @@ -40,7 +47,7 @@ const CompanyInputV2 = ({ summitId, isRequired, sx, onChange, id, name, label, v
setOptions(newOptions);
}, options2Show);
return undefined;
}, [value, inputValue]);
}, [value, inputValue, summitId, options2Show]);

return (
<Autocomplete
Expand Down Expand Up @@ -69,18 +76,18 @@ const CompanyInputV2 = ({ summitId, isRequired, sx, onChange, id, name, label, v
onChange={(_, newValue) => {
let tmpValue = newValue?.inputValue || newValue;
// if new option is selected ...
if (newValue && typeof newValue === 'object' && newValue.inputValue) {
if (newValue && typeof newValue === "object" && newValue.inputValue) {
tmpValue = {
id: 0,
name: newValue.inputValue
};
}
}
setOptions(tmpValue ? [tmpValue, ...options] : options);
let ev = {
target: {
id: name,
value: tmpValue,
type: 'companyinput'
type: "companyinput"
}
};
onChange(ev);
Expand All @@ -90,15 +97,24 @@ const CompanyInputV2 = ({ summitId, isRequired, sx, onChange, id, name, label, v
}}
filterOptions={(options, params) => {
const { inputValue } = params;
const trimmedInput = inputValue.trim();
const filtered = [...options];

// Suggest the creation of a new value
const isExisting = options.some(
(option) => inputValue === option.title
(option) => {
if (typeof option === "string") {
return option.toLowerCase() === trimmedInput.toLowerCase();
}

return option.name?.toLowerCase() === trimmedInput.toLowerCase();
}
);
if (inputValue !== "" && !isExisting) {

if (trimmedInput !== "" && !isExisting) {
filtered.push({
inputValue,
name: `Select "${inputValue}"`
inputValue: trimmedInput,
name: noCompanyMatchText
});
}

Expand All @@ -115,20 +131,48 @@ const CompanyInputV2 = ({ summitId, isRequired, sx, onChange, id, name, label, v
helperText={helperText}
error={error}
margin="normal"
InputLabelProps={{ shrink: disableShrink }}
InputLabelProps={disableShrink ? { shrink: false } : undefined}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

MUI Autocomplete v6 InputLabelProps shrink false value overlap behavior

💡 Result:

In MUI (Material-UI) v6, the Autocomplete component renders its input via the renderInput prop, typically a TextField. The TextField's label shrink behavior is controlled by the InputLabelProps={{ shrink: false }} passed to the TextField inside renderInput.[1][2][3] Setting shrink: false forces the InputLabel to remain in its expanded (non-shrunk) position inside the input field, regardless of whether the Autocomplete has a value, is focused, or has an inputValue.[4][5][6] This can cause the label text to visually overlap with the selected value or input text, as the label does not move to the top border position.[7][8][9][10] This overlap occurs because: - Normally, the label automatically shrinks (via data-shrink="true") when there is a value, focus, or certain input states, positioning it above the input border.[5] - With shrink: false, it stays overlaid on the input content.[11] - In Autocomplete, the internal inputValue may initially be empty even with a value prop, exacerbating animation/jump issues, though recent v6 fixes (e.g., #45734, #46047) address shrink animation regressions for controlled values and multiple mode with empty arrays.[12][13][14][15][16] Example causing overlap: <Autocomplete value={someValue} renderInput={(params) => ( <TextField {...params} label="Label" InputLabelProps={{ shrink: false }} // Forces non-shrunk state /> )} /> The label will overlap the displayed getOptionLabel(someValue) or input text.[8][17] To avoid overlap while keeping the label expanded initially: - Use dynamic shrink: InputLabelProps={{ shrink: !!value || focused }} with state for focus.[18] - Or let MUI auto-handle shrink (omit shrink: false), accepting automatic shrinking on value/focus.[5] - CSS hacks like adjusting top exist but are brittle.[7] Official docs note shrink state isn't always perfect (e.g., number/date inputs), recommending manual shrink override only when needed.[5]

Citations:


🏁 Script executed:

cat -n src/components/inputs/company-input-v2.js | sed -n '125,145p'

Repository: OpenStackweb/openstack-uicore-foundation

Length of output: 910


🏁 Script executed:

cat -n src/components/inputs/company-input-v2.js | sed -n '1,50p'

Repository: OpenStackweb/openstack-uicore-foundation

Length of output: 2284


🏁 Script executed:

cat -n src/components/inputs/company-input-v2.js | sed -n '110,140p'

Repository: OpenStackweb/openstack-uicore-foundation

Length of output: 1195


🏁 Script executed:

cat -n src/components/inputs/company-input-v2.js | sed -n '60,85p'

Repository: OpenStackweb/openstack-uicore-foundation

Length of output: 1131


Prevent label overlap when disableShrink is enabled.

Passing { shrink: false } unconditionally overrides MUI Autocomplete's label management. When a value is present and disableShrink is true, the label stays in its resting position and overlaps the displayed text. Apply shrink: false only when there is no value:

Suggested fix
-          InputLabelProps={disableShrink ? { shrink: false } : undefined}
+          InputLabelProps={disableShrink && !value ? { shrink: false } : undefined}
📝 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
InputLabelProps={disableShrink ? { shrink: false } : undefined}
InputLabelProps={disableShrink && !value ? { shrink: false } : undefined}
🤖 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 `@src/components/inputs/company-input-v2.js` at line 134, The InputLabelProps
is forcing shrink:false whenever disableShrink is true, which causes label
overlap when a value is present; update the InputLabelProps usage in
company-input-v2.js (the line that sets InputLabelProps={...}) to conditionally
set shrink: false only when disableShrink is true AND there is no value (e.g.,
InputLabelProps={ disableShrink ? { shrink: !value } : undefined } or equivalent
using the component's value/inputValue variable) so the label will still shrink
when a value exists.

/>
)}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
const isCreateOption = Boolean(option.inputValue);

return (
// eslint-disable-next-line react/jsx-props-no-spreading
<li key={key} {...optionProps}>
<Typography
variant="body2"
sx={{ fontSize: "1em", color: "text.secondary" }}
>
{option.name}
</Typography>
<li
key={key}
{...optionProps}
style={{
...(isCreateOption
? {
borderTop: "1px solid rgba(0,0,0,0.12)",
display: "block",
padding: "10px 15px"
}
: {})
}}
>
{isCreateOption ? (
<>
<Typography variant="body2" sx={{ color: "text.secondary" }}>
{noCompanyMatchText}
</Typography>
<Typography
variant="body2"
sx={{ color: "primary.main", fontWeight: 500 }}
>
{createAccessRequestText(option.inputValue)}
</Typography>
</>
) : (
<Typography
variant="body2"
sx={{ color: "text.secondary", padding: "5px 0" }}
>
{option.name}
</Typography>
)}
</li>
);
}}
Expand Down
206 changes: 169 additions & 37 deletions src/components/mui/AuthButton/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,56 +11,188 @@
* limitations under the License.
* */

import React, { useState } from "react";
import T from "i18n-react";
import {Button, Box} from '@mui/material';
import styles from "./styles.module.scss"
import React, {useRef, useState} from "react";
import T from "i18n-react/dist/i18n-react";
import {useTheme} from "@mui/material/styles";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Divider from "@mui/material/Divider";
import IconButton from "@mui/material/IconButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import Typography from "@mui/material/Typography";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import LogoutIcon from "@mui/icons-material/Logout";
import PersonIcon from "@mui/icons-material/Person";
import SettingsIcon from "@mui/icons-material/Settings";
import styles from "./styles.module.scss";

const AuthButton = ({ isLoggedUser, doLogin, initLogOut, picture }) => {
const [showLogOut, setShowLogOut] = useState(false);
const AuthButton = ({
isLoggedUser,
doLogin,
initLogOut,
profileEmail,
profileName
}) => {
const theme = useTheme();
const anchorRef = useRef(null);
const [menuOpen, setMenuOpen] = useState(false);

const toggleLogOut = () => {
setShowLogOut(!showLogOut);
const menuFontSize = theme?.custom?.menu?.fontSize || "1.4rem";
const menuLineHeight = theme?.custom?.menu?.lineHeight || "2rem";
const menuIconColor = theme?.custom?.menu?.icon || theme.palette.text.secondary;

const openMenu = () => {
setMenuOpen(true);
};

const closeMenu = () => {
setMenuOpen(false);
};

if (isLoggedUser) {
return (
<div className={styles.userMenu} onClick={toggleLogOut}>
<div
className={styles.profilePic}
style={{ backgroundImage: `url(${picture})` }}
/>
{showLogOut && (
<Button
className={styles.logout}
variant="contained"
size="small"
color="secondary"
<Box className={styles.userMenu}>
<IconButton
aria-controls={menuOpen ? "user-menu" : undefined}
aria-expanded={menuOpen ? "true" : undefined}
aria-haspopup="true"
onClick={openMenu}
size="medium"
data-testid="user-menu-button"
sx={{
color: theme.palette.text.primary,
p: 0,
width: 28,
height: 28
}}
ref={anchorRef}
>
<AccountCircleIcon sx={{fontSize: 28}}/>
</IconButton>
Comment on lines +58 to +74
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

IconButton is missing aria-label — screen readers cannot identify it.

The button contains only an icon with no text. Without an aria-label, assistive technologies announce it as an unlabeled button.

♿ Proposed fix
         <IconButton
           aria-controls={menuOpen ? "user-menu" : undefined}
           aria-expanded={menuOpen ? "true" : undefined}
           aria-haspopup="true"
+          aria-label="Open user menu"
           onClick={openMenu}
📝 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
<IconButton
aria-controls={menuOpen ? "user-menu" : undefined}
aria-expanded={menuOpen ? "true" : undefined}
aria-haspopup="true"
onClick={openMenu}
size="medium"
data-testid="user-menu-button"
sx={{
color: theme.palette.text.primary,
p: 0,
width: 28,
height: 28
}}
ref={anchorRef}
>
<AccountCircleIcon sx={{fontSize: 28}}/>
</IconButton>
<IconButton
aria-controls={menuOpen ? "user-menu" : undefined}
aria-expanded={menuOpen ? "true" : undefined}
aria-haspopup="true"
aria-label="Open user menu"
onClick={openMenu}
size="medium"
data-testid="user-menu-button"
sx={{
color: theme.palette.text.primary,
p: 0,
width: 28,
height: 28
}}
ref={anchorRef}
>
<AccountCircleIcon sx={{fontSize: 28}}/>
</IconButton>
🤖 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 `@src/components/mui/AuthButton/index.js` around lines 58 - 74, The IconButton
used as the user menu trigger (IconButton with AccountCircleIcon, ref anchorRef,
onClick openMenu, state menuOpen) lacks an accessible name; add an appropriate
aria-label (e.g., "Open user menu" or "User account") to the IconButton so
screen readers can identify the control, keeping existing
aria-controls/aria-expanded/aria-haspopup props intact and using a concise,
localized label matching the button's purpose.

<Menu
id="user-menu"
anchorEl={anchorRef.current}
open={menuOpen}
onClose={closeMenu}
anchorOrigin={{
vertical: "bottom",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "right"
}}
slotProps={{
paper: {
sx: {
mt: 1.5,
minWidth: 260,
borderRadius: 2,
overflow: "hidden",
boxShadow: "0 16px 40px rgba(0, 0, 0, 0.18)"
}
},
list: {
disablePadding: true,
sx: {py: 0}
}
}}
>
<Box sx={{px: 3, py: 2.5}}>
<Typography
variant="body1"
sx={{
fontSize: menuFontSize,
lineHeight: menuLineHeight,
color: theme.palette.text.primary,
mb: 0.5
}}
>
{profileName || "User"}
</Typography>
<Typography
variant="body2"
sx={{
fontSize: menuFontSize,
lineHeight: menuLineHeight,
color: theme.palette.text.secondary
}}
>
{profileEmail || ""}
</Typography>
</Box>
<Divider/>
<MenuItem onClick={closeMenu} sx={{py: 1.5, px: 3, gap: 1.5}}>
<ListItemIcon sx={{minWidth: 32, color: menuIconColor}}>
<SettingsIcon sx={{fontSize: 24}}/>
</ListItemIcon>
<ListItemText
primary={T.translate("general.settings")}
primaryTypographyProps={{
sx: {
fontSize: menuFontSize,
color: theme.palette.text.primary
}
}}
/>
</MenuItem>
<MenuItem onClick={closeMenu} sx={{py: 1.5, px: 3, gap: 1.5}}>
<ListItemIcon sx={{minWidth: 32, color: menuIconColor}}>
<PersonIcon sx={{fontSize: 24}}/>
</ListItemIcon>
<ListItemText
primary={T.translate("general.profile")}
primaryTypographyProps={{
sx: {
fontSize: menuFontSize,
color: theme.palette.text.primary
}
}}
/>
</MenuItem>
Comment on lines +128 to +155
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Settings and Profile menu items are non-functional — missing callback props.

Both MenuItem elements only call closeMenu(). There is no onSettings or onProfile (or equivalent) prop on AuthButton, so clicking either item closes the dropdown and does nothing else. These appear as functional options to the user but have no effect.

Add callback props and wire them up:

🐛 Proposed fix
 const AuthButton = ({
                      isLoggedUser,
                      doLogin,
                      initLogOut,
                      profileEmail,
-                     profileName
+                     profileName,
+                     onSettings,
+                     onProfile
                    }) => {
-          <MenuItem onClick={closeMenu} sx={{py: 1.5, px: 3, gap: 1.5}}>
+          <MenuItem onClick={() => { closeMenu(); onSettings?.(); }} sx={{py: 1.5, px: 3, gap: 1.5}}>
             ...
           </MenuItem>
-          <MenuItem onClick={closeMenu} sx={{py: 1.5, px: 3, gap: 1.5}}>
+          <MenuItem onClick={() => { closeMenu(); onProfile?.(); }} sx={{py: 1.5, px: 3, gap: 1.5}}>
🤖 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 `@src/components/mui/AuthButton/index.js` around lines 128 - 155, The Settings
and Profile MenuItem entries in AuthButton only call closeMenu and lack
callbacks, so they close the menu but perform no action; add two optional props
(e.g., onSettings and onProfile) to the AuthButton component signature and
propTypes/TS types, then wire them into the respective MenuItem onClick handlers
(call closeMenu() and then call props.onSettings() or props.onProfile() if
provided) so clicking those items triggers the supplied callbacks; update any
places that render AuthButton to pass handlers where needed and ensure default
behavior remains safe when callbacks are undefined.

<Divider/>
<MenuItem
onClick={() => {
closeMenu();
initLogOut();
}}
sx={{py: 1.5, px: 3, gap: 1.5}}
>
{T.translate("buttons.sign_out")}
</Button>
)}
</div>
);
}
return (
<Box className={styles.login}>
<Button
variant="contained"
size="small"
color="secondary"
onClick={() => {
doLogin();
}}
>
{T.translate("buttons.log_in")}
</Button>
<ListItemIcon sx={{minWidth: 32, color: menuIconColor}}>
<LogoutIcon sx={{fontSize: 24}}/>
</ListItemIcon>
<ListItemText
primary={T.translate("buttons.sign_out")}
primaryTypographyProps={{
sx: {
fontSize: menuFontSize,
color: theme.palette.text.primary
}
}}
/>
</MenuItem>
</Menu>
</Box>
);
}

return (
<Box className={styles.login}>
<Button
variant="contained"
size="small"
color="secondary"
onClick={() => {
doLogin();
}}
>
{T.translate("buttons.log_in")}
</Button>
</Box>
);
};

export default AuthButton;
25 changes: 5 additions & 20 deletions src/components/mui/AuthButton/styles.module.scss
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
.userMenu {
position: absolute;
top: 12px;
right: 34px;
height: 40px;
width: 140px;
cursor: pointer;
top: 50%;
right: 24px;
display: flex;
justify-content: flex-end;
transform: translateY(-50%);
}

.login {
width: 100%;
text-align: right;
}

.logout {
top: 4px;
right: 4px;
}

.profilePic {
height: 40px;
width: 40px;
border: 1px solid #afafaf;
border-radius: 20px;
overflow: hidden;
float: right;
background-size: cover;
}
Loading
Loading