@ta-interaktiv/react-municipality-search 1.4.1 → 1.5.3

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.
@@ -3,12 +3,9 @@ import React, { ChangeEvent, Component } from 'react'
3
3
  import '@ta-interaktiv/semantic-ui/semantic/dist/components/icon.css'
4
4
  import '@ta-interaktiv/semantic-ui/semantic/dist/components/input.css'
5
5
  import '@ta-interaktiv/semantic-ui/semantic/dist/components/label.css'
6
- import styles from './styles.module.css'
7
- import {
8
- animated,
9
- config as springConfig,
10
- Transition,
11
- } from 'react-spring'
6
+ // import './styles.scss'
7
+ import styled from 'styled-components'
8
+ import { animated, config as springConfig, Transition } from 'react-spring'
12
9
  import IntlMessageFormat from 'intl-messageformat'
13
10
  import de from './locales/de.yml'
14
11
  import fr from './locales/fr.yml'
@@ -133,7 +130,7 @@ export class MunicipalitySearch extends Component<Props, State> {
133
130
  public static defaultProps = {
134
131
  municipalityData: '2021v3',
135
132
  locale: 'de',
136
- numberOfLastSelectedMunicipalities: 1,
133
+ numberOfLastSelectedMunicipalities: 1
137
134
  }
138
135
 
139
136
  public state = {
@@ -141,14 +138,14 @@ export class MunicipalitySearch extends Component<Props, State> {
141
138
  results: [],
142
139
  value: '',
143
140
  error: null,
144
- municipalities: [],
141
+ municipalities: []
145
142
  }
146
143
 
147
144
  private resetComponent = () => {
148
145
  this.setState({
149
146
  isLoading: false,
150
147
  results: [],
151
- value: '',
148
+ value: ''
152
149
  })
153
150
  }
154
151
 
@@ -205,7 +202,7 @@ export class MunicipalitySearch extends Component<Props, State> {
205
202
  window.dataLayer.push({
206
203
  event: 'Interactions',
207
204
  event_action: 'input',
208
- event_label: `search:for_${searchTerm}:results_${results.length}`,
205
+ event_label: `search:for_${searchTerm}:results_${results.length}`
209
206
  })
210
207
  }
211
208
 
@@ -217,7 +214,7 @@ export class MunicipalitySearch extends Component<Props, State> {
217
214
  searchTerm: string
218
215
  ): Municipality[] {
219
216
  const map = new Map()
220
- arr.forEach((v) => {
217
+ arr.forEach(v => {
221
218
  if (this.props.dedupe) {
222
219
  map.set(v.ORTNAME + v.GDENR, v)
223
220
  } else {
@@ -276,48 +273,57 @@ export class MunicipalitySearch extends Component<Props, State> {
276
273
  .fetch(url, {
277
274
  method: 'GET',
278
275
  mode: 'no-cors',
279
- credentials: 'include',
276
+ credentials: 'include'
280
277
  })
281
- .then((response) => {
278
+ .then(response => {
282
279
  console.log(response)
283
280
  })
284
- .catch((error) => {
281
+ .catch(error => {
285
282
  console.log(error)
286
283
  })
287
284
  }
288
285
 
289
286
  private pushToLocalStorageArray = (newMuni: Municipality) => {
290
- const localStorageItemName =
291
- 'selectedMunicipalities_' + this.props.municipalityData
292
- const prevSelectedMunicipalities =
293
- localStorage.getItem(localStorageItemName) ?? JSON.stringify([])
294
- const newArray = JSON.parse(prevSelectedMunicipalities)
287
+ let prevSelectedMunicipalities
288
+ let newArray: Municipality[] = []
289
+ const localStorageItemName = 'selectedMunicipalities'
290
+ try {
291
+ prevSelectedMunicipalities =
292
+ localStorage.getItem(localStorageItemName) ?? JSON.stringify([])
293
+ newArray = JSON.parse(prevSelectedMunicipalities)
294
+ } catch {
295
+ console.warn('🗄 localStorage is not available')
296
+ }
295
297
  // limit the number of stored municipalities
296
298
  const slicedArray = newArray.slice(0, 15)
297
299
  slicedArray.unshift(newMuni)
298
- // deduplicate the array and stringify it for localStorage
299
- localStorage.setItem(
300
- localStorageItemName,
301
- JSON.stringify([
302
- ...new Map(
303
- slicedArray.map((muni: Municipality) => [
304
- muni.ORTNAME + muni.GDENR,
305
- muni,
306
- ])
307
- ).values(),
308
- ])
309
- )
300
+ try {
301
+ // deduplicate the array and stringify it for localStorage
302
+ localStorage.setItem(
303
+ localStorageItemName,
304
+ JSON.stringify([
305
+ ...new Map(
306
+ slicedArray.map((muni: Municipality) => [
307
+ muni.ORTNAME + muni.GDENR,
308
+ muni
309
+ ])
310
+ ).values()
311
+ ])
312
+ )
313
+ } catch {
314
+ console.warn('🗄 localStorage is not writeable')
315
+ }
310
316
  }
311
317
 
312
318
  private getMunicipalitiesData = () => {
313
319
  this.setState({
314
- isLoading: true,
320
+ isLoading: true
315
321
  })
316
322
 
317
323
  return fetch(
318
324
  `https://interaktiv.tagesanzeiger.ch/static/gemeindesuche/${this.props.municipalityData}.json`
319
325
  )
320
- .then((res) => {
326
+ .then(res => {
321
327
  if (!res.ok) {
322
328
  throw new MunicipalityDownloadError(
323
329
  `Download error: ${res.status}: ${res.statusText}.`
@@ -325,13 +331,14 @@ export class MunicipalitySearch extends Component<Props, State> {
325
331
  }
326
332
  return res.json()
327
333
  })
328
- .then((data) => {
334
+ .then(data => {
329
335
  this.setState({
330
336
  isLoading: false,
331
- municipalities: data,
337
+ municipalities: data
332
338
  })
339
+ return Promise.resolve(data)
333
340
  })
334
- .catch((error) => {
341
+ .catch(error => {
335
342
  if (error instanceof MunicipalityDownloadError) {
336
343
  console.log(error)
337
344
  console.info(
@@ -342,7 +349,7 @@ export class MunicipalitySearch extends Component<Props, State> {
342
349
  )
343
350
  this.setState({
344
351
  isLoading: false,
345
- error: 'error.municipalitiesNotDownloaded',
352
+ error: 'error.municipalitiesNotDownloaded'
346
353
  })
347
354
  } else {
348
355
  throw error
@@ -361,24 +368,31 @@ export class MunicipalitySearch extends Component<Props, State> {
361
368
 
362
369
  /** Component mounting */
363
370
  public componentDidMount() {
364
- // set the municipalities from localStorage as results
365
- const locallyStoredMunicipalitiesArray = localStorage.getItem(
366
- 'selectedMunicipalities_' + this.props.municipalityData
367
- )
368
- if (
369
- this.props.showLastSelectedMunicipalities &&
370
- locallyStoredMunicipalitiesArray
371
- ) {
372
- const parsed = JSON.parse(
373
- locallyStoredMunicipalitiesArray
374
- ) as Municipality[]
375
- const muniCount = this.props.numberOfLastSelectedMunicipalities ?? 1
376
- this.setState({ results: parsed.slice(0, muniCount) })
377
- }
378
-
379
371
  // Download municipalities list
380
372
  // This is a preliminary measure, before we get a proper API
381
- this.getMunicipalitiesData()
373
+ this.getMunicipalitiesData().then(data => {
374
+ // set the municipalities from localStorage as results
375
+ let locallyStoredMunicipalitiesArray
376
+ try {
377
+ locallyStoredMunicipalitiesArray = localStorage.getItem(
378
+ 'selectedMunicipalities'
379
+ )
380
+ } catch {
381
+ console.warn('🗄 localStorage is not available')
382
+ }
383
+ if (
384
+ this.props.showLastSelectedMunicipalities &&
385
+ locallyStoredMunicipalitiesArray
386
+ ) {
387
+ const parsed = JSON.parse(
388
+ locallyStoredMunicipalitiesArray
389
+ ) as Municipality[]
390
+ const allIDs = data.map(muni => muni.GDENR)
391
+ const filtered = parsed.filter(muni => allIDs.includes(muni.GDENR))
392
+ const muniCount = this.props.numberOfLastSelectedMunicipalities ?? 3
393
+ this.setState({ results: filtered.slice(0, muniCount) })
394
+ }
395
+ })
382
396
  }
383
397
 
384
398
  public render() {
@@ -414,13 +428,13 @@ export class MunicipalitySearch extends Component<Props, State> {
414
428
  : 'left pointing'
415
429
 
416
430
  return (
417
- <div className={`municipality-search ${styles.municipalitySearch}`}>
418
- <div className={styles.inputRow}>
431
+ <MunicipalitySearchContainer className='municipality-search'>
432
+ <InputRow className='inputRow'>
419
433
  <div className='ui left icon input'>
420
- <input
434
+ <FlexInput
421
435
  id='search'
422
436
  type='text'
423
- className={`prompt ${styles.flexInput}`}
437
+ className='prompt flexInput'
424
438
  placeholder={
425
439
  this.props.placeholder || (t('placeholder') as string)
426
440
  }
@@ -430,7 +444,20 @@ export class MunicipalitySearch extends Component<Props, State> {
430
444
  }
431
445
  onChange={this.handleSearchChange}
432
446
  />
433
- <i className='search icon' />
447
+ <Icon>
448
+ <svg
449
+ width='18'
450
+ height='18'
451
+ viewBox='0 0 18 18'
452
+ fill='none'
453
+ xmlns='http://www.w3.org/2000/svg'
454
+ stroke='black'
455
+ strokeWidth='3'
456
+ >
457
+ <circle cx='7.5' cy='7.5' r='6' />
458
+ <line x1='11' y1='11' x2='17' y2='17' />
459
+ </svg>
460
+ </Icon>
434
461
  </div>
435
462
  {!this.props.hideTooltip && (
436
463
  <div>
@@ -444,9 +471,9 @@ export class MunicipalitySearch extends Component<Props, State> {
444
471
  </label>
445
472
  </div>
446
473
  )}
447
- </div>
474
+ </InputRow>
448
475
  {showResults(results) && (
449
- <div className={`results ${styles.results}`}>
476
+ <Results className='results'>
450
477
  {showResults(results) && (
451
478
  <Transition
452
479
  native
@@ -459,48 +486,110 @@ export class MunicipalitySearch extends Component<Props, State> {
459
486
  leave={{ transform: 'translate(0, 60px)', opacity: 0 }}
460
487
  config={springConfig.gentle}
461
488
  >
462
- {(values, result) =>
463
- (
464
- <animated.div
465
- onClick={() => {
489
+ {(values, result) => (
490
+ <animated.div
491
+ onClick={() => {
492
+ this.props.onSelectionHandler(result)
493
+ // this.sendToDataPipeline(result.PLZ4)
494
+ if (this.props.showLastSelectedMunicipalities) {
495
+ this.pushToLocalStorageArray(result)
496
+ }
497
+ if (this.props.resetOnSelect) {
498
+ this.resetComponent()
499
+ }
500
+ }}
501
+ onKeyUp={e => {
502
+ if (e.key === 'Enter' || e.key === 'Space') {
466
503
  this.props.onSelectionHandler(result)
467
504
  // this.sendToDataPipeline(result.PLZ4)
468
- if (this.props.showLastSelectedMunicipalities) {
469
- this.pushToLocalStorageArray(result)
470
- }
471
505
  if (this.props.resetOnSelect) {
472
506
  this.resetComponent()
473
507
  }
474
- }}
475
- onKeyUp={(e) => {
476
- if (e.key === 'Enter' || e.key === 'Space') {
477
- this.props.onSelectionHandler(result)
478
- // this.sendToDataPipeline(result.PLZ4)
479
- if (this.props.resetOnSelect) {
480
- this.resetComponent()
481
- }
482
- }
483
- }}
484
- className={`result ${styles.result}`}
485
- style={values}
486
- tabIndex={0}
487
- >
488
- <div className={styles.resultHeader}>
489
- <span className={styles.resultPlz}>{result.PLZ4}</span>{' '}
490
- {result.ORTNAME}
491
- </div>
492
- <div className={styles.resultMeta}>
493
- {t('list.municipalityPrefix')} {result.GDENAMK}
494
- </div>
495
- </animated.div>
496
- )}
508
+ }
509
+ }}
510
+ className='result'
511
+ style={values}
512
+ tabIndex={0}
513
+ >
514
+ <ResultHeader className='resultHeader'>
515
+ <ResultPlz className='resultPlz'>{result.PLZ4}</ResultPlz>{' '}
516
+ {result.ORTNAME}
517
+ </ResultHeader>
518
+ <ResultMeta className='resultMeta'>
519
+ {t('list.municipalityPrefix')} {result.GDENAMK}
520
+ </ResultMeta>
521
+ </animated.div>
522
+ )}
497
523
  </Transition>
498
524
  )}
499
- </div>
525
+ </Results>
500
526
  )}
501
- </div>
527
+ </MunicipalitySearchContainer>
502
528
  )
503
529
  }
504
530
 
505
531
  // endregion
506
532
  }
533
+ const Icon = styled.i`
534
+ position: absolute;
535
+ top: 50%;
536
+ transform: translateY(-50%);
537
+ display: flex;
538
+ padding-left: 0.8em;
539
+ opacity: 0.5;
540
+ transition: opacity 0.5s ease-in-out;
541
+ `
542
+ const MunicipalitySearchContainer = styled.div``
543
+ const InputRow = styled.div`
544
+ display: flex;
545
+ flex-direction: row;
546
+ align-items: center;
547
+ input:focus ~ i {
548
+ opacity: 1;
549
+ }
550
+ @media screen and (max-width: 599px) {
551
+ flex-direction: column;
552
+ .ui.input {
553
+ width: 100%;
554
+ max-width: 100%;
555
+ }
556
+ }
557
+ `
558
+ const FlexInput = styled.input`
559
+ display: flex;
560
+ `
561
+ const Results = styled.div`
562
+ margin-top: 1ex;
563
+ display: grid;
564
+ grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
565
+ grid-gap: 0;
566
+ font-family: var(--ui-font-stack);
567
+ .result {
568
+ padding: calc(11 / 16 * 1em) calc(14 / 16 * 1em);
569
+ border-radius: 0.2ex;
570
+ box-shadow: 0 0 0 rgba(0, 0, 0, 0.3);
571
+ //border: 1px solid #007abf;
572
+ color: rgba(0, 0, 0, 0.9);
573
+ line-height: 1.1em;
574
+ cursor: pointer;
575
+ transition: box-shadow 200ms ease-in-out;
576
+ background-color: transparent;
577
+
578
+ &:hover,
579
+ &:focus {
580
+ box-shadow: 0 0 0.5ex rgba(0, 0, 0, 0.2), 0 0 0 1px #007abf inset;
581
+ background-color: $backgroundColor;
582
+ }
583
+ }
584
+ `
585
+ const ResultHeader = styled.div`
586
+ font-weight: 700;
587
+ `
588
+ const ResultPlz = styled.span`
589
+ opacity: 0.5;
590
+ font-weight: 400;
591
+ `
592
+ const ResultMeta = styled.div`
593
+ opacity: 0.66;
594
+ font-size: 0.9em;
595
+ `
@@ -1,77 +0,0 @@
1
- /* IDENTIFICATOR STYLES */
2
-
3
-
4
- .flexInput {
5
- display: flex;
6
- }
7
-
8
-
9
- .municipalitySearch {
10
- --backgroundColor: rgba(255, 255, 255, 0.95);
11
- }
12
-
13
-
14
- .inputRow {
15
- display: flex;
16
- flex-direction: row;
17
- align-items: center;
18
-
19
-
20
- @media screen and (max-width: 599px) {
21
- flex-direction: column;
22
-
23
-
24
- :global .ui.input {
25
- width: 100%;
26
- max-width: 100%;
27
- }
28
- }
29
- }
30
-
31
-
32
- .results {
33
- margin-top: 1ex;
34
- display: grid;
35
- grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
36
- grid-gap: 0;
37
- font-family: var(--ui-font-stack);
38
- }
39
-
40
-
41
- .result {
42
- padding: calc(11 / 16 * 1em) calc(14 / 16 * 1em);
43
- border-radius: 0.2ex;
44
- box-shadow: 0 0 0 rgba(0, 0, 0, 0.3);
45
- /* //border: 1px solid #007abf; */
46
- color: rgba(0, 0, 0, 0.9);
47
- line-height: 1.1em;
48
- cursor: pointer;
49
- transition: box-shadow 200ms ease-in-out;
50
- background-color: transparent;
51
-
52
-
53
-
54
- }
55
- .result:hover,
56
- .result:focus {
57
- box-shadow: 0 0 0.5ex rgba(0, 0, 0, 0.2), 0 0 0 1px #007abf inset;
58
- background-color: var(--backgroundColor);
59
- }
60
-
61
-
62
- .resultHeader {
63
- font-weight: 700;
64
- }
65
-
66
-
67
- .resultPlz {
68
- opacity: 0.5;
69
- font-weight: 400;
70
- }
71
-
72
-
73
- .resultMeta {
74
- opacity: 0.66;
75
- font-size: 0.9em;
76
- }
77
-