@axinom/mosaic-ui 0.54.0-rc.1 → 0.54.0-rc.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.54.0-rc.1",
3
+ "version": "0.54.0-rc.2",
4
4
  "description": "UI components for building Axinom Mosaic applications",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -32,7 +32,7 @@
32
32
  "build-storybook": "storybook build"
33
33
  },
34
34
  "dependencies": {
35
- "@axinom/mosaic-core": "^0.4.27-rc.1",
35
+ "@axinom/mosaic-core": "^0.4.27-rc.2",
36
36
  "@faker-js/faker": "^7.4.0",
37
37
  "@geoffcox/react-splitter": "^2.1.2",
38
38
  "@popperjs/core": "^2.11.8",
@@ -106,5 +106,5 @@
106
106
  "publishConfig": {
107
107
  "access": "public"
108
108
  },
109
- "gitHead": "317b13a7a34e369eb547a0a91aaedd05f9bb00fe"
109
+ "gitHead": "2d015f129e61beade8826336c6d32e49a5eb36a3"
110
110
  }
@@ -246,6 +246,10 @@ export const WithSelectInputRenderer: StoryObj<StoryDDLType> = {
246
246
  },
247
247
  ],
248
248
  allowNewData: true,
249
+ rowValidationSchema: Yup.object({
250
+ id: Yup.number().required(),
251
+ title: Yup.string().required().max(25).label('Title'),
252
+ }),
249
253
  },
250
254
  };
251
255
 
@@ -1,92 +1,15 @@
1
1
  @import '../../../../../styles/common.scss';
2
2
 
3
- @function svg-arrow-glyph($color) {
4
- @return url('data:image/svg+xml;utf8,<svg stroke="' + $color + '" version="1.1" xmlns="http://www.w3.org/2000/svg" width="40px" height="20px" viewBox="0 0 40 40"><path vector-effect="non-scaling-stroke" fill="none" stroke-width="2" d="M38.5,9.5L20,30.5L1.5,9.5" /></svg>');
5
- }
6
-
7
3
  .container {
8
4
  height: 100%;
9
5
  width: 100%;
10
6
 
11
7
  display: grid;
12
8
  grid-template-columns: 1fr;
13
- grid-template-rows: 1fr;
9
+ grid-template-rows: min-content min-content;
14
10
 
15
11
  select {
16
- box-sizing: border-box;
17
- height: var(--dynamic-list-input-height, $dynamic-list-input-height);
12
+ max-width: none;
18
13
  width: 100%;
19
-
20
- padding-left: 6px;
21
-
22
- color: var(--input-color, $input-color);
23
- font-size: var(--label-font-size, $label-font-size);
24
-
25
- -moz-appearance: none;
26
- -webkit-appearance: none;
27
- appearance: none;
28
- cursor: pointer;
29
- color: var(--input-color, $input-color);
30
- border: 1px solid var(--input-border-color, $input-border-color);
31
- outline: none;
32
-
33
- background-image: svg-arrow-glyph(
34
- var(--select-arrow-color, encodecolor($select-arrow-color))
35
- );
36
- //background-image: url('data:image/svg+xml;utf8,<svg stroke="%231478af" version="1.1" xmlns="http://www.w3.org/2000/svg" width="40px" height="20px" viewBox="0 0 40 40"><path vector-effect="non-scaling-stroke" fill="none" stroke-width="2" d="M38.5,9.5L20,30.5L1.5,9.5" /></svg>');
37
- background-repeat: no-repeat;
38
- background-position: center;
39
- background-position-x: 100%;
40
-
41
- padding: 0 40px 0 12px;
42
-
43
- transition: box-shadow 0.15s ease-in-out 0s;
44
-
45
- &.hasError {
46
- border: 1px solid
47
- var(--input-invalid-border-color, $input-invalid-border-color);
48
- }
49
-
50
- &:disabled {
51
- border-color: var(
52
- --input-disabled-border-color,
53
- $input-disabled-border-color
54
- );
55
-
56
- cursor: default;
57
-
58
- background-image: svg-arrow-glyph(
59
- var(--select-arrow-color, encodecolor($select-disabled-arrow-color))
60
- );
61
- background-color: var(
62
- --input-disabled-background-color,
63
- $input-disabled-background-color
64
- );
65
- }
66
-
67
- &:not(:disabled):hover {
68
- border: 1px solid var(--input-hover-color, $input-hover-color);
69
- box-shadow: 0 0 0 2px var(--input-hover-color, $input-hover-color);
70
- }
71
-
72
- &.hasError {
73
- border: 1px solid
74
- var(--input-invalid-border-color, $input-invalid-border-color);
75
- box-shadow: 0 0 0 2px
76
- var(--input-invalid-hover-color, $input-invalid-hover-color);
77
- }
78
- }
79
-
80
- select,
81
- option {
82
- overflow: hidden;
83
- white-space: nowrap;
84
- text-overflow: ellipsis;
85
- }
86
-
87
- select > small {
88
- padding: 6px 0;
89
- white-space: normal;
90
- color: var(--input-invalid-color, $input-invalid-color);
91
14
  }
92
15
  }
@@ -1,7 +1,8 @@
1
- import { shallow } from 'enzyme';
1
+ import { mount, shallow } from 'enzyme';
2
2
  import React from 'react';
3
3
  import { act } from 'react-dom/test-utils';
4
4
  import { noop } from '../../../../../helpers/utils';
5
+ import { Select } from '../../../../FormElements';
5
6
  import { CreateSelectRendererConfig } from '../renderers.model';
6
7
  import { createSelectRenderer } from './createSelectRenderer';
7
8
 
@@ -42,102 +43,29 @@ describe('createSelectRenderer', () => {
42
43
  expect(wrapper).toBeTruthy();
43
44
  });
44
45
 
45
- it(`renders an 'id' attribute on the 'select' element if set`, () => {
46
- const mockId = 'test-id';
47
- const wrapper = shallow(<RendererWrapper id={mockId} />);
48
-
49
- const select = wrapper.find('select');
50
-
51
- expect(select.prop('id')).toBe(mockId);
52
- });
53
-
54
- it(`renders a 'name' attribute on the 'select' element if set`, () => {
55
- const mockName = 'test-name';
56
- const wrapper = shallow(<RendererWrapper name={mockName} />);
57
-
58
- const select = wrapper.find('select');
59
-
60
- expect(select.prop('name')).toBe(mockName);
61
- });
62
-
63
- it('renders "disabled" attribute on the "select" element with value "false" if not set otherwise', () => {
64
- const wrapper = shallow(<RendererWrapper />);
65
-
66
- const input = wrapper.find('select');
67
-
68
- expect(input.prop('disabled')).toBe(false);
69
- });
70
-
71
- it('renders "disabled" attribute on the "select" element if set', () => {
72
- const wrapper = shallow(<RendererWrapper disabled={true} />);
73
-
74
- const input = wrapper.find('select');
75
-
76
- expect(input.prop('disabled')).toBe(true);
77
- });
78
-
79
- it(`doesn't render placeholder by default`, () => {
80
- const wrapper = shallow(<RendererWrapper />);
81
-
82
- const option = wrapper.find('option');
83
-
84
- expect(option.exists()).toBe(false);
85
- });
86
-
87
- it(`renders a placeholder option if the 'placeholder' prop is set`, () => {
88
- const mockPlaceholder = 'test-placeholder';
89
- const wrapper = shallow(<RendererWrapper placeholder={mockPlaceholder} />);
90
-
91
- const option = wrapper.find('option');
92
-
93
- expect(option.text()).toBe(mockPlaceholder);
94
- expect(option.prop('value')).toBe('');
95
- expect(option.prop('disabled')).toBe(true);
96
- });
97
46
  it(`sets 'select' value to current value`, () => {
98
47
  const mockValue = 'test-value';
99
- const wrapper = shallow(<RendererWrapper currentValue={mockValue} />);
48
+ const wrapper = mount(<RendererWrapper currentValue={mockValue} />);
100
49
 
101
50
  const select = wrapper.find('select');
102
51
 
103
52
  expect(select.prop('value')).toBe(mockValue);
104
53
  });
105
54
 
106
- it('renders all options', () => {
107
- const mockOptions = [
108
- { value: '10', label: '10' },
109
- { value: '11', label: '11' },
110
- ];
111
- const wrapper = shallow(<RendererWrapper options={mockOptions} />);
112
-
113
- const options = wrapper.find('option');
114
-
115
- options.forEach((option, idx) => {
116
- expect(option.text()).toBe(mockOptions[idx].label);
117
- expect(option.prop('value')).toBe(mockOptions[idx].value);
118
- });
119
- });
120
-
121
- it(`doesn't render any option elements if 'options' prop is not set`, () => {
122
- const wrapper = shallow(<RendererWrapper options={undefined} />);
123
-
124
- const options = wrapper.find('option');
125
-
126
- expect(options.exists()).toBe(false);
127
- });
128
-
129
55
  it(`emits 'onValueChange' with the new value when a new option has been selected`, () => {
130
56
  const onValueChangeSpy = jest.fn();
131
57
  const mockValueUpdated = 'updated-test-value';
132
- const wrapper = shallow(
133
- <RendererWrapper onValueChange={onValueChangeSpy} />,
134
- );
58
+ const wrapper = mount(<RendererWrapper onValueChange={onValueChangeSpy} />);
135
59
 
136
- const select = wrapper.find('select');
60
+ const select = wrapper.find(Select);
137
61
  expect(select.prop('value')).toBe('');
138
62
 
139
63
  act(() => {
140
- select.simulate('change', { target: { value: mockValueUpdated } });
64
+ select.prop('onChange')?.({
65
+ // @ts-expect-error not full event args object
66
+ currentTarget: { value: mockValueUpdated },
67
+ });
68
+ wrapper.update();
141
69
  });
142
70
 
143
71
  expect(onValueChangeSpy).toHaveBeenCalledTimes(1);
@@ -149,17 +77,21 @@ describe('createSelectRenderer', () => {
149
77
  const mockStringifiedNumber = '5';
150
78
  const stringToNumberTransformer = (value: string): number => Number(value);
151
79
 
152
- const wrapper = shallow(
80
+ const wrapper = mount(
153
81
  <RendererWrapper
154
82
  onValueChange={onValueChangeSpy}
155
83
  transform={stringToNumberTransformer}
156
84
  />,
157
85
  );
158
86
 
159
- const select = wrapper.find('select');
87
+ const select = wrapper.find(Select);
160
88
 
161
89
  act(() => {
162
- select.simulate('change', { target: { value: mockStringifiedNumber } });
90
+ select.prop('onChange')?.({
91
+ // @ts-expect-error not full event args object
92
+ currentTarget: { value: mockStringifiedNumber },
93
+ });
94
+ wrapper.update();
163
95
  });
164
96
 
165
97
  expect(onValueChangeSpy).toHaveBeenCalledTimes(1);
@@ -1,6 +1,6 @@
1
- import clsx from 'clsx';
2
- import React, { ChangeEvent } from 'react';
1
+ import React, { FormEvent } from 'react';
3
2
  import { DynamicListDataEntryRenderer } from '../../../../DynamicDataList/DynamicDataList.model';
3
+ import { Select } from '../../../../FormElements';
4
4
  import { CreateSelectRendererConfig } from '../renderers.model';
5
5
  import classes from './createSelectRenderer.scss';
6
6
 
@@ -17,7 +17,7 @@ import classes from './createSelectRenderer.scss';
17
17
  */
18
18
  export const createSelectRenderer = ({
19
19
  id,
20
- name,
20
+ name = '',
21
21
  placeholder,
22
22
  options = [],
23
23
  defaultValue,
@@ -33,34 +33,23 @@ export const createSelectRenderer = ({
33
33
  onValueChange(defaultValue);
34
34
  }
35
35
 
36
- const onChangeHandler = (e: ChangeEvent<HTMLSelectElement>): void => {
37
- onValueChange(transform(e.target.value)); // emit onChange with transformed value
36
+ const onChangeHandler = (e: FormEvent<HTMLSelectElement>): void => {
37
+ onValueChange(transform(e.currentTarget.value)); // emit onChange with transformed value
38
38
  };
39
39
 
40
40
  return (
41
- <div className={classes.container}>
42
- <select
43
- id={id}
44
- name={name}
45
- placeholder={disabled ? undefined : placeholder}
46
- value={(currentValue as string) ?? ''}
47
- onChange={onChangeHandler}
48
- className={clsx({ [classes.hasError]: error !== undefined })}
49
- disabled={disabled}
50
- >
51
- {placeholder && (
52
- <option value="" disabled>
53
- {disabled ? '' : placeholder}
54
- </option>
55
- )}
56
- {options.map((option) => (
57
- <option key={option.value} value={option.value}>
58
- {option.label}
59
- </option>
60
- ))}
61
- </select>
62
- {error !== undefined && <small>{error}</small>}
63
- </div>
41
+ <Select
42
+ id={id}
43
+ name={name}
44
+ disabled={disabled}
45
+ placeholder={placeholder}
46
+ onChange={onChangeHandler}
47
+ inlineMode={true}
48
+ error={error}
49
+ className={classes.container}
50
+ value={(currentValue as string) ?? ''}
51
+ options={options}
52
+ />
64
53
  );
65
54
  };
66
55
 
@@ -13,6 +13,8 @@ export interface SelectProps extends BaseFormControl, BaseSelectEvents {
13
13
  autoFocus?: boolean;
14
14
  /** Defines whether an empty option should be added as the first option (default: false) */
15
15
  addEmptyOption?: boolean;
16
+ /** Select placeholder */
17
+ placeholder?: string;
16
18
  }
17
19
 
18
20
  export const Select: React.FC<SelectProps> = ({
@@ -24,6 +26,7 @@ export const Select: React.FC<SelectProps> = ({
24
26
  error,
25
27
  autoFocus = false,
26
28
  addEmptyOption = false,
29
+ placeholder,
27
30
  onChange,
28
31
  onBlur,
29
32
  onFocus,
@@ -50,7 +53,15 @@ export const Select: React.FC<SelectProps> = ({
50
53
  onBlur={onBlur}
51
54
  onFocus={onFocus}
52
55
  >
53
- {addEmptyOption && <option value=""></option>}
56
+ {addEmptyOption &&
57
+ (placeholder === null || placeholder === undefined) && (
58
+ <option value=""></option>
59
+ )}
60
+ {placeholder && (
61
+ <option value="" disabled>
62
+ {disabled ? '' : placeholder}
63
+ </option>
64
+ )}
54
65
  {options.map((option) => (
55
66
  <option key={option.value} value={option.value}>
56
67
  {option.label}