@khanacademy/math-input 0.4.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/{build/math-input.css → dist/es/index.css} +0 -150
  4. package/dist/es/index.js +7798 -0
  5. package/dist/es/index.js.map +1 -0
  6. package/dist/index.css +586 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +7768 -0
  9. package/dist/index.js.flow +2 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/strings.js +71 -0
  12. package/index.html +20 -0
  13. package/less/echo.less +56 -0
  14. package/less/main.less +5 -0
  15. package/less/overrides.less +129 -0
  16. package/less/popover.less +22 -0
  17. package/less/tabbar.less +6 -0
  18. package/package.json +60 -89
  19. package/src/actions/index.js +57 -0
  20. package/src/components/__tests__/gesture-state-machine_test.js +437 -0
  21. package/src/components/__tests__/node-manager_test.js +89 -0
  22. package/src/components/__tests__/two-page-keypad_test.js +42 -0
  23. package/src/components/app.js +73 -0
  24. package/src/components/common-style.js +47 -0
  25. package/src/components/compute-layout-parameters.js +157 -0
  26. package/src/components/corner-decal.js +56 -0
  27. package/src/components/echo-manager.js +160 -0
  28. package/src/components/empty-keypad-button.js +49 -0
  29. package/src/components/expression-keypad.js +323 -0
  30. package/src/components/fraction-keypad.js +176 -0
  31. package/src/components/gesture-manager.js +226 -0
  32. package/src/components/gesture-state-machine.js +283 -0
  33. package/src/components/icon.js +74 -0
  34. package/src/components/iconography/arrow.js +22 -0
  35. package/src/components/iconography/backspace.js +29 -0
  36. package/src/components/iconography/cdot.js +29 -0
  37. package/src/components/iconography/cos.js +30 -0
  38. package/src/components/iconography/cube-root.js +36 -0
  39. package/src/components/iconography/dismiss.js +25 -0
  40. package/src/components/iconography/divide.js +34 -0
  41. package/src/components/iconography/down.js +16 -0
  42. package/src/components/iconography/equal.js +33 -0
  43. package/src/components/iconography/exp-2.js +29 -0
  44. package/src/components/iconography/exp-3.js +29 -0
  45. package/src/components/iconography/exp.js +29 -0
  46. package/src/components/iconography/frac.js +44 -0
  47. package/src/components/iconography/geq.js +33 -0
  48. package/src/components/iconography/gt.js +33 -0
  49. package/src/components/iconography/index.js +45 -0
  50. package/src/components/iconography/jump-into-numerator.js +41 -0
  51. package/src/components/iconography/jump-out-base.js +30 -0
  52. package/src/components/iconography/jump-out-denominator.js +41 -0
  53. package/src/components/iconography/jump-out-exponent.js +30 -0
  54. package/src/components/iconography/jump-out-numerator.js +41 -0
  55. package/src/components/iconography/jump-out-parentheses.js +33 -0
  56. package/src/components/iconography/left-paren.js +33 -0
  57. package/src/components/iconography/left.js +16 -0
  58. package/src/components/iconography/leq.js +33 -0
  59. package/src/components/iconography/ln.js +29 -0
  60. package/src/components/iconography/log-n.js +29 -0
  61. package/src/components/iconography/log.js +29 -0
  62. package/src/components/iconography/lt.js +33 -0
  63. package/src/components/iconography/minus.js +32 -0
  64. package/src/components/iconography/neq.js +33 -0
  65. package/src/components/iconography/parens.js +33 -0
  66. package/src/components/iconography/percent.js +49 -0
  67. package/src/components/iconography/period.js +26 -0
  68. package/src/components/iconography/plus.js +32 -0
  69. package/src/components/iconography/radical.js +36 -0
  70. package/src/components/iconography/right-paren.js +33 -0
  71. package/src/components/iconography/right.js +16 -0
  72. package/src/components/iconography/sin.js +30 -0
  73. package/src/components/iconography/sqrt.js +32 -0
  74. package/src/components/iconography/tan.js +30 -0
  75. package/src/components/iconography/times.js +33 -0
  76. package/src/components/iconography/up.js +16 -0
  77. package/src/components/input/__tests__/context-tracking_test.js +177 -0
  78. package/src/components/input/__tests__/math-wrapper.jsx +33 -0
  79. package/src/components/input/__tests__/mathquill_test.js +747 -0
  80. package/src/components/input/cursor-contexts.js +29 -0
  81. package/src/components/input/cursor-handle.js +137 -0
  82. package/src/components/input/drag-listener.js +75 -0
  83. package/src/components/input/math-input.js +924 -0
  84. package/src/components/input/math-wrapper.js +959 -0
  85. package/src/components/input/scroll-into-view.js +72 -0
  86. package/src/components/keypad/button-assets.js +492 -0
  87. package/src/components/keypad/button.js +106 -0
  88. package/src/components/keypad/button.stories.js +29 -0
  89. package/src/components/keypad/index.js +64 -0
  90. package/src/components/keypad/keypad-page-items.js +106 -0
  91. package/src/components/keypad/keypad-pages.stories.js +32 -0
  92. package/src/components/keypad/keypad.stories.js +35 -0
  93. package/src/components/keypad/numeric-input-page.js +100 -0
  94. package/src/components/keypad/pre-algebra-page.js +98 -0
  95. package/src/components/keypad/trigonometry-page.js +90 -0
  96. package/src/components/keypad-button.js +366 -0
  97. package/src/components/keypad-container.js +303 -0
  98. package/src/components/keypad.js +154 -0
  99. package/src/components/many-keypad-button.js +44 -0
  100. package/src/components/math-icon.js +65 -0
  101. package/src/components/multi-symbol-grid.js +182 -0
  102. package/src/components/multi-symbol-popover.js +59 -0
  103. package/src/components/navigation-pad.js +139 -0
  104. package/src/components/node-manager.js +129 -0
  105. package/src/components/popover-manager.js +76 -0
  106. package/src/components/popover-state-machine.js +173 -0
  107. package/src/components/prop-types.js +82 -0
  108. package/src/components/provided-keypad.js +103 -0
  109. package/src/components/styles.js +38 -0
  110. package/src/components/svg-icon.js +25 -0
  111. package/src/components/tabbar/__tests__/tabbar_test.js +65 -0
  112. package/src/components/tabbar/icons.js +69 -0
  113. package/src/components/tabbar/item.js +138 -0
  114. package/src/components/tabbar/tabbar.js +61 -0
  115. package/src/components/tabbar/tabbar.stories.js +60 -0
  116. package/src/components/tabbar/types.js +3 -0
  117. package/src/components/text-icon.js +52 -0
  118. package/src/components/touchable-keypad-button.js +146 -0
  119. package/src/components/two-page-keypad.js +99 -0
  120. package/src/components/velocity-tracker.js +76 -0
  121. package/src/components/z-indexes.js +9 -0
  122. package/src/consts.js +74 -0
  123. package/src/data/key-configs.js +349 -0
  124. package/src/data/keys.js +72 -0
  125. package/src/demo.js +8 -0
  126. package/src/fake-react-native-web/index.js +12 -0
  127. package/src/fake-react-native-web/text.js +56 -0
  128. package/src/fake-react-native-web/view.js +91 -0
  129. package/src/index.js +14 -0
  130. package/src/native-app.js +84 -0
  131. package/src/store/index.js +505 -0
  132. package/src/utils.js +18 -0
  133. package/tools/svg-to-react/convert.py +111 -0
  134. package/tools/svg-to-react/icons/math-keypad-icon-0.svg +32 -0
  135. package/tools/svg-to-react/icons/math-keypad-icon-1.svg +32 -0
  136. package/tools/svg-to-react/icons/math-keypad-icon-2.svg +32 -0
  137. package/tools/svg-to-react/icons/math-keypad-icon-3.svg +32 -0
  138. package/tools/svg-to-react/icons/math-keypad-icon-4.svg +32 -0
  139. package/tools/svg-to-react/icons/math-keypad-icon-5.svg +32 -0
  140. package/tools/svg-to-react/icons/math-keypad-icon-6.svg +32 -0
  141. package/tools/svg-to-react/icons/math-keypad-icon-7.svg +32 -0
  142. package/tools/svg-to-react/icons/math-keypad-icon-8.svg +32 -0
  143. package/tools/svg-to-react/icons/math-keypad-icon-9.svg +32 -0
  144. package/tools/svg-to-react/icons/math-keypad-icon-addition.svg +34 -0
  145. package/tools/svg-to-react/icons/math-keypad-icon-cos.svg +38 -0
  146. package/tools/svg-to-react/icons/math-keypad-icon-delete.svg +36 -0
  147. package/tools/svg-to-react/icons/math-keypad-icon-dismiss.svg +36 -0
  148. package/tools/svg-to-react/icons/math-keypad-icon-division.svg +36 -0
  149. package/tools/svg-to-react/icons/math-keypad-icon-equals-not.svg +50 -0
  150. package/tools/svg-to-react/icons/math-keypad-icon-equals.svg +48 -0
  151. package/tools/svg-to-react/icons/math-keypad-icon-exponent-2.svg +38 -0
  152. package/tools/svg-to-react/icons/math-keypad-icon-exponent-3.svg +38 -0
  153. package/tools/svg-to-react/icons/math-keypad-icon-exponent.svg +38 -0
  154. package/tools/svg-to-react/icons/math-keypad-icon-fraction.svg +42 -0
  155. package/tools/svg-to-react/icons/math-keypad-icon-greater-than.svg +46 -0
  156. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-base.svg +44 -0
  157. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-denominator.svg +48 -0
  158. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-exponent.svg +44 -0
  159. package/tools/svg-to-react/icons/math-keypad-icon-jump-out-parentheses.svg +44 -0
  160. package/tools/svg-to-react/icons/math-keypad-icon-less-than.svg +46 -0
  161. package/tools/svg-to-react/icons/math-keypad-icon-log-10.svg +36 -0
  162. package/tools/svg-to-react/icons/math-keypad-icon-log-e.svg +36 -0
  163. package/tools/svg-to-react/icons/math-keypad-icon-log.svg +38 -0
  164. package/tools/svg-to-react/icons/math-keypad-icon-multiplication-cross.svg +40 -0
  165. package/tools/svg-to-react/icons/math-keypad-icon-multiplication-dot.svg +38 -0
  166. package/tools/svg-to-react/icons/math-keypad-icon-percent.svg +42 -0
  167. package/tools/svg-to-react/icons/math-keypad-icon-radical-2.svg +36 -0
  168. package/tools/svg-to-react/icons/math-keypad-icon-radical-3.svg +38 -0
  169. package/tools/svg-to-react/icons/math-keypad-icon-radical.svg +38 -0
  170. package/tools/svg-to-react/icons/math-keypad-icon-radix-character.svg +32 -0
  171. package/tools/svg-to-react/icons/math-keypad-icon-sin.svg +38 -0
  172. package/tools/svg-to-react/icons/math-keypad-icon-subtraction.svg +32 -0
  173. package/tools/svg-to-react/icons/math-keypad-icon-tan.svg +38 -0
  174. package/tools/svg-to-react/symbol_map.py +41 -0
  175. package/LICENSE.txt +0 -21
  176. package/build/math-input.js +0 -1
@@ -0,0 +1,154 @@
1
+ /**
2
+ * A keypad component that acts as a container for rows or columns of buttons,
3
+ * and manages the rendering of echo animations on top of those buttons.
4
+ */
5
+
6
+ import PropTypes from "prop-types";
7
+ import * as React from "react";
8
+ import ReactDOM from "react-dom";
9
+ import {connect} from "react-redux";
10
+
11
+ import {removeEcho} from "../actions/index.js";
12
+ import {View} from "../fake-react-native-web/index.js";
13
+
14
+ import EchoManager from "./echo-manager.js";
15
+ import PopoverManager from "./popover-manager.js";
16
+ import {echoPropType, popoverPropType} from "./prop-types.js";
17
+
18
+ // eslint-disable-next-line react/no-unsafe
19
+ class Keypad extends React.Component {
20
+ static propTypes = {
21
+ children: PropTypes.oneOfType([
22
+ PropTypes.arrayOf(PropTypes.node),
23
+ PropTypes.node,
24
+ ]),
25
+ removeEcho: PropTypes.func.isRequired,
26
+ style: PropTypes.any,
27
+
28
+ // The props below are injected by redux
29
+
30
+ // Whether the keypad is active, i.e., whether it should be rendered as
31
+ // visible or invisible.
32
+ active: PropTypes.bool,
33
+ echoes: PropTypes.arrayOf(echoPropType).isRequired,
34
+ popover: popoverPropType,
35
+ };
36
+
37
+ componentDidMount() {
38
+ this._isMounted = true;
39
+
40
+ window.addEventListener("resize", this._onResize);
41
+ this._updateSizeAndPosition();
42
+ }
43
+
44
+ UNSAFE_componentWillReceiveProps(newProps) {
45
+ if (!this._container && (newProps.popover || newProps.echoes.length)) {
46
+ this._computeContainer();
47
+ }
48
+ }
49
+
50
+ componentWillUnmount() {
51
+ this._isMounted = false;
52
+
53
+ window.removeEventListener("resize", this._onResize);
54
+ }
55
+
56
+ _computeContainer = () => {
57
+ const domNode = ReactDOM.findDOMNode(this);
58
+ this._container = domNode.getBoundingClientRect();
59
+ };
60
+
61
+ _updateSizeAndPosition = () => {
62
+ // Mark the container for recalculation next time the keypad is
63
+ // opened.
64
+ // TODO(charlie): Since we're not recalculating the container
65
+ // immediately, if you were to resize the page while a popover were
66
+ // active, you'd likely get unexpected behavior. This seems very
67
+ // difficult to do and, as such, incredibly unlikely, but we may
68
+ // want to reconsider the caching here.
69
+ this._container = null;
70
+ };
71
+
72
+ _onResize = () => {
73
+ // Whenever the page resizes, we need to recompute the container's
74
+ // bounding box. This is the only time that the bounding box can change.
75
+
76
+ // Throttle resize events -- taken from:
77
+ // https://developer.mozilla.org/en-US/docs/Web/Events/resize
78
+ if (this._resizeTimeout == null) {
79
+ this._resizeTimeout = setTimeout(() => {
80
+ this._resizeTimeout = null;
81
+
82
+ if (this._isMounted) {
83
+ this._updateSizeAndPosition();
84
+ }
85
+ }, 66);
86
+ }
87
+ };
88
+
89
+ render() {
90
+ const {children, echoes, removeEcho, popover, style} = this.props;
91
+
92
+ // Translate the echo boxes, as they'll be positioned absolutely to
93
+ // this relative container.
94
+ const relativeEchoes = echoes.map((echo) => {
95
+ const {initialBounds, ...rest} = echo;
96
+ return {
97
+ ...rest,
98
+ initialBounds: {
99
+ top: initialBounds.top - this._container.top,
100
+ right: initialBounds.right - this._container.left,
101
+ bottom: initialBounds.bottom - this._container.top,
102
+ left: initialBounds.left - this._container.left,
103
+ width: initialBounds.width,
104
+ height: initialBounds.height,
105
+ },
106
+ };
107
+ });
108
+
109
+ // Translate the popover bounds from page-absolute to keypad-relative.
110
+ // Note that we only need three bounds, since popovers are anchored to
111
+ // the bottom left corners of the keys over which they appear.
112
+ const relativePopover = popover && {
113
+ ...popover,
114
+ bounds: {
115
+ bottom:
116
+ this._container.height -
117
+ (popover.bounds.bottom - this._container.top),
118
+ left: popover.bounds.left - this._container.left,
119
+ width: popover.bounds.width,
120
+ },
121
+ };
122
+
123
+ return (
124
+ <View style={style}>
125
+ {children}
126
+ <EchoManager
127
+ echoes={relativeEchoes}
128
+ onAnimationFinish={removeEcho}
129
+ />
130
+ <PopoverManager popover={relativePopover} />
131
+ </View>
132
+ );
133
+ }
134
+ }
135
+
136
+ const mapStateToProps = (state) => {
137
+ return {
138
+ ...state.echoes,
139
+ active: state.keypad.active,
140
+ popover: state.gestures.popover,
141
+ };
142
+ };
143
+
144
+ const mapDispatchToProps = (dispatch) => {
145
+ return {
146
+ removeEcho: (animationId) => {
147
+ dispatch(removeEcho(animationId));
148
+ },
149
+ };
150
+ };
151
+
152
+ export default connect(mapStateToProps, mapDispatchToProps, null, {
153
+ forwardRef: true,
154
+ })(Keypad);
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A keypad button that displays an arbitrary number of symbols, with no
3
+ * 'default' symbol.
4
+ */
5
+
6
+ import PropTypes from "prop-types";
7
+ import * as React from "react";
8
+
9
+ import {KeyTypes} from "../consts.js";
10
+ import KeyConfigs from "../data/key-configs.js";
11
+ import Keys from "../data/keys.js";
12
+
13
+ import EmptyKeypadButton from "./empty-keypad-button.js";
14
+ import {keyIdPropType} from "./prop-types.js";
15
+ import TouchableKeypadButton from "./touchable-keypad-button.js";
16
+
17
+ class ManyKeypadButton extends React.Component {
18
+ static propTypes = {
19
+ keys: PropTypes.arrayOf(keyIdPropType).isRequired,
20
+ };
21
+
22
+ render() {
23
+ const {keys, ...rest} = this.props;
24
+
25
+ // If we have no extra symbols, render an empty button. If we have just
26
+ // one, render a standard button. Otherwise, capture them all in a
27
+ // single button.
28
+ if (keys.length === 0) {
29
+ return <EmptyKeypadButton {...rest} />;
30
+ } else if (keys.length === 1) {
31
+ const keyConfig = KeyConfigs[keys[0]];
32
+ return <TouchableKeypadButton keyConfig={keyConfig} {...rest} />;
33
+ } else {
34
+ const keyConfig = {
35
+ id: Keys.MANY,
36
+ type: KeyTypes.MANY,
37
+ childKeyIds: keys,
38
+ };
39
+ return <TouchableKeypadButton keyConfig={keyConfig} {...rest} />;
40
+ }
41
+ }
42
+ }
43
+
44
+ export default ManyKeypadButton;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * A component that renders an icon with math (via KaTeX).
3
+ */
4
+
5
+ import {StyleSheet} from "aphrodite";
6
+ import katex from "katex";
7
+ import PropTypes from "prop-types";
8
+ import * as React from "react";
9
+ import ReactDOM from "react-dom";
10
+
11
+ import {View} from "../fake-react-native-web/index.js";
12
+
13
+ import {iconSizeHeightPx, iconSizeWidthPx} from "./common-style.js";
14
+ import Styles from "./styles.js";
15
+
16
+ const {row, centered} = Styles;
17
+
18
+ class MathIcon extends React.Component {
19
+ static propTypes = {
20
+ math: PropTypes.string.isRequired,
21
+ style: PropTypes.any,
22
+ };
23
+
24
+ componentDidMount() {
25
+ this._renderMath();
26
+ }
27
+
28
+ componentDidUpdate(prevProps) {
29
+ if (prevProps.math !== this.props.math) {
30
+ this._renderMath();
31
+ }
32
+ }
33
+
34
+ _renderMath = () => {
35
+ const {math} = this.props;
36
+ katex.render(math, ReactDOM.findDOMNode(this));
37
+ };
38
+
39
+ render() {
40
+ const {style} = this.props;
41
+
42
+ const containerStyle = [
43
+ row,
44
+ centered,
45
+ styles.size,
46
+ styles.base,
47
+ ...(Array.isArray(style) ? style : [style]),
48
+ ];
49
+
50
+ return <View style={containerStyle} />;
51
+ }
52
+ }
53
+
54
+ const styles = StyleSheet.create({
55
+ size: {
56
+ height: iconSizeHeightPx,
57
+ width: iconSizeWidthPx,
58
+ },
59
+
60
+ base: {
61
+ fontSize: 25,
62
+ },
63
+ });
64
+
65
+ export default MathIcon;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * A grid of symbols, rendered as text and positioned based on the number of
3
+ * symbols provided. Up to four symbols will be shown.
4
+ */
5
+
6
+ import {StyleSheet} from "aphrodite";
7
+ import PropTypes from "prop-types";
8
+ import * as React from "react";
9
+
10
+ import {IconTypes} from "../consts.js";
11
+ import {View} from "../fake-react-native-web/index.js";
12
+
13
+ import {iconSizeHeightPx, iconSizeWidthPx} from "./common-style.js";
14
+ import Icon from "./icon.js";
15
+ import {iconPropType} from "./prop-types.js";
16
+ import Styles from "./styles.js";
17
+
18
+ const {row, column, centered, fullWidth} = Styles;
19
+
20
+ class MultiSymbolGrid extends React.Component {
21
+ static propTypes = {
22
+ focused: PropTypes.bool,
23
+ icons: PropTypes.arrayOf(iconPropType).isRequired,
24
+ };
25
+
26
+ render() {
27
+ const {focused, icons} = this.props;
28
+
29
+ // Validate that we only received math-based icons. Right now, this
30
+ // component only supports math icons (and it should only be passed
31
+ // variables and Greek letters, which are always rendered as math).
32
+ // Supporting other types of icons is possible but would require
33
+ // some styles coercion and doesn't seem worthwhile right now.
34
+ icons.forEach((icon) => {
35
+ if (icon.type !== IconTypes.MATH) {
36
+ throw new Error(
37
+ `Received invalid icon: type=${icon.type}, ` +
38
+ `data=${icon.data}`,
39
+ );
40
+ }
41
+ });
42
+
43
+ if (icons.length === 1) {
44
+ return <Icon icon={icons[0]} focused={focused} />;
45
+ } else {
46
+ const primaryIconStyle = styles.base;
47
+ const secondaryIconStyle = [styles.base, styles.secondary];
48
+
49
+ if (icons.length === 2) {
50
+ return (
51
+ <View style={[row, styles.size]}>
52
+ <View
53
+ style={[
54
+ column,
55
+ centered,
56
+ fullWidth,
57
+ styles.middleLeft,
58
+ ]}
59
+ >
60
+ <Icon
61
+ style={primaryIconStyle}
62
+ icon={icons[0]}
63
+ focused={focused}
64
+ />
65
+ </View>
66
+ <View
67
+ style={[
68
+ column,
69
+ centered,
70
+ fullWidth,
71
+ styles.middleRight,
72
+ ]}
73
+ >
74
+ <Icon
75
+ style={secondaryIconStyle}
76
+ icon={icons[1]}
77
+ focused={focused}
78
+ />
79
+ </View>
80
+ </View>
81
+ );
82
+ } else if (icons.length >= 3) {
83
+ return (
84
+ <View style={[column, styles.size]}>
85
+ <View style={row}>
86
+ <View style={[centered, fullWidth, styles.topLeft]}>
87
+ <Icon
88
+ style={primaryIconStyle}
89
+ icon={icons[0]}
90
+ focused={focused}
91
+ />
92
+ </View>
93
+ <View
94
+ style={[centered, fullWidth, styles.topRight]}
95
+ >
96
+ <Icon
97
+ style={secondaryIconStyle}
98
+ icon={icons[1]}
99
+ focused={focused}
100
+ />
101
+ </View>
102
+ </View>
103
+ <View style={row}>
104
+ <View
105
+ style={[centered, fullWidth, styles.bottomLeft]}
106
+ >
107
+ <Icon
108
+ style={secondaryIconStyle}
109
+ icon={icons[2]}
110
+ focused={focused}
111
+ />
112
+ </View>
113
+ <View
114
+ style={[
115
+ centered,
116
+ fullWidth,
117
+ styles.bottomRight,
118
+ ]}
119
+ >
120
+ {icons[3] && (
121
+ <Icon
122
+ style={secondaryIconStyle}
123
+ icon={icons[3]}
124
+ focused={focused}
125
+ />
126
+ )}
127
+ </View>
128
+ </View>
129
+ </View>
130
+ );
131
+ }
132
+ }
133
+
134
+ throw new Error("Invalid number of icons:", icons.length);
135
+ }
136
+ }
137
+
138
+ const verticalInsetPx = 2;
139
+ const horizontalInsetPx = 4;
140
+
141
+ const styles = StyleSheet.create({
142
+ size: {
143
+ height: iconSizeHeightPx,
144
+ width: iconSizeWidthPx,
145
+ },
146
+
147
+ // For the three- and four-icon layouts.
148
+ bottomLeft: {
149
+ marginBottom: verticalInsetPx,
150
+ marginLeft: horizontalInsetPx,
151
+ },
152
+ topLeft: {
153
+ marginTop: verticalInsetPx,
154
+ marginLeft: horizontalInsetPx,
155
+ },
156
+ topRight: {
157
+ marginTop: verticalInsetPx,
158
+ marginRight: horizontalInsetPx,
159
+ },
160
+ bottomRight: {
161
+ marginBottom: verticalInsetPx,
162
+ marginRight: horizontalInsetPx,
163
+ },
164
+
165
+ // For the two-icon layout.
166
+ middleLeft: {
167
+ marginLeft: horizontalInsetPx,
168
+ },
169
+ middleRight: {
170
+ marginRight: horizontalInsetPx,
171
+ },
172
+
173
+ base: {
174
+ fontSize: 18,
175
+ },
176
+
177
+ secondary: {
178
+ opacity: 0.3,
179
+ },
180
+ });
181
+
182
+ export default MultiSymbolGrid;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * A popover that renders a set of keys floating above the page.
3
+ */
4
+
5
+ import {StyleSheet} from "aphrodite";
6
+ import PropTypes from "prop-types";
7
+ import * as React from "react";
8
+
9
+ import {BorderStyles} from "../consts.js";
10
+ import {View} from "../fake-react-native-web/index.js";
11
+
12
+ import {keyConfigPropType} from "./prop-types.js";
13
+ import TouchableKeypadButton from "./touchable-keypad-button.js";
14
+ import * as zIndexes from "./z-indexes.js";
15
+
16
+ class MultiSymbolPopover extends React.Component {
17
+ static propTypes = {
18
+ keys: PropTypes.arrayOf(keyConfigPropType),
19
+ };
20
+
21
+ render() {
22
+ const {keys} = this.props;
23
+
24
+ // TODO(charlie): We have to require this lazily because of a cyclic
25
+ // dependence in our components.
26
+ return (
27
+ <View style={styles.container}>
28
+ {keys.map((key) => {
29
+ return (
30
+ <TouchableKeypadButton
31
+ key={key.id}
32
+ keyConfig={key}
33
+ borders={BorderStyles.NONE}
34
+ />
35
+ );
36
+ })}
37
+ </View>
38
+ );
39
+ }
40
+ }
41
+
42
+ const styles = StyleSheet.create({
43
+ container: {
44
+ flexDirection: "column-reverse",
45
+ position: "relative",
46
+ width: "100%",
47
+ borderRadius: 2,
48
+ boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3)",
49
+ zIndex: zIndexes.popover,
50
+ },
51
+
52
+ // eslint-disable-next-line react-native/no-unused-styles
53
+ popoverButton: {
54
+ backgroundColor: "#FFF",
55
+ borderWidth: 0,
56
+ },
57
+ });
58
+
59
+ export default MultiSymbolPopover;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * A component that renders a navigation pad, which consists of an arrow for
3
+ * each possible direction.
4
+ */
5
+
6
+ import {StyleSheet} from "aphrodite";
7
+ import PropTypes from "prop-types";
8
+ import * as React from "react";
9
+
10
+ import {BorderStyles} from "../consts.js";
11
+ import KeyConfigs from "../data/key-configs.js";
12
+ import {View} from "../fake-react-native-web/index.js";
13
+
14
+ import {
15
+ navigationPadWidthPx,
16
+ controlGrey,
17
+ valueGrey,
18
+ offBlack16,
19
+ } from "./common-style.js";
20
+ import Styles from "./styles.js";
21
+ import TouchableKeypadButton from "./touchable-keypad-button.js";
22
+
23
+ const {row, column, centered, stretch, roundedTopLeft} = Styles;
24
+
25
+ class NavigationPad extends React.Component {
26
+ static propTypes = {
27
+ roundTopLeft: PropTypes.bool,
28
+ style: PropTypes.any,
29
+ };
30
+
31
+ render() {
32
+ // TODO(charlie): Disable the navigational arrows depending on the
33
+ // cursor context.
34
+ const {roundTopLeft, style} = this.props;
35
+
36
+ const containerStyle = [
37
+ column,
38
+ centered,
39
+ styles.container,
40
+ roundTopLeft && roundedTopLeft,
41
+ ...(Array.isArray(style) ? style : [style]),
42
+ ];
43
+
44
+ return (
45
+ <View style={containerStyle}>
46
+ <View style={[row, centered]}>
47
+ <TouchableKeypadButton
48
+ keyConfig={KeyConfigs.UP}
49
+ borders={BorderStyles.NONE}
50
+ style={[styles.navigationKey, styles.topArrow]}
51
+ />
52
+ </View>
53
+ <View style={[row, centered, stretch]}>
54
+ <TouchableKeypadButton
55
+ keyConfig={KeyConfigs.LEFT}
56
+ borders={BorderStyles.NONE}
57
+ style={[styles.navigationKey, styles.leftArrow]}
58
+ />
59
+ <View style={styles.horizontalSpacer} />
60
+ <TouchableKeypadButton
61
+ keyConfig={KeyConfigs.RIGHT}
62
+ borders={BorderStyles.NONE}
63
+ style={[styles.navigationKey, styles.rightArrow]}
64
+ />
65
+ </View>
66
+ <View style={[row, centered]}>
67
+ <TouchableKeypadButton
68
+ keyConfig={KeyConfigs.DOWN}
69
+ borders={BorderStyles.NONE}
70
+ style={[styles.navigationKey, styles.bottomArrow]}
71
+ />
72
+ </View>
73
+ </View>
74
+ );
75
+ }
76
+ }
77
+
78
+ const buttonSizePx = 48;
79
+ const borderRadiusPx = 4;
80
+ const borderWidthPx = 1;
81
+
82
+ const styles = StyleSheet.create({
83
+ container: {
84
+ backgroundColor: controlGrey,
85
+ width: navigationPadWidthPx,
86
+ },
87
+
88
+ navigationKey: {
89
+ borderColor: offBlack16,
90
+ backgroundColor: valueGrey,
91
+ width: buttonSizePx,
92
+ height: buttonSizePx,
93
+
94
+ // Override the default box-sizing so that our buttons are
95
+ // `buttonSizePx` exclusive of their borders.
96
+ boxSizing: "content-box",
97
+ },
98
+
99
+ topArrow: {
100
+ borderTopWidth: borderWidthPx,
101
+ borderLeftWidth: borderWidthPx,
102
+ borderRightWidth: borderWidthPx,
103
+ borderTopLeftRadius: borderRadiusPx,
104
+ borderTopRightRadius: borderRadiusPx,
105
+ },
106
+
107
+ rightArrow: {
108
+ borderTopWidth: borderWidthPx,
109
+ borderRightWidth: borderWidthPx,
110
+ borderBottomWidth: borderWidthPx,
111
+ borderTopRightRadius: borderRadiusPx,
112
+ borderBottomRightRadius: borderRadiusPx,
113
+ },
114
+
115
+ bottomArrow: {
116
+ borderBottomWidth: borderWidthPx,
117
+ borderLeftWidth: borderWidthPx,
118
+ borderRightWidth: borderWidthPx,
119
+ borderBottomLeftRadius: borderRadiusPx,
120
+ borderBottomRightRadius: borderRadiusPx,
121
+ },
122
+
123
+ leftArrow: {
124
+ borderTopWidth: borderWidthPx,
125
+ borderBottomWidth: borderWidthPx,
126
+ borderLeftWidth: borderWidthPx,
127
+ borderTopLeftRadius: borderRadiusPx,
128
+ borderBottomLeftRadius: borderRadiusPx,
129
+ },
130
+
131
+ horizontalSpacer: {
132
+ background: valueGrey,
133
+ // No need to set a height -- the spacer will be stretched by its
134
+ // parent.
135
+ width: buttonSizePx,
136
+ },
137
+ });
138
+
139
+ export default NavigationPad;