@heyramzi/tasks 0.0.0-stage → 0.4.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +285 -2
  3. package/package.json +98 -3
  4. package/src/ambient.d.ts +6 -0
  5. package/src/board-order.ts +137 -0
  6. package/src/dates.ts +31 -0
  7. package/src/define-statuses.ts +32 -0
  8. package/src/demo.svelte +394 -0
  9. package/src/icons/board-icon.svelte +11 -0
  10. package/src/icons/chevron-down.svg +1 -0
  11. package/src/icons/chevron-right.svg +1 -0
  12. package/src/icons/clock.svg +1 -0
  13. package/src/icons/close.svg +1 -0
  14. package/src/icons/gantt-icon.svelte +11 -0
  15. package/src/icons/list-icon.svelte +16 -0
  16. package/src/icons/milestone-flag.svg +1 -0
  17. package/src/icons/play.svg +1 -0
  18. package/src/icons/spinner.svg +1 -0
  19. package/src/inbox/icons/call.svg +1 -0
  20. package/src/inbox/icons/email.svg +1 -0
  21. package/src/inbox/icons/form.svg +1 -0
  22. package/src/inbox/icons/slack.svg +1 -0
  23. package/src/inbox/inbox-card.svelte +279 -0
  24. package/src/inbox/inbox-draft.svelte +84 -0
  25. package/src/inbox/inbox-logic.ts +56 -0
  26. package/src/inbox/inbox-statuses.ts +50 -0
  27. package/src/inbox/inbox-trace.svelte +44 -0
  28. package/src/inbox/inbox-types.ts +108 -0
  29. package/src/inbox/inbox.svelte +203 -0
  30. package/src/inbox/index.ts +23 -0
  31. package/src/index.ts +78 -0
  32. package/src/kanban-board.svelte +560 -0
  33. package/src/kanban-column.svelte +117 -0
  34. package/src/kanban-drag.ts +38 -0
  35. package/src/kanban-types.ts +65 -0
  36. package/src/lib/cn.ts +11 -0
  37. package/src/presets/sales.ts +16 -0
  38. package/src/reorder.ts +20 -0
  39. package/src/status-badge.svelte +57 -0
  40. package/src/status-dot.svelte +228 -0
  41. package/src/status-icon.svelte +30 -0
  42. package/src/status-types.ts +42 -0
  43. package/src/task-gantt.svelte +845 -0
  44. package/src/task-list.svelte +463 -0
  45. package/src/task-types.ts +43 -0
  46. package/src/task-view-switch.svelte +118 -0
  47. package/src/time/duration.ts +69 -0
  48. package/src/time/time-chip.svelte +62 -0
  49. package/src/time/time-dot.svelte +58 -0
  50. package/src/time/time-ring.svelte +74 -0
  51. package/src/time/time-tracker.svelte +567 -0
  52. package/src/time/time-types.ts +74 -0
  53. package/src/tone.ts +54 -0
  54. package/src/use-kanban-dnd.svelte.ts +173 -0
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import { cn } from "../lib/cn.js";
3
+ import { TONE_CLASS, TONE_TEXT } from "../tone.js";
4
+ import { budgetTone, formatMinutes } from "./duration.js";
5
+ import TimeDot from "./time-dot.svelte";
6
+ import TimeRing from "./time-ring.svelte";
7
+ import clockIcon from "../icons/clock.svg?raw";
8
+
9
+ type Props = {
10
+ /** Minutes logged on the task. */
11
+ minutes: number;
12
+ /** Minutes budgeted. Draws a small ring and tints the chip as the budget runs out. */
13
+ estimate?: number;
14
+ /** A timer is running on the task. */
15
+ running?: boolean;
16
+ /** Replaces the spoken name. Defaults to a sentence built from the numbers. */
17
+ label?: string;
18
+ class?: string;
19
+ };
20
+
21
+ let { minutes, estimate, running = false, label, class: className }: Props = $props();
22
+
23
+ let tone = $derived(budgetTone(minutes, estimate));
24
+ let visible = $derived(minutes > 0 || running || !!estimate);
25
+ let spoken = $derived(
26
+ label ??
27
+ [
28
+ `${formatMinutes(minutes)} logged`,
29
+ estimate ? `of ${formatMinutes(estimate)}` : "",
30
+ running ? "timer running" : "",
31
+ ]
32
+ .filter(Boolean)
33
+ .join(", "),
34
+ );
35
+ </script>
36
+
37
+ {#if visible}
38
+ <span
39
+ role="img"
40
+ aria-label={spoken}
41
+ class={cn(
42
+ "inline-flex h-5 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-md border px-1.5 text-xs font-medium tabular-nums",
43
+ running
44
+ ? TONE_CLASS.accent
45
+ : tone === "neutral"
46
+ ? "border-transparent text-muted-foreground"
47
+ : TONE_CLASS[tone],
48
+ className,
49
+ )}
50
+ >
51
+ {#if running}
52
+ <TimeDot running size={6} />
53
+ {:else if estimate}
54
+ <TimeRing value={minutes} max={estimate} size={12} stroke={6} />
55
+ {:else}
56
+ <span class="size-3 shrink-0 [&>svg]:size-full" aria-hidden="true">{@html clockIcon}</span>
57
+ {/if}
58
+ <span aria-hidden="true">
59
+ {formatMinutes(minutes)}{#if estimate}<span class={cn("font-normal", tone === "neutral" ? "text-muted-foreground/70" : TONE_TEXT[tone])}> / {formatMinutes(estimate)}</span>{/if}
60
+ </span>
61
+ </span>
62
+ {/if}
@@ -0,0 +1,58 @@
1
+ <script lang="ts">
2
+ import { cn } from "../lib/cn.js";
3
+
4
+ type Props = {
5
+ /** Pulses while true. The dot takes its color from the text around it. */
6
+ running?: boolean;
7
+ size?: number;
8
+ class?: string;
9
+ };
10
+
11
+ let { running = false, size = 8, class: className }: Props = $props();
12
+ </script>
13
+
14
+ <span
15
+ class={cn("time-dot", running && "is-running", className)}
16
+ style:--time-dot-size="{size}px"
17
+ aria-hidden="true"
18
+ ></span>
19
+
20
+ <style>
21
+ .time-dot {
22
+ position: relative;
23
+ display: inline-block;
24
+ flex: none;
25
+ width: var(--time-dot-size);
26
+ height: var(--time-dot-size);
27
+ border-radius: 9999px;
28
+ background: currentColor;
29
+ }
30
+
31
+ .time-dot.is-running::after {
32
+ content: "";
33
+ position: absolute;
34
+ inset: 0;
35
+ border-radius: inherit;
36
+ background: currentColor;
37
+ animation: time-dot-pulse var(--time-pulse-duration, 2000ms) var(--ease-out, ease-out) infinite;
38
+ }
39
+
40
+ @keyframes time-dot-pulse {
41
+ from {
42
+ transform: scale(1);
43
+ opacity: 0.45;
44
+ }
45
+ to {
46
+ transform: scale(2.8);
47
+ opacity: 0;
48
+ }
49
+ }
50
+
51
+ @media (prefers-reduced-motion: reduce) {
52
+ .time-dot.is-running::after {
53
+ animation: none;
54
+ transform: scale(1.9);
55
+ opacity: 0.25;
56
+ }
57
+ }
58
+ </style>
@@ -0,0 +1,74 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { cn } from "../lib/cn.js";
4
+ import { TONE_TEXT } from "../tone.js";
5
+ import { budgetTone } from "./duration.js";
6
+
7
+ type Props = {
8
+ /** Minutes logged. */
9
+ value: number;
10
+ /** Minutes budgeted. The ring turns warning at 90% and destructive past 100%. */
11
+ max: number;
12
+ /** Pixels. */
13
+ size?: number;
14
+ /** Stroke width in a 36 unit box. */
15
+ stroke?: number;
16
+ label?: string;
17
+ /** Rendered in the middle, e.g. the percentage. */
18
+ children?: Snippet;
19
+ class?: string;
20
+ };
21
+
22
+ let { value, max, size = 56, stroke = 4, label, children, class: className }: Props = $props();
23
+
24
+ const R = 16;
25
+ let tone = $derived(budgetTone(value, max));
26
+ let fraction = $derived(max > 0 ? Math.min(1, Math.max(0, value / max)) : 0);
27
+ let colorClass = $derived(tone === "neutral" ? TONE_TEXT.accent : TONE_TEXT[tone]);
28
+ </script>
29
+
30
+ <span
31
+ class={cn("relative inline-grid shrink-0 place-items-center", className)}
32
+ style:width="{size}px"
33
+ style:height="{size}px"
34
+ role={label ? "img" : undefined}
35
+ aria-label={label}
36
+ aria-hidden={label ? undefined : "true"}
37
+ >
38
+ <svg viewBox="0 0 36 36" class="size-full -rotate-90" fill="none" aria-hidden="true">
39
+ <circle cx="18" cy="18" r={R} stroke="currentColor" stroke-width={stroke} class="text-foreground/10" />
40
+ <circle
41
+ cx="18"
42
+ cy="18"
43
+ r={R}
44
+ pathLength="100"
45
+ stroke="currentColor"
46
+ stroke-width={stroke}
47
+ stroke-linecap="round"
48
+ stroke-dasharray="{fraction * 100} 100"
49
+ class={cn("time-ring-arc", colorClass)}
50
+ class:is-empty={fraction === 0}
51
+ />
52
+ </svg>
53
+ {#if children}
54
+ <span class="absolute inset-0 grid place-items-center">{@render children()}</span>
55
+ {/if}
56
+ </span>
57
+
58
+ <style>
59
+ .time-ring-arc {
60
+ transition:
61
+ stroke-dasharray var(--duration-slow, 400ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)),
62
+ color var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
63
+ }
64
+
65
+ .time-ring-arc.is-empty {
66
+ opacity: 0;
67
+ }
68
+
69
+ @media (prefers-reduced-motion: reduce) {
70
+ .time-ring-arc {
71
+ transition: none;
72
+ }
73
+ }
74
+ </style>