@simplybusiness/mobius 6.6.0 → 6.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1a28ffc: Add support for auto-stacking in RadioGroup
8
+
3
9
  ## 6.6.0
4
10
 
5
11
  ### Minor Changes
package/dist/cjs/index.js CHANGED
@@ -3738,12 +3738,19 @@ var Radio = (0, import_react62.forwardRef)(
3738
3738
  selected,
3739
3739
  setSelected,
3740
3740
  isRequired,
3741
+ onOverflow,
3741
3742
  ...otherProps
3742
3743
  } = props;
3743
3744
  const realDisabled = groupDisabled || isDisabled;
3744
3745
  const isMultiline = label && children;
3745
3746
  const isControlled = selected !== void 0;
3746
3747
  const isChecked = isControlled ? selected === value : defaultChecked;
3748
+ const contentRef = (0, import_react62.useRef)(null);
3749
+ const prevOverflowRef = (0, import_react62.useRef)({
3750
+ vertical: false,
3751
+ horizontal: false
3752
+ });
3753
+ const currentOrientation = otherProps.orientation;
3747
3754
  const hasIconFirst = (0, import_react62.useMemo)(() => {
3748
3755
  if (!children || import_react62.Children.count(children) === 0) return false;
3749
3756
  const firstChild = import_react62.Children.toArray(children)[0];
@@ -3751,6 +3758,29 @@ var Radio = (0, import_react62.forwardRef)(
3751
3758
  const props2 = firstChild.props;
3752
3759
  return "icon" in props2 && props2.icon !== void 0;
3753
3760
  }, [children]);
3761
+ (0, import_react62.useLayoutEffect)(() => {
3762
+ if (!contentRef.current || !onOverflow) return;
3763
+ if (currentOrientation === "vertical") {
3764
+ return;
3765
+ }
3766
+ const element = contentRef.current;
3767
+ const scrollOverflowVertical = element.scrollHeight > element.clientHeight;
3768
+ const scrollOverflowHorizontal = element.scrollWidth > element.clientWidth;
3769
+ const styles = window.getComputedStyle(element);
3770
+ const lineHeight = parseFloat(styles.lineHeight);
3771
+ const fontSize = parseFloat(styles.fontSize);
3772
+ const singleLineHeight = isNaN(lineHeight) ? fontSize * 1.2 : lineHeight;
3773
+ const WRAP_DETECTION_TOLERANCE = 1.1;
3774
+ const hasWrapped = element.clientHeight > singleLineHeight * WRAP_DETECTION_TOLERANCE;
3775
+ const vertical = scrollOverflowVertical || hasWrapped;
3776
+ const horizontal = scrollOverflowHorizontal;
3777
+ const newOverflowState = { vertical, horizontal };
3778
+ const prevOverflow = prevOverflowRef.current;
3779
+ if (newOverflowState.vertical !== prevOverflow.vertical || newOverflowState.horizontal !== prevOverflow.horizontal) {
3780
+ prevOverflowRef.current = newOverflowState;
3781
+ onOverflow(newOverflowState);
3782
+ }
3783
+ }, [label, children, onOverflow, currentOrientation]);
3754
3784
  const radioClasses = {
3755
3785
  "--is-disabled": realDisabled,
3756
3786
  "--is-selected": selected === value,
@@ -3801,10 +3831,10 @@ var Radio = (0, import_react62.forwardRef)(
3801
3831
  ...rest
3802
3832
  }
3803
3833
  ),
3804
- isMultiline ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "mobius-radio__content--multiline", children: [
3834
+ isMultiline ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { ref: contentRef, className: "mobius-radio__content--multiline", children: [
3805
3835
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "mobius-radio__content-first-line", children: label }),
3806
3836
  /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "mobius-radio__extra-content", children })
3807
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "mobius-radio__content", children: label || children })
3837
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { ref: contentRef, className: "mobius-radio__content", children: label || children })
3808
3838
  ] }),
3809
3839
  errorMessage && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(ErrorMessage, { errorMessage })
3810
3840
  ] });
@@ -3840,15 +3870,34 @@ var RadioGroup = (0, import_react63.forwardRef)((props, ref) => {
3840
3870
  isReadOnly,
3841
3871
  name,
3842
3872
  onChange,
3873
+ autoStack = false,
3843
3874
  ...rest
3844
3875
  } = props;
3845
3876
  const defaultSelected = getDefaultVal(children, value || defaultValue);
3846
3877
  const [selected, setSelected] = (0, import_react63.useState)(defaultSelected);
3878
+ const overflowsRef = (0, import_react63.useRef)({});
3879
+ const [hasOverflow, setHasOverflow] = (0, import_react63.useState)(false);
3847
3880
  (0, import_react63.useEffect)(() => {
3848
3881
  if (value !== void 0) {
3849
3882
  setSelected(value);
3850
3883
  }
3851
3884
  }, [value]);
3885
+ const handleOverflow = (0, import_react63.useCallback)(
3886
+ (radioValue, overflow) => {
3887
+ overflowsRef.current = {
3888
+ ...overflowsRef.current,
3889
+ [radioValue]: overflow
3890
+ };
3891
+ const anyOverflow = Object.values(overflowsRef.current).some(
3892
+ (o) => o.vertical || o.horizontal
3893
+ );
3894
+ if (anyOverflow !== hasOverflow) {
3895
+ setHasOverflow(anyOverflow);
3896
+ }
3897
+ },
3898
+ [hasOverflow]
3899
+ );
3900
+ const effectiveOrientation = autoStack && orientation === "horizontal" && hasOverflow ? "vertical" : orientation;
3852
3901
  const validationClasses = useValidationClasses({
3853
3902
  validationState,
3854
3903
  isInvalid
@@ -3857,7 +3906,7 @@ var RadioGroup = (0, import_react63.forwardRef)((props, ref) => {
3857
3906
  "--is-disabled": isDisabled,
3858
3907
  "--is-required": typeof isRequired === "boolean" && isRequired,
3859
3908
  "--is-optional": typeof isRequired === "boolean" && !isRequired,
3860
- [`--is-${orientation}`]: true,
3909
+ [`--is-${effectiveOrientation}`]: true,
3861
3910
  [className || ""]: true
3862
3911
  };
3863
3912
  const radioGroupClasses = (0, import_dedupe39.default)(
@@ -3867,7 +3916,7 @@ var RadioGroup = (0, import_react63.forwardRef)((props, ref) => {
3867
3916
  validationClasses
3868
3917
  );
3869
3918
  const radioWrapperClasses = (0, import_dedupe39.default)("mobius-radio__wrapper", {
3870
- [`--is-${orientation}`]: true
3919
+ [`--is-${effectiveOrientation}`]: true
3871
3920
  });
3872
3921
  const labelClasses = (0, import_dedupe39.default)(radioClasses, validationClasses);
3873
3922
  const errorMessageId = (0, import_react63.useId)();
@@ -3889,7 +3938,7 @@ var RadioGroup = (0, import_react63.forwardRef)((props, ref) => {
3889
3938
  "aria-invalid": isInvalid,
3890
3939
  "aria-label": props["aria-label"],
3891
3940
  "aria-labelledby": props["aria-labelledby"] || labelId,
3892
- "aria-orientation": orientation,
3941
+ "aria-orientation": effectiveOrientation,
3893
3942
  "aria-readonly": isReadOnly,
3894
3943
  "aria-required": isRequired,
3895
3944
  ref,
@@ -3899,10 +3948,11 @@ var RadioGroup = (0, import_react63.forwardRef)((props, ref) => {
3899
3948
  label && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Label, { htmlFor: name, id: labelId, className: labelClasses, children: label }),
3900
3949
  /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: radioWrapperClasses, children: import_react63.Children.map(children, (child) => {
3901
3950
  if ((0, import_react63.isValidElement)(child)) {
3951
+ const childValue = child.props.value;
3902
3952
  return (0, import_react63.cloneElement)(
3903
3953
  child,
3904
3954
  {
3905
- orientation,
3955
+ orientation: effectiveOrientation,
3906
3956
  groupDisabled: isDisabled,
3907
3957
  name: nameAttribute,
3908
3958
  selected: selected || defaultSelected,
@@ -3910,7 +3960,8 @@ var RadioGroup = (0, import_react63.forwardRef)((props, ref) => {
3910
3960
  setSelected,
3911
3961
  isRequired,
3912
3962
  "aria-describedby": describedBy,
3913
- onChange
3963
+ onChange,
3964
+ onOverflow: childValue ? (overflow) => handleOverflow(childValue, overflow) : void 0
3914
3965
  }
3915
3966
  );
3916
3967
  }
package/dist/esm/index.js CHANGED
@@ -3639,7 +3639,14 @@ Progress.displayName = "Progress";
3639
3639
 
3640
3640
  // src/components/Radio/Radio.tsx
3641
3641
  import classNames41 from "classnames/dedupe";
3642
- import { Children as Children7, forwardRef as forwardRef38, isValidElement as isValidElement5, useMemo as useMemo3 } from "react";
3642
+ import {
3643
+ Children as Children7,
3644
+ forwardRef as forwardRef38,
3645
+ isValidElement as isValidElement5,
3646
+ useLayoutEffect,
3647
+ useMemo as useMemo3,
3648
+ useRef as useRef16
3649
+ } from "react";
3643
3650
  import { Fragment as Fragment4, jsx as jsx50, jsxs as jsxs24 } from "react/jsx-runtime";
3644
3651
  var Radio = forwardRef38(
3645
3652
  (props, ref) => {
@@ -3657,12 +3664,19 @@ var Radio = forwardRef38(
3657
3664
  selected,
3658
3665
  setSelected,
3659
3666
  isRequired,
3667
+ onOverflow,
3660
3668
  ...otherProps
3661
3669
  } = props;
3662
3670
  const realDisabled = groupDisabled || isDisabled;
3663
3671
  const isMultiline = label && children;
3664
3672
  const isControlled = selected !== void 0;
3665
3673
  const isChecked = isControlled ? selected === value : defaultChecked;
3674
+ const contentRef = useRef16(null);
3675
+ const prevOverflowRef = useRef16({
3676
+ vertical: false,
3677
+ horizontal: false
3678
+ });
3679
+ const currentOrientation = otherProps.orientation;
3666
3680
  const hasIconFirst = useMemo3(() => {
3667
3681
  if (!children || Children7.count(children) === 0) return false;
3668
3682
  const firstChild = Children7.toArray(children)[0];
@@ -3670,6 +3684,29 @@ var Radio = forwardRef38(
3670
3684
  const props2 = firstChild.props;
3671
3685
  return "icon" in props2 && props2.icon !== void 0;
3672
3686
  }, [children]);
3687
+ useLayoutEffect(() => {
3688
+ if (!contentRef.current || !onOverflow) return;
3689
+ if (currentOrientation === "vertical") {
3690
+ return;
3691
+ }
3692
+ const element = contentRef.current;
3693
+ const scrollOverflowVertical = element.scrollHeight > element.clientHeight;
3694
+ const scrollOverflowHorizontal = element.scrollWidth > element.clientWidth;
3695
+ const styles = window.getComputedStyle(element);
3696
+ const lineHeight = parseFloat(styles.lineHeight);
3697
+ const fontSize = parseFloat(styles.fontSize);
3698
+ const singleLineHeight = isNaN(lineHeight) ? fontSize * 1.2 : lineHeight;
3699
+ const WRAP_DETECTION_TOLERANCE = 1.1;
3700
+ const hasWrapped = element.clientHeight > singleLineHeight * WRAP_DETECTION_TOLERANCE;
3701
+ const vertical = scrollOverflowVertical || hasWrapped;
3702
+ const horizontal = scrollOverflowHorizontal;
3703
+ const newOverflowState = { vertical, horizontal };
3704
+ const prevOverflow = prevOverflowRef.current;
3705
+ if (newOverflowState.vertical !== prevOverflow.vertical || newOverflowState.horizontal !== prevOverflow.horizontal) {
3706
+ prevOverflowRef.current = newOverflowState;
3707
+ onOverflow(newOverflowState);
3708
+ }
3709
+ }, [label, children, onOverflow, currentOrientation]);
3673
3710
  const radioClasses = {
3674
3711
  "--is-disabled": realDisabled,
3675
3712
  "--is-selected": selected === value,
@@ -3720,10 +3757,10 @@ var Radio = forwardRef38(
3720
3757
  ...rest
3721
3758
  }
3722
3759
  ),
3723
- isMultiline ? /* @__PURE__ */ jsxs24("div", { className: "mobius-radio__content--multiline", children: [
3760
+ isMultiline ? /* @__PURE__ */ jsxs24("div", { ref: contentRef, className: "mobius-radio__content--multiline", children: [
3724
3761
  /* @__PURE__ */ jsx50("div", { className: "mobius-radio__content-first-line", children: label }),
3725
3762
  /* @__PURE__ */ jsx50("div", { className: "mobius-radio__extra-content", children })
3726
- ] }) : /* @__PURE__ */ jsx50("div", { className: "mobius-radio__content", children: label || children })
3763
+ ] }) : /* @__PURE__ */ jsx50("div", { ref: contentRef, className: "mobius-radio__content", children: label || children })
3727
3764
  ] }),
3728
3765
  errorMessage && /* @__PURE__ */ jsx50(ErrorMessage, { errorMessage })
3729
3766
  ] });
@@ -3738,8 +3775,10 @@ import {
3738
3775
  cloneElement as cloneElement10,
3739
3776
  forwardRef as forwardRef39,
3740
3777
  isValidElement as isValidElement6,
3778
+ useCallback as useCallback6,
3741
3779
  useEffect as useEffect22,
3742
3780
  useId as useId10,
3781
+ useRef as useRef17,
3743
3782
  useState as useState20
3744
3783
  } from "react";
3745
3784
  import { jsx as jsx51, jsxs as jsxs25 } from "react/jsx-runtime";
@@ -3767,15 +3806,34 @@ var RadioGroup = forwardRef39((props, ref) => {
3767
3806
  isReadOnly,
3768
3807
  name,
3769
3808
  onChange,
3809
+ autoStack = false,
3770
3810
  ...rest
3771
3811
  } = props;
3772
3812
  const defaultSelected = getDefaultVal(children, value || defaultValue);
3773
3813
  const [selected, setSelected] = useState20(defaultSelected);
3814
+ const overflowsRef = useRef17({});
3815
+ const [hasOverflow, setHasOverflow] = useState20(false);
3774
3816
  useEffect22(() => {
3775
3817
  if (value !== void 0) {
3776
3818
  setSelected(value);
3777
3819
  }
3778
3820
  }, [value]);
3821
+ const handleOverflow = useCallback6(
3822
+ (radioValue, overflow) => {
3823
+ overflowsRef.current = {
3824
+ ...overflowsRef.current,
3825
+ [radioValue]: overflow
3826
+ };
3827
+ const anyOverflow = Object.values(overflowsRef.current).some(
3828
+ (o) => o.vertical || o.horizontal
3829
+ );
3830
+ if (anyOverflow !== hasOverflow) {
3831
+ setHasOverflow(anyOverflow);
3832
+ }
3833
+ },
3834
+ [hasOverflow]
3835
+ );
3836
+ const effectiveOrientation = autoStack && orientation === "horizontal" && hasOverflow ? "vertical" : orientation;
3779
3837
  const validationClasses = useValidationClasses({
3780
3838
  validationState,
3781
3839
  isInvalid
@@ -3784,7 +3842,7 @@ var RadioGroup = forwardRef39((props, ref) => {
3784
3842
  "--is-disabled": isDisabled,
3785
3843
  "--is-required": typeof isRequired === "boolean" && isRequired,
3786
3844
  "--is-optional": typeof isRequired === "boolean" && !isRequired,
3787
- [`--is-${orientation}`]: true,
3845
+ [`--is-${effectiveOrientation}`]: true,
3788
3846
  [className || ""]: true
3789
3847
  };
3790
3848
  const radioGroupClasses = classNames42(
@@ -3794,7 +3852,7 @@ var RadioGroup = forwardRef39((props, ref) => {
3794
3852
  validationClasses
3795
3853
  );
3796
3854
  const radioWrapperClasses = classNames42("mobius-radio__wrapper", {
3797
- [`--is-${orientation}`]: true
3855
+ [`--is-${effectiveOrientation}`]: true
3798
3856
  });
3799
3857
  const labelClasses = classNames42(radioClasses, validationClasses);
3800
3858
  const errorMessageId = useId10();
@@ -3816,7 +3874,7 @@ var RadioGroup = forwardRef39((props, ref) => {
3816
3874
  "aria-invalid": isInvalid,
3817
3875
  "aria-label": props["aria-label"],
3818
3876
  "aria-labelledby": props["aria-labelledby"] || labelId,
3819
- "aria-orientation": orientation,
3877
+ "aria-orientation": effectiveOrientation,
3820
3878
  "aria-readonly": isReadOnly,
3821
3879
  "aria-required": isRequired,
3822
3880
  ref,
@@ -3826,10 +3884,11 @@ var RadioGroup = forwardRef39((props, ref) => {
3826
3884
  label && /* @__PURE__ */ jsx51(Label, { htmlFor: name, id: labelId, className: labelClasses, children: label }),
3827
3885
  /* @__PURE__ */ jsx51("div", { className: radioWrapperClasses, children: Children8.map(children, (child) => {
3828
3886
  if (isValidElement6(child)) {
3887
+ const childValue = child.props.value;
3829
3888
  return cloneElement10(
3830
3889
  child,
3831
3890
  {
3832
- orientation,
3891
+ orientation: effectiveOrientation,
3833
3892
  groupDisabled: isDisabled,
3834
3893
  name: nameAttribute,
3835
3894
  selected: selected || defaultSelected,
@@ -3837,7 +3896,8 @@ var RadioGroup = forwardRef39((props, ref) => {
3837
3896
  setSelected,
3838
3897
  isRequired,
3839
3898
  "aria-describedby": describedBy,
3840
- onChange
3899
+ onChange,
3900
+ onOverflow: childValue ? (overflow) => handleOverflow(childValue, overflow) : void 0
3841
3901
  }
3842
3902
  );
3843
3903
  }
@@ -3971,10 +4031,10 @@ Select.displayName = "Select";
3971
4031
 
3972
4032
  // src/components/Slider/Slider.tsx
3973
4033
  import classNames46 from "classnames/dedupe";
3974
- import { useRef as useRef16, useState as useState21 } from "react";
4034
+ import { useRef as useRef18, useState as useState21 } from "react";
3975
4035
 
3976
4036
  // src/components/Slider/helpers.ts
3977
- import { useCallback as useCallback6, useMemo as useMemo4 } from "react";
4037
+ import { useCallback as useCallback7, useMemo as useMemo4 } from "react";
3978
4038
  function numberFormatter(value, formatOptions, locale = navigator.languages?.[0] || "en-GB") {
3979
4039
  if (!formatOptions) {
3980
4040
  return value?.toString() || "";
@@ -4004,7 +4064,7 @@ var Slider = (props) => {
4004
4064
  formatOptions,
4005
4065
  isDisabled = false
4006
4066
  } = props;
4007
- const trackRef = useRef16(null);
4067
+ const trackRef = useRef18(null);
4008
4068
  const [currentValue, setCurrentValue] = useState21(
4009
4069
  value || defaultValue || 0
4010
4070
  );
@@ -4402,7 +4462,7 @@ Title.displayName = "Title";
4402
4462
 
4403
4463
  // src/components/Trust/Trust.tsx
4404
4464
  import classNames60 from "classnames/dedupe";
4405
- import { forwardRef as forwardRef55, useEffect as useEffect24, useRef as useRef17, useState as useState23 } from "react";
4465
+ import { forwardRef as forwardRef55, useEffect as useEffect24, useRef as useRef19, useState as useState23 } from "react";
4406
4466
 
4407
4467
  // src/components/Trust/constants.ts
4408
4468
  var REQUIRED_TRUSTPILOT_CLASS_NAME = "trustpilot-widget";
@@ -4479,7 +4539,7 @@ var Trust = forwardRef55((props, ref) => {
4479
4539
  className
4480
4540
  } = props;
4481
4541
  const [isMounted, setIsMounted] = useState23(false);
4482
- const trustRef = useRef17(null);
4542
+ const trustRef = useRef19(null);
4483
4543
  const {
4484
4544
  templateId,
4485
4545
  className: variantClassName,
@@ -4539,7 +4599,7 @@ var Trust = forwardRef55((props, ref) => {
4539
4599
 
4540
4600
  // src/components/ExpandableText/ExpandableText.tsx
4541
4601
  import classNames61 from "classnames/dedupe";
4542
- import { forwardRef as forwardRef56, useEffect as useEffect25, useId as useId12, useRef as useRef18, useState as useState24 } from "react";
4602
+ import { forwardRef as forwardRef56, useEffect as useEffect25, useId as useId12, useRef as useRef20, useState as useState24 } from "react";
4543
4603
  import { jsx as jsx71, jsxs as jsxs32 } from "react/jsx-runtime";
4544
4604
  var ExpandableText = forwardRef56((props, ref) => {
4545
4605
  const {
@@ -4556,7 +4616,7 @@ var ExpandableText = forwardRef56((props, ref) => {
4556
4616
  } = props;
4557
4617
  const [isExpanded, setIsExpanded] = useState24(false);
4558
4618
  const [isCollapsed, setIsCollapsed] = useState24(false);
4559
- const textRef = useRef18(null);
4619
+ const textRef = useRef20(null);
4560
4620
  const { down } = useBreakpoint();
4561
4621
  const baseId = useId12();
4562
4622
  const expandButtonId = `expandable-text-expand-${baseId}`;
@@ -4624,11 +4684,11 @@ var ExpandableText = forwardRef56((props, ref) => {
4624
4684
  ExpandableText.displayName = "ExpandableText";
4625
4685
 
4626
4686
  // src/components/MaskedField/MaskedField.tsx
4627
- import { forwardRef as forwardRef57, useCallback as useCallback7, useEffect as useEffect26 } from "react";
4687
+ import { forwardRef as forwardRef57, useCallback as useCallback8, useEffect as useEffect26 } from "react";
4628
4688
  import { useIMask } from "react-imask";
4629
4689
  import { jsx as jsx72 } from "react/jsx-runtime";
4630
4690
  var useAcceptHandler = (onChange, useMaskedValue, name) => {
4631
- return useCallback7(
4691
+ return useCallback8(
4632
4692
  (maskedValue, maskInstance) => {
4633
4693
  if (!onChange) {
4634
4694
  return;
@@ -4644,7 +4704,7 @@ var useAcceptHandler = (onChange, useMaskedValue, name) => {
4644
4704
  );
4645
4705
  };
4646
4706
  var useCombinedRef = (imaskRef, forwardedRef) => {
4647
- return useCallback7(
4707
+ return useCallback8(
4648
4708
  (element) => {
4649
4709
  imaskRef.current = element;
4650
4710
  if (typeof forwardedRef === "function") {
@@ -4657,7 +4717,7 @@ var useCombinedRef = (imaskRef, forwardedRef) => {
4657
4717
  );
4658
4718
  };
4659
4719
  var useBlurHandler = (onBlur, maskRef, useMaskedValue, name) => {
4660
- return useCallback7(
4720
+ return useCallback8(
4661
4721
  (event) => {
4662
4722
  if (!onBlur || !maskRef.current) {
4663
4723
  return;