@sio-group/form-react 0.1.0 → 0.1.3

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 (59) hide show
  1. package/CHANGELOG.md +35 -4
  2. package/dist/index.cjs +268 -18
  3. package/dist/index.d.cts +2 -2
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +258 -17
  6. package/package.json +4 -3
  7. package/src/assets/scss/components/button.scss +164 -0
  8. package/src/assets/scss/components/checkbox.scss +90 -0
  9. package/src/assets/scss/components/color.scss +29 -0
  10. package/src/assets/scss/components/form-field.scss +34 -0
  11. package/src/assets/scss/components/form-states.scss +80 -0
  12. package/src/assets/scss/components/grid.scss +134 -0
  13. package/src/assets/scss/components/input.scss +112 -0
  14. package/src/assets/scss/components/link.scss +66 -0
  15. package/src/assets/scss/components/radio.scss +104 -0
  16. package/src/assets/scss/components/range.scss +52 -0
  17. package/src/assets/scss/components/select.scss +35 -0
  18. package/src/assets/scss/components/upload.scss +52 -0
  19. package/src/assets/scss/index.scss +19 -0
  20. package/src/assets/scss/tokens/_colors.scss +49 -0
  21. package/src/assets/scss/tokens/_form.scss +6 -0
  22. package/src/assets/scss/utilities/_mixins.scss +6 -0
  23. package/src/components/Button/index.tsx +106 -0
  24. package/src/components/Fields/Checkbox/index.tsx +59 -0
  25. package/src/components/Fields/Input/DateInput/index.tsx +95 -0
  26. package/src/components/Fields/Input/FileInput/index.tsx +169 -0
  27. package/src/components/Fields/Input/Input.tsx +45 -0
  28. package/src/components/Fields/Input/NumberInput/index.tsx +169 -0
  29. package/src/components/Fields/Input/RangeInput/index.tsx +77 -0
  30. package/src/components/Fields/Input/TextInput/index.tsx +65 -0
  31. package/src/components/Fields/InputWrapper/index.tsx +78 -0
  32. package/src/components/Fields/Radio/index.tsx +82 -0
  33. package/src/components/Fields/Select/index.tsx +103 -0
  34. package/src/components/Fields/Textarea/index.tsx +70 -0
  35. package/src/components/Fields/index.tsx +11 -0
  36. package/src/components/Form.tsx +163 -0
  37. package/src/components/Icon/index.tsx +16 -0
  38. package/src/components/Link/index.tsx +106 -0
  39. package/src/hooks/useConnectionStatus.ts +20 -0
  40. package/src/hooks/useForm.ts +230 -0
  41. package/src/index.ts +15 -0
  42. package/src/types/field-props.d.ts +94 -0
  43. package/src/types/field-setters.d.ts +6 -0
  44. package/src/types/field-state.d.ts +21 -0
  45. package/src/types/form-config.d.ts +30 -0
  46. package/src/types/form-layout.d.ts +6 -0
  47. package/src/types/index.ts +18 -0
  48. package/src/types/ui-props.d.ts +33 -0
  49. package/src/types/use-form-options.d.ts +3 -0
  50. package/src/utils/create-field-props.ts +115 -0
  51. package/src/utils/create-field-state.ts +99 -0
  52. package/src/utils/custom-icons.tsx +145 -0
  53. package/src/utils/file-type-icon.ts +63 -0
  54. package/src/utils/get-accept-string.ts +24 -0
  55. package/src/utils/get-column-classes.ts +21 -0
  56. package/src/utils/get-file-size.ts +9 -0
  57. package/src/utils/parse-date.ts +36 -0
  58. package/src/utils/slugify.ts +9 -0
  59. package/tsconfig.json +15 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Final updated release with bugfixes
8
+ - Updated dependencies
9
+ - @sio-group/form-validation@0.1.3
10
+ - @sio-group/form-types@0.1.3
11
+
12
+ ## 0.1.2
13
+
14
+ ### Patch Changes
15
+
16
+ - Final minor release with bugfixes
17
+ - Updated dependencies
18
+ - @sio-group/form-validation@0.1.2
19
+ - @sio-group/form-types@0.1.2
20
+
21
+ ## 0.1.1
22
+
23
+ ### Patch Changes
24
+
25
+ - 69e584e: minor changes in readme
26
+ - Updated dependencies [69e584e]
27
+ - @sio-group/form-validation@0.1.1
28
+ - @sio-group/form-types@0.1.1
29
+
3
30
  All notable changes to this project will be documented in this file.
4
31
 
5
32
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@@ -36,8 +63,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
36
63
  ### Security
37
64
 
38
65
  - N/A (initial release)
39
- -
40
- ---
66
+ - ***
41
67
 
42
68
  ## [0.1.0] - 2024-03-13
43
69
 
@@ -59,6 +85,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
59
85
  ## Versioning Guidelines
60
86
 
61
87
  This project follows [SemVer](https://semver.org/):
88
+
62
89
  - **MAJOR** version for incompatible API changes
63
90
  - **MINOR** version for new functionality in a backward compatible manner
64
91
  - **PATCH** version for backward compatible bug fixes
@@ -66,7 +93,7 @@ This project follows [SemVer](https://semver.org/):
66
93
  ## Release History
67
94
 
68
95
  | Version | Date | Highlights |
69
- |---------|------------|----------------------|
96
+ | ------- | ---------- | -------------------- |
70
97
  | 0.1.0 | 2024-03-13 | Initial beta release |
71
98
  | 1.0.0 | TBD | Stable release |
72
99
 
@@ -75,16 +102,19 @@ This project follows [SemVer](https://semver.org/):
75
102
  ## Upgrade Guides
76
103
 
77
104
  ### From 0.x to 1.x
78
- *Coming soon*
105
+
106
+ _Coming soon_
79
107
 
80
108
  ---
81
109
 
82
110
  ## Migration Notes
83
111
 
84
112
  ### Beta Users
113
+
85
114
  If you're using the beta version (0.x), please note that the API is considered stable, but minor changes may occur before the 1.0.0 release. Check the release notes for any breaking changes.
86
115
 
87
116
  ### Breaking Changes in Future Versions
117
+
88
118
  - **1.0.0** - First stable release (no breaking changes planned)
89
119
 
90
120
  ---
@@ -92,6 +122,7 @@ If you're using the beta version (0.x), please note that the API is considered s
92
122
  ## Dependencies
93
123
 
94
124
  ### Internal Dependencies
125
+
95
126
  - `@sio-group/form-types` - Shared type definitions
96
127
  - `@sio-group/form-builder` - Form structure builder (optional)
97
128
  - `@sio-group/form-validation` - Validation rules (optional)
package/dist/index.cjs CHANGED
@@ -75,11 +75,264 @@ var slugify = (string, separator = "_") => {
75
75
  return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9 ]/g, "").replace(/\s+/g, separator);
76
76
  };
77
77
 
78
- // src/utils/create-field-state.ts
79
- var import_form_validation = require("@sio-group/form-validation");
78
+ // ../form-validation/dist/index.js
79
+ var isRequired = (errorMessage) => (val, label = "Dit") => {
80
+ const message = errorMessage?.replace("{label}", label) || `${label} is een verplicht veld.`;
81
+ if (val === null || val === void 0) {
82
+ return message;
83
+ }
84
+ if (typeof val === "string" && val.trim().length === 0) {
85
+ return message;
86
+ }
87
+ if (typeof val === "boolean" && !val) {
88
+ return message;
89
+ }
90
+ if (Array.isArray(val) && val.length === 0) {
91
+ return message;
92
+ }
93
+ return null;
94
+ };
95
+ var isEmail = (errorMessage) => (val, label = "Dit") => {
96
+ if (val === null || val === void 0) {
97
+ return null;
98
+ }
99
+ if (typeof val !== "string") {
100
+ return errorMessage || `${label} is geen geldig e-mailadres.`;
101
+ }
102
+ const trimmed = val.trim();
103
+ if (trimmed.length === 0) {
104
+ return null;
105
+ }
106
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
107
+ return emailRegex.test(trimmed) ? null : errorMessage || `${label} is geen geldig e-mailadres.`;
108
+ };
109
+ var isUrl = (allowLocalhost = false, allowFtp = false, secureOnly = true) => (val, label = "Dit") => {
110
+ if (val === null || val === void 0) {
111
+ return null;
112
+ }
113
+ if (typeof val !== "string") {
114
+ return `${label} is geen geldige URL.`;
115
+ }
116
+ const trimmed = val.trim();
117
+ if (trimmed.length === 0) {
118
+ return null;
119
+ }
120
+ try {
121
+ const url = new URL(trimmed);
122
+ if (secureOnly && url.protocol !== "https:") {
123
+ return `${label} moet https gebruiken.`;
124
+ }
125
+ if (!allowFtp && url.protocol === "ftp") {
126
+ return `${label} mag geen ftp gebruiken.`;
127
+ }
128
+ if (!allowLocalhost && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
129
+ return `${label} mag geen localhost zijn.`;
130
+ }
131
+ return null;
132
+ } catch {
133
+ return `${label} is geen geldige URL.`;
134
+ }
135
+ };
136
+ var isBiggerThan = (min, errorMessage) => (val, label = "Dit") => {
137
+ if (val === null || val === void 0 || val === "") {
138
+ return null;
139
+ }
140
+ const num = Number(val);
141
+ if (Number.isNaN(num)) {
142
+ return `${label} moet een geldig getal zijn.`;
143
+ }
144
+ return min <= num ? null : errorMessage?.replace("{label}", label)?.replace("{min}", min.toString()) || `${label} mag niet kleiner zijn dan ${min}.`;
145
+ };
146
+ var isSmallerThan = (max, errorMessage) => (val, label = "Dit") => {
147
+ if (val === null || val === void 0 || val === "") {
148
+ return null;
149
+ }
150
+ const num = Number(val);
151
+ if (Number.isNaN(num)) {
152
+ return `${label} moet een geldig getal zijn.`;
153
+ }
154
+ return num <= max ? null : errorMessage?.replace("{label}", label)?.replace("{max}", max.toString()) || `${label} mag niet groter zijn dan ${max}.`;
155
+ };
156
+ var parseDateValue = (val) => {
157
+ if (val instanceof Date) {
158
+ return {
159
+ type: hasExplicitTime(val) ? "datetime" : "date",
160
+ value: isNaN(val.getTime()) ? null : val
161
+ };
162
+ }
163
+ if (typeof val !== "string") {
164
+ return { type: "invalid", value: null };
165
+ }
166
+ const trimmed = val.trim();
167
+ if (trimmed === "") {
168
+ return { type: "invalid", value: null };
169
+ }
170
+ const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/;
171
+ if (timeRegex.test(trimmed)) {
172
+ const [hours, minutes, seconds = 0] = trimmed.split(":").map(Number);
173
+ const date = /* @__PURE__ */ new Date();
174
+ date.setHours(hours, minutes, seconds, 0);
175
+ return { type: "time", value: date };
176
+ }
177
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
178
+ if (dateRegex.test(trimmed)) {
179
+ const date = /* @__PURE__ */ new Date(trimmed + "T00:00:00.000Z");
180
+ return {
181
+ type: "date",
182
+ value: isNaN(date.getTime()) ? null : date
183
+ };
184
+ }
185
+ const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
186
+ if (dateTimeRegex.test(trimmed)) {
187
+ const date = /* @__PURE__ */ new Date(trimmed + ".000Z");
188
+ return {
189
+ type: "datetime",
190
+ value: isNaN(date.getTime()) ? null : date
191
+ };
192
+ }
193
+ return { type: "invalid", value: null };
194
+ };
195
+ function hasExplicitTime(date) {
196
+ return date.getUTCHours() !== 0 || date.getUTCMinutes() !== 0 || date.getUTCSeconds() !== 0 || date.getUTCMilliseconds() !== 0;
197
+ }
198
+ var dateIsBiggerThan = (min, errorMessage) => (val, label = "Dit") => {
199
+ if (val === null || val === void 0 || val === "") {
200
+ return null;
201
+ }
202
+ const parsed = parseDateValue(val);
203
+ if (!parsed.value) {
204
+ return `${label} moet een geldige ${parsed.type === "time" ? "tijd" : "datum"} zijn.`;
205
+ }
206
+ let formattedMin;
207
+ switch (parsed.type) {
208
+ case "time":
209
+ formattedMin = min.toLocaleTimeString("nl-NL", {
210
+ hour: "2-digit",
211
+ minute: "2-digit",
212
+ hour12: false,
213
+ timeZone: "UTC"
214
+ });
215
+ break;
216
+ case "date":
217
+ formattedMin = min.toLocaleDateString("nl-NL", { timeZone: "UTC" });
218
+ break;
219
+ case "datetime":
220
+ formattedMin = min.toLocaleString("nl-NL", {
221
+ year: "numeric",
222
+ month: "2-digit",
223
+ day: "2-digit",
224
+ hour: "2-digit",
225
+ minute: "2-digit",
226
+ hour12: false,
227
+ timeZone: "UTC"
228
+ });
229
+ break;
230
+ default:
231
+ formattedMin = min.toLocaleDateString("nl-NL", { timeZone: "UTC" });
232
+ }
233
+ if (parsed.type === "time") {
234
+ const timeValue = parsed.value.getHours() * 60 + parsed.value.getMinutes();
235
+ const minTime = min.getHours() * 60 + min.getMinutes();
236
+ return timeValue >= minTime ? null : errorMessage?.replace("{label}", label)?.replace("{min}", formattedMin) || `${label} mag niet voor ${formattedMin} zijn.`;
237
+ }
238
+ return min.getTime() <= parsed.value.getTime() ? null : errorMessage?.replace("{label}", label)?.replace("{min}", formattedMin) || `${label} mag niet voor ${formattedMin} zijn.`;
239
+ };
240
+ var dateIsSmallerThan = (max, errorMessage) => (val, label = "Dit") => {
241
+ if (val === null || val === void 0 || val === "") {
242
+ return null;
243
+ }
244
+ const parsed = parseDateValue(val);
245
+ if (!parsed.value) {
246
+ return `${label} moet een geldige ${parsed.type === "time" ? "tijd" : "datum"} zijn.`;
247
+ }
248
+ let formattedMax;
249
+ switch (parsed.type) {
250
+ case "time":
251
+ formattedMax = max.toLocaleTimeString("nl-NL", {
252
+ hour: "2-digit",
253
+ minute: "2-digit",
254
+ hour12: false,
255
+ timeZone: "UTC"
256
+ });
257
+ break;
258
+ case "date":
259
+ formattedMax = max.toLocaleDateString("nl-NL", { timeZone: "UTC" });
260
+ break;
261
+ case "datetime":
262
+ formattedMax = max.toLocaleString("nl-NL", {
263
+ year: "numeric",
264
+ month: "2-digit",
265
+ day: "2-digit",
266
+ hour: "2-digit",
267
+ minute: "2-digit",
268
+ hour12: false,
269
+ timeZone: "UTC"
270
+ });
271
+ break;
272
+ default:
273
+ formattedMax = max.toLocaleDateString("nl-NL", { timeZone: "UTC" });
274
+ }
275
+ if (parsed.type === "time") {
276
+ const timeValue = parsed.value.getHours() * 60 + parsed.value.getMinutes();
277
+ const macTime = max.getHours() * 60 + max.getMinutes();
278
+ return macTime >= timeValue ? null : errorMessage?.replace("{label}", label)?.replace("{min}", formattedMax) || `${label} mag niet na ${formattedMax} zijn.`;
279
+ }
280
+ return parsed.value.getTime() <= max.getTime() ? null : errorMessage?.replace("{label}", label)?.replace("{min}", formattedMax) || `${label} mag niet na ${formattedMax} zijn.`;
281
+ };
282
+ var isValidFile = (maxSize, accept) => (file) => {
283
+ if (!file) {
284
+ return null;
285
+ }
286
+ if (maxSize && file.size / 1024 > maxSize) {
287
+ return `${file.name} is te groot (max ${maxSize} KB).`;
288
+ }
289
+ if (accept) {
290
+ const allowed = accept.split(",").map((s) => s.trim().toLowerCase());
291
+ const ext = `.${file.name.split(".").pop()?.toLowerCase()}`;
292
+ const mime = file.type.toLowerCase();
293
+ const valid = allowed.some((pattern) => {
294
+ if (pattern === mime) return true;
295
+ if (pattern.endsWith("/*")) {
296
+ return mime.startsWith(pattern.replace("/*", "/"));
297
+ }
298
+ return pattern === ext;
299
+ });
300
+ if (!valid) {
301
+ return `${file.name} is niet toegelaten.`;
302
+ }
303
+ }
304
+ return null;
305
+ };
306
+ var isPattern = (pattern, errorMsg) => (val, label = "Dit") => {
307
+ if (val === null || val === void 0 || val === "") {
308
+ return null;
309
+ }
310
+ if (typeof val !== "string") {
311
+ return errorMsg || `${label} heeft een ongeldig patroon.`;
312
+ }
313
+ const trimmed = val.trim();
314
+ if (trimmed === "") {
315
+ return null;
316
+ }
317
+ let regex;
318
+ if (typeof pattern === "string") {
319
+ if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
320
+ const lastSlash = pattern.lastIndexOf("/");
321
+ const patternBody = pattern.substring(1, lastSlash);
322
+ const flags = pattern.substring(lastSlash + 1);
323
+ const safeFlags = flags.replace("g", "");
324
+ regex = new RegExp(patternBody, safeFlags);
325
+ } else {
326
+ regex = new RegExp(pattern);
327
+ }
328
+ } else {
329
+ regex = new RegExp(pattern.source, pattern.flags.replace("g", ""));
330
+ }
331
+ return regex.test(trimmed) ? null : errorMsg || `${label} heeft een ongeldig patroon.`;
332
+ };
80
333
 
81
334
  // src/utils/parse-date.ts
82
- var parseDateValue = (val) => {
335
+ var parseDateValue2 = (val) => {
83
336
  if (val instanceof Date) {
84
337
  return isNaN(val.getTime()) ? null : val;
85
338
  }
@@ -149,31 +402,31 @@ function getDefaultValue(config) {
149
402
  function getDefaultValidations(config) {
150
403
  const validations = [];
151
404
  if (config.config?.required) {
152
- validations.push((0, import_form_validation.isRequired)());
405
+ validations.push(isRequired());
153
406
  }
154
407
  switch (config.type) {
155
408
  case "email":
156
- validations.push((0, import_form_validation.isEmail)());
409
+ validations.push(isEmail());
157
410
  break;
158
411
  case "number":
159
412
  case "range":
160
- if (config.config?.min) validations.push((0, import_form_validation.isBiggerThan)(config.config?.min));
161
- if (config.config?.max) validations.push((0, import_form_validation.isSmallerThan)(config.config?.max));
413
+ if (config.config?.min) validations.push(isBiggerThan(config.config?.min));
414
+ if (config.config?.max) validations.push(isSmallerThan(config.config?.max));
162
415
  break;
163
416
  case "date":
164
417
  case "datetime-local":
165
418
  case "time":
166
419
  const min = config.config?.min;
167
420
  const max = config.config?.max;
168
- const parsedMin = parseDateValue(min);
169
- const parsedMax = parseDateValue(max);
170
- if (parsedMin) validations.push((0, import_form_validation.dateIsBiggerThan)(parsedMin));
171
- if (parsedMax) validations.push((0, import_form_validation.dateIsSmallerThan)(parsedMax));
421
+ const parsedMin = parseDateValue2(min);
422
+ const parsedMax = parseDateValue2(max);
423
+ if (parsedMin) validations.push(dateIsBiggerThan(parsedMin));
424
+ if (parsedMax) validations.push(dateIsSmallerThan(parsedMax));
172
425
  break;
173
426
  case "url":
174
- if (config.config?.pattern) validations.push((0, import_form_validation.isPattern)(config.config.pattern));
427
+ if (config.config?.pattern) validations.push(isPattern(config.config.pattern));
175
428
  validations.push(
176
- (0, import_form_validation.isUrl)(
429
+ isUrl(
177
430
  config.config?.allowLocalhost || false,
178
431
  config.config?.allowFtp || false,
179
432
  config.config?.secureOnly || !(config.config?.allowLocalhost || config.config?.allowFtp)
@@ -182,7 +435,7 @@ function getDefaultValidations(config) {
182
435
  break;
183
436
  case "text":
184
437
  case "tel":
185
- if (config.config?.pattern) validations.push((0, import_form_validation.isPattern)(config.config.pattern));
438
+ if (config.config?.pattern) validations.push(isPattern(config.config.pattern));
186
439
  break;
187
440
  }
188
441
  return validations;
@@ -1115,9 +1368,6 @@ var DateInput = ({
1115
1368
  );
1116
1369
  };
1117
1370
 
1118
- // src/components/Fields/Input/FileInput/index.tsx
1119
- var import_form_validation2 = require("@sio/form-validation");
1120
-
1121
1371
  // src/utils/get-accept-string.ts
1122
1372
  var getAccept = (accept) => {
1123
1373
  if (Array.isArray(accept)) {
@@ -1248,7 +1498,7 @@ var FileInput = ({
1248
1498
  };
1249
1499
  const handleChange = (e) => {
1250
1500
  const files = Array.from(e.target.files ?? []);
1251
- const validator = (0, import_form_validation2.isValidFile)(filesize, getAccept(accept));
1501
+ const validator = isValidFile(filesize, getAccept(accept));
1252
1502
  let fileList = [...currentFiles];
1253
1503
  const errorList = [];
1254
1504
  for (let file of files) {
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React, { CSSProperties, ComponentType, ComponentPropsWithoutRef, HTMLAttributes } from 'react';
3
- import { LayoutType, FormField, IconType, AcceptType, CaptureType, SpinnerVariant, SelectOption, Option, FieldConfigMap } from '@sio-group/form-types';
3
+ import { LayoutType, FormField, IconType, AcceptType, CaptureType, SpinnerVariant, SelectOption, Option, FieldConfigMap } from '@sio/form-types';
4
4
  import { Properties } from 'csstype';
5
- import { ValidationRule } from '@sio-group/form-types/src/core/valudation-rule';
5
+ import { ValidationRule } from '@sio/form-types/src/core/valudation-rule';
6
6
 
7
7
  interface FormLayout {
8
8
  fields: string[];
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React, { CSSProperties, ComponentType, ComponentPropsWithoutRef, HTMLAttributes } from 'react';
3
- import { LayoutType, FormField, IconType, AcceptType, CaptureType, SpinnerVariant, SelectOption, Option, FieldConfigMap } from '@sio-group/form-types';
3
+ import { LayoutType, FormField, IconType, AcceptType, CaptureType, SpinnerVariant, SelectOption, Option, FieldConfigMap } from '@sio/form-types';
4
4
  import { Properties } from 'csstype';
5
- import { ValidationRule } from '@sio-group/form-types/src/core/valudation-rule';
5
+ import { ValidationRule } from '@sio/form-types/src/core/valudation-rule';
6
6
 
7
7
  interface FormLayout {
8
8
  fields: string[];