@codecademy/gamut 67.6.4 → 67.6.5-alpha.9b8a7f.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/dist/ConnectedForm/ConnectedFormGroup.d.ts +8 -2
- package/dist/ConnectedForm/ConnectedFormGroup.js +1 -1
- package/dist/ConnectedForm/ConnectedInputs/ConnectedCheckbox.js +2 -0
- package/dist/Form/__tests__/testUtils.d.ts +22 -0
- package/dist/Form/elements/FormGroupLabel.d.ts +2 -2
- package/dist/Form/elements/FormGroupLabel.js +10 -3
- package/dist/Form/inputs/Checkbox.d.ts +7 -0
- package/dist/Form/inputs/Checkbox.js +27 -11
- package/dist/Form/inputs/Radio.d.ts +9 -5
- package/dist/Form/inputs/Radio.js +13 -10
- package/dist/Form/styles/Radio-styles.d.ts +0 -3
- package/dist/Form/styles/Radio-styles.js +0 -6
- package/dist/Form/styles/shared-system-props.d.ts +7 -0
- package/dist/Form/styles/shared-system-props.js +11 -0
- package/dist/GridForm/GridFormInputGroup/GridFormRadioGroupInput/index.js +2 -0
- package/dist/GridForm/GridFormInputGroup/__fixtures__/renderers.d.ts +4 -0
- package/dist/GridForm/types.d.ts +7 -2
- package/dist/Tip/InfoTip/InfoTipButton.js +5 -2
- package/dist/Tip/InfoTip/index.d.ts +27 -2
- package/dist/Tip/InfoTip/index.js +50 -67
- package/dist/Tip/InfoTip/type-utils.d.ts +35 -0
- package/dist/Tip/InfoTip/type-utils.js +72 -0
- package/dist/Tip/__tests__/helpers.d.ts +5 -26
- package/dist/Tip/shared/FloatingTip.js +3 -3
- package/dist/Tip/shared/InlineTip.js +4 -1
- package/dist/Tip/shared/types.d.ts +1 -1
- package/dist/Tip/shared/utils.d.ts +19 -0
- package/dist/Tip/shared/utils.js +104 -0
- package/package.json +2 -2
- package/dist/Tip/InfoTip/elements.d.ts +0 -12
- package/dist/Tip/InfoTip/elements.js +0 -9
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { css, theme, timing, transitionConcat, variant } from '@codecademy/gamut-styles';
|
|
2
2
|
import { formBaseComponentStyles, formFieldBaseDisabledStyles, InputSelectors } from '.';
|
|
3
|
-
export const radioWrapper = css({
|
|
4
|
-
margin: '0.25rem 0',
|
|
5
|
-
width: '100%',
|
|
6
|
-
fontWeight: 'normal',
|
|
7
|
-
display: 'flex'
|
|
8
|
-
});
|
|
9
3
|
const consistentLabelStyles = {
|
|
10
4
|
content: '""',
|
|
11
5
|
display: 'block',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
1
2
|
export type conditionalStyleProps = {
|
|
2
3
|
error?: boolean;
|
|
3
4
|
activated?: boolean;
|
|
@@ -91,6 +92,12 @@ export declare const formBaseFieldStylesObject: {
|
|
|
91
92
|
export declare const formBaseFieldStyles: (props: {
|
|
92
93
|
theme?: import("@emotion/react").Theme | undefined;
|
|
93
94
|
}) => import("@codecademy/variance").CSSObject;
|
|
95
|
+
export declare const InputWrapper: import("@emotion/styled").StyledComponent<{
|
|
96
|
+
theme?: import("@emotion/react").Theme | undefined;
|
|
97
|
+
as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
|
|
98
|
+
} & {
|
|
99
|
+
theme?: import("@emotion/react").Theme | undefined;
|
|
100
|
+
}, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
|
|
94
101
|
export declare const formFieldStyles: (props: {
|
|
95
102
|
theme?: import("@emotion/react").Theme | undefined;
|
|
96
103
|
}) => import("@codecademy/variance").CSSObject;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _styled from "@emotion/styled/base";
|
|
1
2
|
import { css, theme, transitionConcat, variant } from '@codecademy/gamut-styles';
|
|
2
3
|
export let InputSelectors = /*#__PURE__*/function (InputSelectors) {
|
|
3
4
|
InputSelectors["HOVER"] = "&:hover";
|
|
@@ -66,6 +67,16 @@ export const formBaseFieldStylesObject = {
|
|
|
66
67
|
}
|
|
67
68
|
};
|
|
68
69
|
export const formBaseFieldStyles = css(formBaseFieldStylesObject);
|
|
70
|
+
const inputWrapper = css({
|
|
71
|
+
margin: '0.25rem 0',
|
|
72
|
+
width: '100%',
|
|
73
|
+
fontWeight: 'normal',
|
|
74
|
+
display: 'flex'
|
|
75
|
+
});
|
|
76
|
+
export const InputWrapper = /*#__PURE__*/_styled("div", {
|
|
77
|
+
target: "e1o3jdqp0",
|
|
78
|
+
label: "InputWrapper"
|
|
79
|
+
})(inputWrapper, process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9Gb3JtL3N0eWxlcy9zaGFyZWQtc3lzdGVtLXByb3BzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXNHNEIiLCJmaWxlIjoiLi4vLi4vLi4vc3JjL0Zvcm0vc3R5bGVzL3NoYXJlZC1zeXN0ZW0tcHJvcHMudHMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBjc3MsXG4gIHRoZW1lLFxuICB0cmFuc2l0aW9uQ29uY2F0LFxuICB2YXJpYW50LFxufSBmcm9tICdAY29kZWNhZGVteS9nYW11dC1zdHlsZXMnO1xuaW1wb3J0IHN0eWxlZCBmcm9tICdAZW1vdGlvbi9zdHlsZWQnO1xuXG5leHBvcnQgdHlwZSBjb25kaXRpb25hbFN0eWxlUHJvcHMgPSB7XG4gIGVycm9yPzogYm9vbGVhbjtcbiAgYWN0aXZhdGVkPzogYm9vbGVhbjtcbiAgaXNGb2N1c2VkPzogYm9vbGVhbiB8IG51bGw7XG4gIGlzRGlzYWJsZWQ/OiBib29sZWFuIHwgbnVsbDtcbn07XG5cbmV4cG9ydCBlbnVtIElucHV0U2VsZWN0b3JzIHtcbiAgSE9WRVIgPSAnJjpob3ZlcicsXG4gIEFDVElWRSA9ICcmOmFjdGl2ZScsXG4gIFBMQUNFSE9MREVSID0gJyY6cGxhY2Vob2xkZXInLFxuICBGT0NVUyA9ICcmOmZvY3VzJyxcbiAgRk9DVVNfTEFCRUxfRElWX0NISUxEID0gJyY6Zm9jdXMgKyBsYWJlbCA+IGRpdicsXG4gIERJU0FCTEVEID0gXCImOmRpc2FibGVkLCAmW2FyaWEtZGlzYWJsZWQ9J3RydWUnXVwiLFxuICBCRUZPUkUgPSAnJjo6YmVmb3JlJyxcbiAgQUZURVIgPSAnJjo6YWZ0ZXInLFxuICBCRUZPUkVfQU5EX0FGVEVSID0gJyY6OmJlZm9yZSwgJjo6YWZ0ZXInLFxuICBDSEVDS0VEX0JFRk9SRSA9ICcmOmNoZWNrZWQgKyBsYWJlbDo6YmVmb3JlJyxcbiAgQ0hFQ0tFRF9BRlRFUiA9ICcmOmNoZWNrZWQgKyBsYWJlbDo6YWZ0ZXInLFxuICBIT1ZFUl9GT0NVU19CRUZPUkUgPSAnJjpob3ZlciArIGxhYmVsOjpiZWZvcmUsICY6Zm9jdXMgKyBsYWJlbDo6YmVmb3JlJyxcbn1cblxuZXhwb3J0IGNvbnN0IGZvcm1CYXNlU3R5bGVzID0ge1xuICBmb250V2VpZ2h0OiAnYmFzZScsXG4gIGZvbnRTaXplOiAxNixcbiAgY29sb3I6ICd0ZXh0Jyxcbn0gYXMgY29uc3Q7XG5cbmV4cG9ydCBjb25zdCBmb3JtQmFzZUNvbXBvbmVudFN0eWxlcyA9IHtcbiAgd2lkdGg6IDEsXG4gIG91dGxpbmU6ICdub25lJyxcbiAgYmc6ICdiYWNrZ3JvdW5kJyxcbiAgbWluV2lkdGg6ICdhdXRvJyxcbiAgLi4uZm9ybUJhc2VTdHlsZXMsXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY29uc3QgZm9ybUZpZWxkRm9jdXNTdHlsZXMgPSB7XG4gIGJvcmRlckNvbG9yOiAncHJpbWFyeScsXG4gIGJveFNoYWRvdzogYGluc2V0IDAgMCAwIDFweCAke3RoZW1lLmNvbG9ycy5wcmltYXJ5fWAsXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY29uc3QgZm9ybUZpZWxkVGV4dERpc2FibGVkU3R5bGVzID0ge1xuICBjb2xvcjogJ3RleHQtZGlzYWJsZWQnLFxuICBjdXJzb3I6ICdub3QtYWxsb3dlZCcsXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY29uc3QgZm9ybUZpZWxkQmFzZURpc2FibGVkU3R5bGVzID0ge1xuICBib3JkZXJDb2xvcjogJ2N1cnJlbnRDb2xvcicsXG4gIG9wYWNpdHk6IDEsXG4gIC4uLmZvcm1GaWVsZFRleHREaXNhYmxlZFN0eWxlcyxcbn0gYXMgY29uc3Q7XG5cbmV4cG9ydCBjb25zdCBmb3JtRmllbGREaXNhYmxlZFN0eWxlcyA9IHtcbiAgLi4uZm9ybUZpZWxkQmFzZURpc2FibGVkU3R5bGVzLFxuICBiZzogJ2JhY2tncm91bmQtZGlzYWJsZWQnLFxuICBbSW5wdXRTZWxlY3RvcnMuSE9WRVJdOiB7XG4gICAgYm9yZGVyQ29sb3I6ICdjdXJyZW50Q29sb3InLFxuICB9LFxufSBhcyBjb25zdDtcblxuZXhwb3J0IGNvbnN0IGZvcm1GaWVsZFBhZGRpbmdTdHlsZXMgPSB7XG4gIHB5OiAxMixcbiAgcHg6IDgsXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY29uc3QgZm9ybUJhc2VGaWVsZFN0eWxlc09iamVjdCA9IHtcbiAgLi4uZm9ybUJhc2VDb21wb25lbnRTdHlsZXMsXG4gIHRyYW5zaXRpb246IHRyYW5zaXRpb25Db25jYXQoXG4gICAgWydiYWNrZ3JvdW5kLWNvbG9yJywgJ2JveC1zaGFkb3cnXSxcbiAgICAnc2xvdycsXG4gICAgJ2Vhc2UtaW4tb3V0J1xuICApLFxuICBib3JkZXI6IDEsXG4gIGJvcmRlclJhZGl1czogJ21kJyxcbiAgW0lucHV0U2VsZWN0b3JzLkhPVkVSXToge1xuICAgIGJvcmRlckNvbG9yOiAncHJpbWFyeScsXG4gIH0sXG4gIFtJbnB1dFNlbGVjdG9ycy5QTEFDRUhPTERFUl06IHtcbiAgICBmb250U3R5bGU6ICdpdGFsaWMnLFxuICB9LFxuICBbSW5wdXRTZWxlY3RvcnMuRElTQUJMRURdOiB7XG4gICAgLi4uZm9ybUZpZWxkRGlzYWJsZWRTdHlsZXMsXG4gIH0sXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY29uc3QgZm9ybUJhc2VGaWVsZFN0eWxlcyA9IGNzcyhmb3JtQmFzZUZpZWxkU3R5bGVzT2JqZWN0KTtcblxuY29uc3QgaW5wdXRXcmFwcGVyID0gY3NzKHtcbiAgbWFyZ2luOiAnMC4yNXJlbSAwJyxcbiAgd2lkdGg6ICcxMDAlJyxcbiAgZm9udFdlaWdodDogJ25vcm1hbCcsXG4gIGRpc3BsYXk6ICdmbGV4Jyxcbn0pO1xuXG5leHBvcnQgY29uc3QgSW5wdXRXcmFwcGVyID0gc3R5bGVkLmRpdihpbnB1dFdyYXBwZXIpO1xuXG5leHBvcnQgY29uc3QgZm9ybUZpZWxkU3R5bGVzID0gY3NzKHtcbiAgLi4uZm9ybUJhc2VGaWVsZFN0eWxlc09iamVjdCxcbiAgLi4uZm9ybUZpZWxkUGFkZGluZ1N0eWxlcyxcbiAgbGluZUhlaWdodDogJ2Jhc2UnLFxuICBbSW5wdXRTZWxlY3RvcnMuRk9DVVNdOiBmb3JtRmllbGRGb2N1c1N0eWxlcyxcbn0pO1xuXG5leHBvcnQgY29uc3QgY29uZGl0aW9uYWxTdHlsZXMgPSB2YXJpYW50KHtcbiAgdmFyaWFudHM6IHtcbiAgICBlcnJvcjoge1xuICAgICAgYm9yZGVyQ29sb3I6ICdmZWVkYmFjay1lcnJvcicsXG4gICAgICBbSW5wdXRTZWxlY3RvcnMuSE9WRVJdOiB7XG4gICAgICAgIGJvcmRlckNvbG9yOiAnZmVlZGJhY2stZXJyb3InLFxuICAgICAgfSxcbiAgICAgIFtJbnB1dFNlbGVjdG9ycy5GT0NVU106IHtcbiAgICAgICAgYm9yZGVyQ29sb3I6ICdmZWVkYmFjay1lcnJvcicsXG4gICAgICAgIGJveFNoYWRvdzogYGluc2V0IDAgMCAwIDFweCAke3RoZW1lLmNvbG9yc1snZmVlZGJhY2stZXJyb3InXX1gLFxuICAgICAgfSxcbiAgICB9LFxuICAgIGFjdGl2YXRlZDogeyBib3JkZXJDb2xvcjogJ2N1cnJlbnRDb2xvcicgfSxcbiAgfSxcbn0pO1xuXG5leHBvcnQgY29uc3QgY29uZGl0aW9uYWxTdHlsZVN0YXRlID0gKGVycm9yOiBib29sZWFuLCBhY3RpdmF0ZWQ6IGJvb2xlYW4pID0+IHtcbiAgcmV0dXJuIGVycm9yID8gJ2Vycm9yJyA6IGFjdGl2YXRlZCA/ICdhY3RpdmF0ZWQnIDogdW5kZWZpbmVkO1xufTtcblxuZXhwb3J0IGNvbnN0IGlucHV0U2l6ZVN0eWxlcyA9IHZhcmlhbnQoe1xuICBwcm9wOiAnaW5wdXRTaXplJyxcbiAgZGVmYXVsdFZhcmlhbnQ6ICdiYXNlJyxcbiAgYmFzZToge1xuICAgIHB4OiA4LFxuICB9LFxuICB2YXJpYW50czoge1xuICAgIGJhc2U6IHtcbiAgICAgIC4uLmZvcm1GaWVsZFBhZGRpbmdTdHlsZXMsXG4gICAgfSxcbiAgICBzbWFsbDoge1xuICAgICAgcHk6IDMgYXMgYW55LFxuICAgIH0sXG4gICAgc21hbGxGaWxlOiB7XG4gICAgICBweTogMiBhcyBhbnksXG4gICAgfSxcbiAgfSxcbn0pO1xuIl19 */");
|
|
69
80
|
export const formFieldStyles = css({
|
|
70
81
|
...formBaseFieldStylesObject,
|
|
71
82
|
...formFieldPaddingStyles,
|
|
@@ -31,12 +31,14 @@ export const GridFormRadioGroupInput = ({
|
|
|
31
31
|
children: field.options.map(({
|
|
32
32
|
label,
|
|
33
33
|
value,
|
|
34
|
+
infotip,
|
|
34
35
|
...rest
|
|
35
36
|
}) => /*#__PURE__*/_createElement(Radio, {
|
|
36
37
|
...register(field.name, field.validation),
|
|
37
38
|
disabled: disabled,
|
|
38
39
|
error: error,
|
|
39
40
|
id: field.id,
|
|
41
|
+
infotip: infotip,
|
|
40
42
|
key: value,
|
|
41
43
|
label: label,
|
|
42
44
|
value: value,
|
|
@@ -21,6 +21,10 @@ export declare const getComponent: (componentName: string) => {
|
|
|
21
21
|
};
|
|
22
22
|
type GridFormInputGroupTestComponentProps = GridFormInputGroupProps & {
|
|
23
23
|
mode?: 'onSubmit' | 'onChange';
|
|
24
|
+
externalLabel?: {
|
|
25
|
+
id: string;
|
|
26
|
+
text: string;
|
|
27
|
+
};
|
|
24
28
|
};
|
|
25
29
|
export declare const GridFormInputGroupTestComponent: React.FC<GridFormInputGroupTestComponentProps>;
|
|
26
30
|
export {};
|
package/dist/GridForm/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { MinimalCheckboxProps } from '../ConnectedForm';
|
|
|
5
5
|
import { CheckboxLabelUnion, TextAreaProps } from '../Form';
|
|
6
6
|
import { CheckboxPaddingProps } from '../Form/types';
|
|
7
7
|
import { ColumnProps } from '../Layout';
|
|
8
|
-
import {
|
|
8
|
+
import { InfoTipSubComponentProps } from '../Tip/InfoTip/type-utils';
|
|
9
9
|
import { Text, TextProps } from '../Typography/Text';
|
|
10
10
|
export interface BaseFormInputProps {
|
|
11
11
|
className?: string;
|
|
@@ -25,7 +25,12 @@ export type BaseFormField<Value> = {
|
|
|
25
25
|
* HTML id to use instead of the name.
|
|
26
26
|
*/
|
|
27
27
|
id?: string;
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* InfoTip to display next to the field label. The InfoTip button is
|
|
30
|
+
* automatically labelled by the field label. To override this behavior,
|
|
31
|
+
* provide `ariaLabel` or `ariaLabelledby`.
|
|
32
|
+
*/
|
|
33
|
+
infotip?: InfoTipSubComponentProps;
|
|
29
34
|
isSoloField?: boolean;
|
|
30
35
|
name: string;
|
|
31
36
|
onUpdate?: (value: Value) => void;
|
|
@@ -8,14 +8,17 @@ export const InfoTipButton = /*#__PURE__*/forwardRef(({
|
|
|
8
8
|
active,
|
|
9
9
|
children,
|
|
10
10
|
emphasis,
|
|
11
|
+
'aria-label': ariaLabel,
|
|
12
|
+
'aria-labelledby': ariaLabelledby,
|
|
11
13
|
...props
|
|
12
14
|
}, ref) => {
|
|
13
15
|
const Icon = emphasis === 'high' ? MiniInfoCircleIcon : MiniInfoOutlineIcon;
|
|
14
16
|
return /*#__PURE__*/_jsxs(InfoTipButtonBase, {
|
|
17
|
+
...props,
|
|
15
18
|
active: active,
|
|
16
19
|
"aria-expanded": active,
|
|
17
|
-
"aria-label":
|
|
18
|
-
|
|
20
|
+
"aria-label": ariaLabel,
|
|
21
|
+
"aria-labelledby": ariaLabelledby,
|
|
19
22
|
ref: ref,
|
|
20
23
|
children: [Icon && /*#__PURE__*/_jsx(Icon, {
|
|
21
24
|
"aria-hidden": true,
|
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { TipBaseAlignment, TipBaseProps } from '../shared/types';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Base props shared by all InfoTip variants.
|
|
5
|
+
* Contains all common props except the aria-* specific props.
|
|
6
|
+
*/
|
|
7
|
+
export type InfoTipBaseProps = TipBaseProps & {
|
|
4
8
|
alignment?: TipBaseAlignment;
|
|
9
|
+
/**
|
|
10
|
+
* Accessible role description for the InfoTip button. Useful for translation.
|
|
11
|
+
* @default "More information button"
|
|
12
|
+
*/
|
|
13
|
+
ariaRoleDescription?: string;
|
|
5
14
|
emphasis?: 'low' | 'high';
|
|
6
15
|
/**
|
|
7
|
-
* Called when the
|
|
16
|
+
* Called when the InfoTip button is clicked - the onClick function is called after the DOM updates and the tip is mounted.
|
|
8
17
|
*/
|
|
9
18
|
onClick?: (arg0: {
|
|
10
19
|
isTipHidden: boolean;
|
|
11
20
|
}) => void;
|
|
12
21
|
};
|
|
22
|
+
type InfoTipPropsWithAriaLabel = InfoTipBaseProps & {
|
|
23
|
+
/**
|
|
24
|
+
* Accessible label for the InfoTip button. ariaLabel or ariaLabelledby should be provided for accessibility.
|
|
25
|
+
*/
|
|
26
|
+
ariaLabel?: string;
|
|
27
|
+
ariaLabelledby?: never;
|
|
28
|
+
};
|
|
29
|
+
type InfoTipPropsWithAriaLabelledby = InfoTipBaseProps & {
|
|
30
|
+
ariaLabel?: never;
|
|
31
|
+
/**
|
|
32
|
+
* ID of an element that labels the InfoTip button.
|
|
33
|
+
*/
|
|
34
|
+
ariaLabelledby?: string;
|
|
35
|
+
};
|
|
36
|
+
export type InfoTipProps = InfoTipPropsWithAriaLabel | InfoTipPropsWithAriaLabelledby;
|
|
13
37
|
export declare const InfoTip: React.FC<InfoTipProps>;
|
|
38
|
+
export {};
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { getFocusableElements as getFocusableElementsUtil } from '../../utils/focus';
|
|
3
|
-
import { extractTextContent } from '../../utils/react';
|
|
4
3
|
import { FloatingTip } from '../shared/FloatingTip';
|
|
5
4
|
import { InlineTip } from '../shared/InlineTip';
|
|
6
5
|
import { tipDefaultProps } from '../shared/types';
|
|
7
|
-
import {
|
|
8
|
-
import { ScreenreaderNavigableText } from './elements';
|
|
6
|
+
import { isFloatingElementOpen } from '../shared/utils';
|
|
9
7
|
import { InfoTipButton } from './InfoTipButton';
|
|
10
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
-
const ARIA_HIDDEN_DELAY_MS = 1000;
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Base props shared by all InfoTip variants.
|
|
11
|
+
* Contains all common props except the aria-* specific props.
|
|
12
|
+
*/
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
14
|
const MODAL_SELECTOR = 'dialog[open],[role="dialog"]:not([aria-hidden="true"]),[role="alertdialog"]:not([aria-hidden="true"])';
|
|
15
15
|
export const InfoTip = ({
|
|
16
16
|
alignment = 'top-right',
|
|
17
|
+
ariaLabel,
|
|
18
|
+
ariaLabelledby,
|
|
19
|
+
ariaRoleDescription = 'More information button',
|
|
17
20
|
emphasis = 'low',
|
|
18
21
|
info,
|
|
19
22
|
onClick,
|
|
@@ -22,25 +25,14 @@ export const InfoTip = ({
|
|
|
22
25
|
}) => {
|
|
23
26
|
const isFloating = placement === 'floating';
|
|
24
27
|
const [isTipHidden, setHideTip] = useState(true);
|
|
25
|
-
const [isAriaHidden, setIsAriaHidden] = useState(false);
|
|
26
|
-
const [shouldAnnounce, setShouldAnnounce] = useState(false);
|
|
27
28
|
const [loaded, setLoaded] = useState(false);
|
|
28
29
|
const wrapperRef = useRef(null);
|
|
29
30
|
const buttonRef = useRef(null);
|
|
30
31
|
const popoverContentNodeRef = useRef(null);
|
|
31
32
|
const isInitialMount = useRef(true);
|
|
32
|
-
const ariaHiddenTimeoutRef = useRef(null);
|
|
33
|
-
const announceTimeoutRef = useRef(null);
|
|
34
33
|
const getFocusableElements = useCallback(() => {
|
|
35
34
|
return getFocusableElementsUtil(popoverContentNodeRef.current);
|
|
36
35
|
}, []);
|
|
37
|
-
const clearAndSetTimeout = useCallback((timeoutRef, callback, delay) => {
|
|
38
|
-
clearTimeout(timeoutRef.current ?? undefined);
|
|
39
|
-
timeoutRef.current = setTimeout(() => {
|
|
40
|
-
callback();
|
|
41
|
-
timeoutRef.current = null;
|
|
42
|
-
}, delay);
|
|
43
|
-
}, []);
|
|
44
36
|
const popoverContentRef = useCallback(node => {
|
|
45
37
|
popoverContentNodeRef.current = node;
|
|
46
38
|
if (node && !isTipHidden && isFloating) {
|
|
@@ -51,24 +43,10 @@ export const InfoTip = ({
|
|
|
51
43
|
}, [onClick, isTipHidden, isFloating]);
|
|
52
44
|
useEffect(() => {
|
|
53
45
|
setLoaded(true);
|
|
54
|
-
const ariaHiddenTimeout = ariaHiddenTimeoutRef.current;
|
|
55
|
-
const announceTimeout = announceTimeoutRef.current;
|
|
56
|
-
return () => {
|
|
57
|
-
clearTimeout(ariaHiddenTimeout ?? undefined);
|
|
58
|
-
clearTimeout(announceTimeout ?? undefined);
|
|
59
|
-
};
|
|
60
46
|
}, []);
|
|
61
47
|
const setTipIsHidden = useCallback(nextTipState => {
|
|
62
48
|
setHideTip(nextTipState);
|
|
63
|
-
|
|
64
|
-
clearAndSetTimeout(ariaHiddenTimeoutRef, () => setIsAriaHidden(true), ARIA_HIDDEN_DELAY_MS);
|
|
65
|
-
} else if (nextTipState) {
|
|
66
|
-
if (isAriaHidden) setIsAriaHidden(false);
|
|
67
|
-
setShouldAnnounce(false);
|
|
68
|
-
clearTimeout(ariaHiddenTimeoutRef.current ?? undefined);
|
|
69
|
-
ariaHiddenTimeoutRef.current = null;
|
|
70
|
-
}
|
|
71
|
-
}, [isAriaHidden, isFloating, clearAndSetTimeout]);
|
|
49
|
+
}, []);
|
|
72
50
|
const handleOutsideClick = useCallback(e => {
|
|
73
51
|
const wrapper = wrapperRef.current;
|
|
74
52
|
const isOutside = wrapper && (!(e.target instanceof HTMLElement) || !wrapper.contains(e.target));
|
|
@@ -79,10 +57,7 @@ export const InfoTip = ({
|
|
|
79
57
|
const clickHandler = useCallback(() => {
|
|
80
58
|
const currentTipState = !isTipHidden;
|
|
81
59
|
setTipIsHidden(currentTipState);
|
|
82
|
-
|
|
83
|
-
clearAndSetTimeout(announceTimeoutRef, () => setShouldAnnounce(true), 0);
|
|
84
|
-
}
|
|
85
|
-
}, [isTipHidden, setTipIsHidden, clearAndSetTimeout]);
|
|
60
|
+
}, [isTipHidden, setTipIsHidden]);
|
|
86
61
|
useLayoutEffect(() => {
|
|
87
62
|
if (isInitialMount.current) {
|
|
88
63
|
isInitialMount.current = false;
|
|
@@ -108,7 +83,14 @@ export const InfoTip = ({
|
|
|
108
83
|
const handleGlobalEscapeKey = e => {
|
|
109
84
|
if (e.key !== 'Escape') return;
|
|
110
85
|
const openModals = document.querySelectorAll(MODAL_SELECTOR);
|
|
111
|
-
const hasUnrelatedModal = Array.from(openModals).some(modal =>
|
|
86
|
+
const hasUnrelatedModal = Array.from(openModals).some(modal => {
|
|
87
|
+
// Only consider floating elements that are actually open
|
|
88
|
+
if (!isFloatingElementOpen(modal)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
// Check if it's unrelated to this InfoTip
|
|
92
|
+
return wrapperRef.current && !modal.contains(wrapperRef.current);
|
|
93
|
+
});
|
|
112
94
|
if (hasUnrelatedModal) return;
|
|
113
95
|
e.preventDefault();
|
|
114
96
|
e.stopPropagation();
|
|
@@ -120,7 +102,18 @@ export const InfoTip = ({
|
|
|
120
102
|
const handleTabKeyInPopover = event => {
|
|
121
103
|
if (event.key !== 'Tab' || event.shiftKey) return;
|
|
122
104
|
const focusableElements = getFocusableElements();
|
|
123
|
-
|
|
105
|
+
const {
|
|
106
|
+
activeElement
|
|
107
|
+
} = document;
|
|
108
|
+
|
|
109
|
+
// If no focusable elements and popover itself has focus, wrap to button
|
|
110
|
+
if (focusableElements.length === 0) {
|
|
111
|
+
if (activeElement === popoverContentNodeRef.current) {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
buttonRef.current?.focus();
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
124
117
|
const lastElement = focusableElements[focusableElements.length - 1];
|
|
125
118
|
|
|
126
119
|
// Only wrap forward: if on last element, wrap to button
|
|
@@ -141,45 +134,35 @@ export const InfoTip = ({
|
|
|
141
134
|
};
|
|
142
135
|
}
|
|
143
136
|
return () => document.removeEventListener('keydown', handleGlobalEscapeKey, true);
|
|
144
|
-
}, [isTipHidden, isFloating,
|
|
137
|
+
}, [isTipHidden, isFloating, getFocusableElements, setTipIsHidden]);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (isTipHidden) return;
|
|
140
|
+
const timeoutId = setTimeout(() => {
|
|
141
|
+
popoverContentNodeRef.current?.focus();
|
|
142
|
+
}, 0);
|
|
143
|
+
return () => clearTimeout(timeoutId);
|
|
144
|
+
}, [isTipHidden]);
|
|
145
145
|
const Tip = loaded && isFloating ? FloatingTip : InlineTip;
|
|
146
146
|
const tipProps = useMemo(() => ({
|
|
147
147
|
alignment,
|
|
148
148
|
info,
|
|
149
149
|
isTipHidden,
|
|
150
|
+
contentRef: popoverContentRef,
|
|
150
151
|
wrapperRef,
|
|
151
|
-
...(isFloating && {
|
|
152
|
-
popoverContentRef
|
|
153
|
-
}),
|
|
154
152
|
...rest
|
|
155
|
-
}), [alignment, info, isTipHidden,
|
|
156
|
-
const extractedTextContent = useMemo(() => extractTextContent(info), [info]);
|
|
157
|
-
const screenreaderInfo = shouldAnnounce && !isTipHidden ? extractedTextContent : '\xa0';
|
|
158
|
-
const screenreaderText = useMemo(() => /*#__PURE__*/_jsx(ScreenreaderNavigableText, {
|
|
159
|
-
"aria-hidden": isAriaHidden,
|
|
160
|
-
"aria-live": "assertive",
|
|
161
|
-
screenreader: true,
|
|
162
|
-
children: screenreaderInfo
|
|
163
|
-
}), [isAriaHidden, screenreaderInfo]);
|
|
164
|
-
const button = useMemo(() => /*#__PURE__*/_jsx(InfoTipButton, {
|
|
165
|
-
active: !isTipHidden,
|
|
166
|
-
"aria-expanded": !isTipHidden,
|
|
167
|
-
emphasis: emphasis,
|
|
168
|
-
ref: buttonRef,
|
|
169
|
-
onClick: clickHandler
|
|
170
|
-
}), [isTipHidden, emphasis, clickHandler]);
|
|
171
|
-
|
|
172
|
-
/*
|
|
173
|
-
* For floating placement, screenreader text comes before button to maintain
|
|
174
|
-
* correct DOM order despite Portal rendering. See GMT-64 for planned fix.
|
|
175
|
-
*/
|
|
153
|
+
}), [alignment, info, isTipHidden, popoverContentRef, wrapperRef, rest]);
|
|
176
154
|
return /*#__PURE__*/_jsx(Tip, {
|
|
177
155
|
...tipProps,
|
|
178
156
|
type: "info",
|
|
179
|
-
children:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
157
|
+
children: /*#__PURE__*/_jsx(InfoTipButton, {
|
|
158
|
+
active: !isTipHidden,
|
|
159
|
+
"aria-expanded": !isTipHidden,
|
|
160
|
+
"aria-label": ariaLabel,
|
|
161
|
+
"aria-labelledby": ariaLabelledby,
|
|
162
|
+
"aria-roledescription": ariaRoleDescription,
|
|
163
|
+
emphasis: emphasis,
|
|
164
|
+
ref: buttonRef,
|
|
165
|
+
onClick: clickHandler
|
|
183
166
|
})
|
|
184
167
|
});
|
|
185
168
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { InfoTipBaseProps, InfoTipProps } from './index';
|
|
2
|
+
type InfoTipAriaLabel = InfoTipBaseProps & {
|
|
3
|
+
ariaLabel?: string;
|
|
4
|
+
ariaLabelledby?: never;
|
|
5
|
+
};
|
|
6
|
+
type InfoTipAriaLabelledby = InfoTipBaseProps & {
|
|
7
|
+
ariaLabel?: never;
|
|
8
|
+
ariaLabelledby?: string;
|
|
9
|
+
};
|
|
10
|
+
type InfoTipPropsAriaWithFallback = InfoTipBaseProps & {
|
|
11
|
+
ariaLabel?: never;
|
|
12
|
+
ariaLabelledby?: never;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* InfoTip props that allow both ariaLabel and ariaLabelledby to be optional.
|
|
16
|
+
* Used in components that automatically provide ariaLabelledby when neither is provided.
|
|
17
|
+
*/
|
|
18
|
+
export type InfoTipSubComponentProps = InfoTipAriaLabel | InfoTipAriaLabelledby | InfoTipPropsAriaWithFallback;
|
|
19
|
+
export declare const createInfoTipProps: (props: InfoTipSubComponentProps, fallbackAriaLabelledby?: string) => {
|
|
20
|
+
infotipProps: InfoTipProps;
|
|
21
|
+
shouldLabelInfoTip: boolean;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Hook to handle infotip props and accessibility labeling.
|
|
25
|
+
* Extracts the common pattern used by Radio, Checkbox, and FormGroupLabel components.
|
|
26
|
+
*
|
|
27
|
+
* @param infotip - Optional infotip subcomponent props
|
|
28
|
+
* @returns Object containing infotipProps, labelId, and shouldLabelInfoTip flag
|
|
29
|
+
*/
|
|
30
|
+
export declare const useInfotipProps: (infotip?: InfoTipSubComponentProps) => {
|
|
31
|
+
infotipProps: InfoTipProps | undefined;
|
|
32
|
+
labelId: string;
|
|
33
|
+
shouldLabelInfoTip: boolean;
|
|
34
|
+
};
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* InfoTip props that allow both ariaLabel and ariaLabelledby to be optional.
|
|
5
|
+
* Used in components that automatically provide ariaLabelledby when neither is provided.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const createInfoTipProps = (props, fallbackAriaLabelledby) => {
|
|
9
|
+
const {
|
|
10
|
+
ariaLabel,
|
|
11
|
+
ariaLabelledby,
|
|
12
|
+
...rest
|
|
13
|
+
} = props;
|
|
14
|
+
const hasAriaLabel = ariaLabel !== undefined;
|
|
15
|
+
const hasAriaLabelledby = ariaLabelledby !== undefined;
|
|
16
|
+
if (hasAriaLabel) {
|
|
17
|
+
return {
|
|
18
|
+
infotipProps: {
|
|
19
|
+
...rest,
|
|
20
|
+
ariaLabel
|
|
21
|
+
},
|
|
22
|
+
shouldLabelInfoTip: false
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (hasAriaLabelledby) {
|
|
26
|
+
return {
|
|
27
|
+
infotipProps: {
|
|
28
|
+
...rest,
|
|
29
|
+
ariaLabelledby
|
|
30
|
+
},
|
|
31
|
+
shouldLabelInfoTip: false
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (fallbackAriaLabelledby) {
|
|
35
|
+
return {
|
|
36
|
+
infotipProps: {
|
|
37
|
+
...rest,
|
|
38
|
+
ariaLabelledby: fallbackAriaLabelledby
|
|
39
|
+
},
|
|
40
|
+
shouldLabelInfoTip: true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
infotipProps: {
|
|
45
|
+
...rest
|
|
46
|
+
},
|
|
47
|
+
shouldLabelInfoTip: false
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Hook to handle infotip props and accessibility labeling.
|
|
53
|
+
* Extracts the common pattern used by Radio, Checkbox, and FormGroupLabel components.
|
|
54
|
+
*
|
|
55
|
+
* @param infotip - Optional infotip subcomponent props
|
|
56
|
+
* @returns Object containing infotipProps, labelId, and shouldLabelInfoTip flag
|
|
57
|
+
*/
|
|
58
|
+
export const useInfotipProps = infotip => {
|
|
59
|
+
const labelId = useId();
|
|
60
|
+
const {
|
|
61
|
+
infotipProps,
|
|
62
|
+
shouldLabelInfoTip
|
|
63
|
+
} = infotip ? createInfoTipProps(infotip, labelId) : {
|
|
64
|
+
infotipProps: undefined,
|
|
65
|
+
shouldLabelInfoTip: false
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
infotipProps,
|
|
69
|
+
labelId,
|
|
70
|
+
shouldLabelInfoTip
|
|
71
|
+
};
|
|
72
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { setupRtl } from '@codecademy/gamut-tests';
|
|
2
2
|
import { RefObject } from 'react';
|
|
3
|
-
import { InfoTip
|
|
3
|
+
import { InfoTip } from '../InfoTip';
|
|
4
4
|
import { TipPlacements } from '../shared/types';
|
|
5
5
|
type InfoTipView = ReturnType<ReturnType<typeof setupRtl<typeof InfoTip>>>['view'];
|
|
6
|
-
type Placement = NonNullable<InfoTipProps['placement']>;
|
|
7
6
|
type ViewParam = {
|
|
8
7
|
view: InfoTipView;
|
|
9
8
|
};
|
|
@@ -14,7 +13,7 @@ type InfoParam = {
|
|
|
14
13
|
info: string;
|
|
15
14
|
};
|
|
16
15
|
type PlacementParam = {
|
|
17
|
-
placement:
|
|
16
|
+
placement: TipPlacements;
|
|
18
17
|
};
|
|
19
18
|
export declare const createFocusOnClick: (ref: RefObject<HTMLDivElement>) => ({ isTipHidden }: {
|
|
20
19
|
isTipHidden: boolean;
|
|
@@ -46,13 +45,10 @@ export declare const pressKey: (key: string) => Promise<void>;
|
|
|
46
45
|
export declare const waitForLinkToHaveFocus: ({ view, linkText, }: ViewParam & LinkTextParam) => Promise<HTMLElement>;
|
|
47
46
|
export declare const openTipAndWaitForLink: ({ view, linkText, }: ViewParam & LinkTextParam) => Promise<HTMLElement>;
|
|
48
47
|
export declare const openTipTabToLinkAndWaitForFocus: (view: InfoTipView, linkText: string) => Promise<HTMLElement>;
|
|
49
|
-
export declare const
|
|
50
|
-
export declare const testEscapeKeyReturnsFocus: ({ view, info, placement, }: ViewParam & InfoParam & PlacementParam) => Promise<void>;
|
|
51
|
-
export declare const testFocusWrap: ({ view, containerRef, direction, }: ViewParam & {
|
|
52
|
-
containerRef: RefObject<HTMLDivElement>;
|
|
48
|
+
export declare const testFocusWrap: ({ view, direction, }: ViewParam & {
|
|
53
49
|
direction: 'forward' | 'backward';
|
|
54
50
|
}) => Promise<void>;
|
|
55
|
-
export declare const
|
|
51
|
+
export declare const testTabFromPopoverWithNoInteractiveElements: (view: InfoTipView) => Promise<void>;
|
|
56
52
|
export declare const testTabbingBetweenLinks: ({ view, firstLinkText, secondLinkText, placement, }: ViewParam & {
|
|
57
53
|
firstLinkText: string;
|
|
58
54
|
secondLinkText: string;
|
|
@@ -60,7 +56,6 @@ export declare const testTabbingBetweenLinks: ({ view, firstLinkText, secondLink
|
|
|
60
56
|
}) => Promise<void>;
|
|
61
57
|
export declare const setupLinkTestWithPlacement: (linkText: string, placement: TipPlacements, renderView: ReturnType<typeof setupRtl<typeof InfoTip>>) => {
|
|
62
58
|
view: import("@testing-library/react").RenderResult;
|
|
63
|
-
containerRef: RefObject<HTMLDivElement>;
|
|
64
59
|
info: import("react/jsx-runtime").JSX.Element;
|
|
65
60
|
onClick: ({ isTipHidden }: {
|
|
66
61
|
isTipHidden: boolean;
|
|
@@ -84,28 +79,12 @@ type ViewWithQueries = {
|
|
|
84
79
|
getAllByText: (text: string) => HTMLElement[];
|
|
85
80
|
getAllByLabelText: (text: string) => HTMLElement[];
|
|
86
81
|
};
|
|
87
|
-
export declare const getVisibleTip: ({ text, placement, }: {
|
|
88
|
-
text: string;
|
|
89
|
-
placement?: "inline" | "floating" | undefined;
|
|
90
|
-
}) => HTMLElement | undefined;
|
|
91
|
-
export declare const expectTipToBeVisible: ({ text, placement, }: {
|
|
92
|
-
text: string;
|
|
93
|
-
placement?: "inline" | "floating" | undefined;
|
|
94
|
-
}) => void;
|
|
95
|
-
export declare const expectTipToBeClosed: ({ text, placement, }: {
|
|
96
|
-
text: string;
|
|
97
|
-
placement?: "inline" | "floating" | undefined;
|
|
98
|
-
}) => void;
|
|
99
82
|
export declare const openInfoTipsWithKeyboard: ({ view, count, }: {
|
|
100
83
|
view: ViewWithQueries;
|
|
101
84
|
count: number;
|
|
102
85
|
}) => Promise<void>;
|
|
103
86
|
export declare const expectTipsVisible: (tips: {
|
|
104
87
|
text: string;
|
|
105
|
-
placement?: 'inline' | 'floating';
|
|
106
|
-
}[]) => void;
|
|
107
|
-
export declare const expectTipsClosed: (tips: {
|
|
108
|
-
text: string;
|
|
109
|
-
placement?: 'inline' | 'floating';
|
|
110
88
|
}[]) => void;
|
|
89
|
+
export declare const expectTipsClosed: () => void;
|
|
111
90
|
export {};
|
|
@@ -18,7 +18,7 @@ export const FloatingTip = ({
|
|
|
18
18
|
loading,
|
|
19
19
|
narrow,
|
|
20
20
|
overline,
|
|
21
|
-
|
|
21
|
+
contentRef,
|
|
22
22
|
truncateLines,
|
|
23
23
|
type,
|
|
24
24
|
username,
|
|
@@ -123,7 +123,7 @@ export const FloatingTip = ({
|
|
|
123
123
|
width: inheritDims ? 'inherit' : undefined,
|
|
124
124
|
onBlur: toolOnlyEventFunc,
|
|
125
125
|
onFocus: toolOnlyEventFunc,
|
|
126
|
-
onKeyDown: escapeKeyPressHandler
|
|
126
|
+
onKeyDown: escapeKeyPressHandler,
|
|
127
127
|
onMouseDown: e => e.preventDefault(),
|
|
128
128
|
onMouseEnter: toolOnlyEventFunc,
|
|
129
129
|
children: children
|
|
@@ -134,7 +134,7 @@ export const FloatingTip = ({
|
|
|
134
134
|
horizontalOffset: offset,
|
|
135
135
|
isOpen: isPopoverOpen,
|
|
136
136
|
outline: true,
|
|
137
|
-
popoverContainerRef:
|
|
137
|
+
popoverContainerRef: contentRef,
|
|
138
138
|
skipFocusTrap: true,
|
|
139
139
|
targetRef: ref,
|
|
140
140
|
variant: "secondary",
|
|
@@ -17,6 +17,7 @@ export const InlineTip = ({
|
|
|
17
17
|
loading,
|
|
18
18
|
narrow,
|
|
19
19
|
overline,
|
|
20
|
+
contentRef,
|
|
20
21
|
truncateLines,
|
|
21
22
|
type,
|
|
22
23
|
username,
|
|
@@ -42,7 +43,7 @@ export const InlineTip = ({
|
|
|
42
43
|
height: inheritDims ? 'inherit' : undefined,
|
|
43
44
|
ref: wrapperRef,
|
|
44
45
|
width: inheritDims ? 'inherit' : undefined,
|
|
45
|
-
onKeyDown: escapeKeyPressHandler
|
|
46
|
+
onKeyDown: escapeKeyPressHandler,
|
|
46
47
|
children: children
|
|
47
48
|
});
|
|
48
49
|
const tipBody = /*#__PURE__*/_jsx(InlineTipBodyWrapper, {
|
|
@@ -55,7 +56,9 @@ export const InlineTip = ({
|
|
|
55
56
|
color: "currentColor",
|
|
56
57
|
horizNarrow: narrow && isHorizontalCenter,
|
|
57
58
|
id: id,
|
|
59
|
+
ref: contentRef,
|
|
58
60
|
role: type === 'tool' ? 'tooltip' : undefined,
|
|
61
|
+
tabIndex: type === 'info' ? -1 : undefined,
|
|
59
62
|
width: narrow && !isHorizontalCenter ? narrowWidth : 'max-content',
|
|
60
63
|
zIndex: "auto",
|
|
61
64
|
children: type === 'preview' ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
@@ -46,7 +46,7 @@ export type TipPlacementComponentProps = Omit<TipNewBaseProps, 'placement' | 'em
|
|
|
46
46
|
escapeKeyPressHandler?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
47
47
|
id?: string;
|
|
48
48
|
isTipHidden?: boolean;
|
|
49
|
-
|
|
49
|
+
contentRef?: React.RefObject<HTMLDivElement> | ((node: HTMLDivElement | null) => void);
|
|
50
50
|
type: 'info' | 'tool' | 'preview';
|
|
51
51
|
wrapperRef?: React.RefObject<HTMLDivElement>;
|
|
52
52
|
zIndex?: number;
|
|
@@ -6,3 +6,22 @@ export declare const escapeKeyPressHandler: (event: React.KeyboardEvent<HTMLDivE
|
|
|
6
6
|
* Uses the modern checkVisibility API with a fallback for older browsers.
|
|
7
7
|
*/
|
|
8
8
|
export declare const isElementVisible: (element: Element) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Check if a floating element (modal, dialog, popover, overlay, etc.) is actually open and blocking.
|
|
11
|
+
*
|
|
12
|
+
* A floating element is considered "open" and blocking if:
|
|
13
|
+
* 1. It's a <dialog> element with the open attribute (always blocking per HTML spec), OR
|
|
14
|
+
* 2. It has role="alertdialog" (always blocking per ARIA spec), OR
|
|
15
|
+
* 3. It has role="dialog" AND:
|
|
16
|
+
* - It's not aria-hidden="true", AND
|
|
17
|
+
* - It doesn't have aria-expanded="false" (for collapsible dialogs), AND
|
|
18
|
+
* - It's actually visible (not just in DOM), AND
|
|
19
|
+
* - It has aria-modal="true" (indicates blocking modal per ARIA spec)
|
|
20
|
+
*
|
|
21
|
+
* Non-blocking popovers and collapsible dialogs without aria-modal="true" are not considered
|
|
22
|
+
* blocking and should not prevent InfoTip from closing.
|
|
23
|
+
*
|
|
24
|
+
* @param element - The DOM element to check
|
|
25
|
+
* @returns `true` if the floating element is actually open and blocking, `false` otherwise
|
|
26
|
+
*/
|
|
27
|
+
export declare const isFloatingElementOpen: (element: Element) => boolean;
|