@nyaruka/temba-components 0.170.1 → 0.172.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.
@@ -4,107 +4,165 @@ import { DateTime } from 'luxon';
4
4
  import { html, css, PropertyValues } from 'lit';
5
5
  import { DatePicker } from './DatePicker';
6
6
  import { CustomEventType } from '../interfaces';
7
+ import { Icon } from '../Icons';
8
+ import { designTokens } from '../styles/designTokens';
7
9
 
8
10
  export class RangePicker extends RapidElement {
9
11
  static styles = css`
12
+ ${designTokens}
13
+
14
+ :host {
15
+ display: inline-block;
16
+ font-family: var(--font);
17
+ }
18
+
10
19
  .range-container {
11
20
  display: flex;
12
- gap: 0.5em;
21
+ gap: 8px;
13
22
  align-items: center;
23
+ font-size: 13.5px;
24
+ color: var(--text-1);
14
25
  }
26
+
15
27
  .date-display {
16
28
  cursor: pointer;
17
- padding: 0.2em 0.5em;
18
- margin: 0.6em 0;
19
- border-radius: var(--curvature-widget);
20
- border: 1px solid transparent;
21
- transition: border 0.2s;
29
+ padding: 3px 6px;
30
+ border-radius: var(--r-sm);
31
+ transition:
32
+ background 120ms,
33
+ color 120ms;
22
34
  }
23
35
 
24
36
  .date-display:hover {
25
- border: 1px solid var(--color-widget-border);
26
37
  background: var(--sunken);
27
38
  }
28
39
 
29
- input[type='date'] {
30
- font-size: 1em;
31
- padding: 0.2em 0.5em;
32
- border-radius: var(--curvature-widget);
33
- border: 1px solid var(--color-widget-border);
40
+ .range-separator {
41
+ color: var(--text-3);
34
42
  }
35
43
 
36
44
  .navigation-container {
37
45
  display: flex;
38
46
  align-items: center;
39
- gap: 0.25em;
47
+ gap: 4px;
40
48
  }
41
49
 
50
+ /* Chevron-only step buttons, same chrome as the list pager: bare
51
+ glyph at rest, sunken wash on hover. No border of their own, so
52
+ they don't compete with the period track between them. */
42
53
  .nav-arrow {
43
- background: var(--sunken);
44
- border: 1px solid var(--color-widget-border);
45
-
46
- border-radius: var(--curvature-widget);
47
- padding: 0em 0em;
48
- cursor: pointer;
49
- font-size: 0.6em;
50
- display: flex;
54
+ display: inline-flex;
51
55
  align-items: center;
52
56
  justify-content: center;
53
- width: 23px;
54
- height: 23px;
57
+ width: 24px;
58
+ height: 24px;
59
+ padding: 0;
60
+ border: 0;
61
+ background: transparent;
62
+ border-radius: var(--r-sm);
63
+ color: var(--text-3);
64
+ cursor: pointer;
55
65
  transition:
56
- background 0.2s,
57
- border 0.2s,
58
- opacity 0.2s;
66
+ background 120ms,
67
+ color 120ms,
68
+ opacity 120ms;
69
+ }
70
+
71
+ .nav-arrow temba-icon {
72
+ --icon-color: currentColor;
59
73
  }
60
74
 
61
75
  .nav-arrow:hover:not(:disabled) {
62
- background: var(--accent-50);
63
- border-color: var(--accent);
76
+ background: var(--sunken);
77
+ color: var(--text-1);
64
78
  }
65
79
 
66
80
  .nav-arrow:disabled {
67
- opacity: 0.5;
68
- cursor: not-allowed;
69
- background: var(--sunken);
81
+ opacity: 0.35;
82
+ cursor: default;
70
83
  }
71
84
 
72
85
  .nav-arrow.hidden {
73
86
  visibility: hidden;
74
87
  }
75
88
 
89
+ /* Segmented control — one bordered sunken track holds every
90
+ period and the selected one lifts out as a surface pill. The
91
+ buttons carry no borders themselves, so there are no
92
+ overlapping 1px edges to clip the selection's outline (the old
93
+ -1px margin stacking left the selected button's left border
94
+ painted over by its neighbor). */
76
95
  .button-group {
77
- display: flex;
78
- margin-left: 0em;
96
+ display: inline-flex;
97
+ align-items: center;
98
+ gap: 2px;
99
+ padding: 2px;
100
+ background: var(--sunken);
101
+ border: 1px solid var(--border);
102
+ border-radius: var(--r-sm);
79
103
  }
104
+
80
105
  .range-btn {
81
- background: var(--sunken);
82
- border: 1px solid var(--color-widget-border);
83
- border-radius: 0px;
84
- margin-left: -1px;
85
- padding: 0.2em 0.8em;
106
+ height: 22px;
107
+ padding: 0 10px;
108
+ border: 0;
109
+ background: transparent;
110
+ border-radius: var(--r-xs);
111
+ font-family: inherit;
112
+ font-size: 12.5px;
113
+ font-weight: var(--w-medium);
114
+ line-height: 1;
115
+ color: var(--text-2);
86
116
  cursor: pointer;
87
- font-size: 0.95em;
88
117
  transition:
89
- background 0.2s,
90
- border 0.2s;
118
+ background 120ms,
119
+ color 120ms,
120
+ box-shadow 120ms;
91
121
  }
92
122
 
93
- .button-group .range-btn:first-child {
94
- border-radius: var(--curvature-widget) 0 0 var(--curvature-widget);
123
+ /* The hover wash sits on the sunken track, so --sunken itself
124
+ would be invisible here. Derived from --text-1 rather than a raw
125
+ rgba literal so it follows the palette (the tokens file mixes
126
+ against transparent the same way for --focus-halo). The rgba
127
+ fallback pre-mixes the default --text-1 for browsers without
128
+ color-mix(). */
129
+ .range-btn:hover:not(.selected) {
130
+ background: rgba(26, 31, 38, 0.06);
131
+ color: var(--text-1);
95
132
  }
96
133
 
97
- .button-group .range-btn:last-child {
98
- border-radius: 0 var(--curvature-widget) var(--curvature-widget) 0;
134
+ @supports (color: color-mix(in srgb, red, red)) {
135
+ .range-btn:hover:not(.selected) {
136
+ background: color-mix(in srgb, var(--text-1) 6%, transparent);
137
+ }
138
+ }
139
+
140
+ .range-btn.selected {
141
+ background: var(--surface);
142
+ color: var(--accent-700);
143
+ box-shadow: var(--shadow-1);
144
+ cursor: default;
99
145
  }
100
146
 
101
- .range-btn.selected,
102
- .range-btn:active {
103
- background: var(--accent-50);
104
- border-color: var(--accent);
147
+ .range-btn:focus-visible,
148
+ .nav-arrow:focus-visible {
149
+ outline: none;
150
+ box-shadow: var(--focus-halo);
105
151
  }
106
152
  `;
107
153
 
154
+ // the periods in the segmented control, in display order
155
+ private static RANGES: {
156
+ type: 'W' | 'M' | 'Y' | 'ALL';
157
+ label: string;
158
+ title: string;
159
+ }[] = [
160
+ { type: 'W', label: 'W', title: 'Last week' },
161
+ { type: 'M', label: 'M', title: 'Last month' },
162
+ { type: 'Y', label: 'Y', title: 'Last year' },
163
+ { type: 'ALL', label: 'All', title: 'All time' }
164
+ ];
165
+
108
166
  @property({ type: String, attribute: 'start' })
109
167
  startDate = '';
110
168
 
@@ -465,6 +523,15 @@ export class RangePicker extends RapidElement {
465
523
  } ${amount} ${unit}`;
466
524
  }
467
525
 
526
+ private getNavigationTitle(direction: 'previous' | 'next'): string {
527
+ const prefix = direction === 'previous' ? 'Previous' : 'Next';
528
+ if (this.selectedRange === 'W') return `${prefix} week`;
529
+ if (this.selectedRange === 'M') return `${prefix} month`;
530
+ if (this.selectedRange === 'Y') return `${prefix} year`;
531
+ if (this.selectedRange === '') return this.getNavigationLabel(direction);
532
+ return `${prefix} period`;
533
+ }
534
+
468
535
  willUpdate(changed: PropertyValues) {
469
536
  super.willUpdate(changed);
470
537
 
@@ -543,7 +610,7 @@ export class RangePicker extends RapidElement {
543
610
  >${this.formatDateForDisplay(this.startDate) ||
544
611
  'Start date'}</span
545
612
  >`}
546
- <span> - </span>
613
+ <span class="range-separator">–</span>
547
614
  ${this.editingEnd
548
615
  ? html`<temba-datepicker
549
616
  .value=${this.endDate}
@@ -566,61 +633,34 @@ export class RangePicker extends RapidElement {
566
633
  class="nav-arrow ${this.selectedRange === 'ALL' ? 'hidden' : ''}"
567
634
  ?disabled=${!this.canNavigatePrevious()}
568
635
  @click=${this.navigatePrevious}
569
- title="Previous ${this.selectedRange === 'W'
570
- ? 'week'
571
- : this.selectedRange === 'M'
572
- ? 'month'
573
- : this.selectedRange === 'Y'
574
- ? 'year'
575
- : this.selectedRange === ''
576
- ? this.getNavigationLabel('previous')
577
- : 'period'}"
636
+ title=${this.getNavigationTitle('previous')}
637
+ aria-label=${this.getNavigationTitle('previous')}
578
638
  >
579
-
639
+ <temba-icon name=${Icon.arrow_left}></temba-icon>
580
640
  </button>
581
- <div class="button-group">
582
- <button
583
- class="range-btn ${this.selectedRange === 'W' ? 'selected' : ''}"
584
- @click=${() => this.setRange('W')}
585
- >
586
- W
587
- </button>
588
- <button
589
- class="range-btn ${this.selectedRange === 'M' ? 'selected' : ''}"
590
- @click=${() => this.setRange('M')}
591
- >
592
- M
593
- </button>
594
- <button
595
- class="range-btn ${this.selectedRange === 'Y' ? 'selected' : ''}"
596
- @click=${() => this.setRange('Y')}
597
- >
598
- Y
599
- </button>
600
- <button
601
- class="range-btn ${this.selectedRange === 'ALL'
602
- ? 'selected'
603
- : ''}"
604
- @click=${() => this.setRange('ALL')}
605
- >
606
- All
607
- </button>
641
+ <div class="button-group" role="group" aria-label="Period">
642
+ ${RangePicker.RANGES.map(
643
+ (range) =>
644
+ html`<button
645
+ class="range-btn ${this.selectedRange === range.type
646
+ ? 'selected'
647
+ : ''}"
648
+ title=${range.title}
649
+ aria-pressed=${this.selectedRange === range.type}
650
+ @click=${() => this.setRange(range.type)}
651
+ >
652
+ ${range.label}
653
+ </button>`
654
+ )}
608
655
  </div>
609
656
  <button
610
657
  class="nav-arrow ${this.selectedRange === 'ALL' ? 'hidden' : ''}"
611
658
  ?disabled=${!this.canNavigateNext()}
612
659
  @click=${this.navigateNext}
613
- title="Next ${this.selectedRange === 'W'
614
- ? 'week'
615
- : this.selectedRange === 'M'
616
- ? 'month'
617
- : this.selectedRange === 'Y'
618
- ? 'year'
619
- : this.selectedRange === ''
620
- ? this.getNavigationLabel('next')
621
- : 'period'}"
660
+ title=${this.getNavigationTitle('next')}
661
+ aria-label=${this.getNavigationTitle('next')}
622
662
  >
623
-
663
+ <temba-icon name=${Icon.arrow_right}></temba-icon>
624
664
  </button>
625
665
  </div>
626
666
  </div>