nurseitaly-components 0.0.6 → 0.0.8

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.
@@ -16,7 +16,7 @@ export default function Avatar({ src, className, size = 'large', title, onClick,
16
16
  placeholder: usePlaceholder,
17
17
  });
18
18
  useEffect(() => {
19
- !src && setUsePlaceholder(true);
19
+ setUsePlaceholder(!src);
20
20
  }, [src]);
21
21
  return (_jsx("div", { className: getClassNames, children: usePlaceholder ? (_jsx(Icon, { name: "account_circle", size: size, filled: true, onClick: onClick })) : (_jsx("img", { src: src, onError: () => {
22
22
  setUsePlaceholder(true);
@@ -39,6 +39,34 @@ export const selectFieldTheme = (outerTheme, { hasLabel = true, hasAddornments:
39
39
  },
40
40
  },
41
41
  },
42
+ MuiSelect: {
43
+ styleOverrides: {
44
+ select: {
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ boxSizing: 'border-box',
48
+ minHeight: 'unset!important',
49
+ height: hasLabel ? '48px!important' : 'auto',
50
+ padding: hasLabel ? '21px 0px 4px!important' : '8px 16px!important',
51
+ fontSize: '16px',
52
+ fontWeight: '400',
53
+ lineHeight: '120%',
54
+ letterSpacing: '0px',
55
+ fontFamily: 'Work Sans, sans-serif',
56
+ whiteSpace: 'nowrap',
57
+ overflow: 'hidden',
58
+ textOverflow: 'ellipsis',
59
+ '&.MuiSelect-multiple': {
60
+ minHeight: 'unset!important',
61
+ height: hasLabel ? '48px!important' : 'auto',
62
+ padding: hasLabel ? '21px 0px 4px!important' : '8px 16px!important',
63
+ },
64
+ },
65
+ icon: {
66
+ marginRight: hasAddorments ? '24px' : '0px',
67
+ },
68
+ },
69
+ },
42
70
  MuiInputLabel: {
43
71
  styleOverrides: {
44
72
  root: {
@@ -8,7 +8,7 @@ export type SelectItem = {
8
8
  type DefaultEndAdornmentType = 'close' | 'info';
9
9
  type Props<TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues> = {
10
10
  attr: string;
11
- label: string;
11
+ label?: string;
12
12
  createdForm: UseFormReturn<TFieldValues, TContext, TTransformedValues>;
13
13
  className?: string;
14
14
  disabled?: boolean;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
3
- import { FormControl, InputAdornment, InputLabel, MenuItem, Select, } from '@mui/material';
3
+ import { FilledInput, FormControl, InputAdornment, InputLabel, MenuItem, Select, } from '@mui/material';
4
4
  import { ThemeProvider, useTheme } from '@mui/material/styles';
5
5
  import cx from 'classnames';
6
6
  import { useEffect, useState } from 'react';
@@ -52,7 +52,10 @@ export default function SelectMultipleField({ className, attr, label, createdFor
52
52
  }
53
53
  return _jsx("div", { className: "ds-end-adornments", children: adornments });
54
54
  };
55
- return (_jsx("div", { className: getClassNames, children: _jsx(Controller, { name: fieldName, control: createdForm.control, render: ({ field }) => (_jsxs(ThemeProvider, { theme: selectFieldTheme(outerTheme), children: [_jsxs(FormControl, { fullWidth: true, variant: "filled", disabled: disabled, children: [_jsx(InputLabel, { shrink: true, sx: {
55
+ return (_jsx("div", { className: getClassNames, children: _jsx(Controller, { name: fieldName, control: createdForm.control, render: ({ field }) => (_jsxs(ThemeProvider, { theme: selectFieldTheme(outerTheme, {
56
+ hasLabel: !!label,
57
+ hasAddornments: !!endAdornment,
58
+ }), children: [_jsxs(FormControl, { fullWidth: true, variant: "filled", disabled: disabled, children: [label && (_jsx(InputLabel, { htmlFor: `${attr}-select`, shrink: true, sx: {
56
59
  color: disabled
57
60
  ? '#BEBFC1'
58
61
  : hasError()
@@ -60,7 +63,7 @@ export default function SelectMultipleField({ className, attr, label, createdFor
60
63
  : active
61
64
  ? '#1C83F4'
62
65
  : '#000000',
63
- }, children: `${label}${required ? ' *' : ''}` }), _jsxs(Select, { ...field, value: Array.isArray(field.value) ? field.value : [], multiple: true, fullWidth: true, displayEmpty: true, required: required, disabled: disabled, variant: "filled", color: "primary", onFocus: () => setFocus(true), onBlur: () => setFocus(false), onMouseOver: () => setMouseOver(true), onMouseOut: () => setMouseOver(false), onChange: (event) => {
66
+ }, children: `${label}${required ? ' *' : ''}` })), _jsxs(Select, { ...field, id: `${attr}-select`, value: Array.isArray(field.value) ? field.value : [], multiple: true, fullWidth: true, displayEmpty: true, required: required, disabled: disabled, variant: "filled", onFocus: () => setFocus(true), onBlur: () => setFocus(false), onMouseOver: () => setMouseOver(true), onMouseOut: () => setMouseOver(false), onChange: (event) => {
64
67
  let value = event.target.value;
65
68
  if (Array.isArray(value) && value.includes('')) {
66
69
  value = [];
@@ -81,18 +84,18 @@ export default function SelectMultipleField({ className, attr, label, createdFor
81
84
  // while keeping the smart "flipping" logic active
82
85
  variant: 'menu',
83
86
  anchorReference: 'anchorEl',
84
- }, sx: {
85
- borderColor: disabled
86
- ? '#BEBFC1'
87
- : hasError()
88
- ? '#C6080A!important'
89
- : active
90
- ? '#1C83F4!important'
91
- : '#BEBFC1!important',
92
- backgroundColor: disabled
93
- ? '#BEBFC1!important'
94
- : '#FFFFFF!important',
95
- }, endAdornment: showEndAdornment && (_jsx(InputAdornment, { position: "end", children: getEndAdornmentIcon(field.value) })), children: [_jsx(MenuItem, { value: "", sx: (theme) => ({
87
+ }, input: _jsx(FilledInput, { endAdornment: showEndAdornment && (_jsx(InputAdornment, { position: "end", children: getEndAdornmentIcon(field.value) })), sx: {
88
+ borderColor: disabled
89
+ ? '#BEBFC1'
90
+ : hasError()
91
+ ? '#C6080A!important'
92
+ : active
93
+ ? '#1C83F4!important'
94
+ : '#BEBFC1!important',
95
+ backgroundColor: disabled
96
+ ? '#BEBFC1!important'
97
+ : '#FFFFFF!important',
98
+ } }), children: [_jsx(MenuItem, { value: "", sx: (theme) => ({
96
99
  minHeight: theme.spacing(6),
97
100
  }), children: placeholder || '\u00a0' }), options.map((option, idx) => (_jsx(MenuItem, { value: option.value, children: option.label }, idx.valueOf())))] })] }), title && !hasError() && _jsx("div", { className: "helper", children: title }), hasError() && (_jsx("div", { className: "helper error", children: String(createdForm.formState.errors[attr]?.message) }))] })) }) }));
98
101
  }
@@ -2,6 +2,21 @@
2
2
  width: 100%;
3
3
  position: relative;
4
4
 
5
+ > .helper {
6
+ @include helper-small-1;
7
+ margin-top: 4px;
8
+
9
+ &.error {
10
+ color: var(--text-error);
11
+ }
12
+ }
13
+
14
+ .ds-end-adornments {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 8px;
18
+ }
19
+
5
20
  .ds-icon {
6
21
  cursor: pointer;
7
22
  }
@@ -10,7 +10,6 @@ type Props<TFieldValues extends FieldValues = FieldValues, TContext = any, TTran
10
10
  onBlurFunctions?: Record<string, () => void>;
11
11
  runtimeOptions?: Record<string, any[]>;
12
12
  runtimeLoadOptions?: Record<string, (query: string) => Promise<SelectItem[]>>;
13
- twoColumns?: boolean;
14
13
  };
15
- export default function AppForm<TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues>({ fields, form, prefix, onChangeFunctions, onBlurFunctions, runtimeOptions, runtimeLoadOptions, twoColumns, }: Props<TFieldValues, TContext, TTransformedValues>): import("react").JSX.Element;
14
+ export default function AppForm<TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues>({ fields, form, prefix, onChangeFunctions, onBlurFunctions, runtimeOptions, runtimeLoadOptions, }: Props<TFieldValues, TContext, TTransformedValues>): import("react").JSX.Element;
16
15
  export {};
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { AsyncSelectField, CheckboxField, DatePickerField, SelectField, SelectMultipleField, SwitchField, TextField, } from '../../basic';
3
3
  import FormColorField from './FormColorField';
4
+ import { getFieldFlexStyle, groupFieldsIntoRows } from './groupFieldsIntoRows';
4
5
  import './style.scss';
5
6
  const convertList = (values, labelAttr, valueAttr) => {
6
7
  if (!values)
@@ -11,7 +12,7 @@ const convertList = (values, labelAttr, valueAttr) => {
11
12
  });
12
13
  return fields;
13
14
  };
14
- export default function AppForm({ fields, form, prefix, onChangeFunctions = {}, onBlurFunctions = {}, runtimeOptions = {}, runtimeLoadOptions = {}, twoColumns = false, }) {
15
+ export default function AppForm({ fields, form, prefix, onChangeFunctions = {}, onBlurFunctions = {}, runtimeOptions = {}, runtimeLoadOptions = {}, }) {
15
16
  const getOnChangeFn = (attr) => onChangeFunctions[attr];
16
17
  const getOnBlurFn = (attr) => onBlurFunctions[attr];
17
18
  const getOptions = (attr) => {
@@ -19,49 +20,50 @@ export default function AppForm({ fields, form, prefix, onChangeFunctions = {},
19
20
  return undefined;
20
21
  return convertList(runtimeOptions[attr], 'name', 'uid');
21
22
  };
22
- return (_jsx("div", { className: `app-form-container ${twoColumns ? 'two-columns' : ''}`, children: fields.map((field) => {
23
- const name = prefix ? `${prefix}.${field.attr}` : field.attr;
24
- const handleChange = getOnChangeFn(field.attr);
25
- if (field.type === 'autocomplete') {
26
- const loadOptions = runtimeLoadOptions[field.attr];
27
- if (!loadOptions)
28
- return null;
29
- return (_jsx(AsyncSelectField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled, required: field.required, multiple: field.multiple, freeSolo: field.freeSolo, minQueryLength: field.minQueryLength, loadOptions: loadOptions, placeholder: field.title, noOptionsText: field.translation, onChange: (_attr, value) => handleChange?.(value) }, name));
30
- }
31
- if (field.type === 'select') {
32
- const selectOptions = getOptions(field.attr) || field.options || [];
33
- const selectProps = {
34
- attr: name,
35
- label: field.name,
36
- createdForm: form,
37
- options: selectOptions,
38
- disabled: field.disabled,
39
- required: field.required,
40
- onChange: (value) => handleChange?.(value),
41
- showEndAdornment: false,
42
- };
43
- if (field.multiple) {
44
- return _jsx(SelectMultipleField, { ...selectProps }, name);
45
- }
46
- return _jsx(SelectField, { ...selectProps }, name);
47
- }
48
- if (field.type === 'check') {
49
- return (_jsx(CheckboxField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled }, name));
50
- }
51
- if (field.type === 'switch') {
52
- return (_jsx(SwitchField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled, onChange: (value) => handleChange?.(value) }, name));
53
- }
54
- if (field.type === 'datepicker') {
55
- const handleBlur = getOnBlurFn(field.attr);
56
- return (_jsx(DatePickerField, { attr: name, label: field.size === 'small' ? undefined : field.name, createdForm: form, disabled: field.disabled, required: field.required, onChange: (_attr, value) => handleChange?.(value), onBlur: handleBlur, height: field.size === 'small' ? '32px' : undefined }, name));
57
- }
58
- if (field.type === 'textarea') {
59
- return (_jsx(TextField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled, required: field.required, onChange: (value) => handleChange?.(value), multiline: true, rows: field.rows, height: "100%", infoText: field.infoText, defaultEndAdornments: field.infoText ? ['info'] : ['close'] }, name));
60
- }
61
- if (field.type === 'color') {
62
- return (_jsx(FormColorField, { attr: name, label: field.name, form: form, disabled: field.disabled, tooltipText: field.tooltipText, onChange: (value) => handleChange?.(value) }, name));
23
+ const renderField = (field) => {
24
+ const name = prefix ? `${prefix}.${field.attr}` : field.attr;
25
+ const handleChange = getOnChangeFn(field.attr);
26
+ if (field.type === 'autocomplete') {
27
+ const loadOptions = runtimeLoadOptions[field.attr];
28
+ if (!loadOptions)
29
+ return null;
30
+ return (_jsx(AsyncSelectField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled, required: field.required, multiple: field.multiple, freeSolo: field.freeSolo, minQueryLength: field.minQueryLength, loadOptions: loadOptions, placeholder: field.title, noOptionsText: field.translation, onChange: (_attr, value) => handleChange?.(value) }, name));
31
+ }
32
+ if (field.type === 'select') {
33
+ const selectOptions = getOptions(field.attr) || field.options || [];
34
+ const selectProps = {
35
+ attr: name,
36
+ label: field.name,
37
+ createdForm: form,
38
+ options: selectOptions,
39
+ disabled: field.disabled,
40
+ required: field.required,
41
+ onChange: (value) => handleChange?.(value),
42
+ showEndAdornment: false,
43
+ };
44
+ if (field.multiple) {
45
+ return _jsx(SelectMultipleField, { ...selectProps }, name);
63
46
  }
47
+ return _jsx(SelectField, { ...selectProps }, name);
48
+ }
49
+ if (field.type === 'check') {
50
+ return (_jsx(CheckboxField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled }, name));
51
+ }
52
+ if (field.type === 'switch') {
53
+ return (_jsx(SwitchField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled, onChange: (value) => handleChange?.(value) }, name));
54
+ }
55
+ if (field.type === 'datepicker') {
64
56
  const handleBlur = getOnBlurFn(field.attr);
65
- return (_jsx(TextField, { attr: name, label: field.size === 'small' ? undefined : field.name, placeholder: field.size === 'small' ? field.name : undefined, createdForm: form, mask: field.mask, blurMask: field.blurMask, disabled: field.disabled, required: field.required, onChange: (value) => handleChange?.(value), height: field.size === 'small' ? '32px' : undefined, onBlur: handleBlur, infoText: field.infoText, defaultEndAdornments: field.infoText ? ['info'] : ['close'] }, name));
66
- }) }));
57
+ return (_jsx(DatePickerField, { attr: name, label: field.size === 'small' ? undefined : field.name, createdForm: form, disabled: field.disabled, required: field.required, onChange: (_attr, value) => handleChange?.(value), onBlur: handleBlur, height: field.size === 'small' ? '32px' : undefined }, name));
58
+ }
59
+ if (field.type === 'textarea') {
60
+ return (_jsx(TextField, { attr: name, label: field.name, createdForm: form, disabled: field.disabled, required: field.required, onChange: (value) => handleChange?.(value), multiline: true, rows: field.rows, height: "100%", infoText: field.infoText, defaultEndAdornments: field.infoText ? ['info'] : ['close'] }, name));
61
+ }
62
+ if (field.type === 'color') {
63
+ return (_jsx(FormColorField, { attr: name, label: field.name, form: form, disabled: field.disabled, tooltipText: field.tooltipText, onChange: (value) => handleChange?.(value) }, name));
64
+ }
65
+ const handleBlur = getOnBlurFn(field.attr);
66
+ return (_jsx(TextField, { attr: name, label: field.size === 'small' ? undefined : field.name, placeholder: field.size === 'small' ? field.name : undefined, createdForm: form, mask: field.mask, blurMask: field.blurMask, disabled: field.disabled, required: field.required, onChange: (value) => handleChange?.(value), height: field.size === 'small' ? '32px' : undefined, onBlur: handleBlur, infoText: field.infoText, defaultEndAdornments: field.infoText ? ['info'] : ['close'] }, name));
67
+ };
68
+ return (_jsx("div", { className: "app-form-container", children: groupFieldsIntoRows(fields).map((row, rowIdx) => (_jsx("div", { className: "app-form-row", children: row.map((field) => (_jsx("div", { className: "app-form-field", style: getFieldFlexStyle(field), children: renderField(field) }, field.attr))) }, rowIdx))) }));
67
69
  }
@@ -34,4 +34,10 @@ export type AppFormField = {
34
34
  display?: DisplayFunction;
35
35
  copyToClipboard?: boolean;
36
36
  truncate?: number;
37
+ /** Flex-grow weight when sharing a row with adjacent flex fields. Omit for full-width. */
38
+ flexGrow?: number;
39
+ /** Optional flex-basis (default: 0 for even ratio distribution). */
40
+ flexBasis?: number | string;
41
+ /** Force a new row before this field (even if it has flexGrow). */
42
+ breakBefore?: boolean;
37
43
  };
@@ -1,6 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { formatSwitchFieldValue } from './AppFormField';
4
+ import { getFieldFlexStyle, groupFieldsIntoRows } from './groupFieldsIntoRows';
4
5
  import './style.scss';
5
6
  import { Icon } from '../../basic';
6
7
  import UsabilityUtils from '../../functions/UsabilityUtils';
@@ -37,9 +38,7 @@ function getFieldDisplayContent(field, raw, t) {
37
38
  }
38
39
  export default function AppFormInfo({ fields, data }) {
39
40
  const { t } = useTranslation('common');
40
- return (_jsx("div", { className: "app-form-info-container", children: fields.map((field, idx) => {
41
- return (_jsxs("div", { className: "item", ...(field.title && { title: field.title }), children: [_jsxs("span", { className: "label", children: [field.name, ":"] }), _jsx("span", { className: "value", children: getFieldDisplayContent(field, data[field.attr], t) }), field.copyToClipboard && (_jsx(Icon, { name: "content_copy", size: "small", className: "ds-copy-icon", filled: true, onClick: () => {
42
- UsabilityUtils.copy(data[field.attr]);
43
- } }))] }, idx));
44
- }) }));
41
+ return (_jsx("div", { className: "app-form-info-container", children: groupFieldsIntoRows(fields).map((row, rowIdx) => (_jsx("div", { className: "app-form-row", children: row.map((field, idx) => (_jsx("div", { className: "app-form-field", style: getFieldFlexStyle(field), children: _jsxs("div", { className: "item", ...(field.title && { title: field.title }), children: [_jsxs("span", { className: "label", children: [field.name, ":"] }), _jsx("span", { className: "value", children: getFieldDisplayContent(field, data[field.attr], t) }), field.copyToClipboard && (_jsx(Icon, { name: "content_copy", size: "small", className: "ds-copy-icon", filled: true, onClick: () => {
42
+ UsabilityUtils.copy(data[field.attr]);
43
+ } }))] }) }, field.attr ?? idx))) }, rowIdx))) }));
45
44
  }
@@ -0,0 +1,4 @@
1
+ import { CSSProperties } from 'react';
2
+ import { AppFormField } from './AppFormField';
3
+ export declare function groupFieldsIntoRows(fields: AppFormField[]): AppFormField[][];
4
+ export declare function getFieldFlexStyle(field: AppFormField): CSSProperties | undefined;
@@ -0,0 +1,30 @@
1
+ export function groupFieldsIntoRows(fields) {
2
+ const rows = [];
3
+ let currentRow = [];
4
+ const flush = () => {
5
+ if (currentRow.length) {
6
+ rows.push(currentRow);
7
+ currentRow = [];
8
+ }
9
+ };
10
+ for (const field of fields) {
11
+ if (field.breakBefore)
12
+ flush();
13
+ if (field.flexGrow != null) {
14
+ currentRow.push(field);
15
+ }
16
+ else {
17
+ flush();
18
+ rows.push([field]);
19
+ }
20
+ }
21
+ flush();
22
+ return rows;
23
+ }
24
+ export function getFieldFlexStyle(field) {
25
+ if (field.flexGrow == null)
26
+ return undefined;
27
+ return {
28
+ flex: `${field.flexGrow} ${field.flexGrow} ${field.flexBasis ?? 0}`,
29
+ };
30
+ }
@@ -8,10 +8,6 @@
8
8
  align-items: center;
9
9
  gap: 4px;
10
10
 
11
- &.full-width {
12
- grid-column: 1 / -1;
13
- }
14
-
15
11
  .label {
16
12
  @include label-medium-2;
17
13
  color: var(--text-primary);
@@ -33,11 +29,26 @@
33
29
  display: flex;
34
30
  flex-direction: column;
35
31
  gap: 16px;
32
+ }
33
+
34
+ .app-form-row {
35
+ display: flex;
36
+ gap: 16px;
37
+ align-items: flex-start;
38
+ width: 100%;
39
+ }
40
+
41
+ .app-form-field {
42
+ min-width: 0;
43
+
44
+ &:only-child {
45
+ flex: 1 1 100%;
46
+ }
47
+ }
36
48
 
37
- &.two-columns {
38
- display: grid;
39
- grid-template-columns: repeat(2, 1fr);
40
- gap: 16px;
49
+ @media (max-width: 768px) {
50
+ .app-form-row {
51
+ flex-direction: column;
41
52
  }
42
53
  }
43
54
 
@@ -20,7 +20,7 @@ export default function AppToast() {
20
20
  return;
21
21
  dispatch(messageAction(EMPTY_TOAST));
22
22
  };
23
- const getLoading = () => (_jsxs("div", { className: "d-flex", children: [_jsx(ReactLoading, { type: "spinningBubbles", color: "blue", height: 24, width: 24 }), _jsx("span", { className: "ms-3", children: state.toastLoadingText ?? t('generic.loading') })] }));
23
+ const getLoading = () => (_jsxs("div", { className: "d-flex", children: [_jsx(ReactLoading, { type: "spinningBubbles", color: "blue", height: 24, width: 24 }), _jsx("span", { className: "ms-3", children: state.toastLoadingText ?? t('generic.loading', { defaultValue: 'Loading...' }) })] }));
24
24
  useEffect(() => {
25
25
  const closeOnEscape = (e) => {
26
26
  if (e.key === 'Escape')
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nurseitaly-components",
3
3
  "private": false,
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {