@saas-ui/forms 1.0.0-rc.6 → 1.0.0-rc.9

Sign up to get free protection for your applications and to get access to all the features.
package/src/field.tsx CHANGED
@@ -22,14 +22,30 @@ import {
22
22
  Checkbox,
23
23
  Switch,
24
24
  useMergeRefs,
25
+ InputGroup,
26
+ InputProps,
27
+ TextareaProps,
28
+ SwitchProps,
29
+ CheckboxProps,
30
+ PinInputProps,
31
+ PinInputField,
32
+ HStack,
33
+ PinInput,
34
+ UsePinInputProps,
35
+ SystemProps,
25
36
  } from '@chakra-ui/react'
26
37
  import { __DEV__ } from '@chakra-ui/utils'
27
38
 
28
- import { NumberInput } from '@saas-ui/number-input'
29
- import { PasswordInput } from '@saas-ui/password-input'
30
- import { RadioInput } from '@saas-ui/radio'
31
- import { PinInput } from '@saas-ui/pin-input'
32
- import { Select, NativeSelect } from '@saas-ui/select'
39
+ import { NumberInput, NumberInputProps } from '@saas-ui/number-input'
40
+ import { PasswordInput, PasswordInputProps } from '@saas-ui/password-input'
41
+ import { RadioInput, RadioInputProps } from '@saas-ui/radio'
42
+
43
+ import {
44
+ Select,
45
+ SelectProps,
46
+ NativeSelect,
47
+ NativeSelectProps,
48
+ } from '@saas-ui/select'
33
49
  import { FocusableElement } from '@chakra-ui/utils'
34
50
 
35
51
  export interface Option {
@@ -43,19 +59,6 @@ export type FieldRules = Pick<
43
59
  'required' | 'min' | 'max' | 'maxLength' | 'minLength' | 'pattern'
44
60
  >
45
61
 
46
- export type FieldTypes =
47
- | 'text'
48
- | 'number'
49
- | 'password'
50
- | 'textarea'
51
- | 'select'
52
- | 'native-select'
53
- | 'checkbox'
54
- | 'radio'
55
- | 'switch'
56
- | 'pin'
57
- | string
58
-
59
62
  export interface FieldProps<
60
63
  TFieldValues extends FieldValues = FieldValues,
61
64
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
@@ -84,11 +87,6 @@ export interface FieldProps<
84
87
  'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
85
88
  >
86
89
  /**
87
- * Options used for selects and radio fields
88
- */
89
- options?: Option[]
90
- /**
91
- * The field type
92
90
  * Build-in types:
93
91
  * - text
94
92
  * - number
@@ -102,16 +100,15 @@ export interface FieldProps<
102
100
  * - pin
103
101
  *
104
102
  * Will default to a text field if there is no matching type.
105
- * @default 'text'
106
103
  */
107
- type?: FieldTypes
104
+ type?: string
108
105
  /**
109
106
  * The input placeholder
110
107
  */
111
108
  placeholder?: string
112
109
  }
113
110
 
114
- const inputTypes: Record<FieldTypes, any> = {}
111
+ const inputTypes: Record<string, React.FC<any>> = {}
115
112
 
116
113
  const defaultInputType = 'text'
117
114
 
@@ -160,11 +157,30 @@ if (__DEV__) {
160
157
  BaseField.displayName = 'BaseField'
161
158
  }
162
159
 
163
- export const Field = forwardRef(
160
+ export type As<Props = any> = React.ElementType<Props>
161
+
162
+ export type PropsOf<T extends As> = React.ComponentPropsWithoutRef<T> & {
163
+ type?: FieldTypes
164
+ }
165
+
166
+ /**
167
+ * Build-in types:
168
+ * - text
169
+ * - number
170
+ * - password
171
+ * - textarea
172
+ * - select
173
+ * - native-select
174
+ * - checkbox
175
+ * - radio
176
+ * - switch
177
+ * - pin
178
+ *
179
+ * Will default to a text field if there is no matching type.
180
+ */
181
+ export const Field = React.forwardRef(
164
182
  <TFieldValues extends FieldValues = FieldValues>(
165
- props: FieldProps<TFieldValues> & {
166
- [key: string]: unknown // Make sure attributes of custom components work. Need to change this to a global typedef at some point.
167
- },
183
+ props: FieldProps<TFieldValues> | FieldTypeProps,
168
184
  ref: React.ForwardedRef<FocusableElement>
169
185
  ) => {
170
186
  const { type = defaultInputType } = props
@@ -172,13 +188,14 @@ export const Field = forwardRef(
172
188
 
173
189
  return <InputComponent ref={ref} {...props} />
174
190
  }
175
- ) as <TFieldValues extends FieldValues>(
176
- props: FieldProps<TFieldValues> & {
177
- [key: string]: unknown
178
- } & {
179
- ref?: React.ForwardedRef<FocusableElement>
180
- }
181
- ) => React.ReactElement
191
+ ) as (<TFieldValues extends FieldValues>(
192
+ props: FieldProps<TFieldValues> &
193
+ FieldTypeProps & {
194
+ ref?: React.ForwardedRef<FocusableElement>
195
+ }
196
+ ) => React.ReactElement) & {
197
+ displayName?: string
198
+ }
182
199
 
183
200
  interface CreateFieldProps {
184
201
  displayName: string
@@ -190,7 +207,7 @@ const createField = (
190
207
  InputComponent: React.FC<any>,
191
208
  { displayName, hideLabel, BaseField }: CreateFieldProps
192
209
  ) => {
193
- const Field = forwardRef<FieldProps, typeof FormControl>((props, ref) => {
210
+ const Field = forwardRef((props, ref) => {
194
211
  const {
195
212
  id,
196
213
  name,
@@ -200,6 +217,7 @@ const createField = (
200
217
  isInvalid,
201
218
  isReadOnly,
202
219
  isRequired,
220
+ isOptional,
203
221
  rules,
204
222
  variant,
205
223
  ...inputProps
@@ -221,6 +239,7 @@ const createField = (
221
239
  isInvalid={isInvalid}
222
240
  isReadOnly={isReadOnly}
223
241
  isRequired={isRequired}
242
+ isOptional={isOptional}
224
243
  variant={variant}
225
244
  >
226
245
  <InputComponent
@@ -239,7 +258,7 @@ const createField = (
239
258
  return Field
240
259
  }
241
260
 
242
- export const withControlledInput = (InputComponent: any) => {
261
+ export const withControlledInput = (InputComponent: React.FC<any>) => {
243
262
  return forwardRef<FieldProps, typeof InputComponent>(
244
263
  ({ name, rules, ...inputProps }, ref) => {
245
264
  const { control } = useFormContext()
@@ -262,7 +281,7 @@ export const withControlledInput = (InputComponent: any) => {
262
281
  )
263
282
  }
264
283
 
265
- export const withUncontrolledInput = (InputComponent: any) => {
284
+ export const withUncontrolledInput = (InputComponent: React.FC<any>) => {
266
285
  return forwardRef<FieldProps, typeof InputComponent>(
267
286
  ({ name, rules, ...inputProps }, ref) => {
268
287
  const { register } = useFormContext()
@@ -294,9 +313,9 @@ export interface RegisterFieldTypeOptions {
294
313
  * @param options.isControlled Set this to true if this is a controlled field.
295
314
  * @param options.hideLabel Hide the field label, for example for the checkbox field.
296
315
  */
297
- export const registerFieldType = (
316
+ export const registerFieldType = <T extends object>(
298
317
  type: string,
299
- component: React.FC<any>,
318
+ component: React.FC<T>,
300
319
  options?: RegisterFieldTypeOptions
301
320
  ) => {
302
321
  let InputComponent
@@ -313,25 +332,59 @@ export const registerFieldType = (
313
332
  .join('')}Field`,
314
333
  hideLabel: options?.hideLabel,
315
334
  BaseField: options?.BaseField || BaseField,
316
- })
335
+ }) as React.FC<T & FieldProps>
317
336
 
318
337
  inputTypes[type] = Field
319
338
 
320
339
  return Field
321
340
  }
322
341
 
323
- export const InputField = registerFieldType(
342
+ export interface InputFieldProps extends InputProps {
343
+ type?: string
344
+ leftAddon?: React.ReactNode
345
+ rightAddon?: React.ReactNode
346
+ }
347
+
348
+ export const InputField = registerFieldType<InputFieldProps>(
324
349
  'text',
325
- forwardRef(({ type = 'text', ...rest }, ref) => {
326
- return <Input type={type} {...rest} ref={ref} />
350
+ forwardRef(({ type = 'text', leftAddon, rightAddon, size, ...rest }, ref) => {
351
+ const input = <Input type={type} size={size} {...rest} ref={ref} />
352
+ if (leftAddon || rightAddon) {
353
+ return (
354
+ <InputGroup size={size}>
355
+ {leftAddon}
356
+ {input}
357
+ {rightAddon}
358
+ </InputGroup>
359
+ )
360
+ }
361
+ return input
327
362
  })
328
363
  )
329
- export const NumberInputField = registerFieldType('number', NumberInput, {
330
- isControlled: true,
331
- })
332
- export const PasswordInputFIeld = registerFieldType('password', PasswordInput)
333
- export const TextareaField = registerFieldType('textarea', Textarea)
334
- export const SwitchField = registerFieldType(
364
+
365
+ export interface NumberInputFieldProps extends NumberInputProps {
366
+ type: 'number'
367
+ }
368
+
369
+ export const NumberInputField = registerFieldType<NumberInputFieldProps>(
370
+ 'number',
371
+ NumberInput,
372
+ {
373
+ isControlled: true,
374
+ }
375
+ )
376
+
377
+ export const PasswordInputField = registerFieldType<PasswordInputProps>(
378
+ 'password',
379
+ forwardRef((props, ref) => <PasswordInput ref={ref} {...props} />)
380
+ )
381
+
382
+ export const TextareaField = registerFieldType<TextareaProps>(
383
+ 'textarea',
384
+ Textarea
385
+ )
386
+
387
+ export const SwitchField = registerFieldType<SwitchProps>(
335
388
  'switch',
336
389
  forwardRef(({ type, ...rest }, ref) => {
337
390
  return <Switch {...rest} ref={ref} />
@@ -340,32 +393,94 @@ export const SwitchField = registerFieldType(
340
393
  isControlled: true,
341
394
  }
342
395
  )
343
- export const SelectField = registerFieldType('select', Select, {
396
+
397
+ export const SelectField = registerFieldType<SelectProps>('select', Select, {
344
398
  isControlled: true,
345
399
  })
346
- export const CheckboxField = registerFieldType(
400
+
401
+ export const CheckboxField = registerFieldType<CheckboxProps>(
347
402
  'checkbox',
348
- forwardRef(
349
- ({ label, type, ...props }: { label?: string; type: string }, ref) => {
350
- return (
351
- <Checkbox ref={ref} {...props}>
352
- {label}
353
- </Checkbox>
354
- )
355
- }
356
- ),
403
+ forwardRef(({ label, type, ...props }, ref) => {
404
+ return (
405
+ <Checkbox ref={ref} {...props}>
406
+ {label}
407
+ </Checkbox>
408
+ )
409
+ }),
357
410
  {
358
411
  hideLabel: true,
359
412
  }
360
413
  )
361
- export const RadioField = registerFieldType('radio', RadioInput, {
362
- isControlled: true,
363
- })
364
- export const PinField = registerFieldType('pin', PinInput, {
365
- isControlled: true,
366
- })
367
- export const NativeSelectField = registerFieldType(
414
+
415
+ export const RadioField = registerFieldType<RadioInputProps>(
416
+ 'radio',
417
+ RadioInput,
418
+ {
419
+ isControlled: true,
420
+ }
421
+ )
422
+
423
+ export const NativeSelectField = registerFieldType<NativeSelectProps>(
368
424
  'native-select',
369
425
  NativeSelect,
370
426
  { isControlled: true }
371
427
  )
428
+
429
+ export interface PinFieldProps extends Omit<UsePinInputProps, 'type'> {
430
+ pinLength?: number
431
+ pinType?: 'alphanumeric' | 'number'
432
+ spacing?: SystemProps['margin']
433
+ }
434
+
435
+ export const PinField = registerFieldType<PinFieldProps>(
436
+ 'pin',
437
+ forwardRef((props, ref) => {
438
+ const { pinLength = 4, pinType, spacing, ...inputProps } = props
439
+
440
+ const inputs: React.ReactNode[] = []
441
+ for (let i = 0; i < pinLength; i++) {
442
+ inputs.push(<PinInputField key={i} ref={ref} />)
443
+ }
444
+
445
+ return (
446
+ <HStack spacing={spacing}>
447
+ <PinInput {...inputProps} type={pinType}>
448
+ {inputs}
449
+ </PinInput>
450
+ </HStack>
451
+ )
452
+ }),
453
+ {
454
+ isControlled: true,
455
+ }
456
+ )
457
+
458
+ const fieldTypes = {
459
+ text: InputField,
460
+ email: InputField,
461
+ url: InputField,
462
+ phone: InputField,
463
+ number: NumberInputField,
464
+ password: PasswordInputField,
465
+ textarea: TextareaField,
466
+ switch: SwitchField,
467
+ checkbox: CheckboxField,
468
+ radio: RadioField,
469
+ pin: PinField,
470
+ select: SelectField,
471
+ 'native-select': NativeSelectField,
472
+ }
473
+
474
+ type FieldTypes = typeof fieldTypes
475
+
476
+ type FieldType<Props = any> = React.ElementType<Props>
477
+
478
+ type TypeProps<P extends FieldType, T> = React.ComponentPropsWithoutRef<P> & {
479
+ type: T
480
+ }
481
+
482
+ type FieldTypeProps =
483
+ | {
484
+ [Property in keyof FieldTypes]: TypeProps<FieldTypes[Property], Property>
485
+ }[keyof FieldTypes]
486
+ | { type?: string }
package/src/form.tsx CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  ResolverResult,
17
17
  } from 'react-hook-form'
18
18
  import { objectFieldResolver, FieldResolver } from './field-resolver'
19
- import { css } from '@emotion/react'
20
19
 
21
20
  export type { UseFormReturn, FieldValues, SubmitHandler }
22
21
 
package/src/step-form.tsx CHANGED
@@ -53,7 +53,7 @@ export const StepForm = React.forwardRef(
53
53
  )
54
54
  }
55
55
  ) as <TFieldValues extends FieldValues>(
56
- props: FormProps<TFieldValues> & {
56
+ props: StepFormProps<TFieldValues> & {
57
57
  ref?: React.ForwardedRef<UseFormReturn<TFieldValues>>
58
58
  }
59
59
  ) => React.ReactElement
@@ -76,7 +76,7 @@ export interface FormStepOptions {
76
76
  export const FormStepper: React.FC<StepperStepsProps> = (props) => {
77
77
  const { activeIndex, setIndex } = useStepperContext()
78
78
 
79
- const { children } = props
79
+ const { children, orientation } = props
80
80
 
81
81
  const elements = React.Children.map(children, (child) => {
82
82
  if (React.isValidElement(child) && child?.type === FormStep) {
@@ -99,7 +99,11 @@ export const FormStepper: React.FC<StepperStepsProps> = (props) => {
99
99
  }, [])
100
100
 
101
101
  return (
102
- <StepperContainer step={activeIndex} onChange={onChange}>
102
+ <StepperContainer
103
+ orientation={orientation}
104
+ step={activeIndex}
105
+ onChange={onChange}
106
+ >
103
107
  <StepperSteps mb="4" {...props}>
104
108
  {elements}
105
109
  </StepperSteps>
@@ -37,7 +37,7 @@ export const SubmitButton = forwardRef<SubmitButtonProps, 'button'>(
37
37
  <Button
38
38
  type="submit"
39
39
  isLoading={formState.isSubmitting}
40
- isPrimary
40
+ colorScheme="primary"
41
41
  ref={ref}
42
42
  isDisabled={isDisabled}
43
43
  {...rest}
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react'
2
2
  import { FieldValues, SubmitHandler } from 'react-hook-form'
3
- import { createContext } from '@chakra-ui/react-utils'
3
+ import { createContext, MaybeRenderProp } from '@chakra-ui/react-utils'
4
4
  import {
5
5
  useStepper,
6
6
  useStep,
@@ -34,9 +34,7 @@ export interface UseStepFormProps<
34
34
  TFieldValues extends FieldValues = FieldValues
35
35
  > extends Omit<UseStepperProps, 'onChange'>,
36
36
  Omit<FormProps<TFieldValues>, 'children'> {
37
- children:
38
- | React.ReactNode
39
- | ((stepper: UseStepFormReturn<TFieldValues>) => React.ReactElement)
37
+ children: MaybeRenderProp<UseStepFormReturn<TFieldValues>>
40
38
  }
41
39
 
42
40
  export interface UseStepFormReturn<