@codecademy/gamut 67.6.1-alpha.1bf8a5.0 → 67.6.1-alpha.74920d.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/BarChart/Bar/elements.d.ts +1392 -0
- package/dist/BarChart/Bar/elements.js +59 -0
- package/dist/BarChart/Bar/index.d.ts +1 -0
- package/dist/BarChart/Bar/index.js +2 -0
- package/dist/BarChart/BarChartProvider.d.ts +33 -0
- package/dist/BarChart/BarChartProvider.js +39 -0
- package/dist/BarChart/BarRow.d.ts +6 -0
- package/dist/BarChart/BarRow.js +179 -0
- package/dist/BarChart/GridLines.d.ts +5 -0
- package/dist/BarChart/GridLines.js +57 -0
- package/dist/BarChart/ScaleChartHeader.d.ts +10 -0
- package/dist/BarChart/ScaleChartHeader.js +65 -0
- package/dist/BarChart/index.d.ts +4 -0
- package/dist/BarChart/index.js +68 -0
- package/dist/BarChart/types.d.ts +56 -0
- package/dist/BarChart/types.js +1 -0
- package/dist/BarChart/utils/index.d.ts +72 -0
- package/dist/BarChart/utils/index.js +144 -0
- package/dist/ConnectedForm/ConnectedFormGroup.d.ts +0 -5
- package/dist/ConnectedForm/ConnectedFormGroup.js +1 -1
- package/dist/Form/elements/FormGroupLabel.js +2 -8
- package/dist/GridForm/GridFormInputGroup/__fixtures__/renderers.d.ts +0 -4
- package/dist/GridForm/types.d.ts +0 -5
- package/dist/Tip/InfoTip/InfoTipButton.js +2 -5
- package/dist/Tip/InfoTip/elements.d.ts +12 -0
- package/dist/Tip/InfoTip/elements.js +9 -0
- package/dist/Tip/InfoTip/index.d.ts +0 -18
- package/dist/Tip/InfoTip/index.js +66 -37
- package/dist/Tip/__tests__/helpers.d.ts +26 -5
- package/dist/Tip/shared/FloatingTip.js +3 -3
- package/dist/Tip/shared/InlineTip.js +1 -4
- package/dist/Tip/shared/types.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +2 -2
|
@@ -1,17 +1,19 @@
|
|
|
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';
|
|
3
4
|
import { FloatingTip } from '../shared/FloatingTip';
|
|
4
5
|
import { InlineTip } from '../shared/InlineTip';
|
|
5
6
|
import { tipDefaultProps } from '../shared/types';
|
|
6
7
|
import { isElementVisible } from '../shared/utils';
|
|
8
|
+
import { ScreenreaderNavigableText } from './elements';
|
|
7
9
|
import { InfoTipButton } from './InfoTipButton';
|
|
8
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
const ARIA_HIDDEN_DELAY_MS = 1000;
|
|
12
|
+
|
|
13
|
+
// Match native dialogs with open attribute, and role-based dialogs that aren't aria-hidden
|
|
9
14
|
const MODAL_SELECTOR = 'dialog[open],[role="dialog"]:not([aria-hidden="true"]),[role="alertdialog"]:not([aria-hidden="true"])';
|
|
10
15
|
export const InfoTip = ({
|
|
11
16
|
alignment = 'top-right',
|
|
12
|
-
ariaLabel,
|
|
13
|
-
ariaLabelledby,
|
|
14
|
-
ariaRoleDescription = 'More information button',
|
|
15
17
|
emphasis = 'low',
|
|
16
18
|
info,
|
|
17
19
|
onClick,
|
|
@@ -20,14 +22,25 @@ export const InfoTip = ({
|
|
|
20
22
|
}) => {
|
|
21
23
|
const isFloating = placement === 'floating';
|
|
22
24
|
const [isTipHidden, setHideTip] = useState(true);
|
|
25
|
+
const [isAriaHidden, setIsAriaHidden] = useState(false);
|
|
26
|
+
const [shouldAnnounce, setShouldAnnounce] = useState(false);
|
|
23
27
|
const [loaded, setLoaded] = useState(false);
|
|
24
28
|
const wrapperRef = useRef(null);
|
|
25
29
|
const buttonRef = useRef(null);
|
|
26
30
|
const popoverContentNodeRef = useRef(null);
|
|
27
31
|
const isInitialMount = useRef(true);
|
|
32
|
+
const ariaHiddenTimeoutRef = useRef(null);
|
|
33
|
+
const announceTimeoutRef = useRef(null);
|
|
28
34
|
const getFocusableElements = useCallback(() => {
|
|
29
35
|
return getFocusableElementsUtil(popoverContentNodeRef.current);
|
|
30
36
|
}, []);
|
|
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
|
+
}, []);
|
|
31
44
|
const popoverContentRef = useCallback(node => {
|
|
32
45
|
popoverContentNodeRef.current = node;
|
|
33
46
|
if (node && !isTipHidden && isFloating) {
|
|
@@ -38,10 +51,24 @@ export const InfoTip = ({
|
|
|
38
51
|
}, [onClick, isTipHidden, isFloating]);
|
|
39
52
|
useEffect(() => {
|
|
40
53
|
setLoaded(true);
|
|
54
|
+
const ariaHiddenTimeout = ariaHiddenTimeoutRef.current;
|
|
55
|
+
const announceTimeout = announceTimeoutRef.current;
|
|
56
|
+
return () => {
|
|
57
|
+
clearTimeout(ariaHiddenTimeout ?? undefined);
|
|
58
|
+
clearTimeout(announceTimeout ?? undefined);
|
|
59
|
+
};
|
|
41
60
|
}, []);
|
|
42
61
|
const setTipIsHidden = useCallback(nextTipState => {
|
|
43
62
|
setHideTip(nextTipState);
|
|
44
|
-
|
|
63
|
+
if (!nextTipState && !isFloating) {
|
|
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]);
|
|
45
72
|
const handleOutsideClick = useCallback(e => {
|
|
46
73
|
const wrapper = wrapperRef.current;
|
|
47
74
|
const isOutside = wrapper && (!(e.target instanceof HTMLElement) || !wrapper.contains(e.target));
|
|
@@ -52,7 +79,10 @@ export const InfoTip = ({
|
|
|
52
79
|
const clickHandler = useCallback(() => {
|
|
53
80
|
const currentTipState = !isTipHidden;
|
|
54
81
|
setTipIsHidden(currentTipState);
|
|
55
|
-
|
|
82
|
+
if (!currentTipState) {
|
|
83
|
+
clearAndSetTimeout(announceTimeoutRef, () => setShouldAnnounce(true), 0);
|
|
84
|
+
}
|
|
85
|
+
}, [isTipHidden, setTipIsHidden, clearAndSetTimeout]);
|
|
56
86
|
useLayoutEffect(() => {
|
|
57
87
|
if (isInitialMount.current) {
|
|
58
88
|
isInitialMount.current = false;
|
|
@@ -90,18 +120,7 @@ export const InfoTip = ({
|
|
|
90
120
|
const handleTabKeyInPopover = event => {
|
|
91
121
|
if (event.key !== 'Tab' || event.shiftKey) return;
|
|
92
122
|
const focusableElements = getFocusableElements();
|
|
93
|
-
|
|
94
|
-
activeElement
|
|
95
|
-
} = document;
|
|
96
|
-
|
|
97
|
-
// If no focusable elements and popover itself has focus, wrap to button
|
|
98
|
-
if (focusableElements.length === 0) {
|
|
99
|
-
if (activeElement === popoverContentNodeRef.current) {
|
|
100
|
-
event.preventDefault();
|
|
101
|
-
buttonRef.current?.focus();
|
|
102
|
-
}
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
123
|
+
if (focusableElements.length === 0) return;
|
|
105
124
|
const lastElement = focusableElements[focusableElements.length - 1];
|
|
106
125
|
|
|
107
126
|
// Only wrap forward: if on last element, wrap to button
|
|
@@ -122,35 +141,45 @@ export const InfoTip = ({
|
|
|
122
141
|
};
|
|
123
142
|
}
|
|
124
143
|
return () => document.removeEventListener('keydown', handleGlobalEscapeKey, true);
|
|
125
|
-
}, [isTipHidden, isFloating,
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (isTipHidden) return;
|
|
128
|
-
const timeoutId = setTimeout(() => {
|
|
129
|
-
popoverContentNodeRef.current?.focus();
|
|
130
|
-
}, 0);
|
|
131
|
-
return () => clearTimeout(timeoutId);
|
|
132
|
-
}, [isTipHidden]);
|
|
144
|
+
}, [isTipHidden, isFloating, setTipIsHidden, getFocusableElements]);
|
|
133
145
|
const Tip = loaded && isFloating ? FloatingTip : InlineTip;
|
|
134
146
|
const tipProps = useMemo(() => ({
|
|
135
147
|
alignment,
|
|
136
148
|
info,
|
|
137
149
|
isTipHidden,
|
|
138
|
-
contentRef: popoverContentRef,
|
|
139
150
|
wrapperRef,
|
|
151
|
+
...(isFloating && {
|
|
152
|
+
popoverContentRef
|
|
153
|
+
}),
|
|
140
154
|
...rest
|
|
141
|
-
}), [alignment, info, isTipHidden,
|
|
155
|
+
}), [alignment, info, isTipHidden, wrapperRef, isFloating, popoverContentRef, rest]);
|
|
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
|
+
*/
|
|
142
176
|
return /*#__PURE__*/_jsx(Tip, {
|
|
143
177
|
...tipProps,
|
|
144
178
|
type: "info",
|
|
145
|
-
children: /*#__PURE__*/
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
"aria-labelledby": ariaLabelledby,
|
|
150
|
-
"aria-roledescription": ariaRoleDescription,
|
|
151
|
-
emphasis: emphasis,
|
|
152
|
-
ref: buttonRef,
|
|
153
|
-
onClick: clickHandler
|
|
179
|
+
children: isFloating && alignment.includes('top') ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
180
|
+
children: [screenreaderText, button]
|
|
181
|
+
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
182
|
+
children: [button, screenreaderText]
|
|
154
183
|
})
|
|
155
184
|
});
|
|
156
185
|
};
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
/// <reference types="testing-library__jest-dom" />
|
|
3
3
|
import { setupRtl } from '@codecademy/gamut-tests';
|
|
4
4
|
import { RefObject } from 'react';
|
|
5
|
-
import { InfoTip } from '../InfoTip';
|
|
5
|
+
import { InfoTip, InfoTipProps } from '../InfoTip';
|
|
6
6
|
import { TipPlacements } from '../shared/types';
|
|
7
7
|
type InfoTipView = ReturnType<ReturnType<typeof setupRtl<typeof InfoTip>>>['view'];
|
|
8
|
+
type Placement = NonNullable<InfoTipProps['placement']>;
|
|
8
9
|
type ViewParam = {
|
|
9
10
|
view: InfoTipView;
|
|
10
11
|
};
|
|
@@ -15,7 +16,7 @@ type InfoParam = {
|
|
|
15
16
|
info: string;
|
|
16
17
|
};
|
|
17
18
|
type PlacementParam = {
|
|
18
|
-
placement:
|
|
19
|
+
placement: Placement;
|
|
19
20
|
};
|
|
20
21
|
export declare const createFocusOnClick: (ref: RefObject<HTMLDivElement>) => ({ isTipHidden }: {
|
|
21
22
|
isTipHidden: boolean;
|
|
@@ -47,10 +48,13 @@ export declare const pressKey: (key: string) => Promise<void>;
|
|
|
47
48
|
export declare const waitForLinkToHaveFocus: ({ view, linkText, }: ViewParam & LinkTextParam) => Promise<HTMLElement>;
|
|
48
49
|
export declare const openTipAndWaitForLink: ({ view, linkText, }: ViewParam & LinkTextParam) => Promise<HTMLElement>;
|
|
49
50
|
export declare const openTipTabToLinkAndWaitForFocus: (view: InfoTipView, linkText: string) => Promise<HTMLElement>;
|
|
50
|
-
export declare const
|
|
51
|
+
export declare const testShowTipOnClick: ({ view, info, placement, }: ViewParam & InfoParam & PlacementParam) => Promise<void>;
|
|
52
|
+
export declare const testEscapeKeyReturnsFocus: ({ view, info, placement, }: ViewParam & InfoParam & PlacementParam) => Promise<void>;
|
|
53
|
+
export declare const testFocusWrap: ({ view, containerRef, direction, }: ViewParam & {
|
|
54
|
+
containerRef: RefObject<HTMLDivElement>;
|
|
51
55
|
direction: 'forward' | 'backward';
|
|
52
56
|
}) => Promise<void>;
|
|
53
|
-
export declare const
|
|
57
|
+
export declare const getTipContent: (view: InfoTipView, text: string, useQuery?: boolean) => HTMLElement;
|
|
54
58
|
export declare const testTabbingBetweenLinks: ({ view, firstLinkText, secondLinkText, placement, }: ViewParam & {
|
|
55
59
|
firstLinkText: string;
|
|
56
60
|
secondLinkText: string;
|
|
@@ -58,6 +62,7 @@ export declare const testTabbingBetweenLinks: ({ view, firstLinkText, secondLink
|
|
|
58
62
|
}) => Promise<void>;
|
|
59
63
|
export declare const setupLinkTestWithPlacement: (linkText: string, placement: TipPlacements, renderView: ReturnType<typeof setupRtl<typeof InfoTip>>) => {
|
|
60
64
|
view: import("@testing-library/react").RenderResult;
|
|
65
|
+
containerRef: RefObject<HTMLDivElement>;
|
|
61
66
|
info: import("react/jsx-runtime").JSX.Element;
|
|
62
67
|
onClick: ({ isTipHidden }: {
|
|
63
68
|
isTipHidden: boolean;
|
|
@@ -81,12 +86,28 @@ type ViewWithQueries = {
|
|
|
81
86
|
getAllByText: (text: string) => HTMLElement[];
|
|
82
87
|
getAllByLabelText: (text: string) => HTMLElement[];
|
|
83
88
|
};
|
|
89
|
+
export declare const getVisibleTip: ({ text, placement, }: {
|
|
90
|
+
text: string;
|
|
91
|
+
placement?: "inline" | "floating" | undefined;
|
|
92
|
+
}) => HTMLElement | undefined;
|
|
93
|
+
export declare const expectTipToBeVisible: ({ text, placement, }: {
|
|
94
|
+
text: string;
|
|
95
|
+
placement?: "inline" | "floating" | undefined;
|
|
96
|
+
}) => void;
|
|
97
|
+
export declare const expectTipToBeClosed: ({ text, placement, }: {
|
|
98
|
+
text: string;
|
|
99
|
+
placement?: "inline" | "floating" | undefined;
|
|
100
|
+
}) => void;
|
|
84
101
|
export declare const openInfoTipsWithKeyboard: ({ view, count, }: {
|
|
85
102
|
view: ViewWithQueries;
|
|
86
103
|
count: number;
|
|
87
104
|
}) => Promise<void>;
|
|
88
105
|
export declare const expectTipsVisible: (tips: {
|
|
89
106
|
text: string;
|
|
107
|
+
placement?: 'inline' | 'floating';
|
|
108
|
+
}[]) => void;
|
|
109
|
+
export declare const expectTipsClosed: (tips: {
|
|
110
|
+
text: string;
|
|
111
|
+
placement?: 'inline' | 'floating';
|
|
90
112
|
}[]) => void;
|
|
91
|
-
export declare const expectTipsClosed: () => void;
|
|
92
113
|
export {};
|
|
@@ -18,7 +18,7 @@ export const FloatingTip = ({
|
|
|
18
18
|
loading,
|
|
19
19
|
narrow,
|
|
20
20
|
overline,
|
|
21
|
-
|
|
21
|
+
popoverContentRef,
|
|
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 ? e => escapeKeyPressHandler(e) : undefined,
|
|
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: popoverContentRef,
|
|
138
138
|
skipFocusTrap: true,
|
|
139
139
|
targetRef: ref,
|
|
140
140
|
variant: "secondary",
|
|
@@ -17,7 +17,6 @@ export const InlineTip = ({
|
|
|
17
17
|
loading,
|
|
18
18
|
narrow,
|
|
19
19
|
overline,
|
|
20
|
-
contentRef,
|
|
21
20
|
truncateLines,
|
|
22
21
|
type,
|
|
23
22
|
username,
|
|
@@ -43,7 +42,7 @@ export const InlineTip = ({
|
|
|
43
42
|
height: inheritDims ? 'inherit' : undefined,
|
|
44
43
|
ref: wrapperRef,
|
|
45
44
|
width: inheritDims ? 'inherit' : undefined,
|
|
46
|
-
onKeyDown: escapeKeyPressHandler,
|
|
45
|
+
onKeyDown: escapeKeyPressHandler ? e => escapeKeyPressHandler(e) : undefined,
|
|
47
46
|
children: children
|
|
48
47
|
});
|
|
49
48
|
const tipBody = /*#__PURE__*/_jsx(InlineTipBodyWrapper, {
|
|
@@ -56,9 +55,7 @@ export const InlineTip = ({
|
|
|
56
55
|
color: "currentColor",
|
|
57
56
|
horizNarrow: narrow && isHorizontalCenter,
|
|
58
57
|
id: id,
|
|
59
|
-
ref: contentRef,
|
|
60
58
|
role: type === 'tool' ? 'tooltip' : undefined,
|
|
61
|
-
tabIndex: type === 'info' ? -1 : undefined,
|
|
62
59
|
width: narrow && !isHorizontalCenter ? narrowWidth : 'max-content',
|
|
63
60
|
zIndex: "auto",
|
|
64
61
|
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
|
+
popoverContentRef?: React.RefObject<HTMLDivElement> | ((node: HTMLDivElement | null) => void);
|
|
50
50
|
type: 'info' | 'tool' | 'preview';
|
|
51
51
|
wrapperRef?: React.RefObject<HTMLDivElement>;
|
|
52
52
|
zIndex?: number;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecademy/gamut",
|
|
3
3
|
"description": "Styleguide & Component library for Codecademy",
|
|
4
|
-
"version": "67.6.1-alpha.
|
|
4
|
+
"version": "67.6.1-alpha.74920d.0",
|
|
5
5
|
"author": "Codecademy Engineering <dev@codecademy.com>",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@codecademy/gamut-icons": "9.53.0",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"dist/**/[A-Z]**/[A-Z]*.js",
|
|
57
57
|
"dist/**/[A-Z]**/index.js"
|
|
58
58
|
],
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "784e7c9064d61b233dbba456f90e5973546f5434"
|
|
60
60
|
}
|