@page-speed/forms 0.5.0 → 0.5.2

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.
package/dist/core.js CHANGED
@@ -1,9 +1,11 @@
1
- import * as React3 from 'react';
1
+ import * as React4 from 'react';
2
2
  import { useRef, useCallback, useContext } from 'react';
3
3
  import { useObservable, useSelector } from '@legendapp/state/react';
4
4
  import { useMap } from '@opensite/hooks/useMap';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
+ import { cva } from 'class-variance-authority';
8
+ import { Label as Label$1, Slot } from 'radix-ui';
7
9
 
8
10
  // src/core/useForm.ts
9
11
  function useForm(options) {
@@ -249,7 +251,7 @@ function useForm(options) {
249
251
  getFieldMeta
250
252
  };
251
253
  }
252
- var FormContext = React3.createContext(null);
254
+ var FormContext = React4.createContext(null);
253
255
  FormContext.displayName = "FormContext";
254
256
 
255
257
  // src/core/useField.ts
@@ -317,7 +319,7 @@ function cn(...inputs) {
317
319
  // src/core/form-feedback.tsx
318
320
  function renderMessage(message, fallbackClassName, className) {
319
321
  if (typeof message === "string") {
320
- return /* @__PURE__ */ React3.createElement(
322
+ return /* @__PURE__ */ React4.createElement(
321
323
  "p",
322
324
  {
323
325
  className: cn(
@@ -328,7 +330,7 @@ function renderMessage(message, fallbackClassName, className) {
328
330
  message
329
331
  );
330
332
  }
331
- return /* @__PURE__ */ React3.createElement("div", { className: cn(fallbackClassName, className) }, message);
333
+ return /* @__PURE__ */ React4.createElement("div", { className: cn(fallbackClassName, className) }, message);
332
334
  }
333
335
  function FormFeedback({
334
336
  successMessage,
@@ -339,7 +341,7 @@ function FormFeedback({
339
341
  if (!successMessage && !submissionError) {
340
342
  return null;
341
343
  }
342
- return /* @__PURE__ */ React3.createElement(React3.Fragment, null, successMessage ? /* @__PURE__ */ React3.createElement(
344
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, null, successMessage ? /* @__PURE__ */ React4.createElement(
343
345
  "div",
344
346
  {
345
347
  className: cn(
@@ -354,7 +356,7 @@ function FormFeedback({
354
356
  "text-primary-foreground",
355
357
  "text-primary-foreground"
356
358
  )
357
- ) : null, submissionError ? /* @__PURE__ */ React3.createElement(
359
+ ) : null, submissionError ? /* @__PURE__ */ React4.createElement(
358
360
  "div",
359
361
  {
360
362
  className: cn(
@@ -372,6 +374,54 @@ function FormFeedback({
372
374
  ) : null);
373
375
  }
374
376
  FormFeedback.displayName = "FormFeedback";
377
+ var buttonVariants = cva(
378
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
379
+ {
380
+ variants: {
381
+ variant: {
382
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
383
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20",
384
+ outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
385
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
386
+ ghost: "hover:bg-accent hover:text-accent-foreground",
387
+ link: "text-primary underline-offset-4 hover:underline"
388
+ },
389
+ size: {
390
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
391
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
392
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
393
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
394
+ icon: "size-9",
395
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
396
+ "icon-sm": "size-8",
397
+ "icon-lg": "size-10"
398
+ }
399
+ },
400
+ defaultVariants: {
401
+ variant: "default",
402
+ size: "default"
403
+ }
404
+ }
405
+ );
406
+ function Button({
407
+ className,
408
+ variant = "default",
409
+ size = "default",
410
+ asChild = false,
411
+ ...props
412
+ }) {
413
+ const Comp = asChild ? Slot.Root : "button";
414
+ return /* @__PURE__ */ React4.createElement(
415
+ Comp,
416
+ {
417
+ "data-slot": "button",
418
+ "data-variant": variant,
419
+ "data-size": size,
420
+ className: cn(buttonVariants({ variant, size, className })),
421
+ ...props
422
+ }
423
+ );
424
+ }
375
425
 
376
426
  // src/core/Form.tsx
377
427
  function Form({
@@ -379,7 +429,7 @@ function Form({
379
429
  children,
380
430
  className,
381
431
  action,
382
- method = "post",
432
+ method,
383
433
  noValidate = true,
384
434
  submissionConfig,
385
435
  successMessage,
@@ -387,9 +437,12 @@ function Form({
387
437
  successMessageClassName,
388
438
  errorMessageClassName,
389
439
  onNewSubmission,
440
+ notificationConfig,
441
+ styleConfig,
442
+ formConfig,
390
443
  ...props
391
444
  }) {
392
- const handleFormSubmit = React3.useCallback(
445
+ const handleFormSubmit = React4.useCallback(
393
446
  async (e) => {
394
447
  try {
395
448
  await form.handleSubmit(e);
@@ -398,71 +451,102 @@ function Form({
398
451
  },
399
452
  [form]
400
453
  );
401
- const behavior = submissionConfig?.behavior || "showConfirmation";
402
- const shouldManageSubmissionUi = submissionConfig !== void 0 || successMessage !== void 0 || successMessageClassName !== void 0 || errorMessageClassName !== void 0 || submissionError != null || onNewSubmission !== void 0;
403
- const hasSubmissionError = Boolean(submissionError);
454
+ const resolvedClassName = className ?? styleConfig?.formClassName;
455
+ const resolvedAction = action ?? formConfig?.endpoint;
456
+ const resolvedMethod = method ?? formConfig?.method ?? "post";
457
+ const resolvedSubmissionConfig = submissionConfig ?? formConfig?.submissionConfig;
458
+ const resolvedSuccessMessage = successMessage ?? notificationConfig?.successMessage;
459
+ const resolvedSubmissionError = submissionError ?? notificationConfig?.submissionError;
460
+ const resolvedSuccessMessageClassName = successMessageClassName ?? styleConfig?.successMessageClassName;
461
+ const resolvedErrorMessageClassName = errorMessageClassName ?? styleConfig?.errorMessageClassName;
462
+ const behavior = resolvedSubmissionConfig?.behavior || "showConfirmation";
463
+ const shouldManageSubmissionUi = resolvedSubmissionConfig !== void 0 || resolvedSuccessMessage !== void 0 || resolvedSuccessMessageClassName !== void 0 || resolvedErrorMessageClassName !== void 0 || resolvedSubmissionError != null || onNewSubmission !== void 0;
464
+ const hasSubmissionError = Boolean(resolvedSubmissionError);
404
465
  const isSubmissionSuccessful = shouldManageSubmissionUi && form.status === "success" && !hasSubmissionError;
405
466
  const defaultSuccessMessage = behavior === "redirect" ? "Form submitted successfully. Redirecting..." : "Thank you. Your form has been submitted successfully.";
406
- const resolvedSuccessMessage = successMessage ?? defaultSuccessMessage;
407
- const shouldRenderCustomComponent = isSubmissionSuccessful && behavior === "renderCustomComponent" && Boolean(submissionConfig?.customComponent);
408
- const newSubmissionAction = submissionConfig?.newFormSubmissionAction;
467
+ const finalSuccessMessage = resolvedSuccessMessage ?? defaultSuccessMessage;
468
+ const shouldRenderCustomComponent = isSubmissionSuccessful && behavior === "renderCustomComponent" && Boolean(resolvedSubmissionConfig?.customComponent);
469
+ const newSubmissionAction = resolvedSubmissionConfig?.newFormSubmissionAction;
409
470
  const showNewSubmissionAction = isSubmissionSuccessful && (typeof newSubmissionAction?.enable === "boolean" ? newSubmissionAction.enable : Boolean(newSubmissionAction?.label));
410
471
  const newSubmissionLabel = newSubmissionAction?.label ?? "Submit another response";
411
- const handleNewSubmission = React3.useCallback(() => {
472
+ const handleNewSubmission = React4.useCallback(() => {
412
473
  form.resetForm();
413
474
  onNewSubmission?.();
414
475
  }, [form, onNewSubmission]);
415
- return /* @__PURE__ */ React3.createElement(FormContext.Provider, { value: form }, /* @__PURE__ */ React3.createElement(
476
+ return /* @__PURE__ */ React4.createElement(FormContext.Provider, { value: form }, /* @__PURE__ */ React4.createElement(
416
477
  "form",
417
478
  {
418
479
  onSubmit: handleFormSubmit,
419
- action,
420
- method,
480
+ action: resolvedAction,
481
+ method: resolvedMethod,
421
482
  noValidate,
422
- className,
483
+ className: resolvedClassName,
423
484
  ...props
424
485
  },
425
- isSubmissionSuccessful ? /* @__PURE__ */ React3.createElement("div", { className: "space-y-4" }, shouldRenderCustomComponent ? submissionConfig?.customComponent : /* @__PURE__ */ React3.createElement(
486
+ isSubmissionSuccessful ? /* @__PURE__ */ React4.createElement("div", { className: "space-y-4" }, shouldRenderCustomComponent ? resolvedSubmissionConfig?.customComponent : /* @__PURE__ */ React4.createElement(
426
487
  FormFeedback,
427
488
  {
428
- successMessage: resolvedSuccessMessage,
429
- successMessageClassName
489
+ successMessage: finalSuccessMessage,
490
+ successMessageClassName: resolvedSuccessMessageClassName
430
491
  }
431
- ), showNewSubmissionAction ? /* @__PURE__ */ React3.createElement(
432
- "button",
492
+ ), showNewSubmissionAction ? /* @__PURE__ */ React4.createElement(
493
+ Button,
433
494
  {
434
495
  type: "button",
435
- onClick: handleNewSubmission,
436
- className: cn(
437
- "inline-flex items-center justify-center whitespace-nowrap rounded-md border border-input bg-transparent px-4 py-2 text-sm font-medium shadow-sm transition-colors",
438
- "hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
439
- )
496
+ variant: "outline",
497
+ onClick: handleNewSubmission
440
498
  },
441
499
  newSubmissionLabel
442
- ) : null) : /* @__PURE__ */ React3.createElement(React3.Fragment, null, children, submissionError ? /* @__PURE__ */ React3.createElement("div", { className: "mt-4" }, /* @__PURE__ */ React3.createElement(
500
+ ) : null) : /* @__PURE__ */ React4.createElement(React4.Fragment, null, children, resolvedSubmissionError ? /* @__PURE__ */ React4.createElement("div", { className: "mt-4" }, /* @__PURE__ */ React4.createElement(
443
501
  FormFeedback,
444
502
  {
445
- submissionError,
446
- errorMessageClassName
503
+ submissionError: resolvedSubmissionError,
504
+ errorMessageClassName: resolvedErrorMessageClassName
447
505
  }
448
506
  )) : null)
449
507
  ));
450
508
  }
451
509
  Form.displayName = "Form";
452
- var Field = React3.forwardRef(({ className, ...props }, ref) => {
453
- return /* @__PURE__ */ React3.createElement(
454
- "div",
510
+ function Label({
511
+ className,
512
+ ...props
513
+ }) {
514
+ return /* @__PURE__ */ React4.createElement(
515
+ Label$1.Root,
455
516
  {
456
- ref,
457
- "data-slot": "field",
458
- className: cn("flex flex-col gap-1.5", className),
517
+ "data-slot": "label",
518
+ className: cn(
519
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
520
+ className
521
+ ),
459
522
  ...props
460
523
  }
461
524
  );
462
- });
525
+ }
526
+
527
+ // src/components/ui/field.tsx
528
+ var Field = React4.forwardRef(
529
+ ({ className, orientation = "vertical", invalid = false, ...props }, ref) => {
530
+ return /* @__PURE__ */ React4.createElement(
531
+ "div",
532
+ {
533
+ ref,
534
+ "data-slot": "field",
535
+ "data-orientation": orientation,
536
+ "data-invalid": invalid || void 0,
537
+ className: cn(
538
+ "flex",
539
+ orientation === "horizontal" ? "items-center gap-2" : "flex-col gap-1.5",
540
+ className
541
+ ),
542
+ ...props
543
+ }
544
+ );
545
+ }
546
+ );
463
547
  Field.displayName = "Field";
464
- var FieldGroup = React3.forwardRef(({ className, ...props }, ref) => {
465
- return /* @__PURE__ */ React3.createElement(
548
+ var FieldGroup = React4.forwardRef(({ className, ...props }, ref) => {
549
+ return /* @__PURE__ */ React4.createElement(
466
550
  "div",
467
551
  {
468
552
  ref,
@@ -473,9 +557,9 @@ var FieldGroup = React3.forwardRef(({ className, ...props }, ref) => {
473
557
  );
474
558
  });
475
559
  FieldGroup.displayName = "FieldGroup";
476
- var FieldLabel = React3.forwardRef(({ className, required, children, ...props }, ref) => {
477
- return /* @__PURE__ */ React3.createElement(
478
- "label",
560
+ var FieldLabel = React4.forwardRef(({ className, required, children, ...props }, ref) => {
561
+ return /* @__PURE__ */ React4.createElement(
562
+ Label,
479
563
  {
480
564
  ref,
481
565
  "data-slot": "field-label",
@@ -487,12 +571,12 @@ var FieldLabel = React3.forwardRef(({ className, required, children, ...props },
487
571
  ...props
488
572
  },
489
573
  children,
490
- required && /* @__PURE__ */ React3.createElement("span", { className: "text-destructive ml-1" }, "*")
574
+ required && /* @__PURE__ */ React4.createElement("span", { className: "text-destructive ml-1" }, "*")
491
575
  );
492
576
  });
493
577
  FieldLabel.displayName = "FieldLabel";
494
- var FieldDescription = React3.forwardRef(({ className, ...props }, ref) => {
495
- return /* @__PURE__ */ React3.createElement(
578
+ var FieldDescription = React4.forwardRef(({ className, ...props }, ref) => {
579
+ return /* @__PURE__ */ React4.createElement(
496
580
  "p",
497
581
  {
498
582
  ref,
@@ -503,8 +587,8 @@ var FieldDescription = React3.forwardRef(({ className, ...props }, ref) => {
503
587
  );
504
588
  });
505
589
  FieldDescription.displayName = "FieldDescription";
506
- var FieldError = React3.forwardRef(({ className, ...props }, ref) => {
507
- return /* @__PURE__ */ React3.createElement(
590
+ var FieldError = React4.forwardRef(({ className, ...props }, ref) => {
591
+ return /* @__PURE__ */ React4.createElement(
508
592
  "p",
509
593
  {
510
594
  ref,
@@ -527,7 +611,7 @@ var FieldFeedback = ({
527
611
  }) => {
528
612
  const errorText = Array.isArray(error) ? error.join(", ") : error;
529
613
  if (!errorText || !shouldRenderError) return null;
530
- return /* @__PURE__ */ React3.createElement(FieldError, { id: errorId, className: errorClassName }, errorText);
614
+ return /* @__PURE__ */ React4.createElement(FieldError, { id: errorId, className: errorClassName }, errorText);
531
615
  };
532
616
  var LabelGroup = ({
533
617
  labelHtmlFor,
@@ -544,40 +628,38 @@ var LabelGroup = ({
544
628
  variant === "legend" ? "mb-1.5" : "mb-1 block",
545
629
  primaryClassName
546
630
  );
547
- const requiredIndicator = required ? /* @__PURE__ */ React3.createElement("span", { className: "text-destructive pl-0.5", "aria-label": "required" }, "*") : null;
631
+ const requiredIndicator = required && variant !== "label" ? /* @__PURE__ */ React4.createElement("span", { className: "text-destructive pl-0.5", "aria-label": "required" }, "*") : null;
548
632
  let primaryElement = null;
549
633
  if (primary) {
550
634
  if (variant === "label") {
551
- primaryElement = /* @__PURE__ */ React3.createElement(
552
- "label",
635
+ primaryElement = /* @__PURE__ */ React4.createElement(
636
+ FieldLabel,
553
637
  {
554
638
  htmlFor: labelHtmlFor,
555
- "data-slot": "field-label",
639
+ required,
556
640
  className: primaryClasses
557
641
  },
558
- primary,
559
- requiredIndicator
642
+ primary
560
643
  );
561
644
  } else if (variant === "legend") {
562
- primaryElement = /* @__PURE__ */ React3.createElement("legend", { "data-slot": "field-legend", className: primaryClasses }, primary, requiredIndicator);
645
+ primaryElement = /* @__PURE__ */ React4.createElement("legend", { "data-slot": "field-legend", className: primaryClasses }, primary, requiredIndicator);
563
646
  } else {
564
- primaryElement = /* @__PURE__ */ React3.createElement("div", { "data-slot": "field-label", className: primaryClasses }, primary, requiredIndicator);
647
+ primaryElement = /* @__PURE__ */ React4.createElement("div", { "data-slot": "field-label", className: primaryClasses }, primary, requiredIndicator);
565
648
  }
566
649
  }
567
- const secondaryElement = secondary ? /* @__PURE__ */ React3.createElement(
568
- "p",
650
+ const secondaryElement = secondary ? /* @__PURE__ */ React4.createElement(
651
+ FieldDescription,
569
652
  {
570
- "data-slot": "field-description",
571
653
  id: secondaryId,
572
- className: cn("text-sm leading-normal font-normal", secondaryClassName)
654
+ className: cn("leading-normal font-normal", secondaryClassName)
573
655
  },
574
656
  secondary
575
657
  ) : null;
576
658
  if (!primaryElement && !secondaryElement) return null;
577
659
  if (variant === "legend") {
578
- return /* @__PURE__ */ React3.createElement(React3.Fragment, null, primaryElement, secondaryElement);
660
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, null, primaryElement, secondaryElement);
579
661
  }
580
- return /* @__PURE__ */ React3.createElement("div", { className: "flex flex-1 flex-col gap-0.5" }, primaryElement, secondaryElement);
662
+ return /* @__PURE__ */ React4.createElement("div", { className: "flex flex-1 flex-col gap-0.5" }, primaryElement, secondaryElement);
581
663
  };
582
664
 
583
665
  // src/core/Field.tsx
@@ -594,30 +676,40 @@ function Field2({
594
676
  }) {
595
677
  const fieldState = useField({ name, validate });
596
678
  const { meta } = fieldState;
597
- const hasError = React3.useMemo(() => {
679
+ const hasError = React4.useMemo(() => {
598
680
  return showError && meta.touched && meta.error ? true : false;
599
681
  }, [meta?.touched, meta?.error, showError]);
600
682
  const errorId = `${name}-error`;
601
683
  const descriptionId = `${name}-description`;
602
- return /* @__PURE__ */ React3.createElement("div", { className, "data-field": name }, /* @__PURE__ */ React3.createElement(
603
- LabelGroup,
684
+ return /* @__PURE__ */ React4.createElement(
685
+ Field,
604
686
  {
605
- labelHtmlFor: name,
606
- required,
607
- variant: "label",
608
- secondaryId: descriptionId,
609
- secondary: description,
610
- primary: label
611
- }
612
- ), /* @__PURE__ */ React3.createElement("div", null, typeof children === "function" ? children(fieldState) : children), /* @__PURE__ */ React3.createElement(
613
- FieldFeedback,
614
- {
615
- errorId,
616
- errorClassName,
617
- shouldRenderError: hasError,
618
- error: meta.error
619
- }
620
- ));
687
+ className,
688
+ "data-field": name,
689
+ invalid: hasError
690
+ },
691
+ /* @__PURE__ */ React4.createElement(
692
+ LabelGroup,
693
+ {
694
+ labelHtmlFor: name,
695
+ required,
696
+ variant: "label",
697
+ secondaryId: descriptionId,
698
+ secondary: description,
699
+ primary: label
700
+ }
701
+ ),
702
+ /* @__PURE__ */ React4.createElement("div", { "data-slot": "field-control" }, typeof children === "function" ? children(fieldState) : children),
703
+ /* @__PURE__ */ React4.createElement(
704
+ FieldFeedback,
705
+ {
706
+ errorId,
707
+ errorClassName,
708
+ shouldRenderError: hasError,
709
+ error: meta.error
710
+ }
711
+ )
712
+ );
621
713
  }
622
714
  Field2.displayName = "Field";
623
715