Skip to content

Commit bf75645

Browse files
Feature: improve file size input and tables (#286)
* chore: allow Kb on filesize input and tableSx on all tables * v5.0.41-beta.0
1 parent 72d7f58 commit bf75645

6 files changed

Lines changed: 110 additions & 18 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openstack-uicore-foundation",
3-
"version": "5.0.38",
3+
"version": "5.0.41-beta.0",
44
"description": "ui reactjs components for openstack marketing site",
55
"main": "lib/openstack-uicore-foundation.js",
66
"scripts": {

src/components/mui/__tests__/mui-formik-file-size-field.test.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,75 @@ describe("MuiFormikFilesizeField", () => {
212212
});
213213
});
214214

215+
describe("custom units", () => {
216+
it("displays and stores the value as-is when valueUnit and displayUnit are both KB", async () => {
217+
const onSubmit = jest.fn();
218+
renderWithFormik(
219+
{
220+
label: "Max File Size",
221+
onSubmit,
222+
valueUnit: "KB",
223+
displayUnit: "KB"
224+
},
225+
{ max_file_size: 1024 }
226+
);
227+
228+
const field = screen.getByLabelText("Max File Size");
229+
expect(field).toHaveValue(1024);
230+
231+
await act(async () => {
232+
await userEvent.clear(field);
233+
await userEvent.type(field, "2048");
234+
await userEvent.click(screen.getByText("submit"));
235+
});
236+
237+
expect(onSubmit).toHaveBeenCalledWith(
238+
expect.objectContaining({ max_file_size: 2048 }),
239+
expect.anything()
240+
);
241+
});
242+
243+
it("converts bytes to KB for display when displayUnit is KB", () => {
244+
renderWithFormik(
245+
{ label: "Max File Size", onSubmit: jest.fn(), displayUnit: "KB" },
246+
{ max_file_size: 2048 } // 2 * 1024
247+
);
248+
249+
const field = screen.getByLabelText("Max File Size");
250+
expect(field).toHaveValue(2);
251+
});
252+
253+
it("converts KB input to bytes when displayUnit is KB", async () => {
254+
const onSubmit = jest.fn();
255+
renderWithFormik(
256+
{ label: "Max File Size", onSubmit, displayUnit: "KB" },
257+
{ max_file_size: 0 }
258+
);
259+
260+
const field = screen.getByLabelText("Max File Size");
261+
262+
await act(async () => {
263+
await userEvent.clear(field);
264+
await userEvent.type(field, "5");
265+
await userEvent.click(screen.getByText("submit"));
266+
});
267+
268+
expect(onSubmit).toHaveBeenCalledWith(
269+
expect.objectContaining({ max_file_size: 5 * 1024 }),
270+
expect.anything()
271+
);
272+
});
273+
274+
it("shows the displayUnit as the field's unit adornment", () => {
275+
renderWithFormik(
276+
{ label: "Max File Size", onSubmit: jest.fn(), displayUnit: "KB" },
277+
{ max_file_size: 0 }
278+
);
279+
280+
expect(screen.getByText("KB")).toBeInTheDocument();
281+
});
282+
});
283+
215284
describe("blocked keys", () => {
216285
it.each(["e", "E", "+", "-", ".", ","])(
217286
"blocks '%s' key from being entered",

src/components/mui/editable-table/mui-table-editable.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ const MuiTableEditable = ({
158158
onArchive,
159159
onDelete,
160160
onCellChange, // New prop for handling cell value changes
161-
deleteDialogBody
161+
deleteDialogBody,
162+
tableSx = {}
162163
}) => {
163164
// State to track which cell is currently being edited
164165
const [editingCell, setEditingCell] = React.useState(null);
@@ -235,7 +236,7 @@ const MuiTableEditable = ({
235236
component={Paper}
236237
sx={{ borderRadius: 0, boxShadow: "none" }}
237238
>
238-
<Table>
239+
<Table sx={tableSx}>
239240
{/* TABLE HEADER */}
240241
<TableHead sx={{ backgroundColor: "#EAEAEA" }}>
241242
<TableRow>

src/components/mui/formik-inputs/mui-formik-file-size-field.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,48 @@ import PropTypes from "prop-types";
1616
import { InputAdornment } from "@mui/material";
1717
import { useField } from "formik";
1818
import MuiFormikTextField from "./mui-formik-textfield";
19-
import { BYTES_PER_MB } from "../../../utils/constants";
2019

2120
const BLOCKED_KEYS = ["e", "E", "+", "-", ".", ","];
2221

23-
const bytesToMb = (bytes) => Math.floor(bytes / BYTES_PER_MB);
22+
// bytes = value * 1024 ** UNIT_POWERS[unit]
23+
const UNIT_POWERS = { B: 0, KB: 1, MB: 2 };
2424

25-
const MuiFormikFilesizeField = ({ name, label, ...props }) => {
25+
const unitToBytesFactor = (unit) => 1024 ** UNIT_POWERS[unit];
26+
27+
const MuiFormikFilesizeField = ({
28+
name,
29+
label,
30+
displayUnit,
31+
valueUnit,
32+
...props
33+
}) => {
2634
const [field, meta, helpers] = useField(name);
2735
const [cleared, setCleared] = useState(false);
2836

2937
const emptyValue = meta.initialValue === null ? null : 0;
38+
// value (in valueUnit) -> displayed number (in displayUnit)
39+
const conversionFactor =
40+
unitToBytesFactor(valueUnit) / unitToBytesFactor(displayUnit);
3041

3142
const getDisplayValue = () => {
3243
if (cleared) return "";
3344
if (field.value == null || field.value === 0) {
3445
return field.value === 0 ? 0 : "";
3546
}
36-
return bytesToMb(field.value);
47+
return Math.floor(field.value * conversionFactor);
3748
};
3849

3950
const handleChange = (e) => {
40-
const mbValue = e.target.value;
51+
const displayValue = e.target.value;
4152

42-
if (mbValue === "") {
53+
if (displayValue === "") {
4354
setCleared(true);
4455
helpers.setValue(emptyValue);
4556
return;
4657
}
4758

4859
setCleared(false);
49-
const bytes = Number(mbValue) * BYTES_PER_MB;
50-
helpers.setValue(bytes);
60+
helpers.setValue(Number(displayValue) / conversionFactor);
5161
};
5262

5363
const handleKeyDown = (e) => {
@@ -73,7 +83,9 @@ const MuiFormikFilesizeField = ({ name, label, ...props }) => {
7383
onChange={handleChange}
7484
slotProps={{
7585
input: {
76-
endAdornment: <InputAdornment position="end">MB</InputAdornment>
86+
endAdornment: (
87+
<InputAdornment position="end">{displayUnit}</InputAdornment>
88+
)
7789
},
7890
htmlInput: {
7991
min: 0,
@@ -90,7 +102,14 @@ const MuiFormikFilesizeField = ({ name, label, ...props }) => {
90102

91103
MuiFormikFilesizeField.propTypes = {
92104
name: PropTypes.string.isRequired,
93-
label: PropTypes.string.isRequired
105+
label: PropTypes.string.isRequired,
106+
displayUnit: PropTypes.oneOf(Object.keys(UNIT_POWERS)),
107+
valueUnit: PropTypes.oneOf(Object.keys(UNIT_POWERS))
108+
};
109+
110+
MuiFormikFilesizeField.defaultProps = {
111+
displayUnit: "MB",
112+
valueUnit: "B"
94113
};
95114

96115
export default MuiFormikFilesizeField;

src/components/mui/sortable-table/mui-table-sortable.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ const MuiTableSortable = ({
5757
deleteDialogBody = null,
5858
onReorder,
5959
idKey = "id",
60-
updateOrderKey = "order"
60+
updateOrderKey = "order",
61+
tableSx = {}
6162
}) => {
6263
const handleChangePage = (_, newPage) => {
6364
onPageChange(newPage + 1);
@@ -128,7 +129,7 @@ const MuiTableSortable = ({
128129
component={Paper}
129130
sx={{ borderRadius: 0, boxShadow: "none" }}
130131
>
131-
<Table>
132+
<Table sx={tableSx}>
132133
{/* TABLE HEADER */}
133134
<TableHead sx={{ backgroundColor: "#EAEAEA" }}>
134135
<TableRow>

src/components/mui/table/mui-table.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ const MuiTable = ({
7171
deleteDialogTitle = null,
7272
deleteDialogBody = null,
7373
deleteDialogConfirmText = null,
74-
confirmButtonColor = null
74+
confirmButtonColor = null,
75+
tableSx = {}
7576
}) => {
7677
const totalColumnsCount =
7778
columns.length + (onEdit ? 1 : 0) + (onDelete ? 1 : 0) + (onArchive ? 1 : 0) + (onSelect ? 1 : 0);
@@ -157,7 +158,7 @@ const MuiTable = ({
157158
component={Paper}
158159
sx={{ borderRadius: 0, boxShadow: "none" }}
159160
>
160-
<Table sx={{ tableLayout: "fixed" }}>
161+
<Table sx={{ tableLayout: "fixed", ...tableSx }}>
161162
{/* TABLE HEADER */}
162163
<TableHead sx={{ backgroundColor: "#EAEDF4" }}>
163164
<TableRow>
@@ -372,7 +373,8 @@ MuiTable.propTypes = {
372373
deleteDialogTitle: PropTypes.string,
373374
deleteDialogBody: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
374375
deleteDialogConfirmText: PropTypes.string,
375-
confirmButtonColor: PropTypes.string
376+
confirmButtonColor: PropTypes.string,
377+
tableSx: PropTypes.object
376378
};
377379

378380
export default MuiTable;

0 commit comments

Comments
 (0)