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.
- package/esm/components/button/button.component.js +8 -1
- package/esm/components/checkbox/checkbox-group.component.d.ts +4 -0
- package/esm/components/checkbox/checkbox-group.component.js +45 -5
- package/esm/components/checkbox/checkbox-group.style.d.ts +2 -0
- package/esm/components/checkbox/checkbox-group.style.js +18 -1
- package/esm/components/checkbox/checkbox.component.js +17 -13
- package/esm/components/multi-action-button/multi-action-button.component.js +21 -68
- package/esm/components/split-button/__internal__/split-button.context.d.ts +7 -0
- package/esm/components/split-button/__internal__/split-button.context.js +4 -0
- package/esm/components/split-button/split-button.component.js +20 -61
- package/esm/hooks/__internal__/useChildButtons/index.d.ts +1 -0
- package/esm/hooks/__internal__/useChildButtons/index.js +1 -0
- package/esm/hooks/__internal__/useChildButtons/useChildButtons.d.ts +21 -0
- package/esm/hooks/__internal__/useChildButtons/useChildButtons.js +71 -0
- package/esm/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.d.ts +1 -1
- package/esm/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.js +8 -7
- package/lib/components/button/button.component.js +8 -1
- package/lib/components/checkbox/checkbox-group.component.d.ts +4 -0
- package/lib/components/checkbox/checkbox-group.component.js +47 -5
- package/lib/components/checkbox/checkbox-group.style.d.ts +2 -0
- package/lib/components/checkbox/checkbox-group.style.js +20 -2
- package/lib/components/checkbox/checkbox.component.js +17 -13
- package/lib/components/multi-action-button/multi-action-button.component.js +20 -67
- package/lib/components/split-button/__internal__/split-button.context.d.ts +7 -0
- package/lib/components/split-button/__internal__/split-button.context.js +12 -0
- package/lib/components/split-button/split-button.component.js +19 -60
- package/lib/hooks/__internal__/useChildButtons/index.d.ts +1 -0
- package/lib/hooks/__internal__/useChildButtons/index.js +13 -0
- package/lib/hooks/__internal__/useChildButtons/package.json +6 -0
- package/lib/hooks/__internal__/useChildButtons/useChildButtons.d.ts +21 -0
- package/lib/hooks/__internal__/useChildButtons/useChildButtons.js +79 -0
- package/lib/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.d.ts +1 -1
- package/lib/hooks/__internal__/useMenuKeyboardNavigation/useMenuKeyboardNavigation.js +7 -6
- 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
|
-
|
|
30
|
+
legendHelp,
|
|
31
|
+
tooltipPosition,
|
|
32
|
+
inline
|
|
24
33
|
} = props;
|
|
25
|
-
return /*#__PURE__*/React.createElement(
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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:
|
|
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:
|
|
102
|
-
}, /*#__PURE__*/React.createElement(StyledButtonChildrenContainer, {
|
|
103
|
-
role: "menu",
|
|
61
|
+
reference: buttonNode
|
|
62
|
+
}, /*#__PURE__*/React.createElement(StyledButtonChildrenContainer, _extends({}, wrapperProps, {
|
|
104
63
|
"aria-label": text,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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:
|
|
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;
|
|
@@ -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,
|
|
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
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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:
|
|
151
|
-
}, /*#__PURE__*/React.createElement(StyledSplitButtonChildrenContainer, {
|
|
152
|
-
role: "menu",
|
|
111
|
+
reference: buttonNode
|
|
112
|
+
}, /*#__PURE__*/React.createElement(StyledSplitButtonChildrenContainer, _extends({}, wrapperProps, {
|
|
153
113
|
"aria-label": text,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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:
|
|
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>,
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
65
|
+
buttonChildren?.[nextIndex]?.focus();
|
|
65
66
|
}
|
|
66
|
-
}, [
|
|
67
|
+
}, [hide, refocusMainControl, mainControlRef, getButtonChildren]);
|
|
67
68
|
return handleKeyDown;
|
|
68
69
|
});
|