@khanacademy/math-input 4.3.0 → 5.0.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/CHANGELOG.md +25 -0
- package/dist/components/input/math-wrapper.d.ts +2 -2
- package/dist/components/input/math-wrapper.js.flow +2 -4
- package/dist/components/input/mathquill-helpers.d.ts +1 -1
- package/dist/components/input/mathquill-helpers.js.flow +2 -2
- package/dist/components/input/mathquill-types.d.ts +270 -10
- package/dist/components/input/mathquill-types.js.flow +312 -10
- package/dist/components/keypad/index.d.ts +11 -1
- package/dist/components/keypad/index.js.flow +14 -1
- package/dist/components/keypad/shared-keys.d.ts +4 -0
- package/dist/components/keypad/shared-keys.js.flow +4 -0
- package/dist/components/tabbar/tabbar.d.ts +1 -0
- package/dist/components/tabbar/tabbar.js.flow +1 -0
- package/dist/components/tabbar/types.d.ts +1 -1
- package/dist/components/tabbar/types.js.flow +6 -1
- package/dist/es/index.js +607 -409
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +607 -409
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/input/__tests__/mathquill-helpers.test.ts +105 -0
- package/src/components/input/math-input.tsx +1 -1
- package/src/components/input/math-wrapper.ts +6 -10
- package/src/components/input/mathquill-helpers.ts +8 -1
- package/src/components/input/mathquill-types.ts +308 -40
- package/src/components/key-handlers/__tests__/handle-jump-out.test.ts +94 -0
- package/src/components/key-handlers/handle-jump-out.ts +3 -2
- package/src/components/keypad/__tests__/keypad-v2-mathquill.test.tsx +42 -39
- package/src/components/keypad/__tests__/keypad.test.tsx +42 -0
- package/src/components/keypad/button-assets.tsx +540 -316
- package/src/components/keypad/index.tsx +25 -2
- package/src/components/keypad/keypad-mathquill.stories.tsx +20 -1
- package/src/components/keypad/keypad-pages/extras-page.tsx +1 -1
- package/src/components/keypad/keypad-pages/numbers-page.tsx +25 -16
- package/src/components/keypad/keypad.stories.tsx +11 -0
- package/src/components/keypad/shared-keys.tsx +56 -8
- package/src/components/tabbar/__tests__/tabbar.test.tsx +54 -14
- package/src/components/tabbar/icons.tsx +48 -8
- package/src/components/tabbar/item.tsx +2 -0
- package/src/components/tabbar/tabbar.tsx +32 -12
- package/src/components/tabbar/types.ts +6 -1
- package/tsconfig-build.json +3 -1
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -5,6 +5,7 @@ import * as React from "react";
|
|
|
5
5
|
|
|
6
6
|
import Key from "../../data/keys";
|
|
7
7
|
import {ClickKeyCallback} from "../../types";
|
|
8
|
+
import {CursorContext} from "../input/cursor-contexts";
|
|
8
9
|
import Tabbar from "../tabbar/tabbar";
|
|
9
10
|
import {TabbarItemType} from "../tabbar/types";
|
|
10
11
|
|
|
@@ -14,8 +15,11 @@ import NumbersPage from "./keypad-pages/numbers-page";
|
|
|
14
15
|
import OperatorsPage from "./keypad-pages/operators-page";
|
|
15
16
|
import SharedKeys from "./shared-keys";
|
|
16
17
|
|
|
18
|
+
import type {SendEventFn} from "@khanacademy/perseus-core";
|
|
19
|
+
|
|
17
20
|
export type Props = {
|
|
18
21
|
onClickKey: ClickKeyCallback;
|
|
22
|
+
cursorContext?: CursorContext;
|
|
19
23
|
trigonometry?: boolean;
|
|
20
24
|
extraKeys: ReadonlyArray<Key>;
|
|
21
25
|
multiplicationDot?: boolean;
|
|
@@ -24,6 +28,12 @@ export type Props = {
|
|
|
24
28
|
logarithms?: boolean;
|
|
25
29
|
basicRelations?: boolean;
|
|
26
30
|
advancedRelations?: boolean;
|
|
31
|
+
|
|
32
|
+
sendEvent: SendEventFn;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const defaultProps = {
|
|
36
|
+
extraKeys: [],
|
|
27
37
|
};
|
|
28
38
|
|
|
29
39
|
function allPages(props: Props): ReadonlyArray<TabbarItemType> {
|
|
@@ -50,6 +60,8 @@ function allPages(props: Props): ReadonlyArray<TabbarItemType> {
|
|
|
50
60
|
return pages;
|
|
51
61
|
}
|
|
52
62
|
|
|
63
|
+
// The main (v2) Keypad. Use this component to present an accessible, onscreen
|
|
64
|
+
// keypad to learners for entering math expressions.
|
|
53
65
|
export default function Keypad(props: Props) {
|
|
54
66
|
const [selectedPage, setSelectedPage] =
|
|
55
67
|
React.useState<TabbarItemType>("Numbers");
|
|
@@ -58,7 +70,8 @@ export default function Keypad(props: Props) {
|
|
|
58
70
|
|
|
59
71
|
const {
|
|
60
72
|
onClickKey,
|
|
61
|
-
|
|
73
|
+
cursorContext,
|
|
74
|
+
extraKeys,
|
|
62
75
|
multiplicationDot,
|
|
63
76
|
divisionKey,
|
|
64
77
|
preAlgebra,
|
|
@@ -76,9 +89,15 @@ export default function Keypad(props: Props) {
|
|
|
76
89
|
setSelectedPage(tabbarItem);
|
|
77
90
|
}}
|
|
78
91
|
style={styles.tabbar}
|
|
92
|
+
onClickClose={() => onClickKey("DISMISS")}
|
|
79
93
|
/>
|
|
80
94
|
|
|
81
|
-
<View
|
|
95
|
+
<View
|
|
96
|
+
style={styles.grid}
|
|
97
|
+
role="grid"
|
|
98
|
+
tabIndex={0}
|
|
99
|
+
aria-label="Keypad"
|
|
100
|
+
>
|
|
82
101
|
{selectedPage === "Numbers" && (
|
|
83
102
|
<NumbersPage onClickKey={onClickKey} />
|
|
84
103
|
)}
|
|
@@ -99,14 +118,18 @@ export default function Keypad(props: Props) {
|
|
|
99
118
|
)}
|
|
100
119
|
<SharedKeys
|
|
101
120
|
onClickKey={onClickKey}
|
|
121
|
+
cursorContext={cursorContext}
|
|
102
122
|
multiplicationDot={multiplicationDot}
|
|
103
123
|
divisionKey={divisionKey}
|
|
124
|
+
selectedPage={selectedPage}
|
|
104
125
|
/>
|
|
105
126
|
</View>
|
|
106
127
|
</View>
|
|
107
128
|
);
|
|
108
129
|
}
|
|
109
130
|
|
|
131
|
+
Keypad.defaultProps = defaultProps;
|
|
132
|
+
|
|
110
133
|
const styles = StyleSheet.create({
|
|
111
134
|
tabbar: {
|
|
112
135
|
background: Color.white,
|
|
@@ -3,6 +3,8 @@ import {Popover, PopoverContentCore} from "@khanacademy/wonder-blocks-popover";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
import Key from "../../data/keys";
|
|
6
|
+
import {CursorContext} from "../input/cursor-contexts";
|
|
7
|
+
import {getCursorContext} from "../input/mathquill-helpers";
|
|
6
8
|
import {createMathField} from "../input/mathquill-instance";
|
|
7
9
|
import {MathFieldInterface} from "../input/mathquill-types";
|
|
8
10
|
import keyTranslator from "../key-handlers/key-translator";
|
|
@@ -16,11 +18,22 @@ export default {
|
|
|
16
18
|
export function V2KeypadWithMathquill() {
|
|
17
19
|
const mathFieldWrapperRef = React.useRef<HTMLDivElement>(null);
|
|
18
20
|
const [mathField, setMathField] = React.useState<MathFieldInterface>();
|
|
21
|
+
const [cursorContext, setCursorContext] = React.useState<CursorContext>(
|
|
22
|
+
CursorContext.NONE,
|
|
23
|
+
);
|
|
19
24
|
|
|
20
25
|
React.useEffect(() => {
|
|
21
26
|
if (!mathField && mathFieldWrapperRef.current) {
|
|
22
27
|
const mathFieldInstance = createMathField(
|
|
23
28
|
mathFieldWrapperRef.current,
|
|
29
|
+
(baseConfig) => ({
|
|
30
|
+
...baseConfig,
|
|
31
|
+
handlers: {
|
|
32
|
+
edit: (_mathField) => {
|
|
33
|
+
setCursorContext(getCursorContext(_mathField));
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
24
37
|
);
|
|
25
38
|
setMathField(mathFieldInstance);
|
|
26
39
|
}
|
|
@@ -34,6 +47,7 @@ export function V2KeypadWithMathquill() {
|
|
|
34
47
|
const mathFieldCallback = keyTranslator[key];
|
|
35
48
|
if (mathFieldCallback) {
|
|
36
49
|
mathFieldCallback(mathField, key);
|
|
50
|
+
setCursorContext(getCursorContext(mathField));
|
|
37
51
|
} else {
|
|
38
52
|
// eslint-disable-next-line no-console
|
|
39
53
|
console.warn(`No translation to Mathquill for: ${key}`);
|
|
@@ -51,8 +65,9 @@ export function V2KeypadWithMathquill() {
|
|
|
51
65
|
}}
|
|
52
66
|
>
|
|
53
67
|
<Keypad
|
|
54
|
-
extraKeys={["
|
|
68
|
+
extraKeys={["x", "y", "PI", "THETA"]}
|
|
55
69
|
onClickKey={handleClickKey}
|
|
70
|
+
cursorContext={cursorContext}
|
|
56
71
|
advancedRelations
|
|
57
72
|
basicRelations
|
|
58
73
|
divisionKey
|
|
@@ -60,6 +75,10 @@ export function V2KeypadWithMathquill() {
|
|
|
60
75
|
multiplicationDot
|
|
61
76
|
preAlgebra
|
|
62
77
|
trigonometry
|
|
78
|
+
sendEvent={async (event) => {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.log("Send Event:", event);
|
|
81
|
+
}}
|
|
63
82
|
/>
|
|
64
83
|
</PopoverContentCore>
|
|
65
84
|
}
|
|
@@ -10,26 +10,29 @@ type Props = {
|
|
|
10
10
|
|
|
11
11
|
export default function NumbersPage(props: Props) {
|
|
12
12
|
const {onClickKey} = props;
|
|
13
|
+
// These keys are arranged sequentially so that tabbing follows numerical order. This
|
|
14
|
+
// allows us to visually mimic a keypad without affecting a11y. The visual order of the
|
|
15
|
+
// keys in the keypad is determined by their coordinates, not their order in the DOM.
|
|
13
16
|
return (
|
|
14
17
|
<>
|
|
15
|
-
{/* Row
|
|
18
|
+
{/* Row 4 */}
|
|
16
19
|
<KeypadButton
|
|
17
|
-
keyConfig={Keys.
|
|
20
|
+
keyConfig={Keys.NUM_1}
|
|
18
21
|
onClickKey={onClickKey}
|
|
19
|
-
coord={[0,
|
|
22
|
+
coord={[0, 2]}
|
|
20
23
|
/>
|
|
21
24
|
<KeypadButton
|
|
22
|
-
keyConfig={Keys.
|
|
25
|
+
keyConfig={Keys.NUM_2}
|
|
23
26
|
onClickKey={onClickKey}
|
|
24
|
-
coord={[1,
|
|
27
|
+
coord={[1, 2]}
|
|
25
28
|
/>
|
|
26
29
|
<KeypadButton
|
|
27
|
-
keyConfig={Keys.
|
|
30
|
+
keyConfig={Keys.NUM_3}
|
|
28
31
|
onClickKey={onClickKey}
|
|
29
|
-
coord={[2,
|
|
32
|
+
coord={[2, 2]}
|
|
30
33
|
/>
|
|
31
34
|
|
|
32
|
-
{/* Row
|
|
35
|
+
{/* Row 3 */}
|
|
33
36
|
<KeypadButton
|
|
34
37
|
keyConfig={Keys.NUM_4}
|
|
35
38
|
onClickKey={onClickKey}
|
|
@@ -46,24 +49,24 @@ export default function NumbersPage(props: Props) {
|
|
|
46
49
|
coord={[2, 1]}
|
|
47
50
|
/>
|
|
48
51
|
|
|
49
|
-
{/* Row
|
|
52
|
+
{/* Row 2 */}
|
|
50
53
|
<KeypadButton
|
|
51
|
-
keyConfig={Keys.
|
|
54
|
+
keyConfig={Keys.NUM_7}
|
|
52
55
|
onClickKey={onClickKey}
|
|
53
|
-
coord={[0,
|
|
56
|
+
coord={[0, 0]}
|
|
54
57
|
/>
|
|
55
58
|
<KeypadButton
|
|
56
|
-
keyConfig={Keys.
|
|
59
|
+
keyConfig={Keys.NUM_8}
|
|
57
60
|
onClickKey={onClickKey}
|
|
58
|
-
coord={[1,
|
|
61
|
+
coord={[1, 0]}
|
|
59
62
|
/>
|
|
60
63
|
<KeypadButton
|
|
61
|
-
keyConfig={Keys.
|
|
64
|
+
keyConfig={Keys.NUM_9}
|
|
62
65
|
onClickKey={onClickKey}
|
|
63
|
-
coord={[2,
|
|
66
|
+
coord={[2, 0]}
|
|
64
67
|
/>
|
|
65
68
|
|
|
66
|
-
{/* Row
|
|
69
|
+
{/* Row 1 */}
|
|
67
70
|
<KeypadButton
|
|
68
71
|
keyConfig={Keys.NUM_0}
|
|
69
72
|
onClickKey={onClickKey}
|
|
@@ -79,6 +82,12 @@ export default function NumbersPage(props: Props) {
|
|
|
79
82
|
onClickKey={onClickKey}
|
|
80
83
|
coord={[2, 3]}
|
|
81
84
|
/>
|
|
85
|
+
<KeypadButton
|
|
86
|
+
keyConfig={Keys.PERCENT}
|
|
87
|
+
onClickKey={onClickKey}
|
|
88
|
+
coord={[3, 0]}
|
|
89
|
+
secondary
|
|
90
|
+
/>
|
|
82
91
|
</>
|
|
83
92
|
);
|
|
84
93
|
}
|
|
@@ -89,3 +89,14 @@ Trigonometry.args = {
|
|
|
89
89
|
preAlgebra: true,
|
|
90
90
|
trigonometry: true,
|
|
91
91
|
};
|
|
92
|
+
|
|
93
|
+
export const Everything = Template.bind({});
|
|
94
|
+
Everything.args = {
|
|
95
|
+
advancedRelations: true,
|
|
96
|
+
basicRelations: true,
|
|
97
|
+
divisionKey: true,
|
|
98
|
+
logarithms: true,
|
|
99
|
+
multiplicationDot: false,
|
|
100
|
+
preAlgebra: true,
|
|
101
|
+
trigonometry: true,
|
|
102
|
+
};
|
|
@@ -2,21 +2,61 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
import Keys from "../../data/key-configs";
|
|
4
4
|
import {ClickKeyCallback} from "../../types";
|
|
5
|
+
import {CursorContext} from "../input/cursor-contexts";
|
|
6
|
+
import {TabbarItemType} from "../tabbar/types";
|
|
5
7
|
|
|
6
8
|
import {KeypadButton} from "./keypad-button";
|
|
7
9
|
|
|
8
10
|
type Props = {
|
|
9
11
|
onClickKey: ClickKeyCallback;
|
|
12
|
+
selectedPage: TabbarItemType;
|
|
13
|
+
cursorContext?: CursorContext;
|
|
10
14
|
multiplicationDot?: boolean;
|
|
11
15
|
divisionKey?: boolean;
|
|
12
16
|
};
|
|
13
17
|
|
|
18
|
+
function getCursorContextConfig(cursorContext?: CursorContext) {
|
|
19
|
+
if (!cursorContext) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
switch (cursorContext) {
|
|
24
|
+
case CursorContext.NONE:
|
|
25
|
+
return null;
|
|
26
|
+
case CursorContext.IN_PARENS:
|
|
27
|
+
return Keys.JUMP_OUT_PARENTHESES;
|
|
28
|
+
case CursorContext.IN_SUPER_SCRIPT:
|
|
29
|
+
return Keys.JUMP_OUT_EXPONENT;
|
|
30
|
+
case CursorContext.IN_SUB_SCRIPT:
|
|
31
|
+
return Keys.JUMP_OUT_BASE;
|
|
32
|
+
case CursorContext.IN_NUMERATOR:
|
|
33
|
+
return Keys.JUMP_OUT_NUMERATOR;
|
|
34
|
+
case CursorContext.IN_DENOMINATOR:
|
|
35
|
+
return Keys.JUMP_OUT_DENOMINATOR;
|
|
36
|
+
case CursorContext.BEFORE_FRACTION:
|
|
37
|
+
return Keys.JUMP_INTO_NUMERATOR;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
export default function SharedKeys(props: Props) {
|
|
15
|
-
const {
|
|
42
|
+
const {
|
|
43
|
+
onClickKey,
|
|
44
|
+
cursorContext,
|
|
45
|
+
divisionKey,
|
|
46
|
+
multiplicationDot,
|
|
47
|
+
selectedPage,
|
|
48
|
+
} = props;
|
|
49
|
+
|
|
50
|
+
const cursorKeyConfig = getCursorContextConfig(cursorContext);
|
|
51
|
+
|
|
52
|
+
// Fraction position depends on the page
|
|
53
|
+
const fractionCoord: readonly [number, number] =
|
|
54
|
+
selectedPage === "Numbers" || selectedPage === "Operators"
|
|
55
|
+
? [3, 1]
|
|
56
|
+
: [3, 0];
|
|
16
57
|
|
|
17
58
|
return (
|
|
18
59
|
<>
|
|
19
|
-
{/* Row 1 */}
|
|
20
60
|
<KeypadButton
|
|
21
61
|
keyConfig={Keys.PLUS}
|
|
22
62
|
onClickKey={onClickKey}
|
|
@@ -29,6 +69,12 @@ export default function SharedKeys(props: Props) {
|
|
|
29
69
|
coord={[5, 0]}
|
|
30
70
|
secondary
|
|
31
71
|
/>
|
|
72
|
+
<KeypadButton
|
|
73
|
+
keyConfig={Keys.FRAC_INCLUSIVE}
|
|
74
|
+
onClickKey={onClickKey}
|
|
75
|
+
coord={fractionCoord}
|
|
76
|
+
secondary
|
|
77
|
+
/>
|
|
32
78
|
|
|
33
79
|
{/* Row 2 */}
|
|
34
80
|
<KeypadButton
|
|
@@ -61,12 +107,14 @@ export default function SharedKeys(props: Props) {
|
|
|
61
107
|
/>
|
|
62
108
|
|
|
63
109
|
{/* Row 4 */}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
110
|
+
{cursorKeyConfig && (
|
|
111
|
+
<KeypadButton
|
|
112
|
+
keyConfig={cursorKeyConfig}
|
|
113
|
+
onClickKey={onClickKey}
|
|
114
|
+
coord={[4, 3]}
|
|
115
|
+
secondary
|
|
116
|
+
/>
|
|
117
|
+
)}
|
|
70
118
|
<KeypadButton
|
|
71
119
|
keyConfig={Keys.BACKSPACE}
|
|
72
120
|
onClickKey={onClickKey}
|
|
@@ -17,9 +17,7 @@ describe("<Tabbar />", () => {
|
|
|
17
17
|
);
|
|
18
18
|
|
|
19
19
|
// Assert
|
|
20
|
-
expect(
|
|
21
|
-
screen.getByRole("button", {name: "Numbers"}),
|
|
22
|
-
).toBeInTheDocument();
|
|
20
|
+
expect(screen.getByRole("tab", {name: "Numbers"})).toBeInTheDocument();
|
|
23
21
|
});
|
|
24
22
|
|
|
25
23
|
it("renders many tabs", () => {
|
|
@@ -33,17 +31,11 @@ describe("<Tabbar />", () => {
|
|
|
33
31
|
);
|
|
34
32
|
|
|
35
33
|
// Assert
|
|
34
|
+
expect(screen.getByRole("tab", {name: "Numbers"})).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByRole("tab", {name: "Extras"})).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByRole("tab", {name: "Geometry"})).toBeInTheDocument();
|
|
36
37
|
expect(
|
|
37
|
-
screen.getByRole("
|
|
38
|
-
).toBeInTheDocument();
|
|
39
|
-
expect(
|
|
40
|
-
screen.getByRole("button", {name: "Extras"}),
|
|
41
|
-
).toBeInTheDocument();
|
|
42
|
-
expect(
|
|
43
|
-
screen.getByRole("button", {name: "Geometry"}),
|
|
44
|
-
).toBeInTheDocument();
|
|
45
|
-
expect(
|
|
46
|
-
screen.getByRole("button", {name: "Operators"}),
|
|
38
|
+
screen.getByRole("tab", {name: "Operators"}),
|
|
47
39
|
).toBeInTheDocument();
|
|
48
40
|
});
|
|
49
41
|
|
|
@@ -59,7 +51,55 @@ describe("<Tabbar />", () => {
|
|
|
59
51
|
);
|
|
60
52
|
|
|
61
53
|
// Assert
|
|
62
|
-
userEvent.click(screen.getByRole("
|
|
54
|
+
userEvent.click(screen.getByRole("tab", {name: "Geometry"}));
|
|
63
55
|
expect(mockSelectCallback).toHaveBeenCalledWith("Geometry");
|
|
64
56
|
});
|
|
57
|
+
|
|
58
|
+
it("shows dismiss button with a onClickClose callback", () => {
|
|
59
|
+
// Arrange
|
|
60
|
+
render(
|
|
61
|
+
<Tabbar
|
|
62
|
+
items={["Numbers"]}
|
|
63
|
+
selectedItem={"Numbers"}
|
|
64
|
+
onSelectItem={() => {}}
|
|
65
|
+
onClickClose={() => {}}
|
|
66
|
+
/>,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Assert
|
|
70
|
+
expect(screen.getByRole("tab", {name: "Dismiss"})).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("does not show dismiss button without onClickClose callback", () => {
|
|
74
|
+
// Arrange
|
|
75
|
+
render(
|
|
76
|
+
<Tabbar
|
|
77
|
+
items={["Numbers"]}
|
|
78
|
+
selectedItem={"Numbers"}
|
|
79
|
+
onSelectItem={() => {}}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Assert
|
|
84
|
+
expect(
|
|
85
|
+
screen.queryByRole("tab", {name: "Dismiss"}),
|
|
86
|
+
).not.toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("handles onClickClose callback", () => {
|
|
90
|
+
// Arrange
|
|
91
|
+
const mockClickCloseCallback = jest.fn();
|
|
92
|
+
render(
|
|
93
|
+
<Tabbar
|
|
94
|
+
items={["Numbers", "Geometry"]}
|
|
95
|
+
selectedItem={"Numbers"}
|
|
96
|
+
onSelectItem={() => {}}
|
|
97
|
+
onClickClose={mockClickCloseCallback}
|
|
98
|
+
/>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Assert
|
|
102
|
+
userEvent.click(screen.getByRole("tab", {name: "Dismiss"}));
|
|
103
|
+
expect(mockClickCloseCallback).toHaveBeenCalled();
|
|
104
|
+
});
|
|
65
105
|
});
|
|
@@ -21,7 +21,7 @@ const IconAsset = function ({tintColor, type}: Props): React.ReactElement {
|
|
|
21
21
|
<path
|
|
22
22
|
fillRule="evenodd"
|
|
23
23
|
clipRule="evenodd"
|
|
24
|
-
d="M7.57584 7.
|
|
24
|
+
d="M7.57584 7.09442c.35139-.16458.76626-.11103 1.06434.13737L26.6402 22.2318c.3234.2695.4434.7128.3001 1.1086C26.7969 23.7363 26.421 24 26 24H8c-.55228 0-1-.4477-1-1V8.00001c0-.38802.22446-.74101.57584-.90559zM9 10.1351V17h4c.5523 0 1 .4477 1 1v4h9.238L9 10.1351zM12 22v-3H9v3h3z"
|
|
25
25
|
fill={tintColor}
|
|
26
26
|
/>
|
|
27
27
|
</svg>
|
|
@@ -30,6 +30,8 @@ const IconAsset = function ({tintColor, type}: Props): React.ReactElement {
|
|
|
30
30
|
case "Operators": {
|
|
31
31
|
return (
|
|
32
32
|
<svg
|
|
33
|
+
width="32"
|
|
34
|
+
height="32"
|
|
33
35
|
viewBox="0 0 32 32"
|
|
34
36
|
fill="none"
|
|
35
37
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -37,7 +39,7 @@ const IconAsset = function ({tintColor, type}: Props): React.ReactElement {
|
|
|
37
39
|
<path
|
|
38
40
|
fillRule="evenodd"
|
|
39
41
|
clipRule="evenodd"
|
|
40
|
-
d="M29
|
|
42
|
+
d="M29 6h1v1h-1V6zm-2 0c0-1.10457.8954-2 2-2h1c1.1046 0 2 .89543 2 2v1c0 1.10457-.8954 2-2 2h-1c-1.1046 0-2-.89543-2-2V6zm-15.8682.50386C11.3098 6.19229 11.6411 6 12 6h2c.5523 0 1 .44772 1 1s-.4477 1-1 1h-1.4197l-3.71206 6.4961c-.18297.3202-.52733.5137-.89599.5035-.36865-.0102-.70175-.2225-.86668-.5524l-2-4c-.24699-.49396-.04676-1.09464.44722-1.34163.49397-.24699 1.09465-.04676 1.34164.44722L8.0588 11.8815l3.073-5.37764zM7.70676 16.2925c.39072.3904.39103 1.0235.00069 1.4143-.42202.4224-.86362 1.0235-1.19588 1.659C6.17039 20.0184 6 20.601 6 21c0 .3789.17235.9897.51638 1.6649.33677.661.7685 1.2472 1.15148 1.5908.41106.3689.44528 1.0011.07643 1.4122-.36885.411-1.00109.4452-1.41215.0764-.61604-.5528-1.18431-1.3599-1.5978-2.1715C4.32813 22.7755 3.99999 21.8345 4 21c.00001-.8609.3301-1.7783.73917-2.5608.41798-.7995.97637-1.5684 1.55338-2.146.39033-.3907 1.0235-.391 1.41421-.0007zm3.58644 0c.3908-.3903 1.0239-.39 1.4143.0007.577.5776 1.1353 1.3465 1.5533 2.146C14.6699 19.2217 15 20.1391 15 21c0 .8345-.3281 1.7755-.7343 2.5728-.4135.8116-.9818 1.6187-1.5978 2.1715-.4111.3688-1.0433.3346-1.4122-.0764-.3688-.4111-.3346-1.0433.0764-1.4122.383-.3436.8148-.9298 1.1515-1.5908.344-.6752.5164-1.286.5164-1.6649 0-.399-.1704-.9816-.5116-1.6342-.3322-.6355-.7738-1.2366-1.1959-1.659-.3903-.3908-.39-1.0239.0007-1.4143zm16.6431 1.3564c.1939.5171-.0681 1.0935-.5852 1.2874L21.848 21l5.5031 2.0637c.5171.1939.7791.7703.5852 1.2874-.1939.5171-.7703.7791-1.2874.5852l-8-3C18.2586 21.79 18 21.4168 18 21c0-.4168.2586-.79.6489-.9363l8-3c.5171-.1939 1.0935.0681 1.2874.5852zM21 8v5h3V8h-3zm-1-2c-.5523 0-1 .44772-1 1v7c0 .5523.4477 1 1 1h5c.5523 0 1-.4477 1-1V7c0-.55228-.4477-1-1-1h-5z"
|
|
41
43
|
fill={tintColor}
|
|
42
44
|
/>
|
|
43
45
|
</svg>
|
|
@@ -53,7 +55,7 @@ const IconAsset = function ({tintColor, type}: Props): React.ReactElement {
|
|
|
53
55
|
xmlns="http://www.w3.org/2000/svg"
|
|
54
56
|
>
|
|
55
57
|
<path
|
|
56
|
-
d="M10.4123 19.
|
|
58
|
+
d="M10.4123 19.5794v1.421H4.71434v-1.421h2.016v-5.558c0-.1213.00233-.245.007-.371.00466-.126.01166-.2543.021-.385l-1.33 1.106c-.09334.07-.18434.1144-.273.133-.08867.0187-.17267.021-.252.007-.07934-.0186-.14934-.0466-.21-.084-.06067-.042-.10734-.084-.14-.126l-.609-.819 3.122-2.646h1.589v8.743h1.75696zm8.3801-.35c.21 0 .3734.0584.49.175.1214.1167.182.2707.182.462v1.134h-7.042v-.63c0-.1213.0257-.252.077-.392.0514-.1446.1377-.2753.259-.392l3.01-3.017c.2567-.2566.483-.5016.679-.735.196-.238.3594-.469.49-.693.1307-.2286.2287-.4596.294-.693.0654-.2333.098-.4783.098-.735 0-.4526-.1166-.7956-.35-1.029-.2333-.238-.5623-.357-.987-.357-.1866 0-.3593.028-.518.084-.154.0514-.294.1237-.42.217-.1213.0934-.2263.203-.315.329-.0886.126-.154.2637-.196.413-.084.2334-.1983.3897-.343.469-.14.0747-.3406.091-.602.049l-1.022-.182c.0747-.4946.2147-.9286.42-1.302.2054-.3733.462-.6836.77-.931.308-.2473.6604-.4316 1.057-.553.3967-.126.8237-.189 1.281-.189.4807 0 .917.0724 1.309.217.3967.14.735.3384 1.015.595.28.252.497.5577.651.917.154.3594.231.756.231 1.19 0 .3734-.0536.7187-.161 1.036-.1073.3174-.2543.6207-.441.91-.182.2847-.3943.5624-.637.833-.2426.2707-.4993.5437-.77.819l-2.156 2.205c.238-.07.4737-.1236.707-.161.2334-.042.4527-.063.658-.063h2.282zm2.6611-5.523c.0747-.4946.2147-.9286.42-1.302.2054-.3733.462-.6836.77-.931.308-.2473.658-.4316 1.05-.553.3967-.126.8237-.189 1.281-.189.4854 0 .9194.07 1.302.21.3874.1354.714.322.98.56.266.238.469.5157.609.833.1447.3174.217.658.217 1.022 0 .322-.035.6067-.105.854-.0653.2427-.1656.455-.301.637-.1306.182-.294.336-.49.462-.1913.126-.413.231-.665.315 1.1667.3827 1.75 1.1597 1.75 2.331 0 .518-.0956.9754-.287 1.372-.1913.392-.448.721-.77.987s-.6976.4667-1.127.602c-.4246.1307-.8703.196-1.337.196-.4946 0-.931-.056-1.309-.168-.378-.112-.7116-.28-1.001-.504-.2846-.224-.5296-.504-.735-.84-.2053-.3406-.385-.7373-.539-1.19l.854-.35c.224-.0933.4317-.119.623-.077.196.042.336.1447.42.308.0934.1774.1914.3407.294.49.1074.1494.2264.28.357.392.1307.1074.2777.1914.441.252.168.0607.3594.091.574.091.2707 0 .5064-.0443.707-.133.2007-.0886.3687-.203.504-.343.1354-.1446.2357-.3056.301-.483.07-.182.105-.3616.105-.539 0-.2333-.021-.4433-.063-.63-.042-.1913-.14-.3523-.294-.483-.1493-.1353-.3733-.238-.672-.308-.294-.0746-.6953-.112-1.204-.112v-1.358c.4247 0 .7724-.035 1.043-.105.2707-.07.483-.168.637-.294.154-.126.259-.2776.315-.455.0607-.1773.091-.371.091-.581 0-.4433-.1166-.7816-.35-1.015-.2286-.2333-.5553-.35-.98-.35-.1866 0-.3593.028-.518.084-.154.0514-.294.1237-.42.217-.1213.0934-.2263.203-.315.329-.0886.126-.154.2637-.196.413-.0886.2334-.203.3897-.343.469-.14.0747-.343.091-.609.049l-1.015-.182z"
|
|
57
59
|
fill={tintColor}
|
|
58
60
|
/>
|
|
59
61
|
</svg>
|
|
@@ -63,12 +65,50 @@ const IconAsset = function ({tintColor, type}: Props): React.ReactElement {
|
|
|
63
65
|
return (
|
|
64
66
|
<svg
|
|
65
67
|
xmlns="http://www.w3.org/2000/svg"
|
|
66
|
-
width="
|
|
67
|
-
height="
|
|
68
|
-
fill="
|
|
69
|
-
viewBox="0 0
|
|
68
|
+
width="32"
|
|
69
|
+
height="32"
|
|
70
|
+
fill="none"
|
|
71
|
+
viewBox="0 0 32 32"
|
|
70
72
|
>
|
|
71
|
-
<
|
|
73
|
+
<g clipPath="url(#a)">
|
|
74
|
+
<path
|
|
75
|
+
fill={tintColor}
|
|
76
|
+
fillRule="evenodd"
|
|
77
|
+
d="M6.127 10.454c.224-.032.403-.05.53-.05.591 0 1.137.153 1.63.459.398.239.713.529.94.872l.188-.248.005-.007.006-.006c.619-.703 1.347-1.07 2.178-1.07.691 0 1.269.204 1.71.626.454.403.686.91.686 1.51 0 .533-.156.994-.476 1.37-.312.383-.738.574-1.254.574-.345 0-.643-.101-.878-.317a1.1 1.1 0 0 1-.353-.843c0-.405.11-.76.342-1.051.153-.193.354-.352.596-.479a1.416 1.416 0 0 0-.492-.07c-.195 0-.316.016-.38.035-.53.226-.938.694-1.208 1.445l-.001.003c-.02.05-.085.289-.202.74-.115.443-.275 1.077-.481 1.901-.414 1.641-.633 2.547-.662 2.74l-.002.01a3.423 3.423 0 0 0-.067.673c0 .337.097.581.272.756.176.177.413.272.733.272.6 0 1.15-.247 1.657-.768.518-.549.864-1.146 1.044-1.791l.001-.004a1.2 1.2 0 0 1 .088-.224.368.368 0 0 1 .161-.164.564.564 0 0 1 .198-.056 2.19 2.19 0 0 1 .276-.014c.159 0 .305.016.42.064.059.025.12.063.167.122.05.063.073.137.073.213 0 .023-.004.048-.005.057a12.52 12.52 0 0 1-.046.245l-.004.015c-.281 1.026-.86 1.917-1.73 2.67l-.007.007c-.776.611-1.605.925-2.484.925-1.08 0-1.93-.45-2.53-1.33-.453.605-1.015 1.024-1.685 1.248l-.01.003-.011.002a3.23 3.23 0 0 1-.664.053c-.974 0-1.703-.35-2.13-1.078A2.05 2.05 0 0 1 2 19.437c0-.52.158-.975.478-1.349.326-.38.749-.572 1.252-.572.372 0 .69.091.913.31.224.218.318.531.318.898 0 .327-.078.621-.241.874a1.706 1.706 0 0 1-.707.597l-.018.009c.158.063.331.095.52.095.467 0 .902-.285 1.295-.966l.002-.005c.071-.115.185-.417.341-.938.154-.51.341-1.209.563-2.096v-.002c.095-.364.198-.767.31-1.21.11-.444.188-.78.235-1.014l.002-.013c.058-.216.098-.36.119-.425.077-.42.113-.709.113-.877 0-.342-.092-.588-.254-.762-.159-.171-.384-.267-.704-.267-.652 0-1.217.251-1.704.768l-.002.002A4.215 4.215 0 0 0 3.79 14.28a1.084 1.084 0 0 1-.065.207.41.41 0 0 1-.14.176l-.01.007-.012.006a.35.35 0 0 1-.104.03 1.16 1.16 0 0 1-.095.01 5.04 5.04 0 0 1-.275.006H2.67l-.061-.061c-.109-.11-.204-.247-.204-.41v-.015l.003-.015c.07-.472.335-1.05.768-1.723l.001-.002c.771-1.165 1.754-1.857 2.949-2.042h.002Z"
|
|
78
|
+
clipRule="evenodd"
|
|
79
|
+
/>
|
|
80
|
+
<path
|
|
81
|
+
fill={tintColor}
|
|
82
|
+
d="M21.084 10.284c.932-.008 2.301-.013 4.107-.013 1.325 0 2.327.003 3.007.007a75.812 75.812 0 0 1 .99.013c.025 0 .047.002.065.003h.002c.01 0 .04.003.067.01l.01.002.011.004c.201.07.37.183.488.347a.966.966 0 0 1 .169.574c0 .3-.078.568-.248.79-.168.221-.411.377-.708.479h-.002a1.01 1.01 0 0 1-.221.034 8.213 8.213 0 0 1-.35.016c-.29.008-.696.012-1.219.012h-1.39l-.038.223v.001c-.198 1.185-.295 2.156-.295 2.916 0 1.446.251 2.746.75 3.905l.004.007c.059.153.105.284.137.393.03.103.053.205.053.29 0 .359-.16.68-.44.961-.278.296-.63.445-1.041.445-.255 0-.492-.03-.654-.139l-.009-.006-.008-.006c-.126-.101-.236-.274-.338-.477l-.006-.012c-.331-.768-.49-1.722-.49-2.852 0-.595.007-1.002.025-1.212v-.005c.118-1.157.377-2.551.776-4.18v-.002c.024-.096.045-.18.061-.25h-1.948c-.008.038-.02.086-.034.143l-.002.007a35.14 35.14 0 0 0-.146.537c-.05.232-.1.448-.15.648v.001a230.673 230.673 0 0 1-1.312 4.936 41.285 41.285 0 0 1-.411 1.384c-.104.322-.19.557-.256.681-.115.262-.28.473-.5.617-.225.146-.49.212-.783.212-.449 0-.807-.173-1.006-.549l-.006-.011-.005-.012a1.37 1.37 0 0 1-.067-.486v-.326l.346-.745c1.24-2.61 2.136-4.858 2.695-6.747l.002-.008.094-.281h-.463c-.662 0-1.105.025-1.346.07-.198.04-.47.173-.824.43l-.007.005-.007.005c-.366.228-.69.542-.97.947-.044.069-.085.13-.125.18a.651.651 0 0 1-.141.136l-.027.017-.03.01a.8.8 0 0 1-.19.03c-.07.005-.156.008-.258.008-.17 0-.335-.021-.465-.09a.437.437 0 0 1-.216-.546c.014-.042.034-.086.057-.132.047-.093.113-.208.198-.343l.003-.005c1.147-1.745 2.311-2.774 3.508-2.96a2.345 2.345 0 0 1 .158-.015 60.295 60.295 0 0 1 1.369-.026Z"
|
|
83
|
+
/>
|
|
84
|
+
</g>
|
|
85
|
+
<defs>
|
|
86
|
+
<clipPath id="a">
|
|
87
|
+
<path
|
|
88
|
+
fill="#fff"
|
|
89
|
+
d="M0 0h28v11.457H0z"
|
|
90
|
+
transform="translate(2 10.271)"
|
|
91
|
+
/>
|
|
92
|
+
</clipPath>
|
|
93
|
+
</defs>
|
|
94
|
+
</svg>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
case "Dismiss": {
|
|
98
|
+
return (
|
|
99
|
+
<svg
|
|
100
|
+
width="44"
|
|
101
|
+
height="44"
|
|
102
|
+
viewBox="0 0 44 44"
|
|
103
|
+
fill="none"
|
|
104
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
105
|
+
>
|
|
106
|
+
<path
|
|
107
|
+
fillRule="evenodd"
|
|
108
|
+
clipRule="evenodd"
|
|
109
|
+
d="M28.7071 15.2929C28.3166 14.9024 27.6834 14.9024 27.2929 15.2929L22 20.5858L16.7071 15.2929C16.3166 14.9024 15.6834 14.9024 15.2929 15.2929C14.9024 15.6834 14.9024 16.3166 15.2929 16.7071L20.5858 22L15.2929 27.2929C14.9024 27.6834 14.9024 28.3166 15.2929 28.7071C15.6834 29.0976 16.3166 29.0976 16.7071 28.7071L22 23.4142L27.2929 28.7071C27.6834 29.0976 28.3166 29.0976 28.7071 28.7071C29.0976 28.3166 29.0976 27.6834 28.7071 27.2929L23.4142 22L28.7071 16.7071C29.0976 16.3166 29.0976 15.6834 28.7071 15.2929Z"
|
|
110
|
+
fill={tintColor}
|
|
111
|
+
/>
|
|
72
112
|
</svg>
|
|
73
113
|
);
|
|
74
114
|
}
|
|
@@ -84,6 +84,8 @@ class TabbarItem extends React.Component<Props> {
|
|
|
84
84
|
onClick={onClick}
|
|
85
85
|
disabled={itemState === "disabled"}
|
|
86
86
|
aria-label={itemType}
|
|
87
|
+
aria-selected={itemState === "active"}
|
|
88
|
+
role="tab"
|
|
87
89
|
>
|
|
88
90
|
{({hovered, focused, pressed}) => {
|
|
89
91
|
const tintColor = imageTintColor(
|
|
@@ -9,33 +9,53 @@ const styles = StyleSheet.create({
|
|
|
9
9
|
tabbar: {
|
|
10
10
|
display: "flex",
|
|
11
11
|
flexDirection: "row",
|
|
12
|
+
justifyContent: "space-between",
|
|
12
13
|
paddingTop: 2,
|
|
13
14
|
paddingBottom: 2,
|
|
14
15
|
},
|
|
16
|
+
pages: {
|
|
17
|
+
display: "flex",
|
|
18
|
+
flexDirection: "row",
|
|
19
|
+
},
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
type Props = {
|
|
18
23
|
items: ReadonlyArray<TabbarItemType>;
|
|
19
24
|
selectedItem: TabbarItemType;
|
|
25
|
+
onClickClose?: () => void;
|
|
20
26
|
onSelectItem: (item: TabbarItemType) => void;
|
|
21
27
|
style?: StyleType;
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
function Tabbar(props: Props): React.ReactElement {
|
|
25
|
-
const {items, selectedItem, onSelectItem, style} = props;
|
|
31
|
+
const {items, onClickClose, selectedItem, onSelectItem, style} = props;
|
|
26
32
|
|
|
27
33
|
return (
|
|
28
|
-
<View style={[styles.tabbar, style]}>
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
<View style={[styles.tabbar, style]} role="tablist">
|
|
35
|
+
<View style={[styles.pages]}>
|
|
36
|
+
{items.map((item) => (
|
|
37
|
+
<TabbarItem
|
|
38
|
+
key={`tabbar-item-${item}`}
|
|
39
|
+
itemState={
|
|
40
|
+
item === selectedItem ? "active" : "inactive"
|
|
41
|
+
}
|
|
42
|
+
itemType={item}
|
|
43
|
+
onClick={() => {
|
|
44
|
+
onSelectItem(item);
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</View>
|
|
49
|
+
|
|
50
|
+
<View>
|
|
51
|
+
{onClickClose && (
|
|
52
|
+
<TabbarItem
|
|
53
|
+
itemState="inactive"
|
|
54
|
+
itemType="Dismiss"
|
|
55
|
+
onClick={onClickClose}
|
|
56
|
+
/>
|
|
57
|
+
)}
|
|
58
|
+
</View>
|
|
39
59
|
</View>
|
|
40
60
|
);
|
|
41
61
|
}
|