@indico-data/design-system 2.33.0 → 2.34.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 (36) hide show
  1. package/.storybook/preview-head.html +0 -4
  2. package/lib/index.css +95 -3
  3. package/lib/index.d.ts +8 -8
  4. package/lib/index.esm.css +95 -3
  5. package/lib/index.esm.js +1 -1
  6. package/lib/index.esm.js.map +1 -1
  7. package/lib/index.js +1 -1
  8. package/lib/index.js.map +1 -1
  9. package/lib/src/components/forms/input/Input.d.ts +5 -3
  10. package/lib/src/components/forms/numberInput/NumberInput.d.ts +11 -0
  11. package/lib/src/components/forms/numberInput/NumberInput.stories.d.ts +12 -0
  12. package/lib/src/components/forms/numberInput/__tests__/NumberInput.test.d.ts +1 -0
  13. package/lib/src/components/forms/numberInput/index.d.ts +1 -0
  14. package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +3 -3
  15. package/lib/src/components/forms/subcomponents/Label.d.ts +1 -3
  16. package/lib/src/components/forms/textarea/Textarea.d.ts +3 -3
  17. package/lib/src/storybook/formArgTypes.d.ts +5 -0
  18. package/package.json +1 -1
  19. package/src/components/button/styles/Button.scss +0 -4
  20. package/src/components/forms/input/Input.stories.tsx +2 -96
  21. package/src/components/forms/input/Input.tsx +5 -3
  22. package/src/components/forms/numberInput/NumberInput.mdx +32 -0
  23. package/src/components/forms/numberInput/NumberInput.stories.tsx +215 -0
  24. package/src/components/forms/numberInput/NumberInput.tsx +90 -0
  25. package/src/components/forms/numberInput/__tests__/NumberInput.test.tsx +94 -0
  26. package/src/components/forms/numberInput/index.ts +1 -0
  27. package/src/components/forms/numberInput/styles/NumberInput.scss +108 -0
  28. package/src/components/forms/passwordInput/PasswordInput.stories.tsx +3 -70
  29. package/src/components/forms/passwordInput/PasswordInput.tsx +2 -2
  30. package/src/components/forms/subcomponents/Label.tsx +1 -4
  31. package/src/components/forms/textarea/Textarea.stories.tsx +3 -68
  32. package/src/components/forms/textarea/Textarea.tsx +2 -2
  33. package/src/storybook/formArgTypes.ts +152 -0
  34. package/src/styles/index.scss +1 -0
  35. package/lib/src/storybook/labelArgTypes.d.ts +0 -3
  36. package/src/storybook/labelArgTypes.ts +0 -50
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { IconName } from '@/types';
3
- import { WithLabelProps } from '../subcomponents/Label';
4
- export interface InputProps extends WithLabelProps {
3
+ import { LabelProps } from '../subcomponents/Label';
4
+ export interface BaseInputProps {
5
5
  value?: string | undefined;
6
6
  placeholder?: string;
7
7
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
@@ -13,5 +13,7 @@ export interface InputProps extends WithLabelProps {
13
13
  className?: string;
14
14
  defaultValue?: string;
15
15
  }
16
- declare const LabeledInput: React.ForwardRefExoticComponent<Omit<InputProps & React.RefAttributes<HTMLInputElement> & WithLabelProps, "ref"> & React.RefAttributes<any>>;
16
+ export interface InputProps extends BaseInputProps, LabelProps {
17
+ }
18
+ declare const LabeledInput: React.ForwardRefExoticComponent<Omit<InputProps & React.RefAttributes<HTMLInputElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
17
19
  export { LabeledInput as Input };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { LabelProps } from '../subcomponents/Label';
3
+ import { BaseInputProps } from '../input/Input';
4
+ export interface NumberInputProps extends Omit<BaseInputProps, 'value'>, LabelProps {
5
+ value?: number | '';
6
+ min?: number;
7
+ max?: number;
8
+ step?: number;
9
+ }
10
+ declare const LabeledNumberInput: React.ForwardRefExoticComponent<Omit<NumberInputProps & React.RefAttributes<HTMLInputElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
11
+ export { LabeledNumberInput as NumberInput };
@@ -0,0 +1,12 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { NumberInput } from './NumberInput';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ type Story = StoryObj<typeof NumberInput>;
6
+ export declare const Default: Story;
7
+ export declare const Errors: Story;
8
+ export declare const HiddenLabel: Story;
9
+ export declare const HelpText: Story;
10
+ export declare const Clearable: Story;
11
+ export declare const Icon: Story;
12
+ export declare const Required: Story;
@@ -0,0 +1 @@
1
+ export { NumberInput } from './NumberInput';
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import { WithLabelProps } from '../subcomponents/Label';
3
- export interface PasswordInputProps extends WithLabelProps {
2
+ import { LabelProps } from '../subcomponents/Label';
3
+ export interface PasswordInputProps extends LabelProps {
4
4
  ref?: React.LegacyRef<HTMLInputElement>;
5
5
  value?: string | undefined;
6
6
  placeholder?: string;
@@ -11,5 +11,5 @@ export interface PasswordInputProps extends WithLabelProps {
11
11
  hasShowPassword?: boolean;
12
12
  defaultValue?: string;
13
13
  }
14
- declare const LabeledPasswordInput: React.ForwardRefExoticComponent<Omit<Omit<PasswordInputProps, "ref"> & React.RefAttributes<HTMLInputElement> & WithLabelProps, "ref"> & React.RefAttributes<any>>;
14
+ declare const LabeledPasswordInput: React.ForwardRefExoticComponent<Omit<Omit<PasswordInputProps, "ref"> & React.RefAttributes<HTMLInputElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
15
15
  export { LabeledPasswordInput as PasswordInput };
@@ -3,9 +3,7 @@ export interface LabelProps {
3
3
  label: string;
4
4
  name: string;
5
5
  isRequired?: boolean;
6
- }
7
- export interface WithLabelProps extends LabelProps {
8
6
  hasHiddenLabel?: boolean;
9
7
  }
10
8
  export declare const Label: ({ label, name, isRequired }: LabelProps) => import("react/jsx-runtime").JSX.Element;
11
- export declare function withLabel<P extends object>(WrappedComponent: React.ComponentType<P>): React.ForwardRefExoticComponent<React.PropsWithoutRef<P & WithLabelProps> & React.RefAttributes<any>>;
9
+ export declare function withLabel<P extends object>(WrappedComponent: React.ComponentType<P>): React.ForwardRefExoticComponent<React.PropsWithoutRef<P & LabelProps> & React.RefAttributes<any>>;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import { WithLabelProps } from '../subcomponents/Label';
3
- export interface TextareaProps extends WithLabelProps {
2
+ import { LabelProps } from '../subcomponents/Label';
3
+ export interface TextareaProps extends LabelProps {
4
4
  ref?: React.LegacyRef<HTMLTextAreaElement>;
5
5
  placeholder?: string;
6
6
  value?: string | undefined;
@@ -17,5 +17,5 @@ export interface TextareaProps extends WithLabelProps {
17
17
  autofocus?: boolean;
18
18
  defaultValue?: string;
19
19
  }
20
- declare const LabeledTextarea: React.ForwardRefExoticComponent<Omit<Omit<TextareaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement> & WithLabelProps, "ref"> & React.RefAttributes<any>>;
20
+ declare const LabeledTextarea: React.ForwardRefExoticComponent<Omit<Omit<TextareaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
21
21
  export { LabeledTextarea as Textarea };
@@ -0,0 +1,5 @@
1
+ import { ArgTypes } from '@storybook/react';
2
+ declare const labelArgTypes: ArgTypes;
3
+ declare const baseInputArgTypes: ArgTypes;
4
+ declare const inputArgTypes: ArgTypes;
5
+ export { labelArgTypes, baseInputArgTypes, inputArgTypes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.33.0",
3
+ "version": "2.34.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -40,10 +40,6 @@
40
40
  .btn--icon-only {
41
41
  padding-left: var(--pf-padding-2);
42
42
  padding-right: var(--pf-padding-2);
43
-
44
- &.btn--md {
45
- height: 32px;
46
- }
47
43
  }
48
44
 
49
45
  @mixin button-variant($color-name) {
@@ -1,107 +1,13 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
  import { Input, InputProps } from './Input';
3
3
  import { SetStateAction, useEffect, useState } from 'react';
4
- import { iconNames } from 'build/generated/iconTypes';
5
- import labelArgTypes from '@/storybook/labelArgTypes';
4
+ import { inputArgTypes, labelArgTypes } from '@/storybook/formArgTypes';
6
5
 
7
6
  const meta: Meta = {
8
7
  title: 'Forms/Input',
9
8
  component: Input,
10
9
  argTypes: {
11
- onChange: {
12
- control: false,
13
- description: 'onChange event handler',
14
- table: {
15
- category: 'Callbacks',
16
- type: {
17
- summary: '(e: React.ChangeEvent<HTMLInputElement>) => void',
18
- },
19
- },
20
- type: { name: 'function', required: true },
21
- action: 'onChange',
22
- },
23
- placeholder: {
24
- control: 'text',
25
- description: 'The placeholder for the input field',
26
- table: {
27
- category: 'Props',
28
- type: {
29
- summary: 'string',
30
- },
31
- },
32
- type: { name: 'string', required: false },
33
- },
34
- value: {
35
- control: 'text',
36
- description: 'The value for the input field',
37
- table: {
38
- category: 'Props',
39
- type: {
40
- summary: 'string',
41
- },
42
- },
43
- type: { name: 'string', required: false },
44
- },
45
- isDisabled: {
46
- control: 'boolean',
47
- description: 'Toggles the disabled state of the input',
48
- table: {
49
- category: 'Props',
50
- type: {
51
- summary: 'boolean',
52
- },
53
- },
54
- defaultValue: { summary: 'false' },
55
- },
56
- errorMessage: {
57
- control: 'text',
58
- description: 'Error message',
59
- table: {
60
- category: 'Props',
61
- type: {
62
- summary: 'string',
63
- },
64
- },
65
- defaultValue: { summary: '' },
66
- },
67
- helpText: {
68
- control: 'text',
69
- description: 'The help text for the input field',
70
- table: {
71
- category: 'Props',
72
- type: {
73
- summary: 'string',
74
- },
75
- },
76
- },
77
- iconName: {
78
- control: 'select',
79
- options: iconNames,
80
- description: 'Adds an icon to the left hand side of the input field',
81
- table: {
82
- category: 'Props',
83
- type: {
84
- summary: 'string',
85
- },
86
- },
87
- defaultValue: { summary: '' },
88
- },
89
- isClearable: {
90
- control: 'boolean',
91
- description: 'Adds a clear x icon to the right hand side of the input field',
92
- table: {
93
- category: 'Props',
94
- type: {
95
- summary: 'boolean',
96
- },
97
- },
98
- defaultValue: { summary: 'false' },
99
- },
100
- ref: {
101
- table: {
102
- disable: true,
103
- },
104
- },
10
+ ...inputArgTypes,
105
11
  ...labelArgTypes,
106
12
  },
107
13
  };
@@ -3,10 +3,10 @@ import classNames from 'classnames';
3
3
 
4
4
  import { Icon } from '@/components/icons';
5
5
  import { IconName } from '@/types';
6
- import { withLabel, WithLabelProps } from '../subcomponents/Label';
6
+ import { withLabel, LabelProps } from '../subcomponents/Label';
7
7
  import { DisplayFormError } from '../subcomponents/DisplayFormError';
8
8
 
9
- export interface InputProps extends WithLabelProps {
9
+ export interface BaseInputProps {
10
10
  value?: string | undefined;
11
11
  placeholder?: string;
12
12
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
@@ -19,6 +19,8 @@ export interface InputProps extends WithLabelProps {
19
19
  defaultValue?: string;
20
20
  }
21
21
 
22
+ export interface InputProps extends BaseInputProps, LabelProps {}
23
+
22
24
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
23
25
  (
24
26
  {
@@ -70,7 +72,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
70
72
  aria-required={isRequired}
71
73
  {...rest}
72
74
  />
73
- {isClearable && (
75
+ {isClearable && !isDisabled && (
74
76
  <Icon
75
77
  name="x-close"
76
78
  data-testid={`${name}-clearable-icon`}
@@ -0,0 +1,32 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/blocks';
2
+ import * as NumberInput from './NumberInput.stories';
3
+
4
+ <Meta title="Forms/Number Input" name="Number Input" />
5
+
6
+ # Number Input
7
+
8
+ The `NumberInput` component is a specialized input field designed to handle numeric values. It supports features such as min and max constraints, step increments, and optional icons or clear buttons. This component is intended to be used within forms where numeric input is required.
9
+
10
+ <Canvas
11
+ of={NumberInput.Default}
12
+ source={{
13
+ code: `
14
+ <NumberInput
15
+ label="Enter a number"
16
+ name="number_input"
17
+ placeholder="Please enter a value"
18
+ helpText="This Is Help Text"
19
+ isRequired
20
+ hasHiddenLabel={false}
21
+ isClearable
22
+ iconName="fa-calculator"
23
+ isDisabled={false}
24
+ errorMessage=""
25
+ value={0}
26
+ onChange={() => {}}
27
+ />
28
+ `,
29
+ }}
30
+ />
31
+
32
+ <Controls of={NumberInput.Default} />
@@ -0,0 +1,215 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { NumberInput, NumberInputProps } from './NumberInput';
3
+ import { useEffect, useState } from 'react';
4
+ import { iconNames } from 'build/generated/iconTypes';
5
+ import { labelArgTypes, inputArgTypes } from '@/storybook/formArgTypes';
6
+
7
+ const meta: Meta = {
8
+ title: 'Forms/Number Input',
9
+ component: NumberInput,
10
+ argTypes: {
11
+ ...labelArgTypes,
12
+ ...inputArgTypes,
13
+ value: {
14
+ control: 'number',
15
+ description: 'The value for the input field',
16
+ table: {
17
+ category: 'Props',
18
+ type: {
19
+ summary: 'number | ""',
20
+ },
21
+ },
22
+ type: { name: 'number', required: false },
23
+ },
24
+ min: {
25
+ control: 'number',
26
+ description: 'The minimum value for the input field',
27
+ table: {
28
+ category: 'Props',
29
+ type: {
30
+ summary: 'number',
31
+ },
32
+ },
33
+ type: { name: 'number', required: false },
34
+ },
35
+ max: {
36
+ control: 'number',
37
+ description: 'The maximum value for the input field',
38
+ table: {
39
+ category: 'Props',
40
+ type: {
41
+ summary: 'number',
42
+ },
43
+ },
44
+ type: { name: 'number', required: false },
45
+ },
46
+ step: {
47
+ control: 'number',
48
+ description: 'The step value for the input field',
49
+ table: {
50
+ category: 'Props',
51
+ type: {
52
+ summary: 'number',
53
+ },
54
+ },
55
+ type: { name: 'number', required: false },
56
+ },
57
+ },
58
+ };
59
+
60
+ export default meta;
61
+
62
+ type Story = StoryObj<typeof NumberInput>;
63
+
64
+ const defaultArgs: NumberInputProps = {
65
+ label: 'Enter a number',
66
+ name: 'number_input',
67
+ placeholder: 'Please enter a value',
68
+ onChange: () => {},
69
+ };
70
+
71
+ export const Default: Story = {
72
+ args: {
73
+ ...defaultArgs,
74
+ isRequired: true,
75
+ iconName: 'fa-calculator',
76
+ helpText: 'This Is Help Text',
77
+ isClearable: true,
78
+ hasHiddenLabel: false,
79
+ isDisabled: false,
80
+ errorMessage: '',
81
+ },
82
+ render: (args) => {
83
+ const [value, setValue] = useState<number | ''>(0);
84
+
85
+ useEffect(() => {
86
+ setValue(args.value || '');
87
+ }, [args.value]);
88
+
89
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
90
+ setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
91
+ };
92
+
93
+ return <NumberInput {...args} value={value} onChange={handleChange} />;
94
+ },
95
+ };
96
+
97
+ export const Errors: Story = {
98
+ args: {
99
+ ...defaultArgs,
100
+ errorMessage: 'This field requires a number.',
101
+ },
102
+ render: (args) => {
103
+ const [value, setValue] = useState<number | ''>(0);
104
+
105
+ useEffect(() => {
106
+ setValue(args.value || '');
107
+ }, [args.value]);
108
+
109
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
110
+ setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
111
+ };
112
+
113
+ return <NumberInput {...args} value={value} onChange={handleChange} />;
114
+ },
115
+ };
116
+
117
+ export const HiddenLabel: Story = {
118
+ args: {
119
+ ...defaultArgs,
120
+ hasHiddenLabel: true,
121
+ },
122
+ render: (args) => {
123
+ const [value, setValue] = useState<number | ''>(0);
124
+
125
+ useEffect(() => {
126
+ setValue(args.value || '');
127
+ }, [args.value]);
128
+
129
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
130
+ setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
131
+ };
132
+
133
+ return <NumberInput {...args} value={value} onChange={handleChange} />;
134
+ },
135
+ };
136
+
137
+ export const HelpText: Story = {
138
+ args: {
139
+ ...defaultArgs,
140
+ helpText: 'In order to submit the form, this field is required.',
141
+ },
142
+ render: (args) => {
143
+ const [value, setValue] = useState<number | ''>(0);
144
+
145
+ useEffect(() => {
146
+ setValue(args.value || '');
147
+ }, [args.value]);
148
+
149
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
150
+ setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
151
+ };
152
+
153
+ return <NumberInput {...args} value={value} onChange={handleChange} />;
154
+ },
155
+ };
156
+
157
+ export const Clearable: Story = {
158
+ args: {
159
+ ...defaultArgs,
160
+ isClearable: true,
161
+ },
162
+ render: (args) => {
163
+ const [value, setValue] = useState<number | ''>(args.value || '');
164
+
165
+ useEffect(() => {
166
+ setValue(args.value || '');
167
+ }, [args.value]);
168
+
169
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
170
+ setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
171
+ };
172
+
173
+ return <NumberInput {...args} value={value} onChange={handleChange} />;
174
+ },
175
+ };
176
+
177
+ export const Icon: Story = {
178
+ args: {
179
+ ...defaultArgs,
180
+ iconName: 'fa-calculator',
181
+ },
182
+ render: (args) => {
183
+ const [value, setValue] = useState<number | ''>(0);
184
+
185
+ useEffect(() => {
186
+ setValue(args.value || '');
187
+ }, [args.value]);
188
+
189
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
190
+ setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
191
+ };
192
+
193
+ return <NumberInput {...args} value={value} onChange={handleChange} />;
194
+ },
195
+ };
196
+
197
+ export const Required: Story = {
198
+ args: {
199
+ ...defaultArgs,
200
+ isRequired: true,
201
+ },
202
+ render: (args) => {
203
+ const [value, setValue] = useState<number | ''>(0);
204
+
205
+ useEffect(() => {
206
+ setValue(args.value || '');
207
+ }, [args.value]);
208
+
209
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
210
+ setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
211
+ };
212
+
213
+ return <NumberInput {...args} value={value} onChange={handleChange} />;
214
+ },
215
+ };
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ import { Icon } from '@/components/icons';
5
+ import { LabelProps, withLabel } from '../subcomponents/Label';
6
+ import { DisplayFormError } from '../subcomponents/DisplayFormError';
7
+ import { BaseInputProps } from '../input/Input';
8
+
9
+ export interface NumberInputProps extends Omit<BaseInputProps, 'value'>, LabelProps {
10
+ value?: number | '';
11
+ min?: number;
12
+ max?: number;
13
+ step?: number;
14
+ }
15
+
16
+ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
17
+ (
18
+ {
19
+ name,
20
+ onChange,
21
+ isRequired,
22
+ isDisabled,
23
+ isClearable,
24
+ errorMessage,
25
+ helpText,
26
+ iconName,
27
+ className,
28
+ ...rest
29
+ },
30
+ ref,
31
+ ) => {
32
+ const hasErrors = errorMessage && errorMessage.length > 0;
33
+
34
+ const handleClear = () => {
35
+ onChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
36
+ };
37
+
38
+ const inputClasses = classNames(
39
+ 'number-input',
40
+ {
41
+ error: hasErrors,
42
+ 'number-input--has-icon': iconName,
43
+ 'number-input--is-clearable': isClearable,
44
+ },
45
+ className,
46
+ );
47
+
48
+ return (
49
+ <>
50
+ <div className="number-input-wrapper">
51
+ {iconName && (
52
+ <Icon name={iconName} data-testid={`${name}-embedded-icon`} className="embedded-icon" />
53
+ )}
54
+ <input
55
+ ref={ref}
56
+ data-testid={`form-number-input-${name}`}
57
+ name={name}
58
+ type="number"
59
+ disabled={isDisabled}
60
+ onChange={onChange}
61
+ className={inputClasses}
62
+ aria-invalid={hasErrors ? true : undefined}
63
+ aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
64
+ aria-required={isRequired}
65
+ {...rest}
66
+ />
67
+ {isClearable && !isDisabled && (
68
+ <Icon
69
+ name="x-close"
70
+ data-testid={`${name}-clearable-icon`}
71
+ onClick={handleClear}
72
+ className="clearable-icon"
73
+ size="sm"
74
+ />
75
+ )}
76
+ </div>
77
+ {hasErrors && <DisplayFormError message={errorMessage} />}
78
+ {helpText && !hasErrors && (
79
+ <div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
80
+ {helpText}
81
+ </div>
82
+ )}
83
+ </>
84
+ );
85
+ },
86
+ );
87
+
88
+ const LabeledNumberInput = withLabel(NumberInput);
89
+ LabeledNumberInput.displayName = 'NumberInput';
90
+ export { LabeledNumberInput as NumberInput };