@overmap-ai/forms 1.0.15 → 1.0.17-master.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 (284) hide show
  1. package/.husky/pre-commit +6 -0
  2. package/.prettierrc.json +10 -0
  3. package/.storybook/StoryDecorator.tsx +22 -0
  4. package/.storybook/main.ts +20 -0
  5. package/.storybook/palettes/green.css +66 -0
  6. package/.storybook/palettes/red.css +66 -0
  7. package/.storybook/preview.css +39 -0
  8. package/.storybook/preview.tsx +31 -0
  9. package/.storybook/tailwind-theme/accentPalette.css +181 -0
  10. package/.storybook/tailwind-theme/backgrounds.css +11 -0
  11. package/.storybook/tailwind-theme/basePalette.css +178 -0
  12. package/dev/publish-alpha.sh +13 -0
  13. package/dev/publish-patch.sh +3 -0
  14. package/dist/ColorPicker/ColorPicker.d.ts +10 -0
  15. package/dist/ColorPicker/index.d.ts +1 -0
  16. package/dist/FileBadge/FileBadge.d.ts +7 -0
  17. package/dist/FileBadge/index.d.ts +1 -0
  18. package/dist/FileCard/FileCard.d.ts +8 -0
  19. package/dist/FileCard/index.d.ts +1 -0
  20. package/dist/FileIcon/FileIcon.d.ts +4 -0
  21. package/dist/FileIcon/index.d.ts +1 -0
  22. package/dist/FileViewer/FileViewerProvider.d.ts +2 -0
  23. package/dist/FileViewer/context.d.ts +4 -0
  24. package/dist/FileViewer/index.d.ts +3 -0
  25. package/dist/FileViewer/typings.d.ts +5 -0
  26. package/dist/ImageCard/ImageCard.d.ts +9 -0
  27. package/dist/ImageCard/index.d.ts +1 -0
  28. package/dist/ImageMarkup/ImageMarkup.d.ts +14 -0
  29. package/dist/ImageMarkup/index.d.ts +1 -0
  30. package/dist/ImageViewer/ImageViewer.d.ts +7 -0
  31. package/dist/ImageViewer/constants.d.ts +1 -0
  32. package/dist/ImageViewer/index.d.ts +2 -0
  33. package/dist/PDFViewer/PDFViewer.d.ts +7 -0
  34. package/dist/PDFViewer/constants.d.ts +1 -0
  35. package/dist/PDFViewer/index.d.ts +2 -0
  36. package/dist/SpreadsheetViewer/SpreadsheetViewer.d.ts +7 -0
  37. package/dist/SpreadsheetViewer/constants.d.ts +1 -0
  38. package/dist/SpreadsheetViewer/index.d.ts +2 -0
  39. package/dist/{builder → forms/builder}/DropDispatch.d.ts +2 -2
  40. package/dist/forms/builder/FieldActions.d.ts +13 -0
  41. package/dist/forms/builder/FieldBuilder.d.ts +10 -0
  42. package/dist/forms/builder/FieldSectionWithActions.d.ts +10 -0
  43. package/dist/forms/builder/FieldWithActions.d.ts +9 -0
  44. package/dist/forms/builder/FieldsEditor.d.ts +5 -0
  45. package/dist/forms/builder/FormBuilder.d.ts +25 -0
  46. package/dist/forms/builder/constants.d.ts +18 -0
  47. package/dist/forms/builder/hooks.d.ts +7 -0
  48. package/dist/forms/builder/index.d.ts +2 -0
  49. package/dist/{builder → forms/builder}/typings.d.ts +2 -1
  50. package/dist/forms/builder/utils.d.ts +23 -0
  51. package/dist/forms/constants.d.ts +3 -0
  52. package/dist/forms/constantsJsx.d.ts +9 -0
  53. package/dist/{fields → forms/fields}/BaseField/BaseField.d.ts +23 -10
  54. package/dist/forms/fields/BaseField/hooks.d.ts +388 -0
  55. package/dist/forms/fields/BaseField/index.d.ts +4 -0
  56. package/dist/{fields → forms/fields}/BaseField/layouts.d.ts +11 -5
  57. package/dist/{fields → forms/fields}/BaseField/typings.d.ts +2 -2
  58. package/dist/{fields → forms/fields}/BooleanField/BooleanField.d.ts +12 -6
  59. package/dist/forms/fields/BooleanField/BooleanInput.d.ts +3 -0
  60. package/dist/forms/fields/BooleanField/index.d.ts +2 -0
  61. package/dist/{fields → forms/fields}/CustomField/CustomField.d.ts +12 -6
  62. package/dist/{fields → forms/fields}/CustomField/FieldInputClonerField/FieldInputCloner.d.ts +2 -3
  63. package/dist/{fields → forms/fields}/CustomField/FieldInputClonerField/FieldInputClonerField.d.ts +3 -3
  64. package/dist/forms/fields/CustomField/FieldInputClonerField/index.d.ts +3 -0
  65. package/dist/forms/fields/CustomField/FieldInputClonerField/typings.d.ts +5 -0
  66. package/dist/forms/fields/CustomField/index.d.ts +1 -0
  67. package/dist/forms/fields/DateField/DateField.d.ts +22 -0
  68. package/dist/forms/fields/DateField/DateInput.d.ts +3 -0
  69. package/dist/forms/fields/DateField/index.d.ts +2 -0
  70. package/dist/{fields → forms/fields}/FieldSection/FieldSection.d.ts +13 -9
  71. package/dist/forms/fields/FieldSection/FieldSectionLayout.d.ts +6 -0
  72. package/dist/forms/fields/FieldSection/index.d.ts +1 -0
  73. package/dist/forms/fields/MultiStringField/MultiStringField.d.ts +40 -0
  74. package/dist/forms/fields/MultiStringField/MultiStringInput.d.ts +7 -0
  75. package/dist/forms/fields/MultiStringField/index.d.ts +2 -0
  76. package/dist/{fields → forms/fields}/NumberField/NumberField.d.ts +27 -10
  77. package/dist/forms/fields/NumberField/NumberInput.d.ts +3 -0
  78. package/dist/forms/fields/NumberField/index.d.ts +2 -0
  79. package/dist/forms/fields/QrField/QrField.d.ts +21 -0
  80. package/dist/forms/fields/QrField/QrInput.d.ts +9 -0
  81. package/dist/forms/fields/QrField/index.d.ts +2 -0
  82. package/dist/{fields → forms/fields}/SelectField/BaseSelectField.d.ts +12 -5
  83. package/dist/{fields → forms/fields}/SelectField/MultiSelectField.d.ts +13 -6
  84. package/dist/forms/fields/SelectField/MultiSelectInput.d.ts +3 -0
  85. package/dist/{fields → forms/fields}/SelectField/SelectField.d.ts +14 -7
  86. package/dist/forms/fields/SelectField/SelectInput.d.ts +3 -0
  87. package/dist/forms/fields/SelectField/index.d.ts +4 -0
  88. package/dist/forms/fields/StringOrTextFields/StringField/StringField.d.ts +26 -0
  89. package/dist/forms/fields/StringOrTextFields/StringField/StringInput.d.ts +3 -0
  90. package/dist/forms/fields/StringOrTextFields/StringField/index.d.ts +2 -0
  91. package/dist/{fields → forms/fields}/StringOrTextFields/StringOrTextField.d.ts +13 -8
  92. package/dist/forms/fields/StringOrTextFields/TextField/TextField.d.ts +22 -0
  93. package/dist/forms/fields/StringOrTextFields/TextField/TextInput.d.ts +3 -0
  94. package/dist/forms/fields/StringOrTextFields/TextField/index.d.ts +2 -0
  95. package/dist/forms/fields/StringOrTextFields/index.d.ts +2 -0
  96. package/dist/{fields → forms/fields}/UploadField/UploadField.d.ts +24 -9
  97. package/dist/forms/fields/UploadField/UploadInput.d.ts +3 -0
  98. package/dist/forms/fields/UploadField/index.d.ts +2 -0
  99. package/dist/forms/fields/constants.d.ts +106 -0
  100. package/dist/forms/fields/hooks.d.ts +6 -0
  101. package/dist/forms/fields/index.d.ts +12 -0
  102. package/dist/{fields → forms/fields}/typings.d.ts +9 -6
  103. package/dist/{fields → forms/fields}/utils.d.ts +7 -3
  104. package/dist/forms/index.d.ts +5 -0
  105. package/dist/{renderer → forms/renderer}/FormRenderer/FormRenderer.d.ts +4 -3
  106. package/dist/{renderer → forms/renderer}/PatchForm/Field.d.ts +5 -3
  107. package/dist/{renderer → forms/renderer}/PatchForm/Provider.d.ts +8 -4
  108. package/dist/forms/renderer/PatchForm/index.d.ts +2 -0
  109. package/dist/forms/renderer/index.d.ts +2 -0
  110. package/dist/forms/typings.d.ts +105 -0
  111. package/dist/forms/utils.d.ts +7 -0
  112. package/dist/forms.js +4450 -2478
  113. package/dist/forms.umd.cjs +44 -2777
  114. package/dist/index.d.ts +11 -3
  115. package/eslint.config.js +56 -0
  116. package/package.json +96 -94
  117. package/src/ColorPicker/ColorPicker.tsx +47 -0
  118. package/src/ColorPicker/index.ts +1 -0
  119. package/src/FileBadge/FileBadge.tsx +27 -0
  120. package/src/FileBadge/index.ts +1 -0
  121. package/src/FileCard/FileCard.stories.tsx +69 -0
  122. package/src/FileCard/FileCard.tsx +53 -0
  123. package/src/FileCard/index.ts +1 -0
  124. package/src/FileIcon/FileIcon.tsx +31 -0
  125. package/src/FileIcon/index.ts +1 -0
  126. package/src/FileViewer/FileViewerProvider.stories.tsx +50 -0
  127. package/src/FileViewer/FileViewerProvider.tsx +72 -0
  128. package/src/FileViewer/context.ts +11 -0
  129. package/src/FileViewer/index.ts +3 -0
  130. package/src/FileViewer/typings.ts +5 -0
  131. package/src/ImageCard/ImageCard.stories.tsx +94 -0
  132. package/src/ImageCard/ImageCard.tsx +82 -0
  133. package/src/ImageCard/index.ts +1 -0
  134. package/src/ImageMarkup/ImageMarkup.stories.tsx +65 -0
  135. package/src/ImageMarkup/ImageMarkup.tsx +268 -0
  136. package/src/ImageMarkup/index.ts +1 -0
  137. package/src/ImageViewer/ImageViewer.stories.tsx +57 -0
  138. package/src/ImageViewer/ImageViewer.tsx +124 -0
  139. package/src/ImageViewer/constants.ts +1 -0
  140. package/src/ImageViewer/index.ts +2 -0
  141. package/src/PDFViewer/PDFViewer.stories.tsx +55 -0
  142. package/src/PDFViewer/PDFViewer.tsx +170 -0
  143. package/src/PDFViewer/constants.ts +1 -0
  144. package/src/PDFViewer/index.ts +2 -0
  145. package/src/SpreadsheetViewer/SpreadsheetViewer.stories.tsx +55 -0
  146. package/src/SpreadsheetViewer/SpreadsheetViewer.tsx +162 -0
  147. package/src/SpreadsheetViewer/constants.ts +8 -0
  148. package/src/SpreadsheetViewer/index.ts +2 -0
  149. package/src/forms/builder/DropDispatch.ts +84 -0
  150. package/src/forms/builder/FieldActions.tsx +155 -0
  151. package/src/forms/builder/FieldBuilder.tsx +386 -0
  152. package/src/forms/builder/FieldSectionWithActions.tsx +260 -0
  153. package/src/forms/builder/FieldWithActions.tsx +129 -0
  154. package/src/forms/builder/FieldsEditor.tsx +180 -0
  155. package/src/forms/builder/FormBuilder.stories.tsx +105 -0
  156. package/src/forms/builder/FormBuilder.tsx +237 -0
  157. package/src/forms/builder/constants.ts +18 -0
  158. package/src/forms/builder/hooks.tsx +24 -0
  159. package/src/forms/builder/index.ts +2 -0
  160. package/src/forms/builder/typings.ts +18 -0
  161. package/src/forms/builder/utils.ts +229 -0
  162. package/src/forms/constants.ts +9 -0
  163. package/src/forms/constantsJsx.tsx +67 -0
  164. package/src/forms/fields/BaseField/BaseField.ts +152 -0
  165. package/src/forms/fields/BaseField/hooks.tsx +60 -0
  166. package/src/forms/fields/BaseField/index.ts +4 -0
  167. package/src/forms/fields/BaseField/layouts.tsx +100 -0
  168. package/src/forms/fields/BaseField/typings.ts +9 -0
  169. package/src/forms/fields/BooleanField/BooleanField.tsx +48 -0
  170. package/src/forms/fields/BooleanField/BooleanInput.tsx +54 -0
  171. package/src/forms/fields/BooleanField/index.ts +2 -0
  172. package/src/forms/fields/CustomField/CustomField.tsx +45 -0
  173. package/src/forms/fields/CustomField/FieldInputClonerField/FieldInputCloner.tsx +25 -0
  174. package/src/forms/fields/CustomField/FieldInputClonerField/FieldInputClonerField.tsx +26 -0
  175. package/src/forms/fields/CustomField/FieldInputClonerField/index.ts +3 -0
  176. package/src/forms/fields/CustomField/FieldInputClonerField/typings.ts +8 -0
  177. package/src/forms/fields/CustomField/index.ts +1 -0
  178. package/src/forms/fields/DateField/DateField.tsx +42 -0
  179. package/src/forms/fields/DateField/DateInput.tsx +39 -0
  180. package/src/forms/fields/DateField/index.ts +2 -0
  181. package/src/forms/fields/FieldSection/FieldSection.tsx +173 -0
  182. package/src/forms/fields/FieldSection/FieldSectionLayout.tsx +56 -0
  183. package/src/forms/fields/FieldSection/index.ts +1 -0
  184. package/src/forms/fields/MultiStringField/MultiStringField.tsx +90 -0
  185. package/src/forms/fields/MultiStringField/MultiStringInput.tsx +207 -0
  186. package/src/forms/fields/MultiStringField/index.ts +2 -0
  187. package/src/forms/fields/NumberField/NumberField.tsx +173 -0
  188. package/src/forms/fields/NumberField/NumberInput.tsx +44 -0
  189. package/src/forms/fields/NumberField/index.ts +2 -0
  190. package/src/forms/fields/QrField/QrField.tsx +38 -0
  191. package/src/forms/fields/QrField/QrInput.module.sass +5 -0
  192. package/src/forms/fields/QrField/QrInput.tsx +144 -0
  193. package/src/forms/fields/QrField/index.ts +2 -0
  194. package/src/forms/fields/SelectField/BaseSelectField.ts +73 -0
  195. package/src/forms/fields/SelectField/MultiSelectField.tsx +53 -0
  196. package/src/forms/fields/SelectField/MultiSelectInput.tsx +80 -0
  197. package/src/forms/fields/SelectField/SelectField.tsx +49 -0
  198. package/src/forms/fields/SelectField/SelectInput.tsx +69 -0
  199. package/src/forms/fields/SelectField/index.ts +4 -0
  200. package/src/forms/fields/StringOrTextFields/StringField/StringField.tsx +61 -0
  201. package/src/forms/fields/StringOrTextFields/StringField/StringInput.tsx +41 -0
  202. package/src/forms/fields/StringOrTextFields/StringField/index.ts +2 -0
  203. package/src/forms/fields/StringOrTextFields/StringOrTextField.ts +143 -0
  204. package/src/forms/fields/StringOrTextFields/TextField/TextField.tsx +52 -0
  205. package/src/forms/fields/StringOrTextFields/TextField/TextInput.tsx +42 -0
  206. package/src/forms/fields/StringOrTextFields/TextField/index.ts +2 -0
  207. package/src/forms/fields/StringOrTextFields/index.ts +2 -0
  208. package/src/forms/fields/UploadField/UploadField.tsx +156 -0
  209. package/src/forms/fields/UploadField/UploadInput.tsx +220 -0
  210. package/src/forms/fields/UploadField/index.ts +2 -0
  211. package/src/forms/fields/UploadField/utils.ts +17 -0
  212. package/src/forms/fields/constants.ts +43 -0
  213. package/src/forms/fields/hooks.tsx +26 -0
  214. package/src/forms/fields/index.ts +12 -0
  215. package/src/forms/fields/typings.ts +45 -0
  216. package/src/forms/fields/utils.ts +125 -0
  217. package/src/forms/index.ts +5 -0
  218. package/src/forms/renderer/FormRenderer/FormRenderer.stories.tsx +142 -0
  219. package/src/forms/renderer/FormRenderer/FormRenderer.tsx +135 -0
  220. package/src/forms/renderer/PatchForm/Field.tsx +41 -0
  221. package/src/forms/renderer/PatchForm/PatchForm.stories.tsx +91 -0
  222. package/src/forms/renderer/PatchForm/Provider.tsx +119 -0
  223. package/src/forms/renderer/PatchForm/index.ts +2 -0
  224. package/src/forms/renderer/index.ts +2 -0
  225. package/src/forms/typings.ts +162 -0
  226. package/src/forms/utils.ts +69 -0
  227. package/src/index.ts +11 -0
  228. package/src/vite-env.d.ts +1 -0
  229. package/tailwind.config.ts +8 -0
  230. package/tsconfig.json +26 -0
  231. package/vite.config.ts +23 -0
  232. package/README.md +0 -12
  233. package/dist/builder/FieldActions.d.ts +0 -12
  234. package/dist/builder/FieldBuilder.d.ts +0 -24
  235. package/dist/builder/FieldSectionWithActions.d.ts +0 -10
  236. package/dist/builder/FieldWithActions.d.ts +0 -11
  237. package/dist/builder/FieldsEditor.d.ts +0 -2
  238. package/dist/builder/FormBuilder.d.ts +0 -15
  239. package/dist/builder/constants.d.ts +0 -1
  240. package/dist/builder/index.d.ts +0 -2
  241. package/dist/builder/utils.d.ts +0 -13
  242. package/dist/fields/BaseField/hooks.d.ts +0 -374
  243. package/dist/fields/BaseField/index.d.ts +0 -4
  244. package/dist/fields/BooleanField/BooleanInput.d.ts +0 -4
  245. package/dist/fields/BooleanField/index.d.ts +0 -2
  246. package/dist/fields/CustomField/FieldInputClonerField/index.d.ts +0 -3
  247. package/dist/fields/CustomField/FieldInputClonerField/typings.d.ts +0 -5
  248. package/dist/fields/CustomField/index.d.ts +0 -1
  249. package/dist/fields/DateField/DateField.d.ts +0 -16
  250. package/dist/fields/DateField/DateInput.d.ts +0 -4
  251. package/dist/fields/DateField/index.d.ts +0 -2
  252. package/dist/fields/FieldSection/FieldSectionLayout.d.ts +0 -7
  253. package/dist/fields/FieldSection/index.d.ts +0 -1
  254. package/dist/fields/MultiStringField/MultiStringField.d.ts +0 -30
  255. package/dist/fields/MultiStringField/MultiStringInput.d.ts +0 -8
  256. package/dist/fields/MultiStringField/index.d.ts +0 -2
  257. package/dist/fields/NumberField/NumberInput.d.ts +0 -4
  258. package/dist/fields/NumberField/index.d.ts +0 -2
  259. package/dist/fields/SelectField/MultiSelectInput.d.ts +0 -4
  260. package/dist/fields/SelectField/SelectInput.d.ts +0 -4
  261. package/dist/fields/SelectField/index.d.ts +0 -4
  262. package/dist/fields/StringOrTextFields/StringField/StringField.d.ts +0 -19
  263. package/dist/fields/StringOrTextFields/StringField/StringInput.d.ts +0 -4
  264. package/dist/fields/StringOrTextFields/StringField/index.d.ts +0 -2
  265. package/dist/fields/StringOrTextFields/TextField/TextField.d.ts +0 -16
  266. package/dist/fields/StringOrTextFields/TextField/TextInput.d.ts +0 -4
  267. package/dist/fields/StringOrTextFields/TextField/index.d.ts +0 -2
  268. package/dist/fields/StringOrTextFields/index.d.ts +0 -2
  269. package/dist/fields/UploadField/UploadInput.d.ts +0 -4
  270. package/dist/fields/UploadField/index.d.ts +0 -2
  271. package/dist/fields/constants.d.ts +0 -20
  272. package/dist/fields/hooks.d.ts +0 -6
  273. package/dist/fields/index.d.ts +0 -11
  274. package/dist/forms.js.map +0 -1
  275. package/dist/forms.umd.cjs.map +0 -1
  276. package/dist/renderer/FormBrowser/FormBrowser.d.ts +0 -11
  277. package/dist/renderer/FormSubmissionBrowser/FormSubmissionBrowser.d.ts +0 -28
  278. package/dist/renderer/FormSubmissionViewer/FormSubmissionViewer.d.ts +0 -17
  279. package/dist/renderer/PatchForm/index.d.ts +0 -2
  280. package/dist/renderer/index.d.ts +0 -5
  281. package/dist/style.css +0 -34
  282. package/dist/typings.d.ts +0 -17
  283. package/dist/utils.d.ts +0 -7
  284. /package/dist/{fields → forms/fields}/UploadField/utils.d.ts +0 -0
@@ -0,0 +1,152 @@
1
+ import { ISerializedOnlyField } from "@overmap-ai/core"
2
+ import { ChangeEvent, ReactNode } from "react"
3
+
4
+ import { FormikUserFormRevision } from "../../builder"
5
+ import {
6
+ BaseSerializedField,
7
+ BaseSerializedObject,
8
+ FieldTypeIdentifier,
9
+ FieldValue,
10
+ Form,
11
+ ISerializedField,
12
+ } from "../../typings"
13
+ import { FieldSection } from "../FieldSection"
14
+ import { AnyField, GetInputProps, InputFieldLevelValidator, InputFormLevelValidator } from "../typings"
15
+ import { FieldOptions } from "./typings"
16
+
17
+ // TODO: These types redefine ISerializedField and related types.
18
+ // Looks like they can be merged.
19
+
20
+ // TODO: "Element" implies an instantiated component in React.
21
+ export abstract class BaseFormElement<TIdentifier extends FieldTypeIdentifier = FieldTypeIdentifier> {
22
+ public readonly type: TIdentifier
23
+ readonly identifier: string
24
+ public readonly description: string | null
25
+
26
+ protected constructor(options: BaseSerializedObject) {
27
+ const { description = null, identifier, type } = options
28
+ this.identifier = identifier
29
+ this.description = description
30
+ this.type = type as TIdentifier
31
+ }
32
+
33
+ getId(): string {
34
+ return this.identifier
35
+ }
36
+
37
+ abstract getInput(props: GetInputProps<this>): ReactNode
38
+
39
+ static deserialize(_data: ISerializedField): AnyField | FieldSection {
40
+ throw new Error(`${this.name} must implement deserialize.`)
41
+ }
42
+
43
+ protected _serialize(): BaseSerializedObject<TIdentifier> {
44
+ if (!this.identifier) {
45
+ throw new Error("Field identifier must be set before serializing.")
46
+ }
47
+ return {
48
+ type: this.type,
49
+ identifier: this.identifier,
50
+ description: this.description,
51
+ }
52
+ }
53
+ }
54
+
55
+ export const emptyBaseField = {
56
+ label: "",
57
+ description: "",
58
+ required: false,
59
+ }
60
+
61
+ export interface FieldCreationSchemaObject {
62
+ field: AnyField
63
+ showDirectly: boolean
64
+ }
65
+
66
+ export abstract class BaseField<
67
+ TValue extends FieldValue,
68
+ TIdentifier extends FieldTypeIdentifier = FieldTypeIdentifier,
69
+ > extends BaseFormElement<TIdentifier> {
70
+ static readonly fieldTypeName: string
71
+ static readonly fieldTypeDescription: string
72
+
73
+ public readonly required: boolean
74
+ private readonly formValidators: InputFormLevelValidator<TValue>[]
75
+ private readonly fieldValidators: InputFieldLevelValidator<TValue>[]
76
+ public readonly label: string
77
+ public readonly image: File | Promise<File> | undefined
78
+
79
+ /**
80
+ * By default, validation doesn't execute on `onChange` events when editing fields
81
+ * until the field has been `touched`. This can be overridden by setting this to `false`
82
+ * if you want to validate on every `onChange` event. This is important for fields like booleans
83
+ * which don't have a `onBlur` event (which is used to set the `touched` state).
84
+ */
85
+ public readonly onlyValidateAfterTouched: boolean = true
86
+
87
+ protected constructor(options: FieldOptions<TValue>) {
88
+ const { label, required, image, fieldValidators = [], formValidators = [], ...base } = options
89
+ super(base)
90
+ this.label = label
91
+ this.required = required
92
+ this.image = image
93
+ this.fieldValidators = fieldValidators
94
+ this.formValidators = formValidators
95
+ }
96
+
97
+ public static getFieldCreationSchema(): FieldCreationSchemaObject[] {
98
+ return []
99
+ }
100
+
101
+ protected isBlank(value: TValue | undefined): boolean {
102
+ return value === null || value === undefined || value === ""
103
+ }
104
+
105
+ public getValueFromChangeEvent(event: ChangeEvent<HTMLInputElement>): TValue {
106
+ return event.target.value as unknown as TValue
107
+ }
108
+
109
+ public getError(value: TValue, allValues?: Form | FormikUserFormRevision): string | undefined {
110
+ if (this.required && this.isBlank(value)) {
111
+ return "This field is required."
112
+ }
113
+ for (const validator of this.getFieldValidators()) {
114
+ const error = validator(value)
115
+ if (error) return error
116
+ }
117
+ if (allValues) {
118
+ for (const validator of this.getFormValidators()) {
119
+ const error = validator(value, allValues)
120
+ if (error) return error
121
+ }
122
+ }
123
+ }
124
+
125
+ // TODO: We can probably combine _serialize and serialize.
126
+ protected _serialize(): BaseSerializedField<TIdentifier> {
127
+ return {
128
+ ...super._serialize(),
129
+ label: this.label,
130
+ required: this.required,
131
+ image: this.image,
132
+ }
133
+ }
134
+
135
+ abstract serialize(): ISerializedOnlyField
136
+
137
+ public getFieldValidators(): InputFieldLevelValidator<TValue>[] {
138
+ return [...this.fieldValidators]
139
+ }
140
+
141
+ public getFormValidators(): InputFormLevelValidator<TValue>[] {
142
+ return [...this.formValidators]
143
+ }
144
+
145
+ public encodeValueToJson(value: TValue) {
146
+ return JSON.stringify(value)
147
+ }
148
+
149
+ public decodeJsonToValue(json: string): TValue {
150
+ return JSON.parse(json) as TValue
151
+ }
152
+ }
@@ -0,0 +1,60 @@
1
+ import { useField } from "formik"
2
+ import { ChangeEventHandler, FocusEventHandler, useMemo } from "react"
3
+
4
+ import { Severity, ValueOfField } from "../../typings"
5
+ import { AnyField, ComponentProps } from "../typings"
6
+
7
+ // Wrapper for Formik's useField hook that returns the field's props, meta, and helpers.
8
+ export const useFormikInput = <TField extends AnyField>(props: ComponentProps<TField>) => {
9
+ const { id, field, formId, size, showInputOnly, internal, ...rest } = props
10
+ const [fieldProps, meta, helpers] = useField<ValueOfField<TField>>(field.getId())
11
+ const { touched } = meta
12
+ const helpText = meta.error ?? field.description
13
+ const severity: Severity | undefined = meta.error ? "danger" : undefined
14
+ const inputId = id ?? `${formId}-${field.getId()}-input`
15
+ const labelId = `${inputId}-label`
16
+ const label = field.required ? `${field.label} *` : field.label
17
+
18
+ // adds field-level validation to onChange and onBlur events
19
+ // this is necessary because Formik's useField hook does not support field-level validation
20
+ const fieldPropsWithValidation: typeof fieldProps = useMemo(() => {
21
+ const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
22
+ const value = field.getValueFromChangeEvent(e) as ValueOfField<TField>
23
+ void helpers.setValue(value, false).then()
24
+ // only validate onChange if the field has been touched
25
+ if (touched || !field.onlyValidateAfterTouched) {
26
+ helpers.setError(field.getError(value))
27
+ }
28
+ }
29
+
30
+ const handleBlur: FocusEventHandler<HTMLInputElement> = (e) => {
31
+ void helpers.setTouched(true, false).then()
32
+ helpers.setError(field.getError(field.getValueFromChangeEvent(e)))
33
+ }
34
+
35
+ return {
36
+ ...fieldProps,
37
+ onChange: handleChange,
38
+ onBlur: handleBlur,
39
+ }
40
+ }, [field, fieldProps, helpers, touched])
41
+
42
+ console.debug("severity", severity)
43
+
44
+ return [
45
+ {
46
+ helpText,
47
+ size,
48
+ severity,
49
+ inputId,
50
+ labelId,
51
+ label,
52
+ showInputOnly,
53
+ internal,
54
+ fieldProps: fieldPropsWithValidation,
55
+ helpers,
56
+ field,
57
+ },
58
+ { ...rest, "aria-labelledby": labelId },
59
+ ] as const
60
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./BaseField"
2
+ export * from "./hooks"
3
+ export * from "./layouts"
4
+ export * from "./typings"
@@ -0,0 +1,100 @@
1
+ import { Text, TextSize } from "@overmap-ai/blocks"
2
+ import { cx } from "class-variance-authority"
3
+ import { ReactElement, ReactNode, useEffect, useState } from "react"
4
+
5
+ import { SEVERITY_COLOR_MAPPING } from "../../constants"
6
+ import { FullScreenImagePreview } from "../../constantsJsx"
7
+ import { Severity } from "../../typings"
8
+
9
+ interface InputWithLabelProps {
10
+ size?: TextSize
11
+ severity: Severity | undefined
12
+ inputId: string
13
+ labelId: string
14
+ label: string
15
+ image: File | Promise<File> | undefined
16
+ children: ReactNode
17
+ className?: string
18
+ }
19
+ export const InputWithLabel = (props: InputWithLabelProps) => {
20
+ const { className, label, children, size, severity, inputId, labelId, image } = props
21
+ const [resolvedImage, setResolvedImage] = useState<File | undefined>(undefined)
22
+ const [showImagePreview, setShowImagePreview] = useState<boolean>(false)
23
+
24
+ const color = severity ? SEVERITY_COLOR_MAPPING[severity] : "base"
25
+
26
+ console.debug(severity, color)
27
+
28
+ useEffect(() => {
29
+ if (image instanceof Promise) {
30
+ image.then(setResolvedImage).catch(console.error)
31
+ } else {
32
+ setResolvedImage(image)
33
+ }
34
+ }, [image])
35
+
36
+ const resolvedImageURL = resolvedImage ? URL.createObjectURL(resolvedImage) : undefined
37
+
38
+ return (
39
+ <div className="flex flex-col gap-2">
40
+ {resolvedImage && (
41
+ <>
42
+ <img
43
+ className="h-[100px] w-full min-w-[300px] cursor-pointer rounded-md object-cover"
44
+ src={resolvedImageURL}
45
+ alt={resolvedImage.name}
46
+ onClick={() => {
47
+ setShowImagePreview(true)
48
+ }}
49
+ />
50
+ {showImagePreview && (
51
+ <FullScreenImagePreview
52
+ file={resolvedImage}
53
+ url={resolvedImageURL!}
54
+ name={resolvedImage.name}
55
+ setShowPreview={setShowImagePreview}
56
+ />
57
+ )}
58
+ </>
59
+ )}
60
+
61
+ <label className={cx(className, "flex flex-col gap-1")} htmlFor={inputId}>
62
+ <Text accentColor={color} size={size} id={labelId} weight="medium">
63
+ {label}
64
+ </Text>
65
+ {children}
66
+ </label>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ interface InputWithHelpTextProps {
72
+ severity: Severity | undefined
73
+ helpText: string | null
74
+ children: ReactElement
75
+ }
76
+
77
+ export const InputWithHelpText = (props: InputWithHelpTextProps) => {
78
+ const { helpText, children, severity } = props
79
+
80
+ const color = severity ? SEVERITY_COLOR_MAPPING[severity] : "base"
81
+
82
+ return (
83
+ <div className="flex flex-col gap-1">
84
+ {children}
85
+ <div className="flex flex-col w-full">
86
+ <Text accentColor={color} size="xs">
87
+ {helpText}
88
+ </Text>
89
+ </div>
90
+ </div>
91
+ )
92
+ }
93
+
94
+ interface InputWithLabelAndHelpTextProps extends InputWithHelpTextProps {
95
+ children: ReactElement<InputWithLabelProps>
96
+ }
97
+ export const InputWithLabelAndHelpText = (props: InputWithLabelAndHelpTextProps) => {
98
+ const { children, ...restProps } = props
99
+ return <InputWithHelpText {...restProps}>{children}</InputWithHelpText>
100
+ }
@@ -0,0 +1,9 @@
1
+ import { BaseSerializedField } from "../../typings"
2
+ import { InputFieldLevelValidator, InputFormLevelValidator } from "../typings"
3
+
4
+ export interface FieldOptions<TValue> extends BaseSerializedField {
5
+ fieldValidators?: InputFieldLevelValidator<TValue>[]
6
+ formValidators?: InputFormLevelValidator<TValue>[]
7
+ }
8
+
9
+ export type ChildFieldOptions<TValue> = Omit<FieldOptions<TValue>, "type">
@@ -0,0 +1,48 @@
1
+ import { ChangeEvent, ReactNode } from "react"
2
+ import { RiCheckboxCircleLine } from "react-icons/ri"
3
+
4
+ import { ISerializedField, SerializedBooleanField } from "../../typings"
5
+ import { BaseField, ChildFieldOptions, emptyBaseField } from "../BaseField"
6
+ import { ComponentProps } from "../typings"
7
+ import { BooleanInput } from "./BooleanInput"
8
+
9
+ export const emptyBooleanField = {
10
+ ...emptyBaseField,
11
+ type: "boolean",
12
+ }
13
+
14
+ export class BooleanField extends BaseField<boolean, "boolean"> {
15
+ static readonly fieldTypeName = "Checkbox"
16
+ static readonly fieldTypeDescription = "Perfect for both optional and required yes/no questions."
17
+
18
+ public readonly onlyValidateAfterTouched = false
19
+
20
+ static Icon: typeof RiCheckboxCircleLine = RiCheckboxCircleLine
21
+
22
+ public constructor(options: ChildFieldOptions<boolean>) {
23
+ super({ ...options, type: "boolean" })
24
+ }
25
+
26
+ // if a BooleanField is required, `false` is considered blank
27
+ protected isBlank(value: boolean): boolean {
28
+ return this.required && !value
29
+ }
30
+
31
+ public getValueFromChangeEvent(event: ChangeEvent<HTMLInputElement> | boolean): boolean {
32
+ if (typeof event === "boolean") return event
33
+ return event.target.checked
34
+ }
35
+
36
+ serialize(): SerializedBooleanField {
37
+ return super._serialize()
38
+ }
39
+
40
+ static deserialize(data: ISerializedField): BooleanField {
41
+ if (data.type !== "boolean") throw new Error("Type mismatch.")
42
+ return new BooleanField(data)
43
+ }
44
+
45
+ getInput(props: ComponentProps<BooleanField>): ReactNode {
46
+ return <BooleanInput {...props} field={this} />
47
+ }
48
+ }
@@ -0,0 +1,54 @@
1
+ import { Checkbox, RiIcon } from "@overmap-ai/blocks"
2
+ import { memo } from "react"
3
+
4
+ import { SEVERITY_COLOR_MAPPING } from "../../constants"
5
+ import { InputWithLabel, InputWithLabelAndHelpText } from "../BaseField"
6
+ import { useFormikInput } from "../BaseField"
7
+ import { ComponentProps } from "../typings"
8
+ import { BooleanField } from "./BooleanField"
9
+
10
+ const truthyValues = [true, "true"]
11
+
12
+ export const BooleanInput = memo((props: ComponentProps<BooleanField>) => {
13
+ const [{ inputId, labelId, size, severity, showInputOnly, field, fieldProps }, rest] = useFormikInput(props)
14
+ let [{ helpText, label }] = useFormikInput(props)
15
+ helpText = showInputOnly ? null : helpText
16
+ label = showInputOnly ? "" : label
17
+
18
+ const color = severity ? SEVERITY_COLOR_MAPPING[severity] : undefined
19
+ const value = truthyValues.includes(fieldProps.value)
20
+
21
+ return (
22
+ <InputWithLabelAndHelpText helpText={helpText} severity={severity}>
23
+ <InputWithLabel
24
+ size={size}
25
+ severity={severity}
26
+ inputId={inputId}
27
+ labelId={labelId}
28
+ label={label}
29
+ image={showInputOnly ? undefined : field.image}
30
+ className="align-center flex-row-reverse justify-end gap-2"
31
+ >
32
+ <Checkbox.Root
33
+ {...rest}
34
+ {...fieldProps}
35
+ id={inputId}
36
+ accentColor={color}
37
+ value={value.toString()}
38
+ checked={value}
39
+ onCheckedChange={fieldProps.onChange}
40
+ // disabled onChange and onBlur as that is handled by onCheckedChange
41
+ onChange={undefined}
42
+ onBlur={undefined}
43
+ variant="soft"
44
+ >
45
+ <Checkbox.Indicator>
46
+ <RiIcon icon="RiCheckLine" />
47
+ </Checkbox.Indicator>
48
+ </Checkbox.Root>
49
+ </InputWithLabel>
50
+ </InputWithLabelAndHelpText>
51
+ )
52
+ })
53
+
54
+ BooleanInput.displayName = "BooleanInput"
@@ -0,0 +1,2 @@
1
+ export * from "./BooleanField"
2
+ export * from "./BooleanInput"
@@ -0,0 +1,45 @@
1
+ import { ISerializedOnlyField } from "@overmap-ai/core"
2
+ import { FC, ReactNode } from "react"
3
+
4
+ import { FieldTypeIdentifier, FieldValue } from "../../typings"
5
+ import { BaseField, ChildFieldOptions, emptyBaseField } from "../BaseField"
6
+ import { GetInputProps } from "../typings"
7
+
8
+ export type CustomFieldOptions<TValue> = ChildFieldOptions<TValue>
9
+
10
+ export const emptyCustomField = {
11
+ ...emptyBaseField,
12
+ type: "custom",
13
+ }
14
+
15
+ export class CustomField<
16
+ TValue extends FieldValue,
17
+ /** The options passed to constructor */
18
+ TFieldOptions extends CustomFieldOptions<TValue>,
19
+ /** The props passed to the custom component */
20
+ TComponentProps extends GetInputProps<CustomField<TValue, TFieldOptions, TComponentProps>>,
21
+ TIdentifier extends FieldTypeIdentifier = FieldTypeIdentifier,
22
+ > extends BaseField<TValue, TIdentifier> {
23
+ static readonly fieldTypeName = "Custom"
24
+ static readonly fieldTypeDescription = "Allows re-rendering of field already in the form"
25
+
26
+ public readonly Component: FC<TComponentProps>
27
+
28
+ // identifier of the field whose value is the label of the field to re-render
29
+ public readonly options: TFieldOptions
30
+
31
+ constructor(options: TFieldOptions, Component: FC<TComponentProps>) {
32
+ super({ ...options, type: "custom" })
33
+ this.options = options
34
+ this.Component = Component
35
+ }
36
+
37
+ serialize(): ISerializedOnlyField {
38
+ throw new Error("Serializing only supported for public input types.")
39
+ }
40
+
41
+ getInput(props: TComponentProps): ReactNode {
42
+ const CustomInput = this.Component
43
+ return <CustomInput field={this} {...props} />
44
+ }
45
+ }
@@ -0,0 +1,25 @@
1
+ import { useField } from "formik"
2
+ import { memo, useMemo } from "react"
3
+
4
+ import { deserialize, useFieldInput } from "../../index"
5
+ import { FieldInputClonerProps } from "./typings"
6
+
7
+ /**
8
+ * Used to dynamically "clone" a field's input for use in conditional sections. When a field is selected for a
9
+ * condition, we need to render the same input in the condition section, so the user can input a value for the
10
+ * condition.
11
+ */
12
+ export const FieldInputCloner = memo((props: FieldInputClonerProps) => {
13
+ const { field, ...rest } = props
14
+ const [{ value: identifier }] = useField<string>(field.options.clonedFieldIdentifier)
15
+
16
+ const deserializedField = useMemo(() => {
17
+ const options = field.options.getFieldToClone(identifier)
18
+ if (!options) return null
19
+ return deserialize(options)
20
+ }, [field.options, identifier])
21
+
22
+ return useFieldInput(deserializedField, rest)
23
+ })
24
+
25
+ FieldInputCloner.displayName = "FieldInputCloner"
@@ -0,0 +1,26 @@
1
+ import { FieldValue, ISerializedField } from "../../../typings"
2
+ import { CustomField, CustomFieldOptions } from "../CustomField"
3
+ import { FieldInputCloner, FieldInputClonerProps } from "./index"
4
+
5
+ export interface FieldInputClonerFieldOptions extends CustomFieldOptions<unknown> {
6
+ /** Given an identifier, should return the options of the field with the
7
+ * corresponding identifier (the field being cloned) */
8
+ getFieldToClone: (identifier: string) => ISerializedField | null
9
+ /** The identifier of the field to clone */
10
+ clonedFieldIdentifier: string
11
+ }
12
+
13
+ /**
14
+ * The purpose of this is to display a value input field in the condition of a section. The input field will look like
15
+ * the input field of the condition. For example, when specifying the conditional value of a SelectField, a SelectInput
16
+ * will be rendered.
17
+ */
18
+ export class FieldInputClonerField extends CustomField<
19
+ FieldValue,
20
+ FieldInputClonerFieldOptions,
21
+ FieldInputClonerProps
22
+ > {
23
+ constructor(options: FieldInputClonerFieldOptions) {
24
+ super(options, FieldInputCloner)
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./FieldInputCloner"
2
+ export * from "./FieldInputClonerField"
3
+ export * from "./typings"
@@ -0,0 +1,8 @@
1
+ import { FieldValue } from "../../../typings"
2
+ import { AnyField, ComponentProps, GetInputProps } from "../../typings"
3
+ import { CustomField } from "../CustomField"
4
+ import { FieldInputClonerFieldOptions } from "./FieldInputClonerField"
5
+
6
+ export type FieldInputClonerProps = ComponentProps<
7
+ CustomField<FieldValue, FieldInputClonerFieldOptions, GetInputProps<AnyField>>
8
+ >
@@ -0,0 +1 @@
1
+ export * from "./CustomField"
@@ -0,0 +1,42 @@
1
+ import { ChangeEvent, ReactNode } from "react"
2
+ import { RiCalendarLine } from "react-icons/ri"
3
+
4
+ import { ISerializedField, SerializedDateField } from "../../typings"
5
+ import { BaseField, ChildFieldOptions, emptyBaseField } from "../BaseField"
6
+ import { GetInputProps } from "../typings"
7
+ import { DateInput } from "./DateInput"
8
+
9
+ export const emptyDateField = {
10
+ ...emptyBaseField,
11
+ type: "date",
12
+ }
13
+
14
+ export class DateField extends BaseField<string, "date"> {
15
+ static readonly fieldTypeName = "Date"
16
+ static readonly fieldTypeDescription = "Allows specifying a date."
17
+
18
+ static Icon: typeof RiCalendarLine = RiCalendarLine
19
+
20
+ public readonly onlyValidateAfterTouched = false
21
+
22
+ public constructor(options: ChildFieldOptions<string>) {
23
+ super({ ...options, type: "date" })
24
+ }
25
+
26
+ serialize(): SerializedDateField {
27
+ return super._serialize()
28
+ }
29
+
30
+ public getValueFromChangeEvent(event: ChangeEvent<HTMLInputElement>): string {
31
+ return new Date(event.target.value).toISOString()
32
+ }
33
+
34
+ static deserialize(data: ISerializedField): DateField {
35
+ if (data.type !== "date") throw new Error("Type mismatch.")
36
+ return new DateField(data)
37
+ }
38
+
39
+ getInput(props: GetInputProps<this>): ReactNode {
40
+ return <DateInput field={this} {...props} />
41
+ }
42
+ }
@@ -0,0 +1,39 @@
1
+ import { Input } from "@overmap-ai/blocks"
2
+ import { memo } from "react"
3
+
4
+ import { SEVERITY_COLOR_MAPPING } from "../../constants"
5
+ import { InputWithLabel, InputWithLabelAndHelpText, useFormikInput } from "../BaseField"
6
+ import { ComponentProps } from "../typings"
7
+ import { DateField } from "./DateField"
8
+
9
+ export const DateInput = memo((props: ComponentProps<DateField>) => {
10
+ const [{ inputId, labelId, size, severity, showInputOnly, field, fieldProps }, rest] = useFormikInput(props)
11
+ let [{ helpText, label }] = useFormikInput(props)
12
+ helpText = showInputOnly ? null : helpText
13
+ label = showInputOnly ? "" : label
14
+
15
+ const color = severity ? SEVERITY_COLOR_MAPPING[severity] : undefined
16
+
17
+ // TODO: Add timezone info
18
+ // remove the time from the date
19
+ const value: string = fieldProps.value ? fieldProps.value.split("T")[0]! : ""
20
+
21
+ return (
22
+ <InputWithLabelAndHelpText helpText={helpText} severity={severity}>
23
+ <InputWithLabel
24
+ size={size}
25
+ severity={severity}
26
+ inputId={inputId}
27
+ labelId={labelId}
28
+ label={label}
29
+ image={showInputOnly ? undefined : field.image}
30
+ >
31
+ <Input.Root accentColor={color} variant="soft">
32
+ <Input.Field {...rest} type="date" id={inputId} color={color} value={value} />
33
+ </Input.Root>
34
+ </InputWithLabel>
35
+ </InputWithLabelAndHelpText>
36
+ )
37
+ })
38
+
39
+ DateInput.displayName = "DateInput"
@@ -0,0 +1,2 @@
1
+ export * from "./DateField"
2
+ export * from "./DateInput"