@progress/kendo-themes-html 4.41.3-dev.3 → 5.0.0-alpha.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.
Files changed (62) hide show
  1. package/lib/jsx-runtime.js +112 -11
  2. package/package.json +6 -6
  3. package/src/autocomplete/README.md +24 -0
  4. package/src/autocomplete/autocomplete.jsx +160 -0
  5. package/src/autocomplete/index.js +1 -0
  6. package/src/button/README.md +22 -0
  7. package/src/button/button.jsx +3 -1
  8. package/src/checkbox/README.md +22 -0
  9. package/src/checkbox/checkbox.jsx +123 -0
  10. package/src/checkbox/index.js +1 -0
  11. package/src/colorpicker/README.md +30 -0
  12. package/src/colorpicker/color-preview.jsx +90 -0
  13. package/src/colorpicker/colorpicker.jsx +152 -0
  14. package/src/colorpicker/index.js +2 -0
  15. package/src/combobox/README.md +30 -0
  16. package/src/combobox/combobox.jsx +170 -0
  17. package/src/combobox/index.js +1 -0
  18. package/src/component.js +3 -3
  19. package/src/dropdownlist/README.md +30 -0
  20. package/src/dropdownlist/dropdownlist.jsx +190 -0
  21. package/src/dropdownlist/index.js +1 -0
  22. package/src/icon/icon.jsx +29 -7
  23. package/src/index.js +34 -5
  24. package/src/input/index.js +5 -0
  25. package/src/input/input-clear-value.jsx +30 -0
  26. package/src/input/input-inner-input.jsx +77 -0
  27. package/src/input/input-inner-span.jsx +91 -0
  28. package/src/input/input-inner-textarea.jsx +74 -0
  29. package/src/input/input-inner.jsx +3 -0
  30. package/src/input/input-loading-icon.jsx +28 -0
  31. package/src/input/input-prefix.jsx +1 -4
  32. package/src/input/input-suffix.jsx +1 -4
  33. package/src/input/input-validation-icon.jsx +37 -0
  34. package/src/input/input.jsx +90 -32
  35. package/src/input/picker.jsx +145 -0
  36. package/src/maskedtextbox/README.md +24 -0
  37. package/src/maskedtextbox/index.js +1 -0
  38. package/src/maskedtextbox/maskedtextbox.jsx +155 -0
  39. package/src/numerictextbox/README.md +42 -0
  40. package/src/numerictextbox/index.js +1 -0
  41. package/src/numerictextbox/numerictextbox.jsx +171 -0
  42. package/src/radio/README.md +21 -0
  43. package/src/radio/index.js +1 -0
  44. package/src/radio/radio.jsx +103 -0
  45. package/src/searchbox/README.md +26 -0
  46. package/src/searchbox/index.js +1 -0
  47. package/src/searchbox/searchbox.jsx +171 -0
  48. package/src/spinbutton/spinbutton.jsx +2 -2
  49. package/src/switch/README.md +28 -0
  50. package/src/switch/index.js +1 -0
  51. package/src/switch/switch.jsx +132 -0
  52. package/src/textarea/README.md +24 -0
  53. package/src/textarea/textarea.jsx +44 -65
  54. package/src/textbox/README.md +24 -0
  55. package/src/textbox/textbox.jsx +64 -66
  56. package/utils/styles.js +29 -2
  57. package/src/masked/README.md +0 -0
  58. package/src/masked/index.js +0 -1
  59. package/src/masked/masked.jsx +0 -139
  60. package/src/numeric/README.md +0 -0
  61. package/src/numeric/index.js +0 -1
  62. package/src/numeric/numeric.jsx +0 -149
@@ -1,5 +1,7 @@
1
+ /* eslint-disable no-new */
2
+ /* global kendo */
1
3
  import { isFunction, isArray, isObject } from '../utils/object';
2
- import { classNames } from '../utils/styles';
4
+ import { classNames, cssStyle } from '../utils/styles';
3
5
 
4
6
  const JSX_FRAGMENT = '#fragment';
5
7
  const JSX_TEXT = '#text';
@@ -8,7 +10,32 @@ const attrMap = {
8
10
  'class': 'className',
9
11
  themecolor: 'themeColor',
10
12
  fillmode: 'fillMode',
11
- showspinbuttons: 'showSpinButtons'
13
+
14
+ // Inputs
15
+ showtext: 'showText',
16
+ showvalue: 'showValue',
17
+ valueicon: 'valueIcon',
18
+ valueiconname: 'valueIconName',
19
+ showicon: 'showIcon',
20
+ iconposition: 'iconPosition',
21
+ iconname: 'iconName',
22
+ showarrow: 'showArrow',
23
+ arrowicon: 'arrowIcon',
24
+ arrowiconname: 'arrowIconName',
25
+ showarrowbutton: 'showArrowButton',
26
+ arrowbutton: 'arrowButton',
27
+ showdropdownbutton: 'showDropdownButton',
28
+ dropdownbutton: 'dropdownButton',
29
+ showspinbutton: 'showSpinButton',
30
+ spinbutton: 'spinButton',
31
+ showclearbutton: 'showClearButton',
32
+ clearbutton: 'clearButton',
33
+
34
+ // Switch
35
+ onlabel: 'onLabel',
36
+ offlabel: 'offLabel',
37
+ trackrounded: 'trackRounded',
38
+ thumbrounded: 'thumbRounded'
12
39
  };
13
40
 
14
41
  const booleanAttr = new Set([
@@ -19,18 +46,27 @@ const booleanAttr = new Set([
19
46
  'active',
20
47
  'disabled',
21
48
 
22
- 'selected',
23
-
24
49
  'checked',
25
50
  'indeterminate',
26
51
 
27
52
  'valid',
28
53
  'invalid',
54
+ 'required',
55
+
56
+ 'selected',
57
+ 'loading',
58
+
59
+ 'showText',
60
+ 'showValue',
61
+ 'showIcon',
62
+ 'showClearButton',
63
+ 'showSpinButton',
29
64
 
30
65
  'aria'
31
66
  ]);
32
67
 
33
68
  const skipAttr = new Set([
69
+ 'is',
34
70
  'aria',
35
71
  'legacy'
36
72
  ]);
@@ -38,6 +74,7 @@ const skipAttr = new Set([
38
74
  const setAttr = new Set([
39
75
  // Global
40
76
  'id',
77
+ 'style',
41
78
 
42
79
  // Related to forms
43
80
  'type',
@@ -46,8 +83,56 @@ const setAttr = new Set([
46
83
  'autocomplete'
47
84
  ]);
48
85
 
49
- function attrToProps( element ) {
50
- let attributes = element.attributes;
86
+ function htmlToProps( element ) {
87
+ let props = attrToProps( element.attributes );
88
+ let children = Array.from( element.childNodes );
89
+
90
+ props.children = [];
91
+
92
+ children.forEach( child => {
93
+ let childProps;
94
+ let nodeType = child.nodeType;
95
+
96
+ if ( nodeType === Node.TEXT_NODE ) {
97
+ let textContent = child.textContent.trim();
98
+
99
+ if ( textContent !== '' ) {
100
+ childProps = {
101
+ type: JSX_TEXT,
102
+ props: {
103
+ text: textContent
104
+ }
105
+ };
106
+
107
+ props.children.push( childProps );
108
+ }
109
+ }
110
+
111
+ if ( nodeType === Node.ELEMENT_NODE ) {
112
+ let componentName = child.getAttribute('is');
113
+ let component = kendo.Html[componentName];
114
+
115
+ if (isFunction( component )) {
116
+ component = new component( child );
117
+ childProps = component.render();
118
+ childProps._component = componentName;
119
+ } else {
120
+ childProps = {
121
+ type: child.nodeName,
122
+ props: htmlToProps( child )
123
+ };
124
+
125
+ }
126
+
127
+ props.children.push( childProps );
128
+ }
129
+
130
+ });
131
+
132
+ return props;
133
+ }
134
+
135
+ function attrToProps( attributes ) {
51
136
  let props = {};
52
137
 
53
138
  Array.from(attributes).forEach((attrObj) => {
@@ -58,8 +143,16 @@ function attrToProps( element ) {
58
143
  attrName = attrMap[attrName];
59
144
  }
60
145
 
61
- if (booleanAttr.has(attrName) && attrValue === '') {
62
- props[ attrName ] = true;
146
+ if (booleanAttr.has(attrName) && typeof attrValue === 'string') {
147
+ switch (attrValue) {
148
+ case '':
149
+ case 'true':
150
+ props[ attrName ] = true;
151
+ break;
152
+ default:
153
+ props[ attrName ] = false;
154
+ break;
155
+ }
63
156
  } else {
64
157
  props[ attrName ] = attrValue;
65
158
  }
@@ -144,14 +237,17 @@ function renderDOM( jsxNode, container = null ) {
144
237
  }
145
238
 
146
239
  props.className = classNames( props.className );
240
+ props.style = cssStyle( props.style );
147
241
 
148
242
  for (let [ attr, val ] of Object.entries(props)) {
149
243
  if (skipAttr.has(attr)) {
150
244
  continue;
151
245
  }
152
246
 
153
- if (setAttr.has(attr) && val !== '') {
154
- element.setAttribute( attr, val );
247
+ if (setAttr.has(attr)) {
248
+ if (val !== '') {
249
+ element.setAttribute( attr, val );
250
+ }
155
251
  } else {
156
252
  element[attr] = val;
157
253
  }
@@ -161,6 +257,10 @@ function renderDOM( jsxNode, container = null ) {
161
257
  element.append( children );
162
258
  } else {
163
259
  children.forEach( child => {
260
+ if (child === null) {
261
+ return;
262
+ }
263
+
164
264
  renderDOM( child, element );
165
265
  });
166
266
  }
@@ -177,5 +277,6 @@ export {
177
277
  jsxs,
178
278
  JSX_FRAGMENT as Fragment,
179
279
  renderDOM,
180
- attrToProps
280
+ attrToProps,
281
+ htmlToProps
181
282
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@progress/kendo-themes-html",
3
3
  "description": "A collection of HTML helpers used for developing Kendo UI themes",
4
- "version": "4.41.3-dev.3",
4
+ "version": "5.0.0-alpha.0",
5
5
  "author": "Progress",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
@@ -34,13 +34,13 @@
34
34
  "dev": "rollup -cw"
35
35
  },
36
36
  "devDependencies": {
37
- "@babel/core": "^7.15.5",
38
- "@babel/preset-env": "^7.15.6",
37
+ "@babel/core": "^7.15.8",
38
+ "@babel/preset-env": "^7.15.8",
39
39
  "@babel/preset-react": "^7.14.5",
40
40
  "@rollup/plugin-babel": "^5.3.0",
41
- "@rollup/plugin-commonjs": "^20.0.0",
41
+ "@rollup/plugin-commonjs": "^21.0.0",
42
42
  "@rollup/plugin-node-resolve": "^13.0.5",
43
- "rollup": "^2.57.0"
43
+ "rollup": "^2.58.0"
44
44
  },
45
- "gitHead": "fa5c9603df4ce625274a6478de659303b4d7b871"
45
+ "gitHead": "3fd060a3f688d79324c6e976857536557f71dc11"
46
46
  }
@@ -0,0 +1,24 @@
1
+ ```html
2
+ <!-- default rendering -->
3
+ <span class="k-autocomplete k-input k-input-md k-rounded-md k-input-solid">
4
+ <input type="text" class="k-input-inner" value="..." placeholder="..." />
5
+ </span>
6
+
7
+ <!-- canonical rendering -->
8
+ <span class="
9
+ k-autocomplete
10
+ k-input
11
+ k-input-${size}
12
+ k-rounded-${rounded}
13
+ k-input-${fillMode}
14
+
15
+ ${valid && 'k-valid'}
16
+ ${invalid && 'k-invalid'}
17
+ ${required && 'k-required'}
18
+ ${disabled && 'k-disabled'}
19
+ ">
20
+ <input type={type} class="k-input-inner" value={value} placeholder={placeholder} disabled={disabled} />
21
+ {valid && <span class="k-input-icon k-icon k-i-check"></span>}
22
+ {invalid && <span class="k-input-icon k-icon k-i-check"></span>}
23
+ </span>
24
+ ```
@@ -0,0 +1,160 @@
1
+ import { globalDefaultProps } from '../component';
2
+ import { Input, InputStatic, InputInnerInputStatic } from '../input/index';
3
+ import { InputValidationIconStatic, InputLoadingIconStatic, InputClearValueStatic } from '../input/index';
4
+
5
+ class Autocomplete extends Input {
6
+ render() {
7
+ return <AutocompleteStatic {...this.props} />;
8
+ }
9
+ }
10
+
11
+ function AutocompleteStatic(props) {
12
+
13
+ const {
14
+ className: ownClassName,
15
+
16
+ type,
17
+ value,
18
+ placeholder,
19
+ autocomplete,
20
+
21
+ prefix,
22
+ suffix,
23
+
24
+ size,
25
+ rounded,
26
+
27
+ fillMode,
28
+
29
+ hover,
30
+ focus,
31
+ valid,
32
+ invalid,
33
+ required,
34
+ disabled,
35
+
36
+ aria,
37
+ legacy,
38
+
39
+ ...htmlAttributes
40
+ } = props;
41
+
42
+ htmlAttributes.size = size;
43
+ htmlAttributes.rounded = rounded;
44
+ htmlAttributes.fillMode = fillMode;
45
+ htmlAttributes.hover = hover;
46
+ htmlAttributes.focus = focus;
47
+ htmlAttributes.valid = valid;
48
+ htmlAttributes.invalid = invalid;
49
+ htmlAttributes.required = required;
50
+ htmlAttributes.disabled = disabled;
51
+
52
+ const inputAttributes = {
53
+ type,
54
+ value,
55
+ placeholder,
56
+ autocomplete,
57
+
58
+ disabled
59
+ };
60
+
61
+ let autocompleteClasses = [
62
+ ownClassName,
63
+ 'k-autocomplete'
64
+ ];
65
+
66
+ let ariaAttr = aria
67
+ ? {}
68
+ : {};
69
+
70
+ if (legacy) {
71
+
72
+ let legacyClasses = [
73
+ ownClassName,
74
+ 'k-widget',
75
+ 'k-autocomplete',
76
+ {
77
+ 'k-state-hover': hover === true,
78
+ 'k-state-focused': focus === true,
79
+ 'k-state-invalid': invalid === true,
80
+ 'k-state-required': required === true,
81
+ 'k-state-disabled': disabled === true
82
+ }
83
+ ];
84
+
85
+ return (
86
+ <InputStatic className={legacyClasses} {...htmlAttributes}>
87
+ <input type={type} className="k-input" {...inputAttributes} />
88
+ <InputValidationIconStatic {...props} />
89
+ <InputLoadingIconStatic {...props} />
90
+ <InputClearValueStatic {...props} />
91
+ </InputStatic>
92
+ );
93
+ }
94
+
95
+ return (
96
+ <InputStatic className={autocompleteClasses} {...ariaAttr} {...htmlAttributes}>
97
+ {prefix}
98
+ <InputInnerInputStatic {...inputAttributes} />
99
+ {suffix}
100
+ <InputValidationIconStatic {...props} />
101
+ <InputLoadingIconStatic {...props} />
102
+ <InputClearValueStatic {...props} />
103
+ </InputStatic>
104
+ );
105
+ }
106
+
107
+ AutocompleteStatic.defaultProps = {
108
+ ...globalDefaultProps,
109
+
110
+ type: 'text',
111
+ value: '',
112
+ placeholder: '',
113
+ autocomplete: 'off',
114
+
115
+ showValidationIcon: true,
116
+ showLoadingIcon: true,
117
+ showClearButton: true,
118
+
119
+ size: 'medium',
120
+ rounded: 'medium',
121
+
122
+ fillMode: 'solid'
123
+ };
124
+
125
+ AutocompleteStatic.propTypes = {
126
+ children: typeof [],
127
+ className: typeof '',
128
+
129
+ type: typeof [ 'text', 'password' ],
130
+ value: typeof '',
131
+ placeholder: typeof '',
132
+ autocomplete: typeof [ 'on', 'off' ],
133
+
134
+ showValidationIcon: typeof true,
135
+ showLoadingIcon: typeof true,
136
+ showClearButton: typeof true,
137
+
138
+ prefix: typeof '#fragment',
139
+ suffix: typeof '#fragment',
140
+
141
+ size: typeof [ 'none', 'small', 'medium', 'large' ],
142
+ rounded: typeof [ 'none', 'small', 'medium', 'large', 'pill' ],
143
+
144
+ fillMode: typeof [ 'none', 'solid', 'flat', 'outline' ],
145
+
146
+ hover: typeof false,
147
+ focus: typeof false,
148
+ valid: typeof false,
149
+ invalid: typeof false,
150
+ loading: typeof false,
151
+ required: typeof false,
152
+ disabled: typeof false,
153
+
154
+ aria: typeof false,
155
+ legacy: typeof false,
156
+
157
+ htmlAttributes: typeof []
158
+ };
159
+
160
+ export { Autocomplete, AutocompleteStatic };
@@ -0,0 +1 @@
1
+ export * from './autocomplete.jsx';
@@ -0,0 +1,22 @@
1
+ ```html
2
+ <!-- default rendering -->
3
+ <button type="button" class="k-button k-button-md k-rounded-md k-button-solid k-button-solid-base">
4
+ <span class="k-button-text">Text</span>
5
+ </button>
6
+
7
+ <!-- canonical rendering -->
8
+ <button class="
9
+ k-button
10
+ ${text === '' && icon !== '' && 'k-icon-button'}
11
+ k-button-${size}
12
+ k-button-${shape}
13
+ k-rounded-${rounded}
14
+ k-button-${fillMode}
15
+ k-button-${fillMode}-${themeColor}
16
+
17
+ ${disabled && 'k-disabled'}
18
+ " type={type} disabled={disabled}>
19
+ {icon !== '' && <span class="k-button-icon k-icon k-i-${icon}"></span>}
20
+ {text !== '' && <span class="k-button-text">Button</span>}
21
+ </button>
22
+ ```
@@ -41,6 +41,8 @@ function ButtonStatic(props) {
41
41
  ...htmlAttributes
42
42
  } = props;
43
43
 
44
+ const isIconButton = Boolean( icon ) === true && Boolean( text ) === false;
45
+
44
46
  let buttonClasses = [
45
47
  ownClassName,
46
48
  'k-button',
@@ -55,7 +57,7 @@ function ButtonStatic(props) {
55
57
  'k-active': active === true,
56
58
  'k-selected': selected === true,
57
59
  'k-disabled': disabled === true,
58
- 'k-icon-button': Boolean( icon ) === true && Boolean( text ) === false
60
+ 'k-icon-button': isIconButton
59
61
  }
60
62
  ];
61
63
 
@@ -0,0 +1,22 @@
1
+ ```html
2
+ <!-- default rendering -->
3
+ <input type="checkbox" class="k-checkbox k-checkbox-md k-rounded-md" />
4
+
5
+ <!-- canonical rendering -->
6
+ <input type="checkbox"
7
+ class="
8
+ k-checkbox
9
+ k-checkbox-${size}
10
+ k-rounded-${rounded}
11
+
12
+ ${checked && 'k-checked'}
13
+ ${indeterminate && 'k-indeterminate'}
14
+
15
+ ${valid && 'k-valid'}
16
+ ${invalid && 'k-invalid'}
17
+ ${required && 'k-required'}
18
+ ${disabled && 'k-disabled'}
19
+ "
20
+ disabled={disabled}
21
+ />
22
+ ```
@@ -0,0 +1,123 @@
1
+ import * as styles from '../../utils/styles';
2
+ import { Component, globalDefaultProps } from '../component';
3
+
4
+ class Checkbox extends Component {
5
+ render() {
6
+ return <CheckboxStatic {...this.props} />;
7
+ }
8
+ }
9
+
10
+ function CheckboxStatic(props) {
11
+
12
+ const {
13
+ className: ownClassName,
14
+
15
+ checked,
16
+ indeterminate,
17
+
18
+ size,
19
+ rounded,
20
+
21
+ hover,
22
+ focus,
23
+ invalid,
24
+ required,
25
+ disabled,
26
+
27
+ aria,
28
+ legacy,
29
+
30
+ ...htmlAttributes
31
+
32
+ } = props;
33
+
34
+ htmlAttributes.checked = checked;
35
+ htmlAttributes.indeterminate = indeterminate;
36
+ htmlAttributes.required = required;
37
+ htmlAttributes.disabled = disabled;
38
+
39
+ let ariaAttr = aria
40
+ ? {}
41
+ : {};
42
+
43
+ const isIndeterminate = checked === false && indeterminate === true;
44
+
45
+ let checkboxClasses = [
46
+ ownClassName,
47
+ 'k-checkbox',
48
+ styles.sizeClass( size, 'k-checkbox' ),
49
+ styles.roundedClass( rounded ),
50
+ {
51
+ 'k-hover': hover === true,
52
+ 'k-focus': focus === true,
53
+ 'k-invalid': invalid === true,
54
+ 'k-required': required === true,
55
+ 'k-disabled': disabled === true,
56
+ 'k-checked': checked === true,
57
+ 'k-indeterminate': isIndeterminate,
58
+ }
59
+ ];
60
+
61
+ if (legacy) {
62
+
63
+ let legacyClasses = [
64
+ ownClassName,
65
+ 'k-checkbox',
66
+ {
67
+ 'k-state-hover': hover === true,
68
+ 'k-state-focus': focus === true,
69
+ 'k-state-invalid': invalid === true,
70
+ 'k-state-disabled': disabled === true,
71
+ 'k-checked': checked === true,
72
+ 'k-state-indeterminate': isIndeterminate,
73
+ }
74
+ ];
75
+
76
+ return (
77
+ <input type="checkbox" className={legacyClasses} {...ariaAttr} {...htmlAttributes} />
78
+ );
79
+ }
80
+
81
+ return (
82
+ <input type="checkbox" className={checkboxClasses} {...ariaAttr} {...htmlAttributes} />
83
+ );
84
+ }
85
+
86
+ CheckboxStatic.defaultProps = {
87
+ ...globalDefaultProps,
88
+
89
+ id: '',
90
+ name: '',
91
+
92
+ size: 'medium',
93
+ rounded: 'medium'
94
+ };
95
+
96
+ CheckboxStatic.propTypes = {
97
+ className: typeof '',
98
+
99
+ id: typeof '',
100
+ name: typeof '',
101
+ value: typeof '',
102
+
103
+ checked: typeof false,
104
+ indeterminate: typeof false,
105
+
106
+ size: typeof [ 'none', 'small', 'medium', 'large' ],
107
+ rounded: typeof [ 'none', 'small', 'medium', 'large' ],
108
+
109
+ hover: typeof false,
110
+ focus: typeof false,
111
+ valid: typeof false,
112
+ invalid: typeof false,
113
+ required: typeof false,
114
+ disabled: typeof false,
115
+
116
+ aria: typeof false,
117
+ legacy: typeof false,
118
+
119
+ htmlAttributes: typeof []
120
+ };
121
+
122
+
123
+ export { Checkbox, CheckboxStatic };
@@ -0,0 +1 @@
1
+ export * from './checkbox.jsx';
@@ -0,0 +1,30 @@
1
+ ```html
2
+ <!-- default rendering -->
3
+ <span class="k-combobox k-input k-input-md k-rounded-md k-input-solid">
4
+ <input type="text" class="k-input-inner" value="..." placeholder="..." />
5
+ <button type="button" class="k-input-button k-button k-icon-button k-button-md k-button-solid k-button-solid-base">
6
+ <span class="k-button-icon k-icon k-i-arrow-s"></span>
7
+ </button>
8
+ </span>
9
+
10
+ <!-- canonical rendering -->
11
+ <span class="
12
+ k-combobox
13
+ k-input
14
+ k-input-${size}
15
+ k-rounded-${rounded}
16
+ k-input-${fillMode}
17
+
18
+ ${valid && 'k-valid'}
19
+ ${invalid && 'k-invalid'}
20
+ ${required && 'k-required'}
21
+ ${disabled && 'k-disabled'}
22
+ ">
23
+ <input type={type} class="k-input-inner" value={value} placeholder={placeholder} disabled={disabled} />
24
+ {valid && <span class="k-input-icon k-icon k-i-check"></span>}
25
+ {invalid && <span class="k-input-icon k-icon k-i-check"></span>}
26
+ <button type="button" class="k-input-button k-button k-icon-button k-button-{size} k-button-{fillMode} k-button-{fillMode}-base">
27
+ <span class="k-button-icon k-icon k-i-arrow-s"></span>
28
+ </button>
29
+ </span>
30
+ ```