@dvrd/dvr-controls 1.0.44 → 1.0.46

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.
@@ -5,7 +5,7 @@ import './style/dvrdDatePicker.scss';
5
5
 
6
6
  import classNames from 'classnames';
7
7
  import {Moment} from 'moment';
8
- import React, {PureComponent, useMemo, useState} from 'react';
8
+ import React, {MouseEventHandler, useEffect, useMemo, useRef, useState} from 'react';
9
9
  import {ChangeFunction, ErrorType} from '../util/interfaces';
10
10
  import AwesomeIcon from '../icon/awesomeIcon';
11
11
  import {toMoment} from '../util/momentUtil';
@@ -26,12 +26,14 @@ interface Props {
26
26
  alwaysShowArrows?: boolean;
27
27
  useMobileNative?: boolean;
28
28
  id?: string;
29
+ max?: Moment | string;
30
+ min?: Moment | string;
29
31
  }
30
32
 
31
33
  export default function DvrdDatePicker(props: Props) {
32
34
  const {
33
35
  onChange, className, closeOnChange, error, label, value, placeholder, disabled, alwaysShowArrows,
34
- useMobileNative
36
+ useMobileNative, max, min
35
37
  } = props;
36
38
  const [pickerOpen, setPickerOpen] = useState(false);
37
39
  const dateFormat = useMemo(() => {
@@ -80,7 +82,7 @@ export default function DvrdDatePicker(props: Props) {
80
82
  <AwesomeIcon name='calendar-alt' className='calendar-icon'/>
81
83
  </div>
82
84
  <Picker onClose={onClosePicker} onChange={onChangePicker} open={pickerOpen} value={value}
83
- alwaysShowArrows={alwaysShowArrows}/>
85
+ alwaysShowArrows={alwaysShowArrows} max={max} min={min}/>
84
86
  <label className='error-label'>{error}</label>
85
87
  </div>
86
88
  )
@@ -92,121 +94,162 @@ interface PickerProps {
92
94
  value: Moment | null;
93
95
  open: boolean;
94
96
  alwaysShowArrows?: boolean;
97
+ max?: Moment | string;
98
+ min?: Moment | string;
95
99
  }
96
100
 
97
- class Picker extends PureComponent<PickerProps> {
98
- private ref: HTMLDivElement | null = null;
101
+ function Picker(props: PickerProps) {
102
+ const {min, max, alwaysShowArrows, open, onClose, value, onChange} = props;
103
+ const divRef = useRef<HTMLDivElement>(null);
104
+ const pickerValue = useMemo(() => toMoment(value), [value]);
105
+ const [padBeforeDays, days, padAfterDays] = useMemo(() => {
106
+ const daysInMonth = pickerValue.daysInMonth();
107
+ const days: number[] = [];
108
+ const padBeforeDays: number[] = [];
109
+ const padAfterDays: number[] = [];
110
+ for (let i = 1; i <= daysInMonth; i++) days.push(i);
111
+ let firstMonthDay = pickerValue.clone().date(1).day();
112
+ if (!firstMonthDay) firstMonthDay = 7;
113
+ const padDaysBefore = firstMonthDay - 1;
114
+ let dayBefore = pickerValue.clone().add(-1, 'month').daysInMonth()
115
+ for (let i = 0; i < padDaysBefore; i++)
116
+ padBeforeDays.splice(0, 0, dayBefore--);
99
117
 
100
- getPickerValue = (): Moment => toMoment(this.props.value);
118
+ const lastWeekDay = pickerValue.clone().date(daysInMonth).day();
119
+ const padDaysAfter = 7 - lastWeekDay;
120
+ for (let i = 0; i < padDaysAfter; i++)
121
+ padAfterDays.push(i + 1);
101
122
 
102
- onReduce = (key: 'year' | 'month') => () => {
103
- const {onChange} = this.props, pickerValue = this.getPickerValue();
104
- pickerValue.add(-1, key);
105
- pickerValue.date(Math.min(pickerValue.daysInMonth(), pickerValue.date()))
106
- onChange(false)(pickerValue);
107
- };
123
+ if (Math.ceil(padAfterDays.concat(days).concat(padBeforeDays).length / 7) < 6) {
124
+ let dayAfter = padAfterDays[padAfterDays.length - 1] + 1;
125
+ for (let i = 0; i < 7; i++)
126
+ padAfterDays.push(dayAfter++);
127
+ }
128
+ return [padBeforeDays, days, padAfterDays];
129
+ }, [pickerValue]);
130
+
131
+ function onReduce(key: 'year' | 'month') {
132
+ return function () {
133
+ const value = pickerValue.clone();
134
+ value.add(-1, key);
135
+ value.date(Math.min(value.daysInMonth(), value.date()))
136
+ onChange(false)(value);
137
+ }
138
+ }
108
139
 
109
- onAdd = (key: 'year' | 'month') => () => {
110
- const {onChange} = this.props, pickerValue = this.getPickerValue();
111
- pickerValue.add(1, key);
112
- pickerValue.date(Math.min(pickerValue.daysInMonth(), pickerValue.date()))
113
- onChange(false)(pickerValue);
114
- };
140
+ function onAdd(key: 'year' | 'month') {
141
+ return function () {
142
+ const value = pickerValue.clone();
143
+ value.add(1, key);
144
+ value.date(Math.min(value.daysInMonth(), value.date()))
145
+ onChange(false)(value);
146
+ }
147
+ }
115
148
 
116
- onResetSwitcher = (key: 'year' | 'month') => () => {
117
- const {onChange} = this.props, pickerValue = this.getPickerValue();
118
- if (key === 'year') pickerValue.year(toMoment().year());
119
- else pickerValue.month(toMoment().month());
120
- pickerValue.date(Math.min(pickerValue.daysInMonth(), pickerValue.date()))
121
- onChange(false)(pickerValue);
149
+ function onResetSwitcher(key: 'year' | 'month') {
150
+ return function () {
151
+ const value = pickerValue.clone();
152
+ if (key === 'year') value.year(toMoment().year());
153
+ else value.month(toMoment().month());
154
+ value.date(Math.min(value.daysInMonth(), value.date()))
155
+ onChange(false)(value);
156
+ }
122
157
  }
123
158
 
124
- onClickToday = () => {
125
- const {onChange} = this.props;
159
+ function onClickToday() {
126
160
  onChange(true)(toMoment());
127
- };
161
+ }
128
162
 
129
- onSelectDay = (day: number, month?: number, year?: number) => () => {
130
- const {onChange, value} = this.props, pickerValue = this.getPickerValue();
131
- if (day || value === null) {
132
- pickerValue.date(day);
133
- if (month !== undefined) pickerValue.month(month);
134
- if (year !== undefined) pickerValue.year(year);
135
- onChange(true)(pickerValue);
163
+ function onSelectDay(day: number, month?: number, year?: number) {
164
+ return function () {
165
+ const pickValue = pickerValue.clone();
166
+ if (day || value === null) {
167
+ pickValue.date(day);
168
+ if (month !== undefined) pickValue.month(month);
169
+ if (year !== undefined) pickValue.year(year);
170
+ onChange(true)(pickValue);
171
+ }
136
172
  }
137
173
  }
138
174
 
139
- onSelectPadDay = (day: number, kind: 'prev' | 'next') => () => {
140
- const {onChange} = this.props, pickerValue = this.getPickerValue();
141
- const value = pickerValue.clone();
142
- if (kind === 'prev') value.add(-1, 'month');
143
- else value.add(1, 'month');
144
- value.date(day);
145
- onChange(true)(value);
146
- };
175
+ function onSelectPadDay(day: number, kind: 'prev' | 'next') {
176
+ return function () {
177
+ const value = pickerValue.clone();
178
+ if (kind === 'prev') value.add(-1, 'month');
179
+ else value.add(1, 'month');
180
+ value.date(day);
181
+ onChange(true)(value);
182
+ }
183
+ }
147
184
 
148
- keyListener = (evt: KeyboardEvent) => {
149
- const {key, shiftKey, ctrlKey} = evt, {onClose} = this.props;
185
+ function keyListener(evt: KeyboardEvent) {
186
+ const {key, shiftKey, ctrlKey} = evt;
150
187
  if (['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'Enter'].includes(key)) {
151
188
  evt.preventDefault();
152
189
  }
153
- if (key === 'ArrowLeft') {
154
- this.onLeftKey(shiftKey, ctrlKey);
155
- } else if (key === 'ArrowRight') {
156
- this.onRightKey(shiftKey, ctrlKey);
157
- } else if (key === 'ArrowUp') {
158
- this.onUpKey();
159
- } else if (key === 'ArrowDown') {
160
- this.onDownKey();
161
- } else if (key === 'Enter') {
162
- onClose();
163
- }
164
- };
190
+ if (key === 'ArrowLeft') onLeftKey(shiftKey, ctrlKey);
191
+ else if (key === 'ArrowRight') onRightKey(shiftKey, ctrlKey);
192
+ else if (key === 'ArrowUp') onUpKey();
193
+ else if (key === 'ArrowDown') onDownKey();
194
+ else if (key === 'Enter') onClose();
195
+ }
165
196
 
166
- onLeftKey = (shiftKey: boolean, ctrlKey: boolean) => {
167
- const {onChange} = this.props, pickerValue = this.getPickerValue()
197
+ function onLeftKey(shiftKey: boolean, ctrlKey: boolean) {
198
+ const value = pickerValue.clone();
168
199
  if (shiftKey) {
169
- if (ctrlKey) this.onReduce('year')();
170
- else this.onReduce('month')();
171
- } else onChange(false)(pickerValue.add(-1, 'd'));
200
+ if (ctrlKey) onReduce('year')();
201
+ else onReduce('month')();
202
+ } else onChange(false)(value.add(-1, 'days'));
172
203
  }
173
204
 
174
- onRightKey = (shiftKey: boolean, ctrlKey: boolean) => {
175
- const {onChange} = this.props, pickerValue = this.getPickerValue();
205
+ function onRightKey(shiftKey: boolean, ctrlKey: boolean) {
206
+ const value = pickerValue.clone();
176
207
  if (shiftKey) {
177
- if (ctrlKey) this.onAdd('year')();
178
- else this.onAdd('month')();
179
- } else onChange(false)(pickerValue.add(1, 'd'));
208
+ if (ctrlKey) onAdd('year')();
209
+ else onAdd('month')();
210
+ } else onChange(false)(value.add(1, 'days'));
180
211
  }
181
212
 
182
- onUpKey = () => {
183
- const {onChange} = this.props, pickerValue = this.getPickerValue();
184
- onChange(false)(pickerValue.add(-7, 'd'));
213
+ function onUpKey() {
214
+ const value = pickerValue.clone();
215
+ onChange(false)(value.add(-7, 'days'));
185
216
  }
186
217
 
187
- onDownKey = () => {
188
- const {onChange} = this.props, pickerValue = this.getPickerValue();
189
- onChange(false)(pickerValue.add(7, 'd'));
218
+ function onDownKey() {
219
+ const value = pickerValue.clone();
220
+ onChange(false)(value.add(7, 'days'));
190
221
  }
191
222
 
192
- addMountClass = () => {
193
- if (!this.ref?.classList.contains('switch-mount'))
194
- this.ref?.classList.add('switch-mount');
195
- };
223
+ function addMountClass() {
224
+ const div = divRef.current;
225
+ if (div?.classList.contains('switch-mount'))
226
+ div.classList.add('switch-mount');
227
+ }
196
228
 
197
- removeMountClass = () => {
198
- if (!this.props.alwaysShowArrows)
199
- this.ref?.classList.remove('switch-mount');
200
- };
229
+ function removeMountClass() {
230
+ if (!alwaysShowArrows)
231
+ divRef.current?.classList.remove('switch-mount');
232
+ }
233
+
234
+ function dateIsDisabled(day: number, month?: number, year?: number) {
235
+ if (!min && !max) return false;
236
+ const pickValue = pickerValue.clone();
237
+ pickValue.date(day);
238
+ if (month !== undefined) pickValue.month(month);
239
+ if (year !== undefined) pickValue.year(year);
240
+ if (min && toMoment(min).isAfter(pickValue, 'date')) return true;
241
+ else if (max && toMoment(max).isBefore(pickValue, 'date')) return true;
242
+ return false;
243
+ }
201
244
 
202
- renderSwitcher = (key: 'year' | 'month', kind: 'add' | 'reduce') => {
203
- const icon = kind === 'add' ? 'chevron-right' : 'chevron-left',
204
- title = kind === 'add' ? 'Volgende' : 'Vorige',
205
- onClick = kind === 'add' ? this.onAdd(key) : this.onReduce(key);
245
+ function renderSwitcher(key: 'year' | 'month', kind: 'add' | 'reduce') {
246
+ const icon = kind === 'add' ? 'chevron-right' : 'chevron-left';
247
+ const title = kind === 'add' ? 'Volgende' : 'Vorige';
248
+ const onClick = kind === 'add' ? onAdd(key) : onReduce(key);
206
249
  return <AwesomeIcon name={icon} title={title} className={classNames('switcher-icon', kind)} onClick={onClick}/>
207
250
  }
208
251
 
209
- renderTooltipContainer = () => {
252
+ function renderTooltipContainer() {
210
253
  return (
211
254
  <div className='tooltip-container'>
212
255
  <AwesomeIcon name='question-circle' className='tooltip-icon' prefix='far'/>
@@ -269,113 +312,94 @@ class Picker extends PureComponent<PickerProps> {
269
312
  )
270
313
  }
271
314
 
272
- componentDidUpdate = (prevProps: PickerProps) => {
273
- const {open} = this.props, prevOpen = prevProps.open;
274
- if (open && !prevOpen) {
275
- document.addEventListener('keydown', this.keyListener);
276
- this.addMountClass();
277
- window.setTimeout(this.removeMountClass, 1000);
278
- } else if (!open && prevOpen) document.removeEventListener('keydown', this.keyListener);
279
- };
280
-
281
- componentWillUnmount = () => {
282
- document.removeEventListener('keydown', this.keyListener);
283
- };
284
-
285
- render = () => {
286
- const {open, onClose} = this.props, pickerValue = this.getPickerValue(),
287
- daysInMonth = pickerValue.daysInMonth(), days: number[] = [], padBeforeDays: number[] = [],
288
- padAfterDays: number[] = [];
289
- for (let i = 1; i <= daysInMonth; i++) days.push(i);
290
- let firstMonthDay = pickerValue.clone().date(1).day();
291
- if (!firstMonthDay) firstMonthDay = 7;
292
- const padDaysBefore = firstMonthDay - 1;
293
- let dayBefore = pickerValue.clone().add(-1, 'month').daysInMonth()
294
- for (let i = 0; i < padDaysBefore; i++)
295
- padBeforeDays.splice(0, 0, dayBefore--);
296
-
297
- const lastWeekDay = pickerValue.clone().date(daysInMonth).day();
298
- const padDaysAfter = 7 - lastWeekDay;
299
- for (let i = 0; i < padDaysAfter; i++)
300
- padAfterDays.push(i + 1);
301
-
302
- if (Math.ceil(padAfterDays.concat(days).concat(padBeforeDays).length / 7) < 6) {
303
- let dayAfter = padAfterDays[padAfterDays.length - 1] + 1;
304
- for (let i = 0; i < 7; i++)
305
- padAfterDays.push(dayAfter++);
315
+ useEffect(() => {
316
+ if (open) {
317
+ document.addEventListener('keydown', keyListener);
318
+ addMountClass();
319
+ window.setTimeout(removeMountClass, 1000);
320
+ } else {
321
+ document.removeEventListener('keydown', keyListener);
306
322
  }
307
-
323
+ return function () {
324
+ document.removeEventListener('keydown', keyListener);
325
+ }
326
+ }, [open]);
327
+
328
+ function renderDay(day: number, index: number, onClick: MouseEventHandler, className?: string, type?: 'prev' | 'next') {
329
+ let pickValue = pickerValue.clone();
330
+ if (type === 'prev')
331
+ pickValue = pickValue.subtract(1, 'month');
332
+ else if (type === 'next')
333
+ pickValue = pickValue.add(1, 'month');
334
+ const month = pickValue.month();
335
+ const year = pickValue.year();
336
+ const disabled = dateIsDisabled(day, month, year);
308
337
  return (
309
- <WithBackground active={open} onClose={onClose}>
310
- <div className='picker' ref={(ref: HTMLDivElement) => {
311
- this.ref = ref
312
- }}>
313
- <div className='switcher year'>
314
- {this.renderSwitcher('year', 'reduce')}
315
- <label className='switcher-label'
316
- onClick={this.onResetSwitcher('year')}>{pickerValue.year()}</label>
317
- {this.renderSwitcher('year', 'add')}
338
+ <div className={classNames('day', disabled && 'disabled', className)}
339
+ onClick={disabled ? undefined : onClick} key={index}>
340
+ <span className='day-label'>{day}</span>
341
+ </div>
342
+ )
343
+ }
344
+
345
+ return (
346
+ <WithBackground active={open} onClose={onClose}>
347
+ <div className='picker' ref={divRef}>
348
+ <div className='switcher year'>
349
+ {renderSwitcher('year', 'reduce')}
350
+ <label className='switcher-label'
351
+ onClick={onResetSwitcher('year')}>{pickerValue.year()}</label>
352
+ {renderSwitcher('year', 'add')}
353
+ </div>
354
+ <div className='switcher month'>
355
+ {renderSwitcher('month', 'reduce')}
356
+ <label className='switcher-label' onClick={onResetSwitcher('month')}>{pickerValue.format(
357
+ 'MMMM')}</label>
358
+ {renderSwitcher('month', 'add')}
359
+ </div>
360
+ <div className='current-date-container'>
361
+ <label className='current-date-label'>{pickerValue.format('dddd D MMMM YYYY')}</label>
362
+ {renderTooltipContainer()}
363
+ </div>
364
+ <div className='days-container'>
365
+ <div className='day no-select'>
366
+ <span className='day-label'>Ma</span>
318
367
  </div>
319
- <div className='switcher month'>
320
- {this.renderSwitcher('month', 'reduce')}
321
- <label className='switcher-label' onClick={this.onResetSwitcher('month')}>{pickerValue.format(
322
- 'MMMM')}</label>
323
- {this.renderSwitcher('month', 'add')}
368
+ <div className='day no-select'>
369
+ <span className='day-label'>Di</span>
324
370
  </div>
325
- <div className='current-date-container'>
326
- <label className='current-date-label'>{pickerValue.format('dddd D MMMM YYYY')}</label>
327
- {this.renderTooltipContainer()}
371
+ <div className='day no-select'>
372
+ <span className='day-label'>Wo</span>
328
373
  </div>
329
- <div className='days-container'>
330
- <div className='day no-select'>
331
- <span className='day-label'>Ma</span>
332
- </div>
333
- <div className='day no-select'>
334
- <span className='day-label'>Di</span>
335
- </div>
336
- <div className='day no-select'>
337
- <span className='day-label'>Wo</span>
338
- </div>
339
- <div className='day no-select'>
340
- <span className='day-label'>Do</span>
341
- </div>
342
- <div className='day no-select'>
343
- <span className='day-label'>Vr</span>
344
- </div>
345
- <div className='day no-select'>
346
- <span className='day-label'>Za</span>
347
- </div>
348
- <div className='day no-select'>
349
- <span className='day-label'>Zo</span>
350
- </div>
351
- {padBeforeDays.map((day: number, index: number) => (
352
- <div className={classNames('day', 'pad')} onClick={this.onSelectPadDay(day, 'prev')}
353
- key={index}>
354
- <span className='day-label'>{day}</span>
355
- </div>
356
- ))}
357
- {days.map((day: number, index: number) => {
358
- const isSelected = day === pickerValue.date();
359
- return (
360
- <div className={classNames('day', isSelected && 'selected', day === 0 && 'hide')}
361
- key={index} onClick={this.onSelectDay(day)}>
362
- <span className='day-label'>{day}</span>
363
- </div>
364
- )
365
- })}
366
- {padAfterDays.map((day: number, index: number) => (
367
- <div className={classNames('day', 'pad')} onClick={this.onSelectPadDay(day, 'next')}
368
- key={index}>
369
- <span className='day-label'>{day}</span>
370
- </div>
371
- ))}
374
+ <div className='day no-select'>
375
+ <span className='day-label'>Do</span>
376
+ </div>
377
+ <div className='day no-select'>
378
+ <span className='day-label'>Vr</span>
379
+ </div>
380
+ <div className='day no-select'>
381
+ <span className='day-label'>Za</span>
372
382
  </div>
373
- <div className='actions-container'>
374
- <DvrdButton onClick={this.onClickToday} label='Vandaag' className='action'/>
375
- <DvrdButton onClick={onClose} label='Sluiten' secondary className='action'/>
383
+ <div className='day no-select'>
384
+ <span className='day-label'>Zo</span>
376
385
  </div>
386
+ {padBeforeDays.map((day: number, index: number) => {
387
+ return renderDay(day, index, onSelectPadDay(day, 'prev'), 'pad', 'prev');
388
+ })}
389
+ {days.map((day: number, index: number) => {
390
+ const isSelected = day === pickerValue.date();
391
+ return renderDay(day, index, onSelectDay(day),
392
+ classNames(isSelected && 'selected', day === 0 && 'hide'));
393
+ })}
394
+ {padAfterDays.map((day: number, index: number) => {
395
+ return renderDay(day, index, onSelectPadDay(day, 'next'), 'pad', 'next');
396
+ })}
377
397
  </div>
378
- </WithBackground>
379
- )
380
- }
398
+ <div className='actions-container'>
399
+ <DvrdButton onClick={onClickToday} label='Vandaag' className='action'/>
400
+ <DvrdButton onClick={onClose} label='Sluiten' secondary className='action'/>
401
+ </div>
402
+ </div>
403
+ </WithBackground>
404
+ )
381
405
  }
@@ -254,6 +254,12 @@
254
254
  &:hover {
255
255
  background-color: rgba($color-blue-1, .2);
256
256
  }
257
+
258
+ &.disabled {
259
+ color: $color-gray-4;
260
+ background-color: unset;
261
+ cursor: default;
262
+ }
257
263
  }
258
264
 
259
265
  .extra-row {