@trackunit/react-form-components 1.1.1 → 1.2.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.
package/index.cjs.js CHANGED
@@ -464,10 +464,41 @@ const BaseInput = React.forwardRef(({ className, isInvalid, dataTestId, prefix,
464
464
  });
465
465
  BaseInput.displayName = "BaseInput";
466
466
 
467
+ /**
468
+ * Shared CVA for binary control items: Checkbox, RadioItem, ToggleSwitchOption
469
+ */
470
+ const cvaBinaryControlWrapper = cssClassVarianceUtilities.cvaMerge([
471
+ "grid-rows-subgrid", //* Children align to this parent grid
472
+ "grid-cols-min-fr", //* explicit horizontal placement for input and label/description
473
+ "auto-rows-auto", //* optional extra rows (suffix) will be auto-sized
474
+ "group",
475
+ "grid",
476
+ "gap-x-2",
477
+ ]);
478
+ const cvaBinaryControlLabelTooltip = cssClassVarianceUtilities.cvaMerge(["col-start-2", "w-full", "self-center"]);
479
+ const cvaBinaryControlDescriptionTooltip = cssClassVarianceUtilities.cvaMerge(["col-span-2", "col-start-2", "row-start-2", "w-full"]);
480
+ const cvaBinaryControlDescription = cssClassVarianceUtilities.cvaMerge([
481
+ "text-sm",
482
+ "font-normal",
483
+ "text-slate-500",
484
+ "text-left",
485
+ "whitespace-nowrap",
486
+ "select-none",
487
+ "text-ellipsis",
488
+ "overflow-hidden",
489
+ ], {
490
+ variants: {
491
+ disabled: {
492
+ true: ["text-slate-400", "hover:text-slate-400", "group-hover:text-slate-400"],
493
+ false: "",
494
+ },
495
+ },
496
+ });
497
+ const cvaBinaryControlSuffixContainer = cssClassVarianceUtilities.cvaMerge(["flex", "col-start-3", "items-center"]);
498
+
467
499
  const cvaLabel = cssClassVarianceUtilities.cvaMerge([
468
500
  "text-sm",
469
501
  "text-slate-700",
470
- "truncate",
471
502
  "hover:text-slate-800",
472
503
  "group-hover:text-slate-800",
473
504
  "active:text-slate-800",
@@ -479,13 +510,18 @@ const cvaLabel = cssClassVarianceUtilities.cvaMerge([
479
510
  false: "",
480
511
  },
481
512
  disabled: {
482
- true: "text-slate-400 hover:text-slate-400 group-hover:text-slate-400",
513
+ true: "text-slate-400 hover:text-slate-400 active:text-slate-400 group-hover:text-slate-400 group-active:text-slate-400",
514
+ false: "",
515
+ },
516
+ truncate: {
517
+ true: "truncate",
483
518
  false: "",
484
519
  },
485
520
  },
486
521
  defaultVariants: {
487
522
  invalid: false,
488
523
  disabled: false,
524
+ truncate: true,
489
525
  },
490
526
  });
491
527
 
@@ -596,14 +632,6 @@ const cvaCheckbox = cssClassVarianceUtilities.cvaMerge([
596
632
  state: "deselected",
597
633
  },
598
634
  });
599
- const cvaCheckboxContainer = cssClassVarianceUtilities.cvaMerge([
600
- "items-center",
601
- "gap-2",
602
- "group",
603
- "grid",
604
- "grid-cols-min-fr",
605
- "has-[:nth-child(3)]:grid-cols-min-fr-min", //if there's 3 children
606
- ]);
607
635
  const cvaCheckboxInput = cssClassVarianceUtilities.cvaMerge(["absolute", "opacity-0", "m-0", "pointer-events-none", "hidden"]);
608
636
  const cvaCheckboxIcon = cssClassVarianceUtilities.cvaMerge(["w-2.5", "h-2.5", "text-white"]);
609
637
 
@@ -629,9 +657,9 @@ const IndeterminateIcon = ({ className }) => (jsxRuntime.jsx("svg", { className:
629
657
  * @param {CheckboxProps} props - The props for the Checkbox component
630
658
  * @returns {JSX.Element} Checkbox component
631
659
  */
632
- const Checkbox = React__namespace.forwardRef(({ className, dataTestId = "checkbox", onChange, checked = false, disabled = false, isInvalid = false, readOnly, indeterminate = false, suffix, label, tabIndex = 0, stopPropagation, ...rest }, ref) => {
660
+ const Checkbox = React.forwardRef(({ className, dataTestId = "checkbox", onChange, checked = false, disabled = false, isInvalid = false, readOnly, indeterminate = false, suffix, label, tabIndex = 0, stopPropagation, ...rest }, ref) => {
633
661
  const icon = indeterminate ? (jsxRuntime.jsx(IndeterminateIcon, { className: cvaCheckboxIcon() })) : (checked && jsxRuntime.jsx(CheckIcon, { className: cvaCheckboxIcon() }));
634
- const internalRef = React__namespace.useRef(null);
662
+ const internalRef = React.useRef(null);
635
663
  const { isTextTruncated: isLabelCutOff, ref: labelRef } = reactComponents.useIsTextTruncated();
636
664
  const isReadonly = disabled || readOnly;
637
665
  const onKeyPress = e => {
@@ -644,14 +672,14 @@ const Checkbox = React__namespace.forwardRef(({ className, dataTestId = "checkbo
644
672
  }
645
673
  };
646
674
  const uuid = rest.id;
647
- return (jsxRuntime.jsxs("label", { className: cvaCheckboxContainer({ className }), "data-testid": dataTestId ? `${dataTestId}-container` : null, htmlFor: uuid, onClick: e => stopPropagation && e.stopPropagation(), onKeyDown: onKeyPress, ref: internalRef, children: [jsxRuntime.jsx("input", { "aria-checked": !indeterminate && checked, checked: !indeterminate && checked, className: cvaCheckboxInput(), "data-testid": dataTestId, disabled: disabled, id: uuid, onChange: onChange, readOnly: readOnly, type: "checkbox", ...rest, ref: ref }), jsxRuntime.jsx("span", { className: cvaCheckbox({
648
- disabled: isReadonly,
649
- invalid: isReadonly ? false : isInvalid,
650
- state: indeterminate ? "indeterminate" : checked ? "selected" : "deselected",
651
- }), id: uuid, tabIndex: isReadonly ? -1 : tabIndex, children: icon }), jsxRuntime.jsx(reactComponents.Tooltip, { className: "w-full", disabled: !isLabelCutOff, label: label, placement: "top", children: jsxRuntime.jsx("span", { className: cvaLabel({
675
+ return (jsxRuntime.jsxs("label", { className: cvaBinaryControlWrapper({ className }), "data-testid": dataTestId ? `${dataTestId}-container` : null, htmlFor: uuid, onClick: e => stopPropagation && e.stopPropagation(), onKeyDown: onKeyPress, ref: internalRef, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("input", { "aria-checked": !indeterminate && checked, checked: !indeterminate && checked, className: cvaCheckboxInput(), "data-testid": dataTestId, disabled: disabled, id: uuid, onChange: onChange, readOnly: readOnly, type: "checkbox", ...rest, ref: ref }), jsxRuntime.jsx("span", { className: cvaCheckbox({
676
+ disabled: isReadonly,
677
+ invalid: isReadonly ? false : isInvalid,
678
+ state: indeterminate ? "indeterminate" : checked ? "selected" : "deselected",
679
+ }), id: uuid, tabIndex: isReadonly ? -1 : tabIndex, children: icon })] }), jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaBinaryControlLabelTooltip(), disabled: !isLabelCutOff, label: label, placement: "top", children: jsxRuntime.jsx("span", { className: cvaLabel({
652
680
  invalid: isReadonly ? false : isInvalid,
653
681
  disabled: isReadonly,
654
- }), id: `checkbox-label-${label}`, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix] }));
682
+ }), id: `checkbox-label-${label}`, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix ? (jsxRuntime.jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null] }));
655
683
  });
656
684
  Checkbox.displayName = "Checkbox";
657
685
 
@@ -1495,25 +1523,6 @@ const cvaRadioItem = cssClassVarianceUtilities.cvaMerge([
1495
1523
  },
1496
1524
  ],
1497
1525
  });
1498
- const cvaRadioItemWrapper = cssClassVarianceUtilities.cvaMerge([
1499
- "grid-rows-subgrid", //* Children align to this parent grid
1500
- "grid-cols-min-fr", //* explicit horizontal placement for input and label/description
1501
- "auto-rows-auto", //* optional extra rows (suffix) will be auto-sized
1502
- "group",
1503
- "grid",
1504
- "gap-x-2",
1505
- ]);
1506
- const cvaRadioItemDescription = cssClassVarianceUtilities.cvaMerge(["text-sm", "font-normal", "text-slate-500", "text-left", "whitespace-nowrap", "text-ellipsis", "overflow-hidden"], {
1507
- variants: {
1508
- disabled: {
1509
- true: ["text-slate-400", "hover:text-slate-400", "group-hover:text-slate-400"],
1510
- false: "",
1511
- },
1512
- },
1513
- });
1514
- const cvaLabelTooltip = cssClassVarianceUtilities.cvaMerge(["col-start-2", "w-full", "self-center"]);
1515
- const cvaDescriptionTooltip = cssClassVarianceUtilities.cvaMerge(["col-span-2", "col-start-2", "row-start-2", "w-full"]);
1516
- const cvaSuffixContainer = cssClassVarianceUtilities.cvaMerge(["flex", "col-start-3", "items-center"]);
1517
1526
 
1518
1527
  const RadioGroupContext = React__namespace.createContext(null);
1519
1528
 
@@ -1554,14 +1563,14 @@ const RadioItem = ({ label, value, dataTestId, className, description, suffix, .
1554
1563
  const { ref: descriptionRef, isTextTruncated: isDescriptionTruncated } = reactComponents.useIsTextTruncated();
1555
1564
  const descriptionId = description ? `${groupCtx?.id}-${value}-description` : undefined;
1556
1565
  const inputId = `${groupCtx?.id}-${value}`;
1557
- return (jsxRuntime.jsxs("label", { className: cvaRadioItemWrapper({ className }), "data-testid": dataTestId ? `${dataTestId}-Wrapper` : undefined, htmlFor: inputId, children: [jsxRuntime.jsx("input", { "aria-describedby": descriptionId, checked: isChecked, className: cvaRadioItem({
1566
+ return (jsxRuntime.jsxs("label", { className: cvaBinaryControlWrapper({ className }), "data-testid": dataTestId ? `${dataTestId}-Wrapper` : undefined, htmlFor: inputId, children: [jsxRuntime.jsx("input", { "aria-describedby": descriptionId, checked: isChecked, className: cvaRadioItem({
1558
1567
  checked: isChecked,
1559
1568
  disabled: groupCtx?.disabled,
1560
1569
  invalid: groupCtx?.isInvalid,
1561
- }), "data-testid": dataTestId, id: inputId, onChange: groupCtx?.onChange, type: "radio", value: value, ...rest }), jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaLabelTooltip(), dataTestId: dataTestId ? `${dataTestId}-Label-Tooltip` : undefined, disabled: !isLabelTruncated, label: label, placement: "top", children: jsxRuntime.jsx("span", { className: cvaLabel({
1570
+ }), "data-testid": dataTestId, id: inputId, onChange: groupCtx?.onChange, type: "radio", value: value, ...rest }), jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaBinaryControlLabelTooltip(), dataTestId: dataTestId ? `${dataTestId}-Label-Tooltip` : undefined, disabled: !isLabelTruncated, label: label, placement: "top", children: jsxRuntime.jsx("span", { className: cvaLabel({
1562
1571
  invalid: groupCtx?.isInvalid,
1563
1572
  disabled: groupCtx?.disabled,
1564
- }), "data-testid": dataTestId ? `${dataTestId}-Label` : undefined, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix ? (jsxRuntime.jsx("div", { className: cvaSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null, description ? (jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaDescriptionTooltip(), dataTestId: dataTestId ? `${dataTestId}-Description-Tooltip` : undefined, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsxRuntime.jsx("span", { className: cvaRadioItemDescription({ disabled: groupCtx?.disabled }), "data-testid": dataTestId ? `${dataTestId}-Description` : undefined, id: descriptionId, ref: descriptionRef, children: description }) }, "description-tooltip-" + rest.name)) : null] }));
1573
+ }), "data-testid": dataTestId ? `${dataTestId}-Label` : undefined, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix ? (jsxRuntime.jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null, description ? (jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaBinaryControlDescriptionTooltip(), dataTestId: dataTestId ? `${dataTestId}-Description-Tooltip` : undefined, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsxRuntime.jsx("span", { className: cvaBinaryControlDescription({ disabled: groupCtx?.disabled }), "data-testid": dataTestId ? `${dataTestId}-Description` : undefined, id: descriptionId, ref: descriptionRef, children: description }) }, "description-tooltip-" + rest.name)) : null] }));
1565
1574
  };
1566
1575
 
1567
1576
  const cvaTimeRange = cssClassVarianceUtilities.cvaMerge([
@@ -2615,9 +2624,9 @@ const TimeRangeField = ({ className, dataTestId, onChange, isInvalid, errorMessa
2615
2624
  return (jsxRuntime.jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, label: label, tip: tip, children: jsxRuntime.jsx(TimeRange, { className: className, dataTestId: dataTestId, isInvalid: renderAsInvalid, onChange: onChange, ...rest, children: children }) }));
2616
2625
  };
2617
2626
 
2618
- const cvaToggleWrapper = cssClassVarianceUtilities.cvaMerge(["flex", "gap-2", "items-center"]);
2619
- cssClassVarianceUtilities.cvaMerge(["relative"]);
2620
- const cvaToggleTrack = cssClassVarianceUtilities.cvaMerge(["shrink-0", "cursor-pointer", "rounded-full", "bg-slate-300", "hover:bg-slate-400", "active:bg-slate-500"], {
2627
+ const cvaToggleSwitchWrapper = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-[auto]", "items-center"]);
2628
+ const cvaToggleSwitchInput = cssClassVarianceUtilities.cvaMerge(["absolute", "pointer-events-none", "w-0", "h-0", "opacity-0"]);
2629
+ const cvaToggleSwitchTrack = cssClassVarianceUtilities.cvaMerge(["items-center", "shrink-0", "rounded-full"], {
2621
2630
  variants: {
2622
2631
  size: {
2623
2632
  small: ["w-5", "p-0.5"],
@@ -2625,13 +2634,18 @@ const cvaToggleTrack = cssClassVarianceUtilities.cvaMerge(["shrink-0", "cursor-p
2625
2634
  large: ["w-[38px]", "p-[3px]"],
2626
2635
  },
2627
2636
  disabled: {
2628
- true: ["cursor-not-allowed", "pointer-events-none", "bg-slate-200"],
2629
- false: "",
2637
+ true: ["cursor-not-allowed", "bg-slate-200"],
2638
+ false: ["cursor-pointer", "bg-slate-300", "hover:bg-slate-400", "active:bg-slate-500"],
2630
2639
  },
2631
2640
  toggled: {
2632
- true: ["bg-primary-600", "hover:bg-primary-700", "active:bg-primary-800"],
2641
+ true: "", // classes moved to compoundVariants to avoid conflicts with the disabled variant
2633
2642
  false: "",
2634
2643
  },
2644
+ focused: {
2645
+ true: "outline-native",
2646
+ false: "outline-0",
2647
+ auto: "[&:has(:focus-visible)]:outline-native",
2648
+ },
2635
2649
  },
2636
2650
  compoundVariants: [
2637
2651
  {
@@ -2639,12 +2653,19 @@ const cvaToggleTrack = cssClassVarianceUtilities.cvaMerge(["shrink-0", "cursor-p
2639
2653
  toggled: true,
2640
2654
  className: ["bg-slate-400"],
2641
2655
  },
2656
+ {
2657
+ disabled: false,
2658
+ toggled: true,
2659
+ className: ["hover:bg-primary-700", "active:bg-primary-800", "bg-primary-600"],
2660
+ },
2642
2661
  ],
2643
2662
  defaultVariants: {
2644
2663
  size: "medium",
2664
+ focused: "auto",
2665
+ disabled: false,
2645
2666
  },
2646
2667
  });
2647
- const cvaToggleThumb = cssClassVarianceUtilities.cvaMerge(["block", "rounded-full", "bg-white", "aspect-square", "translate-x-0", "transition-all"], {
2668
+ const cvaToggleSwitchThumb = cssClassVarianceUtilities.cvaMerge(["block", "rounded-full", "bg-white", "aspect-square", "translate-x-0", "transition-all"], {
2648
2669
  variants: {
2649
2670
  toggled: {
2650
2671
  true: "",
@@ -2675,38 +2696,70 @@ const cvaToggleThumb = cssClassVarianceUtilities.cvaMerge(["block", "rounded-ful
2675
2696
  ],
2676
2697
  defaultVariants: {},
2677
2698
  });
2678
- const cvaToggleInput = cssClassVarianceUtilities.cvaMerge(["absolute", "opacity-0", "-z-10"]);
2679
- const cvaToggleLabelContainer = cssClassVarianceUtilities.cvaMerge(["grid", "gap-1"]);
2680
- const cvaToggleLabel = cssClassVarianceUtilities.cvaMerge([]);
2681
- const cvaToggleDescription = cssClassVarianceUtilities.cvaMerge(["text-sm", "font-normal", "text-slate-500", "text-left", "whitespace-nowrap", "text-ellipsis", "overflow-hidden"], {
2682
- variants: {
2683
- disabled: {
2684
- true: "text-slate-400 hover:text-slate-400 group-hover:text-slate-400",
2685
- false: "",
2686
- },
2687
- },
2699
+
2700
+ /**
2701
+ * A checkbox input wrapper with role="switch". Used as an input element for **ToggleSwitchOption**
2702
+ * or custom components.
2703
+ *
2704
+ * Not intended for standalone use.
2705
+ *
2706
+ * @param {ToggleSwitchProps} props - The props for the ToggleSwitch component
2707
+ * @returns {ReactElement} ToggleSwitch component
2708
+ */
2709
+ const ToggleSwitch = React.forwardRef(({ onChange, onClick, preventDefaultOnClick, className, dataTestId = "toggle-switch", showInputFocus, toggled, size = "medium", tabIndex = 0, readOnly, disabled, ...rest }, ref) => {
2710
+ const localInputRef = React.useRef(null);
2711
+ const inputRef = typeof ref === "function" ? localInputRef : ref || localInputRef;
2712
+ const handleWrapperClick = (e) => {
2713
+ // Prevents double-toggling when wrapped in a label or if preventDefaultOnClick is true
2714
+ const isFromLabel = e.target instanceof Element && e.target.closest("label");
2715
+ if (!isFromLabel && !preventDefaultOnClick && !disabled && !readOnly) {
2716
+ inputRef.current?.click();
2717
+ }
2718
+ onClick?.(e);
2719
+ };
2720
+ const handleKeyPress = e => {
2721
+ if (e.code === "Enter") {
2722
+ e.preventDefault();
2723
+ !readOnly && inputRef.current?.click();
2724
+ }
2725
+ // Space key is already supported natively by the input element
2726
+ };
2727
+ const handleInputChange = (e) => {
2728
+ if (readOnly || disabled) {
2729
+ return;
2730
+ }
2731
+ e.stopPropagation();
2732
+ onChange?.(!toggled, e);
2733
+ };
2734
+ return (jsxRuntime.jsx("span", { className: cvaToggleSwitchWrapper({ className }), "data-testid": `${dataTestId}`, onClick: handleWrapperClick, onKeyDown: handleKeyPress, children: jsxRuntime.jsxs("span", { className: cvaToggleSwitchTrack({
2735
+ toggled,
2736
+ disabled: disabled || readOnly,
2737
+ size,
2738
+ focused: showInputFocus,
2739
+ }), "data-testid": `${dataTestId}-track`, children: [jsxRuntime.jsx("span", { className: cvaToggleSwitchThumb({ toggled, size }), "data-testid": `${dataTestId}-thumb` }), jsxRuntime.jsx("input", { "aria-disabled": disabled || readOnly, checked: toggled, className: cvaToggleSwitchInput(), "data-testid": `${dataTestId}-input`, disabled: disabled, onChange: handleInputChange, onClick: e => e.stopPropagation(), ref: inputRef, role: "switch", type: "checkbox", ...rest })] }) }));
2688
2740
  });
2741
+ ToggleSwitch.displayName = "ToggleSwitch";
2689
2742
 
2690
2743
  /**
2691
- * Use Toggle when you have to only enable/disable a feature.
2744
+ * Use ToggleSwitchOption when you have to only enable/disable a feature.
2745
+ * Wrapper component for ToggleSwitch.
2692
2746
  *
2693
- * _**Do use** Toggle in forms or settings._
2747
+ * _**Do use** ToggleSwitchOption in forms or settings._
2694
2748
  *
2695
- * _**Do not use** Toggle if a user can select many option from a list, use checkboxes instead of toggle._
2749
+ * _**Do not use** ToggleSwitchOption if a user can select multiple options from a list, use checkboxes instead of toggle._
2696
2750
  *
2697
- * @param {ToggleProps} props - The props for the Toggle component
2698
- * @returns {JSX.Element} Toggle component
2751
+ * @param {ToggleSwitchOptionProps} props - The props for the ToggleSwitchOption component
2752
+ * @returns {ReactElement} ToggleSwitchOption component
2699
2753
  */
2700
- const Toggle = React__namespace.forwardRef(({ toggled = false, onChange, onClick, disabled, size = "medium", id, tabIndex, required, className, dataTestId = "toggle", onBlur, name, description = "", }, ref) => {
2701
- const showLabelContainer = Boolean(name || description);
2702
- const showDescription = Boolean(description);
2703
- const getTestId = (suffix) => `${dataTestId}-${suffix}`;
2704
- return (jsxRuntime.jsx("span", { className: className, "data-testid": getTestId("outer-wrapper"), onClick: onClick, children: jsxRuntime.jsxs("label", { className: cvaToggleWrapper(), "data-testid": getTestId("wrapper"), htmlFor: id, children: [jsxRuntime.jsx("span", { className: cvaToggleTrack({ disabled, size, toggled }), "data-testid": getTestId("track"), children: jsxRuntime.jsx("span", { className: cvaToggleThumb({ toggled, size }), "data-testid": getTestId("thumb") }) }), showLabelContainer ? (jsxRuntime.jsxs("span", { className: cvaToggleLabelContainer(), "data-testid": getTestId("label-container"), children: [jsxRuntime.jsx(Label, { className: cvaToggleLabel(), "data-testid": getTestId("label"), disabled: disabled, children: name }), showDescription ? (jsxRuntime.jsx("span", { className: cvaToggleDescription({ disabled }), "data-testid": getTestId("description"), children: description })) : null] })) : null, jsxRuntime.jsx("input", { "aria-checked": toggled, checked: toggled, className: cvaToggleInput(), "data-testid": getTestId("input"), disabled: disabled, id: id, name: name, onBlur: onBlur, onChange: e => {
2705
- e.stopPropagation();
2706
- onChange(!toggled, e);
2707
- }, ref: ref, required: required, tabIndex: tabIndex, type: "checkbox" })] }) }));
2708
- });
2709
- Toggle.displayName = "Toggle";
2754
+ const ToggleSwitchOption = ({ label, className, description, suffix, id, dataTestId = "toggle-switch-option", ...rest }) => {
2755
+ const { ref: labelRef, isTextTruncated: isLabelTruncated } = reactComponents.useIsTextTruncated();
2756
+ const { ref: descriptionRef, isTextTruncated: isDescriptionTruncated } = reactComponents.useIsTextTruncated();
2757
+ return (jsxRuntime.jsxs("label", { className: cvaBinaryControlWrapper({ className }), "data-testid": dataTestId, htmlFor: `${id}-toggle-switch`, tabIndex: -1, children: [jsxRuntime.jsx(ToggleSwitch, { dataTestId: `${dataTestId}-switcher`, id: `${id}-toggle-switch`, ...rest }), jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaBinaryControlLabelTooltip(), dataTestId: `${dataTestId}-label-tooltip`, disabled: !isLabelTruncated, label: label, placement: "top", children: jsxRuntime.jsx("span", { className: cvaLabel({
2758
+ disabled: rest.disabled || rest.readOnly,
2759
+ className: "select-none",
2760
+ }), "data-testid": `${dataTestId}-label`, ref: labelRef, children: label }) }, `${id}-label-tooltip`), suffix ? (jsxRuntime.jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": `${dataTestId}-suffix-container`, children: suffix })) : null, description ? (jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaBinaryControlDescriptionTooltip(), dataTestId: `${dataTestId}-description-tooltip`, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsxRuntime.jsx("span", { className: cvaBinaryControlDescription({ disabled: rest.disabled || rest.readOnly }), "data-testid": `${dataTestId}-description`, id: `${id}-description`, ref: descriptionRef, children: description }) }, `${id}-description-tooltip`)) : null] }));
2761
+ };
2762
+ ToggleSwitchOption.displayName = "ToggleSwitchOption";
2710
2763
 
2711
2764
  const cvaUploadInputField = cssClassVarianceUtilities.cvaMerge([
2712
2765
  "px-0",
@@ -2962,7 +3015,8 @@ exports.TextField = TextField;
2962
3015
  exports.TextInput = TextInput;
2963
3016
  exports.TimeRange = TimeRange;
2964
3017
  exports.TimeRangeField = TimeRangeField;
2965
- exports.Toggle = Toggle;
3018
+ exports.ToggleSwitch = ToggleSwitch;
3019
+ exports.ToggleSwitchOption = ToggleSwitchOption;
2966
3020
  exports.UploadField = UploadField;
2967
3021
  exports.UploadInput = UploadInput;
2968
3022
  exports.UrlField = UrlField;
@@ -2981,6 +3035,7 @@ exports.cvaInputField = cvaInputField;
2981
3035
  exports.cvaInputItemPlacementManager = cvaInputItemPlacementManager;
2982
3036
  exports.cvaInputPrefix = cvaInputPrefix;
2983
3037
  exports.cvaInputSuffix = cvaInputSuffix;
3038
+ exports.cvaLabel = cvaLabel;
2984
3039
  exports.cvaSelect = cvaSelect;
2985
3040
  exports.cvaSelectControl = cvaSelectControl;
2986
3041
  exports.cvaSelectCounter = cvaSelectCounter;
package/index.esm.js CHANGED
@@ -445,10 +445,41 @@ const BaseInput = forwardRef(({ className, isInvalid, dataTestId, prefix, suffix
445
445
  });
446
446
  BaseInput.displayName = "BaseInput";
447
447
 
448
+ /**
449
+ * Shared CVA for binary control items: Checkbox, RadioItem, ToggleSwitchOption
450
+ */
451
+ const cvaBinaryControlWrapper = cvaMerge([
452
+ "grid-rows-subgrid", //* Children align to this parent grid
453
+ "grid-cols-min-fr", //* explicit horizontal placement for input and label/description
454
+ "auto-rows-auto", //* optional extra rows (suffix) will be auto-sized
455
+ "group",
456
+ "grid",
457
+ "gap-x-2",
458
+ ]);
459
+ const cvaBinaryControlLabelTooltip = cvaMerge(["col-start-2", "w-full", "self-center"]);
460
+ const cvaBinaryControlDescriptionTooltip = cvaMerge(["col-span-2", "col-start-2", "row-start-2", "w-full"]);
461
+ const cvaBinaryControlDescription = cvaMerge([
462
+ "text-sm",
463
+ "font-normal",
464
+ "text-slate-500",
465
+ "text-left",
466
+ "whitespace-nowrap",
467
+ "select-none",
468
+ "text-ellipsis",
469
+ "overflow-hidden",
470
+ ], {
471
+ variants: {
472
+ disabled: {
473
+ true: ["text-slate-400", "hover:text-slate-400", "group-hover:text-slate-400"],
474
+ false: "",
475
+ },
476
+ },
477
+ });
478
+ const cvaBinaryControlSuffixContainer = cvaMerge(["flex", "col-start-3", "items-center"]);
479
+
448
480
  const cvaLabel = cvaMerge([
449
481
  "text-sm",
450
482
  "text-slate-700",
451
- "truncate",
452
483
  "hover:text-slate-800",
453
484
  "group-hover:text-slate-800",
454
485
  "active:text-slate-800",
@@ -460,13 +491,18 @@ const cvaLabel = cvaMerge([
460
491
  false: "",
461
492
  },
462
493
  disabled: {
463
- true: "text-slate-400 hover:text-slate-400 group-hover:text-slate-400",
494
+ true: "text-slate-400 hover:text-slate-400 active:text-slate-400 group-hover:text-slate-400 group-active:text-slate-400",
495
+ false: "",
496
+ },
497
+ truncate: {
498
+ true: "truncate",
464
499
  false: "",
465
500
  },
466
501
  },
467
502
  defaultVariants: {
468
503
  invalid: false,
469
504
  disabled: false,
505
+ truncate: true,
470
506
  },
471
507
  });
472
508
 
@@ -577,14 +613,6 @@ const cvaCheckbox = cvaMerge([
577
613
  state: "deselected",
578
614
  },
579
615
  });
580
- const cvaCheckboxContainer = cvaMerge([
581
- "items-center",
582
- "gap-2",
583
- "group",
584
- "grid",
585
- "grid-cols-min-fr",
586
- "has-[:nth-child(3)]:grid-cols-min-fr-min", //if there's 3 children
587
- ]);
588
616
  const cvaCheckboxInput = cvaMerge(["absolute", "opacity-0", "m-0", "pointer-events-none", "hidden"]);
589
617
  const cvaCheckboxIcon = cvaMerge(["w-2.5", "h-2.5", "text-white"]);
590
618
 
@@ -610,9 +638,9 @@ const IndeterminateIcon = ({ className }) => (jsx("svg", { className: className,
610
638
  * @param {CheckboxProps} props - The props for the Checkbox component
611
639
  * @returns {JSX.Element} Checkbox component
612
640
  */
613
- const Checkbox = React.forwardRef(({ className, dataTestId = "checkbox", onChange, checked = false, disabled = false, isInvalid = false, readOnly, indeterminate = false, suffix, label, tabIndex = 0, stopPropagation, ...rest }, ref) => {
641
+ const Checkbox = forwardRef(({ className, dataTestId = "checkbox", onChange, checked = false, disabled = false, isInvalid = false, readOnly, indeterminate = false, suffix, label, tabIndex = 0, stopPropagation, ...rest }, ref) => {
614
642
  const icon = indeterminate ? (jsx(IndeterminateIcon, { className: cvaCheckboxIcon() })) : (checked && jsx(CheckIcon, { className: cvaCheckboxIcon() }));
615
- const internalRef = React.useRef(null);
643
+ const internalRef = useRef(null);
616
644
  const { isTextTruncated: isLabelCutOff, ref: labelRef } = useIsTextTruncated();
617
645
  const isReadonly = disabled || readOnly;
618
646
  const onKeyPress = e => {
@@ -625,14 +653,14 @@ const Checkbox = React.forwardRef(({ className, dataTestId = "checkbox", onChang
625
653
  }
626
654
  };
627
655
  const uuid = rest.id;
628
- return (jsxs("label", { className: cvaCheckboxContainer({ className }), "data-testid": dataTestId ? `${dataTestId}-container` : null, htmlFor: uuid, onClick: e => stopPropagation && e.stopPropagation(), onKeyDown: onKeyPress, ref: internalRef, children: [jsx("input", { "aria-checked": !indeterminate && checked, checked: !indeterminate && checked, className: cvaCheckboxInput(), "data-testid": dataTestId, disabled: disabled, id: uuid, onChange: onChange, readOnly: readOnly, type: "checkbox", ...rest, ref: ref }), jsx("span", { className: cvaCheckbox({
629
- disabled: isReadonly,
630
- invalid: isReadonly ? false : isInvalid,
631
- state: indeterminate ? "indeterminate" : checked ? "selected" : "deselected",
632
- }), id: uuid, tabIndex: isReadonly ? -1 : tabIndex, children: icon }), jsx(Tooltip, { className: "w-full", disabled: !isLabelCutOff, label: label, placement: "top", children: jsx("span", { className: cvaLabel({
656
+ return (jsxs("label", { className: cvaBinaryControlWrapper({ className }), "data-testid": dataTestId ? `${dataTestId}-container` : null, htmlFor: uuid, onClick: e => stopPropagation && e.stopPropagation(), onKeyDown: onKeyPress, ref: internalRef, children: [jsxs("div", { children: [jsx("input", { "aria-checked": !indeterminate && checked, checked: !indeterminate && checked, className: cvaCheckboxInput(), "data-testid": dataTestId, disabled: disabled, id: uuid, onChange: onChange, readOnly: readOnly, type: "checkbox", ...rest, ref: ref }), jsx("span", { className: cvaCheckbox({
657
+ disabled: isReadonly,
658
+ invalid: isReadonly ? false : isInvalid,
659
+ state: indeterminate ? "indeterminate" : checked ? "selected" : "deselected",
660
+ }), id: uuid, tabIndex: isReadonly ? -1 : tabIndex, children: icon })] }), jsx(Tooltip, { className: cvaBinaryControlLabelTooltip(), disabled: !isLabelCutOff, label: label, placement: "top", children: jsx("span", { className: cvaLabel({
633
661
  invalid: isReadonly ? false : isInvalid,
634
662
  disabled: isReadonly,
635
- }), id: `checkbox-label-${label}`, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix] }));
663
+ }), id: `checkbox-label-${label}`, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix ? (jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null] }));
636
664
  });
637
665
  Checkbox.displayName = "Checkbox";
638
666
 
@@ -1476,25 +1504,6 @@ const cvaRadioItem = cvaMerge([
1476
1504
  },
1477
1505
  ],
1478
1506
  });
1479
- const cvaRadioItemWrapper = cvaMerge([
1480
- "grid-rows-subgrid", //* Children align to this parent grid
1481
- "grid-cols-min-fr", //* explicit horizontal placement for input and label/description
1482
- "auto-rows-auto", //* optional extra rows (suffix) will be auto-sized
1483
- "group",
1484
- "grid",
1485
- "gap-x-2",
1486
- ]);
1487
- const cvaRadioItemDescription = cvaMerge(["text-sm", "font-normal", "text-slate-500", "text-left", "whitespace-nowrap", "text-ellipsis", "overflow-hidden"], {
1488
- variants: {
1489
- disabled: {
1490
- true: ["text-slate-400", "hover:text-slate-400", "group-hover:text-slate-400"],
1491
- false: "",
1492
- },
1493
- },
1494
- });
1495
- const cvaLabelTooltip = cvaMerge(["col-start-2", "w-full", "self-center"]);
1496
- const cvaDescriptionTooltip = cvaMerge(["col-span-2", "col-start-2", "row-start-2", "w-full"]);
1497
- const cvaSuffixContainer = cvaMerge(["flex", "col-start-3", "items-center"]);
1498
1507
 
1499
1508
  const RadioGroupContext = React.createContext(null);
1500
1509
 
@@ -1535,14 +1544,14 @@ const RadioItem = ({ label, value, dataTestId, className, description, suffix, .
1535
1544
  const { ref: descriptionRef, isTextTruncated: isDescriptionTruncated } = useIsTextTruncated();
1536
1545
  const descriptionId = description ? `${groupCtx?.id}-${value}-description` : undefined;
1537
1546
  const inputId = `${groupCtx?.id}-${value}`;
1538
- return (jsxs("label", { className: cvaRadioItemWrapper({ className }), "data-testid": dataTestId ? `${dataTestId}-Wrapper` : undefined, htmlFor: inputId, children: [jsx("input", { "aria-describedby": descriptionId, checked: isChecked, className: cvaRadioItem({
1547
+ return (jsxs("label", { className: cvaBinaryControlWrapper({ className }), "data-testid": dataTestId ? `${dataTestId}-Wrapper` : undefined, htmlFor: inputId, children: [jsx("input", { "aria-describedby": descriptionId, checked: isChecked, className: cvaRadioItem({
1539
1548
  checked: isChecked,
1540
1549
  disabled: groupCtx?.disabled,
1541
1550
  invalid: groupCtx?.isInvalid,
1542
- }), "data-testid": dataTestId, id: inputId, onChange: groupCtx?.onChange, type: "radio", value: value, ...rest }), jsx(Tooltip, { className: cvaLabelTooltip(), dataTestId: dataTestId ? `${dataTestId}-Label-Tooltip` : undefined, disabled: !isLabelTruncated, label: label, placement: "top", children: jsx("span", { className: cvaLabel({
1551
+ }), "data-testid": dataTestId, id: inputId, onChange: groupCtx?.onChange, type: "radio", value: value, ...rest }), jsx(Tooltip, { className: cvaBinaryControlLabelTooltip(), dataTestId: dataTestId ? `${dataTestId}-Label-Tooltip` : undefined, disabled: !isLabelTruncated, label: label, placement: "top", children: jsx("span", { className: cvaLabel({
1543
1552
  invalid: groupCtx?.isInvalid,
1544
1553
  disabled: groupCtx?.disabled,
1545
- }), "data-testid": dataTestId ? `${dataTestId}-Label` : undefined, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix ? (jsx("div", { className: cvaSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null, description ? (jsx(Tooltip, { className: cvaDescriptionTooltip(), dataTestId: dataTestId ? `${dataTestId}-Description-Tooltip` : undefined, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsx("span", { className: cvaRadioItemDescription({ disabled: groupCtx?.disabled }), "data-testid": dataTestId ? `${dataTestId}-Description` : undefined, id: descriptionId, ref: descriptionRef, children: description }) }, "description-tooltip-" + rest.name)) : null] }));
1554
+ }), "data-testid": dataTestId ? `${dataTestId}-Label` : undefined, ref: labelRef, children: label }) }, "tooltip-" + rest.name), suffix ? (jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": dataTestId ? `${dataTestId}-suffix-container` : undefined, children: suffix })) : null, description ? (jsx(Tooltip, { className: cvaBinaryControlDescriptionTooltip(), dataTestId: dataTestId ? `${dataTestId}-Description-Tooltip` : undefined, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsx("span", { className: cvaBinaryControlDescription({ disabled: groupCtx?.disabled }), "data-testid": dataTestId ? `${dataTestId}-Description` : undefined, id: descriptionId, ref: descriptionRef, children: description }) }, "description-tooltip-" + rest.name)) : null] }));
1546
1555
  };
1547
1556
 
1548
1557
  const cvaTimeRange = cvaMerge([
@@ -2596,9 +2605,9 @@ const TimeRangeField = ({ className, dataTestId, onChange, isInvalid, errorMessa
2596
2605
  return (jsx(FormGroup, { dataTestId: dataTestId ? `${dataTestId}-FormGroup` : undefined, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: htmlFor, isInvalid: renderAsInvalid, label: label, tip: tip, children: jsx(TimeRange, { className: className, dataTestId: dataTestId, isInvalid: renderAsInvalid, onChange: onChange, ...rest, children: children }) }));
2597
2606
  };
2598
2607
 
2599
- const cvaToggleWrapper = cvaMerge(["flex", "gap-2", "items-center"]);
2600
- cvaMerge(["relative"]);
2601
- const cvaToggleTrack = cvaMerge(["shrink-0", "cursor-pointer", "rounded-full", "bg-slate-300", "hover:bg-slate-400", "active:bg-slate-500"], {
2608
+ const cvaToggleSwitchWrapper = cvaMerge(["grid", "grid-cols-[auto]", "items-center"]);
2609
+ const cvaToggleSwitchInput = cvaMerge(["absolute", "pointer-events-none", "w-0", "h-0", "opacity-0"]);
2610
+ const cvaToggleSwitchTrack = cvaMerge(["items-center", "shrink-0", "rounded-full"], {
2602
2611
  variants: {
2603
2612
  size: {
2604
2613
  small: ["w-5", "p-0.5"],
@@ -2606,13 +2615,18 @@ const cvaToggleTrack = cvaMerge(["shrink-0", "cursor-pointer", "rounded-full", "
2606
2615
  large: ["w-[38px]", "p-[3px]"],
2607
2616
  },
2608
2617
  disabled: {
2609
- true: ["cursor-not-allowed", "pointer-events-none", "bg-slate-200"],
2610
- false: "",
2618
+ true: ["cursor-not-allowed", "bg-slate-200"],
2619
+ false: ["cursor-pointer", "bg-slate-300", "hover:bg-slate-400", "active:bg-slate-500"],
2611
2620
  },
2612
2621
  toggled: {
2613
- true: ["bg-primary-600", "hover:bg-primary-700", "active:bg-primary-800"],
2622
+ true: "", // classes moved to compoundVariants to avoid conflicts with the disabled variant
2614
2623
  false: "",
2615
2624
  },
2625
+ focused: {
2626
+ true: "outline-native",
2627
+ false: "outline-0",
2628
+ auto: "[&:has(:focus-visible)]:outline-native",
2629
+ },
2616
2630
  },
2617
2631
  compoundVariants: [
2618
2632
  {
@@ -2620,12 +2634,19 @@ const cvaToggleTrack = cvaMerge(["shrink-0", "cursor-pointer", "rounded-full", "
2620
2634
  toggled: true,
2621
2635
  className: ["bg-slate-400"],
2622
2636
  },
2637
+ {
2638
+ disabled: false,
2639
+ toggled: true,
2640
+ className: ["hover:bg-primary-700", "active:bg-primary-800", "bg-primary-600"],
2641
+ },
2623
2642
  ],
2624
2643
  defaultVariants: {
2625
2644
  size: "medium",
2645
+ focused: "auto",
2646
+ disabled: false,
2626
2647
  },
2627
2648
  });
2628
- const cvaToggleThumb = cvaMerge(["block", "rounded-full", "bg-white", "aspect-square", "translate-x-0", "transition-all"], {
2649
+ const cvaToggleSwitchThumb = cvaMerge(["block", "rounded-full", "bg-white", "aspect-square", "translate-x-0", "transition-all"], {
2629
2650
  variants: {
2630
2651
  toggled: {
2631
2652
  true: "",
@@ -2656,38 +2677,70 @@ const cvaToggleThumb = cvaMerge(["block", "rounded-full", "bg-white", "aspect-sq
2656
2677
  ],
2657
2678
  defaultVariants: {},
2658
2679
  });
2659
- const cvaToggleInput = cvaMerge(["absolute", "opacity-0", "-z-10"]);
2660
- const cvaToggleLabelContainer = cvaMerge(["grid", "gap-1"]);
2661
- const cvaToggleLabel = cvaMerge([]);
2662
- const cvaToggleDescription = cvaMerge(["text-sm", "font-normal", "text-slate-500", "text-left", "whitespace-nowrap", "text-ellipsis", "overflow-hidden"], {
2663
- variants: {
2664
- disabled: {
2665
- true: "text-slate-400 hover:text-slate-400 group-hover:text-slate-400",
2666
- false: "",
2667
- },
2668
- },
2680
+
2681
+ /**
2682
+ * A checkbox input wrapper with role="switch". Used as an input element for **ToggleSwitchOption**
2683
+ * or custom components.
2684
+ *
2685
+ * Not intended for standalone use.
2686
+ *
2687
+ * @param {ToggleSwitchProps} props - The props for the ToggleSwitch component
2688
+ * @returns {ReactElement} ToggleSwitch component
2689
+ */
2690
+ const ToggleSwitch = forwardRef(({ onChange, onClick, preventDefaultOnClick, className, dataTestId = "toggle-switch", showInputFocus, toggled, size = "medium", tabIndex = 0, readOnly, disabled, ...rest }, ref) => {
2691
+ const localInputRef = useRef(null);
2692
+ const inputRef = typeof ref === "function" ? localInputRef : ref || localInputRef;
2693
+ const handleWrapperClick = (e) => {
2694
+ // Prevents double-toggling when wrapped in a label or if preventDefaultOnClick is true
2695
+ const isFromLabel = e.target instanceof Element && e.target.closest("label");
2696
+ if (!isFromLabel && !preventDefaultOnClick && !disabled && !readOnly) {
2697
+ inputRef.current?.click();
2698
+ }
2699
+ onClick?.(e);
2700
+ };
2701
+ const handleKeyPress = e => {
2702
+ if (e.code === "Enter") {
2703
+ e.preventDefault();
2704
+ !readOnly && inputRef.current?.click();
2705
+ }
2706
+ // Space key is already supported natively by the input element
2707
+ };
2708
+ const handleInputChange = (e) => {
2709
+ if (readOnly || disabled) {
2710
+ return;
2711
+ }
2712
+ e.stopPropagation();
2713
+ onChange?.(!toggled, e);
2714
+ };
2715
+ return (jsx("span", { className: cvaToggleSwitchWrapper({ className }), "data-testid": `${dataTestId}`, onClick: handleWrapperClick, onKeyDown: handleKeyPress, children: jsxs("span", { className: cvaToggleSwitchTrack({
2716
+ toggled,
2717
+ disabled: disabled || readOnly,
2718
+ size,
2719
+ focused: showInputFocus,
2720
+ }), "data-testid": `${dataTestId}-track`, children: [jsx("span", { className: cvaToggleSwitchThumb({ toggled, size }), "data-testid": `${dataTestId}-thumb` }), jsx("input", { "aria-disabled": disabled || readOnly, checked: toggled, className: cvaToggleSwitchInput(), "data-testid": `${dataTestId}-input`, disabled: disabled, onChange: handleInputChange, onClick: e => e.stopPropagation(), ref: inputRef, role: "switch", type: "checkbox", ...rest })] }) }));
2669
2721
  });
2722
+ ToggleSwitch.displayName = "ToggleSwitch";
2670
2723
 
2671
2724
  /**
2672
- * Use Toggle when you have to only enable/disable a feature.
2725
+ * Use ToggleSwitchOption when you have to only enable/disable a feature.
2726
+ * Wrapper component for ToggleSwitch.
2673
2727
  *
2674
- * _**Do use** Toggle in forms or settings._
2728
+ * _**Do use** ToggleSwitchOption in forms or settings._
2675
2729
  *
2676
- * _**Do not use** Toggle if a user can select many option from a list, use checkboxes instead of toggle._
2730
+ * _**Do not use** ToggleSwitchOption if a user can select multiple options from a list, use checkboxes instead of toggle._
2677
2731
  *
2678
- * @param {ToggleProps} props - The props for the Toggle component
2679
- * @returns {JSX.Element} Toggle component
2732
+ * @param {ToggleSwitchOptionProps} props - The props for the ToggleSwitchOption component
2733
+ * @returns {ReactElement} ToggleSwitchOption component
2680
2734
  */
2681
- const Toggle = React.forwardRef(({ toggled = false, onChange, onClick, disabled, size = "medium", id, tabIndex, required, className, dataTestId = "toggle", onBlur, name, description = "", }, ref) => {
2682
- const showLabelContainer = Boolean(name || description);
2683
- const showDescription = Boolean(description);
2684
- const getTestId = (suffix) => `${dataTestId}-${suffix}`;
2685
- return (jsx("span", { className: className, "data-testid": getTestId("outer-wrapper"), onClick: onClick, children: jsxs("label", { className: cvaToggleWrapper(), "data-testid": getTestId("wrapper"), htmlFor: id, children: [jsx("span", { className: cvaToggleTrack({ disabled, size, toggled }), "data-testid": getTestId("track"), children: jsx("span", { className: cvaToggleThumb({ toggled, size }), "data-testid": getTestId("thumb") }) }), showLabelContainer ? (jsxs("span", { className: cvaToggleLabelContainer(), "data-testid": getTestId("label-container"), children: [jsx(Label, { className: cvaToggleLabel(), "data-testid": getTestId("label"), disabled: disabled, children: name }), showDescription ? (jsx("span", { className: cvaToggleDescription({ disabled }), "data-testid": getTestId("description"), children: description })) : null] })) : null, jsx("input", { "aria-checked": toggled, checked: toggled, className: cvaToggleInput(), "data-testid": getTestId("input"), disabled: disabled, id: id, name: name, onBlur: onBlur, onChange: e => {
2686
- e.stopPropagation();
2687
- onChange(!toggled, e);
2688
- }, ref: ref, required: required, tabIndex: tabIndex, type: "checkbox" })] }) }));
2689
- });
2690
- Toggle.displayName = "Toggle";
2735
+ const ToggleSwitchOption = ({ label, className, description, suffix, id, dataTestId = "toggle-switch-option", ...rest }) => {
2736
+ const { ref: labelRef, isTextTruncated: isLabelTruncated } = useIsTextTruncated();
2737
+ const { ref: descriptionRef, isTextTruncated: isDescriptionTruncated } = useIsTextTruncated();
2738
+ return (jsxs("label", { className: cvaBinaryControlWrapper({ className }), "data-testid": dataTestId, htmlFor: `${id}-toggle-switch`, tabIndex: -1, children: [jsx(ToggleSwitch, { dataTestId: `${dataTestId}-switcher`, id: `${id}-toggle-switch`, ...rest }), jsx(Tooltip, { className: cvaBinaryControlLabelTooltip(), dataTestId: `${dataTestId}-label-tooltip`, disabled: !isLabelTruncated, label: label, placement: "top", children: jsx("span", { className: cvaLabel({
2739
+ disabled: rest.disabled || rest.readOnly,
2740
+ className: "select-none",
2741
+ }), "data-testid": `${dataTestId}-label`, ref: labelRef, children: label }) }, `${id}-label-tooltip`), suffix ? (jsx("div", { className: cvaBinaryControlSuffixContainer(), "data-testid": `${dataTestId}-suffix-container`, children: suffix })) : null, description ? (jsx(Tooltip, { className: cvaBinaryControlDescriptionTooltip(), dataTestId: `${dataTestId}-description-tooltip`, disabled: !isDescriptionTruncated, label: description, placement: "top", children: jsx("span", { className: cvaBinaryControlDescription({ disabled: rest.disabled || rest.readOnly }), "data-testid": `${dataTestId}-description`, id: `${id}-description`, ref: descriptionRef, children: description }) }, `${id}-description-tooltip`)) : null] }));
2742
+ };
2743
+ ToggleSwitchOption.displayName = "ToggleSwitchOption";
2691
2744
 
2692
2745
  const cvaUploadInputField = cvaMerge([
2693
2746
  "px-0",
@@ -2903,4 +2956,4 @@ const useZodValidators = () => {
2903
2956
  */
2904
2957
  setupLibraryTranslations();
2905
2958
 
2906
- export { ActionButton, BaseInput, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DateField, DateInput, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, EmailInput, FormFieldSelectAdapter, FormGroup, Label, MultiSelectMenuItem, NumberField, NumberInput, OptionCard, PasswordField, PasswordInput, PhoneField, PhoneFieldWithController, PhoneInput, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, Select, SelectField, SingleSelectMenuItem, TextArea, TextAreaField, TextField, TextInput, TimeRange, TimeRangeField, Toggle, UploadField, UploadInput, UrlField, UrlInput, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaAccessoriesContainer, cvaActionButton, cvaActionContainer, cvaInput, cvaInputAddon, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputItemPlacementManager, cvaInputPrefix, cvaInputSuffix, cvaSelect, cvaSelectControl, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefixSuffix, cvaSelectXIcon, getCountryAbbreviation, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isValidHEXColor, parseSchedule, phoneErrorMessage, serializeSchedule, useCustomComponents, useGetPhoneValidationRules, usePhoneInput, useZodValidators, validateEmailAddress, validatePhoneNumber, weekDay };
2959
+ export { ActionButton, BaseInput, Checkbox, CheckboxField, ColorField, CreatableSelect, CreatableSelectField, DateField, DateInput, DropZone, DropZoneDefaultLabel, EMAIL_REGEX, EmailField, EmailInput, FormFieldSelectAdapter, FormGroup, Label, MultiSelectMenuItem, NumberField, NumberInput, OptionCard, PasswordField, PasswordInput, PhoneField, PhoneFieldWithController, PhoneInput, RadioGroup, RadioItem, Schedule, ScheduleVariant, Search, Select, SelectField, SingleSelectMenuItem, TextArea, TextAreaField, TextField, TextInput, TimeRange, TimeRangeField, ToggleSwitch, ToggleSwitchOption, UploadField, UploadInput, UrlField, UrlInput, checkIfPhoneNumberHasPlus, countryCodeToFlagEmoji, cvaAccessoriesContainer, cvaActionButton, cvaActionContainer, cvaInput, cvaInputAddon, cvaInputBase, cvaInputBaseDisabled, cvaInputBaseInvalid, cvaInputField, cvaInputItemPlacementManager, cvaInputPrefix, cvaInputSuffix, cvaLabel, cvaSelect, cvaSelectControl, cvaSelectCounter, cvaSelectDynamicTagContainer, cvaSelectIcon, cvaSelectMenu, cvaSelectMenuList, cvaSelectPrefixSuffix, cvaSelectXIcon, getCountryAbbreviation, getPhoneNumberWithPlus, isInvalidCountryCode, isInvalidPhoneNumber, isValidHEXColor, parseSchedule, phoneErrorMessage, serializeSchedule, useCustomComponents, useGetPhoneValidationRules, usePhoneInput, useZodValidators, validateEmailAddress, validatePhoneNumber, weekDay };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-form-components",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -17,12 +17,12 @@
17
17
  "zod": "3.22.4",
18
18
  "react-hook-form": "7.53.1",
19
19
  "tailwind-merge": "^2.0.0",
20
- "@trackunit/css-class-variance-utilities": "1.1.1",
21
- "@trackunit/react-components": "1.2.1",
22
- "@trackunit/ui-icons": "1.1.1",
23
- "@trackunit/shared-utils": "1.3.1",
24
- "@trackunit/ui-design-tokens": "1.1.1",
25
- "@trackunit/i18n-library-translation": "1.1.1",
20
+ "@trackunit/css-class-variance-utilities": "1.2.0",
21
+ "@trackunit/react-components": "1.3.0",
22
+ "@trackunit/ui-icons": "1.2.0",
23
+ "@trackunit/shared-utils": "1.4.0",
24
+ "@trackunit/ui-design-tokens": "1.2.0",
25
+ "@trackunit/i18n-library-translation": "1.2.0",
26
26
  "string-ts": "^2.0.0"
27
27
  },
28
28
  "module": "./index.esm.js",
@@ -1,15 +1,15 @@
1
1
  import { CommonProps } from "@trackunit/react-components";
2
2
  import { MappedOmit } from "@trackunit/shared-utils";
3
- import * as React from "react";
4
- export interface CheckboxProps extends CommonProps, MappedOmit<React.InputHTMLAttributes<HTMLInputElement>, "prefix"> {
3
+ import { InputHTMLAttributes, ReactNode } from "react";
4
+ export interface CheckboxProps extends CommonProps, MappedOmit<InputHTMLAttributes<HTMLInputElement>, "prefix"> {
5
5
  /**
6
6
  * A string element to be displayed as a label
7
7
  */
8
- label?: string | React.ReactNode;
8
+ label?: string | ReactNode;
9
9
  /**
10
10
  * An element to display after the label
11
11
  */
12
- suffix?: React.ReactNode;
12
+ suffix?: ReactNode;
13
13
  /**
14
14
  * An boolean flag to set checkbox to checked state
15
15
  */
@@ -50,4 +50,4 @@ export interface CheckboxProps extends CommonProps, MappedOmit<React.InputHTMLAt
50
50
  * @param {CheckboxProps} props - The props for the Checkbox component
51
51
  * @returns {JSX.Element} Checkbox component
52
52
  */
53
- export declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
53
+ export declare const Checkbox: import("react").ForwardRefExoticComponent<CheckboxProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -3,6 +3,5 @@ export declare const cvaCheckbox: (props?: ({
3
3
  state?: "selected" | "deselected" | "indeterminate" | null | undefined;
4
4
  disabled?: boolean | null | undefined;
5
5
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
6
- export declare const cvaCheckboxContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
7
6
  export declare const cvaCheckboxInput: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
8
7
  export declare const cvaCheckboxIcon: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
@@ -1,4 +1,5 @@
1
1
  export declare const cvaLabel: (props?: ({
2
2
  invalid?: boolean | null | undefined;
3
3
  disabled?: boolean | null | undefined;
4
+ truncate?: boolean | null | undefined;
4
5
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
@@ -1 +1,2 @@
1
1
  export * from "./Label";
2
+ export * from "./Label.variants";
@@ -6,10 +6,3 @@ export declare const cvaRadioItem: (props?: ({
6
6
  invalid?: boolean | null | undefined;
7
7
  disabled?: boolean | null | undefined;
8
8
  } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
9
- export declare const cvaRadioItemWrapper: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
10
- export declare const cvaRadioItemDescription: (props?: ({
11
- disabled?: boolean | null | undefined;
12
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
13
- export declare const cvaLabelTooltip: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
14
- export declare const cvaDescriptionTooltip: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
15
- export declare const cvaSuffixContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
@@ -0,0 +1,51 @@
1
+ import { CommonProps } from "@trackunit/react-components";
2
+ import { MappedOmit, Size } from "@trackunit/shared-utils";
3
+ import { InputHTMLAttributes } from "react";
4
+ export interface ToggleSwitchProps extends CommonProps, MappedOmit<InputHTMLAttributes<HTMLInputElement>, "prefix" | "onChange" | "size" | "children"> {
5
+ /**
6
+ * custom onChange event handler.
7
+ */
8
+ onChange?: (toggled: boolean, event?: React.ChangeEvent<HTMLInputElement>) => void;
9
+ /**
10
+ * onClick event handler.
11
+ *
12
+ * _**Do not**_ pass onClick handlers to trigger the toggle behavior. Use the onChange handler instead.
13
+ */
14
+ onClick?: (e: React.MouseEvent<HTMLSpanElement>) => void;
15
+ /**
16
+ * Whether the ToggleSwitch is toggled(active) or not.
17
+ */
18
+ toggled: boolean;
19
+ /**
20
+ * Size of the toggle.
21
+ */
22
+ size?: Size;
23
+ /**
24
+ * Whether the track should have a focus outline.
25
+ *
26
+ * Set it to false when wrapped by a component that receives focus styles instead (for example in a filter/menu item)
27
+ *
28
+ * @default true
29
+ */
30
+ showInputFocus?: boolean;
31
+ /**
32
+ * Tab index for the track element
33
+ *
34
+ * @default 0
35
+ */
36
+ tabIndex?: number;
37
+ /**
38
+ * Prevents the native checkbox toggle behavior of the toggle switch.
39
+ */
40
+ preventDefaultOnClick?: boolean;
41
+ }
42
+ /**
43
+ * A checkbox input wrapper with role="switch". Used as an input element for **ToggleSwitchOption**
44
+ * or custom components.
45
+ *
46
+ * Not intended for standalone use.
47
+ *
48
+ * @param {ToggleSwitchProps} props - The props for the ToggleSwitch component
49
+ * @returns {ReactElement} ToggleSwitch component
50
+ */
51
+ export declare const ToggleSwitch: import("react").ForwardRefExoticComponent<ToggleSwitchProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,12 @@
1
+ export declare const cvaToggleSwitchWrapper: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
2
+ export declare const cvaToggleSwitchInput: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
3
+ export declare const cvaToggleSwitchTrack: (props?: ({
4
+ size?: "small" | "medium" | "large" | null | undefined;
5
+ disabled?: boolean | null | undefined;
6
+ toggled?: boolean | null | undefined;
7
+ focused?: boolean | "auto" | null | undefined;
8
+ } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
9
+ export declare const cvaToggleSwitchThumb: (props?: ({
10
+ toggled?: boolean | null | undefined;
11
+ size?: "small" | "medium" | "large" | null | undefined;
12
+ } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
@@ -0,0 +1 @@
1
+ export * from "./ToggleSwitch";
@@ -0,0 +1,31 @@
1
+ import { ReactElement } from "react";
2
+ import { ToggleSwitchProps } from "../ToggleSwitch";
3
+ export interface ToggleSwitchOptionProps extends ToggleSwitchProps {
4
+ /**
5
+ * The label of the toggle.
6
+ */
7
+ label: ReactElement | string;
8
+ /**
9
+ * The description of the toggle.
10
+ */
11
+ description?: ReactElement | string;
12
+ /**
13
+ * An element to display after the label
14
+ */
15
+ suffix?: ReactElement;
16
+ }
17
+ /**
18
+ * Use ToggleSwitchOption when you have to only enable/disable a feature.
19
+ * Wrapper component for ToggleSwitch.
20
+ *
21
+ * _**Do use** ToggleSwitchOption in forms or settings._
22
+ *
23
+ * _**Do not use** ToggleSwitchOption if a user can select multiple options from a list, use checkboxes instead of toggle._
24
+ *
25
+ * @param {ToggleSwitchOptionProps} props - The props for the ToggleSwitchOption component
26
+ * @returns {ReactElement} ToggleSwitchOption component
27
+ */
28
+ export declare const ToggleSwitchOption: {
29
+ ({ label, className, description, suffix, id, dataTestId, ...rest }: ToggleSwitchOptionProps): ReactElement;
30
+ displayName: string;
31
+ };
@@ -0,0 +1 @@
1
+ export * from "./ToggleSwitchOption";
package/src/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export * from "./components/DropZone/DropZoneDefaultLabel";
10
10
  export * from "./components/EmailField/EmailField";
11
11
  export * from "./components/EmailInput";
12
12
  export * from "./components/FormGroup/FormGroup";
13
- export * from "./components/Label/Label";
13
+ export * from "./components/Label";
14
14
  export * from "./components/NumberField/NumberField";
15
15
  export * from "./components/NumberInput/NumberInput";
16
16
  export * from "./components/OptionCard/OptionCard";
@@ -35,7 +35,8 @@ export * from "./components/TextField";
35
35
  export * from "./components/TextInput/TextInput";
36
36
  export * from "./components/TimeRange";
37
37
  export * from "./components/TimeRangeField/TimeRangeField";
38
- export * from "./components/Toggle";
38
+ export * from "./components/ToggleSwitch";
39
+ export * from "./components/ToggleSwitchOption";
39
40
  export * from "./components/UploadField/UploadField";
40
41
  export * from "./components/UploadInput/UploadInput";
41
42
  export * from "./components/UrlField/UrlField";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared CVA for binary control items: Checkbox, RadioItem, ToggleSwitchOption
3
+ */
4
+ export declare const cvaBinaryControlWrapper: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
5
+ export declare const cvaBinaryControlLabelTooltip: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
6
+ export declare const cvaBinaryControlDescriptionTooltip: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
7
+ export declare const cvaBinaryControlDescription: (props?: ({
8
+ disabled?: boolean | null | undefined;
9
+ } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
10
+ export declare const cvaBinaryControlSuffixContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
@@ -1,60 +0,0 @@
1
- import { CommonProps } from "@trackunit/react-components";
2
- import { Size } from "@trackunit/shared-utils";
3
- import * as React from "react";
4
- export interface ToggleProps extends CommonProps {
5
- /**
6
- * id of the toggle.
7
- */
8
- id: string;
9
- /**
10
- * Whether the toggle is toggled(active) or not.
11
- */
12
- toggled: boolean;
13
- /**
14
- * onChange event handler.
15
- */
16
- onChange: (toggled: boolean, event?: React.ChangeEvent<HTMLInputElement>) => void;
17
- /**
18
- * onClick event handler it is thrown on root element of the component tree.
19
- */
20
- onClick?: React.MouseEventHandler<HTMLDivElement>;
21
- /**
22
- * Whether the toggle is disabled or not.
23
- */
24
- disabled?: boolean;
25
- /**
26
- * Size of the toggle.
27
- */
28
- size?: Size;
29
- /**
30
- * Tab index specifies the keyboard tab order of the toggle relative to the other controls on the page.
31
- */
32
- tabIndex?: number;
33
- /**
34
- * Whether the toggle is required or not.
35
- */
36
- required?: boolean;
37
- /**
38
- * Name of the toggle.
39
- */
40
- name?: string;
41
- /**
42
- * The description of the toggle.
43
- */
44
- description?: string;
45
- /**
46
- * onBlur event handler.
47
- */
48
- onBlur?: React.FocusEventHandler<HTMLInputElement>;
49
- }
50
- /**
51
- * Use Toggle when you have to only enable/disable a feature.
52
- *
53
- * _**Do use** Toggle in forms or settings._
54
- *
55
- * _**Do not use** Toggle if a user can select many option from a list, use checkboxes instead of toggle._
56
- *
57
- * @param {ToggleProps} props - The props for the Toggle component
58
- * @returns {JSX.Element} Toggle component
59
- */
60
- export declare const Toggle: React.ForwardRefExoticComponent<ToggleProps & React.RefAttributes<HTMLInputElement>>;
@@ -1,17 +0,0 @@
1
- export declare const cvaToggleWrapper: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
2
- export declare const cvaToggleInputContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
3
- export declare const cvaToggleTrack: (props?: ({
4
- size?: "small" | "medium" | "large" | null | undefined;
5
- disabled?: boolean | null | undefined;
6
- toggled?: boolean | null | undefined;
7
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
8
- export declare const cvaToggleThumb: (props?: ({
9
- toggled?: boolean | null | undefined;
10
- size?: "small" | "medium" | "large" | null | undefined;
11
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
12
- export declare const cvaToggleInput: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
13
- export declare const cvaToggleLabelContainer: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
14
- export declare const cvaToggleLabel: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
15
- export declare const cvaToggleDescription: (props?: ({
16
- disabled?: boolean | null | undefined;
17
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
@@ -1 +0,0 @@
1
- export * from "./Toggle";