@lub-crm/forms 1.0.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 (52) hide show
  1. package/README.md +298 -0
  2. package/dist/lub-forms.css +1 -0
  3. package/dist/lub-forms.es.js +5848 -0
  4. package/dist/lub-forms.es.js.map +1 -0
  5. package/dist/lub-forms.standalone.js +10 -0
  6. package/dist/lub-forms.standalone.js.map +1 -0
  7. package/dist/lub-forms.umd.js +227 -0
  8. package/dist/lub-forms.umd.js.map +1 -0
  9. package/package.json +68 -0
  10. package/src/api/client.ts +115 -0
  11. package/src/api/index.ts +2 -0
  12. package/src/api/types.ts +202 -0
  13. package/src/core/FormProvider.tsx +228 -0
  14. package/src/core/FormRenderer.tsx +134 -0
  15. package/src/core/LubForm.tsx +476 -0
  16. package/src/core/StepManager.tsx +199 -0
  17. package/src/core/index.ts +4 -0
  18. package/src/embed.ts +188 -0
  19. package/src/fields/CheckboxField.tsx +62 -0
  20. package/src/fields/CheckboxGroupField.tsx +57 -0
  21. package/src/fields/CountryField.tsx +43 -0
  22. package/src/fields/DateField.tsx +33 -0
  23. package/src/fields/DateTimeField.tsx +33 -0
  24. package/src/fields/DividerField.tsx +16 -0
  25. package/src/fields/FieldWrapper.tsx +60 -0
  26. package/src/fields/FileField.tsx +45 -0
  27. package/src/fields/HiddenField.tsx +18 -0
  28. package/src/fields/HtmlField.tsx +17 -0
  29. package/src/fields/NumberField.tsx +39 -0
  30. package/src/fields/RadioField.tsx +57 -0
  31. package/src/fields/RecaptchaField.tsx +137 -0
  32. package/src/fields/SelectField.tsx +49 -0
  33. package/src/fields/StateField.tsx +84 -0
  34. package/src/fields/TextField.tsx +51 -0
  35. package/src/fields/TextareaField.tsx +37 -0
  36. package/src/fields/TimeField.tsx +33 -0
  37. package/src/fields/index.ts +84 -0
  38. package/src/hooks/index.ts +4 -0
  39. package/src/hooks/useConditionalLogic.ts +59 -0
  40. package/src/hooks/useFormApi.ts +118 -0
  41. package/src/hooks/useFormDesign.ts +48 -0
  42. package/src/hooks/useMultiStep.ts +98 -0
  43. package/src/index.ts +101 -0
  44. package/src/main.tsx +40 -0
  45. package/src/styles/index.css +707 -0
  46. package/src/utils/cn.ts +6 -0
  47. package/src/utils/countries.ts +163 -0
  48. package/src/utils/css-variables.ts +63 -0
  49. package/src/utils/index.ts +3 -0
  50. package/src/validation/conditional.ts +170 -0
  51. package/src/validation/index.ts +2 -0
  52. package/src/validation/schema-builder.ts +327 -0
@@ -0,0 +1,707 @@
1
+ /* ==========================================================================
2
+ Lub Forms - Embeddable Form Styles
3
+ ========================================================================== */
4
+
5
+ /* CSS Custom Properties (Design Tokens) */
6
+ .lub-form {
7
+ --lub-bg-color: #ffffff;
8
+ --lub-text-color: #1f2937;
9
+ --lub-primary-color: #3b82f6;
10
+ --lub-primary-hover: #2563eb;
11
+ --lub-border-color: #d1d5db;
12
+ --lub-error-color: #ef4444;
13
+ --lub-success-color: #10b981;
14
+ --lub-muted-color: #6b7280;
15
+ --lub-font-family:
16
+ system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
17
+ "Helvetica Neue", Arial, sans-serif;
18
+ --lub-font-size: 16px;
19
+ --lub-padding: 24px;
20
+ --lub-field-spacing: 16px;
21
+ --lub-border-radius: 6px;
22
+ --lub-input-height: 42px;
23
+ --lub-btn-bg: var(--lub-primary-color);
24
+ --lub-btn-text: #ffffff;
25
+ --lub-btn-radius: var(--lub-border-radius);
26
+ --lub-focus-ring: 0 0 0 3px rgba(59, 130, 246, 0.3);
27
+ --lub-transition: 150ms ease;
28
+ }
29
+
30
+ /* Base Form Container */
31
+ .lub-form {
32
+ font-family: var(--lub-font-family);
33
+ font-size: var(--lub-font-size);
34
+ color: var(--lub-text-color);
35
+ background-color: var(--lub-bg-color);
36
+ padding: var(--lub-padding);
37
+ border-radius: var(--lub-border-radius);
38
+ box-sizing: border-box;
39
+ line-height: 1.5;
40
+ }
41
+
42
+ .lub-form *,
43
+ .lub-form *::before,
44
+ .lub-form *::after {
45
+ box-sizing: border-box;
46
+ }
47
+
48
+ /* Form Element */
49
+ .lub-form__form {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: var(--lub-field-spacing);
53
+ }
54
+
55
+ /* Header */
56
+ .lub-form__header {
57
+ margin-bottom: var(--lub-field-spacing);
58
+ }
59
+
60
+ .lub-form__title {
61
+ font-size: 1.5em;
62
+ font-weight: 600;
63
+ margin: 0 0 0.5em;
64
+ color: var(--lub-text-color);
65
+ }
66
+
67
+ .lub-form__description {
68
+ margin: 0;
69
+ color: var(--lub-muted-color);
70
+ }
71
+
72
+ /* ==========================================================================
73
+ Fields Container
74
+ ========================================================================== */
75
+
76
+ .lub-form__fields {
77
+ display: flex;
78
+ flex-direction: column;
79
+ gap: var(--lub-field-spacing);
80
+ }
81
+
82
+ .lub-form__fields--two_column {
83
+ display: flex;
84
+ flex-wrap: wrap;
85
+ gap: var(--lub-field-spacing);
86
+ }
87
+
88
+ .lub-form__field-group {
89
+ display: contents;
90
+ }
91
+
92
+ .lub-form__field-group--row {
93
+ display: flex;
94
+ flex-wrap: wrap;
95
+ gap: var(--lub-field-spacing);
96
+ width: 100%;
97
+ }
98
+
99
+ /* Field Widths */
100
+ .lub-form__field {
101
+ width: 100%;
102
+ }
103
+
104
+ .lub-form__field--full {
105
+ width: 100%;
106
+ }
107
+
108
+ .lub-form__field--half {
109
+ width: calc(50% - var(--lub-field-spacing) / 2);
110
+ }
111
+
112
+ .lub-form__field--third {
113
+ width: calc(33.333% - var(--lub-field-spacing) * 2 / 3);
114
+ }
115
+
116
+ .lub-form__field--quarter {
117
+ width: calc(25% - var(--lub-field-spacing) * 3 / 4);
118
+ }
119
+
120
+ @media (max-width: 640px) {
121
+ .lub-form__field--half,
122
+ .lub-form__field--third,
123
+ .lub-form__field--quarter {
124
+ width: 100%;
125
+ }
126
+ }
127
+
128
+ /* ==========================================================================
129
+ Field Wrapper
130
+ ========================================================================== */
131
+
132
+ .lub-form__field-wrapper {
133
+ display: flex;
134
+ flex-direction: column;
135
+ gap: 6px;
136
+ }
137
+
138
+ /* ==========================================================================
139
+ Labels
140
+ ========================================================================== */
141
+
142
+ .lub-form__label {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 4px;
146
+ font-size: 0.875em;
147
+ font-weight: 500;
148
+ color: var(--lub-text-color);
149
+ }
150
+
151
+ .lub-form__required-indicator {
152
+ color: var(--lub-error-color);
153
+ font-weight: 400;
154
+ }
155
+
156
+ /* ==========================================================================
157
+ Inputs
158
+ ========================================================================== */
159
+
160
+ .lub-form__input,
161
+ .lub-form__textarea,
162
+ .lub-form__select {
163
+ width: 100%;
164
+ padding: 10px 12px;
165
+ font-family: inherit;
166
+ font-size: 1em;
167
+ color: var(--lub-text-color);
168
+ background-color: var(--lub-bg-color);
169
+ border: 1px solid var(--lub-border-color);
170
+ border-radius: var(--lub-border-radius);
171
+ transition:
172
+ border-color var(--lub-transition),
173
+ box-shadow var(--lub-transition);
174
+ outline: none;
175
+ }
176
+
177
+ .lub-form__input {
178
+ height: var(--lub-input-height);
179
+ }
180
+
181
+ .lub-form__textarea {
182
+ min-height: 100px;
183
+ resize: vertical;
184
+ }
185
+
186
+ .lub-form__select {
187
+ height: var(--lub-input-height);
188
+ cursor: pointer;
189
+ appearance: none;
190
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%236b7280'%3E%3Cpath fill-rule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clip-rule='evenodd'/%3E%3C/svg%3E");
191
+ background-repeat: no-repeat;
192
+ background-position: right 10px center;
193
+ padding-right: 36px;
194
+ }
195
+
196
+ .lub-form__input::placeholder,
197
+ .lub-form__textarea::placeholder {
198
+ color: var(--lub-muted-color);
199
+ }
200
+
201
+ /* Focus States */
202
+ .lub-form__input:focus,
203
+ .lub-form__textarea:focus,
204
+ .lub-form__select:focus {
205
+ border-color: var(--lub-primary-color);
206
+ box-shadow: var(--lub-focus-ring);
207
+ }
208
+
209
+ /* Error States */
210
+ .lub-form__input--error,
211
+ .lub-form__textarea--error,
212
+ .lub-form__select--error {
213
+ border-color: var(--lub-error-color);
214
+ }
215
+
216
+ .lub-form__input--error:focus,
217
+ .lub-form__textarea--error:focus,
218
+ .lub-form__select--error:focus {
219
+ border-color: var(--lub-error-color);
220
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.3);
221
+ }
222
+
223
+ /* ==========================================================================
224
+ Checkboxes & Radios
225
+ ========================================================================== */
226
+
227
+ .lub-form__checkbox-group,
228
+ .lub-form__radio-group {
229
+ display: flex;
230
+ flex-direction: column;
231
+ gap: 8px;
232
+ }
233
+
234
+ .lub-form__checkbox-label,
235
+ .lub-form__radio-label {
236
+ display: flex;
237
+ align-items: flex-start;
238
+ gap: 10px;
239
+ cursor: pointer;
240
+ font-size: 0.9375em;
241
+ }
242
+
243
+ .lub-form__checkbox,
244
+ .lub-form__radio {
245
+ width: 18px;
246
+ height: 18px;
247
+ margin: 2px 0 0;
248
+ flex-shrink: 0;
249
+ cursor: pointer;
250
+ accent-color: var(--lub-primary-color);
251
+ }
252
+
253
+ .lub-form__checkbox-text,
254
+ .lub-form__radio-text {
255
+ color: var(--lub-text-color);
256
+ }
257
+
258
+ /* ==========================================================================
259
+ File Input
260
+ ========================================================================== */
261
+
262
+ .lub-form__file-input {
263
+ width: 100%;
264
+ padding: 10px 12px;
265
+ font-family: inherit;
266
+ font-size: 0.9375em;
267
+ color: var(--lub-text-color);
268
+ background-color: var(--lub-bg-color);
269
+ border: 1px dashed var(--lub-border-color);
270
+ border-radius: var(--lub-border-radius);
271
+ cursor: pointer;
272
+ transition:
273
+ border-color var(--lub-transition),
274
+ background-color var(--lub-transition);
275
+ }
276
+
277
+ .lub-form__file-input:hover {
278
+ background-color: rgba(0, 0, 0, 0.02);
279
+ }
280
+
281
+ .lub-form__file-input:focus {
282
+ border-color: var(--lub-primary-color);
283
+ outline: none;
284
+ }
285
+
286
+ .lub-form__file-hint {
287
+ margin: 4px 0 0;
288
+ font-size: 0.8125em;
289
+ color: var(--lub-muted-color);
290
+ }
291
+
292
+ /* ==========================================================================
293
+ Help Text & Errors
294
+ ========================================================================== */
295
+
296
+ .lub-form__help-text {
297
+ margin: 0;
298
+ font-size: 0.8125em;
299
+ color: var(--lub-muted-color);
300
+ }
301
+
302
+ .lub-form__error {
303
+ margin: 0;
304
+ font-size: 0.8125em;
305
+ color: var(--lub-error-color);
306
+ }
307
+
308
+ /* ==========================================================================
309
+ HTML Content & Divider
310
+ ========================================================================== */
311
+
312
+ .lub-form__html-content {
313
+ padding: 8px 0;
314
+ color: var(--lub-text-color);
315
+ }
316
+
317
+ .lub-form__html-content a {
318
+ color: var(--lub-primary-color);
319
+ text-decoration: underline;
320
+ }
321
+
322
+ .lub-form__divider {
323
+ display: flex;
324
+ align-items: center;
325
+ gap: 12px;
326
+ padding: 8px 0;
327
+ }
328
+
329
+ .lub-form__divider-line {
330
+ flex: 1;
331
+ height: 1px;
332
+ background-color: var(--lub-border-color);
333
+ border: none;
334
+ margin: 0;
335
+ }
336
+
337
+ .lub-form__divider-label {
338
+ font-size: 0.875em;
339
+ color: var(--lub-muted-color);
340
+ white-space: nowrap;
341
+ }
342
+
343
+ /* ==========================================================================
344
+ Consent Checkbox
345
+ ========================================================================== */
346
+
347
+ .lub-form__consent {
348
+ padding-top: var(--lub-field-spacing);
349
+ }
350
+
351
+ .lub-form__consent-label {
352
+ display: flex;
353
+ align-items: flex-start;
354
+ gap: 10px;
355
+ cursor: pointer;
356
+ }
357
+
358
+ .lub-form__consent-checkbox {
359
+ width: 18px;
360
+ height: 18px;
361
+ margin-top: 2px;
362
+ flex-shrink: 0;
363
+ cursor: pointer;
364
+ accent-color: var(--lub-primary-color);
365
+ }
366
+
367
+ .lub-form__consent-text {
368
+ font-size: 0.875em;
369
+ color: var(--lub-text-color);
370
+ }
371
+
372
+ .lub-form__consent-text a {
373
+ color: var(--lub-primary-color);
374
+ text-decoration: underline;
375
+ }
376
+
377
+ /* ==========================================================================
378
+ Buttons
379
+ ========================================================================== */
380
+
381
+ .lub-form__actions {
382
+ display: flex;
383
+ justify-content: flex-end;
384
+ padding-top: var(--lub-field-spacing);
385
+ }
386
+
387
+ .lub-form__button {
388
+ display: inline-flex;
389
+ align-items: center;
390
+ justify-content: center;
391
+ gap: 8px;
392
+ padding: 12px 24px;
393
+ font-family: inherit;
394
+ font-size: 1em;
395
+ font-weight: 500;
396
+ border: none;
397
+ border-radius: var(--lub-btn-radius);
398
+ cursor: pointer;
399
+ transition:
400
+ background-color var(--lub-transition),
401
+ opacity var(--lub-transition);
402
+ }
403
+
404
+ .lub-form__button--primary {
405
+ color: var(--lub-btn-text);
406
+ background-color: var(--lub-btn-bg);
407
+ }
408
+
409
+ .lub-form__button--primary:hover:not(:disabled) {
410
+ background-color: var(--lub-primary-hover);
411
+ }
412
+
413
+ .lub-form__button--secondary {
414
+ color: var(--lub-text-color);
415
+ background-color: transparent;
416
+ border: 1px solid var(--lub-border-color);
417
+ }
418
+
419
+ .lub-form__button--secondary:hover:not(:disabled) {
420
+ background-color: rgba(0, 0, 0, 0.05);
421
+ }
422
+
423
+ .lub-form__button:disabled {
424
+ opacity: 0.6;
425
+ cursor: not-allowed;
426
+ }
427
+
428
+ .lub-form__button-loading {
429
+ display: inline-flex;
430
+ align-items: center;
431
+ gap: 8px;
432
+ }
433
+
434
+ /* ==========================================================================
435
+ Step Navigation
436
+ ========================================================================== */
437
+
438
+ .lub-form__navigation {
439
+ display: flex;
440
+ align-items: center;
441
+ gap: 12px;
442
+ padding-top: var(--lub-field-spacing);
443
+ }
444
+
445
+ .lub-form__navigation-spacer {
446
+ flex: 1;
447
+ }
448
+
449
+ /* ==========================================================================
450
+ Steps Indicator
451
+ ========================================================================== */
452
+
453
+ .lub-form__steps {
454
+ padding-bottom: var(--lub-field-spacing);
455
+ }
456
+
457
+ .lub-form__steps-list {
458
+ display: flex;
459
+ align-items: center;
460
+ list-style: none;
461
+ margin: 0;
462
+ padding: 0;
463
+ gap: 0;
464
+ }
465
+
466
+ .lub-form__step {
467
+ display: flex;
468
+ align-items: center;
469
+ flex: 1;
470
+ }
471
+
472
+ .lub-form__step:last-child {
473
+ flex: 0 0 auto;
474
+ }
475
+
476
+ .lub-form__step-indicator,
477
+ .lub-form__step-button {
478
+ display: flex;
479
+ align-items: center;
480
+ gap: 8px;
481
+ padding: 8px;
482
+ background: none;
483
+ border: none;
484
+ font-family: inherit;
485
+ font-size: 0.875em;
486
+ color: var(--lub-muted-color);
487
+ }
488
+
489
+ .lub-form__step-button {
490
+ cursor: pointer;
491
+ }
492
+
493
+ .lub-form__step-button:hover {
494
+ color: var(--lub-primary-color);
495
+ }
496
+
497
+ .lub-form__step-number {
498
+ display: flex;
499
+ align-items: center;
500
+ justify-content: center;
501
+ width: 28px;
502
+ height: 28px;
503
+ border-radius: 50%;
504
+ background-color: var(--lub-border-color);
505
+ color: var(--lub-muted-color);
506
+ font-weight: 500;
507
+ font-size: 0.8125em;
508
+ }
509
+
510
+ .lub-form__step--active .lub-form__step-number {
511
+ background-color: var(--lub-primary-color);
512
+ color: white;
513
+ }
514
+
515
+ .lub-form__step--completed .lub-form__step-number {
516
+ background-color: var(--lub-success-color);
517
+ color: white;
518
+ }
519
+
520
+ .lub-form__step-name {
521
+ display: none;
522
+ }
523
+
524
+ @media (min-width: 640px) {
525
+ .lub-form__step-name {
526
+ display: inline;
527
+ }
528
+ }
529
+
530
+ .lub-form__step--active .lub-form__step-indicator,
531
+ .lub-form__step--active .lub-form__step-button {
532
+ color: var(--lub-text-color);
533
+ }
534
+
535
+ .lub-form__step-connector {
536
+ flex: 1;
537
+ height: 2px;
538
+ background-color: var(--lub-border-color);
539
+ margin: 0 8px;
540
+ }
541
+
542
+ .lub-form__step-connector--completed {
543
+ background-color: var(--lub-success-color);
544
+ }
545
+
546
+ .lub-form__check-icon {
547
+ width: 16px;
548
+ height: 16px;
549
+ }
550
+
551
+ .lub-form__step-description {
552
+ margin: 12px 0 0;
553
+ font-size: 0.875em;
554
+ color: var(--lub-muted-color);
555
+ }
556
+
557
+ /* ==========================================================================
558
+ Loading Spinner
559
+ ========================================================================== */
560
+
561
+ .lub-form__spinner {
562
+ width: 18px;
563
+ height: 18px;
564
+ animation: lub-spin 1s linear infinite;
565
+ }
566
+
567
+ .lub-form__spinner-track {
568
+ opacity: 0.25;
569
+ }
570
+
571
+ .lub-form__spinner-head {
572
+ opacity: 1;
573
+ }
574
+
575
+ @keyframes lub-spin {
576
+ from {
577
+ transform: rotate(0deg);
578
+ }
579
+ to {
580
+ transform: rotate(360deg);
581
+ }
582
+ }
583
+
584
+ /* ==========================================================================
585
+ Loading Skeleton
586
+ ========================================================================== */
587
+
588
+ .lub-form--loading .lub-form__skeleton {
589
+ display: flex;
590
+ flex-direction: column;
591
+ gap: var(--lub-field-spacing);
592
+ }
593
+
594
+ .lub-form__skeleton-title,
595
+ .lub-form__skeleton-field,
596
+ .lub-form__skeleton-button {
597
+ background: linear-gradient(
598
+ 90deg,
599
+ var(--lub-border-color) 25%,
600
+ rgba(0, 0, 0, 0.05) 50%,
601
+ var(--lub-border-color) 75%
602
+ );
603
+ background-size: 200% 100%;
604
+ animation: lub-shimmer 1.5s infinite;
605
+ border-radius: var(--lub-border-radius);
606
+ }
607
+
608
+ .lub-form__skeleton-title {
609
+ height: 28px;
610
+ width: 60%;
611
+ margin-bottom: 8px;
612
+ }
613
+
614
+ .lub-form__skeleton-field {
615
+ height: var(--lub-input-height);
616
+ }
617
+
618
+ .lub-form__skeleton-field--short {
619
+ width: 50%;
620
+ }
621
+
622
+ .lub-form__skeleton-button {
623
+ height: 46px;
624
+ width: 120px;
625
+ margin-left: auto;
626
+ }
627
+
628
+ @keyframes lub-shimmer {
629
+ 0% {
630
+ background-position: 200% 0;
631
+ }
632
+ 100% {
633
+ background-position: -200% 0;
634
+ }
635
+ }
636
+
637
+ /* ==========================================================================
638
+ Success State
639
+ ========================================================================== */
640
+
641
+ .lub-form--success .lub-form__success {
642
+ display: flex;
643
+ flex-direction: column;
644
+ align-items: center;
645
+ justify-content: center;
646
+ text-align: center;
647
+ padding: var(--lub-padding);
648
+ min-height: 200px;
649
+ }
650
+
651
+ .lub-form__success-icon {
652
+ width: 64px;
653
+ height: 64px;
654
+ color: var(--lub-success-color);
655
+ margin-bottom: 16px;
656
+ }
657
+
658
+ .lub-form__success-message {
659
+ font-size: 1.125em;
660
+ color: var(--lub-text-color);
661
+ margin: 0;
662
+ }
663
+
664
+ /* ==========================================================================
665
+ Error State
666
+ ========================================================================== */
667
+
668
+ .lub-form--error .lub-form__error-container {
669
+ display: flex;
670
+ flex-direction: column;
671
+ align-items: center;
672
+ justify-content: center;
673
+ text-align: center;
674
+ padding: var(--lub-padding);
675
+ min-height: 150px;
676
+ }
677
+
678
+ .lub-form__error-message {
679
+ font-size: 1em;
680
+ color: var(--lub-error-color);
681
+ margin: 0;
682
+ }
683
+
684
+ /* ==========================================================================
685
+ reCAPTCHA
686
+ ========================================================================== */
687
+
688
+ .lub-form__recaptcha {
689
+ display: flex;
690
+ justify-content: flex-start;
691
+ padding: 8px 0;
692
+ }
693
+
694
+ .lub-form__recaptcha-widget {
695
+ transform-origin: left top;
696
+ }
697
+
698
+ /* ==========================================================================
699
+ Dark Theme Override
700
+ ========================================================================== */
701
+
702
+ .lub-form[data-theme="dark"] {
703
+ --lub-bg-color: #1f2937;
704
+ --lub-text-color: #f9fafb;
705
+ --lub-border-color: #4b5563;
706
+ --lub-muted-color: #9ca3af;
707
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Combine class names, filtering out falsy values
3
+ */
4
+ export function cn(...classes: (string | undefined | null | false)[]): string {
5
+ return classes.filter(Boolean).join(" ");
6
+ }