@balena/ui-shared-components 12.3.2-build-fix-non-unified-os-release-variant-selection-da43c55fce8efff9448ca4ad4807613647f1195f-1 → 12.3.3-build-primary-column-highlight-af8b5c7c8d49b0bb8367062650a4b12e8cec1d2c-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,7 @@ interface ImageFormProps {
14
14
  model: DownloadImageFormModel;
15
15
  hasEsrVersions?: boolean;
16
16
  onSelectedOsTypeChange: (osType: string) => void;
17
- onChange: (obj: Partial<DownloadImageFormModel>) => void;
17
+ onChange: (key: keyof DownloadImageFormModel, value: DownloadImageFormModel[keyof DownloadImageFormModel]) => void;
18
18
  }
19
19
  export declare const ImageForm: import("react").NamedExoticComponent<ImageFormProps>;
20
20
  export {};
@@ -1,19 +1,17 @@
1
1
  import { __rest } from "tslib";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Avatar, Box, Button, Checkbox, Chip, Collapse, Divider, FormControl, FormControlLabel, FormLabel, InputAdornment, InputLabel, Radio, RadioGroup, TextField, Tooltip, Typography, IconButton, Autocomplete, Stack, } from '@mui/material';
3
+ import { Avatar, Box, Checkbox, Chip, Divider, FormControl, FormControlLabel, FormLabel, InputAdornment, Radio, RadioGroup, TextField, Tooltip, Typography, IconButton, Autocomplete, Stack, Accordion, AccordionSummary, AccordionDetails, accordionSummaryClasses, } from '@mui/material';
4
4
  import HelpIcon from '@mui/icons-material/Help';
5
5
  import { memo, useCallback, useEffect, useMemo, useState } from 'react';
6
6
  import { getPreferredVersionOpts, transformVersions } from './version';
7
7
  import { OsTypeSelector } from './OsTypeSelector';
8
8
  import { VariantSelector } from './VariantSelector';
9
- import AddIcon from '@mui/icons-material/Add';
10
- import RemoveIcon from '@mui/icons-material/Remove';
11
9
  import ArticleIcon from '@mui/icons-material/Article';
12
10
  import { MUILinkWithTracking } from '../MUILinkWithTracking';
13
11
  import { Visibility, VisibilityOff } from '@mui/icons-material';
14
12
  import { FALLBACK_LOGO_UNKNOWN_DEVICE } from './utils';
15
13
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
16
- import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
14
+ import { faChevronRight, faTriangleExclamation, } from '@fortawesome/free-solid-svg-icons';
17
15
  import { Callout } from '../Callout';
18
16
  import { token } from '../../utils/token';
19
17
  const POLL_INTERVAL_DOCS = 'https://www.balena.io/docs/reference/supervisor/bandwidth-reduction/#side-effects--warnings';
@@ -46,37 +44,25 @@ export const ImageForm = memo(function ImageForm({ compatibleDeviceTypes, osVers
46
44
  const { selectionOpts, preferredSelectionOpts } = useMemo(() => getCategorizedVersions(osVersions, model.deviceType, osType), [osVersions, model.deviceType, osType]);
47
45
  const versionSelectionOpts = useMemo(() => (showAllVersions ? selectionOpts : preferredSelectionOpts), [preferredSelectionOpts, selectionOpts, showAllVersions]);
48
46
  const showAllVersionsToggle = useMemo(() => preferredSelectionOpts.length < selectionOpts.length, [preferredSelectionOpts.length, selectionOpts.length]);
49
- const handleVariantChange = useCallback((newVariant) => {
50
- setVariant(newVariant);
51
- const newState = {
52
- developmentMode: newVariant === 'dev',
53
- };
54
- if (version === null || version === void 0 ? void 0 : version.hasPrebuiltVariants) {
55
- const rawVersionForVariant = version.rawVersions[newVariant];
56
- if (rawVersionForVariant != null) {
57
- newState.version = rawVersionForVariant;
58
- }
59
- }
60
- onChange(newState);
61
- }, [onChange, version]);
47
+ const handleVariantChange = useCallback((v) => {
48
+ setVariant(v);
49
+ onChange('developmentMode', v === 'dev');
50
+ }, [onChange]);
62
51
  const handleVersionChange = useCallback((ver) => {
63
52
  var _a;
64
53
  ver !== null && ver !== void 0 ? ver : (ver = (_a = versionSelectionOpts.find((v) => v.isRecommended)) !== null && _a !== void 0 ? _a : versionSelectionOpts[0]);
65
- const newState = {
66
- developmentMode: variant === 'dev',
67
- };
68
54
  if (ver === null || ver === void 0 ? void 0 : ver.hasPrebuiltVariants) {
69
55
  const rawVersionForVariant = ver.rawVersions[variant];
70
- if (!rawVersionForVariant) {
56
+ if (rawVersionForVariant) {
57
+ onChange('version', rawVersionForVariant);
58
+ setVersion(ver);
59
+ }
60
+ else {
71
61
  handleVariantChange(variant === 'dev' ? 'prod' : 'dev');
72
- return;
73
62
  }
74
- newState.version = rawVersionForVariant;
75
- }
76
- else {
77
- newState.version = ver === null || ver === void 0 ? void 0 : ver.rawVersion;
63
+ return;
78
64
  }
79
- onChange(newState);
65
+ onChange('version', ver === null || ver === void 0 ? void 0 : ver.rawVersion);
80
66
  setVersion(ver);
81
67
  }, [versionSelectionOpts, variant, onChange, handleVariantChange]);
82
68
  // TODO: Revisit this as it is clearly not using hooks as intended
@@ -108,49 +94,50 @@ export const ImageForm = memo(function ImageForm({ compatibleDeviceTypes, osVers
108
94
  if (!newDeviceType) {
109
95
  return;
110
96
  }
111
- onChange({ deviceType: newDeviceType });
97
+ onChange('deviceType', newDeviceType);
112
98
  }, [compatibleDeviceTypes, model.deviceType.slug, onChange]);
113
99
  const recommendedVersion = useMemo(() => { var _a; return (_a = versionSelectionOpts.find((v) => { var _a; return !((_a = v.knownIssueList) === null || _a === void 0 ? void 0 : _a.length); })) === null || _a === void 0 ? void 0 : _a.value; }, [versionSelectionOpts]);
114
- return (_jsxs(Box, { action: downloadUrl, method: "post", component: "form", noValidate: true, autoComplete: "off", p: 2, ref: formElement, children: [_jsx("input", { type: "hidden", name: "deviceType", value: model.deviceType.slug }), _jsx("input", { type: "hidden", name: "_token", value: authToken }), _jsx("input", { type: "hidden", name: "appId", value: applicationId }), _jsx("input", { type: "hidden", name: "fileType", value: ".zip" }), _jsx("input", { type: "hidden", name: "version", value: model.version }), _jsxs(Box, { py: 3, display: "flex", flexWrap: "wrap", gap: 2, children: [compatibleDeviceTypes && compatibleDeviceTypes.length > 1 && (_jsxs(Box, { display: "flex", flexDirection: "column", flex: "1", maxWidth: "100%", children: [_jsxs(InputLabel, { htmlFor: "device-type-select", sx: { display: 'flex', alignItems: 'center', mb: 2 }, children: ["Select device type", ' ', _jsx(Tooltip, { title: "Applications can support any devices that share the same architecture as their default device type.", children: _jsx(HelpIcon, { color: "info", sx: { fontSize: '1rem', marginLeft: 1 } }) })] }), _jsx(Autocomplete, { fullWidth: true, id: "device-type-select", value: model.deviceType, options: compatibleDeviceTypes, getOptionLabel: (option) => option.name, renderOption: (props, option) => {
115
- var _a;
116
- return (_jsxs(Box, Object.assign({ component: "li" }, props, { children: [_jsx(Avatar, { variant: "square", src: (_a = option.logo) !== null && _a !== void 0 ? _a : FALLBACK_LOGO_UNKNOWN_DEVICE, sx: { mr: 3, width: '20px', height: '20px' } }), _jsx(Typography, { noWrap: true, children: option.name })] })));
117
- }, renderInput: (_a) => {
118
- var _b;
119
- var { InputProps } = _a, params = __rest(_a, ["InputProps"]);
120
- return (_jsx(TextField, Object.assign({}, params, { InputProps: Object.assign(Object.assign({}, InputProps), { startAdornment: (_jsx(Avatar, { variant: "square", src: (_b = model.deviceType.logo) !== null && _b !== void 0 ? _b : FALLBACK_LOGO_UNKNOWN_DEVICE, sx: { mr: 3, width: '20px', height: '20px' } })) }) })));
121
- }, onChange: (_event, value) => {
122
- if (!value) {
123
- return;
124
- }
125
- handleSelectedDeviceTypeChange(value);
126
- }, disableClearable: true,
127
- // TODO: consider whether there is a better solution than letting the width vary as you search
128
- componentsProps: {
129
- popper: { sx: { width: 'fit-content' } },
130
- } })] })), (!isInitialDefault || osType) &&
100
+ return (_jsxs(Stack, { action: downloadUrl, method: "post", component: "form", noValidate: true, autoComplete: "off", ref: formElement, gap: 3, children: [_jsx("input", { type: "hidden", name: "deviceType", value: model.deviceType.slug }), _jsx("input", { type: "hidden", name: "_token", value: authToken }), _jsx("input", { type: "hidden", name: "appId", value: applicationId }), _jsx("input", { type: "hidden", name: "fileType", value: ".zip" }), _jsx("input", { type: "hidden", name: "version", value: model.version }), _jsxs(Stack, { direction: "row", flexWrap: "wrap", gap: 2, children: [compatibleDeviceTypes && compatibleDeviceTypes.length > 1 && (_jsx(Autocomplete, { fullWidth: true, value: model.deviceType, options: compatibleDeviceTypes, getOptionLabel: (option) => option.name, renderOption: (props, option) => {
101
+ var _a;
102
+ return (_jsxs(Box, Object.assign({ component: "li" }, props, { children: [_jsx(Avatar, { variant: "square", src: (_a = option.logo) !== null && _a !== void 0 ? _a : FALLBACK_LOGO_UNKNOWN_DEVICE, sx: { mr: 3, width: '20px', height: '20px' } }), _jsx(Typography, { noWrap: true, children: option.name })] })));
103
+ }, renderInput: (_a) => {
104
+ var _b;
105
+ var { InputProps } = _a, params = __rest(_a, ["InputProps"]);
106
+ return (_jsx(TextField, Object.assign({}, params, { label: _jsxs(Stack, { direction: "row", alignItems: "center", gap: 1, children: ["Device type", _jsx(Tooltip, { title: "Applications can support any devices that share the same architecture as their default device type.", children: _jsx(HelpIcon, { color: "info", sx: { fontSize: '1rem' } }) })] }), slotProps: {
107
+ input: Object.assign(Object.assign({}, InputProps), { startAdornment: (_jsx(Avatar, { variant: "square", src: (_b = model.deviceType.logo) !== null && _b !== void 0 ? _b : FALLBACK_LOGO_UNKNOWN_DEVICE, sx: { mr: 3, width: '20px', height: '20px' } })) }),
108
+ } })));
109
+ }, onChange: (_event, value) => {
110
+ if (!value) {
111
+ return;
112
+ }
113
+ handleSelectedDeviceTypeChange(value);
114
+ }, disableClearable: true,
115
+ // TODO: consider whether there is a better solution than letting the width vary as you search
116
+ slotProps: {
117
+ popper: { sx: { width: 'fit-content' } },
118
+ }, sx: { flex: 1 } })), (!isInitialDefault || osType) &&
131
119
  hasEsrVersions &&
132
- model.deviceType && (_jsx(OsTypeSelector, { supportedOsTypes: osTypes, hasEsrVersions: hasEsrVersions !== null && hasEsrVersions !== void 0 ? hasEsrVersions : false, selectedOsTypeSlug: osType, onSelectedOsTypeChange: onSelectedOsTypeChange }))] }), !isInitialDefault && version && (_jsxs(Box, { display: "flex", flexWrap: "wrap", maxWidth: "100%", children: [_jsxs(Box, { display: "flex", flexDirection: "column", maxWidth: "100%", flex: 1, children: [_jsx(InputLabel, { sx: { mb: 2 }, htmlFor: "e2e-download-image-versions-list", children: "Select version" }), _jsx(Autocomplete, { fullWidth: true, id: "e2e-download-image-versions-list", value: version, getOptionLabel: (option) => option.value, isOptionEqualToValue: (option, value) => option.value === value.value, options: versionSelectionOpts, onChange: (_event, ver) => {
133
- handleVersionChange(ver);
134
- }, placeholder: "Choose a version...", renderOption: (props, option) => (_jsx(Box, Object.assign({ component: "li" }, props, { children: _jsx(VersionSelectItem, { option: option, isRecommended: option.value === recommendedVersion }) }))), renderInput: (_a) => {
135
- var { InputProps } = _a, params = __rest(_a, ["InputProps"]);
136
- return (_jsx(TextField, Object.assign({}, params, { InputProps: Object.assign(Object.assign({}, InputProps), { endAdornment: (_jsxs(_Fragment, { children: [version.value === recommendedVersion && (_jsx(Chip, { sx: { ml: 1 }, color: "green", label: "recommended" })), !!(version === null || version === void 0 ? void 0 : version.knownIssueList) && (_jsx(Tooltip, { title: version.knownIssueList, children: _jsx(FontAwesomeIcon, { icon: faTriangleExclamation, color: token('color.icon.warning') }) })), InputProps.endAdornment] })) }) })));
137
- }, disableClearable: true })] }), showAllVersionsToggle && (_jsx(Box, { mx: 2, display: "flex", alignItems: "center", alignSelf: "flex-end",
138
- // TODO: find a better way to center the checkbox with the input only (without label)
139
- height: 54, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { id: "e2e-show-all-versions-check", checked: showAllVersions, onChange: handleShowAllVersions }), label: "Show outdated versions" }) }))] })), (!isInitialDefault || !variant) && (_jsx(Box, { sx: { mt: 3 }, children: _jsx(VariantSelector, { version: version, variant: variant, onVariantChange: (v) => {
140
- handleVariantChange(v ? 'dev' : 'prod');
141
- } }) })), _jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), _jsxs(Box, { display: "flex", flexDirection: "column", children: [_jsxs(FormControl, { children: [_jsx(FormLabel, { id: "network-radio-buttons-group-label", children: "Network" }), _jsxs(RadioGroup, { "aria-labelledby": "network-radio-buttons-group-label", value: model.network, name: "network", onChange: (event) => {
142
- onChange({
143
- network: event.target
144
- .value,
145
- });
146
- }, children: [_jsx(FormControlLabel, { value: "ethernet", control: _jsx(Radio, {}), label: "Ethernet only" }), _jsx(FormControlLabel, { value: "wifi", control: _jsx(Radio, {}), label: "Wifi + Ethernet" })] })] }), model.network === 'wifi' && (_jsxs(_Fragment, { children: [_jsx(InputLabel, { htmlFor: "device-wifi-ssid", sx: { mb: 2 }, children: "WiFi SSID" }), _jsx(TextField, { value: model.wifiSsid, id: "device-wifi-ssid", slotProps: {
120
+ model.deviceType && (_jsx(OsTypeSelector, { supportedOsTypes: osTypes, hasEsrVersions: hasEsrVersions !== null && hasEsrVersions !== void 0 ? hasEsrVersions : false, selectedOsTypeSlug: osType, onSelectedOsTypeChange: onSelectedOsTypeChange }))] }), !isInitialDefault && version && (_jsxs(Stack, { direction: "row", flexWrap: "wrap", maxWidth: "100%", gap: 2, alignItems: "center", children: [_jsx(Autocomplete, { fullWidth: true, id: "e2e-download-image-versions-list", value: version, getOptionLabel: (option) => option.value, isOptionEqualToValue: (option, value) => option.value === value.value, options: versionSelectionOpts, onChange: (_event, ver) => {
121
+ handleVersionChange(ver);
122
+ }, placeholder: "Choose a version...", renderOption: (props, option) => (_jsx(Box, Object.assign({ component: "li" }, props, { children: _jsx(VersionSelectItem, { option: option, isRecommended: option.value === recommendedVersion }) }))), renderInput: (_a) => {
123
+ var { InputProps } = _a, params = __rest(_a, ["InputProps"]);
124
+ return (_jsx(TextField, Object.assign({}, params, { slotProps: {
125
+ input: Object.assign(Object.assign({}, InputProps), { endAdornment: (_jsxs(_Fragment, { children: [version.value === recommendedVersion && (_jsx(Chip, { sx: { ml: 1 }, color: "green", label: "recommended" })), !!(version === null || version === void 0 ? void 0 : version.knownIssueList) && (_jsx(Tooltip, { title: version.knownIssueList, children: _jsx(FontAwesomeIcon, { icon: faTriangleExclamation, color: token('color.icon.warning') }) })), InputProps.endAdornment] })) }),
126
+ }, label: "OS version" })));
127
+ }, disableClearable: true, sx: { flex: 1 } }), showAllVersionsToggle && (_jsx(FormControlLabel, { control: _jsx(Checkbox, { id: "e2e-show-all-versions-check", checked: showAllVersions, onChange: handleShowAllVersions }), label: "Show outdated versions",
128
+ // TODO: Find a better way to center the checkbox with the input only (without the label)
129
+ sx: { mt: 3 } }))] })), _jsx(Divider, { variant: "fullWidth", flexItem: true, sx: { borderStyle: 'dashed' } }), (!isInitialDefault || !variant) && (_jsx(VariantSelector, { version: version, variant: variant, onVariantChange: (v) => {
130
+ handleVariantChange(v ? 'dev' : 'prod');
131
+ } })), _jsx(Divider, { variant: "fullWidth", flexItem: true, sx: { borderStyle: 'dashed' } }), _jsxs(Stack, { children: [_jsxs(FormControl, { children: [_jsx(FormLabel, { id: "network-radio-buttons-group-label", children: _jsx(Typography, { variant: "titleSm", children: "Network" }) }), _jsxs(RadioGroup, { "aria-labelledby": "network-radio-buttons-group-label", value: model.network, name: "network", onChange: (event) => {
132
+ onChange('network', event.target.value);
133
+ }, children: [_jsx(FormControlLabel, { value: "ethernet", control: _jsx(Radio, {}), label: "Ethernet only" }), _jsx(FormControlLabel, { value: "wifi", control: _jsx(Radio, {}), label: "Wifi + Ethernet" })] })] }), model.network === 'wifi' && (_jsxs(Stack, { gap: 3, children: [_jsx(TextField, { value: model.wifiSsid, id: "device-wifi-ssid", slotProps: {
147
134
  htmlInput: {
148
135
  name: 'wifiSsid',
149
136
  autoComplete: 'wifiSsid-auto-complete',
150
137
  },
151
138
  }, onChange: (event) => {
152
- onChange({ wifiSsid: event.target.value });
153
- } }), _jsx(InputLabel, { htmlFor: "device-wifi-password", sx: { my: 2 }, children: "Wifi Passphrase" }), _jsx(TextField, { type: showPassword ? 'text' : 'password', id: "device-wifi-password", value: model.wifiKey, slotProps: {
139
+ onChange('wifiSsid', event.target.value);
140
+ }, label: "WiFi SSID" }), _jsx(TextField, { type: showPassword ? 'text' : 'password', id: "device-wifi-password", value: model.wifiKey, slotProps: {
154
141
  htmlInput: {
155
142
  name: 'wifiKey',
156
143
  },
@@ -163,37 +150,43 @@ export const ImageForm = memo(function ImageForm({ compatibleDeviceTypes, osVers
163
150
  }, edge: "end", children: showPassword ? _jsx(VisibilityOff, {}) : _jsx(Visibility, {}) }) })),
164
151
  },
165
152
  }, onChange: (event) => {
166
- onChange({ wifiKey: event.target.value });
167
- } })] }))] }), _jsx(Divider, { variant: "fullWidth", sx: { my: 3, borderStyle: 'dashed' } }), _jsxs(Button, { onClick: () => {
153
+ onChange('wifiKey', event.target.value);
154
+ }, label: "Wifi Passphrase" })] }))] }), _jsx(Divider, { variant: "fullWidth", sx: { borderStyle: 'dashed' } }), _jsxs(Accordion, { disableGutters: true, elevation: 0, expanded: showAdvancedSettings, onChange: () => {
168
155
  setShowAdvancedSettings(!showAdvancedSettings);
169
- }, variant: "outlined", sx: { mb: 2 }, children: [showAdvancedSettings ? _jsx(RemoveIcon, {}) : _jsx(AddIcon, {}), " Advanced settings"] }), _jsx(Collapse, { in: showAdvancedSettings, collapsedSize: 0, children: _jsxs(Box, { display: "flex", flexDirection: "column", children: [_jsxs(FormControl, { children: [_jsxs(FormLabel, { htmlFor: "poll-interval-label", sx: { display: 'flex' }, children: ["Check for updates every X minutes", ' ', _jsx(MUILinkWithTracking, { href: POLL_INTERVAL_DOCS, sx: {
156
+ }, sx: {
157
+ border: 'none',
158
+ '&::before': {
159
+ display: 'none',
160
+ },
161
+ [`& .${accordionSummaryClasses.expandIconWrapper}.${accordionSummaryClasses.expanded}`]: {
162
+ transform: 'rotate(90deg)',
163
+ },
164
+ }, children: [_jsx(AccordionSummary, { expandIcon: _jsx(FontAwesomeIcon, { icon: faChevronRight }), sx: { flexDirection: 'row-reverse', gap: 2 }, children: _jsx(Typography, { variant: "titleSm", children: "Advanced settings" }) }), _jsxs(AccordionDetails, { sx: { display: 'flex', flexDirection: 'column', gap: 3 }, children: [_jsx(TextField, { value: model.appUpdatePollInterval, slotProps: {
165
+ htmlInput: {
166
+ name: 'appUpdatePollInterval',
167
+ autoComplete: 'appUpdatePollInterval-auto-complete',
168
+ },
169
+ }, onChange: (event) => {
170
+ onChange('appUpdatePollInterval', event.target.value);
171
+ }, label: _jsxs(Stack, { direction: "row", alignItems: "center", gap: 1, children: ["Check for updates every X minutes", ' ', _jsx(MUILinkWithTracking, { href: POLL_INTERVAL_DOCS, sx: {
170
172
  display: 'flex',
171
173
  alignItems: 'center',
172
174
  height: '1.5rem',
173
- }, children: _jsx(ArticleIcon, { sx: { ml: 1, fontSize: '1.15rem' } }) })] }), _jsx(TextField, { id: "poll-interval-label", "aria-labelledby": "poll-interval-label", value: model.appUpdatePollInterval, slotProps: {
174
- htmlInput: {
175
- name: 'appUpdatePollInterval',
176
- autoComplete: 'appUpdatePollInterval-auto-complete',
177
- },
178
- }, onChange: (event) => {
179
- onChange({
180
- appUpdatePollInterval: parseInt(event.target.value, 10),
181
- });
182
- } })] }), _jsx(InputLabel, { htmlFor: "provision-key-name", sx: { my: 2 }, children: "Provisioning Key name" }), _jsx(TextField, { name: "provisioningKeyName", id: "provision-key-name", value: (_a = model.provisioningKeyName) !== null && _a !== void 0 ? _a : '', slotProps: {
183
- htmlInput: {
184
- name: 'provisioningKeyName',
185
- autoComplete: 'provisioningKeyName-auto-complete',
186
- },
187
- }, onChange: (event) => {
188
- onChange({ provisioningKeyName: event.target.value });
189
- } }), _jsx(InputLabel, { htmlFor: "provision-key-expiring", sx: { my: 2 }, children: "Provisioning Key expiring on" }), _jsx(TextField, { type: "date", id: "provision-key-expiring", value: (_b = model.provisioningKeyExpiryDate) !== null && _b !== void 0 ? _b : '', slotProps: {
190
- htmlInput: {
191
- name: 'provisioningKeyExpiryDate',
192
- autoComplete: 'provisioningKeyExpiryDate-auto-complete',
193
- },
194
- }, onChange: (event) => {
195
- onChange({ provisioningKeyExpiryDate: event.target.value });
196
- } })] }) })] }));
175
+ }, children: _jsx(ArticleIcon, { sx: { ml: 1, fontSize: '1.15rem' } }) })] }) }), _jsx(TextField, { name: "provisioningKeyName", value: (_a = model.provisioningKeyName) !== null && _a !== void 0 ? _a : '', slotProps: {
176
+ htmlInput: {
177
+ name: 'provisioningKeyName',
178
+ autoComplete: 'provisioningKeyName-auto-complete',
179
+ },
180
+ }, onChange: (event) => {
181
+ onChange('provisioningKeyName', event.target.value);
182
+ }, label: "Provisioning Key name" }), _jsx(TextField, { type: "date", value: (_b = model.provisioningKeyExpiryDate) !== null && _b !== void 0 ? _b : '', slotProps: {
183
+ htmlInput: {
184
+ name: 'provisioningKeyExpiryDate',
185
+ autoComplete: 'provisioningKeyExpiryDate-auto-complete',
186
+ },
187
+ }, onChange: (event) => {
188
+ onChange('provisioningKeyExpiryDate', event.target.value);
189
+ }, label: "Provisioning Key expiring on" })] })] })] }));
197
190
  });
198
191
  // TODO: We need a better way than just copying the styling. Consider creating a component to export
199
192
  const VersionSelectItem = ({ option, isRecommended, }) => {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, InputLabel, MenuItem, Select, Tooltip, Typography, } from '@mui/material';
2
+ import { Box, InputLabel, MenuItem, Select, Tooltip, Typography, Stack, } from '@mui/material';
3
3
  import { getOsTypeName } from './utils';
4
4
  import ArticleIcon from '@mui/icons-material/Article';
5
5
  import { MUILinkWithTracking } from '../MUILinkWithTracking';
@@ -30,12 +30,12 @@ export const OsTypeSelector = ({ supportedOsTypes, hasEsrVersions, selectedOsTyp
30
30
  };
31
31
  });
32
32
  const selectedOsType = selectOsTypes.find((osType) => osType.slug === selectedOsTypeSlug && osType.supportedForDeviceType);
33
- return (_jsxs(Box, { display: "flex", flexDirection: "column", flex: 1, children: [_jsxs(InputLabel, { htmlFor: "newAppApplicationType", sx: { display: 'flex', alignItems: 'center', mb: 2 }, children: ["Select OS type", ' ', _jsx(MUILinkWithTracking, { sx: { height: 14 }, href: "https://www.balena.io/docs/reference/OS/extended-support-release", children: _jsx(ArticleIcon, { sx: { width: 14, height: 14, marginLeft: 1 } }) })] }), _jsx(Select, { id: "newAppApplicationType", fullWidth: true, disabled: supportedOsTypes.length === 0, value: (_a = selectedOsType === null || selectedOsType === void 0 ? void 0 : selectedOsType.slug) !== null && _a !== void 0 ? _a : OsTypesEnum.DEFAULT, renderValue: (selected) => (_jsx(Box, { display: "flex", width: "100%", children: _jsx(OsTypeOption, { osType: selectOsTypes.find((osType) => selected === osType.slug) }) })), onChange: (event) => {
33
+ return (_jsxs(Stack, { flex: 1, children: [_jsx(Stack, { direction: "row", alignItems: "center", gap: 1, children: _jsxs(InputLabel, { id: "newAppApplicationType-label", sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: ["OS type", _jsx(MUILinkWithTracking, { sx: { height: 12 }, href: "https://www.balena.io/docs/reference/OS/extended-support-release", children: _jsx(ArticleIcon, { sx: { fontSize: '1rem' } }) })] }) }), _jsx(Select, { id: "newAppApplicationType", fullWidth: true, disabled: supportedOsTypes.length === 0, value: (_a = selectedOsType === null || selectedOsType === void 0 ? void 0 : selectedOsType.slug) !== null && _a !== void 0 ? _a : OsTypesEnum.DEFAULT, renderValue: (selected) => (_jsx(Box, { display: "flex", width: "100%", children: _jsx(OsTypeOption, { osType: selectOsTypes.find((osType) => selected === osType.slug) }) })), onChange: (event) => {
34
34
  const osType = selectOsTypes.find((os) => os.slug === event.target.value);
35
35
  if (!osType.disabled) {
36
36
  onSelectedOsTypeChange(osType.slug);
37
37
  }
38
- }, children: selectOsTypes.map((option) => (
38
+ }, sx: { flex: 1 }, children: selectOsTypes.map((option) => (
39
39
  // TODO: Consider adding a tooltip for the disabled options
40
40
  _jsx(MenuItem, { value: option.slug, disabled: option.disabled, children: _jsx(OsTypeOption, { osType: option }) }, option.slug))) })] }));
41
41
  };
@@ -1,29 +1,30 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Box, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, Tooltip, Typography, } from '@mui/material';
2
+ import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, Stack, Tooltip, Typography, } from '@mui/material';
3
3
  import { MUILinkWithTracking } from '../MUILinkWithTracking';
4
4
  import { getOsVariantDisplayText } from './utils';
5
5
  import { Lightbulb } from '@mui/icons-material';
6
6
  import { token } from '../../utils/token';
7
- const variantInfo = {
7
+ import { Callout } from '../Callout';
8
+ const variantInfo = (selected) => ({
8
9
  dev: {
9
- title: (_jsxs(Box, { display: "flex", gap: 1, flexDirection: "row", alignItems: "center", children: [_jsx(Typography, { children: getOsVariantDisplayText('dev') }), _jsxs(Typography, { variant: "bodySm", color: token('color.text'), alignItems: "center", display: "flex", children: [_jsx(Lightbulb, { sx: { width: 14, height: 14 } }), "Recommended for first time users"] })] })),
10
- description: (_jsxs(_Fragment, { children: ["Development images should be used when you are developing an application and want to use the fast", ' ', _jsx(MUILinkWithTracking, { href: "https://balena.io/docs/development/local-mode/", children: "local mode" }), ' ', "workflow", ' ', _jsx("strong", { children: "This variant should never be used in production." })] })),
10
+ title: (_jsxs(Stack, { gap: 1, direction: "row", alignItems: "center", children: [_jsx(Typography, { children: getOsVariantDisplayText('dev') }), _jsxs(Typography, { variant: "bodySm", color: token('color.text.accent'), alignItems: "center", display: "flex", children: [_jsx(Lightbulb, { sx: { width: 14, height: 14 } }), "Recommended for first time users"] })] })),
11
+ description: (_jsxs(Stack, { gap: 1, children: [_jsxs(Typography, { children: ["Development images should be used when you are developing an application and want to use the fast", ' ', _jsx(MUILinkWithTracking, { href: "https://balena.io/docs/development/local-mode/", children: "local mode" }), ' ', "workflow."] }), selected === 'dev' && (_jsxs(Callout, { severity: "warning", size: "sm", children: ["This variant should never be used in production for security reasons.", ' ', _jsx(MUILinkWithTracking, { href: "https://docs.balena.io/reference/OS/overview/#development-vs-production-mode", children: "Learn more" }), "."] }))] })),
11
12
  },
12
13
  prod: {
13
14
  title: _jsx(Typography, { children: getOsVariantDisplayText('prod') }),
14
15
  description: (_jsx(_Fragment, { children: "Production images are ready for production deployments, but don't offer easy access for local development." })),
15
16
  },
16
- };
17
+ });
17
18
  const BuildVariants = ['dev', 'prod'];
18
19
  export const VariantSelector = ({ version, variant, onVariantChange, }) => {
19
- return (_jsxs(FormControl, { children: [_jsx(FormLabel, { children: "Select edition" }), _jsx(RadioGroup, { "aria-labelledby": "variant-radio-buttons-group", name: "developmentMode", value: variant === 'dev', onChange: (event) => {
20
+ return (_jsxs(FormControl, { children: [_jsx(FormLabel, { children: _jsx(Typography, { variant: "titleSm", children: "Edition" }) }), _jsx(RadioGroup, { "aria-labelledby": "variant-radio-buttons-group", name: "developmentMode", value: variant === 'dev', onChange: (event) => {
20
21
  onVariantChange(event.target.value === 'true');
21
- }, children: BuildVariants.map((buildVariant, index) => {
22
+ }, sx: { gap: 2 }, children: BuildVariants.map((buildVariant, index) => {
22
23
  const isDev = buildVariant === 'dev';
23
24
  const isDisabled = version == null ||
24
25
  (version.hasPrebuiltVariants && !version.rawVersions[buildVariant]);
25
26
  return (_jsx(Tooltip, { title: isDisabled
26
27
  ? 'This edition is not available for the selected version'
27
- : undefined, children: _jsxs(Box, { display: "flex", flexDirection: "column", children: [_jsx(FormControlLabel, { sx: { opacity: isDisabled ? 0.4 : 1 }, disabled: isDisabled, value: isDev, control: _jsx(Radio, {}), label: variantInfo[buildVariant].title }), _jsx(Typography, { sx: { opacity: isDisabled ? 0.4 : 1 }, variant: "bodySm", children: variantInfo[buildVariant].description })] }) }, index));
28
- }) }, "varian")] }));
28
+ : undefined, children: _jsxs(Stack, { children: [_jsx(FormControlLabel, { sx: { opacity: isDisabled ? 0.4 : 1 }, disabled: isDisabled, value: isDev, control: _jsx(Radio, {}), label: variantInfo(variant)[buildVariant].title }), _jsx(Typography, { sx: { opacity: isDisabled ? 0.4 : 1 }, variant: "bodySm", children: variantInfo(variant)[buildVariant].description })] }) }, index));
29
+ }) }, "variant")] }));
29
30
  };
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Avatar, Box, DialogActions, DialogContent, Divider, Grid2 as Grid, Typography, } from '@mui/material';
2
+ import { Avatar, DialogActions, DialogContent, Divider, Grid2 as Grid, Stack, Typography, } from '@mui/material';
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { FALLBACK_LOGO_UNKNOWN_DEVICE, isUrlAccessible, stripVersionBuild, } from './utils';
5
5
  import { ImageForm } from './ImageForm';
@@ -242,18 +242,18 @@ export const DownloadImageDialog = ({ open, applicationId, releaseId, compatible
242
242
  const setOsTypeCallback = useCallback((osT) => {
243
243
  setOsType(osT);
244
244
  }, []);
245
- const handleChange = useCallback((updatedProps) => {
245
+ const handleChange = useCallback((key, value) => {
246
246
  let newFormModelState;
247
- if (updatedProps.deviceType != null) {
248
- newFormModelState = getInitialState(updatedProps.deviceType, applicationId, releaseId);
247
+ if (key === 'deviceType') {
248
+ newFormModelState = getInitialState(value, applicationId, releaseId);
249
249
  }
250
250
  else {
251
- newFormModelState = Object.assign(Object.assign({}, formModel), updatedProps);
251
+ newFormModelState = Object.assign(Object.assign({}, formModel), { [key]: value });
252
252
  onFieldChange === null || onFieldChange === void 0 ? void 0 : onFieldChange(newFormModelState);
253
253
  }
254
254
  setFormModel(newFormModelState);
255
255
  }, [formModel, applicationId, releaseId, onFieldChange]);
256
- return (_jsxs(DialogWithCloseButton, { title: _jsxs(Box, { display: "flex", alignItems: "center", children: [_jsx(Avatar, { variant: "square", alt: defaultDisplayName, src: (_c = (_b = formModel.deviceType) === null || _b === void 0 ? void 0 : _b.logo) !== null && _c !== void 0 ? _c : FALLBACK_LOGO_UNKNOWN_DEVICE, sx: { mr: 2 } }), _jsx(Typography, { variant: "h5", children: "Add new device" })] }), open: open, onClose: onClose, maxWidth: "lg", fullWidth: true, sx: { p: 4 }, children: [_jsx(DialogContent, { sx: { m: 0 }, children: _jsx(Spinner, { show: isValidatingUrl, children: _jsxs(Grid, { container: true, pb: 5, spacing: [0, 0, 4], children: [_jsx(Grid, { size: {
256
+ return (_jsxs(DialogWithCloseButton, { title: _jsxs(Stack, { direction: "row", alignItems: "center", gap: 2, children: [_jsx(Avatar, { variant: "square", alt: defaultDisplayName, src: (_c = (_b = formModel.deviceType) === null || _b === void 0 ? void 0 : _b.logo) !== null && _c !== void 0 ? _c : FALLBACK_LOGO_UNKNOWN_DEVICE }), _jsx(Typography, { variant: "h5", children: "Add new device" })] }), open: open, onClose: onClose, maxWidth: "lg", fullWidth: true, children: [_jsx(DialogContent, { children: _jsx(Spinner, { show: isValidatingUrl, children: _jsxs(Grid, { container: true, spacing: [0, 0, 4], children: [_jsx(Grid, { size: {
257
257
  xs: 12,
258
258
  sm: 12,
259
259
  md: 6,
@@ -277,11 +277,5 @@ export const DownloadImageDialog = ({ open, applicationId, releaseId, compatible
277
277
  : '',
278
278
  } })] }), ((_d = formModel.deviceType.imageDownloadAlerts) !== null && _d !== void 0 ? _d : []).map((alert) => {
279
279
  return (_jsx(Grid, { pt: 0, size: 12, children: _jsx(Callout, { severity: alert.type, children: alert.message }, alert.message) }, alert.message));
280
- })] }) }) }), _jsx(DialogActions, { sx: {
281
- display: 'flex',
282
- position: 'absolute',
283
- bottom: 0,
284
- right: 0,
285
- justifyContent: 'end',
286
- }, children: _jsx(DropDownButton, { className: "e2e-download-image-submit", items: actions, disabled: isValidatingUrl }) })] }));
280
+ })] }) }) }), _jsx(DialogActions, { children: _jsx(DropDownButton, { className: "e2e-download-image-submit", items: actions, disabled: isValidatingUrl }) })] }));
287
281
  };
@@ -2,20 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import { Link } from '@mui/material';
4
4
  function TableCellComponent({ href, onRowClick, row, rowKey, column, url, }) {
5
- return (_jsx(Link, { sx: (theme) => ({
6
- px: theme.spacing(2),
7
- color: 'inherit',
8
- textDecoration: 'none',
9
- display: 'table-cell',
10
- verticalAlign: 'middle',
11
- whiteSpace: 'nowrap',
12
- boxSizing: 'border-box',
13
- cursor: href ? 'pointer' : 'default',
14
- '&:hover': {
5
+ return (_jsx(Link, { sx: (theme) => (Object.assign({ px: theme.spacing(2), color: 'inherit', textDecoration: 'none', display: 'table-cell', verticalAlign: 'middle', whiteSpace: 'nowrap', boxSizing: 'border-box', cursor: href ? 'pointer' : 'default', '&:hover': {
15
6
  color: 'inherit',
16
- },
17
- height: '50px',
18
- }), href: href, "data-key": row[rowKey], onClick: (event) => {
7
+ }, height: '50px' }, (column.priority === 'primary' ? { fontWeight: 'bold' } : {}))), href: href, "data-key": row[rowKey], onClick: (event) => {
19
8
  onRowClick === null || onRowClick === void 0 ? void 0 : onRowClick(row, event);
20
9
  }, target: url ? '_blank' : undefined, children: column.render(row[column.field], row) }));
21
10
  }
package/dist/theme.js CHANGED
@@ -227,6 +227,16 @@ export const theme = createTheme({
227
227
  full: shape.borderRadius.full.value,
228
228
  },
229
229
  components: {
230
+ MuiAccordionSummary: {
231
+ styleOverrides: {
232
+ root: { paddingLeft: 0, paddingRight: 0 },
233
+ },
234
+ },
235
+ MuiAccordionDetails: {
236
+ styleOverrides: {
237
+ root: { paddingLeft: 0, paddingRight: 0 },
238
+ },
239
+ },
230
240
  MuiAlert: {
231
241
  defaultProps: {
232
242
  iconMapping: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balena/ui-shared-components",
3
- "version": "12.3.2-build-fix-non-unified-os-release-variant-selection-da43c55fce8efff9448ca4ad4807613647f1195f-1",
3
+ "version": "12.3.3-build-primary-column-highlight-af8b5c7c8d49b0bb8367062650a4b12e8cec1d2c-1",
4
4
  "main": "./dist/index.js",
5
5
  "sideEffects": false,
6
6
  "files": [
@@ -138,6 +138,6 @@
138
138
  },
139
139
  "homepage": "https://github.com/balena-io/ui-shared-components#readme",
140
140
  "versionist": {
141
- "publishedAt": "2025-04-24T13:20:07.505Z"
141
+ "publishedAt": "2025-04-24T13:43:25.382Z"
142
142
  }
143
143
  }