@baack-software/baack-ts-tasks 0.0.0-stage → 0.0.1

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 (229) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +158 -2
  3. package/dist/activity.d.ts +20 -0
  4. package/dist/activity.d.ts.map +1 -0
  5. package/dist/activity.js +75 -0
  6. package/dist/activity.js.map +1 -0
  7. package/dist/blockers.d.ts +28 -0
  8. package/dist/blockers.d.ts.map +1 -0
  9. package/dist/blockers.js +134 -0
  10. package/dist/blockers.js.map +1 -0
  11. package/dist/conflicts.d.ts +50 -0
  12. package/dist/conflicts.d.ts.map +1 -0
  13. package/dist/conflicts.js +73 -0
  14. package/dist/conflicts.js.map +1 -0
  15. package/dist/draft.d.ts +45 -0
  16. package/dist/draft.d.ts.map +1 -0
  17. package/dist/draft.js +155 -0
  18. package/dist/draft.js.map +1 -0
  19. package/dist/errors.d.ts +30 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +37 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +11 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/react/TaskActivity.d.ts +15 -0
  28. package/dist/react/TaskActivity.d.ts.map +1 -0
  29. package/dist/react/TaskActivity.js +60 -0
  30. package/dist/react/TaskActivity.js.map +1 -0
  31. package/dist/react/TaskBoard.d.ts +38 -0
  32. package/dist/react/TaskBoard.d.ts.map +1 -0
  33. package/dist/react/TaskBoard.js +85 -0
  34. package/dist/react/TaskBoard.js.map +1 -0
  35. package/dist/react/TaskConflicts.d.ts +26 -0
  36. package/dist/react/TaskConflicts.d.ts.map +1 -0
  37. package/dist/react/TaskConflicts.js +87 -0
  38. package/dist/react/TaskConflicts.js.map +1 -0
  39. package/dist/react/TaskCreateForm.d.ts +13 -0
  40. package/dist/react/TaskCreateForm.d.ts.map +1 -0
  41. package/dist/react/TaskCreateForm.js +68 -0
  42. package/dist/react/TaskCreateForm.js.map +1 -0
  43. package/dist/react/TaskEditor.d.ts +33 -0
  44. package/dist/react/TaskEditor.d.ts.map +1 -0
  45. package/dist/react/TaskEditor.js +172 -0
  46. package/dist/react/TaskEditor.js.map +1 -0
  47. package/dist/react/TaskFilters.d.ts +15 -0
  48. package/dist/react/TaskFilters.d.ts.map +1 -0
  49. package/dist/react/TaskFilters.js +48 -0
  50. package/dist/react/TaskFilters.js.map +1 -0
  51. package/dist/react/TaskView.d.ts +30 -0
  52. package/dist/react/TaskView.d.ts.map +1 -0
  53. package/dist/react/TaskView.js +108 -0
  54. package/dist/react/TaskView.js.map +1 -0
  55. package/dist/react/TasksAbout.d.ts +24 -0
  56. package/dist/react/TasksAbout.d.ts.map +1 -0
  57. package/dist/react/TasksAbout.js +82 -0
  58. package/dist/react/TasksAbout.js.map +1 -0
  59. package/dist/react/copy.d.ts +154 -0
  60. package/dist/react/copy.d.ts.map +1 -0
  61. package/dist/react/copy.js +173 -0
  62. package/dist/react/copy.js.map +1 -0
  63. package/dist/react/fields.d.ts +36 -0
  64. package/dist/react/fields.d.ts.map +1 -0
  65. package/dist/react/fields.js +21 -0
  66. package/dist/react/fields.js.map +1 -0
  67. package/dist/react/hooks.d.ts +9 -0
  68. package/dist/react/hooks.d.ts.map +1 -0
  69. package/dist/react/hooks.js +31 -0
  70. package/dist/react/hooks.js.map +1 -0
  71. package/dist/react/index.d.ts +13 -0
  72. package/dist/react/index.d.ts.map +1 -0
  73. package/dist/react/index.js +13 -0
  74. package/dist/react/index.js.map +1 -0
  75. package/dist/react/parts.d.ts +55 -0
  76. package/dist/react/parts.d.ts.map +1 -0
  77. package/dist/react/parts.js +92 -0
  78. package/dist/react/parts.js.map +1 -0
  79. package/dist/react/ui/alert.d.ts +11 -0
  80. package/dist/react/ui/alert.d.ts.map +1 -0
  81. package/dist/react/ui/alert.js +29 -0
  82. package/dist/react/ui/alert.js.map +1 -0
  83. package/dist/react/ui/avatar.d.ts +12 -0
  84. package/dist/react/ui/avatar.d.ts.map +1 -0
  85. package/dist/react/ui/avatar.js +24 -0
  86. package/dist/react/ui/avatar.js.map +1 -0
  87. package/dist/react/ui/badge.d.ts +10 -0
  88. package/dist/react/ui/badge.d.ts.map +1 -0
  89. package/dist/react/ui/badge.js +26 -0
  90. package/dist/react/ui/badge.js.map +1 -0
  91. package/dist/react/ui/button.d.ts +11 -0
  92. package/dist/react/ui/button.d.ts.map +1 -0
  93. package/dist/react/ui/button.js +37 -0
  94. package/dist/react/ui/button.js.map +1 -0
  95. package/dist/react/ui/card.d.ts +12 -0
  96. package/dist/react/ui/card.d.ts.map +1 -0
  97. package/dist/react/ui/card.js +26 -0
  98. package/dist/react/ui/card.js.map +1 -0
  99. package/dist/react/ui/input.d.ts +4 -0
  100. package/dist/react/ui/input.d.ts.map +1 -0
  101. package/dist/react/ui/input.js +8 -0
  102. package/dist/react/ui/input.js.map +1 -0
  103. package/dist/react/ui/label.d.ts +5 -0
  104. package/dist/react/ui/label.d.ts.map +1 -0
  105. package/dist/react/ui/label.js +10 -0
  106. package/dist/react/ui/label.js.map +1 -0
  107. package/dist/react/ui/radio-group.d.ts +6 -0
  108. package/dist/react/ui/radio-group.d.ts.map +1 -0
  109. package/dist/react/ui/radio-group.js +12 -0
  110. package/dist/react/ui/radio-group.js.map +1 -0
  111. package/dist/react/ui/select.d.ts +16 -0
  112. package/dist/react/ui/select.d.ts.map +1 -0
  113. package/dist/react/ui/select.js +37 -0
  114. package/dist/react/ui/select.js.map +1 -0
  115. package/dist/react/ui/separator.d.ts +5 -0
  116. package/dist/react/ui/separator.d.ts.map +1 -0
  117. package/dist/react/ui/separator.js +10 -0
  118. package/dist/react/ui/separator.js.map +1 -0
  119. package/dist/react/ui/sheet.d.ts +15 -0
  120. package/dist/react/ui/sheet.d.ts.map +1 -0
  121. package/dist/react/ui/sheet.js +38 -0
  122. package/dist/react/ui/sheet.js.map +1 -0
  123. package/dist/react/ui/skeleton.d.ts +3 -0
  124. package/dist/react/ui/skeleton.d.ts.map +1 -0
  125. package/dist/react/ui/skeleton.js +7 -0
  126. package/dist/react/ui/skeleton.js.map +1 -0
  127. package/dist/react/ui/table.d.ts +11 -0
  128. package/dist/react/ui/table.d.ts.map +1 -0
  129. package/dist/react/ui/table.js +29 -0
  130. package/dist/react/ui/table.js.map +1 -0
  131. package/dist/react/ui/tabs.d.ts +12 -0
  132. package/dist/react/ui/tabs.d.ts.map +1 -0
  133. package/dist/react/ui/tabs.js +31 -0
  134. package/dist/react/ui/tabs.js.map +1 -0
  135. package/dist/react/ui/textarea.d.ts +4 -0
  136. package/dist/react/ui/textarea.d.ts.map +1 -0
  137. package/dist/react/ui/textarea.js +8 -0
  138. package/dist/react/ui/textarea.js.map +1 -0
  139. package/dist/react/ui/toggle-group.d.ts +11 -0
  140. package/dist/react/ui/toggle-group.d.ts.map +1 -0
  141. package/dist/react/ui/toggle-group.js +25 -0
  142. package/dist/react/ui/toggle-group.js.map +1 -0
  143. package/dist/react/ui/toggle.d.ts +10 -0
  144. package/dist/react/ui/toggle.d.ts.map +1 -0
  145. package/dist/react/ui/toggle.js +27 -0
  146. package/dist/react/ui/toggle.js.map +1 -0
  147. package/dist/refs.d.ts +16 -0
  148. package/dist/refs.d.ts.map +1 -0
  149. package/dist/refs.js +43 -0
  150. package/dist/refs.js.map +1 -0
  151. package/dist/server/index.d.ts +4 -0
  152. package/dist/server/index.d.ts.map +1 -0
  153. package/dist/server/index.js +4 -0
  154. package/dist/server/index.js.map +1 -0
  155. package/dist/server/recipes.d.ts +39 -0
  156. package/dist/server/recipes.d.ts.map +1 -0
  157. package/dist/server/recipes.js +81 -0
  158. package/dist/server/recipes.js.map +1 -0
  159. package/dist/server/webhooks.d.ts +135 -0
  160. package/dist/server/webhooks.d.ts.map +1 -0
  161. package/dist/server/webhooks.js +243 -0
  162. package/dist/server/webhooks.js.map +1 -0
  163. package/dist/store.d.ts +124 -0
  164. package/dist/store.d.ts.map +1 -0
  165. package/dist/store.js +443 -0
  166. package/dist/store.js.map +1 -0
  167. package/dist/task-client.d.ts +137 -0
  168. package/dist/task-client.d.ts.map +1 -0
  169. package/dist/task-client.js +271 -0
  170. package/dist/task-client.js.map +1 -0
  171. package/dist/tasks.d.ts +84 -0
  172. package/dist/tasks.d.ts.map +1 -0
  173. package/dist/tasks.js +208 -0
  174. package/dist/tasks.js.map +1 -0
  175. package/dist/time.d.ts +15 -0
  176. package/dist/time.d.ts.map +1 -0
  177. package/dist/time.js +34 -0
  178. package/dist/time.js.map +1 -0
  179. package/dist/types.d.ts +74 -0
  180. package/dist/types.d.ts.map +1 -0
  181. package/dist/types.js +9 -0
  182. package/dist/types.js.map +1 -0
  183. package/package.json +101 -4
  184. package/src/activity.ts +86 -0
  185. package/src/blockers.ts +134 -0
  186. package/src/conflicts.ts +115 -0
  187. package/src/draft.ts +162 -0
  188. package/src/errors.ts +40 -0
  189. package/src/index.ts +10 -0
  190. package/src/react/TaskActivity.tsx +187 -0
  191. package/src/react/TaskBoard.tsx +264 -0
  192. package/src/react/TaskConflicts.tsx +217 -0
  193. package/src/react/TaskCreateForm.tsx +165 -0
  194. package/src/react/TaskEditor.tsx +485 -0
  195. package/src/react/TaskFilters.tsx +122 -0
  196. package/src/react/TaskView.tsx +291 -0
  197. package/src/react/TasksAbout.tsx +226 -0
  198. package/src/react/copy.tsx +253 -0
  199. package/src/react/fields.tsx +104 -0
  200. package/src/react/hooks.ts +41 -0
  201. package/src/react/index.ts +12 -0
  202. package/src/react/parts.tsx +176 -0
  203. package/src/react/ui/README.md +6 -0
  204. package/src/react/ui/alert.tsx +75 -0
  205. package/src/react/ui/avatar.tsx +109 -0
  206. package/src/react/ui/badge.tsx +48 -0
  207. package/src/react/ui/button.tsx +66 -0
  208. package/src/react/ui/card.tsx +102 -0
  209. package/src/react/ui/input.tsx +18 -0
  210. package/src/react/ui/label.tsx +23 -0
  211. package/src/react/ui/radio-group.tsx +41 -0
  212. package/src/react/ui/select.tsx +189 -0
  213. package/src/react/ui/separator.tsx +27 -0
  214. package/src/react/ui/sheet.tsx +145 -0
  215. package/src/react/ui/skeleton.tsx +13 -0
  216. package/src/react/ui/table.tsx +113 -0
  217. package/src/react/ui/tabs.tsx +89 -0
  218. package/src/react/ui/textarea.tsx +17 -0
  219. package/src/react/ui/toggle-group.tsx +89 -0
  220. package/src/react/ui/toggle.tsx +44 -0
  221. package/src/refs.ts +49 -0
  222. package/src/server/index.ts +3 -0
  223. package/src/server/recipes.ts +107 -0
  224. package/src/server/webhooks.ts +369 -0
  225. package/src/store.ts +538 -0
  226. package/src/task-client.ts +373 -0
  227. package/src/tasks.ts +263 -0
  228. package/src/time.ts +35 -0
  229. package/src/types.ts +101 -0
@@ -0,0 +1,145 @@
1
+ import * as React from "react"
2
+ import { cn } from "cn"
3
+ import { Dialog as SheetPrimitive } from "radix-ui"
4
+
5
+ import { Button } from "./button.tsx"
6
+ import { XIcon } from "lucide-react"
7
+
8
+ function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
9
+ return <SheetPrimitive.Root data-slot="sheet" {...props} />
10
+ }
11
+
12
+ function SheetTrigger({
13
+ ...props
14
+ }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
15
+ return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
16
+ }
17
+
18
+ function SheetClose({
19
+ ...props
20
+ }: React.ComponentProps<typeof SheetPrimitive.Close>) {
21
+ return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
22
+ }
23
+
24
+ function SheetPortal({
25
+ ...props
26
+ }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
27
+ return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
28
+ }
29
+
30
+ function SheetOverlay({
31
+ className,
32
+ ...props
33
+ }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
34
+ return (
35
+ <SheetPrimitive.Overlay
36
+ data-slot="sheet-overlay"
37
+ className={cn(
38
+ "fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
39
+ className
40
+ )}
41
+ {...props}
42
+ />
43
+ )
44
+ }
45
+
46
+ function SheetContent({
47
+ className,
48
+ children,
49
+ side = "right",
50
+ showCloseButton = true,
51
+ ...props
52
+ }: React.ComponentProps<typeof SheetPrimitive.Content> & {
53
+ side?: "top" | "right" | "bottom" | "left"
54
+ showCloseButton?: boolean
55
+ }) {
56
+ return (
57
+ <SheetPortal>
58
+ <SheetOverlay />
59
+ <SheetPrimitive.Content
60
+ data-slot="sheet-content"
61
+ data-side={side}
62
+ className={cn(
63
+ "fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10",
64
+ className
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ {showCloseButton && (
70
+ <SheetPrimitive.Close data-slot="sheet-close" asChild>
71
+ <Button
72
+ variant="ghost"
73
+ className="absolute top-3 right-3"
74
+ size="icon-sm"
75
+ >
76
+ <XIcon
77
+ />
78
+ <span className="sr-only">Close</span>
79
+ </Button>
80
+ </SheetPrimitive.Close>
81
+ )}
82
+ </SheetPrimitive.Content>
83
+ </SheetPortal>
84
+ )
85
+ }
86
+
87
+ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
88
+ return (
89
+ <div
90
+ data-slot="sheet-header"
91
+ className={cn("flex flex-col gap-0.5 p-4", className)}
92
+ {...props}
93
+ />
94
+ )
95
+ }
96
+
97
+ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
98
+ return (
99
+ <div
100
+ data-slot="sheet-footer"
101
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
102
+ {...props}
103
+ />
104
+ )
105
+ }
106
+
107
+ function SheetTitle({
108
+ className,
109
+ ...props
110
+ }: React.ComponentProps<typeof SheetPrimitive.Title>) {
111
+ return (
112
+ <SheetPrimitive.Title
113
+ data-slot="sheet-title"
114
+ className={cn(
115
+ "text-base font-medium text-foreground",
116
+ className
117
+ )}
118
+ {...props}
119
+ />
120
+ )
121
+ }
122
+
123
+ function SheetDescription({
124
+ className,
125
+ ...props
126
+ }: React.ComponentProps<typeof SheetPrimitive.Description>) {
127
+ return (
128
+ <SheetPrimitive.Description
129
+ data-slot="sheet-description"
130
+ className={cn("text-sm text-muted-foreground", className)}
131
+ {...props}
132
+ />
133
+ )
134
+ }
135
+
136
+ export {
137
+ Sheet,
138
+ SheetTrigger,
139
+ SheetClose,
140
+ SheetContent,
141
+ SheetHeader,
142
+ SheetFooter,
143
+ SheetTitle,
144
+ SheetDescription,
145
+ }
@@ -0,0 +1,13 @@
1
+ import { cn } from "cn"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("animate-pulse rounded-md bg-muted", className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
@@ -0,0 +1,113 @@
1
+ import * as React from "react"
2
+ import { cn } from "cn"
3
+
4
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
5
+ return (
6
+ <div
7
+ data-slot="table-container"
8
+ className="relative w-full overflow-x-auto"
9
+ >
10
+ <table
11
+ data-slot="table"
12
+ className={cn("w-full caption-bottom text-sm", className)}
13
+ {...props}
14
+ />
15
+ </div>
16
+ )
17
+ }
18
+
19
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
20
+ return (
21
+ <thead
22
+ data-slot="table-header"
23
+ className={cn("[&_tr]:border-b", className)}
24
+ {...props}
25
+ />
26
+ )
27
+ }
28
+
29
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
30
+ return (
31
+ <tbody
32
+ data-slot="table-body"
33
+ className={cn("[&_tr:last-child]:border-0", className)}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
40
+ return (
41
+ <tfoot
42
+ data-slot="table-footer"
43
+ className={cn(
44
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ )
50
+ }
51
+
52
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
53
+ return (
54
+ <tr
55
+ data-slot="table-row"
56
+ className={cn(
57
+ "border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
58
+ className
59
+ )}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
66
+ return (
67
+ <th
68
+ data-slot="table-head"
69
+ className={cn(
70
+ "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
71
+ className
72
+ )}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
79
+ return (
80
+ <td
81
+ data-slot="table-cell"
82
+ className={cn(
83
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
84
+ className
85
+ )}
86
+ {...props}
87
+ />
88
+ )
89
+ }
90
+
91
+ function TableCaption({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"caption">) {
95
+ return (
96
+ <caption
97
+ data-slot="table-caption"
98
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
99
+ {...props}
100
+ />
101
+ )
102
+ }
103
+
104
+ export {
105
+ Table,
106
+ TableHeader,
107
+ TableBody,
108
+ TableFooter,
109
+ TableHead,
110
+ TableRow,
111
+ TableCell,
112
+ TableCaption,
113
+ }
@@ -0,0 +1,89 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+ import { cn } from "cn"
6
+ import { Tabs as TabsPrimitive } from "radix-ui"
7
+
8
+ function Tabs({
9
+ className,
10
+ orientation = "horizontal",
11
+ ...props
12
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) {
13
+ return (
14
+ <TabsPrimitive.Root
15
+ data-slot="tabs"
16
+ data-orientation={orientation}
17
+ className={cn(
18
+ "group/tabs flex gap-2 data-horizontal:flex-col",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ const tabsListVariants = cva(
27
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
28
+ {
29
+ variants: {
30
+ variant: {
31
+ default: "bg-muted",
32
+ line: "gap-1 bg-transparent",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ },
38
+ }
39
+ )
40
+
41
+ function TabsList({
42
+ className,
43
+ variant = "default",
44
+ ...props
45
+ }: React.ComponentProps<typeof TabsPrimitive.List> &
46
+ VariantProps<typeof tabsListVariants>) {
47
+ return (
48
+ <TabsPrimitive.List
49
+ data-slot="tabs-list"
50
+ data-variant={variant}
51
+ className={cn(tabsListVariants({ variant }), className)}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function TabsTrigger({
58
+ className,
59
+ ...props
60
+ }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
61
+ return (
62
+ <TabsPrimitive.Trigger
63
+ data-slot="tabs-trigger"
64
+ className={cn(
65
+ "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
66
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
67
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
68
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ )
74
+ }
75
+
76
+ function TabsContent({
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<typeof TabsPrimitive.Content>) {
80
+ return (
81
+ <TabsPrimitive.Content
82
+ data-slot="tabs-content"
83
+ className={cn("flex-1 text-sm outline-none", className)}
84
+ {...props}
85
+ />
86
+ )
87
+ }
88
+
89
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
@@ -0,0 +1,17 @@
1
+ import * as React from "react"
2
+ import { cn } from "cn"
3
+
4
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
5
+ return (
6
+ <textarea
7
+ data-slot="textarea"
8
+ className={cn(
9
+ "flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ export { Textarea }
@@ -0,0 +1,89 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { type VariantProps } from "class-variance-authority"
5
+ import { cn } from "cn"
6
+ import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
7
+
8
+ import { toggleVariants } from "./toggle.tsx"
9
+
10
+ const ToggleGroupContext = React.createContext<
11
+ VariantProps<typeof toggleVariants> & {
12
+ spacing?: number
13
+ orientation?: "horizontal" | "vertical"
14
+ }
15
+ >({
16
+ size: "default",
17
+ variant: "default",
18
+ spacing: 2,
19
+ orientation: "horizontal",
20
+ })
21
+
22
+ function ToggleGroup({
23
+ className,
24
+ variant,
25
+ size,
26
+ spacing = 2,
27
+ orientation = "horizontal",
28
+ children,
29
+ ...props
30
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
31
+ VariantProps<typeof toggleVariants> & {
32
+ spacing?: number
33
+ orientation?: "horizontal" | "vertical"
34
+ }) {
35
+ return (
36
+ <ToggleGroupPrimitive.Root
37
+ data-slot="toggle-group"
38
+ data-variant={variant}
39
+ data-size={size}
40
+ data-spacing={spacing}
41
+ data-orientation={orientation}
42
+ style={{ "--gap": spacing } as React.CSSProperties}
43
+ className={cn(
44
+ "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-vertical:flex-col data-vertical:items-stretch",
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ <ToggleGroupContext.Provider
50
+ value={{ variant, size, spacing, orientation }}
51
+ >
52
+ {children}
53
+ </ToggleGroupContext.Provider>
54
+ </ToggleGroupPrimitive.Root>
55
+ )
56
+ }
57
+
58
+ function ToggleGroupItem({
59
+ className,
60
+ children,
61
+ variant = "default",
62
+ size = "default",
63
+ ...props
64
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
65
+ VariantProps<typeof toggleVariants>) {
66
+ const context = React.useContext(ToggleGroupContext)
67
+
68
+ return (
69
+ <ToggleGroupPrimitive.Item
70
+ data-slot="toggle-group-item"
71
+ data-variant={context.variant || variant}
72
+ data-size={context.size || size}
73
+ data-spacing={context.spacing}
74
+ className={cn(
75
+ "shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
76
+ toggleVariants({
77
+ variant: context.variant || variant,
78
+ size: context.size || size,
79
+ }),
80
+ className
81
+ )}
82
+ {...props}
83
+ >
84
+ {children}
85
+ </ToggleGroupPrimitive.Item>
86
+ )
87
+ }
88
+
89
+ export { ToggleGroup, ToggleGroupItem }
@@ -0,0 +1,44 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "cn"
4
+ import { Toggle as TogglePrimitive } from "radix-ui"
5
+
6
+ const toggleVariants = cva(
7
+ "group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-transparent",
12
+ outline: "border border-input bg-transparent hover:bg-muted",
13
+ },
14
+ size: {
15
+ default:
16
+ "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
17
+ sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
18
+ lg: "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ size: "default",
24
+ },
25
+ }
26
+ )
27
+
28
+ function Toggle({
29
+ className,
30
+ variant = "default",
31
+ size = "default",
32
+ ...props
33
+ }: React.ComponentProps<typeof TogglePrimitive.Root> &
34
+ VariantProps<typeof toggleVariants>) {
35
+ return (
36
+ <TogglePrimitive.Root
37
+ data-slot="toggle"
38
+ className={cn(toggleVariants({ variant, size, className }))}
39
+ {...props}
40
+ />
41
+ )
42
+ }
43
+
44
+ export { Toggle, toggleVariants }
package/src/refs.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { Ref, RefLike } from './types.ts';
2
+
3
+ /** The URN of a reference or a URN string. */
4
+ export function urnOf(value: RefLike): string {
5
+ return typeof value === 'string' ? value : value.urn;
6
+ }
7
+
8
+ /** A bare `{ urn }` reference, dropping any other fields of a read representation. */
9
+ export function ref(value: RefLike): Ref {
10
+ return { urn: urnOf(value) };
11
+ }
12
+
13
+ /** Bare references for a list, without duplicates, in their original order. */
14
+ export function refs(values: readonly RefLike[] | null | undefined): Ref[] {
15
+ const seen = new Set<string>();
16
+ const result: Ref[] = [];
17
+ for (const value of values ?? []) {
18
+ const urn = urnOf(value);
19
+ if (!seen.has(urn)) {
20
+ seen.add(urn);
21
+ result.push({ urn });
22
+ }
23
+ }
24
+ return result;
25
+ }
26
+
27
+ /** Whether two references point at the same representation. Absent references are equal to each other. */
28
+ export function sameRef(a: RefLike | null | undefined, b: RefLike | null | undefined): boolean {
29
+ if (a == null || b == null) return a == null && b == null;
30
+ return urnOf(a) === urnOf(b);
31
+ }
32
+
33
+ /** Whether two lists hold the same references, ignoring order and duplicates. */
34
+ export function sameRefs(a: readonly RefLike[] | null | undefined, b: readonly RefLike[] | null | undefined): boolean {
35
+ const left = new Set((a ?? []).map(urnOf));
36
+ const right = new Set((b ?? []).map(urnOf));
37
+ return left.size === right.size && [...left].every((urn) => right.has(urn));
38
+ }
39
+
40
+ /** A copy of `list` with `value` added, unless it is already there. */
41
+ export function withRef(list: readonly RefLike[] | null | undefined, value: RefLike): Ref[] {
42
+ return refs([...(list ?? []), value]);
43
+ }
44
+
45
+ /** A copy of `list` without `value`. */
46
+ export function withoutRef(list: readonly RefLike[] | null | undefined, value: RefLike): Ref[] {
47
+ const urn = urnOf(value);
48
+ return refs(list).filter((item) => item.urn !== urn);
49
+ }
@@ -0,0 +1,3 @@
1
+ /** Server-side helpers: receiving webhooks and automations. Needs a token that can write tasks, so never ship it to a browser. */
2
+ export * from './webhooks.ts';
3
+ export * from './recipes.ts';
@@ -0,0 +1,107 @@
1
+ import { isBaackApiError, type Task, type TaskStatus } from '@baack-software/baack-ts-client';
2
+ import { openBlockers } from '../blockers.ts';
3
+ import type { DraftInput } from '../draft.ts';
4
+ import { urnOf } from '../refs.ts';
5
+ import type { ChangeOptions, TaskClient } from '../task-client.ts';
6
+ import { byUrgency, isOpen, isOverdue } from '../tasks.ts';
7
+ import type { RefLike } from '../types.ts';
8
+ import { webhookTarget, type WebhookEvent } from './webhooks.ts';
9
+
10
+ /**
11
+ * Automations built from the client and helpers, for webhook handlers and scheduled jobs. Each takes what it needs
12
+ * and returns what it did, so callers can log it or notify someone.
13
+ */
14
+
15
+ export interface WelcomeOptions extends ChangeOptions {
16
+ /** The company that owns the new task: usually yours, the one the webhook subscription belongs to. */
17
+ owner: RefLike;
18
+ /** The task to create, given the new company's URN. Default "Welcome your new customer", priority HIGH. */
19
+ task?: (company: string) => Omit<DraftInput, 'owner'>;
20
+ }
21
+
22
+ /**
23
+ * On a company being created, creates a task to welcome it, related to the new company. Returns the task, or
24
+ * `undefined` for any other event.
25
+ */
26
+ export async function welcomeNewCompany(
27
+ event: WebhookEvent,
28
+ tasks: TaskClient,
29
+ { owner, task, ...options }: WelcomeOptions,
30
+ ): Promise<Task | undefined> {
31
+ if (event.trigger.subject !== 'COMPANY' || event.trigger.event !== 'CREATE') return undefined;
32
+ const company = webhookTarget(event);
33
+ if (!company) return undefined;
34
+ const fields = task?.(company) ?? { title: 'Welcome your new customer', priority: 'HIGH' };
35
+ return tasks.create(
36
+ { owner, ...fields, associatedCompanies: [...(fields.associatedCompanies ?? []), { urn: company }] },
37
+ options,
38
+ );
39
+ }
40
+
41
+ export interface UnblockOptions extends ChangeOptions {
42
+ /** The status a task moves to once nothing blocks it. Default `PENDING`. */
43
+ status?: TaskStatus;
44
+ }
45
+
46
+ /**
47
+ * When `done` is finished, moves each task it was blocking out of `BLOCKED` if no other open task still blocks it.
48
+ * Reads the dependants and their other blockers fresh. Returns the tasks moved. Does nothing while `done` is open.
49
+ */
50
+ export async function unblockDependants(
51
+ done: Task,
52
+ tasks: TaskClient,
53
+ { status = 'PENDING', ...options }: UnblockOptions = {},
54
+ ): Promise<Task[]> {
55
+ if (isOpen(done)) return [];
56
+ const moved: Task[] = [];
57
+ for (const ref of done.blocking ?? []) {
58
+ const dependant = await readOrSkip(tasks, ref.urn);
59
+ if (!dependant || dependant.status !== 'BLOCKED') continue;
60
+ const blockers = new Map<string, Task>([[done.urn, done]]);
61
+ for (const blocker of dependant.blockedBy ?? []) {
62
+ if (blockers.has(blocker.urn)) continue;
63
+ const read = await readOrSkip(tasks, blocker.urn);
64
+ if (read) blockers.set(read.urn, read);
65
+ }
66
+ // A blocker that can't be read (deleted, or out of reach) counts as open, so nothing moves on a guess.
67
+ if (openBlockers(dependant, (urn) => blockers.get(urn)).length > 0) continue;
68
+ moved.push(await tasks.setStatus(dependant, status, options));
69
+ }
70
+ return moved;
71
+ }
72
+
73
+ /**
74
+ * The webhook side of `unblockDependants`: on a task update, reads the task and unblocks its dependants once it is
75
+ * finished. Returns the tasks moved.
76
+ */
77
+ export async function unblockOnCompletion(event: WebhookEvent, tasks: TaskClient, options?: UnblockOptions): Promise<Task[]> {
78
+ if (event.trigger.subject !== 'TASK' || event.trigger.event !== 'UPDATE') return [];
79
+ const urn = webhookTarget(event);
80
+ const task = urn ? await readOrSkip(tasks, urn) : undefined;
81
+ return task ? unblockDependants(task, tasks, options) : [];
82
+ }
83
+
84
+ /** A company's open tasks past their due date, most urgent first. For a nightly job that notifies their assignees. */
85
+ export async function overdueTasks(tasks: TaskClient, owner: RefLike, now: Date = new Date()): Promise<Task[]> {
86
+ const { tasks: all } = await tasks.list({ owner, includeRelated: false });
87
+ return all.filter((task) => isOverdue(task, now)).sort(byUrgency);
88
+ }
89
+
90
+ /** Groups tasks by assignee URN, for one message per person. Unassigned tasks are under `''`. */
91
+ export function byAssignee(tasks: readonly Task[]): Map<string, Task[]> {
92
+ const groups = new Map<string, Task[]>();
93
+ for (const task of tasks) {
94
+ const key = task.assignee ? urnOf(task.assignee) : '';
95
+ groups.set(key, [...(groups.get(key) ?? []), task]);
96
+ }
97
+ return groups;
98
+ }
99
+
100
+ async function readOrSkip(tasks: TaskClient, urn: string): Promise<Task | undefined> {
101
+ try {
102
+ return await tasks.get(urn);
103
+ } catch (error) {
104
+ if (isBaackApiError(error) && error.isNotFound) return undefined;
105
+ throw error;
106
+ }
107
+ }