@khanacademy/math-input 10.0.0 → 10.1.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 +12 -0
- package/dist/components/keypad/keypad.d.ts +1 -0
- package/dist/components/keypad/mobile-keypad.d.ts +8 -0
- package/dist/components/keypad/navigation-button.d.ts +8 -0
- package/dist/components/keypad/navigation-pad.d.ts +6 -0
- package/dist/components/keypad/utils.d.ts +1 -0
- package/dist/es/index.css +3 -5
- package/dist/es/index.js +322 -29
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +3 -5
- package/dist/index.js +326 -29
- package/dist/index.js.map +1 -1
- package/less/overrides.less +8 -9
- package/package.json +1 -1
- package/src/components/keypad/__tests__/__snapshots__/keypad.test.tsx.snap +1855 -0
- package/src/components/keypad/__tests__/keypad.test.tsx +94 -0
- package/src/components/keypad/button-assets.tsx +77 -4
- package/src/components/keypad/keypad-button.tsx +1 -1
- package/src/components/keypad/keypad.stories.tsx +4 -0
- package/src/components/keypad/keypad.tsx +83 -53
- package/src/components/keypad/mobile-keypad.tsx +55 -1
- package/src/components/keypad/navigation-button.tsx +127 -0
- package/src/components/keypad/navigation-pad.stories.tsx +25 -0
- package/src/components/keypad/navigation-pad.tsx +67 -0
- package/src/components/keypad/utils.ts +4 -0
- package/src/components/keypad-legacy/keypad-button.tsx +2 -1
- package/src/full-math-input.stories.tsx +17 -18
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -46,6 +46,42 @@ describe("keypad", () => {
|
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it("should snapshot unexpanded", () => {
|
|
50
|
+
// Arrange
|
|
51
|
+
// Act
|
|
52
|
+
const {container} = render(
|
|
53
|
+
<Keypad
|
|
54
|
+
onClickKey={() => {}}
|
|
55
|
+
preAlgebra
|
|
56
|
+
trigonometry
|
|
57
|
+
extraKeys={["PI"]}
|
|
58
|
+
onAnalyticsEvent={async () => {}}
|
|
59
|
+
expandedView={false}
|
|
60
|
+
/>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Assert
|
|
64
|
+
expect(container).toMatchSnapshot("first render");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should snapshot expanded", () => {
|
|
68
|
+
// Arrange
|
|
69
|
+
// Act
|
|
70
|
+
const {container} = render(
|
|
71
|
+
<Keypad
|
|
72
|
+
onClickKey={() => {}}
|
|
73
|
+
preAlgebra
|
|
74
|
+
trigonometry
|
|
75
|
+
extraKeys={["PI"]}
|
|
76
|
+
onAnalyticsEvent={async () => {}}
|
|
77
|
+
expandedView={true}
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(container).toMatchSnapshot("first render");
|
|
83
|
+
});
|
|
84
|
+
|
|
49
85
|
it(`shows optional dismiss button`, () => {
|
|
50
86
|
// Arrange
|
|
51
87
|
// Act
|
|
@@ -122,4 +158,62 @@ describe("keypad", () => {
|
|
|
122
158
|
// Assert
|
|
123
159
|
expect(onClickKey).toHaveBeenCalledTimes(tabs.length);
|
|
124
160
|
});
|
|
161
|
+
|
|
162
|
+
it(`does not show navigation pad with expanded view turned off`, () => {
|
|
163
|
+
// Arrange
|
|
164
|
+
// Act
|
|
165
|
+
render(
|
|
166
|
+
<Keypad
|
|
167
|
+
onClickKey={() => {}}
|
|
168
|
+
preAlgebra
|
|
169
|
+
trigonometry
|
|
170
|
+
extraKeys={["PI"]}
|
|
171
|
+
onAnalyticsEvent={async () => {}}
|
|
172
|
+
expandedView={false}
|
|
173
|
+
/>,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Assert
|
|
177
|
+
expect(
|
|
178
|
+
screen.queryByRole("button", {name: "Up arrow"}),
|
|
179
|
+
).not.toBeInTheDocument();
|
|
180
|
+
expect(
|
|
181
|
+
screen.queryByRole("button", {name: "Right arrow"}),
|
|
182
|
+
).not.toBeInTheDocument();
|
|
183
|
+
expect(
|
|
184
|
+
screen.queryByRole("button", {name: "Down arrow"}),
|
|
185
|
+
).not.toBeInTheDocument();
|
|
186
|
+
expect(
|
|
187
|
+
screen.queryByRole("button", {name: "Left arrow"}),
|
|
188
|
+
).not.toBeInTheDocument();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it(`shows navigation pad in expanded view`, () => {
|
|
192
|
+
// Arrange
|
|
193
|
+
// Act
|
|
194
|
+
render(
|
|
195
|
+
<Keypad
|
|
196
|
+
onClickKey={() => {}}
|
|
197
|
+
preAlgebra
|
|
198
|
+
trigonometry
|
|
199
|
+
extraKeys={["PI"]}
|
|
200
|
+
onAnalyticsEvent={async () => {}}
|
|
201
|
+
expandedView={true}
|
|
202
|
+
/>,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Assert
|
|
206
|
+
expect(
|
|
207
|
+
screen.getByRole("button", {name: "Up arrow"}),
|
|
208
|
+
).toBeInTheDocument();
|
|
209
|
+
expect(
|
|
210
|
+
screen.getByRole("button", {name: "Right arrow"}),
|
|
211
|
+
).toBeInTheDocument();
|
|
212
|
+
expect(
|
|
213
|
+
screen.getByRole("button", {name: "Down arrow"}),
|
|
214
|
+
).toBeInTheDocument();
|
|
215
|
+
expect(
|
|
216
|
+
screen.getByRole("button", {name: "Left arrow"}),
|
|
217
|
+
).toBeInTheDocument();
|
|
218
|
+
});
|
|
125
219
|
});
|
|
@@ -1735,15 +1735,88 @@ export default function ButtonAsset({id}: Props): React.ReactElement {
|
|
|
1735
1735
|
</svg>
|
|
1736
1736
|
);
|
|
1737
1737
|
|
|
1738
|
+
case "UP":
|
|
1739
|
+
return (
|
|
1740
|
+
<svg width="48" height="48" viewBox="0 0 48 48">
|
|
1741
|
+
<g
|
|
1742
|
+
fill="none"
|
|
1743
|
+
fillRule="evenodd"
|
|
1744
|
+
transform="rotate(90 24 24)"
|
|
1745
|
+
>
|
|
1746
|
+
<path fill="none" d="M0 0h48v48H0z" />
|
|
1747
|
+
<path fill="none" d="M12 12h24v24H12z" />
|
|
1748
|
+
<path
|
|
1749
|
+
stroke="#21242C"
|
|
1750
|
+
strokeWidth="2"
|
|
1751
|
+
strokeLinecap="round"
|
|
1752
|
+
strokeLinejoin="round"
|
|
1753
|
+
d="M22 18l-6 6 6 6M16 24h16"
|
|
1754
|
+
/>
|
|
1755
|
+
</g>
|
|
1756
|
+
</svg>
|
|
1757
|
+
);
|
|
1758
|
+
case "DOWN":
|
|
1759
|
+
return (
|
|
1760
|
+
<svg width="48" height="48" viewBox="0 0 48 48">
|
|
1761
|
+
<g
|
|
1762
|
+
fill="none"
|
|
1763
|
+
fillRule="evenodd"
|
|
1764
|
+
transform="rotate(270 24 24)"
|
|
1765
|
+
>
|
|
1766
|
+
<path fill="none" d="M0 0h48v48H0z" />
|
|
1767
|
+
<path fill="none" d="M12 12h24v24H12z" />
|
|
1768
|
+
<path
|
|
1769
|
+
stroke="#21242C"
|
|
1770
|
+
strokeWidth="2"
|
|
1771
|
+
strokeLinecap="round"
|
|
1772
|
+
strokeLinejoin="round"
|
|
1773
|
+
d="M22 18l-6 6 6 6M16 24h16"
|
|
1774
|
+
/>
|
|
1775
|
+
</g>
|
|
1776
|
+
</svg>
|
|
1777
|
+
);
|
|
1778
|
+
case "LEFT":
|
|
1779
|
+
return (
|
|
1780
|
+
<svg width="48" height="48" viewBox="0 0 48 48">
|
|
1781
|
+
<g fill="none" fillRule="evenodd">
|
|
1782
|
+
<path fill="none" d="M0 0h48v48H0z" />
|
|
1783
|
+
<path fill="none" d="M12 12h24v24H12z" />
|
|
1784
|
+
<path
|
|
1785
|
+
stroke="#21242C"
|
|
1786
|
+
strokeWidth="2"
|
|
1787
|
+
strokeLinecap="round"
|
|
1788
|
+
strokeLinejoin="round"
|
|
1789
|
+
d="M22 18l-6 6 6 6M16 24h16"
|
|
1790
|
+
/>
|
|
1791
|
+
</g>
|
|
1792
|
+
</svg>
|
|
1793
|
+
);
|
|
1794
|
+
case "RIGHT":
|
|
1795
|
+
return (
|
|
1796
|
+
<svg width="48" height="48" viewBox="0 0 48 48">
|
|
1797
|
+
<g
|
|
1798
|
+
fill="none"
|
|
1799
|
+
fillRule="evenodd"
|
|
1800
|
+
transform="rotate(180 24 24)"
|
|
1801
|
+
>
|
|
1802
|
+
<path fill="none" d="M0 0h48v48H0z" />
|
|
1803
|
+
<path fill="none" d="M12 12h24v24H12z" />
|
|
1804
|
+
<path
|
|
1805
|
+
stroke="#21242C"
|
|
1806
|
+
strokeWidth="2"
|
|
1807
|
+
strokeLinecap="round"
|
|
1808
|
+
strokeLinejoin="round"
|
|
1809
|
+
d="M22 18l-6 6 6 6M16 24h16"
|
|
1810
|
+
/>
|
|
1811
|
+
</g>
|
|
1812
|
+
</svg>
|
|
1813
|
+
);
|
|
1814
|
+
|
|
1738
1815
|
/**
|
|
1739
1816
|
* ANYTHING BELOW IS NOT YET HANDLED
|
|
1740
1817
|
*/
|
|
1741
1818
|
case "MANY":
|
|
1742
1819
|
case "NOOP":
|
|
1743
|
-
case "UP":
|
|
1744
|
-
case "DOWN":
|
|
1745
|
-
case "LEFT":
|
|
1746
|
-
case "RIGHT":
|
|
1747
1820
|
case "PHI":
|
|
1748
1821
|
case "NTHROOT3":
|
|
1749
1822
|
case "POW":
|
|
@@ -73,7 +73,7 @@ const styles = StyleSheet.create({
|
|
|
73
73
|
display: "flex",
|
|
74
74
|
justifyContent: "center",
|
|
75
75
|
alignItems: "center",
|
|
76
|
-
boxShadow:
|
|
76
|
+
boxShadow: `0px 1px 0px ${Color.offBlack32}`,
|
|
77
77
|
boxSizing: "border-box",
|
|
78
78
|
background: Color.white,
|
|
79
79
|
borderRadius: 4,
|
|
@@ -31,6 +31,7 @@ export default {
|
|
|
31
31
|
preAlgebra: false,
|
|
32
32
|
trigonometry: false,
|
|
33
33
|
sendEvent: () => {},
|
|
34
|
+
onAnalyticsEvent: async () => {},
|
|
34
35
|
},
|
|
35
36
|
argTypes: {
|
|
36
37
|
advancedRelations: {
|
|
@@ -115,4 +116,7 @@ Everything.args = {
|
|
|
115
116
|
multiplicationDot: false,
|
|
116
117
|
preAlgebra: true,
|
|
117
118
|
trigonometry: true,
|
|
119
|
+
expandedView: true,
|
|
120
|
+
showDismiss: true,
|
|
121
|
+
extraKeys: ["a", "b", "c"],
|
|
118
122
|
};
|
|
@@ -11,7 +11,9 @@ import FractionsPage from "./keypad-pages/fractions-page";
|
|
|
11
11
|
import GeometryPage from "./keypad-pages/geometry-page";
|
|
12
12
|
import NumbersPage from "./keypad-pages/numbers-page";
|
|
13
13
|
import OperatorsPage from "./keypad-pages/operators-page";
|
|
14
|
+
import NavigationPad from "./navigation-pad";
|
|
14
15
|
import SharedKeys from "./shared-keys";
|
|
16
|
+
import {expandedViewThreshold} from "./utils";
|
|
15
17
|
|
|
16
18
|
import type Key from "../../data/keys";
|
|
17
19
|
import type {ClickKeyCallback} from "../../types";
|
|
@@ -23,10 +25,10 @@ export type Props = {
|
|
|
23
25
|
extraKeys: ReadonlyArray<Key>;
|
|
24
26
|
cursorContext?: typeof CursorContext[keyof typeof CursorContext];
|
|
25
27
|
showDismiss?: boolean;
|
|
28
|
+
expandedView?: boolean;
|
|
26
29
|
|
|
27
30
|
multiplicationDot?: boolean;
|
|
28
31
|
divisionKey?: boolean;
|
|
29
|
-
|
|
30
32
|
trigonometry?: boolean;
|
|
31
33
|
preAlgebra?: boolean;
|
|
32
34
|
logarithms?: boolean;
|
|
@@ -96,6 +98,7 @@ export default function Keypad(props: Props) {
|
|
|
96
98
|
showDismiss,
|
|
97
99
|
onAnalyticsEvent,
|
|
98
100
|
fractionsOnly,
|
|
101
|
+
expandedView,
|
|
99
102
|
} = props;
|
|
100
103
|
|
|
101
104
|
// Use a different grid for our fraction keypad
|
|
@@ -128,58 +131,70 @@ export default function Keypad(props: Props) {
|
|
|
128
131
|
}, [onAnalyticsEvent, isMounted]);
|
|
129
132
|
|
|
130
133
|
return (
|
|
131
|
-
<View>
|
|
132
|
-
<Tabbar
|
|
133
|
-
items={availableTabs}
|
|
134
|
-
selectedItem={selectedPage}
|
|
135
|
-
onSelectItem={(tabbarItem: TabbarItemType) => {
|
|
136
|
-
setSelectedPage(tabbarItem);
|
|
137
|
-
}}
|
|
138
|
-
style={styles.tabbar}
|
|
139
|
-
onClickClose={
|
|
140
|
-
showDismiss ? () => onClickKey("DISMISS") : undefined
|
|
141
|
-
}
|
|
142
|
-
/>
|
|
143
|
-
|
|
134
|
+
<View style={expandedView ? styles.keypadOuterContainer : null}>
|
|
144
135
|
<View
|
|
145
|
-
style={[
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
style={[
|
|
137
|
+
styles.wrapper,
|
|
138
|
+
expandedView ? styles.expandedWrapper : null,
|
|
139
|
+
]}
|
|
149
140
|
>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
141
|
+
<Tabbar
|
|
142
|
+
items={availableTabs}
|
|
143
|
+
selectedItem={selectedPage}
|
|
144
|
+
onSelectItem={(tabbarItem: TabbarItemType) => {
|
|
145
|
+
setSelectedPage(tabbarItem);
|
|
146
|
+
}}
|
|
147
|
+
onClickClose={
|
|
148
|
+
showDismiss ? () => onClickKey("DISMISS") : undefined
|
|
149
|
+
}
|
|
150
|
+
/>
|
|
151
|
+
|
|
152
|
+
<View style={styles.keypadInnerContainer}>
|
|
153
|
+
<View
|
|
154
|
+
style={[styles.keypadGrid, gridStyle]}
|
|
155
|
+
role="grid"
|
|
156
|
+
tabIndex={0}
|
|
157
|
+
aria-label="Keypad"
|
|
158
|
+
>
|
|
159
|
+
{selectedPage === "Fractions" && (
|
|
160
|
+
<FractionsPage
|
|
161
|
+
onClickKey={onClickKey}
|
|
162
|
+
cursorContext={cursorContext}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
{selectedPage === "Numbers" && (
|
|
166
|
+
<NumbersPage onClickKey={onClickKey} />
|
|
167
|
+
)}
|
|
168
|
+
{selectedPage === "Extras" && (
|
|
169
|
+
<ExtrasPage
|
|
170
|
+
onClickKey={onClickKey}
|
|
171
|
+
extraKeys={extraKeys}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
{selectedPage === "Operators" && (
|
|
175
|
+
<OperatorsPage
|
|
176
|
+
onClickKey={onClickKey}
|
|
177
|
+
preAlgebra={preAlgebra}
|
|
178
|
+
logarithms={logarithms}
|
|
179
|
+
basicRelations={basicRelations}
|
|
180
|
+
advancedRelations={advancedRelations}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
{selectedPage === "Geometry" && (
|
|
184
|
+
<GeometryPage onClickKey={onClickKey} />
|
|
185
|
+
)}
|
|
186
|
+
{!fractionsOnly && (
|
|
187
|
+
<SharedKeys
|
|
188
|
+
onClickKey={onClickKey}
|
|
189
|
+
cursorContext={cursorContext}
|
|
190
|
+
multiplicationDot={multiplicationDot}
|
|
191
|
+
divisionKey={divisionKey}
|
|
192
|
+
selectedPage={selectedPage}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
</View>
|
|
196
|
+
{expandedView && <NavigationPad onClickKey={onClickKey} />}
|
|
197
|
+
</View>
|
|
183
198
|
</View>
|
|
184
199
|
</View>
|
|
185
200
|
);
|
|
@@ -188,13 +203,28 @@ export default function Keypad(props: Props) {
|
|
|
188
203
|
Keypad.defaultProps = defaultProps;
|
|
189
204
|
|
|
190
205
|
const styles = StyleSheet.create({
|
|
191
|
-
|
|
206
|
+
keypadOuterContainer: {
|
|
207
|
+
display: "flex",
|
|
208
|
+
alignItems: "center",
|
|
209
|
+
},
|
|
210
|
+
wrapper: {
|
|
192
211
|
background: Color.white,
|
|
193
212
|
},
|
|
213
|
+
expandedWrapper: {
|
|
214
|
+
borderWidth: "1px 1px 0 1px",
|
|
215
|
+
borderColor: Color.offBlack32,
|
|
216
|
+
maxWidth: expandedViewThreshold,
|
|
217
|
+
borderRadius: "3px 3px 0 0",
|
|
218
|
+
},
|
|
219
|
+
keypadInnerContainer: {
|
|
220
|
+
display: "flex",
|
|
221
|
+
flexDirection: "row",
|
|
222
|
+
backgroundColor: "#DBDCDD",
|
|
223
|
+
},
|
|
194
224
|
keypadGrid: {
|
|
195
225
|
display: "grid",
|
|
196
226
|
gridTemplateRows: "repeat(4, 1fr)",
|
|
197
|
-
|
|
227
|
+
flex: 1,
|
|
198
228
|
},
|
|
199
229
|
expressionGrid: {
|
|
200
230
|
gridTemplateColumns: "repeat(6, 1fr)",
|
|
@@ -4,6 +4,8 @@ import ReactDOM from "react-dom";
|
|
|
4
4
|
|
|
5
5
|
import {View} from "../../fake-react-native-web/index";
|
|
6
6
|
|
|
7
|
+
import {expandedViewThreshold} from "./utils";
|
|
8
|
+
|
|
7
9
|
import type Key from "../../data/keys";
|
|
8
10
|
import type {
|
|
9
11
|
Cursor,
|
|
@@ -34,18 +36,68 @@ type Props = {
|
|
|
34
36
|
|
|
35
37
|
type State = {
|
|
36
38
|
active: boolean;
|
|
39
|
+
containerWidth: number;
|
|
37
40
|
keypadConfig?: KeypadConfiguration;
|
|
38
41
|
keyHandler?: KeyHandler;
|
|
39
42
|
cursor?: Cursor;
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
46
|
+
_containerRef = React.createRef<HTMLDivElement>();
|
|
47
|
+
_containerResizeObserver: ResizeObserver | null = null;
|
|
48
|
+
_throttleResize = false;
|
|
43
49
|
hasMounted = false;
|
|
44
50
|
|
|
45
51
|
state: State = {
|
|
52
|
+
containerWidth: 0,
|
|
46
53
|
active: false,
|
|
47
54
|
};
|
|
48
55
|
|
|
56
|
+
componentDidMount() {
|
|
57
|
+
this._resize();
|
|
58
|
+
|
|
59
|
+
window.addEventListener("resize", this._throttleResizeHandler);
|
|
60
|
+
window.addEventListener(
|
|
61
|
+
"orientationchange",
|
|
62
|
+
this._throttleResizeHandler,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
this._containerResizeObserver = new ResizeObserver(
|
|
66
|
+
this._throttleResizeHandler,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (this._containerRef.current) {
|
|
70
|
+
this._containerResizeObserver.observe(this._containerRef.current);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
componentWillUnMount() {
|
|
75
|
+
window.removeEventListener("resize", this._throttleResizeHandler);
|
|
76
|
+
window.removeEventListener(
|
|
77
|
+
"orientationchange",
|
|
78
|
+
this._throttleResizeHandler,
|
|
79
|
+
);
|
|
80
|
+
this._containerResizeObserver?.disconnect();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_resize = () => {
|
|
84
|
+
const containerWidth = this._containerRef.current?.clientWidth || 0;
|
|
85
|
+
this.setState({containerWidth});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
_throttleResizeHandler = () => {
|
|
89
|
+
if (this._throttleResize) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this._throttleResize = true;
|
|
94
|
+
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
this._resize();
|
|
97
|
+
this._throttleResize = false;
|
|
98
|
+
}, 100);
|
|
99
|
+
};
|
|
100
|
+
|
|
49
101
|
activate: () => void = () => {
|
|
50
102
|
this.setState({active: true});
|
|
51
103
|
};
|
|
@@ -98,7 +150,7 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
|
98
150
|
|
|
99
151
|
render(): React.ReactNode {
|
|
100
152
|
const {style} = this.props;
|
|
101
|
-
const {active, cursor, keypadConfig} = this.state;
|
|
153
|
+
const {active, containerWidth, cursor, keypadConfig} = this.state;
|
|
102
154
|
|
|
103
155
|
const containerStyle = [
|
|
104
156
|
// internal styles
|
|
@@ -113,6 +165,7 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
|
113
165
|
return (
|
|
114
166
|
<View
|
|
115
167
|
style={containerStyle}
|
|
168
|
+
forwardRef={this._containerRef}
|
|
116
169
|
ref={(element) => {
|
|
117
170
|
if (!this.hasMounted && element) {
|
|
118
171
|
// TODO(matthewc)[LC-1081]: clean up this weird
|
|
@@ -150,6 +203,7 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
|
|
|
150
203
|
logarithms={isExpression}
|
|
151
204
|
basicRelations={isExpression}
|
|
152
205
|
advancedRelations={isExpression}
|
|
206
|
+
expandedView={containerWidth > expandedViewThreshold}
|
|
153
207
|
showDismiss
|
|
154
208
|
/>
|
|
155
209
|
</View>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Clickable from "@khanacademy/wonder-blocks-clickable";
|
|
2
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
3
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
4
|
+
import {StyleSheet} from "aphrodite";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import ButtonAsset from "./button-assets";
|
|
8
|
+
|
|
9
|
+
import type Key from "../../data/keys";
|
|
10
|
+
import type {KeyConfig, ClickKeyCallback} from "../../types";
|
|
11
|
+
|
|
12
|
+
export type KeypadButtonProps = {
|
|
13
|
+
// 0 indexed [x, y] position in keypad CSS grid
|
|
14
|
+
coord: readonly [number, number];
|
|
15
|
+
keyConfig: KeyConfig;
|
|
16
|
+
onClickKey: ClickKeyCallback;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function getStyles(key: Key) {
|
|
20
|
+
switch (key) {
|
|
21
|
+
case "UP":
|
|
22
|
+
return styles.up;
|
|
23
|
+
case "RIGHT":
|
|
24
|
+
return styles.right;
|
|
25
|
+
case "DOWN":
|
|
26
|
+
return styles.down;
|
|
27
|
+
case "LEFT":
|
|
28
|
+
return styles.left;
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Invalid key: ${key}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function NavigationButton({
|
|
35
|
+
coord,
|
|
36
|
+
keyConfig,
|
|
37
|
+
onClickKey,
|
|
38
|
+
}: KeypadButtonProps) {
|
|
39
|
+
const key = keyConfig.id;
|
|
40
|
+
const directionalStyles = getStyles(key);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View
|
|
44
|
+
style={{
|
|
45
|
+
gridColumn: coord[0] + 1,
|
|
46
|
+
gridRow: coord[1] + 1,
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<Clickable
|
|
50
|
+
onClick={(e) => onClickKey(keyConfig.id, e)}
|
|
51
|
+
style={styles.clickable}
|
|
52
|
+
aria-label={keyConfig.ariaLabel}
|
|
53
|
+
>
|
|
54
|
+
{({hovered, focused, pressed}) => (
|
|
55
|
+
<View style={styles.outerBoxBase}>
|
|
56
|
+
<View
|
|
57
|
+
style={[
|
|
58
|
+
styles.base,
|
|
59
|
+
directionalStyles,
|
|
60
|
+
hovered && styles.hovered,
|
|
61
|
+
focused && styles.focused,
|
|
62
|
+
pressed && styles.pressed,
|
|
63
|
+
]}
|
|
64
|
+
>
|
|
65
|
+
<ButtonAsset id={keyConfig.id} />
|
|
66
|
+
</View>
|
|
67
|
+
</View>
|
|
68
|
+
)}
|
|
69
|
+
</Clickable>
|
|
70
|
+
</View>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const borderRadiusPx = 4;
|
|
75
|
+
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
clickable: {
|
|
78
|
+
width: "100%",
|
|
79
|
+
height: "100%",
|
|
80
|
+
|
|
81
|
+
":focus": {
|
|
82
|
+
outline: `none`,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
outerBoxBase: {
|
|
86
|
+
height: "100%",
|
|
87
|
+
width: "100%",
|
|
88
|
+
},
|
|
89
|
+
base: {
|
|
90
|
+
boxShadow: `0px 1px 0px ${Color.offBlack32}`,
|
|
91
|
+
display: "flex",
|
|
92
|
+
justifyContent: "center",
|
|
93
|
+
alignItems: "center",
|
|
94
|
+
background: Color.white,
|
|
95
|
+
borderWidth: 2,
|
|
96
|
+
borderColor: Color.white,
|
|
97
|
+
},
|
|
98
|
+
up: {
|
|
99
|
+
borderTopLeftRadius: borderRadiusPx,
|
|
100
|
+
borderTopRightRadius: borderRadiusPx,
|
|
101
|
+
},
|
|
102
|
+
right: {
|
|
103
|
+
borderTopRightRadius: borderRadiusPx,
|
|
104
|
+
borderBottomRightRadius: borderRadiusPx,
|
|
105
|
+
},
|
|
106
|
+
down: {
|
|
107
|
+
borderBottomLeftRadius: borderRadiusPx,
|
|
108
|
+
borderBottomRightRadius: borderRadiusPx,
|
|
109
|
+
},
|
|
110
|
+
left: {
|
|
111
|
+
borderTopLeftRadius: borderRadiusPx,
|
|
112
|
+
borderBottomLeftRadius: borderRadiusPx,
|
|
113
|
+
},
|
|
114
|
+
hovered: {
|
|
115
|
+
borderColor: Color.blue,
|
|
116
|
+
boxShadow: "none",
|
|
117
|
+
},
|
|
118
|
+
focused: {
|
|
119
|
+
borderColor: Color.blue,
|
|
120
|
+
boxShadow: "none",
|
|
121
|
+
},
|
|
122
|
+
pressed: {
|
|
123
|
+
border: "2px solid #1B50B3",
|
|
124
|
+
background: `linear-gradient(0deg, rgba(24, 101, 242, 0.32), rgba(24, 101, 242, 0.32)), ${Color.white}`,
|
|
125
|
+
boxShadow: "none",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import NavigationPad from "./navigation-pad";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: "MathInput v2 Navigation Pad",
|
|
7
|
+
parameters: {
|
|
8
|
+
backgrounds: {
|
|
9
|
+
default: "light background",
|
|
10
|
+
values: [
|
|
11
|
+
// We want a slightly darker default bg so that we can
|
|
12
|
+
// see the top of the keypad when it is open
|
|
13
|
+
{name: "light background", value: "lightgrey", default: true},
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function basic() {
|
|
20
|
+
return (
|
|
21
|
+
<div style={{padding: 50}}>
|
|
22
|
+
<NavigationPad onClickKey={() => {}} />
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|