馃 distributed transcription service
thistle.dunkirk.sh
1import { css, html, LitElement } from "lit";
2import { customElement, property, state } from "lit/decorators.js";
3
4export interface MeetingTime {
5 day: string;
6 startTime: string;
7 endTime: string;
8 label: string;
9}
10
11interface DayState {
12 day: string;
13 shortName: string;
14 selected: boolean;
15}
16
17@customElement("meeting-time-picker")
18export class MeetingTimePicker extends LitElement {
19 @property({ type: Array }) value: MeetingTime[] = [];
20
21 @state() private days: DayState[] = [
22 { day: "Monday", shortName: "Mon", selected: false },
23 { day: "Tuesday", shortName: "Tue", selected: false },
24 { day: "Wednesday", shortName: "Wed", selected: false },
25 { day: "Thursday", shortName: "Thu", selected: false },
26 { day: "Friday", shortName: "Fri", selected: false },
27 { day: "Saturday", shortName: "Sat", selected: false },
28 { day: "Sunday", shortName: "Sun", selected: false },
29 ];
30
31 static override styles = css`
32 :host {
33 display: block;
34 }
35
36 .day-selector {
37 display: flex;
38 gap: 0.5rem;
39 flex-wrap: wrap;
40 }
41
42 .day-button {
43 flex: 1;
44 min-width: 3.5rem;
45 padding: 0.75rem 0.5rem;
46 background: var(--background);
47 border: 2px solid var(--secondary);
48 border-radius: 6px;
49 font-size: 0.875rem;
50 font-weight: 500;
51 cursor: pointer;
52 transition: all 0.2s;
53 font-family: inherit;
54 color: var(--text);
55 }
56
57 .day-button:hover {
58 border-color: var(--primary);
59 }
60
61 .day-button.selected {
62 background: var(--primary);
63 border-color: var(--primary);
64 color: white;
65 }
66
67 .helper-text {
68 margin-top: 0.5rem;
69 font-size: 0.75rem;
70 color: var(--paynes-gray);
71 }
72 `;
73
74 override connectedCallback() {
75 super.connectedCallback();
76 this.loadFromValue();
77 }
78
79 override updated(changedProperties: Map<string, unknown>) {
80 if (changedProperties.has("value")) {
81 this.loadFromValue();
82 }
83 }
84
85 private loadFromValue() {
86 // Always reset all days first
87 this.days = this.days.map((d) => ({ ...d, selected: false }));
88
89 // If no value, we're done
90 if (!this.value || this.value.length === 0) return;
91
92 // Load from value
93 for (const meeting of this.value) {
94 const dayIndex = this.days.findIndex((d) => d.day === meeting.day);
95 if (dayIndex !== -1) {
96 this.days = this.days.map((d, i) =>
97 i === dayIndex ? { ...d, selected: true } : d,
98 );
99 }
100 }
101 }
102
103 private toggleDay(index: number) {
104 this.days = this.days.map((d, i) =>
105 i === index ? { ...d, selected: !d.selected } : d,
106 );
107 this.dispatchChange();
108 }
109
110 private dispatchChange() {
111 const selectedDays = this.days
112 .filter((d) => d.selected)
113 .map((d) => ({
114 day: d.day,
115 startTime: "",
116 endTime: "",
117 label: d.day,
118 }));
119
120 this.dispatchEvent(
121 new CustomEvent("change", {
122 detail: selectedDays,
123 bubbles: true,
124 composed: true,
125 }),
126 );
127 }
128
129 override render() {
130 return html`
131 <div class="day-selector">
132 ${this.days.map(
133 (day, index) => html`
134 <button
135 type="button"
136 class="day-button ${day.selected ? "selected" : ""}"
137 @click=${() => this.toggleDay(index)}
138 >
139 ${day.shortName}
140 </button>
141 `,
142 )}
143 </div>
144
145 <div class="helper-text">
146 Click on days to select when this class meets
147 </div>
148 `;
149 }
150}