@echothink-ui/todo 0.1.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.
package/src/styles.css ADDED
@@ -0,0 +1,591 @@
1
+ @import "@echothink-ui/core/styles.css";
2
+
3
+ .eth-todo-kanban,
4
+ .eth-kanban-board {
5
+ background: var(--eth-color-background);
6
+ color: var(--eth-color-text-primary);
7
+ inline-size: 100%;
8
+ overflow-x: auto;
9
+ padding: var(--eth-space-xl);
10
+ }
11
+
12
+ .eth-todo-kanban__columns,
13
+ .eth-kanban-board__columns {
14
+ align-items: stretch;
15
+ display: flex;
16
+ gap: var(--eth-space-md);
17
+ min-block-size: 100%;
18
+ }
19
+
20
+ .eth-todo-kanban__column,
21
+ .eth-kanban-column {
22
+ background: var(--eth-color-layer-01);
23
+ border: 1px solid var(--eth-color-border-subtle);
24
+ border-radius: 0;
25
+ display: grid;
26
+ flex: 1 0 18rem;
27
+ grid-template-rows: auto 1fr;
28
+ inline-size: 18rem;
29
+ min-block-size: 22rem;
30
+ min-inline-size: 18rem;
31
+ overflow: hidden;
32
+ }
33
+
34
+ .eth-kanban-column--over-limit {
35
+ border-color: var(--eth-color-danger);
36
+ box-shadow: inset 0 0.25rem 0 var(--eth-color-danger);
37
+ }
38
+
39
+ .eth-todo-kanban__column-header,
40
+ .eth-kanban-column__header {
41
+ align-items: center;
42
+ background: var(--eth-color-layer-02);
43
+ border-bottom: 1px solid var(--eth-color-border-subtle);
44
+ display: flex;
45
+ gap: var(--eth-space-md);
46
+ inset-block-start: 0;
47
+ justify-content: space-between;
48
+ padding: var(--eth-space-md) var(--eth-space-lg);
49
+ position: sticky;
50
+ z-index: 1;
51
+ }
52
+
53
+ .eth-kanban-column__header {
54
+ align-items: stretch;
55
+ display: grid;
56
+ gap: var(--eth-space-md);
57
+ justify-content: initial;
58
+ margin: 0;
59
+ padding: var(--eth-space-lg);
60
+ }
61
+
62
+ .eth-kanban-column__header-main {
63
+ align-items: flex-start;
64
+ display: grid;
65
+ gap: var(--eth-space-md);
66
+ grid-template-columns: minmax(0, 1fr) auto;
67
+ }
68
+
69
+ .eth-kanban-column__heading {
70
+ min-inline-size: 0;
71
+ }
72
+
73
+ .eth-kanban-column__actions {
74
+ align-items: center;
75
+ display: flex;
76
+ flex: 0 0 auto;
77
+ gap: var(--eth-space-xs);
78
+ justify-content: flex-end;
79
+ }
80
+
81
+ .eth-kanban-column__add-glyph {
82
+ font-size: calc(1rem * var(--eth-text-scale, 1));
83
+ font-weight: 600;
84
+ line-height: 1;
85
+ }
86
+
87
+ .eth-todo-kanban__column-header h3,
88
+ .eth-kanban-column__header h3 {
89
+ font-size: calc(0.875rem * var(--eth-text-scale, 1));
90
+ font-weight: 600;
91
+ line-height: 1.2857;
92
+ margin: 0;
93
+ overflow-wrap: anywhere;
94
+ }
95
+
96
+ .eth-todo-kanban__column-header span,
97
+ .eth-kanban-column__count {
98
+ color: var(--eth-color-text-secondary);
99
+ display: block;
100
+ font-size: calc(0.75rem * var(--eth-text-scale, 1));
101
+ line-height: 1.3333;
102
+ margin-block-start: var(--eth-space-2xs);
103
+ }
104
+
105
+ .eth-kanban-column__metrics,
106
+ .eth-kanban-column__limit {
107
+ display: grid;
108
+ gap: var(--eth-space-sm);
109
+ }
110
+
111
+ .eth-kanban-column__limit-row {
112
+ align-items: center;
113
+ display: flex;
114
+ gap: var(--eth-space-sm);
115
+ justify-content: space-between;
116
+ min-inline-size: 0;
117
+ }
118
+
119
+ .eth-kanban-column__limit-note {
120
+ color: var(--eth-color-text-helper);
121
+ flex: 0 0 auto;
122
+ font-size: calc(0.75rem * var(--eth-text-scale, 1));
123
+ line-height: 1.3333;
124
+ text-align: end;
125
+ }
126
+
127
+ .eth-kanban-column--over-limit .eth-kanban-column__limit-note {
128
+ color: var(--eth-color-danger);
129
+ font-weight: 600;
130
+ }
131
+
132
+ .eth-kanban-column__meter {
133
+ background: var(--eth-color-border-subtle);
134
+ block-size: 0.25rem;
135
+ display: block;
136
+ inline-size: 100%;
137
+ overflow: hidden;
138
+ }
139
+
140
+ .eth-kanban-column__meter span {
141
+ background: var(--eth-color-interactive-primary);
142
+ block-size: 100%;
143
+ display: block;
144
+ min-inline-size: 0;
145
+ transition: inline-size 110ms cubic-bezier(0.2, 0, 0.38, 0.9);
146
+ }
147
+
148
+ .eth-kanban-column--over-limit .eth-kanban-column__meter span {
149
+ background: var(--eth-color-danger);
150
+ }
151
+
152
+ .eth-todo-kanban__cards,
153
+ .eth-kanban-column__body {
154
+ align-content: start;
155
+ background: var(--eth-color-layer-01);
156
+ display: grid;
157
+ gap: var(--eth-space-sm);
158
+ }
159
+
160
+ .eth-todo-kanban__cards {
161
+ min-block-size: 13rem;
162
+ padding: var(--eth-space-md);
163
+ }
164
+
165
+ .eth-kanban-column__body {
166
+ min-block-size: 12rem;
167
+ padding: var(--eth-space-lg);
168
+ }
169
+
170
+ .eth-kanban-column__empty {
171
+ align-items: center;
172
+ background: var(--eth-color-layer-02);
173
+ border: 1px dashed var(--eth-color-border-subtle);
174
+ color: var(--eth-color-text-helper);
175
+ display: flex;
176
+ font-size: calc(0.875rem * var(--eth-text-scale, 1));
177
+ justify-content: center;
178
+ line-height: 1.2857;
179
+ min-block-size: 6rem;
180
+ padding: var(--eth-space-md);
181
+ text-align: center;
182
+ }
183
+
184
+ .eth-kanban-column .eth-todo-task-card {
185
+ background: var(--eth-color-layer-02);
186
+ }
187
+
188
+ .eth-kanban-column .eth-todo-task-card:hover {
189
+ background: var(--eth-color-layer-hover);
190
+ }
191
+
192
+ .eth-kanban-card-shell {
193
+ cursor: grab;
194
+ border-radius: 0;
195
+ outline: 0;
196
+ }
197
+
198
+ .eth-kanban-card-shell:active {
199
+ cursor: grabbing;
200
+ }
201
+
202
+ .eth-kanban-card-shell:focus-visible {
203
+ outline: 2px solid var(--eth-color-focus);
204
+ outline-offset: 2px;
205
+ }
206
+
207
+ .eth-kanban-card-shell--lifted {
208
+ background: var(--eth-color-layer-selected);
209
+ }
210
+
211
+ .eth-todo-task-card {
212
+ background: var(--eth-color-layer-01);
213
+ border: 1px solid var(--eth-color-border-subtle);
214
+ border-inline-start: 3px solid transparent;
215
+ border-radius: 0;
216
+ color: var(--eth-color-text-primary);
217
+ display: grid;
218
+ gap: var(--eth-space-sm);
219
+ min-block-size: 7rem;
220
+ padding: var(--eth-space-md);
221
+ transition:
222
+ background-color 110ms cubic-bezier(0.2, 0, 0.38, 0.9),
223
+ border-color 110ms cubic-bezier(0.2, 0, 0.38, 0.9);
224
+ }
225
+
226
+ .eth-todo-task-card:hover {
227
+ background: var(--eth-color-layer-hover);
228
+ }
229
+
230
+ .eth-todo-task-card[data-priority="critical"],
231
+ .eth-todo-task-card[data-priority="urgent"],
232
+ .eth-todo-task-card[data-priority="high"] {
233
+ border-inline-start-color: var(--eth-color-danger);
234
+ }
235
+
236
+ .eth-todo-task-card[data-priority="medium"] {
237
+ border-inline-start-color: var(--eth-color-warning);
238
+ }
239
+
240
+ .eth-todo-task-card[data-priority="low"] {
241
+ border-inline-start-color: var(--eth-color-info);
242
+ }
243
+
244
+ .eth-todo-task-card__header,
245
+ .eth-todo-task-card__meta,
246
+ .eth-todo-task-card__labels,
247
+ .eth-todo-task-table__title {
248
+ align-items: center;
249
+ display: flex;
250
+ flex-wrap: wrap;
251
+ gap: var(--eth-space-sm);
252
+ }
253
+
254
+ .eth-todo-task-card__header {
255
+ align-items: flex-start;
256
+ justify-content: space-between;
257
+ }
258
+
259
+ .eth-todo-task-card__header strong {
260
+ flex: 1 1 auto;
261
+ font-size: calc(0.875rem * var(--eth-text-scale, 1));
262
+ line-height: 1.2857;
263
+ min-inline-size: 0;
264
+ overflow-wrap: anywhere;
265
+ }
266
+
267
+ .eth-todo-task-card__meta,
268
+ .eth-todo-task-card__labels {
269
+ color: var(--eth-color-text-secondary);
270
+ font-size: calc(0.75rem * var(--eth-text-scale, 1));
271
+ line-height: 1.3333;
272
+ }
273
+
274
+ .eth-todo-task-card__due {
275
+ color: var(--eth-color-text-helper);
276
+ }
277
+
278
+ .eth-todo-task-table {
279
+ inline-size: 100%;
280
+ }
281
+
282
+ .eth-todo-task-dependency-list,
283
+ .eth-todo-dependencies,
284
+ .eth-todo-task-timeline,
285
+ .eth-todo-timeline,
286
+ .eth-todo-list {
287
+ background: var(--eth-color-layer-01);
288
+ border: 1px solid var(--eth-color-border-subtle);
289
+ border-radius: 0;
290
+ display: grid;
291
+ gap: 0;
292
+ }
293
+
294
+ .eth-todo-list.eth-panel {
295
+ gap: 0;
296
+ inline-size: 100%;
297
+ max-inline-size: 48rem;
298
+ overflow: hidden;
299
+ padding: 0;
300
+ }
301
+
302
+ .eth-todo-list .eth-panel__header {
303
+ background: var(--eth-color-layer-02);
304
+ border-bottom: 1px solid var(--eth-color-border-subtle);
305
+ padding: var(--eth-space-lg);
306
+ }
307
+
308
+ .eth-todo-list .eth-panel__header h3 {
309
+ font-size: calc(1rem * var(--eth-text-scale, 1));
310
+ font-weight: 600;
311
+ line-height: 1.375;
312
+ }
313
+
314
+ .eth-todo-list .eth-panel__header p {
315
+ font-size: calc(0.75rem * var(--eth-text-scale, 1));
316
+ line-height: 1rem;
317
+ }
318
+
319
+ .eth-todo-list .eth-panel__body {
320
+ display: grid;
321
+ gap: 0;
322
+ min-inline-size: 0;
323
+ }
324
+
325
+ .eth-todo-list__items,
326
+ .eth-todo-dependencies__group,
327
+ .eth-todo-timeline__events {
328
+ display: grid;
329
+ gap: 0;
330
+ margin: 0;
331
+ padding: 0;
332
+ }
333
+
334
+ .eth-todo-dependencies__group {
335
+ border-bottom: 1px solid var(--eth-color-border-subtle);
336
+ padding: var(--eth-space-md);
337
+ }
338
+
339
+ .eth-todo-dependencies__group:last-child {
340
+ border-bottom: 0;
341
+ }
342
+
343
+ .eth-todo-dependencies__group h3 {
344
+ font-size: calc(0.875rem * var(--eth-text-scale, 1));
345
+ font-weight: 600;
346
+ line-height: 1.2857;
347
+ margin: 0 0 var(--eth-space-sm);
348
+ }
349
+
350
+ .eth-todo-dependencies__group ul {
351
+ display: grid;
352
+ gap: 0;
353
+ list-style: none;
354
+ margin: 0;
355
+ padding: 0;
356
+ }
357
+
358
+ .eth-todo-dependencies__group li {
359
+ align-items: center;
360
+ border-top: 1px solid var(--eth-color-border-subtle);
361
+ display: flex;
362
+ gap: var(--eth-space-md);
363
+ justify-content: space-between;
364
+ padding: var(--eth-space-sm) 0;
365
+ }
366
+
367
+ .eth-todo-item {
368
+ align-items: center;
369
+ background: var(--eth-color-layer-01);
370
+ border-block-end: 1px solid var(--eth-color-border-subtle);
371
+ border-inline-start: 3px solid transparent;
372
+ color: var(--eth-color-text-primary);
373
+ display: grid;
374
+ gap: var(--eth-space-md);
375
+ grid-template-columns: minmax(0, 1fr);
376
+ min-block-size: 3.5rem;
377
+ padding: 0 var(--eth-space-lg) 0 var(--eth-space-md);
378
+ transition:
379
+ background-color 110ms cubic-bezier(0.2, 0, 0.38, 0.9),
380
+ border-color 110ms cubic-bezier(0.2, 0, 0.38, 0.9);
381
+ }
382
+
383
+ .eth-todo-item--has-meta {
384
+ grid-template-columns: minmax(0, 1fr) auto;
385
+ }
386
+
387
+ .eth-todo-item[data-priority="critical"],
388
+ .eth-todo-item[data-priority="urgent"],
389
+ .eth-todo-item[data-priority="high"] {
390
+ border-inline-start-color: var(--eth-color-danger);
391
+ }
392
+
393
+ .eth-todo-item[data-priority="medium"] {
394
+ border-inline-start-color: var(--eth-color-warning);
395
+ }
396
+
397
+ .eth-todo-item[data-priority="low"] {
398
+ border-inline-start-color: var(--eth-color-info);
399
+ }
400
+
401
+ .eth-todo-item:focus-within {
402
+ outline: 2px solid var(--eth-color-focus);
403
+ outline-offset: -2px;
404
+ }
405
+
406
+ .eth-todo-item:last-child,
407
+ .eth-todo-list__items .eth-todo-item:last-child {
408
+ border-block-end: 0;
409
+ }
410
+
411
+ .eth-todo-item:hover {
412
+ background: var(--eth-color-layer-hover);
413
+ }
414
+
415
+ .eth-todo-item--done {
416
+ color: var(--eth-color-text-primary);
417
+ }
418
+
419
+ .eth-todo-item__content {
420
+ min-inline-size: 0;
421
+ padding-block: var(--eth-space-md);
422
+ }
423
+
424
+ .eth-todo-item__checkbox {
425
+ min-inline-size: 0;
426
+ }
427
+
428
+ .eth-todo-item__checkbox .cds--checkbox-label {
429
+ align-items: flex-start;
430
+ min-inline-size: 0;
431
+ }
432
+
433
+ .eth-todo-item__checkbox .cds--checkbox-label-text {
434
+ font-size: calc(0.875rem * var(--eth-text-scale, 1));
435
+ line-height: 1.2857;
436
+ min-inline-size: 0;
437
+ overflow-wrap: anywhere;
438
+ }
439
+
440
+ .eth-todo-item--done .cds--checkbox-label-text {
441
+ color: var(--eth-color-text-secondary);
442
+ }
443
+
444
+ .eth-todo-item__primary {
445
+ min-inline-size: 0;
446
+ }
447
+
448
+ .eth-todo-item .eth-checkbox {
449
+ min-inline-size: 0;
450
+ }
451
+
452
+ .eth-todo-item__label {
453
+ color: var(--eth-color-text-primary);
454
+ display: block;
455
+ font-size: calc(0.875rem * var(--eth-text-scale, 1));
456
+ line-height: 1.2857;
457
+ overflow-wrap: anywhere;
458
+ }
459
+
460
+ .eth-todo-item--done .eth-todo-item__label {
461
+ color: var(--eth-color-text-secondary);
462
+ text-decoration: line-through;
463
+ text-decoration-thickness: 1px;
464
+ }
465
+
466
+ .eth-todo-item__meta {
467
+ align-items: center;
468
+ color: var(--eth-color-text-secondary);
469
+ display: flex;
470
+ flex-wrap: wrap;
471
+ gap: var(--eth-space-sm);
472
+ justify-content: flex-end;
473
+ max-inline-size: min(24rem, 45vw);
474
+ min-inline-size: 0;
475
+ padding-block: var(--eth-space-sm);
476
+ }
477
+
478
+ .eth-todo-item--done .eth-todo-item__meta {
479
+ opacity: 0.78;
480
+ }
481
+
482
+ .eth-todo-item__tag {
483
+ flex: 0 1 auto;
484
+ }
485
+
486
+ .eth-todo-item__due {
487
+ color: var(--eth-color-text-helper);
488
+ display: inline-flex;
489
+ flex: 0 0 auto;
490
+ font-size: calc(0.75rem * var(--eth-text-scale, 1));
491
+ gap: var(--eth-space-xs);
492
+ line-height: 1rem;
493
+ white-space: nowrap;
494
+ }
495
+
496
+ .eth-todo-item__due-label {
497
+ color: var(--eth-color-text-helper);
498
+ }
499
+
500
+ .eth-todo-list__empty {
501
+ color: var(--eth-color-text-secondary);
502
+ font-size: calc(0.875rem * var(--eth-text-scale, 1));
503
+ line-height: 1.2857;
504
+ margin: 0;
505
+ padding: var(--eth-space-xl) var(--eth-space-lg);
506
+ }
507
+
508
+ .eth-todo-list__add {
509
+ align-items: end;
510
+ background: var(--eth-color-layer-02);
511
+ border-top: 1px solid var(--eth-color-border-subtle);
512
+ display: grid;
513
+ gap: var(--eth-space-sm);
514
+ grid-template-columns: minmax(0, 1fr) auto;
515
+ min-inline-size: 0;
516
+ padding: var(--eth-space-lg);
517
+ }
518
+
519
+ .eth-todo-list__add .cds--form-item,
520
+ .eth-todo-list__add .cds--text-input-wrapper {
521
+ min-inline-size: 0;
522
+ }
523
+
524
+ .eth-todo-list__add .eth-button {
525
+ min-inline-size: 5.5rem;
526
+ }
527
+
528
+ .eth-todo-timeline__events {
529
+ list-style: none;
530
+ }
531
+
532
+ .eth-todo-timeline__event {
533
+ border-bottom: 1px solid var(--eth-color-border-subtle);
534
+ display: grid;
535
+ gap: var(--eth-space-md);
536
+ grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr);
537
+ padding: var(--eth-space-md);
538
+ }
539
+
540
+ .eth-todo-timeline__event:last-child {
541
+ border-bottom: 0;
542
+ }
543
+
544
+ .eth-todo-timeline__event time {
545
+ color: var(--eth-color-text-helper);
546
+ font-size: calc(0.75rem * var(--eth-text-scale, 1));
547
+ line-height: 1.3333;
548
+ }
549
+
550
+ .eth-todo-timeline__event p {
551
+ margin: var(--eth-space-xs) 0 0;
552
+ }
553
+
554
+ .eth-kanban-board__live {
555
+ block-size: 1px;
556
+ clip-path: inset(50%);
557
+ inline-size: 1px;
558
+ overflow: hidden;
559
+ position: absolute;
560
+ white-space: nowrap;
561
+ }
562
+
563
+ @media (width <= 42rem) {
564
+ .eth-todo-kanban,
565
+ .eth-kanban-board {
566
+ padding: var(--eth-space-md);
567
+ }
568
+
569
+ .eth-todo-list__add,
570
+ .eth-todo-timeline__event {
571
+ grid-template-columns: 1fr;
572
+ }
573
+
574
+ .eth-todo-item {
575
+ align-items: stretch;
576
+ grid-template-columns: 1fr;
577
+ gap: var(--eth-space-xs);
578
+ padding-block: var(--eth-space-sm);
579
+ }
580
+
581
+ .eth-todo-item__content {
582
+ padding-block: 0;
583
+ }
584
+
585
+ .eth-todo-item__meta {
586
+ justify-content: flex-start;
587
+ max-inline-size: 100%;
588
+ padding-block: 0;
589
+ padding-inline-start: var(--eth-space-2xl);
590
+ }
591
+ }
package/src/types.ts ADDED
@@ -0,0 +1,56 @@
1
+ import type * as React from "react";
2
+ import type { EthAction, EthOperationalStatus } from "@echothink-ui/core";
3
+
4
+ export interface TodoTaskItem {
5
+ id: string;
6
+ label: string;
7
+ done: boolean;
8
+ dueAt?: string;
9
+ assignee?: string;
10
+ priority?: string;
11
+ }
12
+
13
+ export interface KanbanCard {
14
+ id: string;
15
+ title: string;
16
+ assignee?: string;
17
+ priority?: string;
18
+ status?: string;
19
+ dueAt?: string;
20
+ labels?: string[];
21
+ }
22
+
23
+ export interface KanbanColumnModel {
24
+ id: string;
25
+ title: string;
26
+ status?: string;
27
+ wipLimit?: number;
28
+ items: KanbanCard[];
29
+ }
30
+
31
+ export interface TaskTableTask extends Record<string, unknown> {
32
+ id: string;
33
+ title: string;
34
+ status?: EthOperationalStatus;
35
+ assignee?: string;
36
+ priority?: string;
37
+ dueAt?: string;
38
+ labels?: string[];
39
+ }
40
+
41
+ export interface TaskDependency {
42
+ id: string;
43
+ title: string;
44
+ status: string;
45
+ relation: "blocks" | "blocked-by";
46
+ }
47
+
48
+ export interface TaskTimelineEvent {
49
+ id: string;
50
+ timestamp: string;
51
+ actor?: string;
52
+ kind: string;
53
+ summary: React.ReactNode;
54
+ }
55
+
56
+ export type TaskRowActions = (task: TaskTableTask) => EthAction[];
package/src/utils.ts ADDED
@@ -0,0 +1,26 @@
1
+ export function formatDateTime(value: string | undefined) {
2
+ if (!value) return "";
3
+ const date = new Date(value);
4
+ if (Number.isNaN(date.getTime())) return value;
5
+ return new Intl.DateTimeFormat(undefined, {
6
+ month: "short",
7
+ day: "numeric",
8
+ hour: "numeric",
9
+ minute: "2-digit"
10
+ }).format(date);
11
+ }
12
+
13
+ export function priorityLabel(priority: string | undefined) {
14
+ return priority ?? "normal";
15
+ }
16
+
17
+ export function prioritySeverity(
18
+ priority: string | undefined
19
+ ): "danger" | "warning" | "info" | "neutral" {
20
+ const normalized = priority?.toLowerCase();
21
+ if (normalized === "urgent" || normalized === "critical" || normalized === "high")
22
+ return "danger";
23
+ if (normalized === "medium") return "warning";
24
+ if (normalized === "low") return "info";
25
+ return "neutral";
26
+ }