From 9b89bf7d3d166ab30d4bbd35d940062231b1c412 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Wed, 8 Jul 2026 15:12:51 -0300 Subject: [PATCH 1/2] chore: allow Kb on filesize input and tableSx on all tables --- .../mui-formik-file-size-field.test.js | 69 +++++++++++++++++++ .../mui/editable-table/mui-table-editable.js | 5 +- .../mui-formik-file-size-field.js | 39 ++++++++--- .../mui/sortable-table/mui-table-sortable.js | 5 +- src/components/mui/table/mui-table.js | 8 ++- 5 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/components/mui/__tests__/mui-formik-file-size-field.test.js b/src/components/mui/__tests__/mui-formik-file-size-field.test.js index d6bbb884..aa44e7ea 100644 --- a/src/components/mui/__tests__/mui-formik-file-size-field.test.js +++ b/src/components/mui/__tests__/mui-formik-file-size-field.test.js @@ -212,6 +212,75 @@ describe("MuiFormikFilesizeField", () => { }); }); + describe("custom units", () => { + it("displays and stores the value as-is when valueUnit and displayUnit are both KB", async () => { + const onSubmit = jest.fn(); + renderWithFormik( + { + label: "Max File Size", + onSubmit, + valueUnit: "KB", + displayUnit: "KB" + }, + { max_file_size: 1024 } + ); + + const field = screen.getByLabelText("Max File Size"); + expect(field).toHaveValue(1024); + + await act(async () => { + await userEvent.clear(field); + await userEvent.type(field, "2048"); + await userEvent.click(screen.getByText("submit")); + }); + + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ max_file_size: 2048 }), + expect.anything() + ); + }); + + it("converts bytes to KB for display when displayUnit is KB", () => { + renderWithFormik( + { label: "Max File Size", onSubmit: jest.fn(), displayUnit: "KB" }, + { max_file_size: 2048 } // 2 * 1024 + ); + + const field = screen.getByLabelText("Max File Size"); + expect(field).toHaveValue(2); + }); + + it("converts KB input to bytes when displayUnit is KB", async () => { + const onSubmit = jest.fn(); + renderWithFormik( + { label: "Max File Size", onSubmit, displayUnit: "KB" }, + { max_file_size: 0 } + ); + + const field = screen.getByLabelText("Max File Size"); + + await act(async () => { + await userEvent.clear(field); + await userEvent.type(field, "5"); + await userEvent.click(screen.getByText("submit")); + }); + + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ max_file_size: 5 * 1024 }), + expect.anything() + ); + }); + + it("shows the displayUnit as the field's unit adornment", () => { + renderWithFormik( + { label: "Max File Size", onSubmit: jest.fn(), displayUnit: "KB" }, + { max_file_size: 0 } + ); + + expect(screen.getByText("KB")).toBeInTheDocument(); + }); + }); + describe("blocked keys", () => { it.each(["e", "E", "+", "-", ".", ","])( "blocks '%s' key from being entered", diff --git a/src/components/mui/editable-table/mui-table-editable.js b/src/components/mui/editable-table/mui-table-editable.js index 6991b7f7..2860019d 100644 --- a/src/components/mui/editable-table/mui-table-editable.js +++ b/src/components/mui/editable-table/mui-table-editable.js @@ -158,7 +158,8 @@ const MuiTableEditable = ({ onArchive, onDelete, onCellChange, // New prop for handling cell value changes - deleteDialogBody + deleteDialogBody, + tableSx = {} }) => { // State to track which cell is currently being edited const [editingCell, setEditingCell] = React.useState(null); @@ -235,7 +236,7 @@ const MuiTableEditable = ({ component={Paper} sx={{ borderRadius: 0, boxShadow: "none" }} > - +
{/* TABLE HEADER */} diff --git a/src/components/mui/formik-inputs/mui-formik-file-size-field.js b/src/components/mui/formik-inputs/mui-formik-file-size-field.js index 6fea7904..a73a7c80 100644 --- a/src/components/mui/formik-inputs/mui-formik-file-size-field.js +++ b/src/components/mui/formik-inputs/mui-formik-file-size-field.js @@ -16,38 +16,48 @@ import PropTypes from "prop-types"; import { InputAdornment } from "@mui/material"; import { useField } from "formik"; import MuiFormikTextField from "./mui-formik-textfield"; -import { BYTES_PER_MB } from "../../../utils/constants"; const BLOCKED_KEYS = ["e", "E", "+", "-", ".", ","]; -const bytesToMb = (bytes) => Math.floor(bytes / BYTES_PER_MB); +// bytes = value * 1024 ** UNIT_POWERS[unit] +const UNIT_POWERS = { B: 0, KB: 1, MB: 2 }; -const MuiFormikFilesizeField = ({ name, label, ...props }) => { +const unitToBytesFactor = (unit) => 1024 ** UNIT_POWERS[unit]; + +const MuiFormikFilesizeField = ({ + name, + label, + displayUnit, + valueUnit, + ...props +}) => { const [field, meta, helpers] = useField(name); const [cleared, setCleared] = useState(false); const emptyValue = meta.initialValue === null ? null : 0; + // value (in valueUnit) -> displayed number (in displayUnit) + const conversionFactor = + unitToBytesFactor(valueUnit) / unitToBytesFactor(displayUnit); const getDisplayValue = () => { if (cleared) return ""; if (field.value == null || field.value === 0) { return field.value === 0 ? 0 : ""; } - return bytesToMb(field.value); + return Math.floor(field.value * conversionFactor); }; const handleChange = (e) => { - const mbValue = e.target.value; + const displayValue = e.target.value; - if (mbValue === "") { + if (displayValue === "") { setCleared(true); helpers.setValue(emptyValue); return; } setCleared(false); - const bytes = Number(mbValue) * BYTES_PER_MB; - helpers.setValue(bytes); + helpers.setValue(Number(displayValue) / conversionFactor); }; const handleKeyDown = (e) => { @@ -73,7 +83,9 @@ const MuiFormikFilesizeField = ({ name, label, ...props }) => { onChange={handleChange} slotProps={{ input: { - endAdornment: MB + endAdornment: ( + {displayUnit} + ) }, htmlInput: { min: 0, @@ -90,7 +102,14 @@ const MuiFormikFilesizeField = ({ name, label, ...props }) => { MuiFormikFilesizeField.propTypes = { name: PropTypes.string.isRequired, - label: PropTypes.string.isRequired + label: PropTypes.string.isRequired, + displayUnit: PropTypes.oneOf(Object.keys(UNIT_POWERS)), + valueUnit: PropTypes.oneOf(Object.keys(UNIT_POWERS)) +}; + +MuiFormikFilesizeField.defaultProps = { + displayUnit: "MB", + valueUnit: "B" }; export default MuiFormikFilesizeField; diff --git a/src/components/mui/sortable-table/mui-table-sortable.js b/src/components/mui/sortable-table/mui-table-sortable.js index 88b8e9d1..325ef608 100644 --- a/src/components/mui/sortable-table/mui-table-sortable.js +++ b/src/components/mui/sortable-table/mui-table-sortable.js @@ -57,7 +57,8 @@ const MuiTableSortable = ({ deleteDialogBody = null, onReorder, idKey = "id", - updateOrderKey = "order" + updateOrderKey = "order", + tableSx = {} }) => { const handleChangePage = (_, newPage) => { onPageChange(newPage + 1); @@ -128,7 +129,7 @@ const MuiTableSortable = ({ component={Paper} sx={{ borderRadius: 0, boxShadow: "none" }} > -
+
{/* TABLE HEADER */} diff --git a/src/components/mui/table/mui-table.js b/src/components/mui/table/mui-table.js index c17c07c4..8965adef 100644 --- a/src/components/mui/table/mui-table.js +++ b/src/components/mui/table/mui-table.js @@ -71,7 +71,8 @@ const MuiTable = ({ deleteDialogTitle = null, deleteDialogBody = null, deleteDialogConfirmText = null, - confirmButtonColor = null + confirmButtonColor = null, + tableSx = {} }) => { const totalColumnsCount = columns.length + (onEdit ? 1 : 0) + (onDelete ? 1 : 0) + (onArchive ? 1 : 0) + (onSelect ? 1 : 0); @@ -157,7 +158,7 @@ const MuiTable = ({ component={Paper} sx={{ borderRadius: 0, boxShadow: "none" }} > -
+
{/* TABLE HEADER */} @@ -372,7 +373,8 @@ MuiTable.propTypes = { deleteDialogTitle: PropTypes.string, deleteDialogBody: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), deleteDialogConfirmText: PropTypes.string, - confirmButtonColor: PropTypes.string + confirmButtonColor: PropTypes.string, + tableSx: PropTypes.object }; export default MuiTable; From ec4861dbe2abe83108abf06a3dd8c7621dcca399 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Wed, 8 Jul 2026 15:14:41 -0300 Subject: [PATCH 2/2] v5.0.41-beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 729cb410..6722bed7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openstack-uicore-foundation", - "version": "5.0.38", + "version": "5.0.41-beta.0", "description": "ui reactjs components for openstack marketing site", "main": "lib/openstack-uicore-foundation.js", "scripts": {