@codecademy/gamut 67.6.1-alpha.1bf8a5.0 → 67.6.1-alpha.21416d.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 +176 -0
- package/dist/BarChart/GridLines.d.ts +5 -0
- package/dist/BarChart/GridLines.js +45 -0
- package/dist/BarChart/ScaleChartHeader.d.ts +10 -0
- package/dist/BarChart/ScaleChartHeader.js +55 -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 +70 -0
- package/dist/BarChart/utils/index.js +140 -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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BarProps, BarChartProps } from '../types';
|
|
2
|
+
export declare const numDigits: ({ num }: {
|
|
3
|
+
num: number;
|
|
4
|
+
}) => number;
|
|
5
|
+
export declare const columnBaseSize: ({ experience }: {
|
|
6
|
+
experience?: number | undefined;
|
|
7
|
+
}) => {
|
|
8
|
+
sm: number;
|
|
9
|
+
md: number;
|
|
10
|
+
lg: number;
|
|
11
|
+
xl: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const calculatePercent: ({ value, total }: {
|
|
14
|
+
value: number;
|
|
15
|
+
total: number;
|
|
16
|
+
}) => number;
|
|
17
|
+
export declare const calculateBarWidth: ({ value, maxRange, }: {
|
|
18
|
+
value: number;
|
|
19
|
+
maxRange: number;
|
|
20
|
+
}) => number;
|
|
21
|
+
export declare const calculateTicksAndRange: ({ maxTicks, minPoint, maxPoint, }: {
|
|
22
|
+
maxTicks: number;
|
|
23
|
+
minPoint: number;
|
|
24
|
+
maxPoint: number;
|
|
25
|
+
}) => [number, number, number];
|
|
26
|
+
/**
|
|
27
|
+
* Returns a "nice" number approximately equal to range
|
|
28
|
+
* Rounds the number if round = true
|
|
29
|
+
* Takes the ceiling if round = false.
|
|
30
|
+
* A nice number is a simple decimal number, for example if a number is 1234, a nice number would be 1000 or 2000.
|
|
31
|
+
*/
|
|
32
|
+
export declare const niceNum: ({ range, roundDown }: {
|
|
33
|
+
range: number;
|
|
34
|
+
roundDown: boolean;
|
|
35
|
+
}) => number;
|
|
36
|
+
export declare const getPercentDiff: ({ v1, v2 }: {
|
|
37
|
+
v1: number;
|
|
38
|
+
v2: number;
|
|
39
|
+
}) => number;
|
|
40
|
+
export declare const formatNumberUS: ({ num }: {
|
|
41
|
+
num: number;
|
|
42
|
+
}) => string;
|
|
43
|
+
export declare const formatNumberUSCompact: ({ num }: {
|
|
44
|
+
num: number;
|
|
45
|
+
}) => string;
|
|
46
|
+
/**
|
|
47
|
+
* Sort bars based on sortBy and order configuration
|
|
48
|
+
*/
|
|
49
|
+
export declare const sortBars: ({ bars, sortBy, order, }: {
|
|
50
|
+
bars: BarProps[];
|
|
51
|
+
sortBy: BarChartProps['sortBy'];
|
|
52
|
+
order: BarChartProps['order'];
|
|
53
|
+
}) => BarProps[];
|
|
54
|
+
/**
|
|
55
|
+
* Generates an accessible summary of the bar values
|
|
56
|
+
*/
|
|
57
|
+
export declare const getValuesSummary: ({ yLabel, seriesOneValue, seriesTwoValue, unit, }: {
|
|
58
|
+
yLabel: string;
|
|
59
|
+
seriesOneValue: number;
|
|
60
|
+
seriesTwoValue?: number | undefined;
|
|
61
|
+
unit: string;
|
|
62
|
+
}) => string;
|
|
63
|
+
/**
|
|
64
|
+
* Calculates the value for a given label position
|
|
65
|
+
*/
|
|
66
|
+
export declare const getLabel: ({ labelCount, labelIndex, max, }: {
|
|
67
|
+
labelCount: number;
|
|
68
|
+
labelIndex: number;
|
|
69
|
+
max: number;
|
|
70
|
+
}) => number;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export const numDigits = ({
|
|
2
|
+
num
|
|
3
|
+
}) => {
|
|
4
|
+
return Math.max(Math.floor(Math.log10(Math.abs(num))), 0) + 1;
|
|
5
|
+
};
|
|
6
|
+
export const columnBaseSize = ({
|
|
7
|
+
experience = 3
|
|
8
|
+
}) => {
|
|
9
|
+
const digits = numDigits({
|
|
10
|
+
num: experience
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
sm: digits > 4 ? 5 : 4,
|
|
14
|
+
md: digits > 4 ? 5 : 4,
|
|
15
|
+
lg: digits > 4 ? 4 : 5,
|
|
16
|
+
xl: digits > 4 ? 5 : 4
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export const calculatePercent = ({
|
|
20
|
+
value,
|
|
21
|
+
total
|
|
22
|
+
}) => {
|
|
23
|
+
return value / total * 100;
|
|
24
|
+
};
|
|
25
|
+
export const calculateBarWidth = ({
|
|
26
|
+
value,
|
|
27
|
+
maxRange
|
|
28
|
+
}) => {
|
|
29
|
+
return Math.floor(calculatePercent({
|
|
30
|
+
value,
|
|
31
|
+
total: maxRange
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Calculate tick spacing and nice minimum and maximum data points on the axis.
|
|
36
|
+
|
|
37
|
+
export const calculateTicksAndRange = ({
|
|
38
|
+
maxTicks,
|
|
39
|
+
minPoint,
|
|
40
|
+
maxPoint
|
|
41
|
+
}) => {
|
|
42
|
+
const range = niceNum({
|
|
43
|
+
range: maxPoint - minPoint,
|
|
44
|
+
roundDown: false
|
|
45
|
+
});
|
|
46
|
+
const tickSpacing = niceNum({
|
|
47
|
+
range: range / (maxTicks - 1),
|
|
48
|
+
roundDown: true
|
|
49
|
+
});
|
|
50
|
+
const niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
|
|
51
|
+
const niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
|
|
52
|
+
const tickCount = range / tickSpacing;
|
|
53
|
+
return [tickCount, niceMin, niceMax];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns a "nice" number approximately equal to range
|
|
58
|
+
* Rounds the number if round = true
|
|
59
|
+
* Takes the ceiling if round = false.
|
|
60
|
+
* A nice number is a simple decimal number, for example if a number is 1234, a nice number would be 1000 or 2000.
|
|
61
|
+
*/
|
|
62
|
+
export const niceNum = ({
|
|
63
|
+
range,
|
|
64
|
+
roundDown
|
|
65
|
+
}) => {
|
|
66
|
+
const exponent = Math.floor(Math.log10(range));
|
|
67
|
+
const fraction = range / 10 ** exponent;
|
|
68
|
+
let niceFraction;
|
|
69
|
+
if (roundDown) {
|
|
70
|
+
if (fraction < 1.5) niceFraction = 1;else if (fraction < 3) niceFraction = 2;else if (fraction < 7) niceFraction = 5;else niceFraction = 10;
|
|
71
|
+
} else if (fraction <= 1) niceFraction = 1;else if (fraction <= 2) niceFraction = 2;else if (fraction <= 5) niceFraction = 5;else niceFraction = 10;
|
|
72
|
+
return niceFraction * 10 ** exponent;
|
|
73
|
+
};
|
|
74
|
+
export const getPercentDiff = ({
|
|
75
|
+
v1,
|
|
76
|
+
v2
|
|
77
|
+
}) => {
|
|
78
|
+
return Math.abs(v1 - v2) / ((v1 + v2) / 2) * 100;
|
|
79
|
+
};
|
|
80
|
+
export const formatNumberUS = ({
|
|
81
|
+
num
|
|
82
|
+
}) => Intl.NumberFormat('en').format(num);
|
|
83
|
+
export const formatNumberUSCompact = ({
|
|
84
|
+
num
|
|
85
|
+
}) => Intl.NumberFormat('en', {
|
|
86
|
+
notation: 'compact',
|
|
87
|
+
compactDisplay: 'short'
|
|
88
|
+
}).format(num);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sort bars based on sortBy and order configuration
|
|
92
|
+
*/
|
|
93
|
+
export const sortBars = ({
|
|
94
|
+
bars,
|
|
95
|
+
sortBy,
|
|
96
|
+
order
|
|
97
|
+
}) => {
|
|
98
|
+
if (sortBy === 'none' || !sortBy) {
|
|
99
|
+
return bars;
|
|
100
|
+
}
|
|
101
|
+
const sorted = [...bars].sort((a, b) => {
|
|
102
|
+
if (sortBy === 'label') {
|
|
103
|
+
return a.yLabel.localeCompare(b.yLabel);
|
|
104
|
+
}
|
|
105
|
+
// sortBy === 'value' - use seriesTwoValue if available, otherwise seriesOneValue
|
|
106
|
+
const aValue = a.seriesTwoValue ?? a.seriesOneValue;
|
|
107
|
+
const bValue = b.seriesTwoValue ?? b.seriesOneValue;
|
|
108
|
+
return aValue - bValue;
|
|
109
|
+
});
|
|
110
|
+
return order === 'descending' ? sorted.reverse() : sorted;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generates an accessible summary of the bar values
|
|
115
|
+
*/
|
|
116
|
+
export const getValuesSummary = ({
|
|
117
|
+
yLabel,
|
|
118
|
+
seriesOneValue,
|
|
119
|
+
seriesTwoValue,
|
|
120
|
+
unit
|
|
121
|
+
}) => {
|
|
122
|
+
if (seriesTwoValue !== undefined) {
|
|
123
|
+
const gained = seriesOneValue;
|
|
124
|
+
return `${gained} ${unit} gained - now at ${seriesTwoValue} ${unit} in ${yLabel} category`;
|
|
125
|
+
}
|
|
126
|
+
return `${seriesOneValue} ${unit} in ${yLabel} category`;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Calculates the value for a given label position
|
|
131
|
+
*/
|
|
132
|
+
export const getLabel = ({
|
|
133
|
+
labelCount,
|
|
134
|
+
labelIndex,
|
|
135
|
+
max
|
|
136
|
+
}) => {
|
|
137
|
+
if (labelCount <= 1) return max;
|
|
138
|
+
const incrementalDecimal = labelIndex / (labelCount - 1);
|
|
139
|
+
return Math.floor(incrementalDecimal * max);
|
|
140
|
+
};
|
|
@@ -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,
|
|
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,
|
|
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 {};
|
package/dist/GridForm/types.d.ts
CHANGED
|
@@ -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":
|
|
21
|
-
|
|
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,25 +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
|
-
/**
|
|
14
|
-
* Accessible role description for the InfoTip button. Useful for translation.
|
|
15
|
-
* @default "More information button"
|
|
16
|
-
*/
|
|
17
|
-
ariaRoleDescription?: string;
|
|
18
5
|
emphasis?: 'low' | 'high';
|
|
19
|
-
/**
|
|
20
|
-
* When true, the InfoTip button will be labelled by the form field's label element.
|
|
21
|
-
* This is automatic for string labels, but can be opted into for ReactNode labels.
|
|
22
|
-
*/
|
|
23
|
-
labelledByFieldLabel?: boolean;
|
|
24
6
|
/**
|
|
25
7
|
* Called when the info tip is clicked - the onClick function is called after the DOM updates and the tip is mounted.
|
|
26
8
|
*/
|
|
@@ -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
|
};
|