UNPKG

7.43 kBJavaScriptView Raw
1var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
2
3import { h, Component } from 'preact';
4import { Icon } from './icon';
5import { Button } from './button';
6import cn from 'classnames';
7import omit from './utils/omit';
8import { FullScreenOverlay } from './full-screen-overlay';
9
10const cellStyle = {
11 width: '80px',
12 height: '80px'
13};
14
15const stopEvent = e => {
16 e.preventDefault();
17 e.stopImmediatePropagation();
18};
19
20const quickTap = e => {
21 e.preventDefault();
22 e.target.click();
23};
24
25const NumberCell = ({ number, callback }) => h(
26 'td',
27 null,
28 h(
29 Button,
30 {
31 onClick: e => {
32 e.stopImmediatePropagation();
33 callback(number);
34 },
35 onTouchStart: quickTap,
36 style: cellStyle
37 },
38 number
39 )
40);
41
42export class Numpad extends Component {
43 constructor(props) {
44 super(props);
45 this.addNumber = this.addNumber.bind(this);
46 this.backspace = this.backspace.bind(this);
47 this.close = this.close.bind(this);
48 this.onKey = this.onKey.bind(this);
49 this.state = {
50 value: '0'
51 };
52 }
53
54 addNumber(number) {
55 const { value } = this.state;
56 const { max, maxDecimals } = this.props;
57 if (number === '.' && value.includes('.')) {
58 return;
59 }
60 let newNumber = this.state.value + '' + number;
61 if (newNumber.slice(0, 1) === '0') {
62 newNumber = newNumber.slice(1);
63 }
64 const decimals = newNumber.split('.')[1];
65 if (decimals && decimals.length > maxDecimals) {
66 return;
67 }
68 if (max && Number(newNumber) > max) {
69 return;
70 }
71 this.setState({ value: newNumber });
72 }
73
74 backspace(e) {
75 if (e) e.stopImmediatePropagation();
76 const number = (this.state.value + '').slice(0, -1);
77 this.setState({ value: number || '0' });
78 }
79
80 onKey(e) {
81 const value = e.which - 48;
82 if (value <= 9 && value >= 0) {
83 this.addNumber(value);
84 } else if (e.which === 8 || e.which === 48) {
85 this.backspace();
86 } else if (e.which === 27) {
87 this.close();
88 } else if (e.which === 13) {
89 this.close(this.state.value);
90 } else if (e.which === 190) {
91 this.addNumber('.');
92 }
93 }
94
95 componentDidMount() {
96 window.addEventListener('keydown', this.onKey);
97 }
98
99 componentWillUnmount() {
100 window.removeEventListener('keydown', this.onKey);
101 }
102
103 close(value) {
104 if (typeof value === 'object') {
105 value = undefined;
106 }
107 this.props.onClose(value !== undefined ? Number(value) : undefined);
108 }
109
110 render(props, { value }) {
111 const { unit, className, max } = props;
112 const rest = omit(props, ['unit', 'className', 'max']);
113 return h(
114 'div',
115 _extends({
116 onClick: stopEvent,
117 className: cn('ba br2 b--gray bg-light-gray pa2 shadow-2 top-0 left-0', className)
118 }, rest),
119 h(
120 'div',
121 { className: 'flex items-center justify-between' },
122 h(
123 'div',
124 { className: 'ml2' },
125 max && `max: ${max}`
126 ),
127 h(
128 'div',
129 { className: 'flex items-center justify-end' },
130 h(
131 'span',
132 { className: 'fw6 f4 mr2' },
133 value,
134 ' ',
135 unit
136 ),
137 h(
138 'button',
139 {
140 className: 'bn bg-transparent',
141 onClick: this.backspace,
142 onTouchStart: e => {
143 e.preventDefault();
144 e.target.click();
145 },
146 style: cellStyle
147 },
148 h(Icon, { icon: 'backspace' })
149 )
150 )
151 ),
152 h(
153 'table',
154 null,
155 h(
156 'tr',
157 null,
158 h(NumberCell, { callback: this.addNumber, number: '1' }),
159 h(NumberCell, { callback: this.addNumber, number: '2' }),
160 h(NumberCell, { callback: this.addNumber, number: '3' }),
161 h(
162 'td',
163 { style: cellStyle },
164 h(
165 'div',
166 { className: 'relative', style: cellStyle },
167 h(
168 Button,
169 {
170 style: {
171 width: '80px',
172 height: '162px',
173 top: '1px'
174 },
175 stop: true,
176 className: 'absolute top-0 left-0',
177 onClick: () => this.close()
178 },
179 'Cancel'
180 )
181 )
182 )
183 ),
184 h(
185 'tr',
186 null,
187 h(NumberCell, { callback: this.addNumber, number: '4' }),
188 h(NumberCell, { callback: this.addNumber, number: '5' }),
189 h(NumberCell, { callback: this.addNumber, number: '6' }),
190 h('td', null)
191 ),
192 h(
193 'tr',
194 null,
195 h(NumberCell, { callback: this.addNumber, number: '7' }),
196 h(NumberCell, { callback: this.addNumber, number: '8' }),
197 h(NumberCell, { callback: this.addNumber, number: '9' }),
198 h(
199 'td',
200 { style: cellStyle },
201 h(
202 'div',
203 { className: 'relative', style: cellStyle },
204 h(
205 Button,
206 {
207 style: {
208 width: '80px',
209 height: '162px',
210 top: '1px'
211 },
212 primary: true,
213 className: 'absolute left-0',
214 onClick: () => {
215 this.close(value);
216 }
217 },
218 'Save'
219 )
220 )
221 )
222 ),
223 h(
224 'tr',
225 null,
226 h(NumberCell, {
227 callback: () => this.setState({ value: '0' }),
228 number: 'Clear'
229 }),
230 h(NumberCell, { callback: this.addNumber, number: '0' }),
231 h(NumberCell, { callback: this.addNumber, number: '.' }),
232 h('td', null)
233 )
234 )
235 );
236 }
237}
238
239export class NumpadOverlay extends Component {
240 componentDidMount() {
241 setTimeout(() => {
242 document.activeElement.blur();
243 }, 200);
244 }
245
246 render({ unit = 'mg', onValue, onCancel, max = null, maxDecimals = 2 }) {
247 return h(
248 FullScreenOverlay,
249 null,
250 h(Numpad, {
251 unit: unit,
252 max: max,
253 maxDecimals: maxDecimals,
254 onClose: value => {
255 if (value !== undefined) {
256 onValue(value);
257 } else {
258 onCancel();
259 }
260 }
261 })
262 );
263 }
264}
265
266export class NumpadButton extends Component {
267 constructor(props) {
268 super(props);
269 this.state = {
270 open: false
271 };
272 }
273
274 render({ unit, max, maxDecimals, onValue, onCancel }, { open }) {
275 return h(
276 Button,
277 {
278 onClick: () => {
279 this.setState({ open: true });
280 }
281 },
282 h(Icon, { icon: 'dialpad' }),
283 open && h(NumpadOverlay, {
284 unit: unit,
285 max: max,
286 maxDecimals: maxDecimals,
287 onCancel: () => {
288 onCancel && onCancel();
289 this.setState({ open: false });
290 },
291 onValue: val => {
292 onValue(val);
293 this.setState({ open: false });
294 }
295 })
296 );
297 }
298}
\No newline at end of file