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": {
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;