@xqmsg/ui-core 0.26.1 → 0.27.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.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.26.1",
2
+ "version": "0.27.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -76,6 +76,7 @@ interface StoryFormSchema {
76
76
  prop4?: string;
77
77
  prop5?: string;
78
78
  prop6?: boolean;
79
+ prop7?: number;
79
80
  }
80
81
 
81
82
  const onStubbedSubmit = () => null;
@@ -87,6 +88,7 @@ const storyFormDefaultValues: StoryFormSchema = {
87
88
  prop4: '',
88
89
  prop5: '',
89
90
  prop6: true,
91
+ prop7: undefined,
90
92
  };
91
93
 
92
94
  const storyFormSchema: Yup.SchemaOf<StoryFormSchema> = Yup.object().shape({
@@ -96,6 +98,7 @@ const storyFormSchema: Yup.SchemaOf<StoryFormSchema> = Yup.object().shape({
96
98
  prop4: Yup.string(),
97
99
  prop5: Yup.string(),
98
100
  prop6: Yup.boolean(),
101
+ prop7: Yup.number(),
99
102
  });
100
103
 
101
104
  export default meta;
@@ -113,10 +116,10 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
113
116
  return (
114
117
  <Form formHandler={formHandler}>
115
118
  <Input
116
- label="Pilled Text Input - scroll mode"
119
+ label={args.label + ' - Pilled Text - scroll mode'}
117
120
  name="recipients"
118
121
  inputType="pilled-text"
119
- placeholder="Enter email address..."
122
+ placeholder={args.placeholder ?? 'Enter email address...'}
120
123
  isInvalid={!!form.formState.errors['prop5']?.message}
121
124
  errorText={form.formState.errors['prop5']?.message}
122
125
  control={form.control}
@@ -129,10 +132,10 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
129
132
  overflowMode="scroll"
130
133
  />
131
134
  <Input
132
- label="Pilled Text - wrap mode"
135
+ label={args.label + ' - Pilled Text - wrap mod'}
133
136
  name="recipients"
134
137
  inputType="pilled-text"
135
- placeholder="Enter email address..."
138
+ placeholder={args.placeholder ?? 'Enter email address...'}
136
139
  isInvalid={!!form.formState.errors['prop5']?.message}
137
140
  errorText={form.formState.errors['prop5']?.message}
138
141
  control={form.control}
@@ -147,6 +150,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
147
150
  <Input
148
151
  {...args}
149
152
  inputType="multi-select"
153
+ label={args.label + ' - multi-select'}
150
154
  setValue={form.setValue}
151
155
  setError={form.setError}
152
156
  clearErrors={form.clearErrors}
@@ -156,6 +160,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
156
160
  />
157
161
  <Input
158
162
  {...args}
163
+ label={args.label + ' - multi-select'}
159
164
  inputType="multi-select"
160
165
  variant="mobile"
161
166
  setValue={form.setValue}
@@ -168,13 +173,31 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
168
173
  />
169
174
  <Input
170
175
  {...args}
176
+ label={args.label + ' - text'}
171
177
  inputType="text"
172
178
  name="prop3"
173
179
  onChange={e => form.setValue('prop3', e.target.value)}
174
180
  />
181
+ <Input
182
+ {...args}
183
+ label={args.label + ' - number'}
184
+ inputType="number"
185
+ name="prop7"
186
+ ariaLabel="number input"
187
+ min={0}
188
+ max={100}
189
+ step={1}
190
+ precision={2}
191
+ setValue={form.setValue}
192
+ setError={form.setError}
193
+ clearErrors={form.clearErrors}
194
+ isInvalid={!!form.formState.errors['prop7']?.message}
195
+ errorText={form.formState.errors['prop7']?.message}
196
+ />
175
197
  <Input
176
198
  {...args}
177
199
  inputType="text"
200
+ label={args.label + ' - text - variant mobile'}
178
201
  variant="mobile"
179
202
  name="prop3"
180
203
  onChange={e => form.setValue('prop3', e.target.value)}
@@ -182,6 +205,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
182
205
  <Input
183
206
  {...args}
184
207
  inputType="text"
208
+ label={args.label + ' - text - tooltip as react node'}
185
209
  name="prop3"
186
210
  onChange={e => form.setValue('prop3', e.target.value)}
187
211
  tooltipText={
@@ -194,6 +218,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
194
218
  <Input
195
219
  {...args}
196
220
  inputType="text"
221
+ label={args.label + ' - text - tooltip as string'}
197
222
  name="prop3"
198
223
  onChange={e => form.setValue('prop3', e.target.value)}
199
224
  tooltipText="test text"
@@ -201,12 +226,14 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
201
226
  <Input
202
227
  {...args}
203
228
  inputType="textarea"
229
+ label={args.label + ' - textarea'}
204
230
  name="prop2"
205
231
  onChange={e => form.setValue('prop2', e.target.value)}
206
232
  />
207
233
  <Input
208
234
  {...args}
209
235
  inputType="textarea"
236
+ label={args.label + ' - textarea - variant mobile'}
210
237
  variant="mobile"
211
238
  name="prop2"
212
239
  onChange={e => form.setValue('prop2', e.target.value)}
@@ -215,6 +242,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
215
242
  {...args}
216
243
  name="prop"
217
244
  inputType="pilled-text"
245
+ label={args.label + ' - Pilled Text'}
218
246
  setValue={form.setValue}
219
247
  setError={form.setError}
220
248
  clearErrors={form.clearErrors}
@@ -226,6 +254,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
226
254
  name="prop"
227
255
  variant="mobile"
228
256
  inputType="pilled-text"
257
+ label={args.label + ' - Pilled Text - variant mobile'}
229
258
  setValue={form.setValue}
230
259
  setError={form.setError}
231
260
  clearErrors={form.clearErrors}
@@ -235,6 +264,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
235
264
  <Input
236
265
  {...args}
237
266
  inputType="select"
267
+ label={args.label + ' - select'}
238
268
  setValue={form.setValue}
239
269
  name="prop4"
240
270
  defaultValue={'value1'}
@@ -242,6 +272,8 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
242
272
  <Input
243
273
  {...args}
244
274
  inputType="select"
275
+ label={args.label + ' - select - variant mobile'}
276
+ variant="mobile"
245
277
  setValue={form.setValue}
246
278
  name="prop4"
247
279
  defaultValue={'value1'}
@@ -250,6 +282,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
250
282
  <Input
251
283
  {...args}
252
284
  inputType="select"
285
+ label={args.label + ' - select - tooltip as react node'}
253
286
  setValue={form.setValue}
254
287
  name="prop4"
255
288
  defaultValue={'value1'}
@@ -263,6 +296,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
263
296
  />
264
297
  <SelectNative
265
298
  {...args}
299
+ label={args.label + ' - select native'}
266
300
  setValue={form.setValue}
267
301
  name="prop4"
268
302
  setError={form.setError}
@@ -280,6 +314,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
280
314
  {...args}
281
315
  name="prop6"
282
316
  inputType="switch"
317
+ label={args.label + ' - switch'}
283
318
  setValue={form.setValue}
284
319
  />
285
320
  <Input
@@ -287,6 +322,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
287
322
  name="prop6"
288
323
  variant="mobile"
289
324
  inputType="switch"
325
+ label={args.label + ' - switch - variant mobile'}
290
326
  setValue={form.setValue}
291
327
  />
292
328
  <Input
@@ -299,6 +335,7 @@ const Template: Story<InputProps<StoryFormSchema>> = args => {
299
335
  {...args}
300
336
  name="prop6"
301
337
  inputType="checkbox"
338
+ label={args.label + ' - checkbox - variant mobile'}
302
339
  variant="mobile"
303
340
  setValue={form.setValue}
304
341
  />
@@ -16,7 +16,8 @@ export type InputType =
16
16
  | 'textarea'
17
17
  | 'radio'
18
18
  | 'checkbox'
19
- | 'switch';
19
+ | 'switch'
20
+ | 'number';
20
21
 
21
22
  export type FieldOption = {
22
23
  label: string;
@@ -43,6 +44,9 @@ export interface InputFieldProps
43
44
  | 'ref'
44
45
  | 'onChange'
45
46
  | 'defaultValue'
47
+ | 'min'
48
+ | 'max'
49
+ | 'step'
46
50
  >,
47
51
  FieldProps,
48
52
  Partial<InputProps> {}
@@ -35,7 +35,7 @@ const StackedInput = React.forwardRef<HTMLInputElement, StackedInputProps>(
35
35
  {label}
36
36
  <Input
37
37
  {...props}
38
- placeholder={placeholder}
38
+ placeholder={props.placeholder ?? placeholder}
39
39
  type={type}
40
40
  isRequired={isRequired}
41
41
  ref={_ref}
@@ -0,0 +1,121 @@
1
+ import React from 'react';
2
+ import {
3
+ InputGroup,
4
+ NumberInput,
5
+ NumberInputField,
6
+ NumberInputStepper,
7
+ NumberIncrementStepper,
8
+ NumberDecrementStepper,
9
+ } from '@chakra-ui/react';
10
+ import { InputFieldProps } from '../InputTypes';
11
+
12
+ export interface StackedNumberInputProps extends InputFieldProps {
13
+ isRequired?: boolean;
14
+ leftElement?: React.ReactNode;
15
+ rightElement?: React.ReactNode;
16
+ variant?: string;
17
+ label?: string;
18
+ min?: number;
19
+ max?: number;
20
+ step?: number;
21
+ precision?: number;
22
+ showStepper?: boolean;
23
+ }
24
+
25
+ /**
26
+ * A functional React component that renders Chakra UI's `NumberInput` wrapped
27
+ * in an `InputGroup`, following the same pattern as `StackedInput`.
28
+ */
29
+ const StackedNumberInput = React.forwardRef<
30
+ HTMLInputElement,
31
+ StackedNumberInputProps
32
+ >(
33
+ (
34
+ {
35
+ name,
36
+ id,
37
+ placeholder,
38
+ isRequired,
39
+ isInvalid,
40
+ disabled,
41
+ value,
42
+ defaultValue,
43
+ min,
44
+ max,
45
+ step = 1,
46
+ precision = 0,
47
+ showStepper = false,
48
+ variant,
49
+ className,
50
+ label,
51
+ leftElement,
52
+ rightElement,
53
+ onChange,
54
+ onBlur,
55
+ },
56
+ ref
57
+ ) => {
58
+ const isMobile = variant === 'mobile';
59
+ const resolvedPlaceholder = isMobile && label ? label : placeholder;
60
+ const inputValue =
61
+ typeof value === 'string' || typeof value === 'number' ? value : undefined;
62
+ const normalizedValue =
63
+ precision === 0 && inputValue !== undefined && inputValue !== ''
64
+ ? Number.isNaN(Number(inputValue))
65
+ ? inputValue
66
+ : String(Math.round(Number(inputValue)))
67
+ : inputValue;
68
+
69
+ return (
70
+ <InputGroup>
71
+ {leftElement && leftElement}
72
+ {label && !isMobile && label}
73
+ <NumberInput
74
+ id={id}
75
+ name={name}
76
+ isRequired={isRequired}
77
+ isInvalid={isInvalid}
78
+ isDisabled={disabled}
79
+ value={normalizedValue}
80
+ defaultValue={defaultValue}
81
+ min={min}
82
+ max={max}
83
+ step={step}
84
+ precision={precision}
85
+ onChange={(_: string, valueAsNumber: number) =>
86
+ onChange?.({
87
+ target: {
88
+ name,
89
+ value: Number.isNaN(valueAsNumber)
90
+ ? ''
91
+ : precision === 0
92
+ ? String(Math.round(valueAsNumber))
93
+ : String(valueAsNumber),
94
+ },
95
+ } as React.ChangeEvent<HTMLInputElement>)
96
+ }
97
+ className={className}
98
+ variant={variant === 'mobile' ? 'mobile' : 'default'}
99
+ width="100%"
100
+ >
101
+ <NumberInputField
102
+ ref={ref}
103
+ placeholder={resolvedPlaceholder}
104
+ onBlur={onBlur}
105
+ />
106
+ {showStepper && (
107
+ <NumberInputStepper>
108
+ <NumberIncrementStepper />
109
+ <NumberDecrementStepper />
110
+ </NumberInputStepper>
111
+ )}
112
+ </NumberInput>
113
+ {rightElement && rightElement}
114
+ </InputGroup>
115
+ );
116
+ }
117
+ );
118
+
119
+ StackedNumberInput.displayName = 'StackedNumberInput';
120
+
121
+ export default StackedNumberInput;
@@ -22,6 +22,7 @@ import {
22
22
  UseFormSetValue,
23
23
  } from 'react-hook-form';
24
24
  import StackedMultiSelect from './StackedMultiSelect';
25
+ import StackedNumberInput from './StackedNumberInput/StackedNumberInput';
25
26
  import StackedPilledInput from './StackedPilledInput';
26
27
  import StackedSwitch from './StackedSwitch';
27
28
  import { Label } from './components/label';
@@ -32,7 +33,7 @@ export interface InputProps<T extends FieldValues = FieldValues>
32
33
  name: string;
33
34
  ariaLabel: string;
34
35
  placeholder?: string;
35
- defaultValue?: string;
36
+ defaultValue?: string | number;
36
37
  label?: string;
37
38
  className?: string;
38
39
  options?: FieldOption[];
@@ -54,6 +55,14 @@ export interface InputProps<T extends FieldValues = FieldValues>
54
55
  truncatePillLength?: number;
55
56
  searchable?: boolean;
56
57
  overflowMode?: 'scroll' | 'wrap';
58
+ /** Minimum value (numberInput only) */
59
+ min?: number;
60
+ /** Maximum value (numberInput only) */
61
+ max?: number;
62
+ /** Increment/decrement step (numberInput only) */
63
+ step?: number;
64
+ /** Number of decimal places (numberInput only) */
65
+ precision?: number;
57
66
  }
58
67
 
59
68
  /**
@@ -90,12 +99,16 @@ export function Input<T extends FieldValues>({
90
99
  truncatePillLength,
91
100
  searchable,
92
101
  overflowMode = 'scroll',
102
+ min,
103
+ max,
104
+ step,
105
+ precision,
93
106
  }: InputProps<T>) {
94
107
  function selectedInputField<T extends Element = Element>(
95
- onChange: ((e: ChangeEvent<T>) => void) | ((v?: string) => void),
108
+ onChange: ((e: ChangeEvent<T>) => void) | ((v?: string | number) => void),
96
109
  onBlur: () => void,
97
110
  ref: RefCallBack,
98
- value: string
111
+ value: string | number
99
112
  ) {
100
113
  switch (inputType) {
101
114
  case 'text':
@@ -121,6 +134,33 @@ export function Input<T extends FieldValues>({
121
134
  label={label as string}
122
135
  />
123
136
  );
137
+ case 'number':
138
+ return (
139
+ <StackedNumberInput
140
+ className={`input-${inputType} ${className ?? ''}`}
141
+ aria-label={ariaLabel}
142
+ name={name}
143
+ id={name}
144
+ placeholder={placeholder}
145
+ maxLength={maxLength}
146
+ isRequired={isRequired}
147
+ isInvalid={isInvalid}
148
+ onChange={onChange as (e: ChangeEvent) => void}
149
+ onBlur={onBlur}
150
+ ref={ref}
151
+ rightElement={rightElement}
152
+ leftElement={leftElement}
153
+ disabled={disabled}
154
+ defaultValue={defaultValue}
155
+ value={value}
156
+ variant={variant}
157
+ label={label as string}
158
+ min={min}
159
+ max={max}
160
+ step={step}
161
+ precision={precision}
162
+ />
163
+ );
124
164
  case 'radio':
125
165
  return (
126
166
  <StackedRadioGroup
@@ -39,6 +39,7 @@ const customXQChakraTheme = extendTheme({
39
39
  FormError,
40
40
  FormLabel,
41
41
  Input,
42
+ NumberInput: Input,
42
43
  Link,
43
44
  Menu,
44
45
  Modal,