@balena/ui-shared-components 0.10.0 → 0.11.0

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.
@@ -0,0 +1,2 @@
1
+ import { ObjectFieldTemplateProps, RJSFSchema } from '@rjsf/utils';
2
+ export declare const ObjectFieldTemplate: <T = any, S extends import("json-schema").JSONSchema7 = RJSFSchema, F extends import("@rjsf/utils").GenericObjectType = any>(props: ObjectFieldTemplateProps<T, S, F>) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,26 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
+ import Grid from '@mui/material/Grid';
14
+ import { canExpand, descriptionId, getTemplate, getUiOptions, titleId, } from '@rjsf/utils';
15
+ export var ObjectFieldTemplate = function (props) {
16
+ var _a, _b;
17
+ var description = props.description, title = props.title, properties = props.properties, required = props.required, disabled = props.disabled, readonly = props.readonly, uiSchema = props.uiSchema, idSchema = props.idSchema, schema = props.schema, formData = props.formData, onAddClick = props.onAddClick, registry = props.registry;
18
+ var uiOptions = getUiOptions(uiSchema);
19
+ var TitleFieldTemplate = getTemplate('TitleFieldTemplate', registry, uiOptions);
20
+ var DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, uiOptions);
21
+ var AddButton = registry.templates.ButtonTemplates.AddButton;
22
+ return (_jsxs(_Fragment, { children: [title && (_jsx(TitleFieldTemplate, { id: titleId(idSchema), title: title, required: required, schema: schema, uiSchema: uiSchema, registry: registry })), description && (_jsx(DescriptionFieldTemplate, { id: descriptionId(idSchema), description: description, schema: schema, uiSchema: uiSchema, registry: registry })), _jsxs(Grid, __assign({ container: true, spacing: 2, style: { marginTop: '10px' } }, ((_b = (_a = uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema['ui:grid']) === null || _a === void 0 ? void 0 : _a.container) !== null && _b !== void 0 ? _b : {}), { children: [properties.map(function (element, index) {
23
+ var _a, _b;
24
+ return element.hidden ? (element.content) : (_jsx(Grid, __assign({ item: true, xs: 12, style: { marginBottom: '10px' } }, ((_b = (_a = uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema['ui:grid']) === null || _a === void 0 ? void 0 : _a.item) !== null && _b !== void 0 ? _b : {}), { children: element.content }), index));
25
+ }), canExpand(schema, uiSchema, formData) && (_jsx(Grid, __assign({ container: true, justifyContent: "flex-end" }, { children: _jsx(Grid, __assign({ item: true }, { children: _jsx(AddButton, { className: "object-property-expand", onClick: onAddClick(schema), disabled: disabled || readonly, uiSchema: uiSchema, registry: registry }) })) })))] }))] }));
26
+ };
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import type { StoryObj } from '@storybook/react';
3
+ declare const meta: {
4
+ title: string;
5
+ component: import("react").FC<import("react").PropsWithChildren<import(".").RJSFormProps>>;
6
+ tags: string[];
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof meta>;
10
+ export declare const Default: Story;
@@ -0,0 +1,95 @@
1
+ import { RJSForm } from '.';
2
+ var meta = {
3
+ title: 'Other/RJSForm',
4
+ component: RJSForm,
5
+ tags: ['autodocs'],
6
+ };
7
+ export default meta;
8
+ export var Default = {
9
+ args: {
10
+ schema: {
11
+ title: 'A registration form',
12
+ description: 'A simple form example.',
13
+ type: 'object',
14
+ required: ['firstName', 'lastName'],
15
+ properties: {
16
+ firstName: {
17
+ type: 'string',
18
+ title: 'First name',
19
+ default: 'Chuck',
20
+ },
21
+ lastName: {
22
+ type: 'string',
23
+ title: 'Last name',
24
+ },
25
+ age: {
26
+ type: 'integer',
27
+ title: 'Age',
28
+ },
29
+ bio: {
30
+ type: 'string',
31
+ title: 'Bio',
32
+ },
33
+ password: {
34
+ type: 'string',
35
+ title: 'Password',
36
+ minLength: 3,
37
+ },
38
+ passwordStrength: {
39
+ type: 'string',
40
+ title: 'Password make it strong',
41
+ minLength: 3,
42
+ },
43
+ telephone: {
44
+ type: 'string',
45
+ title: 'Telephone',
46
+ minLength: 10,
47
+ },
48
+ },
49
+ },
50
+ uiSchema: {
51
+ firstName: {
52
+ 'ui:autofocus': true,
53
+ 'ui:emptyValue': '',
54
+ 'ui:placeholder': 'ui:emptyValue causes this field to always be valid despite being required',
55
+ 'ui:autocomplete': 'family-name',
56
+ 'ui:enableMarkdownInDescription': true,
57
+ 'ui:description': 'Make text **bold** or *italic*. Take a look at other options [here](https://probablyup.com/markdown-to-jsx/).',
58
+ },
59
+ lastName: {
60
+ 'ui:autocomplete': 'given-name',
61
+ 'ui:enableMarkdownInDescription': true,
62
+ 'ui:description': 'Make things **bold** or *italic*. Embed snippets of `code`. <small>And this is a small texts.</small> ',
63
+ },
64
+ age: {
65
+ 'ui:widget': 'updown',
66
+ 'ui:title': 'Age of person',
67
+ 'ui:description': '(earth year)',
68
+ },
69
+ bio: {
70
+ 'ui:widget': 'textarea',
71
+ },
72
+ password: {
73
+ 'ui:widget': 'password',
74
+ },
75
+ passwordStrength: {
76
+ 'ui:widget': 'password',
77
+ 'ui:help': 'Hint: Make it strong!',
78
+ 'ui:options': {
79
+ showPasswordStrengthMeter: true,
80
+ },
81
+ },
82
+ telephone: {
83
+ 'ui:options': {
84
+ inputType: 'tel',
85
+ },
86
+ },
87
+ },
88
+ submitButtonProps: {
89
+ sx: {
90
+ display: 'flex',
91
+ alignSelf: 'flex-end',
92
+ },
93
+ },
94
+ },
95
+ };
@@ -0,0 +1,5 @@
1
+ import { WidgetProps } from '@rjsf/utils';
2
+ export interface PasswordStrengthProps {
3
+ password?: string;
4
+ }
5
+ export declare const PasswordWidget: ({ id, label, value, disabled, name, placeholder, onBlur, onChange, onFocus, options, required, }: WidgetProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,86 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __read = (this && this.__read) || function (o, n) {
13
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
14
+ if (!m) return o;
15
+ var i = m.call(o), r, ar = [], e;
16
+ try {
17
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
18
+ }
19
+ catch (error) { e = { error: error }; }
20
+ finally {
21
+ try {
22
+ if (r && !r.done && (m = i["return"])) m.call(i);
23
+ }
24
+ finally { if (e) throw e.error; }
25
+ }
26
+ return ar;
27
+ };
28
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
29
+ import { Box, FormControl, IconButton, InputAdornment, TextField, Typography, } from '@mui/material';
30
+ import * as React from 'react';
31
+ import { Visibility, VisibilityOff } from '@mui/icons-material';
32
+ import zxcvbn from 'zxcvbn';
33
+ var STRENGTH_TITLES = ['Very weak', 'Weak', 'Good', 'Strong', 'Very strong'];
34
+ var STRENGTH_STYLES = [
35
+ { width: 0 },
36
+ { width: '25%', backgroundColor: 'orange' },
37
+ { width: '50%', backgroundColor: 'yellow' },
38
+ { width: '75%', backgroundColor: 'green' },
39
+ { width: '100%', backgroundColor: 'darkgreen' },
40
+ ];
41
+ var PasswordStrength = function (_a) {
42
+ var password = _a.password;
43
+ var _b = __read(React.useState(), 2), strengthScore = _b[0], setStrengthScore = _b[1];
44
+ React.useEffect(function () {
45
+ if (password) {
46
+ try {
47
+ var score = zxcvbn(password).score;
48
+ setStrengthScore(score);
49
+ }
50
+ catch (_a) {
51
+ // Ignore any errors, as we only want to show the strength meter if it is available.
52
+ }
53
+ }
54
+ }, [password]);
55
+ if (!password || strengthScore === undefined) {
56
+ return null;
57
+ }
58
+ return (_jsxs(Box, { children: [_jsxs(Typography, { children: ["Password strength: ", _jsx("em", { children: STRENGTH_TITLES[strengthScore] })] }), _jsx(Box, { sx: [
59
+ STRENGTH_STYLES[strengthScore],
60
+ { height: '4px', transition: 'all ease-out 150ms' },
61
+ ] })] }));
62
+ };
63
+ export var PasswordWidget = function (_a) {
64
+ var id = _a.id, label = _a.label, value = _a.value, disabled = _a.disabled, name = _a.name, placeholder = _a.placeholder, onBlur = _a.onBlur, onChange = _a.onChange, onFocus = _a.onFocus, options = _a.options, required = _a.required;
65
+ var _b = __read(React.useState(false), 2), showPassword = _b[0], setShowPassword = _b[1];
66
+ var change = function (_a) {
67
+ var value = _a.target.value;
68
+ return onChange(value === '' ? options.emptyValue : value);
69
+ };
70
+ return (_jsxs(FormControl, { children: [_jsx(TextField, __assign({ label: label, required: required, id: id, type: showPassword ? 'text' : 'password' }, {
71
+ value: value,
72
+ disabled: disabled,
73
+ name: name,
74
+ placeholder: placeholder,
75
+ }, { onChange: change, onBlur: onBlur &&
76
+ (function (event) {
77
+ return onBlur(id, event.target.value);
78
+ }), onFocus: onFocus &&
79
+ (function (event) {
80
+ return onFocus(id, event.target.value);
81
+ }), InputProps: {
82
+ endAdornment: (_jsx(InputAdornment, __assign({ position: "end" }, { children: _jsx(IconButton, __assign({ onClick: function () { return setShowPassword(function (show) { return !show; }); }, onMouseDown: function (event) {
83
+ event.preventDefault();
84
+ }, edge: "end" }, { children: showPassword ? _jsx(VisibilityOff, {}) : _jsx(Visibility, {}) })) }))),
85
+ } })), options.showPasswordStrengthMeter && (_jsx(PasswordStrength, { password: value }))] }));
86
+ };
@@ -0,0 +1,2 @@
1
+ import { WidgetProps } from '@rjsf/utils';
2
+ export declare const SelectWidget: ({ id, label, value, disabled, name, placeholder, onChange, multiple, options, required, }: WidgetProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,38 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import { FormControl, Autocomplete, TextField, InputLabel, } from '@mui/material';
14
+ export var SelectWidget = function (_a) {
15
+ var id = _a.id, label = _a.label, value = _a.value, disabled = _a.disabled, name = _a.name, placeholder = _a.placeholder, onChange = _a.onChange, multiple = _a.multiple, options = _a.options, required = _a.required;
16
+ var change = function (_a) {
17
+ var value = _a.target.value;
18
+ return onChange(value === '' ? options.emptyValue : value);
19
+ };
20
+ var enumOptions = options.enumOptions, enumDisabled = options.enumDisabled;
21
+ if (!Array.isArray(enumOptions)) {
22
+ return null;
23
+ }
24
+ var sanitizedEnumDisabled = Array.isArray(enumDisabled)
25
+ ? enumDisabled
26
+ : null;
27
+ var selectOptions = enumOptions.map(function (option) {
28
+ return __assign(__assign({}, option), { disabled: sanitizedEnumDisabled != null &&
29
+ sanitizedEnumDisabled.includes(option.value) });
30
+ });
31
+ return (_jsxs(FormControl, { children: [_jsx(InputLabel, __assign({ htmlFor: id, required: required }, { children: label })), _jsx(Autocomplete, __assign({ fullWidth: true, id: id, value: value !== undefined
32
+ ? selectOptions.find(function (option) { return option.value === value; })
33
+ : undefined }, {
34
+ name: name,
35
+ placeholder: placeholder,
36
+ multiple: multiple,
37
+ }, { disabled: disabled, options: selectOptions, isOptionEqualToValue: function (option, value) { return option.value === value.value; }, getOptionLabel: function (option) { return option.label; }, getOptionDisabled: function (option) { return option.disabled; }, renderInput: function (params) { return _jsx(TextField, __assign({}, params)); }, onChange: change, disableClearable: true }))] }));
38
+ };
@@ -0,0 +1,12 @@
1
+ /// <reference types="react" />
2
+ import { FormProps as RjsFormProps } from '@rjsf/core';
3
+ import { BoxProps, ButtonProps } from '@mui/material';
4
+ export interface RJSFormProps extends Omit<RjsFormProps, 'validator' | 'onFocus' | 'onBlur'>, Pick<BoxProps, 'sx' | 'onFocus' | 'onBlur'>, Partial<Pick<RjsFormProps, 'validator'>> {
5
+ /** If true, do not display the form submit button */
6
+ hideSubmitButton?: boolean;
7
+ /** Properties that are passed to the submit button, these are the same props used for the [`Button`](#button) component */
8
+ submitButtonProps?: ButtonProps;
9
+ /** If passed, it will show a secondary button, these are the same props used for the [`Button`](#button) component */
10
+ actionButtons?: ButtonProps[];
11
+ }
12
+ export declare const RJSForm: React.FC<React.PropsWithChildren<RJSFormProps>>;
@@ -0,0 +1,40 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __rest = (this && this.__rest) || function (s, e) {
13
+ var t = {};
14
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
15
+ t[p] = s[p];
16
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
17
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
18
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
19
+ t[p[i]] = s[p[i]];
20
+ }
21
+ return t;
22
+ };
23
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
+ import Form from '@rjsf/mui';
25
+ import ajvValidator from '@rjsf/validator-ajv8';
26
+ import { Box, Button } from '@mui/material';
27
+ import { PasswordWidget } from './Widgets/PasswordWidget';
28
+ import { SelectWidget } from './Widgets/SelectWidget';
29
+ import { Fragment } from 'react';
30
+ import { ObjectFieldTemplate } from './FieldTemplates/ObjectFieldTemplate';
31
+ var internalWidgets = {
32
+ PasswordWidget: PasswordWidget,
33
+ SelectWidget: SelectWidget,
34
+ };
35
+ export var RJSForm = function (_a) {
36
+ var hideSubmitButton = _a.hideSubmitButton, submitButtonProps = _a.submitButtonProps, actionButtons = _a.actionButtons, _b = _a.validator, validator = _b === void 0 ? ajvValidator : _b, widgets = _a.widgets, fields = _a.fields, children = _a.children, ref = _a.ref, sx = _a.sx, onFocus = _a.onFocus, onBlur = _a.onBlur, templates = _a.templates, otherProps = __rest(_a, ["hideSubmitButton", "submitButtonProps", "actionButtons", "validator", "widgets", "fields", "children", "ref", "sx", "onFocus", "onBlur", "templates"]);
37
+ return (_jsx(Box, __assign({ sx: sx, onFocus: onFocus, onBlur: onBlur }, { children: _jsxs(Form, __assign({ ref: ref, validator: validator, showErrorList: false, widgets: __assign(__assign({}, internalWidgets), (widgets || {})), templates: __assign({ ObjectFieldTemplate: ObjectFieldTemplate }, templates) }, otherProps, { children: [hideSubmitButton && _jsx(Fragment, {}), actionButtons === null || actionButtons === void 0 ? void 0 : actionButtons.map(function (buttonProps) { return (_jsx(Button, __assign({}, buttonProps))); }), !hideSubmitButton && (_jsx(Button, __assign({ children: 'Submit',
38
+ // TODO: remove once we migrate buttons
39
+ disableRipple: true }, submitButtonProps, { color: "customBlue", variant: "contained", type: "submit" }))), children] })) })));
40
+ };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export type { ButtonWithTrackingProps } from './components/ButtonWithTracking';
5
5
  export { ButtonWithTracking } from './components/ButtonWithTracking';
6
6
  export { Code } from './components/Code';
7
7
  export type { CookiesBannerProps, Cookie } from './components/CookiesBanner';
8
+ export { RJSForm } from './components/Form';
9
+ export type { RJSFormProps } from './components/Form';
8
10
  export { CookiesBanner } from './components/CookiesBanner';
9
11
  export type { CollapsedListProps } from './components/CollapsedList';
10
12
  export { CollapsedList } from './components/CollapsedList';
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export { theme } from './theme';
2
2
  export { AnimatedText } from './components/AnimatedText';
3
3
  export { ButtonWithTracking } from './components/ButtonWithTracking';
4
4
  export { Code } from './components/Code';
5
+ export { RJSForm } from './components/Form';
5
6
  export { CookiesBanner } from './components/CookiesBanner';
6
7
  export { CollapsedList } from './components/CollapsedList';
7
8
  export { CustomHelmet } from './components/CustomHelmet';
package/dist/theme.js CHANGED
@@ -66,7 +66,7 @@ export var theme = createTheme({
66
66
  palette: {
67
67
  secondary: {
68
68
  light: '#8F9297',
69
- main: '#3C3E42',
69
+ main: '#fff',
70
70
  dark: '#0C0C0C',
71
71
  contrastText: '#F4F4F4',
72
72
  },
@@ -75,6 +75,7 @@ export var theme = createTheme({
75
75
  light: '#CAF0FF',
76
76
  main: '#00AEEF',
77
77
  dark: '#2A506F',
78
+ contrastText: '#FFFFFF',
78
79
  },
79
80
  customYellow: {
80
81
  xlight: '#F8DD88',
@@ -99,7 +100,7 @@ export var theme = createTheme({
99
100
  },
100
101
  text: {
101
102
  primary: '#2A506F',
102
- secondary: '#3C3E42',
103
+ secondary: '#527699',
103
104
  tertiary: '#8F9297',
104
105
  },
105
106
  },
@@ -251,11 +252,19 @@ export var theme = createTheme({
251
252
  },
252
253
  MuiButton: {
253
254
  styleOverrides: {
254
- root: {
255
- boxShadow: 'none',
256
- borderRadius: '24px',
257
- paddingLeft: '32px',
258
- paddingRight: '32px',
255
+ root: function (_a) {
256
+ var theme = _a.theme;
257
+ return ({
258
+ boxShadow: 'none',
259
+ borderRadius: '24px',
260
+ paddingLeft: '20px',
261
+ paddingRight: '20px',
262
+ fontSize: theme.typography.body1.fontSize,
263
+ // TODO: remove once we migrate buttons
264
+ '&:hover': {
265
+ boxShadow: 'none',
266
+ },
267
+ });
259
268
  },
260
269
  outlined: {
261
270
  textTransform: 'none',
@@ -272,6 +281,14 @@ export var theme = createTheme({
272
281
  },
273
282
  },
274
283
  },
284
+ // This is needed for RJSForm. If it gives any problem, we can remove this rule and override the behavior using the ArrayField field.
285
+ MuiPaper: {
286
+ styleOverrides: {
287
+ root: {
288
+ boxShadow: 'none',
289
+ },
290
+ },
291
+ },
275
292
  MuiTooltip: {
276
293
  styleOverrides: {
277
294
  tooltip: {
@@ -282,5 +299,36 @@ export var theme = createTheme({
282
299
  },
283
300
  },
284
301
  },
302
+ MuiFormControl: {
303
+ defaultProps: {
304
+ variant: 'outlined',
305
+ },
306
+ },
307
+ MuiOutlinedInput: {
308
+ defaultProps: {
309
+ notched: false,
310
+ },
311
+ styleOverrides: {
312
+ root: {
313
+ legend: {
314
+ // We should be able to remove this but there is a css override somewhere that
315
+ // set the fieldset > legend max-width to 100%, and this invalidate the notched: false rule for some inputs.
316
+ maxWidth: 0,
317
+ },
318
+ },
319
+ },
320
+ },
321
+ MuiInputLabel: {
322
+ defaultProps: {
323
+ shrink: true,
324
+ },
325
+ styleOverrides: {
326
+ root: {
327
+ fontSize: 12,
328
+ transform: 'none',
329
+ position: 'relative',
330
+ },
331
+ },
332
+ },
285
333
  },
286
334
  });
@@ -0,0 +1,2 @@
1
+ import { ObjectFieldTemplateProps, RJSFSchema } from '@rjsf/utils';
2
+ export declare const ObjectFieldTemplate: <T = any, S extends import("json-schema").JSONSchema7 = RJSFSchema, F extends import("@rjsf/utils").GenericObjectType = any>(props: ObjectFieldTemplateProps<T, S, F>) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { __assign } from "tslib";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import Grid from '@mui/material/Grid';
4
+ import { canExpand, descriptionId, getTemplate, getUiOptions, titleId, } from '@rjsf/utils';
5
+ export var ObjectFieldTemplate = function (props) {
6
+ var _a, _b;
7
+ var description = props.description, title = props.title, properties = props.properties, required = props.required, disabled = props.disabled, readonly = props.readonly, uiSchema = props.uiSchema, idSchema = props.idSchema, schema = props.schema, formData = props.formData, onAddClick = props.onAddClick, registry = props.registry;
8
+ var uiOptions = getUiOptions(uiSchema);
9
+ var TitleFieldTemplate = getTemplate('TitleFieldTemplate', registry, uiOptions);
10
+ var DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, uiOptions);
11
+ var AddButton = registry.templates.ButtonTemplates.AddButton;
12
+ return (_jsxs(_Fragment, { children: [title && (_jsx(TitleFieldTemplate, { id: titleId(idSchema), title: title, required: required, schema: schema, uiSchema: uiSchema, registry: registry })), description && (_jsx(DescriptionFieldTemplate, { id: descriptionId(idSchema), description: description, schema: schema, uiSchema: uiSchema, registry: registry })), _jsxs(Grid, __assign({ container: true, spacing: 2, style: { marginTop: '10px' } }, ((_b = (_a = uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema['ui:grid']) === null || _a === void 0 ? void 0 : _a.container) !== null && _b !== void 0 ? _b : {}), { children: [properties.map(function (element, index) {
13
+ var _a, _b;
14
+ return element.hidden ? (element.content) : (_jsx(Grid, __assign({ item: true, xs: 12, style: { marginBottom: '10px' } }, ((_b = (_a = uiSchema === null || uiSchema === void 0 ? void 0 : uiSchema['ui:grid']) === null || _a === void 0 ? void 0 : _a.item) !== null && _b !== void 0 ? _b : {}), { children: element.content }), index));
15
+ }), canExpand(schema, uiSchema, formData) && (_jsx(Grid, __assign({ container: true, justifyContent: "flex-end" }, { children: _jsx(Grid, __assign({ item: true }, { children: _jsx(AddButton, { className: "object-property-expand", onClick: onAddClick(schema), disabled: disabled || readonly, uiSchema: uiSchema, registry: registry }) })) })))] }))] }));
16
+ };
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import type { StoryObj } from '@storybook/react';
3
+ declare const meta: {
4
+ title: string;
5
+ component: import("react").FC<import("react").PropsWithChildren<import(".").RJSFormProps>>;
6
+ tags: string[];
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof meta>;
10
+ export declare const Default: Story;
@@ -0,0 +1,95 @@
1
+ import { RJSForm } from '.';
2
+ var meta = {
3
+ title: 'Other/RJSForm',
4
+ component: RJSForm,
5
+ tags: ['autodocs'],
6
+ };
7
+ export default meta;
8
+ export var Default = {
9
+ args: {
10
+ schema: {
11
+ title: 'A registration form',
12
+ description: 'A simple form example.',
13
+ type: 'object',
14
+ required: ['firstName', 'lastName'],
15
+ properties: {
16
+ firstName: {
17
+ type: 'string',
18
+ title: 'First name',
19
+ default: 'Chuck',
20
+ },
21
+ lastName: {
22
+ type: 'string',
23
+ title: 'Last name',
24
+ },
25
+ age: {
26
+ type: 'integer',
27
+ title: 'Age',
28
+ },
29
+ bio: {
30
+ type: 'string',
31
+ title: 'Bio',
32
+ },
33
+ password: {
34
+ type: 'string',
35
+ title: 'Password',
36
+ minLength: 3,
37
+ },
38
+ passwordStrength: {
39
+ type: 'string',
40
+ title: 'Password make it strong',
41
+ minLength: 3,
42
+ },
43
+ telephone: {
44
+ type: 'string',
45
+ title: 'Telephone',
46
+ minLength: 10,
47
+ },
48
+ },
49
+ },
50
+ uiSchema: {
51
+ firstName: {
52
+ 'ui:autofocus': true,
53
+ 'ui:emptyValue': '',
54
+ 'ui:placeholder': 'ui:emptyValue causes this field to always be valid despite being required',
55
+ 'ui:autocomplete': 'family-name',
56
+ 'ui:enableMarkdownInDescription': true,
57
+ 'ui:description': 'Make text **bold** or *italic*. Take a look at other options [here](https://probablyup.com/markdown-to-jsx/).',
58
+ },
59
+ lastName: {
60
+ 'ui:autocomplete': 'given-name',
61
+ 'ui:enableMarkdownInDescription': true,
62
+ 'ui:description': 'Make things **bold** or *italic*. Embed snippets of `code`. <small>And this is a small texts.</small> ',
63
+ },
64
+ age: {
65
+ 'ui:widget': 'updown',
66
+ 'ui:title': 'Age of person',
67
+ 'ui:description': '(earth year)',
68
+ },
69
+ bio: {
70
+ 'ui:widget': 'textarea',
71
+ },
72
+ password: {
73
+ 'ui:widget': 'password',
74
+ },
75
+ passwordStrength: {
76
+ 'ui:widget': 'password',
77
+ 'ui:help': 'Hint: Make it strong!',
78
+ 'ui:options': {
79
+ showPasswordStrengthMeter: true,
80
+ },
81
+ },
82
+ telephone: {
83
+ 'ui:options': {
84
+ inputType: 'tel',
85
+ },
86
+ },
87
+ },
88
+ submitButtonProps: {
89
+ sx: {
90
+ display: 'flex',
91
+ alignSelf: 'flex-end',
92
+ },
93
+ },
94
+ },
95
+ };
@@ -0,0 +1,5 @@
1
+ import { WidgetProps } from '@rjsf/utils';
2
+ export interface PasswordStrengthProps {
3
+ password?: string;
4
+ }
5
+ export declare const PasswordWidget: ({ id, label, value, disabled, name, placeholder, onBlur, onChange, onFocus, options, required, }: WidgetProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,60 @@
1
+ import { __assign, __read } from "tslib";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Box, FormControl, IconButton, InputAdornment, TextField, Typography, } from '@mui/material';
4
+ import * as React from 'react';
5
+ import { Visibility, VisibilityOff } from '@mui/icons-material';
6
+ import zxcvbn from 'zxcvbn';
7
+ var STRENGTH_TITLES = ['Very weak', 'Weak', 'Good', 'Strong', 'Very strong'];
8
+ var STRENGTH_STYLES = [
9
+ { width: 0 },
10
+ { width: '25%', backgroundColor: 'orange' },
11
+ { width: '50%', backgroundColor: 'yellow' },
12
+ { width: '75%', backgroundColor: 'green' },
13
+ { width: '100%', backgroundColor: 'darkgreen' },
14
+ ];
15
+ var PasswordStrength = function (_a) {
16
+ var password = _a.password;
17
+ var _b = __read(React.useState(), 2), strengthScore = _b[0], setStrengthScore = _b[1];
18
+ React.useEffect(function () {
19
+ if (password) {
20
+ try {
21
+ var score = zxcvbn(password).score;
22
+ setStrengthScore(score);
23
+ }
24
+ catch (_a) {
25
+ // Ignore any errors, as we only want to show the strength meter if it is available.
26
+ }
27
+ }
28
+ }, [password]);
29
+ if (!password || strengthScore === undefined) {
30
+ return null;
31
+ }
32
+ return (_jsxs(Box, { children: [_jsxs(Typography, { children: ["Password strength: ", _jsx("em", { children: STRENGTH_TITLES[strengthScore] })] }), _jsx(Box, { sx: [
33
+ STRENGTH_STYLES[strengthScore],
34
+ { height: '4px', transition: 'all ease-out 150ms' },
35
+ ] })] }));
36
+ };
37
+ export var PasswordWidget = function (_a) {
38
+ var id = _a.id, label = _a.label, value = _a.value, disabled = _a.disabled, name = _a.name, placeholder = _a.placeholder, onBlur = _a.onBlur, onChange = _a.onChange, onFocus = _a.onFocus, options = _a.options, required = _a.required;
39
+ var _b = __read(React.useState(false), 2), showPassword = _b[0], setShowPassword = _b[1];
40
+ var change = function (_a) {
41
+ var value = _a.target.value;
42
+ return onChange(value === '' ? options.emptyValue : value);
43
+ };
44
+ return (_jsxs(FormControl, { children: [_jsx(TextField, __assign({ label: label, required: required, id: id, type: showPassword ? 'text' : 'password' }, {
45
+ value: value,
46
+ disabled: disabled,
47
+ name: name,
48
+ placeholder: placeholder,
49
+ }, { onChange: change, onBlur: onBlur &&
50
+ (function (event) {
51
+ return onBlur(id, event.target.value);
52
+ }), onFocus: onFocus &&
53
+ (function (event) {
54
+ return onFocus(id, event.target.value);
55
+ }), InputProps: {
56
+ endAdornment: (_jsx(InputAdornment, __assign({ position: "end" }, { children: _jsx(IconButton, __assign({ onClick: function () { return setShowPassword(function (show) { return !show; }); }, onMouseDown: function (event) {
57
+ event.preventDefault();
58
+ }, edge: "end" }, { children: showPassword ? _jsx(VisibilityOff, {}) : _jsx(Visibility, {}) })) }))),
59
+ } })), options.showPasswordStrengthMeter && (_jsx(PasswordStrength, { password: value }))] }));
60
+ };
@@ -0,0 +1,2 @@
1
+ import { WidgetProps } from '@rjsf/utils';
2
+ export declare const SelectWidget: ({ id, label, value, disabled, name, placeholder, onChange, multiple, options, required, }: WidgetProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,28 @@
1
+ import { __assign } from "tslib";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { FormControl, Autocomplete, TextField, InputLabel, } from '@mui/material';
4
+ export var SelectWidget = function (_a) {
5
+ var id = _a.id, label = _a.label, value = _a.value, disabled = _a.disabled, name = _a.name, placeholder = _a.placeholder, onChange = _a.onChange, multiple = _a.multiple, options = _a.options, required = _a.required;
6
+ var change = function (_a) {
7
+ var value = _a.target.value;
8
+ return onChange(value === '' ? options.emptyValue : value);
9
+ };
10
+ var enumOptions = options.enumOptions, enumDisabled = options.enumDisabled;
11
+ if (!Array.isArray(enumOptions)) {
12
+ return null;
13
+ }
14
+ var sanitizedEnumDisabled = Array.isArray(enumDisabled)
15
+ ? enumDisabled
16
+ : null;
17
+ var selectOptions = enumOptions.map(function (option) {
18
+ return __assign(__assign({}, option), { disabled: sanitizedEnumDisabled != null &&
19
+ sanitizedEnumDisabled.includes(option.value) });
20
+ });
21
+ return (_jsxs(FormControl, { children: [_jsx(InputLabel, __assign({ htmlFor: id, required: required }, { children: label })), _jsx(Autocomplete, __assign({ fullWidth: true, id: id, value: value !== undefined
22
+ ? selectOptions.find(function (option) { return option.value === value; })
23
+ : undefined }, {
24
+ name: name,
25
+ placeholder: placeholder,
26
+ multiple: multiple,
27
+ }, { disabled: disabled, options: selectOptions, isOptionEqualToValue: function (option, value) { return option.value === value.value; }, getOptionLabel: function (option) { return option.label; }, getOptionDisabled: function (option) { return option.disabled; }, renderInput: function (params) { return _jsx(TextField, __assign({}, params)); }, onChange: change, disableClearable: true }))] }));
28
+ };
@@ -0,0 +1,12 @@
1
+ /// <reference types="react" />
2
+ import { FormProps as RjsFormProps } from '@rjsf/core';
3
+ import { BoxProps, ButtonProps } from '@mui/material';
4
+ export interface RJSFormProps extends Omit<RjsFormProps, 'validator' | 'onFocus' | 'onBlur'>, Pick<BoxProps, 'sx' | 'onFocus' | 'onBlur'>, Partial<Pick<RjsFormProps, 'validator'>> {
5
+ /** If true, do not display the form submit button */
6
+ hideSubmitButton?: boolean;
7
+ /** Properties that are passed to the submit button, these are the same props used for the [`Button`](#button) component */
8
+ submitButtonProps?: ButtonProps;
9
+ /** If passed, it will show a secondary button, these are the same props used for the [`Button`](#button) component */
10
+ actionButtons?: ButtonProps[];
11
+ }
12
+ export declare const RJSForm: React.FC<React.PropsWithChildren<RJSFormProps>>;
@@ -0,0 +1,19 @@
1
+ import { __assign, __rest } from "tslib";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Form from '@rjsf/mui';
4
+ import ajvValidator from '@rjsf/validator-ajv8';
5
+ import { Box, Button } from '@mui/material';
6
+ import { PasswordWidget } from './Widgets/PasswordWidget';
7
+ import { SelectWidget } from './Widgets/SelectWidget';
8
+ import { Fragment } from 'react';
9
+ import { ObjectFieldTemplate } from './FieldTemplates/ObjectFieldTemplate';
10
+ var internalWidgets = {
11
+ PasswordWidget: PasswordWidget,
12
+ SelectWidget: SelectWidget,
13
+ };
14
+ export var RJSForm = function (_a) {
15
+ var hideSubmitButton = _a.hideSubmitButton, submitButtonProps = _a.submitButtonProps, actionButtons = _a.actionButtons, _b = _a.validator, validator = _b === void 0 ? ajvValidator : _b, widgets = _a.widgets, fields = _a.fields, children = _a.children, ref = _a.ref, sx = _a.sx, onFocus = _a.onFocus, onBlur = _a.onBlur, templates = _a.templates, otherProps = __rest(_a, ["hideSubmitButton", "submitButtonProps", "actionButtons", "validator", "widgets", "fields", "children", "ref", "sx", "onFocus", "onBlur", "templates"]);
16
+ return (_jsx(Box, __assign({ sx: sx, onFocus: onFocus, onBlur: onBlur }, { children: _jsxs(Form, __assign({ ref: ref, validator: validator, showErrorList: false, widgets: __assign(__assign({}, internalWidgets), (widgets || {})), templates: __assign({ ObjectFieldTemplate: ObjectFieldTemplate }, templates) }, otherProps, { children: [hideSubmitButton && _jsx(Fragment, {}), actionButtons === null || actionButtons === void 0 ? void 0 : actionButtons.map(function (buttonProps) { return (_jsx(Button, __assign({}, buttonProps))); }), !hideSubmitButton && (_jsx(Button, __assign({ children: 'Submit',
17
+ // TODO: remove once we migrate buttons
18
+ disableRipple: true }, submitButtonProps, { color: "customBlue", variant: "contained", type: "submit" }))), children] })) })));
19
+ };
@@ -5,6 +5,8 @@ export type { ButtonWithTrackingProps } from './components/ButtonWithTracking';
5
5
  export { ButtonWithTracking } from './components/ButtonWithTracking';
6
6
  export { Code } from './components/Code';
7
7
  export type { CookiesBannerProps, Cookie } from './components/CookiesBanner';
8
+ export { RJSForm } from './components/Form';
9
+ export type { RJSFormProps } from './components/Form';
8
10
  export { CookiesBanner } from './components/CookiesBanner';
9
11
  export type { CollapsedListProps } from './components/CollapsedList';
10
12
  export { CollapsedList } from './components/CollapsedList';
@@ -2,6 +2,7 @@ export { theme } from './theme';
2
2
  export { AnimatedText } from './components/AnimatedText';
3
3
  export { ButtonWithTracking } from './components/ButtonWithTracking';
4
4
  export { Code } from './components/Code';
5
+ export { RJSForm } from './components/Form';
5
6
  export { CookiesBanner } from './components/CookiesBanner';
6
7
  export { CollapsedList } from './components/CollapsedList';
7
8
  export { CustomHelmet } from './components/CustomHelmet';
@@ -66,7 +66,7 @@ export var theme = createTheme({
66
66
  palette: {
67
67
  secondary: {
68
68
  light: '#8F9297',
69
- main: '#3C3E42',
69
+ main: '#fff',
70
70
  dark: '#0C0C0C',
71
71
  contrastText: '#F4F4F4',
72
72
  },
@@ -75,6 +75,7 @@ export var theme = createTheme({
75
75
  light: '#CAF0FF',
76
76
  main: '#00AEEF',
77
77
  dark: '#2A506F',
78
+ contrastText: '#FFFFFF',
78
79
  },
79
80
  customYellow: {
80
81
  xlight: '#F8DD88',
@@ -99,7 +100,7 @@ export var theme = createTheme({
99
100
  },
100
101
  text: {
101
102
  primary: '#2A506F',
102
- secondary: '#3C3E42',
103
+ secondary: '#527699',
103
104
  tertiary: '#8F9297',
104
105
  },
105
106
  },
@@ -251,11 +252,19 @@ export var theme = createTheme({
251
252
  },
252
253
  MuiButton: {
253
254
  styleOverrides: {
254
- root: {
255
- boxShadow: 'none',
256
- borderRadius: '24px',
257
- paddingLeft: '32px',
258
- paddingRight: '32px',
255
+ root: function (_a) {
256
+ var theme = _a.theme;
257
+ return ({
258
+ boxShadow: 'none',
259
+ borderRadius: '24px',
260
+ paddingLeft: '20px',
261
+ paddingRight: '20px',
262
+ fontSize: theme.typography.body1.fontSize,
263
+ // TODO: remove once we migrate buttons
264
+ '&:hover': {
265
+ boxShadow: 'none',
266
+ },
267
+ });
259
268
  },
260
269
  outlined: {
261
270
  textTransform: 'none',
@@ -272,6 +281,14 @@ export var theme = createTheme({
272
281
  },
273
282
  },
274
283
  },
284
+ // This is needed for RJSForm. If it gives any problem, we can remove this rule and override the behavior using the ArrayField field.
285
+ MuiPaper: {
286
+ styleOverrides: {
287
+ root: {
288
+ boxShadow: 'none',
289
+ },
290
+ },
291
+ },
275
292
  MuiTooltip: {
276
293
  styleOverrides: {
277
294
  tooltip: {
@@ -282,5 +299,36 @@ export var theme = createTheme({
282
299
  },
283
300
  },
284
301
  },
302
+ MuiFormControl: {
303
+ defaultProps: {
304
+ variant: 'outlined',
305
+ },
306
+ },
307
+ MuiOutlinedInput: {
308
+ defaultProps: {
309
+ notched: false,
310
+ },
311
+ styleOverrides: {
312
+ root: {
313
+ legend: {
314
+ // We should be able to remove this but there is a css override somewhere that
315
+ // set the fieldset > legend max-width to 100%, and this invalidate the notched: false rule for some inputs.
316
+ maxWidth: 0,
317
+ },
318
+ },
319
+ },
320
+ },
321
+ MuiInputLabel: {
322
+ defaultProps: {
323
+ shrink: true,
324
+ },
325
+ styleOverrides: {
326
+ root: {
327
+ fontSize: 12,
328
+ transform: 'none',
329
+ position: 'relative',
330
+ },
331
+ },
332
+ },
285
333
  },
286
334
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balena/ui-shared-components",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist_esm5/index.js",
6
6
  "sideEffects": false,
@@ -17,6 +17,10 @@
17
17
  "@mui/material": "^5.12.0",
18
18
  "@mui/styled-engine-sc": "^5.12.0",
19
19
  "@react-google-maps/api": "^2.18.1",
20
+ "@rjsf/core": "^5.13.0",
21
+ "@rjsf/mui": "^5.13.0",
22
+ "@rjsf/utils": "^5.13.0",
23
+ "@rjsf/validator-ajv8": "^5.13.4",
20
24
  "@types/node": "^18.15.11",
21
25
  "@types/react": "^18.0.35",
22
26
  "@types/react-dom": "^18.0.11",
@@ -34,7 +38,8 @@
34
38
  "remark-gfm": "^3.0.1",
35
39
  "rimraf": "^5.0.0",
36
40
  "typescript": "^4.9.5",
37
- "virtua": "^0.13.0"
41
+ "virtua": "^0.13.0",
42
+ "zxcvbn": "^4.4.2"
38
43
  },
39
44
  "devDependencies": {
40
45
  "@balena/lint": "^6.2.2",
@@ -47,6 +52,7 @@
47
52
  "@storybook/react": "^7.3.2",
48
53
  "@storybook/react-webpack5": "^7.3.2",
49
54
  "@storybook/testing-library": "^0.2.0",
55
+ "@types/zxcvbn": "^4.4.2",
50
56
  "babel-plugin-named-exports-order": "^0.0.2",
51
57
  "eslint-plugin-storybook": "^0.6.13",
52
58
  "husky": "^8.0.3",
@@ -102,6 +108,6 @@
102
108
  },
103
109
  "homepage": "https://github.com/balena-io/ui-shared-components#readme",
104
110
  "versionist": {
105
- "publishedAt": "2023-11-07T15:57:15.858Z"
111
+ "publishedAt": "2023-11-08T18:09:30.687Z"
106
112
  }
107
113
  }