carbon-react 119.9.2 → 119.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/esm/components/button/button.component.js +8 -1
  2. package/esm/components/checkbox/checkbox-group.component.d.ts +4 -0
  3. package/esm/components/checkbox/checkbox-group.component.js +45 -5
  4. package/esm/components/checkbox/checkbox-group.style.d.ts +2 -0
  5. package/esm/components/checkbox/checkbox-group.style.js +18 -1
  6. package/esm/components/checkbox/checkbox.component.js +17 -13
  7. package/esm/components/multi-action-button/multi-action-button.component.js +21 -68
  8. package/esm/components/split-button/__internal__/split-button.context.d.ts +7 -0
  9. package/esm/components/split-button/__internal__/split-button.context.js +4 -0
  10. package/esm/components/split-button/split-button.component.js +20 -61
  11. package/esm/hooks/__internal__/useChildButtons/index.d.ts +1 -0
  12. package/esm/hooks/__internal__/useChildButtons/index.js +1 -0
  13. package/esm/hooks/__internal__/useChildButtons/useChildButtons.d.ts +21 -0
  14. package/esm/hooks/__internal__/useChildButtons/useChildButtons.js +71 -0
  15. package/esm/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.d.ts +1 -1
  16. package/esm/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.js +8 -7
  17. package/lib/components/button/button.component.js +8 -1
  18. package/lib/components/checkbox/checkbox-group.component.d.ts +4 -0
  19. package/lib/components/checkbox/checkbox-group.component.js +47 -5
  20. package/lib/components/checkbox/checkbox-group.style.d.ts +2 -0
  21. package/lib/components/checkbox/checkbox-group.style.js +20 -2
  22. package/lib/components/checkbox/checkbox.component.js +17 -13
  23. package/lib/components/multi-action-button/multi-action-button.component.js +20 -67
  24. package/lib/components/split-button/__internal__/split-button.context.d.ts +7 -0
  25. package/lib/components/split-button/__internal__/split-button.context.js +12 -0
  26. package/lib/components/split-button/split-button.component.js +19 -60
  27. package/lib/hooks/__internal__/useChildButtons/index.d.ts +1 -0
  28. package/lib/hooks/__internal__/useChildButtons/index.js +13 -0
  29. package/lib/hooks/__internal__/useChildButtons/package.json +6 -0
  30. package/lib/hooks/__internal__/useChildButtons/useChildButtons.d.ts +21 -0
  31. package/lib/hooks/__internal__/useChildButtons/useChildButtons.js +79 -0
  32. package/lib/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.d.ts +1 -1
  33. package/lib/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.js +7 -6
  34. package/package.json +1 -1
@@ -8,6 +8,7 @@ import tagComponent from "../../__internal__/utils/helpers/tags/tags";
8
8
  import { TooltipProvider } from "../../__internal__/tooltip-provider";
9
9
  import Logger from "../../__internal__/utils/logger";
10
10
  import { ButtonBarContext } from "../button-bar/button-bar.component";
11
+ import SplitButtonContext from "../split-button/__internal__/split-button.context";
11
12
  function renderChildren(_ref) {
12
13
  let {
13
14
  /* eslint-disable react/prop-types */
@@ -79,6 +80,7 @@ const Button = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
79
80
  iconTooltipMessage,
80
81
  iconTooltipPosition,
81
82
  fullWidth: fullWidthProp = false,
83
+ onClick,
82
84
  ...rest
83
85
  } = _ref2;
84
86
  const {
@@ -104,6 +106,10 @@ const Button = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
104
106
  Logger.deprecate("The `dashed` variant of the `buttonType` prop for `Button` component is deprecated and will soon be removed.");
105
107
  }
106
108
  const [internalRef, setInternalRef] = useState();
109
+ const {
110
+ inSplitButton,
111
+ onChildButtonClick
112
+ } = useContext(SplitButtonContext);
107
113
  let paddingX;
108
114
  const handleLinkKeyDown = event => {
109
115
  // If space key click link
@@ -134,11 +140,12 @@ const Button = /*#__PURE__*/React.forwardRef((_ref2, ref) => {
134
140
  "aria-label": !isValidChildren && iconType ? ariaLabel || iconType : ariaLabel,
135
141
  as: !disabled && href ? "a" : "button",
136
142
  onKeyDown: href ? handleLinkKeyDown : undefined,
143
+ onClick: inSplitButton ? onChildButtonClick?.(onClick) : onClick,
137
144
  draggable: false,
138
145
  buttonType: buttonType,
139
146
  disabled: disabled,
140
147
  destructive: destructive,
141
- role: "button",
148
+ role: inSplitButton ? "menu-item" : "button",
142
149
  type: href ? undefined : "button",
143
150
  iconType: iconType,
144
151
  size: size,
@@ -4,6 +4,8 @@ import { ValidationProps } from "../../__internal__/validations";
4
4
  export interface CheckboxGroupProps extends ValidationProps, MarginProps {
5
5
  /** The content for the CheckboxGroup Legend */
6
6
  legend?: string;
7
+ /** The content for the CheckboxGroup Legend Help text */
8
+ legendHelp?: string;
7
9
  /** When true, legend is placed inline with the checkboxes */
8
10
  legendInline?: boolean;
9
11
  /** Percentage width of legend (only when legend is inline) */
@@ -20,6 +22,8 @@ export interface CheckboxGroupProps extends ValidationProps, MarginProps {
20
22
  required?: boolean;
21
23
  /** Overrides the default tooltip */
22
24
  tooltipPosition?: "top" | "bottom" | "left" | "right";
25
+ /** When true, Checkboxes are in line */
26
+ inline?: boolean;
23
27
  }
24
28
  export declare const CheckboxGroupContext: React.Context<ValidationProps>;
25
29
  export declare const CheckboxGroup: {
@@ -1,14 +1,21 @@
1
1
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
- import React from "react";
2
+ import React, { useContext } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import tagComponent from "../../__internal__/utils/helpers/tags/tags";
5
- import StyledCheckboxGroup from "./checkbox-group.style";
5
+ import StyledCheckboxGroup, { StyledHintText } from "./checkbox-group.style";
6
6
  import Fieldset from "../../__internal__/fieldset";
7
7
  import { filterStyledSystemMarginProps } from "../../style/utils";
8
8
  import { TooltipProvider } from "../../__internal__/tooltip-provider";
9
9
  import FormSpacingProvider from "../../__internal__/form-spacing-provider";
10
+ import { NewValidationContext } from "../carbon-provider/carbon-provider.component";
11
+ import ValidationMessage from "../../__internal__/validation-message/validation-message.component";
12
+ import Box from "../../components/box";
13
+ import { ErrorBorder } from "../../components/textbox/textbox.style";
10
14
  export const CheckboxGroupContext = /*#__PURE__*/React.createContext({});
11
15
  export const CheckboxGroup = props => {
16
+ const {
17
+ validationRedesignOptIn
18
+ } = useContext(NewValidationContext);
12
19
  const {
13
20
  children,
14
21
  legend,
@@ -20,9 +27,42 @@ export const CheckboxGroup = props => {
20
27
  legendWidth,
21
28
  legendAlign,
22
29
  legendSpacing,
23
- tooltipPosition
30
+ legendHelp,
31
+ tooltipPosition,
32
+ inline
24
33
  } = props;
25
- return /*#__PURE__*/React.createElement(TooltipProvider, {
34
+ return /*#__PURE__*/React.createElement(React.Fragment, null, validationRedesignOptIn ? /*#__PURE__*/React.createElement(Fieldset, _extends({
35
+ legend: legend,
36
+ inline: legendInline,
37
+ legendWidth: legendWidth,
38
+ legendAlign: legendAlign,
39
+ legendSpacing: legendSpacing,
40
+ error: error,
41
+ warning: warning,
42
+ info: info,
43
+ isRequired: required
44
+ }, tagComponent("checkboxgroup", props), {
45
+ blockGroupBehaviour: !(error || warning)
46
+ }, filterStyledSystemMarginProps(props)), legendHelp && /*#__PURE__*/React.createElement(StyledHintText, null, legendHelp), /*#__PURE__*/React.createElement(Box, {
47
+ position: "relative"
48
+ }, /*#__PURE__*/React.createElement(ValidationMessage, {
49
+ error: error,
50
+ warning: warning
51
+ }), (error || warning) && /*#__PURE__*/React.createElement(ErrorBorder, {
52
+ warning: !!(!error && warning),
53
+ inline: inline
54
+ }), /*#__PURE__*/React.createElement(StyledCheckboxGroup, {
55
+ "data-component": "checkbox-group",
56
+ legendInline: legendInline,
57
+ inline: inline
58
+ }, /*#__PURE__*/React.createElement(CheckboxGroupContext.Provider, {
59
+ value: {
60
+ error: !!error,
61
+ warning: !!warning
62
+ }
63
+ }, /*#__PURE__*/React.createElement(FormSpacingProvider, {
64
+ marginBottom: undefined
65
+ }, children))))) : /*#__PURE__*/React.createElement(TooltipProvider, {
26
66
  tooltipPosition: tooltipPosition
27
67
  }, /*#__PURE__*/React.createElement(Fieldset, _extends({
28
68
  legend: legend,
@@ -47,7 +87,7 @@ export const CheckboxGroup = props => {
47
87
  }
48
88
  }, /*#__PURE__*/React.createElement(FormSpacingProvider, {
49
89
  marginBottom: undefined
50
- }, children)))));
90
+ }, children))))));
51
91
  };
52
92
  CheckboxGroup.displayName = "CheckboxGroup";
53
93
  export default CheckboxGroup;
@@ -1,4 +1,6 @@
1
+ export declare const StyledHintText: import("styled-components").StyledComponent<"div", any, {}, never>;
1
2
  declare const StyledCheckboxGroup: import("styled-components").StyledComponent<"div", any, {
2
3
  legendInline?: boolean | undefined;
4
+ inline?: boolean | undefined;
3
5
  }, never>;
4
6
  export default StyledCheckboxGroup;
@@ -4,10 +4,15 @@ import StyledIcon from "../icon/icon.style";
4
4
  import CheckboxStyle from "./checkbox.style";
5
5
  import { StyledLabelContainer } from "../../__internal__/label/label.style";
6
6
  import StyledValidationIcon from "../../__internal__/validations/validation-icon.style";
7
+ export const StyledHintText = styled.div`
8
+ margin-top: -4px;
9
+ margin-bottom: 8px;
10
+ color: var(--colorsUtilityYin055);
11
+ font-size: 14px;
12
+ `;
7
13
  const StyledCheckboxGroup = styled.div`
8
14
  display: flex;
9
15
  flex-direction: column;
10
-
11
16
  ${StyledIcon}::before {
12
17
  font-size: 16px;
13
18
  }
@@ -45,5 +50,17 @@ const StyledCheckboxGroup = styled.div`
45
50
  }
46
51
  `;
47
52
  }}
53
+
54
+ ${_ref2 => {
55
+ let {
56
+ inline
57
+ } = _ref2;
58
+ return inline && css`
59
+ flex-direction: row;
60
+ ${CheckboxStyle}:not(:first-of-type) {
61
+ margin-left: 32px;
62
+ }
63
+ `;
64
+ }}
48
65
  `;
49
66
  export default StyledCheckboxGroup;
@@ -85,38 +85,42 @@ const Checkbox = /*#__PURE__*/React.forwardRef((_ref, ref) => {
85
85
  labelHelp,
86
86
  labelSpacing,
87
87
  required,
88
- error: contextError || error,
89
- warning: contextWarning || warning,
90
- info: contextInfo || info,
91
88
  fieldHelpInline,
92
89
  checked,
93
90
  disabled,
94
91
  inputWidth,
95
92
  labelWidth,
96
- tooltipPosition,
97
93
  ref: ref || inputRef,
98
94
  ...rest
99
95
  };
96
+ const validationProps = {
97
+ error: contextError || error,
98
+ warning: contextWarning || warning,
99
+ ...(validationRedesignOptIn ? {
100
+ validationOnLabel: false
101
+ } : {
102
+ info: contextInfo || info
103
+ })
104
+ };
100
105
  const marginProps = useFormSpacing(rest);
101
- return /*#__PURE__*/React.createElement(TooltipProvider, {
102
- helpAriaLabel: helpAriaLabel,
103
- tooltipPosition: tooltipPosition
104
- }, /*#__PURE__*/React.createElement(CheckboxStyle, _extends({
106
+ const componentToRender = /*#__PURE__*/React.createElement(CheckboxStyle, _extends({
105
107
  "data-component": dataComponent,
106
108
  "data-role": dataRole,
107
109
  "data-element": dataElement,
108
110
  disabled: disabled,
109
111
  labelSpacing: labelSpacing,
110
112
  inputWidth: inputWidth,
111
- adaptiveSpacingSmallScreen: adaptiveSpacingSmallScreen,
112
- error: contextError || error,
113
- warning: contextWarning || warning,
114
- info: contextInfo || info,
113
+ adaptiveSpacingSmallScreen: adaptiveSpacingSmallScreen
114
+ }, validationProps, {
115
115
  fieldHelpInline: fieldHelpInline,
116
116
  reverse: reverse,
117
117
  size: size,
118
118
  applyNewValidation: validationRedesignOptIn
119
- }, marginProps), /*#__PURE__*/React.createElement(CheckableInput, inputProps, /*#__PURE__*/React.createElement(CheckboxSvg, null))));
119
+ }, marginProps), /*#__PURE__*/React.createElement(CheckableInput, _extends({}, inputProps, validationProps), /*#__PURE__*/React.createElement(CheckboxSvg, null)));
120
+ return /*#__PURE__*/React.createElement(React.Fragment, null, validationRedesignOptIn ? componentToRender : /*#__PURE__*/React.createElement(TooltipProvider, {
121
+ helpAriaLabel: helpAriaLabel,
122
+ tooltipPosition: tooltipPosition
123
+ }, componentToRender));
120
124
  });
121
125
  Checkbox.propTypes = {
122
126
  "about": PropTypes.string,
@@ -1,13 +1,13 @@
1
1
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
- import React, { useCallback, useState, useRef, useMemo } from "react";
2
+ import React, { useRef } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import useClickAwayListener from "../../hooks/__internal__/useClickAwayListener";
5
+ import SplitButtonContext from "../split-button/__internal__/split-button.context";
5
6
  import { StyledMultiActionButton, StyledButtonChildrenContainer } from "./multi-action-button.style";
6
7
  import Button from "../button";
7
- import Events from "../../__internal__/utils/helpers/events";
8
8
  import Popover from "../../__internal__/popover";
9
9
  import { filterStyledSystemMarginProps, filterOutStyledSystemSpacingProps } from "../../style/utils";
10
- import useMenuKeyboardNavigation from "../../hooks/__internal__/useMenuKeyboardNavigation";
10
+ import useChildButtons from "../../hooks/__internal__/useChildButtons";
11
11
  export const MultiActionButton = _ref => {
12
12
  let {
13
13
  align = "left",
@@ -23,57 +23,17 @@ export const MultiActionButton = _ref => {
23
23
  "data-role": dataRole,
24
24
  ...rest
25
25
  } = _ref;
26
- const ref = useRef(null);
27
26
  const buttonRef = useRef(null);
28
- const buttonContainer = useRef(null);
29
- const buttonChildren = useMemo(() => React.Children.toArray(children), [children]);
30
- const buttonChildrenRefs = useMemo(() => buttonChildren.map(() => /*#__PURE__*/React.createRef()), [buttonChildren]);
31
- const [showAdditionalButtons, setShowAdditionalButtons] = useState(false);
32
- const [minWidth, setMinWidth] = useState(0);
33
- const hideButtons = useCallback(() => {
34
- setShowAdditionalButtons(false);
35
- }, []);
36
- const showButtons = () => {
37
- setShowAdditionalButtons(true);
38
-
39
- /* istanbul ignore else */
40
- if (ref.current) {
41
- setMinWidth(ref.current.getBoundingClientRect().width);
42
- }
43
- };
44
- const childrenWithProps = () => {
45
- return buttonChildren.map((child, index) => {
46
- if (! /*#__PURE__*/React.isValidElement(child)) {
47
- return child;
48
- }
49
- const props = {
50
- key: index.toString(),
51
- role: "menuitem",
52
- ref: buttonChildrenRefs[index],
53
- tabIndex: -1,
54
- onClick: ev => {
55
- if (child.props.onClick) child.props.onClick(ev);
56
- hideButtons();
57
- buttonRef.current?.focus();
58
- }
59
- };
60
- return /*#__PURE__*/React.cloneElement(child, props);
61
- });
62
- };
63
- const handleKeyDown = useMenuKeyboardNavigation(buttonRef, buttonChildrenRefs, hideButtons, showAdditionalButtons);
64
- const handleMainButtonKeyDown = ev => {
65
- if (Events.isEnterKey(ev) || Events.isSpaceKey(ev) || Events.isDownKey(ev) || Events.isUpKey(ev)) {
66
- ev.preventDefault();
67
- if (!showAdditionalButtons) {
68
- showButtons();
69
- }
70
-
71
- // see if setTimeout could be removed after we update react to v18 thanks to the concurrent mode
72
- setTimeout(() => {
73
- buttonChildrenRefs[0]?.current?.focus();
74
- }, 0);
75
- }
76
- };
27
+ const {
28
+ showAdditionalButtons,
29
+ showButtons,
30
+ hideButtons,
31
+ buttonNode,
32
+ hideButtonsIfTriggerNotFocused,
33
+ handleToggleButtonKeyDown,
34
+ wrapperProps,
35
+ contextValue
36
+ } = useChildButtons(buttonRef);
77
37
  const handleInsideClick = useClickAwayListener(hideButtons);
78
38
  const handleClick = ev => {
79
39
  showButtons();
@@ -86,7 +46,7 @@ export const MultiActionButton = _ref => {
86
46
  disabled,
87
47
  displayed: showAdditionalButtons,
88
48
  onTouchStart: showButtons,
89
- onKeyDown: handleMainButtonKeyDown,
49
+ onKeyDown: handleToggleButtonKeyDown,
90
50
  onClick: handleClick,
91
51
  buttonType,
92
52
  size,
@@ -98,24 +58,17 @@ export const MultiActionButton = _ref => {
98
58
  };
99
59
  const renderAdditionalButtons = () => /*#__PURE__*/React.createElement(Popover, {
100
60
  placement: "bottom-end",
101
- reference: ref
102
- }, /*#__PURE__*/React.createElement(StyledButtonChildrenContainer, {
103
- role: "menu",
61
+ reference: buttonNode
62
+ }, /*#__PURE__*/React.createElement(StyledButtonChildrenContainer, _extends({}, wrapperProps, {
104
63
  "aria-label": text,
105
- "data-element": "additional-buttons",
106
- align: align,
107
- minWidth: minWidth,
108
- ref: buttonContainer,
109
- onKeyDown: handleKeyDown
110
- }, childrenWithProps()));
111
- const hideButtonsIfTriggerNotFocused = useCallback(() => {
112
- if (buttonRef.current === document.activeElement) return;
113
- setShowAdditionalButtons(false);
114
- }, []);
64
+ align: align
65
+ }), /*#__PURE__*/React.createElement(SplitButtonContext.Provider, {
66
+ value: contextValue
67
+ }, children)));
115
68
  const marginProps = filterStyledSystemMarginProps(rest);
116
69
  return /*#__PURE__*/React.createElement(StyledMultiActionButton, _extends({
117
70
  onMouseLeave: hideButtonsIfTriggerNotFocused,
118
- ref: ref,
71
+ ref: buttonNode,
119
72
  "data-component": "multi-action-button",
120
73
  "data-element": dataElement,
121
74
  "data-role": dataRole,
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ export interface SplitButtonContextProps {
3
+ inSplitButton: boolean;
4
+ onChildButtonClick?: (childOnClick?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>) => React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> | undefined;
5
+ }
6
+ declare const _default: React.Context<SplitButtonContextProps>;
7
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ export default /*#__PURE__*/React.createContext({
3
+ inSplitButton: false
4
+ });
@@ -1,5 +1,5 @@
1
1
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
- import React, { useContext, useCallback, useMemo, useRef, useState } from "react";
2
+ import React, { useContext, useRef } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { ThemeContext } from "styled-components";
5
5
  import useClickAwayListener from "../../hooks/__internal__/useClickAwayListener";
@@ -8,12 +8,12 @@ import Button from "../button";
8
8
  import StyledSplitButton from "./split-button.style";
9
9
  import StyledSplitButtonToggle from "./split-button-toggle.style";
10
10
  import StyledSplitButtonChildrenContainer from "./split-button-children.style";
11
- import Events from "../../__internal__/utils/helpers/events";
12
11
  import guid from "../../__internal__/utils/helpers/guid";
13
12
  import Popover from "../../__internal__/popover";
14
13
  import { filterStyledSystemMarginProps, filterOutStyledSystemSpacingProps } from "../../style/utils";
15
14
  import { baseTheme } from "../../style/themes";
16
- import useMenuKeyboardNavigation from "../../hooks/__internal__/useMenuKeyboardNavigation";
15
+ import useChildButtons from "../../hooks/__internal__/useChildButtons";
16
+ import SplitButtonContext from "./__internal__/split-button.context";
17
17
  const CONTENT_WIDTH_RATIO = 0.75;
18
18
  export const SplitButton = _ref => {
19
19
  let {
@@ -33,39 +33,17 @@ export const SplitButton = _ref => {
33
33
  } = _ref;
34
34
  const theme = useContext(ThemeContext) || baseTheme;
35
35
  const buttonLabelId = useRef(guid());
36
- const buttonChildren = useMemo(() => React.Children.toArray(children), [children]);
37
- const buttonChildrenRefs = useMemo(() => buttonChildren.map(() => /*#__PURE__*/React.createRef()), [buttonChildren]);
38
- const splitButtonNode = useRef(null);
39
36
  const toggleButton = useRef(null);
40
- const [showAdditionalButtons, setShowAdditionalButtons] = useState(false);
41
- const [minWidth, setMinWidth] = useState(0);
42
- const hideButtons = useCallback(() => {
43
- setShowAdditionalButtons(false);
44
- }, []);
45
- const handleKeyDown = useMenuKeyboardNavigation(toggleButton, buttonChildrenRefs, hideButtons, showAdditionalButtons);
46
- function showButtons() {
47
- setShowAdditionalButtons(true);
48
-
49
- /* istanbul ignore else */
50
- if (splitButtonNode.current) {
51
- setMinWidth(CONTENT_WIDTH_RATIO * splitButtonNode.current.getBoundingClientRect().width);
52
- }
53
- }
54
- function handleToggleButtonKeyDown(ev) {
55
- if (Events.isEnterKey(ev) || Events.isSpaceKey(ev) || Events.isDownKey(ev) || Events.isUpKey(ev)) {
56
- ev.preventDefault();
57
- if (!showAdditionalButtons) {
58
- showButtons();
59
- }
60
- setTimeout(() => {
61
- buttonChildrenRefs[0]?.current?.focus();
62
- }, 0);
63
- }
64
- }
65
- const hideButtonsIfTriggerNotFocused = useCallback(() => {
66
- if (toggleButton.current === document.activeElement) return;
67
- setShowAdditionalButtons(false);
68
- }, []);
37
+ const {
38
+ showAdditionalButtons,
39
+ showButtons,
40
+ hideButtons,
41
+ buttonNode,
42
+ hideButtonsIfTriggerNotFocused,
43
+ handleToggleButtonKeyDown,
44
+ wrapperProps,
45
+ contextValue
46
+ } = useChildButtons(toggleButton, CONTENT_WIDTH_RATIO);
69
47
  const mainButtonProps = {
70
48
  onMouseEnter: hideButtonsIfTriggerNotFocused,
71
49
  onFocus: hideButtonsIfTriggerNotFocused,
@@ -126,43 +104,24 @@ export const SplitButton = _ref => {
126
104
  disabled: disabled
127
105
  }))];
128
106
  }
129
- function childrenWithProps() {
130
- const childArray = Array.isArray(children) ? children : [children];
131
- return childArray.filter(Boolean).map((child, index) => {
132
- const childProps = {
133
- key: index.toString(),
134
- role: "menuitem",
135
- ref: buttonChildrenRefs[index],
136
- tabIndex: -1,
137
- onClick: ev => {
138
- if (child.props.onClick) child.props.onClick(ev);
139
- hideButtons();
140
- toggleButton.current?.focus();
141
- }
142
- };
143
- return /*#__PURE__*/React.cloneElement(child, childProps);
144
- });
145
- }
146
107
  function renderAdditionalButtons() {
147
108
  if (!showAdditionalButtons) return null;
148
109
  return /*#__PURE__*/React.createElement(Popover, {
149
110
  placement: "bottom-end",
150
- reference: splitButtonNode
151
- }, /*#__PURE__*/React.createElement(StyledSplitButtonChildrenContainer, {
152
- role: "menu",
111
+ reference: buttonNode
112
+ }, /*#__PURE__*/React.createElement(StyledSplitButtonChildrenContainer, _extends({}, wrapperProps, {
153
113
  "aria-label": text,
154
- "data-element": "additional-buttons",
155
- align: align,
156
- minWidth: minWidth,
157
- onKeyDown: handleKeyDown
158
- }, childrenWithProps()));
114
+ align: align
115
+ }), /*#__PURE__*/React.createElement(SplitButtonContext.Provider, {
116
+ value: contextValue
117
+ }, children)));
159
118
  }
160
119
  const handleClick = useClickAwayListener(hideButtons);
161
120
  const marginProps = filterStyledSystemMarginProps(rest);
162
121
  return /*#__PURE__*/React.createElement(StyledSplitButton, _extends({
163
122
  onMouseLeave: hideButtonsIfTriggerNotFocused,
164
123
  onClick: handleClick,
165
- ref: splitButtonNode
124
+ ref: buttonNode
166
125
  }, componentTags(), marginProps), renderMainButton(), renderAdditionalButtons());
167
126
  };
168
127
  export default SplitButton;
@@ -0,0 +1 @@
1
+ export { default } from "./useChildButtons";
@@ -0,0 +1 @@
1
+ export { default } from "./useChildButtons";
@@ -0,0 +1,21 @@
1
+ /// <reference types="react" />
2
+ declare const useChildButtons: (toggleButtonRef: React.RefObject<HTMLButtonElement>, widthRatio?: number) => {
3
+ showAdditionalButtons: boolean;
4
+ showButtons: () => void;
5
+ hideButtons: () => void;
6
+ buttonNode: import("react").RefObject<HTMLDivElement>;
7
+ hideButtonsIfTriggerNotFocused: () => void;
8
+ handleToggleButtonKeyDown: (ev: React.KeyboardEvent<HTMLButtonElement>) => void;
9
+ wrapperProps: {
10
+ role: string;
11
+ "data-element": string;
12
+ onKeyDown: (ev: any) => void;
13
+ minWidth: number;
14
+ ref: import("react").RefObject<HTMLDivElement>;
15
+ };
16
+ contextValue: {
17
+ inSplitButton: boolean;
18
+ onChildButtonClick: (childOnClick?: React.MouseEventHandler<HTMLButtonElement>) => (ev: React.MouseEvent<HTMLButtonElement>) => void;
19
+ };
20
+ };
21
+ export default useChildButtons;
@@ -0,0 +1,71 @@
1
+ import { useState, useRef, useEffect, useCallback } from "react";
2
+ import Events from "../../../__internal__/utils/helpers/events";
3
+ import useMenuKeyboardNavigation from "../useMenuKeyboardNavigation";
4
+ const useChildButtons = function (toggleButtonRef) {
5
+ let widthRatio = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
6
+ const [showAdditionalButtons, setShowAdditionalButtons] = useState(false);
7
+ const [minWidth, setMinWidth] = useState(0);
8
+ const buttonNode = useRef(null);
9
+ const childrenContainer = useRef(null);
10
+ const focusFirstChildButtonOnOpen = useRef(false);
11
+ const hideButtons = useCallback(() => {
12
+ setShowAdditionalButtons(false);
13
+ }, []);
14
+ function showButtons() {
15
+ setShowAdditionalButtons(true);
16
+
17
+ /* istanbul ignore else */
18
+ if (buttonNode.current) {
19
+ setMinWidth(widthRatio * buttonNode.current.getBoundingClientRect().width);
20
+ }
21
+ }
22
+ const getButtonChildren = useCallback(() => childrenContainer.current?.querySelectorAll('[data-component="button"]'), []);
23
+ useEffect(() => {
24
+ const firstChild = getButtonChildren()?.[0];
25
+ if (focusFirstChildButtonOnOpen.current && showAdditionalButtons && firstChild) {
26
+ focusFirstChildButtonOnOpen.current = false;
27
+ firstChild.focus();
28
+ }
29
+ }, [showAdditionalButtons, getButtonChildren]);
30
+ const handleToggleButtonKeyDown = ev => {
31
+ if (Events.isEnterKey(ev) || Events.isSpaceKey(ev) || Events.isDownKey(ev) || Events.isUpKey(ev)) {
32
+ ev.preventDefault();
33
+ if (!showAdditionalButtons) {
34
+ showButtons();
35
+ }
36
+ focusFirstChildButtonOnOpen.current = true;
37
+ }
38
+ };
39
+ const handleKeyDown = useMenuKeyboardNavigation(toggleButtonRef, getButtonChildren, hideButtons, showAdditionalButtons);
40
+ const onChildButtonClick = childOnClick => ev => {
41
+ childOnClick?.(ev);
42
+ hideButtons();
43
+ toggleButtonRef.current?.focus();
44
+ };
45
+ const hideButtonsIfTriggerNotFocused = useCallback(() => {
46
+ if (toggleButtonRef.current === document.activeElement) return;
47
+ setShowAdditionalButtons(false);
48
+ }, [toggleButtonRef]);
49
+ const wrapperProps = {
50
+ role: "menu",
51
+ "data-element": "additional-buttons",
52
+ onKeyDown: handleKeyDown,
53
+ minWidth,
54
+ ref: childrenContainer
55
+ };
56
+ const contextValue = {
57
+ inSplitButton: true,
58
+ onChildButtonClick
59
+ };
60
+ return {
61
+ showAdditionalButtons,
62
+ showButtons,
63
+ hideButtons,
64
+ buttonNode,
65
+ hideButtonsIfTriggerNotFocused,
66
+ handleToggleButtonKeyDown,
67
+ wrapperProps,
68
+ contextValue
69
+ };
70
+ };
71
+ export default useChildButtons;
@@ -1,2 +1,2 @@
1
- declare const _default: (mainControlRef: React.RefObject<HTMLButtonElement>, childrenRefs: React.RefObject<HTMLButtonElement>[], hide: () => void, isOpen: boolean) => (ev: any) => void;
1
+ declare const _default: (mainControlRef: React.RefObject<HTMLButtonElement>, getButtonChildren: () => NodeListOf<HTMLButtonElement>, hide: () => void, isOpen: boolean) => (ev: any) => void;
2
2
  export default _default;
@@ -1,9 +1,8 @@
1
- import { useCallback, useMemo } from "react";
1
+ import { useCallback } from "react";
2
2
  import Events from "../../../__internal__/utils/helpers/events";
3
3
  import { defaultFocusableSelectors } from "../../../__internal__/focus-trap/focus-trap-utils";
4
4
  import useModalManager from "../useModalManager";
5
- export default ((mainControlRef, childrenRefs, hide, isOpen) => {
6
- const childrenLength = useMemo(() => childrenRefs.length, [childrenRefs]);
5
+ export default ((mainControlRef, getButtonChildren, hide, isOpen) => {
7
6
  const refocusMainControl = useCallback(() => {
8
7
  hide();
9
8
  mainControlRef.current?.focus();
@@ -26,8 +25,10 @@ export default ((mainControlRef, childrenRefs, hide, isOpen) => {
26
25
  if (!(Events.isEnterKey(ev) || Events.isSpaceKey(ev))) {
27
26
  ev.preventDefault();
28
27
  }
29
- const currentIndex = childrenRefs?.findIndex(node => node.current === document.activeElement);
28
+ const buttonChildren = getButtonChildren();
29
+ const childrenLength = buttonChildren?.length;
30
30
  let nextIndex = -1;
31
+ const currentIndex = Array.from(buttonChildren).indexOf(document.activeElement);
31
32
  const arrowModifierPressed = ev.ctrlKey || ev.metaKey;
32
33
  if (Events.isEndKey(ev) || arrowModifierPressed && Events.isDownKey(ev)) {
33
34
  nextIndex = childrenLength - 1;
@@ -54,15 +55,15 @@ export default ((mainControlRef, childrenRefs, hide, isOpen) => {
54
55
  const elements = Array.from(document.querySelectorAll(defaultFocusableSelectors)).filter(el => Number(el.tabIndex) !== -1);
55
56
  const indexOf = elements.indexOf(mainControlRef.current);
56
57
  elements[indexOf + 1]?.focus();
57
- // // timeout enforces that the "hide" method will be run after browser focuses on the next element
58
+ // timeout enforces that the "hide" method will be run after browser focuses on the next element
58
59
  setTimeout(hide, 0);
59
60
  } else {
60
61
  nextIndex = currentIndex + 1;
61
62
  }
62
63
  }
63
64
  if (nextIndex > -1) {
64
- childrenRefs[nextIndex].current?.focus();
65
+ buttonChildren?.[nextIndex]?.focus();
65
66
  }
66
- }, [childrenLength, hide, refocusMainControl, childrenRefs, mainControlRef]);
67
+ }, [hide, refocusMainControl, mainControlRef, getButtonChildren]);
67
68
  return handleKeyDown;
68
69
  });