@codecademy/gamut 67.6.1-alpha.1b85c1.0 → 67.6.1-alpha.7ad049.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 (35) hide show
  1. package/dist/BarChart/Bar/elements.d.ts +1392 -0
  2. package/dist/BarChart/Bar/elements.js +61 -0
  3. package/dist/BarChart/Bar/index.d.ts +1 -0
  4. package/dist/BarChart/Bar/index.js +2 -0
  5. package/dist/BarChart/BarChartProvider.d.ts +33 -0
  6. package/dist/BarChart/BarChartProvider.js +39 -0
  7. package/dist/BarChart/BarRow.d.ts +7 -0
  8. package/dist/BarChart/BarRow.js +213 -0
  9. package/dist/BarChart/GridLines.d.ts +6 -0
  10. package/dist/BarChart/GridLines.js +45 -0
  11. package/dist/BarChart/ScaleChartHeader.d.ts +17 -0
  12. package/dist/BarChart/ScaleChartHeader.js +58 -0
  13. package/dist/BarChart/index.d.ts +4 -0
  14. package/dist/BarChart/index.js +81 -0
  15. package/dist/BarChart/types.d.ts +56 -0
  16. package/dist/BarChart/types.js +1 -0
  17. package/dist/BarChart/utils/index.d.ts +23 -0
  18. package/dist/BarChart/utils/index.js +56 -0
  19. package/dist/ConnectedForm/ConnectedFormGroup.d.ts +0 -5
  20. package/dist/ConnectedForm/ConnectedFormGroup.js +1 -1
  21. package/dist/Form/elements/FormGroupLabel.js +2 -8
  22. package/dist/GridForm/GridFormInputGroup/__fixtures__/renderers.d.ts +0 -4
  23. package/dist/GridForm/types.d.ts +0 -5
  24. package/dist/Tip/InfoTip/InfoTipButton.js +2 -5
  25. package/dist/Tip/InfoTip/elements.d.ts +12 -0
  26. package/dist/Tip/InfoTip/elements.js +9 -0
  27. package/dist/Tip/InfoTip/index.d.ts +0 -13
  28. package/dist/Tip/InfoTip/index.js +66 -36
  29. package/dist/Tip/__tests__/helpers.d.ts +26 -5
  30. package/dist/Tip/shared/FloatingTip.js +3 -3
  31. package/dist/Tip/shared/InlineTip.js +1 -4
  32. package/dist/Tip/shared/types.d.ts +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +1 -0
  35. package/package.json +2 -2
@@ -0,0 +1,56 @@
1
+ import { ColorAlias } from '@codecademy/gamut-styles';
2
+ import { GamutIconProps } from '@codecademy/gamut-icons';
3
+ import { HTMLProps } from 'react';
4
+ import { ButtonProps } from '../Button';
5
+ type BarChartAriaLabel = {
6
+ 'aria-label': string;
7
+ 'aria-labelledby'?: never;
8
+ };
9
+ type BarChartAriaLabelledBy = {
10
+ 'aria-label'?: never;
11
+ 'aria-labelledby': string;
12
+ };
13
+ type BarChartLabel = BarChartAriaLabel | BarChartAriaLabelledBy;
14
+ export type BarChartStyles = {
15
+ /** Color for text labels. Defaults to 'text' */
16
+ textColor?: ColorAlias;
17
+ /** Color for the foreground/progress bar. Defaults to 'feedback-warning' */
18
+ foregroundBarColor?: ColorAlias;
19
+ /** Color for the background/total bar. Defaults to 'paleBlue' */
20
+ backgroundBarColor?: ColorAlias;
21
+ };
22
+ export type BarProps = {
23
+ /** Label displayed on the y-axis for this bar */
24
+ yLabel: string;
25
+ /** The foreground/progress bar value (always shown) */
26
+ seriesOneValue: number;
27
+ /** The background/total bar value (optional - creates stacked effect when provided) */
28
+ seriesTwoValue?: number;
29
+ /** Optional icon to display next to the label */
30
+ icon?: React.ComponentType<GamutIconProps>;
31
+ /** Click handler - makes row interactive as a button */
32
+ onClick?: ButtonProps['onClick'];
33
+ /** Link href - makes row interactive as an anchor */
34
+ href?: HTMLProps<HTMLAnchorElement>['href'];
35
+ };
36
+ export type BarChartProps = BarChartLabel & {
37
+ /** Whether to animate bars on mount */
38
+ animate?: boolean;
39
+ /** Array of bar data to render */
40
+ barValues: BarProps[];
41
+ /** Maximum value for the x-axis scale */
42
+ maxRange: number;
43
+ /** Minimum value for the x-axis scale (usually 0) */
44
+ minRange: number;
45
+ /** Sort order for bars */
46
+ order?: 'ascending' | 'descending';
47
+ /** Property to sort bars by */
48
+ sortBy?: 'label' | 'value' | 'none';
49
+ /** Unit label to display (e.g., "XP") */
50
+ unit?: string;
51
+ /** Style configuration for colors */
52
+ styleConfig?: BarChartStyles;
53
+ /** Interval for x-axis scale markers */
54
+ xScale?: number;
55
+ };
56
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ export declare const numDigits: (num: number) => number;
2
+ export declare const columnBaseSize: (experience?: number) => {
3
+ sm: number;
4
+ md: number;
5
+ lg: number;
6
+ xl: number;
7
+ };
8
+ export declare const calculatePercent: (value: number, total: number) => number;
9
+ export declare const calculateBarWidth: ({ value, maxRange, }: {
10
+ value: number;
11
+ maxRange: number;
12
+ }) => number;
13
+ export declare const calculateTicksAndRange: (maxTicks: number, minPoint: number, maxPoint: number) => [number, number, number];
14
+ /**
15
+ * Returns a "nice" number approximately equal to range
16
+ * Rounds the number if round = true
17
+ * Takes the ceiling if round = false.
18
+ * A nice number is a simple decimal number, for example if a number is 1234, a nice number would be 1000 or 2000.
19
+ */
20
+ export declare const niceNum: (range: number, roundDown: boolean) => number;
21
+ export declare const getPercentDiff: (v1: number, v2: number) => number;
22
+ export declare const formatNumberUS: (num: number) => string;
23
+ export declare const formatNumberUSCompact: (num: number) => string;
@@ -0,0 +1,56 @@
1
+ export const numDigits = num => {
2
+ return Math.max(Math.floor(Math.log10(Math.abs(num))), 0) + 1;
3
+ };
4
+ export const columnBaseSize = (experience = 3) => {
5
+ const digits = numDigits(experience);
6
+ return {
7
+ sm: digits > 4 ? 5 : 4,
8
+ md: digits > 4 ? 5 : 4,
9
+ lg: digits > 4 ? 4 : 5,
10
+ xl: digits > 4 ? 5 : 4
11
+ };
12
+ };
13
+ export const calculatePercent = (value, total) => {
14
+ return value / total * 100;
15
+ };
16
+ export const calculateBarWidth = ({
17
+ value,
18
+ maxRange
19
+ }) => {
20
+ return Math.floor(calculatePercent(value, maxRange));
21
+ };
22
+
23
+ // Calculate tick spacing and nice minimum and maximum data points on the axis.
24
+
25
+ export const calculateTicksAndRange = (maxTicks, minPoint, maxPoint) => {
26
+ const range = niceNum(maxPoint - minPoint, false);
27
+ const tickSpacing = niceNum(range / (maxTicks - 1), true);
28
+ const niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
29
+ const niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
30
+ const tickCount = range / tickSpacing;
31
+ return [tickCount, niceMin, niceMax];
32
+ };
33
+
34
+ /**
35
+ * Returns a "nice" number approximately equal to range
36
+ * Rounds the number if round = true
37
+ * Takes the ceiling if round = false.
38
+ * A nice number is a simple decimal number, for example if a number is 1234, a nice number would be 1000 or 2000.
39
+ */
40
+ export const niceNum = (range, roundDown) => {
41
+ const exponent = Math.floor(Math.log10(range));
42
+ const fraction = range / 10 ** exponent;
43
+ let niceFraction;
44
+ if (roundDown) {
45
+ if (fraction < 1.5) niceFraction = 1;else if (fraction < 3) niceFraction = 2;else if (fraction < 7) niceFraction = 5;else niceFraction = 10;
46
+ } else if (fraction <= 1) niceFraction = 1;else if (fraction <= 2) niceFraction = 2;else if (fraction <= 5) niceFraction = 5;else niceFraction = 10;
47
+ return niceFraction * 10 ** exponent;
48
+ };
49
+ export const getPercentDiff = (v1, v2) => {
50
+ return Math.abs(v1 - v2) / ((v1 + v2) / 2) * 100;
51
+ };
52
+ export const formatNumberUS = num => Intl.NumberFormat('en').format(num);
53
+ export const formatNumberUSCompact = num => Intl.NumberFormat('en', {
54
+ notation: 'compact',
55
+ compactDisplay: 'short'
56
+ }).format(num);
@@ -8,11 +8,6 @@ export interface ConnectedFormGroupBaseProps extends Omit<FormGroupProps, 'label
8
8
  name: string;
9
9
  label: React.ReactNode;
10
10
  required?: boolean;
11
- /**
12
- * InfoTip to display next to the field label. String labels automatically
13
- * label the InfoTip button. For ReactNode labels, provide `ariaLabel` or
14
- * set `labelledByFieldLabel: true` to ensure the InfoTip is accessible.
15
- */
16
11
  infotip?: InfoTipProps;
17
12
  }
18
13
  export interface ConnectedFormGroupProps<T extends ConnectedField> extends SubmitContextProps, ConnectedFormGroupBaseProps {
@@ -13,7 +13,7 @@ const ErrorAnchor = /*#__PURE__*/_styled(Anchor, {
13
13
  label: "ErrorAnchor"
14
14
  })(css({
15
15
  color: 'feedback-error'
16
- }), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9Db25uZWN0ZWRGb3JtL0Nvbm5lY3RlZEZvcm1Hcm91cC50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBa0JvQiIsImZpbGUiOiIuLi8uLi9zcmMvQ29ubmVjdGVkRm9ybS9Db25uZWN0ZWRGb3JtR3JvdXAudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQtc3R5bGVzJztcbmltcG9ydCBzdHlsZWQgZnJvbSAnQGVtb3Rpb24vc3R5bGVkJztcbmltcG9ydCB7IHVzZUVmZmVjdCB9IGZyb20gJ3JlYWN0JztcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0JztcblxuaW1wb3J0IHtcbiAgRm9ybUVycm9yLFxuICBGb3JtR3JvdXAsXG4gIEZvcm1Hcm91cExhYmVsLFxuICBGb3JtR3JvdXBQcm9wcyxcbiAgSW5mb1RpcFByb3BzLFxufSBmcm9tICcuLic7XG5pbXBvcnQgeyBBbmNob3IgfSBmcm9tICcuLi9BbmNob3InO1xuaW1wb3J0IHsgSGlkZGVuVGV4dCB9IGZyb20gJy4uL0hpZGRlblRleHQnO1xuaW1wb3J0IHsgTWFya2Rvd24gfSBmcm9tICcuLi9NYXJrZG93bic7XG5pbXBvcnQgeyBDb25uZWN0ZWRGaWVsZCwgRmllbGRQcm9wcywgU3VibWl0Q29udGV4dFByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQgeyBnZXRFcnJvck1lc3NhZ2UsIHVzZUZpZWxkIH0gZnJvbSAnLi91dGlscyc7XG5cbmNvbnN0IEVycm9yQW5jaG9yID0gc3R5bGVkKEFuY2hvcikoXG4gIGNzcyh7XG4gICAgY29sb3I6ICdmZWVkYmFjay1lcnJvcicsXG4gIH0pXG4pO1xuXG5leHBvcnQgaW50ZXJmYWNlIENvbm5lY3RlZEZvcm1Hcm91cEJhc2VQcm9wc1xuICBleHRlbmRzIE9taXQ8XG4gICAgRm9ybUdyb3VwUHJvcHMsXG4gICAgJ2xhYmVsJyB8ICdkaXNhYmxlZCcgfCAnZGVzY3JpcHRpb24nIHwgJ2h0bWxGb3InXG4gID4ge1xuICBjdXN0b21FcnJvcj86IHN0cmluZztcbiAgZXJyb3JUeXBlPzogJ2luaXRpYWwnIHwgJ2Fic29sdXRlJztcbiAgaGlkZUxhYmVsPzogYm9vbGVhbjtcbiAgbmFtZTogc3RyaW5nO1xuICBsYWJlbDogUmVhY3QuUmVhY3ROb2RlO1xuICByZXF1aXJlZD86IGJvb2xlYW47XG4gIC8qKlxuICAgKiBJbmZvVGlwIHRvIGRpc3BsYXkgbmV4dCB0byB0aGUgZmllbGQgbGFiZWwuIFN0cmluZyBsYWJlbHMgYXV0b21hdGljYWxseVxuICAgKiBsYWJlbCB0aGUgSW5mb1RpcCBidXR0b24uIEZvciBSZWFjdE5vZGUgbGFiZWxzLCBwcm92aWRlIGBhcmlhTGFiZWxgIG9yXG4gICAqIHNldCBgbGFiZWxsZWRCeUZpZWxkTGFiZWw6IHRydWVgIHRvIGVuc3VyZSB0aGUgSW5mb1RpcCBpcyBhY2Nlc3NpYmxlLlxuICAgKi9cbiAgaW5mb3RpcD86IEluZm9UaXBQcm9wcztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBDb25uZWN0ZWRGb3JtR3JvdXBQcm9wczxUIGV4dGVuZHMgQ29ubmVjdGVkRmllbGQ+XG4gIGV4dGVuZHMgU3VibWl0Q29udGV4dFByb3BzLFxuICAgIENvbm5lY3RlZEZvcm1Hcm91cEJhc2VQcm9wcyB7XG4gIC8qKlxuICAgKiBBbiBvYmplY3QgY29uc2lzdGluZyBvZiBhIGBjb21wb25lbnRgIGtleSB0byBzcGVjaWZ5IHdoYXQgQ29ubmVjdGVkRm9ybUlucHV0IHRvIHJlbmRlciAtIHRoZSByZW1haW5pbmcga2V5L3ZhbHVlIHBhaXJzIGFyZSB0aGF0IGNvbXBvbmVudHMgZGVzaXJlZCBwcm9wcy5cbiAgICovXG4gIGZpZWxkOiBPbWl0PFJlYWN0LkNvbXBvbmVudFByb3BzPFQ+LCAnbmFtZScgfCAnZGlzYWJsZWQnPiAmIEZpZWxkUHJvcHM8VD47XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBDb25uZWN0ZWRGb3JtR3JvdXA8VCBleHRlbmRzIENvbm5lY3RlZEZpZWxkPih7XG4gIGN1c3RvbUVycm9yLFxuICBjaGlsZHJlbixcbiAgZGlzYWJsZWQgPSBmYWxzZSxcbiAgZXJyb3JUeXBlID0gJ2Fic29sdXRlJyxcbiAgZmllbGQsXG4gIGhpZGVMYWJlbCxcbiAgaWQsXG4gIGxhYmVsLFxuICBuYW1lLFxuICBsYWJlbFNpemUsXG4gIHNwYWNpbmcgPSAnZml0JyxcbiAgaXNTb2xvRmllbGQsXG4gIGluZm90aXAsXG59OiBDb25uZWN0ZWRGb3JtR3JvdXBQcm9wczxUPikge1xuICBjb25zdCB7IGVycm9yLCBpc0ZpcnN0RXJyb3IsIGlzRGlzYWJsZWQsIHNldEVycm9yLCB2YWxpZGF0aW9uIH0gPSB1c2VGaWVsZCh7XG4gICAgbmFtZSxcbiAgICBkaXNhYmxlZCxcbiAgfSk7XG4gIGNvbnN0IHsgY29tcG9uZW50OiBDb21wb25lbnQsIC4uLnJlc3QgfSA9IGZpZWxkO1xuXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgaWYgKGN1c3RvbUVycm9yKSB7XG4gICAgICBzZXRFcnJvcihuYW1lLCB7XG4gICAgICAgIHR5cGU6ICdtYW51YWwnLFxuICAgICAgICBtZXNzYWdlOiBjdXN0b21FcnJvcixcbiAgICAgIH0pO1xuICAgIH1cbiAgfSwgW2N1c3RvbUVycm9yLCBuYW1lLCBzZXRFcnJvcl0pO1xuXG4gIGNvbnN0IHJlbmRlcmVkTGFiZWwgPSAoXG4gICAgPEZvcm1Hcm91cExhYmVsXG4gICAgICBkaXNhYmxlZD17aXNEaXNhYmxlZH1cbiAgICAgIGh0bWxGb3I9e2lkIHx8IG5hbWV9XG4gICAgICBpbmZvdGlwPXtpbmZvdGlwfVxuICAgICAgaXNTb2xvRmllbGQ9e2lzU29sb0ZpZWxkfVxuICAgICAgcmVxdWlyZWQ9eyEhdmFsaWRhdGlvbj8ucmVxdWlyZWR9XG4gICAgICBzaXplPXtsYWJlbFNpemV9XG4gICAgPlxuICAgICAge2xhYmVsfVxuICAgIDwvRm9ybUdyb3VwTGFiZWw+XG4gICk7XG5cbiAgY29uc3QgdGV4dEVycm9yID0gY3VzdG9tRXJyb3IgfHwgZ2V0RXJyb3JNZXNzYWdlKGVycm9yKTtcbiAgY29uc3Qgc2hvd0Vycm9yID0gISEodGV4dEVycm9yICYmICFoaWRlTGFiZWwpO1xuICBjb25zdCBlcnJvcklkID0gc2hvd0Vycm9yID8gYCR7aWQgfHwgbmFtZX1fZXJyb3JgIDogdW5kZWZpbmVkO1xuXG4gIHJldHVybiAoXG4gICAgPEZvcm1Hcm91cCBzcGFjaW5nPXtoaWRlTGFiZWwgPyAndGlnaHQnIDogc3BhY2luZ30+XG4gICAgICB7aGlkZUxhYmVsID8gPEhpZGRlblRleHQ+e3JlbmRlcmVkTGFiZWx9PC9IaWRkZW5UZXh0PiA6IHJlbmRlcmVkTGFiZWx9XG4gICAgICA8Q29tcG9uZW50XG4gICAgICAgIHsuLi4ocmVzdCBhcyBhbnkpfVxuICAgICAgICBhcmlhLWRlc2NyaWJlZGJ5PXtlcnJvcklkfVxuICAgICAgICBhcmlhLWludmFsaWQ9e3Nob3dFcnJvcn1cbiAgICAgICAgZGlzYWJsZWQ9e2Rpc2FibGVkfVxuICAgICAgICBuYW1lPXtuYW1lfVxuICAgICAgLz5cbiAgICAgIHtjaGlsZHJlbn1cbiAgICAgIHtzaG93RXJyb3IgJiYgKFxuICAgICAgICA8Rm9ybUVycm9yXG4gICAgICAgICAgYXJpYS1saXZlPXtpc0ZpcnN0RXJyb3IgPyAnYXNzZXJ0aXZlJyA6ICdvZmYnfVxuICAgICAgICAgIGlkPXtlcnJvcklkfVxuICAgICAgICAgIHJvbGU9e2lzRmlyc3RFcnJvciA/ICdhbGVydCcgOiAnc3RhdHVzJ31cbiAgICAgICAgICB2YXJpYW50PXtlcnJvclR5cGV9XG4gICAgICAgID5cbiAgICAgICAgICA8TWFya2Rvd25cbiAgICAgICAgICAgIGlubGluZVxuICAgICAgICAgICAgb3ZlcnJpZGVzPXt7XG4gICAgICAgICAgICAgIGE6IHtcbiAgICAgICAgICAgICAgICBhbGxvd2VkQXR0cmlidXRlczogWydocmVmJywgJ3RhcmdldCddLFxuICAgICAgICAgICAgICAgIGNvbXBvbmVudDogRXJyb3JBbmNob3IsXG4gICAgICAgICAgICAgICAgcHJvY2Vzc05vZGU6IChcbiAgICAgICAgICAgICAgICAgIG5vZGU6IHVua25vd24sXG4gICAgICAgICAgICAgICAgICBwcm9wczogeyBvbkNsaWNrPzogKCkgPT4gdm9pZCB9XG4gICAgICAgICAgICAgICAgKSA9PiA8RXJyb3JBbmNob3Igey4uLnByb3BzfSAvPixcbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIH19XG4gICAgICAgICAgICBza2lwRGVmYXVsdE92ZXJyaWRlcz17eyBhOiB0cnVlIH19XG4gICAgICAgICAgICBzcGFjaW5nPVwibm9uZVwiXG4gICAgICAgICAgICB0ZXh0PXt0ZXh0RXJyb3J9XG4gICAgICAgICAgLz5cbiAgICAgICAgPC9Gb3JtRXJyb3I+XG4gICAgICApfVxuICAgIDwvRm9ybUdyb3VwPlxuICApO1xufVxuIl19 */");
16
+ }), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9Db25uZWN0ZWRGb3JtL0Nvbm5lY3RlZEZvcm1Hcm91cC50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBa0JvQiIsImZpbGUiOiIuLi8uLi9zcmMvQ29ubmVjdGVkRm9ybS9Db25uZWN0ZWRGb3JtR3JvdXAudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3NzIH0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQtc3R5bGVzJztcbmltcG9ydCBzdHlsZWQgZnJvbSAnQGVtb3Rpb24vc3R5bGVkJztcbmltcG9ydCB7IHVzZUVmZmVjdCB9IGZyb20gJ3JlYWN0JztcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0JztcblxuaW1wb3J0IHtcbiAgRm9ybUVycm9yLFxuICBGb3JtR3JvdXAsXG4gIEZvcm1Hcm91cExhYmVsLFxuICBGb3JtR3JvdXBQcm9wcyxcbiAgSW5mb1RpcFByb3BzLFxufSBmcm9tICcuLic7XG5pbXBvcnQgeyBBbmNob3IgfSBmcm9tICcuLi9BbmNob3InO1xuaW1wb3J0IHsgSGlkZGVuVGV4dCB9IGZyb20gJy4uL0hpZGRlblRleHQnO1xuaW1wb3J0IHsgTWFya2Rvd24gfSBmcm9tICcuLi9NYXJrZG93bic7XG5pbXBvcnQgeyBDb25uZWN0ZWRGaWVsZCwgRmllbGRQcm9wcywgU3VibWl0Q29udGV4dFByb3BzIH0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQgeyBnZXRFcnJvck1lc3NhZ2UsIHVzZUZpZWxkIH0gZnJvbSAnLi91dGlscyc7XG5cbmNvbnN0IEVycm9yQW5jaG9yID0gc3R5bGVkKEFuY2hvcikoXG4gIGNzcyh7XG4gICAgY29sb3I6ICdmZWVkYmFjay1lcnJvcicsXG4gIH0pXG4pO1xuXG5leHBvcnQgaW50ZXJmYWNlIENvbm5lY3RlZEZvcm1Hcm91cEJhc2VQcm9wc1xuICBleHRlbmRzIE9taXQ8XG4gICAgRm9ybUdyb3VwUHJvcHMsXG4gICAgJ2xhYmVsJyB8ICdkaXNhYmxlZCcgfCAnZGVzY3JpcHRpb24nIHwgJ2h0bWxGb3InXG4gID4ge1xuICBjdXN0b21FcnJvcj86IHN0cmluZztcbiAgZXJyb3JUeXBlPzogJ2luaXRpYWwnIHwgJ2Fic29sdXRlJztcbiAgaGlkZUxhYmVsPzogYm9vbGVhbjtcbiAgbmFtZTogc3RyaW5nO1xuICBsYWJlbDogUmVhY3QuUmVhY3ROb2RlO1xuICByZXF1aXJlZD86IGJvb2xlYW47XG4gIGluZm90aXA/OiBJbmZvVGlwUHJvcHM7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ29ubmVjdGVkRm9ybUdyb3VwUHJvcHM8VCBleHRlbmRzIENvbm5lY3RlZEZpZWxkPlxuICBleHRlbmRzIFN1Ym1pdENvbnRleHRQcm9wcyxcbiAgICBDb25uZWN0ZWRGb3JtR3JvdXBCYXNlUHJvcHMge1xuICAvKipcbiAgICogQW4gb2JqZWN0IGNvbnNpc3Rpbmcgb2YgYSBgY29tcG9uZW50YCBrZXkgdG8gc3BlY2lmeSB3aGF0IENvbm5lY3RlZEZvcm1JbnB1dCB0byByZW5kZXIgLSB0aGUgcmVtYWluaW5nIGtleS92YWx1ZSBwYWlycyBhcmUgdGhhdCBjb21wb25lbnRzIGRlc2lyZWQgcHJvcHMuXG4gICAqL1xuICBmaWVsZDogT21pdDxSZWFjdC5Db21wb25lbnRQcm9wczxUPiwgJ25hbWUnIHwgJ2Rpc2FibGVkJz4gJiBGaWVsZFByb3BzPFQ+O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gQ29ubmVjdGVkRm9ybUdyb3VwPFQgZXh0ZW5kcyBDb25uZWN0ZWRGaWVsZD4oe1xuICBjdXN0b21FcnJvcixcbiAgY2hpbGRyZW4sXG4gIGRpc2FibGVkID0gZmFsc2UsXG4gIGVycm9yVHlwZSA9ICdhYnNvbHV0ZScsXG4gIGZpZWxkLFxuICBoaWRlTGFiZWwsXG4gIGlkLFxuICBsYWJlbCxcbiAgbmFtZSxcbiAgbGFiZWxTaXplLFxuICBzcGFjaW5nID0gJ2ZpdCcsXG4gIGlzU29sb0ZpZWxkLFxuICBpbmZvdGlwLFxufTogQ29ubmVjdGVkRm9ybUdyb3VwUHJvcHM8VD4pIHtcbiAgY29uc3QgeyBlcnJvciwgaXNGaXJzdEVycm9yLCBpc0Rpc2FibGVkLCBzZXRFcnJvciwgdmFsaWRhdGlvbiB9ID0gdXNlRmllbGQoe1xuICAgIG5hbWUsXG4gICAgZGlzYWJsZWQsXG4gIH0pO1xuICBjb25zdCB7IGNvbXBvbmVudDogQ29tcG9uZW50LCAuLi5yZXN0IH0gPSBmaWVsZDtcblxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmIChjdXN0b21FcnJvcikge1xuICAgICAgc2V0RXJyb3IobmFtZSwge1xuICAgICAgICB0eXBlOiAnbWFudWFsJyxcbiAgICAgICAgbWVzc2FnZTogY3VzdG9tRXJyb3IsXG4gICAgICB9KTtcbiAgICB9XG4gIH0sIFtjdXN0b21FcnJvciwgbmFtZSwgc2V0RXJyb3JdKTtcblxuICBjb25zdCByZW5kZXJlZExhYmVsID0gKFxuICAgIDxGb3JtR3JvdXBMYWJlbFxuICAgICAgZGlzYWJsZWQ9e2lzRGlzYWJsZWR9XG4gICAgICBodG1sRm9yPXtpZCB8fCBuYW1lfVxuICAgICAgaW5mb3RpcD17aW5mb3RpcH1cbiAgICAgIGlzU29sb0ZpZWxkPXtpc1NvbG9GaWVsZH1cbiAgICAgIHJlcXVpcmVkPXshIXZhbGlkYXRpb24/LnJlcXVpcmVkfVxuICAgICAgc2l6ZT17bGFiZWxTaXplfVxuICAgID5cbiAgICAgIHtsYWJlbH1cbiAgICA8L0Zvcm1Hcm91cExhYmVsPlxuICApO1xuXG4gIGNvbnN0IHRleHRFcnJvciA9IGN1c3RvbUVycm9yIHx8IGdldEVycm9yTWVzc2FnZShlcnJvcik7XG4gIGNvbnN0IHNob3dFcnJvciA9ICEhKHRleHRFcnJvciAmJiAhaGlkZUxhYmVsKTtcbiAgY29uc3QgZXJyb3JJZCA9IHNob3dFcnJvciA/IGAke2lkIHx8IG5hbWV9X2Vycm9yYCA6IHVuZGVmaW5lZDtcblxuICByZXR1cm4gKFxuICAgIDxGb3JtR3JvdXAgc3BhY2luZz17aGlkZUxhYmVsID8gJ3RpZ2h0JyA6IHNwYWNpbmd9PlxuICAgICAge2hpZGVMYWJlbCA/IDxIaWRkZW5UZXh0PntyZW5kZXJlZExhYmVsfTwvSGlkZGVuVGV4dD4gOiByZW5kZXJlZExhYmVsfVxuICAgICAgPENvbXBvbmVudFxuICAgICAgICB7Li4uKHJlc3QgYXMgYW55KX1cbiAgICAgICAgYXJpYS1kZXNjcmliZWRieT17ZXJyb3JJZH1cbiAgICAgICAgYXJpYS1pbnZhbGlkPXtzaG93RXJyb3J9XG4gICAgICAgIGRpc2FibGVkPXtkaXNhYmxlZH1cbiAgICAgICAgbmFtZT17bmFtZX1cbiAgICAgIC8+XG4gICAgICB7Y2hpbGRyZW59XG4gICAgICB7c2hvd0Vycm9yICYmIChcbiAgICAgICAgPEZvcm1FcnJvclxuICAgICAgICAgIGFyaWEtbGl2ZT17aXNGaXJzdEVycm9yID8gJ2Fzc2VydGl2ZScgOiAnb2ZmJ31cbiAgICAgICAgICBpZD17ZXJyb3JJZH1cbiAgICAgICAgICByb2xlPXtpc0ZpcnN0RXJyb3IgPyAnYWxlcnQnIDogJ3N0YXR1cyd9XG4gICAgICAgICAgdmFyaWFudD17ZXJyb3JUeXBlfVxuICAgICAgICA+XG4gICAgICAgICAgPE1hcmtkb3duXG4gICAgICAgICAgICBpbmxpbmVcbiAgICAgICAgICAgIG92ZXJyaWRlcz17e1xuICAgICAgICAgICAgICBhOiB7XG4gICAgICAgICAgICAgICAgYWxsb3dlZEF0dHJpYnV0ZXM6IFsnaHJlZicsICd0YXJnZXQnXSxcbiAgICAgICAgICAgICAgICBjb21wb25lbnQ6IEVycm9yQW5jaG9yLFxuICAgICAgICAgICAgICAgIHByb2Nlc3NOb2RlOiAoXG4gICAgICAgICAgICAgICAgICBub2RlOiB1bmtub3duLFxuICAgICAgICAgICAgICAgICAgcHJvcHM6IHsgb25DbGljaz86ICgpID0+IHZvaWQgfVxuICAgICAgICAgICAgICAgICkgPT4gPEVycm9yQW5jaG9yIHsuLi5wcm9wc30gLz4sXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB9fVxuICAgICAgICAgICAgc2tpcERlZmF1bHRPdmVycmlkZXM9e3sgYTogdHJ1ZSB9fVxuICAgICAgICAgICAgc3BhY2luZz1cIm5vbmVcIlxuICAgICAgICAgICAgdGV4dD17dGV4dEVycm9yfVxuICAgICAgICAgIC8+XG4gICAgICAgIDwvRm9ybUVycm9yPlxuICAgICAgKX1cbiAgICA8L0Zvcm1Hcm91cD5cbiAgKTtcbn1cbiJdfQ== */");
17
17
  export function ConnectedFormGroup({
18
18
  customError,
19
19
  children,
@@ -1,6 +1,5 @@
1
1
  import _styled from "@emotion/styled/base";
2
2
  import { states, variant } from '@codecademy/gamut-styles';
3
- import { useId } from 'react';
4
3
  import * as React from 'react';
5
4
  import { FlexBox } from '../..';
6
5
  import { InfoTip } from '../../Tip/InfoTip';
@@ -31,7 +30,7 @@ const labelStates = states({
31
30
  const Label = /*#__PURE__*/_styled("label", {
32
31
  target: "e1t0n89n0",
33
32
  label: "Label"
34
- })(labelSizeVariants, labelStates, process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9Gb3JtL2VsZW1lbnRzL0Zvcm1Hcm91cExhYmVsLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFtRGMiLCJmaWxlIjoiLi4vLi4vLi4vc3JjL0Zvcm0vZWxlbWVudHMvRm9ybUdyb3VwTGFiZWwudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc3RhdGVzLCB2YXJpYW50IH0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQtc3R5bGVzJztcbmltcG9ydCB7IFN0eWxlUHJvcHMgfSBmcm9tICdAY29kZWNhZGVteS92YXJpYW5jZSc7XG5pbXBvcnQgc3R5bGVkIGZyb20gJ0BlbW90aW9uL3N0eWxlZCc7XG5pbXBvcnQgeyBIVE1MQXR0cmlidXRlcywgdXNlSWQgfSBmcm9tICdyZWFjdCc7XG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCc7XG5cbmltcG9ydCB7IEZsZXhCb3ggfSBmcm9tICcuLi8uLic7XG5pbXBvcnQgeyBJbmZvVGlwLCBJbmZvVGlwUHJvcHMgfSBmcm9tICcuLi8uLi9UaXAvSW5mb1RpcCc7XG5pbXBvcnQgeyBUZXh0IH0gZnJvbSAnLi4vLi4vVHlwb2dyYXBoeS9UZXh0JztcbmltcG9ydCB7IGZvcm1CYXNlU3R5bGVzLCBmb3JtRmllbGRUZXh0RGlzYWJsZWRTdHlsZXMgfSBmcm9tICcuLi9zdHlsZXMnO1xuaW1wb3J0IHsgQmFzZUlucHV0UHJvcHMgfSBmcm9tICcuLi90eXBlcyc7XG5cbmNvbnN0IGxhYmVsU2l6ZVZhcmlhbnRzID0gdmFyaWFudCh7XG4gIGRlZmF1bHRWYXJpYW50OiAnc21hbGwnLFxuICBwcm9wOiAnc2l6ZScsXG4gIGJhc2U6IHsgZGlzcGxheTogJ2Jsb2NrJywgLi4uZm9ybUJhc2VTdHlsZXMgfSxcbiAgdmFyaWFudHM6IHtcbiAgICBzbWFsbDoge1xuICAgICAgbGluZUhlaWdodDogJ2Jhc2UnLFxuICAgIH0sXG4gICAgbGFyZ2U6IHtcbiAgICAgIGZvbnRTaXplOiAyMixcbiAgICAgIGxpbmVIZWlnaHQ6ICdiYXNlJyxcbiAgICAgIGZvbnRXZWlnaHQ6ICd0aXRsZScsXG4gICAgfSxcbiAgfSxcbn0pO1xuXG5jb25zdCBsYWJlbFN0YXRlcyA9IHN0YXRlcyh7XG4gIGRpc2FibGVkOiBmb3JtRmllbGRUZXh0RGlzYWJsZWRTdHlsZXMsXG59KTtcblxuZXhwb3J0IGludGVyZmFjZSBMYWJlbFZhcmlhbnRzXG4gIGV4dGVuZHMgU3R5bGVQcm9wczx0eXBlb2YgbGFiZWxTaXplVmFyaWFudHM+LFxuICAgIFN0eWxlUHJvcHM8dHlwZW9mIGxhYmVsU3RhdGVzPiB7fVxuXG5leHBvcnQgdHlwZSBGb3JtR3JvdXBMYWJlbFByb3BzID0gSFRNTEF0dHJpYnV0ZXM8SFRNTERpdkVsZW1lbnQ+ICZcbiAgSFRNTEF0dHJpYnV0ZXM8SFRNTExhYmVsRWxlbWVudD4gJlxuICBMYWJlbFZhcmlhbnRzICZcbiAgUGljazxCYXNlSW5wdXRQcm9wcywgJ2h0bWxGb3InIHwgJ3JlcXVpcmVkJz4gJiB7XG4gICAgLyoqXG4gICAgICogW1RoZSBmb3IvaWQgc3RyaW5nIG9mIGEgbGFiZWwgb3IgbGFiZWxhYmxlIGZvcm0tcmVsYXRlZCBlbGVtZW50XShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvSFRNTExhYmVsRWxlbWVudC9odG1sRm9yKS4gVGhlIG91dGVyIEZvcm1Hcm91cCBvciBGb3JtTGFiZWwgc2hvdWxkIGhhdmUgYW4gaWRlbnRpY2FsIHN0cmluZyBhcyB0aGUgaW5uZXIgRm9ybUVsZW1lbnQgZm9yIGFjY2Vzc2liaWxpdHkgcHVycG9zZXMuXG4gICAgICovXG4gICAgaW5mb3RpcD86IEluZm9UaXBQcm9wcztcbiAgICBzaXplPzogJ3NtYWxsJyB8ICdsYXJnZSc7XG4gICAgLyoqXG4gICAgICogU29sbyBmaWVsZHMgc2hvdWxkIGFsd2F5cyBiZSByZXF1aXJlZCBhbmQgaGF2ZSBubyBvcHRpb25hbC9yZXF1aXJlZCB0ZXh0XG4gICAgICovXG4gICAgaXNTb2xvRmllbGQ/OiBib29sZWFuO1xuICB9O1xuXG5jb25zdCBMYWJlbCA9IHN0eWxlZC5sYWJlbDxGb3JtR3JvdXBMYWJlbFByb3BzPihsYWJlbFNpemVWYXJpYW50cywgbGFiZWxTdGF0ZXMpO1xuXG5leHBvcnQgY29uc3QgRm9ybUdyb3VwTGFiZWw6IFJlYWN0LkZDPEZvcm1Hcm91cExhYmVsUHJvcHM+ID0gKHtcbiAgY2hpbGRyZW4sXG4gIGNsYXNzTmFtZSxcbiAgZGlzYWJsZWQsXG4gIGh0bWxGb3IsXG4gIGluZm90aXAsXG4gIGlzU29sb0ZpZWxkLFxuICByZXF1aXJlZCxcbiAgc2l6ZSxcbiAgLi4ucmVzdFxufSkgPT4ge1xuICBjb25zdCBsYWJlbElkID0gdXNlSWQoKTtcbiAgY29uc3QgaXNTdHJpbmdMYWJlbCA9IHR5cGVvZiBjaGlsZHJlbiA9PT0gJ3N0cmluZyc7XG4gIGNvbnN0IHNob3VsZExhYmVsSW5mb1RpcCA9XG4gICAgKGlzU3RyaW5nTGFiZWwgfHwgaW5mb3RpcD8ubGFiZWxsZWRCeUZpZWxkTGFiZWwpICYmXG4gICAgIWluZm90aXA/LmFyaWFMYWJlbCAmJlxuICAgICFpbmZvdGlwPy5hcmlhTGFiZWxsZWRieTtcblxuICByZXR1cm4gKFxuICAgIDxGbGV4Qm94IGp1c3RpZnlDb250ZW50PVwic3BhY2UtYmV0d2VlblwiIG1iPXs0fT5cbiAgICAgIDxMYWJlbFxuICAgICAgICB7Li4ucmVzdH1cbiAgICAgICAgYXM9e2h0bWxGb3IgPyAnbGFiZWwnIDogJ2Rpdid9XG4gICAgICAgIGNsYXNzTmFtZT17Y2xhc3NOYW1lfVxuICAgICAgICBkaXNhYmxlZD17ZGlzYWJsZWR9XG4gICAgICAgIGh0bWxGb3I9e2h0bWxGb3J9XG4gICAgICAgIGlkPXtpbmZvdGlwICYmIHNob3VsZExhYmVsSW5mb1RpcCA/IGxhYmVsSWQgOiB1bmRlZmluZWR9XG4gICAgICAgIHNpemU9e3NpemV9XG4gICAgICA+XG4gICAgICAgIHtjaGlsZHJlbn1cbiAgICAgICAgeyFpc1NvbG9GaWVsZCAmJlxuICAgICAgICAgIChyZXF1aXJlZCA/IChcbiAgICAgICAgICAgIDxUZXh0IGFyaWEtaGlkZGVuIGFzPVwic3BhblwiPlxuICAgICAgICAgICAgICAqXG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgKSA6IChcbiAgICAgICAgICAgICdcXHUwMEEwKG9wdGlvbmFsKSdcbiAgICAgICAgICApKX1cbiAgICAgIDwvTGFiZWw+XG4gICAgICB7aW5mb3RpcCAmJiAoXG4gICAgICAgIDxJbmZvVGlwXG4gICAgICAgICAgey4uLmluZm90aXB9XG4gICAgICAgICAgYXJpYUxhYmVsbGVkYnk9e3Nob3VsZExhYmVsSW5mb1RpcCA/IGxhYmVsSWQgOiBpbmZvdGlwLmFyaWFMYWJlbGxlZGJ5fVxuICAgICAgICAvPlxuICAgICAgKX1cbiAgICA8L0ZsZXhCb3g+XG4gICk7XG59O1xuIl19 */");
33
+ })(labelSizeVariants, labelStates, process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9Gb3JtL2VsZW1lbnRzL0Zvcm1Hcm91cExhYmVsLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFtRGMiLCJmaWxlIjoiLi4vLi4vLi4vc3JjL0Zvcm0vZWxlbWVudHMvRm9ybUdyb3VwTGFiZWwudHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgc3RhdGVzLCB2YXJpYW50IH0gZnJvbSAnQGNvZGVjYWRlbXkvZ2FtdXQtc3R5bGVzJztcbmltcG9ydCB7IFN0eWxlUHJvcHMgfSBmcm9tICdAY29kZWNhZGVteS92YXJpYW5jZSc7XG5pbXBvcnQgc3R5bGVkIGZyb20gJ0BlbW90aW9uL3N0eWxlZCc7XG5pbXBvcnQgeyBIVE1MQXR0cmlidXRlcyB9IGZyb20gJ3JlYWN0JztcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0JztcblxuaW1wb3J0IHsgRmxleEJveCB9IGZyb20gJy4uLy4uJztcbmltcG9ydCB7IEluZm9UaXAsIEluZm9UaXBQcm9wcyB9IGZyb20gJy4uLy4uL1RpcC9JbmZvVGlwJztcbmltcG9ydCB7IFRleHQgfSBmcm9tICcuLi8uLi9UeXBvZ3JhcGh5L1RleHQnO1xuaW1wb3J0IHsgZm9ybUJhc2VTdHlsZXMsIGZvcm1GaWVsZFRleHREaXNhYmxlZFN0eWxlcyB9IGZyb20gJy4uL3N0eWxlcyc7XG5pbXBvcnQgeyBCYXNlSW5wdXRQcm9wcyB9IGZyb20gJy4uL3R5cGVzJztcblxuY29uc3QgbGFiZWxTaXplVmFyaWFudHMgPSB2YXJpYW50KHtcbiAgZGVmYXVsdFZhcmlhbnQ6ICdzbWFsbCcsXG4gIHByb3A6ICdzaXplJyxcbiAgYmFzZTogeyBkaXNwbGF5OiAnYmxvY2snLCAuLi5mb3JtQmFzZVN0eWxlcyB9LFxuICB2YXJpYW50czoge1xuICAgIHNtYWxsOiB7XG4gICAgICBsaW5lSGVpZ2h0OiAnYmFzZScsXG4gICAgfSxcbiAgICBsYXJnZToge1xuICAgICAgZm9udFNpemU6IDIyLFxuICAgICAgbGluZUhlaWdodDogJ2Jhc2UnLFxuICAgICAgZm9udFdlaWdodDogJ3RpdGxlJyxcbiAgICB9LFxuICB9LFxufSk7XG5cbmNvbnN0IGxhYmVsU3RhdGVzID0gc3RhdGVzKHtcbiAgZGlzYWJsZWQ6IGZvcm1GaWVsZFRleHREaXNhYmxlZFN0eWxlcyxcbn0pO1xuXG5leHBvcnQgaW50ZXJmYWNlIExhYmVsVmFyaWFudHNcbiAgZXh0ZW5kcyBTdHlsZVByb3BzPHR5cGVvZiBsYWJlbFNpemVWYXJpYW50cz4sXG4gICAgU3R5bGVQcm9wczx0eXBlb2YgbGFiZWxTdGF0ZXM+IHt9XG5cbmV4cG9ydCB0eXBlIEZvcm1Hcm91cExhYmVsUHJvcHMgPSBIVE1MQXR0cmlidXRlczxIVE1MRGl2RWxlbWVudD4gJlxuICBIVE1MQXR0cmlidXRlczxIVE1MTGFiZWxFbGVtZW50PiAmXG4gIExhYmVsVmFyaWFudHMgJlxuICBQaWNrPEJhc2VJbnB1dFByb3BzLCAnaHRtbEZvcicgfCAncmVxdWlyZWQnPiAmIHtcbiAgICAvKipcbiAgICAgKiBbVGhlIGZvci9pZCBzdHJpbmcgb2YgYSBsYWJlbCBvciBsYWJlbGFibGUgZm9ybS1yZWxhdGVkIGVsZW1lbnRdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9IVE1MTGFiZWxFbGVtZW50L2h0bWxGb3IpLiBUaGUgb3V0ZXIgRm9ybUdyb3VwIG9yIEZvcm1MYWJlbCBzaG91bGQgaGF2ZSBhbiBpZGVudGljYWwgc3RyaW5nIGFzIHRoZSBpbm5lciBGb3JtRWxlbWVudCBmb3IgYWNjZXNzaWJpbGl0eSBwdXJwb3Nlcy5cbiAgICAgKi9cbiAgICBpbmZvdGlwPzogSW5mb1RpcFByb3BzO1xuICAgIHNpemU/OiAnc21hbGwnIHwgJ2xhcmdlJztcbiAgICAvKipcbiAgICAgKiBTb2xvIGZpZWxkcyBzaG91bGQgYWx3YXlzIGJlIHJlcXVpcmVkIGFuZCBoYXZlIG5vIG9wdGlvbmFsL3JlcXVpcmVkIHRleHRcbiAgICAgKi9cbiAgICBpc1NvbG9GaWVsZD86IGJvb2xlYW47XG4gIH07XG5cbmNvbnN0IExhYmVsID0gc3R5bGVkLmxhYmVsPEZvcm1Hcm91cExhYmVsUHJvcHM+KGxhYmVsU2l6ZVZhcmlhbnRzLCBsYWJlbFN0YXRlcyk7XG5cbmV4cG9ydCBjb25zdCBGb3JtR3JvdXBMYWJlbDogUmVhY3QuRkM8Rm9ybUdyb3VwTGFiZWxQcm9wcz4gPSAoe1xuICBjaGlsZHJlbixcbiAgY2xhc3NOYW1lLFxuICBkaXNhYmxlZCxcbiAgaHRtbEZvcixcbiAgaW5mb3RpcCxcbiAgaXNTb2xvRmllbGQsXG4gIHJlcXVpcmVkLFxuICBzaXplLFxuICAuLi5yZXN0XG59KSA9PiB7XG4gIHJldHVybiAoXG4gICAgPEZsZXhCb3gganVzdGlmeUNvbnRlbnQ9XCJzcGFjZS1iZXR3ZWVuXCIgbWI9ezR9PlxuICAgICAgPExhYmVsXG4gICAgICAgIHsuLi5yZXN0fVxuICAgICAgICBhcz17aHRtbEZvciA/ICdsYWJlbCcgOiAnZGl2J31cbiAgICAgICAgY2xhc3NOYW1lPXtjbGFzc05hbWV9XG4gICAgICAgIGRpc2FibGVkPXtkaXNhYmxlZH1cbiAgICAgICAgaHRtbEZvcj17aHRtbEZvcn1cbiAgICAgICAgc2l6ZT17c2l6ZX1cbiAgICAgID5cbiAgICAgICAge2NoaWxkcmVufVxuICAgICAgICB7IWlzU29sb0ZpZWxkICYmXG4gICAgICAgICAgKHJlcXVpcmVkID8gKFxuICAgICAgICAgICAgPFRleHQgYXJpYS1oaWRkZW4gYXM9XCJzcGFuXCI+XG4gICAgICAgICAgICAgICpcbiAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICApIDogKFxuICAgICAgICAgICAgJ1xcdTAwQTAob3B0aW9uYWwpJ1xuICAgICAgICAgICkpfVxuICAgICAgPC9MYWJlbD5cbiAgICAgIHtpbmZvdGlwICYmIDxJbmZvVGlwIHsuLi5pbmZvdGlwfSAvPn1cbiAgICA8L0ZsZXhCb3g+XG4gICk7XG59O1xuIl19 */");
35
34
  export const FormGroupLabel = ({
36
35
  children,
37
36
  className,
@@ -43,9 +42,6 @@ export const FormGroupLabel = ({
43
42
  size,
44
43
  ...rest
45
44
  }) => {
46
- const labelId = useId();
47
- const isStringLabel = typeof children === 'string';
48
- const shouldLabelInfoTip = (isStringLabel || infotip?.labelledByFieldLabel) && !infotip?.ariaLabel && !infotip?.ariaLabelledby;
49
45
  return /*#__PURE__*/_jsxs(FlexBox, {
50
46
  justifyContent: "space-between",
51
47
  mb: 4,
@@ -55,7 +51,6 @@ export const FormGroupLabel = ({
55
51
  className: className,
56
52
  disabled: disabled,
57
53
  htmlFor: htmlFor,
58
- id: infotip && shouldLabelInfoTip ? labelId : undefined,
59
54
  size: size,
60
55
  children: [children, !isSoloField && (required ? /*#__PURE__*/_jsx(Text, {
61
56
  "aria-hidden": true,
@@ -63,8 +58,7 @@ export const FormGroupLabel = ({
63
58
  children: "*"
64
59
  }) : '\u00A0(optional)')]
65
60
  }), infotip && /*#__PURE__*/_jsx(InfoTip, {
66
- ...infotip,
67
- ariaLabelledby: shouldLabelInfoTip ? labelId : infotip.ariaLabelledby
61
+ ...infotip
68
62
  })]
69
63
  });
70
64
  };
@@ -21,10 +21,6 @@ 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
- };
28
24
  };
29
25
  export declare const GridFormInputGroupTestComponent: React.FC<GridFormInputGroupTestComponentProps>;
30
26
  export {};
@@ -25,11 +25,6 @@ export type BaseFormField<Value> = {
25
25
  * HTML id to use instead of the name.
26
26
  */
27
27
  id?: string;
28
- /**
29
- * InfoTip to display next to the field label. String labels automatically
30
- * label the InfoTip button. For ReactNode labels, provide `ariaLabel` or
31
- * set `labelledByFieldLabel: true` to ensure the InfoTip is accessible.
32
- */
33
28
  infotip?: InfoTipProps;
34
29
  isSoloField?: boolean;
35
30
  name: string;
@@ -8,17 +8,14 @@ export const InfoTipButton = /*#__PURE__*/forwardRef(({
8
8
  active,
9
9
  children,
10
10
  emphasis,
11
- 'aria-label': ariaLabel,
12
- 'aria-labelledby': ariaLabelledby,
13
11
  ...props
14
12
  }, ref) => {
15
13
  const Icon = emphasis === 'high' ? MiniInfoCircleIcon : MiniInfoOutlineIcon;
16
14
  return /*#__PURE__*/_jsxs(InfoTipButtonBase, {
17
- ...props,
18
15
  active: active,
19
16
  "aria-expanded": active,
20
- "aria-label": ariaLabel,
21
- "aria-labelledby": ariaLabelledby,
17
+ "aria-label": "Show information",
18
+ ...props,
22
19
  ref: ref,
23
20
  children: [Icon && /*#__PURE__*/_jsx(Icon, {
24
21
  "aria-hidden": true,
@@ -0,0 +1,12 @@
1
+ /// <reference types="react" />
2
+ export declare const ScreenreaderNavigableText: import("@emotion/styled").StyledComponent<(((Omit<{
3
+ theme?: import("@emotion/react").Theme | undefined;
4
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
5
+ } & import("../..").TextTruncateProps & Pick<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "slot" | "style" | "title" | "dir" | "children" | "className" | "aria-hidden" | "onAnimationStart" | "onDragStart" | "onDragEnd" | "onDrag" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "autoFocus" | "contentEditable" | "contextMenu" | "draggable" | "hidden" | "id" | "lang" | "nonce" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "role" | "about" | "content" | "datatype" | "inlist" | "prefix" | "property" | "rel" | "resource" | "rev" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onResize" | "onResizeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDragCapture" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | keyof import("react").ClassAttributes<HTMLSpanElement>>, "ref"> | Omit<{
6
+ theme?: import("@emotion/react").Theme | undefined;
7
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
8
+ } & import("../..").TextNoTruncateProps & Pick<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "slot" | "style" | "title" | "dir" | "children" | "className" | "aria-hidden" | "onAnimationStart" | "onDragStart" | "onDragEnd" | "onDrag" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "autoFocus" | "contentEditable" | "contextMenu" | "draggable" | "hidden" | "id" | "lang" | "nonce" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "role" | "about" | "content" | "datatype" | "inlist" | "prefix" | "property" | "rel" | "resource" | "rev" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onResize" | "onResizeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDragCapture" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | keyof import("react").ClassAttributes<HTMLSpanElement>>, "ref">) & import("react").RefAttributes<HTMLSpanElement>) & {
9
+ theme?: import("@emotion/react").Theme | undefined;
10
+ }) & {
11
+ theme?: import("@emotion/react").Theme | undefined;
12
+ }, {}, {}>;
@@ -0,0 +1,9 @@
1
+ import _styled from "@emotion/styled/base";
2
+ import { css } from '@codecademy/gamut-styles';
3
+ import { Text } from '../../Typography';
4
+ export const ScreenreaderNavigableText = /*#__PURE__*/_styled(Text, {
5
+ target: "e1rvjfdo0",
6
+ label: "ScreenreaderNavigableText"
7
+ })(css({
8
+ position: 'absolute'
9
+ }), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9UaXAvSW5mb1RpcC9lbGVtZW50cy50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBS3lDIiwiZmlsZSI6Ii4uLy4uLy4uL3NyYy9UaXAvSW5mb1RpcC9lbGVtZW50cy50c3giLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBjc3MgfSBmcm9tICdAY29kZWNhZGVteS9nYW11dC1zdHlsZXMnO1xuaW1wb3J0IHN0eWxlZCBmcm9tICdAZW1vdGlvbi9zdHlsZWQnO1xuXG5pbXBvcnQgeyBUZXh0IH0gZnJvbSAnLi4vLi4vVHlwb2dyYXBoeSc7XG5cbmV4cG9ydCBjb25zdCBTY3JlZW5yZWFkZXJOYXZpZ2FibGVUZXh0ID0gc3R5bGVkKFRleHQpKFxuICBjc3MoeyBwb3NpdGlvbjogJ2Fic29sdXRlJyB9KVxuKTtcbiJdfQ== */");
@@ -2,20 +2,7 @@
2
2
  import { TipBaseAlignment, TipBaseProps } from '../shared/types';
3
3
  export type InfoTipProps = TipBaseProps & {
4
4
  alignment?: TipBaseAlignment;
5
- /**
6
- * Accessible label for the InfoTip button. Its recommended to provide either `ariaLabel` or `ariaLabelledby`.
7
- */
8
- ariaLabel?: string;
9
- /**
10
- * ID of an element that labels the InfoTip button. Its recommended to provide either `ariaLabel` or `ariaLabelledby`.
11
- */
12
- ariaLabelledby?: string;
13
5
  emphasis?: 'low' | 'high';
14
- /**
15
- * When true, the InfoTip button will be labelled by the form field's label element.
16
- * This is automatic for string labels, but can be opted into for ReactNode labels.
17
- */
18
- labelledByFieldLabel?: boolean;
19
6
  /**
20
7
  * Called when the info tip is clicked - the onClick function is called after the DOM updates and the tip is mounted.
21
8
  */
@@ -1,16 +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
17
  emphasis = 'low',
15
18
  info,
16
19
  onClick,
@@ -19,14 +22,25 @@ export const InfoTip = ({
19
22
  }) => {
20
23
  const isFloating = placement === 'floating';
21
24
  const [isTipHidden, setHideTip] = useState(true);
25
+ const [isAriaHidden, setIsAriaHidden] = useState(false);
26
+ const [shouldAnnounce, setShouldAnnounce] = useState(false);
22
27
  const [loaded, setLoaded] = useState(false);
23
28
  const wrapperRef = useRef(null);
24
29
  const buttonRef = useRef(null);
25
30
  const popoverContentNodeRef = useRef(null);
26
31
  const isInitialMount = useRef(true);
32
+ const ariaHiddenTimeoutRef = useRef(null);
33
+ const announceTimeoutRef = useRef(null);
27
34
  const getFocusableElements = useCallback(() => {
28
35
  return getFocusableElementsUtil(popoverContentNodeRef.current);
29
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
+ }, []);
30
44
  const popoverContentRef = useCallback(node => {
31
45
  popoverContentNodeRef.current = node;
32
46
  if (node && !isTipHidden && isFloating) {
@@ -37,10 +51,24 @@ export const InfoTip = ({
37
51
  }, [onClick, isTipHidden, isFloating]);
38
52
  useEffect(() => {
39
53
  setLoaded(true);
54
+ const ariaHiddenTimeout = ariaHiddenTimeoutRef.current;
55
+ const announceTimeout = announceTimeoutRef.current;
56
+ return () => {
57
+ clearTimeout(ariaHiddenTimeout ?? undefined);
58
+ clearTimeout(announceTimeout ?? undefined);
59
+ };
40
60
  }, []);
41
61
  const setTipIsHidden = useCallback(nextTipState => {
42
62
  setHideTip(nextTipState);
43
- }, []);
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]);
44
72
  const handleOutsideClick = useCallback(e => {
45
73
  const wrapper = wrapperRef.current;
46
74
  const isOutside = wrapper && (!(e.target instanceof HTMLElement) || !wrapper.contains(e.target));
@@ -51,7 +79,10 @@ export const InfoTip = ({
51
79
  const clickHandler = useCallback(() => {
52
80
  const currentTipState = !isTipHidden;
53
81
  setTipIsHidden(currentTipState);
54
- }, [isTipHidden, setTipIsHidden]);
82
+ if (!currentTipState) {
83
+ clearAndSetTimeout(announceTimeoutRef, () => setShouldAnnounce(true), 0);
84
+ }
85
+ }, [isTipHidden, setTipIsHidden, clearAndSetTimeout]);
55
86
  useLayoutEffect(() => {
56
87
  if (isInitialMount.current) {
57
88
  isInitialMount.current = false;
@@ -89,18 +120,7 @@ export const InfoTip = ({
89
120
  const handleTabKeyInPopover = event => {
90
121
  if (event.key !== 'Tab' || event.shiftKey) return;
91
122
  const focusableElements = getFocusableElements();
92
- const {
93
- activeElement
94
- } = document;
95
-
96
- // If no focusable elements and popover itself has focus, wrap to button
97
- if (focusableElements.length === 0) {
98
- if (activeElement === popoverContentNodeRef.current) {
99
- event.preventDefault();
100
- buttonRef.current?.focus();
101
- }
102
- return;
103
- }
123
+ if (focusableElements.length === 0) return;
104
124
  const lastElement = focusableElements[focusableElements.length - 1];
105
125
 
106
126
  // Only wrap forward: if on last element, wrap to button
@@ -121,35 +141,45 @@ export const InfoTip = ({
121
141
  };
122
142
  }
123
143
  return () => document.removeEventListener('keydown', handleGlobalEscapeKey, true);
124
- }, [isTipHidden, isFloating, getFocusableElements, setTipIsHidden]);
125
- useEffect(() => {
126
- if (isTipHidden) return;
127
- const timeoutId = setTimeout(() => {
128
- popoverContentNodeRef.current?.focus();
129
- }, 0);
130
- return () => clearTimeout(timeoutId);
131
- }, [isTipHidden]);
144
+ }, [isTipHidden, isFloating, setTipIsHidden, getFocusableElements]);
132
145
  const Tip = loaded && isFloating ? FloatingTip : InlineTip;
133
146
  const tipProps = useMemo(() => ({
134
147
  alignment,
135
148
  info,
136
149
  isTipHidden,
137
- contentRef: popoverContentRef,
138
150
  wrapperRef,
151
+ ...(isFloating && {
152
+ popoverContentRef
153
+ }),
139
154
  ...rest
140
- }), [alignment, info, isTipHidden, popoverContentRef, wrapperRef, rest]);
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
+ */
141
176
  return /*#__PURE__*/_jsx(Tip, {
142
177
  ...tipProps,
143
178
  type: "info",
144
- children: /*#__PURE__*/_jsx(InfoTipButton, {
145
- active: !isTipHidden,
146
- "aria-expanded": !isTipHidden,
147
- "aria-label": ariaLabel,
148
- "aria-labelledby": ariaLabelledby,
149
- "aria-roledescription": "More information button",
150
- emphasis: emphasis,
151
- ref: buttonRef,
152
- onClick: clickHandler
179
+ children: isFloating && alignment.includes('top') ? /*#__PURE__*/_jsxs(_Fragment, {
180
+ children: [screenreaderText, button]
181
+ }) : /*#__PURE__*/_jsxs(_Fragment, {
182
+ children: [button, screenreaderText]
153
183
  })
154
184
  });
155
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: TipPlacements;
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 testFocusWrap: ({ view, direction, }: ViewParam & {
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 testTabFromPopoverWithNoInteractiveElements: (view: InfoTipView) => Promise<void>;
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
- contentRef,
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: contentRef,
137
+ popoverContainerRef: popoverContentRef,
138
138
  skipFocusTrap: true,
139
139
  targetRef: ref,
140
140
  variant: "secondary",