@saas-ui/forms 2.0.0-next.3 → 2.0.0-next.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +53 -6
  3. package/dist/ajv/index.d.ts +358 -11
  4. package/dist/ajv/index.js +7 -9
  5. package/dist/ajv/index.js.map +1 -1
  6. package/dist/ajv/index.mjs +7 -10
  7. package/dist/ajv/index.mjs.map +1 -1
  8. package/dist/index.d.ts +448 -247
  9. package/dist/index.js +707 -682
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +691 -666
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/yup/index.d.ts +580 -21
  14. package/dist/yup/index.js +6 -10
  15. package/dist/yup/index.js.map +1 -1
  16. package/dist/yup/index.mjs +4 -8
  17. package/dist/yup/index.mjs.map +1 -1
  18. package/dist/zod/index.d.ts +580 -11
  19. package/dist/zod/index.js +5 -0
  20. package/dist/zod/index.js.map +1 -1
  21. package/dist/zod/index.mjs +5 -1
  22. package/dist/zod/index.mjs.map +1 -1
  23. package/package.json +19 -10
  24. package/src/array-field.tsx +82 -45
  25. package/src/auto-form.tsx +7 -3
  26. package/src/base-field.tsx +54 -0
  27. package/src/create-field.tsx +144 -0
  28. package/src/create-form.tsx +54 -0
  29. package/src/default-fields.tsx +163 -0
  30. package/src/display-field.tsx +9 -11
  31. package/src/display-if.tsx +20 -13
  32. package/src/field-resolver.ts +10 -8
  33. package/src/field.tsx +18 -445
  34. package/src/fields-context.tsx +23 -0
  35. package/src/fields.tsx +34 -21
  36. package/src/form-context.tsx +84 -0
  37. package/src/form.tsx +69 -52
  38. package/src/index.ts +44 -4
  39. package/src/input-right-button/input-right-button.stories.tsx +1 -1
  40. package/src/input-right-button/input-right-button.tsx +0 -2
  41. package/src/layout.tsx +16 -11
  42. package/src/number-input/number-input.tsx +9 -5
  43. package/src/object-field.tsx +13 -8
  44. package/src/password-input/password-input.stories.tsx +23 -2
  45. package/src/password-input/password-input.tsx +6 -6
  46. package/src/pin-input/pin-input.tsx +1 -5
  47. package/src/radio/radio-input.stories.tsx +1 -1
  48. package/src/radio/radio-input.tsx +12 -10
  49. package/src/select/native-select.tsx +1 -4
  50. package/src/select/select-context.tsx +130 -0
  51. package/src/select/select.stories.tsx +116 -85
  52. package/src/select/select.test.tsx +1 -1
  53. package/src/select/select.tsx +160 -146
  54. package/src/step-form.tsx +29 -11
  55. package/src/submit-button.tsx +5 -1
  56. package/src/types.ts +144 -0
  57. package/src/use-array-field.tsx +9 -3
  58. package/src/utils.ts +23 -1
  59. package/src/watch-field.tsx +2 -6
  60. /package/src/radio/{radio.test.tsx → radio-input.test.tsx} +0 -0
@@ -0,0 +1,130 @@
1
+ import {
2
+ HTMLChakraProps,
3
+ useControllableState,
4
+ useFormControl,
5
+ } from '@chakra-ui/react'
6
+ import { createContext } from '@chakra-ui/react-utils'
7
+ import React from 'react'
8
+ import { FieldOptions } from '../types'
9
+ import { mapOptions } from '../utils'
10
+ import { SelectOption } from './select'
11
+
12
+ export const [SelectProvider, useSelectContext] = createContext<
13
+ ReturnType<typeof useSelect>
14
+ >({
15
+ strict: true,
16
+ })
17
+
18
+ export interface SelectOptions {
19
+ /**
20
+ * The name of the input field in a native form.
21
+ */
22
+ name: string
23
+ /**
24
+ * The value of the select field.
25
+ */
26
+ value?: string | string[]
27
+ /**
28
+ * The initial value of the select field.
29
+ */
30
+ defaultValue?: string | string[]
31
+ /**
32
+ * The callback invoked when the value of the select field changes.
33
+ * @param value The value of the select field.
34
+ */
35
+ onChange?: (value: string | string[]) => void
36
+ /**
37
+ * The placeholder text when there's no value.
38
+ */
39
+ placeholder?: string
40
+ /**
41
+ * If `true`, the select will be disabled.
42
+ */
43
+ isDisabled?: boolean
44
+ /**
45
+ * An array of options
46
+ * If you leave this empty the children prop will be rendered.
47
+ */
48
+ options?: FieldOptions<SelectOption>
49
+ /**
50
+ * Enable multiple select.
51
+ */
52
+ multiple?: boolean
53
+ /**
54
+ * The function used to render the value of the select field.
55
+ * @param value The value of the select field.
56
+ * @returns The rendered value.
57
+ */
58
+ renderValue?: (value: string | string[]) => React.ReactNode
59
+ }
60
+
61
+ export const useSelect = (props: SelectOptions) => {
62
+ const {
63
+ name,
64
+ value,
65
+ defaultValue,
66
+ onChange,
67
+ multiple,
68
+ placeholder,
69
+ options: optionsProp,
70
+ isDisabled,
71
+ renderValue = (value) =>
72
+ typeof value === 'string' ? value : value?.join(', '),
73
+ } = props
74
+ const [currentValue, setCurrentValue] = useControllableState({
75
+ value,
76
+ defaultValue,
77
+ onChange,
78
+ })
79
+
80
+ const controlProps = useFormControl({ name } as HTMLChakraProps<'input'>)
81
+
82
+ const options = React.useMemo(
83
+ () => optionsProp && mapOptions(optionsProp),
84
+ [optionsProp]
85
+ )
86
+
87
+ const handleChange = (value: string | string[]) => {
88
+ setCurrentValue(value)
89
+ }
90
+
91
+ const getDisplayValue = React.useCallback(
92
+ (value: string) => {
93
+ if (!options) {
94
+ return value
95
+ }
96
+
97
+ for (const option of options) {
98
+ if (option.value === value) {
99
+ return option.label || option.value
100
+ }
101
+ }
102
+
103
+ return value
104
+ },
105
+ [options]
106
+ )
107
+
108
+ const displayValue = React.useMemo(
109
+ () =>
110
+ currentValue
111
+ ? (Array.isArray(currentValue) ? currentValue : [currentValue]).map(
112
+ getDisplayValue
113
+ )
114
+ : [],
115
+ [currentValue, getDisplayValue]
116
+ )
117
+
118
+ return {
119
+ defaultValue,
120
+ value: currentValue,
121
+ displayValue,
122
+ renderValue,
123
+ onChange: handleChange,
124
+ options,
125
+ multiple,
126
+ controlProps,
127
+ placeholder,
128
+ isDisabled,
129
+ }
130
+ }
@@ -8,15 +8,23 @@ import {
8
8
  } from '@chakra-ui/react'
9
9
  import * as React from 'react'
10
10
 
11
- import { ComponentStory } from '@storybook/react'
11
+ import { StoryFn } from '@storybook/react'
12
12
 
13
- import { Select } from './select'
13
+ import { Select, SelectButton, SelectList, SelectOption } from './select'
14
14
  import { NativeSelect } from './native-select'
15
15
 
16
16
  import { FiSmile } from 'react-icons/fi'
17
17
 
18
+ const Template: StoryFn<typeof Select> = (args) => (
19
+ <Select placeholder="Select an option..." {...args}>
20
+ <SelectButton />
21
+ <SelectList />
22
+ </Select>
23
+ )
24
+
18
25
  export default {
19
26
  title: 'Components/Forms/Select',
27
+ component: Template,
20
28
  decorators: [
21
29
  (Story: any) => (
22
30
  <Container mt="40px" maxW="320px">
@@ -34,111 +42,134 @@ const getOptions = (length = 6) =>
34
42
 
35
43
  const options = getOptions()
36
44
 
37
- const Template: ComponentStory<typeof Select> = (args) => (
38
- <Select placeholder="Select an option..." {...args} />
39
- )
40
-
41
- export const Basic = Template.bind({})
42
- Basic.args = {
43
- name: 'select',
44
- options,
45
+ export const Basic = {
46
+ args: {
47
+ name: 'select',
48
+ options,
49
+ },
45
50
  }
46
51
 
47
- export const DefaultValue = Template.bind({})
48
- DefaultValue.args = {
49
- name: 'select',
50
- options,
51
- defaultValue: 1,
52
+ export const DefaultValue = {
53
+ args: {
54
+ name: 'select',
55
+ options,
56
+ defaultValue: '1',
57
+ },
52
58
  }
53
59
 
54
- export const Placeholder = Template.bind({})
55
- Placeholder.args = {
56
- name: 'select',
57
- options,
58
- placeholder: 'Select an option...',
60
+ export const Placeholder = {
61
+ args: {
62
+ name: 'select',
63
+ options,
64
+ placeholder: 'Select an option...',
65
+ },
59
66
  }
60
-
61
- export const Disabled = Template.bind({})
62
- Disabled.args = {
63
- name: 'select',
64
- options,
65
- placeholder: 'Disabled.',
66
- isDisabled: true,
67
+ export const Disabled = {
68
+ args: {
69
+ name: 'select',
70
+ options,
71
+ placeholder: 'Disabled.',
72
+ isDisabled: true,
73
+ },
67
74
  }
68
75
 
69
- export const Multi = Template.bind({})
70
- Multi.args = {
71
- name: 'select',
72
- options,
73
- placeholder: 'Multiple.',
74
- multiple: true,
76
+ export const Multi = {
77
+ args: {
78
+ name: 'select',
79
+ options,
80
+ placeholder: 'Multiple.',
81
+ multiple: true,
82
+ },
75
83
  }
76
84
 
77
- export const MultiWithDefaultValue = Template.bind({})
78
- MultiWithDefaultValue.args = {
79
- name: 'select',
80
- options,
81
- placeholder: 'Select an option...',
82
- multiple: true,
83
- defaultValue: ['1'],
85
+ export const MultiWithDefaultValue = {
86
+ args: {
87
+ name: 'select',
88
+ options,
89
+ placeholder: 'Select an option...',
90
+ multiple: true,
91
+ defaultValue: ['1'],
92
+ },
84
93
  }
85
94
 
86
- export const MultiWithTags = Template.bind({})
87
- MultiWithTags.args = {
88
- name: 'select',
89
- options,
90
- placeholder: 'Select options...',
91
- multiple: true,
92
- renderValue: (selected) => {
93
- if (selected?.length) {
94
- return (
95
- <Wrap py="1">
96
- {selected.map((value) => (
97
- <WrapItem>
98
- <Tag>{value}</Tag>
99
- </WrapItem>
100
- ))}
101
- </Wrap>
102
- )
103
- }
95
+ export const MultiWithTags = {
96
+ args: {
97
+ name: 'select',
98
+ options,
99
+ placeholder: 'Select options...',
100
+ multiple: true,
101
+ renderValue: (selected) => {
102
+ if (selected?.length) {
103
+ return (
104
+ <Wrap py="1">
105
+ {selected.map((value) => (
106
+ <WrapItem>
107
+ <Tag variant="solid">{value}</Tag>
108
+ </WrapItem>
109
+ ))}
110
+ </Wrap>
111
+ )
112
+ }
113
+ },
104
114
  },
105
115
  }
106
116
 
107
- export const WithIcons = Template.bind({})
108
- WithIcons.args = {
109
- name: 'select',
110
- options,
111
- value: 1,
112
- leftIcon: <Icon as={FiSmile} />,
117
+ export const Test = {
118
+ render: () => (
119
+ <Tag variant="outline" colorScheme="teal">
120
+ Test
121
+ </Tag>
122
+ ),
113
123
  }
114
124
 
115
- export const MaxHeight = Template.bind({})
116
- MaxHeight.args = {
117
- name: 'select',
118
- options: getOptions(100),
125
+ export const WithIcons = {
126
+ render: (args) => (
127
+ <Select placeholder="Select an option..." {...args}>
128
+ <SelectButton leftIcon={<Icon as={FiSmile} />} />
129
+ <SelectList />
130
+ </Select>
131
+ ),
132
+ args: {
133
+ name: 'select',
134
+ options,
135
+ value: '1',
136
+ },
119
137
  }
120
138
 
121
- export const WithChildren = () => {
122
- return (
123
- <Select name="select" value="1">
124
- <MenuItemOption value="1">Option 1</MenuItemOption>
125
- <MenuItemOption value="2">Option 1</MenuItemOption>
139
+ export const MaxHeight = {
140
+ args: {
141
+ name: 'select',
142
+ options: getOptions(100),
143
+ },
144
+ }
145
+
146
+ export const WithChildren = {
147
+ render: () => (
148
+ <Select name="select" defaultValue="1">
149
+ <SelectButton />
150
+ <SelectList>
151
+ <SelectOption value="1">Option 1</SelectOption>
152
+ <SelectOption value="2">Option 2</SelectOption>
153
+ </SelectList>
126
154
  </Select>
127
- )
155
+ ),
128
156
  }
129
157
 
130
- export const WithEmptyOption = () => {
131
- return (
132
- <Select name="select" value="1">
133
- <MenuItemOption value="">None</MenuItemOption>
134
- <MenuItemOption value="1">Option 1</MenuItemOption>
135
- <MenuItemOption value="2">Option 1</MenuItemOption>
158
+ export const WithEmptyOption = {
159
+ render: () => (
160
+ <Select name="select" defaultValue="1">
161
+ <SelectButton />
162
+ <SelectList>
163
+ <SelectOption value="">None</SelectOption>
164
+ <SelectOption value="1">Option 1</SelectOption>
165
+ <SelectOption value="2">Option 2</SelectOption>
166
+ </SelectList>
136
167
  </Select>
137
- )
168
+ ),
138
169
  }
139
170
 
140
- export const WithNativeSelect = () => (
141
- <>
171
+ export const WithNativeSelect = {
172
+ render: () => (
142
173
  <NativeSelect name="select" options={options} aria-label="Select" />
143
- </>
144
- )
174
+ ),
175
+ }
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react'
2
2
 
3
3
  import { render, testStories } from '@saas-ui/test-utils'
4
- import * as stories from '../stories/select.stories'
4
+ import * as stories from './select.stories'
5
5
 
6
6
  const { MaxHeight, ...rest } = stories
7
7