@dxos/react-ui-task 0.10.0 → 0.11.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.
- package/dist/lib/index.mjs +121 -66
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/components/TaskList/TaskList.d.ts +50 -18
- package/dist/types/src/components/TaskList/TaskList.d.ts.map +1 -1
- package/dist/types/src/components/TaskList/TaskList.stories.d.ts +1 -1
- package/dist/types/src/components/TaskList/TaskList.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -10
- package/src/components/TaskList/TaskList.stories.tsx +44 -10
- package/src/components/TaskList/TaskList.tsx +238 -107
package/dist/lib/index.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createContext } from "@radix-ui/react-context";
|
|
2
|
+
import { Fragment, useCallback, useMemo, useState } from "react";
|
|
2
3
|
import { useObject } from "@dxos/echo-react";
|
|
3
|
-
import { Icon, IconButton, Input, Tag } from "@dxos/react-ui";
|
|
4
|
+
import { Icon, IconButton, Input, Tag, composable, composableProps } from "@dxos/react-ui";
|
|
4
5
|
import { Listbox } from "@dxos/react-ui-list";
|
|
6
|
+
import { mx } from "@dxos/ui-theme";
|
|
5
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
8
|
//#region src/components/TaskList/TaskList.tsx
|
|
9
|
+
var TASK_LIST_NAME = "TaskList.Root";
|
|
7
10
|
/** Linear-style status groups, most active first. */
|
|
8
11
|
var STATUS_ORDER = [
|
|
9
12
|
"in-progress",
|
|
@@ -20,10 +23,38 @@ var DEFAULT_STATUS_LABELS = {
|
|
|
20
23
|
"cancelled": "Cancelled"
|
|
21
24
|
};
|
|
22
25
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
26
|
+
* One row geometry shared by `Item` and `Create` so their affordances land in the same columns:
|
|
27
|
+
* a fixed icon gutter (the status toggle above the add `+`), the flexible label, then trailing
|
|
28
|
+
* chips/actions pinned to the far edge. `w-full` is load-bearing — the listbox item is a flex
|
|
29
|
+
* container, so without it the grid shrinks to its content and the trailing actions float
|
|
30
|
+
* mid-row. `px-3` matches the listbox item's own inset, which `Create` does not inherit.
|
|
25
31
|
*/
|
|
26
|
-
var
|
|
32
|
+
var ROW_GRID = "grid grid-cols-[1.5rem_1fr_auto] items-center gap-2 w-full min-w-0 px-3 h-8";
|
|
33
|
+
var [TaskListProvider, useTaskListContext] = createContext(TASK_LIST_NAME);
|
|
34
|
+
var TaskListRoot = ({ children, tasks, groupByStatus = true, statusLabel = (status) => DEFAULT_STATUS_LABELS[status], onTaskCreate, onTaskUpdate, onTaskDelete, onTaskSelect }) => /* @__PURE__ */ jsx(TaskListProvider, {
|
|
35
|
+
tasks,
|
|
36
|
+
groupByStatus,
|
|
37
|
+
statusLabel,
|
|
38
|
+
onTaskCreate,
|
|
39
|
+
onTaskUpdate,
|
|
40
|
+
onTaskDelete,
|
|
41
|
+
onTaskSelect,
|
|
42
|
+
children: /* @__PURE__ */ jsx(Listbox.Root, { children })
|
|
43
|
+
});
|
|
44
|
+
TaskListRoot.displayName = "TaskList.Root";
|
|
45
|
+
var TaskListViewport = composable(({ children, ...props }, forwardedRef) => {
|
|
46
|
+
const { className, ...rest } = composableProps(props);
|
|
47
|
+
return /* @__PURE__ */ jsx(Listbox.Viewport, {
|
|
48
|
+
...rest,
|
|
49
|
+
classNames: mx("min-w-0", className),
|
|
50
|
+
ref: forwardedRef,
|
|
51
|
+
children
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
TaskListViewport.displayName = "TaskList.Viewport";
|
|
55
|
+
var TaskListContent = composable((props, forwardedRef) => {
|
|
56
|
+
const { tasks, groupByStatus, statusLabel } = useTaskListContext("TaskList.Content");
|
|
57
|
+
const { className, ...rest } = composableProps(props);
|
|
27
58
|
const groups = useMemo(() => {
|
|
28
59
|
if (!groupByStatus) return tasks.length > 0 ? [{
|
|
29
60
|
status: void 0,
|
|
@@ -34,92 +65,109 @@ var TaskList = ({ classNames, tasks, groupByStatus = true, statusLabel = (status
|
|
|
34
65
|
tasks: tasks.filter((task) => (task.status ?? "todo") === status)
|
|
35
66
|
})).filter((group) => group.tasks.length > 0);
|
|
36
67
|
}, [tasks, groupByStatus]);
|
|
37
|
-
return /* @__PURE__ */
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
children: groups.map(({ status, tasks }) => /* @__PURE__ */ jsxs(React.Fragment, { children: [status && /* @__PURE__ */ jsx("div", {
|
|
44
|
-
className: "px-2 pt-3 pb-1 text-xs text-subdued uppercase",
|
|
45
|
-
children: statusLabel(status)
|
|
46
|
-
}), tasks.map((task) => /* @__PURE__ */ jsx(TaskRow, {
|
|
47
|
-
task,
|
|
48
|
-
onUpdate: onTaskUpdate,
|
|
49
|
-
onDelete: onTaskDelete,
|
|
50
|
-
onSelect: onTaskSelect
|
|
51
|
-
}, task.id))] }, status ?? "all"))
|
|
52
|
-
})
|
|
53
|
-
}) }), onTaskCreate && /* @__PURE__ */ jsx(TaskCreateRow, {
|
|
54
|
-
placeholder: createPlaceholder,
|
|
55
|
-
onCreate: onTaskCreate
|
|
56
|
-
})]
|
|
68
|
+
return /* @__PURE__ */ jsx(Listbox.Content, {
|
|
69
|
+
...rest,
|
|
70
|
+
"aria-label": "Tasks",
|
|
71
|
+
classNames: className,
|
|
72
|
+
ref: forwardedRef,
|
|
73
|
+
children: groups.map(({ status, tasks }) => /* @__PURE__ */ jsxs(Fragment, { children: [status && /* @__PURE__ */ jsx(TaskListGroupLabel, { children: statusLabel(status) }), tasks.map((task) => /* @__PURE__ */ jsx(TaskListItem, { task }, task.id))] }, status ?? "all"))
|
|
57
74
|
});
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
75
|
+
});
|
|
76
|
+
TaskListContent.displayName = "TaskList.Content";
|
|
77
|
+
var TaskListGroupLabel = composable(({ children, ...props }, forwardedRef) => {
|
|
78
|
+
const { className, ...rest } = composableProps(props);
|
|
79
|
+
return /* @__PURE__ */ jsx("div", {
|
|
80
|
+
...rest,
|
|
81
|
+
className: mx("px-2 pt-3 pb-1 text-xs text-subdued uppercase", className),
|
|
82
|
+
ref: forwardedRef,
|
|
83
|
+
children
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
TaskListGroupLabel.displayName = "TaskList.GroupLabel";
|
|
87
|
+
var TaskListItem = composable(({ task, ...props }, forwardedRef) => {
|
|
88
|
+
const { onTaskUpdate, onTaskDelete, onTaskSelect } = useTaskListContext("TaskList.Item");
|
|
89
|
+
const { className, ...rest } = composableProps(props);
|
|
90
|
+
const [snapshot] = useObject(task);
|
|
91
|
+
const current = snapshot ?? task;
|
|
92
|
+
const done = current.status === "done";
|
|
93
|
+
const handleToggle = useCallback(() => onTaskUpdate?.(task, { status: done ? "todo" : "done" }), [
|
|
94
|
+
onTaskUpdate,
|
|
63
95
|
task,
|
|
64
96
|
done
|
|
65
97
|
]);
|
|
66
98
|
return /* @__PURE__ */ jsx(Listbox.Item, {
|
|
99
|
+
...rest,
|
|
67
100
|
id: task.id,
|
|
68
|
-
classNames: "py-0 group",
|
|
101
|
+
classNames: mx("px-0 py-0 group", className),
|
|
102
|
+
ref: forwardedRef,
|
|
69
103
|
children: /* @__PURE__ */ jsxs("div", {
|
|
70
|
-
|
|
104
|
+
role: "none",
|
|
105
|
+
"data-testid": "taskList.item",
|
|
106
|
+
className: ROW_GRID,
|
|
71
107
|
children: [
|
|
72
|
-
|
|
108
|
+
onTaskUpdate ? /* @__PURE__ */ jsx(IconButton, {
|
|
73
109
|
variant: "ghost",
|
|
74
|
-
density: "
|
|
110
|
+
density: "sm",
|
|
75
111
|
icon: done ? "ph--check--regular" : "ph--circle--regular",
|
|
76
112
|
iconOnly: true,
|
|
77
113
|
label: done ? "Mark todo" : "Mark done",
|
|
78
|
-
classNames: done
|
|
114
|
+
classNames: mx("justify-self-center text-subdued", done && "text-success-text"),
|
|
79
115
|
onClick: handleToggle
|
|
80
116
|
}) : /* @__PURE__ */ jsx(Icon, {
|
|
81
117
|
icon: done ? "ph--check--regular" : "ph--circle--regular",
|
|
82
|
-
classNames: done
|
|
118
|
+
classNames: mx("justify-self-center", done && "text-success-text"),
|
|
83
119
|
size: 4
|
|
84
120
|
}),
|
|
85
121
|
/* @__PURE__ */ jsx("span", {
|
|
86
|
-
className:
|
|
87
|
-
onClick:
|
|
88
|
-
children:
|
|
122
|
+
className: onTaskSelect ? "truncate cursor-pointer" : "truncate",
|
|
123
|
+
onClick: onTaskSelect ? () => onTaskSelect(task) : void 0,
|
|
124
|
+
children: current.title
|
|
89
125
|
}),
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
children:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
126
|
+
/* @__PURE__ */ jsxs("div", {
|
|
127
|
+
className: "flex items-center gap-1",
|
|
128
|
+
children: [
|
|
129
|
+
current.priority && current.priority !== "none" && /* @__PURE__ */ jsx(Tag, {
|
|
130
|
+
hue: "neutral",
|
|
131
|
+
children: current.priority
|
|
132
|
+
}),
|
|
133
|
+
current.assignee && /* @__PURE__ */ jsx(TaskListAssignee, { assignee: current.assignee }),
|
|
134
|
+
onTaskDelete && /* @__PURE__ */ jsx(IconButton, {
|
|
135
|
+
variant: "ghost",
|
|
136
|
+
density: "sm",
|
|
137
|
+
icon: "ph--x--regular",
|
|
138
|
+
iconOnly: true,
|
|
139
|
+
label: "Delete task",
|
|
140
|
+
classNames: "invisible group-hover:visible",
|
|
141
|
+
onClick: () => onTaskDelete(task)
|
|
142
|
+
})
|
|
143
|
+
]
|
|
103
144
|
})
|
|
104
145
|
]
|
|
105
146
|
})
|
|
106
147
|
});
|
|
107
|
-
};
|
|
108
|
-
|
|
148
|
+
});
|
|
149
|
+
TaskListItem.displayName = "TaskList.Item";
|
|
150
|
+
var TaskListCreate = composable(({ placeholder = "Add task", ...props }, forwardedRef) => {
|
|
151
|
+
const { onTaskCreate } = useTaskListContext("TaskList.Create");
|
|
152
|
+
const { className, ...rest } = composableProps(props);
|
|
109
153
|
const [title, setTitle] = useState("");
|
|
110
154
|
const handleKeyDown = useCallback((event) => {
|
|
111
155
|
const trimmed = title.trim();
|
|
112
156
|
if (event.key === "Enter" && trimmed.length > 0) {
|
|
113
|
-
|
|
157
|
+
onTaskCreate?.(trimmed);
|
|
114
158
|
setTitle("");
|
|
115
159
|
}
|
|
116
|
-
}, [title,
|
|
160
|
+
}, [title, onTaskCreate]);
|
|
161
|
+
if (!onTaskCreate) return null;
|
|
117
162
|
return /* @__PURE__ */ jsxs("div", {
|
|
118
|
-
|
|
163
|
+
...rest,
|
|
164
|
+
"data-testid": "taskList.create",
|
|
165
|
+
className: mx(ROW_GRID, className),
|
|
166
|
+
ref: forwardedRef,
|
|
119
167
|
children: [/* @__PURE__ */ jsx(Icon, {
|
|
120
168
|
icon: "ph--plus--regular",
|
|
121
169
|
size: 4,
|
|
122
|
-
classNames: "text-subdued"
|
|
170
|
+
classNames: "justify-self-center text-subdued"
|
|
123
171
|
}), /* @__PURE__ */ jsx(Input.Root, { children: /* @__PURE__ */ jsx(Input.TextInput, {
|
|
124
172
|
variant: "subdued",
|
|
125
173
|
placeholder,
|
|
@@ -128,12 +176,9 @@ var TaskCreateRow = ({ placeholder, onCreate }) => {
|
|
|
128
176
|
onKeyDown: handleKeyDown
|
|
129
177
|
}) })]
|
|
130
178
|
});
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
* name, email, or a shortened DID; agents (`role: 'assistant'`) are marked with a sparkle.
|
|
135
|
-
*/
|
|
136
|
-
var AssigneeChip = ({ assignee }) => {
|
|
179
|
+
});
|
|
180
|
+
TaskListCreate.displayName = "TaskList.Create";
|
|
181
|
+
var TaskListAssignee = composable(({ assignee }, _forwardedRef) => {
|
|
137
182
|
const [contact] = useObject(assignee.contact);
|
|
138
183
|
const label = contact?.fullName ?? assignee.name ?? assignee.email ?? (assignee.identityDid ? shortDid(assignee.identityDid) : void 0);
|
|
139
184
|
const agent = assignee.role === "assistant";
|
|
@@ -146,9 +191,19 @@ var AssigneeChip = ({ assignee }) => {
|
|
|
146
191
|
classNames: "inline-block me-1"
|
|
147
192
|
}), label ?? "agent"]
|
|
148
193
|
});
|
|
149
|
-
};
|
|
194
|
+
});
|
|
195
|
+
TaskListAssignee.displayName = "TaskList.Assignee";
|
|
150
196
|
var shortDid = (did) => `${did.slice(0, 12)}…`;
|
|
197
|
+
var TaskList = {
|
|
198
|
+
Root: TaskListRoot,
|
|
199
|
+
Viewport: TaskListViewport,
|
|
200
|
+
Content: TaskListContent,
|
|
201
|
+
GroupLabel: TaskListGroupLabel,
|
|
202
|
+
Item: TaskListItem,
|
|
203
|
+
Create: TaskListCreate,
|
|
204
|
+
Assignee: TaskListAssignee
|
|
205
|
+
};
|
|
151
206
|
//#endregion
|
|
152
|
-
export {
|
|
207
|
+
export { STATUS_ORDER, TaskList };
|
|
153
208
|
|
|
154
209
|
//# sourceMappingURL=index.mjs.map
|
package/dist/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/TaskList/TaskList.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React, { type KeyboardEvent, useCallback, useMemo, useState } from 'react';\n\nimport { useObject } from '@dxos/echo-react';\nimport { Icon, IconButton, Input, Tag, type ThemedClassName } from '@dxos/react-ui';\nimport { Listbox } from '@dxos/react-ui-list';\nimport { type Actor, type Task } from '@dxos/types';\n\nexport type TaskStatus = NonNullable<Task.Task['status']>;\n\n/** Linear-style status groups, most active first. */\nexport const STATUS_ORDER: TaskStatus[] = ['in-progress', 'todo', 'done', 'failed', 'cancelled'];\n\nconst DEFAULT_STATUS_LABELS: Record<TaskStatus, string> = {\n 'in-progress': 'In progress',\n 'todo': 'Todo',\n 'done': 'Done',\n 'failed': 'Failed',\n 'cancelled': 'Cancelled',\n};\n\nexport type TaskPatch = Partial<Pick<Task.Task, 'title' | 'status' | 'priority' | 'estimate' | 'assignee'>>;\n\nexport type TaskListProps = ThemedClassName<{\n tasks: readonly Task.Task[];\n /** Group rows into status sections (Linear order); flat list otherwise. */\n groupByStatus?: boolean;\n /** i18n hook for group headings; defaults to English labels. */\n statusLabel?: (status: TaskStatus) => string;\n /** Renders an add row; called with the trimmed title on Enter. */\n onTaskCreate?: (title: string) => void;\n /** Enables the done toggle (status patch) and any future field affordances. */\n onTaskUpdate?: (task: Task.Task, patch: TaskPatch) => void;\n /** Renders a per-row delete affordance. */\n onTaskDelete?: (task: Task.Task) => void;\n /** Row click. */\n onTaskSelect?: (task: Task.Task) => void;\n createPlaceholder?: string;\n}>;\n\n/**\n * List of durable {@link Task} objects with optional CRUD affordances — every mutation is\n * delegated to a callback (typically a `TaskOperation` invocation), never performed here.\n */\nexport const TaskList = ({\n classNames,\n tasks,\n groupByStatus = true,\n statusLabel = (status) => DEFAULT_STATUS_LABELS[status],\n onTaskCreate,\n onTaskUpdate,\n onTaskDelete,\n onTaskSelect,\n createPlaceholder = 'Add task',\n}: TaskListProps) => {\n const groups = useMemo(() => {\n if (!groupByStatus) {\n return tasks.length > 0 ? [{ status: undefined, tasks }] : [];\n }\n return STATUS_ORDER.map((status) => ({\n status,\n tasks: tasks.filter((task) => (task.status ?? 'todo') === status),\n })).filter((group) => group.tasks.length > 0);\n }, [tasks, groupByStatus]);\n\n return (\n <div className='flex flex-col min-w-0'>\n <Listbox.Root>\n <Listbox.Viewport classNames={['dx-container', classNames]}>\n <Listbox.Content aria-label='Tasks'>\n {groups.map(({ status, tasks }) => (\n <React.Fragment key={status ?? 'all'}>\n {status && <div className='px-2 pt-3 pb-1 text-xs text-subdued uppercase'>{statusLabel(status)}</div>}\n {tasks.map((task) => (\n <TaskRow\n key={task.id}\n task={task}\n onUpdate={onTaskUpdate}\n onDelete={onTaskDelete}\n onSelect={onTaskSelect}\n />\n ))}\n </React.Fragment>\n ))}\n </Listbox.Content>\n </Listbox.Viewport>\n </Listbox.Root>\n {onTaskCreate && <TaskCreateRow placeholder={createPlaceholder} onCreate={onTaskCreate} />}\n </div>\n );\n};\n\ntype TaskRowProps = {\n task: Task.Task;\n onUpdate?: TaskListProps['onTaskUpdate'];\n onDelete?: TaskListProps['onTaskDelete'];\n onSelect?: TaskListProps['onTaskSelect'];\n};\n\nconst TaskRow = ({ task, onUpdate, onDelete, onSelect }: TaskRowProps) => {\n const done = task.status === 'done';\n const handleToggle = useCallback(() => onUpdate?.(task, { status: done ? 'todo' : 'done' }), [onUpdate, task, done]);\n\n return (\n <Listbox.Item id={task.id} classNames='py-0 group'>\n <div className='flex items-center gap-2 min-w-0'>\n {onUpdate ? (\n <IconButton\n variant='ghost'\n density='xs'\n icon={done ? 'ph--check--regular' : 'ph--circle--regular'}\n iconOnly\n label={done ? 'Mark todo' : 'Mark done'}\n classNames={done ? 'text-success-text' : undefined}\n onClick={handleToggle}\n />\n ) : (\n <Icon\n icon={done ? 'ph--check--regular' : 'ph--circle--regular'}\n classNames={done ? 'text-success-text' : undefined}\n size={4}\n />\n )}\n <span\n className={onSelect ? 'truncate flex-1 cursor-pointer' : 'truncate flex-1'}\n onClick={onSelect ? () => onSelect(task) : undefined}\n >\n {task.title}\n </span>\n {task.priority && task.priority !== 'none' && <Tag hue='neutral'>{task.priority}</Tag>}\n {task.assignee && <AssigneeChip assignee={task.assignee} />}\n {onDelete && (\n <IconButton\n variant='ghost'\n density='xs'\n icon='ph--x--regular'\n iconOnly\n label='Delete task'\n classNames='invisible group-hover:visible'\n onClick={() => onDelete(task)}\n />\n )}\n </div>\n </Listbox.Item>\n );\n};\n\ntype TaskCreateRowProps = {\n placeholder: string;\n onCreate: (title: string) => void;\n};\n\nconst TaskCreateRow = ({ placeholder, onCreate }: TaskCreateRowProps) => {\n const [title, setTitle] = useState('');\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n const trimmed = title.trim();\n if (event.key === 'Enter' && trimmed.length > 0) {\n onCreate(trimmed);\n setTitle('');\n }\n },\n [title, onCreate],\n );\n\n return (\n <div className='flex items-center gap-2 px-2 py-1'>\n <Icon icon='ph--plus--regular' size={4} classNames='text-subdued' />\n <Input.Root>\n <Input.TextInput\n variant='subdued'\n placeholder={placeholder}\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n onKeyDown={handleKeyDown}\n />\n </Input.Root>\n </div>\n );\n};\n\n/**\n * Actor-aware assignee chip: a Person ref resolves to the contact's name; otherwise fall back to\n * name, email, or a shortened DID; agents (`role: 'assistant'`) are marked with a sparkle.\n */\nexport const AssigneeChip = ({ assignee }: { assignee: Actor.Actor }) => {\n const [contact] = useObject(assignee.contact);\n const label =\n contact?.fullName ??\n assignee.name ??\n assignee.email ??\n (assignee.identityDid ? shortDid(assignee.identityDid) : undefined);\n const agent = assignee.role === 'assistant';\n if (!label && !agent) {\n return null;\n }\n\n return (\n <Tag hue={agent ? 'purple' : 'indigo'}>\n {agent && <Icon icon='ph--sparkle--regular' size={3} classNames='inline-block me-1' />}\n {label ?? 'agent'}\n </Tag>\n );\n};\n\nconst shortDid = (did: string): string => `${did.slice(0, 12)}…`;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/TaskList/TaskList.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport { createContext } from '@radix-ui/react-context';\nimport React, { Fragment, type KeyboardEvent, type PropsWithChildren, useCallback, useMemo, useState } from 'react';\n\nimport { useObject } from '@dxos/echo-react';\nimport { Icon, IconButton, Input, Tag, composable, composableProps } from '@dxos/react-ui';\nimport { Listbox } from '@dxos/react-ui-list';\nimport { type Actor, type Task } from '@dxos/types';\nimport { mx } from '@dxos/ui-theme';\nimport { type ComposableProps } from '@dxos/ui-types';\n\nconst TASK_LIST_NAME = 'TaskList.Root';\n\nexport type TaskStatus = NonNullable<Task.Task['status']>;\n\n/** Linear-style status groups, most active first. */\nexport const STATUS_ORDER: TaskStatus[] = ['in-progress', 'todo', 'done', 'failed', 'cancelled'];\n\nconst DEFAULT_STATUS_LABELS: Record<TaskStatus, string> = {\n 'in-progress': 'In progress',\n 'todo': 'Todo',\n 'done': 'Done',\n 'failed': 'Failed',\n 'cancelled': 'Cancelled',\n};\n\nexport type TaskPatch = Partial<Pick<Task.Task, 'title' | 'status' | 'priority' | 'estimate' | 'assignee'>>;\n\n/**\n * One row geometry shared by `Item` and `Create` so their affordances land in the same columns:\n * a fixed icon gutter (the status toggle above the add `+`), the flexible label, then trailing\n * chips/actions pinned to the far edge. `w-full` is load-bearing — the listbox item is a flex\n * container, so without it the grid shrinks to its content and the trailing actions float\n * mid-row. `px-3` matches the listbox item's own inset, which `Create` does not inherit.\n */\nconst ROW_GRID = 'grid grid-cols-[1.5rem_1fr_auto] items-center gap-2 w-full min-w-0 px-3 h-8';\n\n//\n// Context — plain Radix context (un-scoped); nesting task lists has no meaning today.\n//\n\ntype TaskListContextValue = {\n tasks: readonly Task.Task[];\n groupByStatus: boolean;\n statusLabel: (status: TaskStatus) => string;\n onTaskCreate?: (title: string) => void;\n onTaskUpdate?: (task: Task.Task, patch: TaskPatch) => void;\n onTaskDelete?: (task: Task.Task) => void;\n onTaskSelect?: (task: Task.Task) => void;\n};\n\nconst [TaskListProvider, useTaskListContext] = createContext<TaskListContextValue>(TASK_LIST_NAME);\n\n//\n// Root — headless context provider. Renders no DOM.\n//\n\ntype TaskListRootProps = PropsWithChildren<{\n tasks: readonly Task.Task[];\n /** Group rows into status sections (Linear order); flat list otherwise. */\n groupByStatus?: boolean;\n /** i18n hook for group headings; defaults to English labels. */\n statusLabel?: (status: TaskStatus) => string;\n /** Enables `Create`; called with the trimmed title. */\n onTaskCreate?: (title: string) => void;\n /** Enables the done toggle. Every mutation is delegated — the list never writes. */\n onTaskUpdate?: (task: Task.Task, patch: TaskPatch) => void;\n /** Enables the per-row delete affordance. */\n onTaskDelete?: (task: Task.Task) => void;\n /** Row click. */\n onTaskSelect?: (task: Task.Task) => void;\n}>;\n\nconst TaskListRoot = ({\n children,\n tasks,\n groupByStatus = true,\n statusLabel = (status) => DEFAULT_STATUS_LABELS[status],\n onTaskCreate,\n onTaskUpdate,\n onTaskDelete,\n onTaskSelect,\n}: TaskListRootProps) => (\n <TaskListProvider\n tasks={tasks}\n groupByStatus={groupByStatus}\n statusLabel={statusLabel}\n onTaskCreate={onTaskCreate}\n onTaskUpdate={onTaskUpdate}\n onTaskDelete={onTaskDelete}\n onTaskSelect={onTaskSelect}\n >\n {/* Both roots are headless, so the pair renders no DOM of its own. */}\n <Listbox.Root>{children}</Listbox.Root>\n </TaskListProvider>\n);\n\nTaskListRoot.displayName = 'TaskList.Root';\n\n//\n// Viewport — the scrolling region (the listbox's own viewport). `Create` sits outside it, so the\n// add row stays pinned while the rows scroll.\n//\n\ntype TaskListViewportProps = ComposableProps;\n\nconst TaskListViewport = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {\n const { className, ...rest } = composableProps(props);\n return (\n <Listbox.Viewport {...rest} classNames={mx('min-w-0', className)} ref={forwardedRef}>\n {children}\n </Listbox.Viewport>\n );\n});\n\nTaskListViewport.displayName = 'TaskList.Viewport';\n\n//\n// Content — the rows, grouped by status when the root says so.\n//\n\ntype TaskListContentProps = ComposableProps;\n\nconst TaskListContent = composable<HTMLUListElement>((props, forwardedRef) => {\n const { tasks, groupByStatus, statusLabel } = useTaskListContext('TaskList.Content');\n const { className, ...rest } = composableProps(props);\n const groups = useMemo(() => {\n if (!groupByStatus) {\n return tasks.length > 0 ? [{ status: undefined, tasks }] : [];\n }\n\n return STATUS_ORDER.map((status) => ({\n status,\n tasks: tasks.filter((task) => (task.status ?? 'todo') === status),\n })).filter((group) => group.tasks.length > 0);\n }, [tasks, groupByStatus]);\n\n return (\n <Listbox.Content {...rest} aria-label='Tasks' classNames={className} ref={forwardedRef}>\n {groups.map(({ status, tasks }) => (\n <Fragment key={status ?? 'all'}>\n {status && <TaskListGroupLabel>{statusLabel(status)}</TaskListGroupLabel>}\n {tasks.map((task) => (\n <TaskListItem key={task.id} task={task} />\n ))}\n </Fragment>\n ))}\n </Listbox.Content>\n );\n});\n\nTaskListContent.displayName = 'TaskList.Content';\n\n//\n// GroupLabel\n//\n\ntype TaskListGroupLabelProps = ComposableProps;\n\nconst TaskListGroupLabel = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {\n const { className, ...rest } = composableProps(props);\n return (\n <div {...rest} className={mx('px-2 pt-3 pb-1 text-xs text-subdued uppercase', className)} ref={forwardedRef}>\n {children}\n </div>\n );\n});\n\nTaskListGroupLabel.displayName = 'TaskList.GroupLabel';\n\n//\n// Item — one row. Exported so a host can render its own selection of tasks.\n//\n\ntype TaskListItemProps = ComposableProps<{ task: Task.Task }>;\n\nconst TaskListItem = composable<HTMLLIElement, { task: Task.Task }>(({ task, ...props }, forwardedRef) => {\n const { onTaskUpdate, onTaskDelete, onTaskSelect } = useTaskListContext('TaskList.Item');\n const { className, ...rest } = composableProps(props);\n // Subscribe per row: a query re-emits when membership changes, not when a task's own fields do,\n // so a rename elsewhere (task form, agent, sync) would otherwise leave the row stale.\n const [snapshot] = useObject(task);\n const current = snapshot ?? task;\n const done = current.status === 'done';\n const handleToggle = useCallback(\n () => onTaskUpdate?.(task, { status: done ? 'todo' : 'done' }),\n [onTaskUpdate, task, done],\n );\n\n return (\n // `px-0` hands the horizontal inset to `ROW_GRID`, so the row shares the create row's columns.\n <Listbox.Item {...rest} id={task.id} classNames={mx('px-0 py-0 group', className)} ref={forwardedRef}>\n <div role='none' data-testid='taskList.item' className={ROW_GRID}>\n {onTaskUpdate ? (\n <IconButton\n variant='ghost'\n density='sm'\n icon={done ? 'ph--check--regular' : 'ph--circle--regular'}\n iconOnly\n label={done ? 'Mark todo' : 'Mark done'}\n classNames={mx('justify-self-center text-subdued', done && 'text-success-text')}\n onClick={handleToggle}\n />\n ) : (\n <Icon\n icon={done ? 'ph--check--regular' : 'ph--circle--regular'}\n classNames={mx('justify-self-center', done && 'text-success-text')}\n size={4}\n />\n )}\n <span\n className={onTaskSelect ? 'truncate cursor-pointer' : 'truncate'}\n onClick={onTaskSelect ? () => onTaskSelect(task) : undefined}\n >\n {current.title}\n </span>\n <div className='flex items-center gap-1'>\n {current.priority && current.priority !== 'none' && <Tag hue='neutral'>{current.priority}</Tag>}\n {current.assignee && <TaskListAssignee assignee={current.assignee} />}\n {onTaskDelete && (\n <IconButton\n variant='ghost'\n density='sm'\n icon='ph--x--regular'\n iconOnly\n label='Delete task'\n classNames='invisible group-hover:visible'\n onClick={() => onTaskDelete(task)}\n />\n )}\n </div>\n </div>\n </Listbox.Item>\n );\n});\n\nTaskListItem.displayName = 'TaskList.Item';\n\n//\n// Create — the add row; renders nothing unless the root supplies `onTaskCreate`.\n//\n\ntype TaskListCreateProps = ComposableProps<{ placeholder?: string }>;\n\nconst TaskListCreate = composable<HTMLDivElement, { placeholder?: string }>(\n ({ placeholder = 'Add task', ...props }, forwardedRef) => {\n const { onTaskCreate } = useTaskListContext('TaskList.Create');\n const { className, ...rest } = composableProps(props);\n const [title, setTitle] = useState('');\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n const trimmed = title.trim();\n if (event.key === 'Enter' && trimmed.length > 0) {\n onTaskCreate?.(trimmed);\n setTitle('');\n }\n },\n [title, onTaskCreate],\n );\n\n if (!onTaskCreate) {\n return null;\n }\n\n return (\n <div {...rest} data-testid='taskList.create' className={mx(ROW_GRID, className)} ref={forwardedRef}>\n <Icon icon='ph--plus--regular' size={4} classNames='justify-self-center text-subdued' />\n <Input.Root>\n <Input.TextInput\n variant='subdued'\n placeholder={placeholder}\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n onKeyDown={handleKeyDown}\n />\n </Input.Root>\n </div>\n );\n },\n);\n\nTaskListCreate.displayName = 'TaskList.Create';\n\n//\n// Assignee — actor-aware chip: a Person ref resolves to the contact's name; otherwise fall back to\n// name, email, or a shortened DID; agents (`role: 'assistant'`) are marked with a sparkle.\n//\n\ntype TaskListAssigneeProps = { assignee: Actor.Actor };\n\nconst TaskListAssignee = composable<HTMLSpanElement, TaskListAssigneeProps>(({ assignee }, _forwardedRef) => {\n const [contact] = useObject(assignee.contact);\n const label =\n contact?.fullName ??\n assignee.name ??\n assignee.email ??\n (assignee.identityDid ? shortDid(assignee.identityDid) : undefined);\n const agent = assignee.role === 'assistant';\n if (!label && !agent) {\n return null;\n }\n\n return (\n <Tag hue={agent ? 'purple' : 'indigo'}>\n {agent && <Icon icon='ph--sparkle--regular' size={3} classNames='inline-block me-1' />}\n {label ?? 'agent'}\n </Tag>\n );\n});\n\nTaskListAssignee.displayName = 'TaskList.Assignee';\n\nconst shortDid = (did: string): string => `${did.slice(0, 12)}…`;\n\n//\n// TaskList\n//\n\nexport const TaskList = {\n Root: TaskListRoot,\n Viewport: TaskListViewport,\n Content: TaskListContent,\n GroupLabel: TaskListGroupLabel,\n Item: TaskListItem,\n Create: TaskListCreate,\n Assignee: TaskListAssignee,\n};\n\nexport type {\n TaskListAssigneeProps,\n TaskListContentProps,\n TaskListCreateProps,\n TaskListGroupLabelProps,\n TaskListItemProps,\n TaskListRootProps,\n TaskListViewportProps,\n};\n"],"mappings":"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"}
|
|
@@ -1,36 +1,68 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
1
|
+
import React, { type PropsWithChildren } from 'react';
|
|
3
2
|
import { type Actor, type Task } from '@dxos/types';
|
|
3
|
+
import { type ComposableProps } from '@dxos/ui-types';
|
|
4
4
|
export type TaskStatus = NonNullable<Task.Task['status']>;
|
|
5
5
|
/** Linear-style status groups, most active first. */
|
|
6
6
|
export declare const STATUS_ORDER: TaskStatus[];
|
|
7
7
|
export type TaskPatch = Partial<Pick<Task.Task, 'title' | 'status' | 'priority' | 'estimate' | 'assignee'>>;
|
|
8
|
-
|
|
8
|
+
type TaskListRootProps = PropsWithChildren<{
|
|
9
9
|
tasks: readonly Task.Task[];
|
|
10
10
|
/** Group rows into status sections (Linear order); flat list otherwise. */
|
|
11
11
|
groupByStatus?: boolean;
|
|
12
12
|
/** i18n hook for group headings; defaults to English labels. */
|
|
13
13
|
statusLabel?: (status: TaskStatus) => string;
|
|
14
|
-
/**
|
|
14
|
+
/** Enables `Create`; called with the trimmed title. */
|
|
15
15
|
onTaskCreate?: (title: string) => void;
|
|
16
|
-
/** Enables the done toggle
|
|
16
|
+
/** Enables the done toggle. Every mutation is delegated — the list never writes. */
|
|
17
17
|
onTaskUpdate?: (task: Task.Task, patch: TaskPatch) => void;
|
|
18
|
-
/**
|
|
18
|
+
/** Enables the per-row delete affordance. */
|
|
19
19
|
onTaskDelete?: (task: Task.Task) => void;
|
|
20
20
|
/** Row click. */
|
|
21
21
|
onTaskSelect?: (task: Task.Task) => void;
|
|
22
|
-
createPlaceholder?: string;
|
|
23
22
|
}>;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
declare function TaskListRoot({ children, tasks, groupByStatus, statusLabel, onTaskCreate, onTaskUpdate, onTaskDelete, onTaskSelect }: TaskListRootProps): React.JSX.Element;
|
|
24
|
+
declare namespace TaskListRoot {
|
|
25
|
+
var displayName: string;
|
|
26
|
+
}
|
|
27
|
+
type TaskListViewportProps = ComposableProps;
|
|
28
|
+
type TaskListContentProps = ComposableProps;
|
|
29
|
+
type TaskListGroupLabelProps = ComposableProps;
|
|
30
|
+
type TaskListItemProps = ComposableProps<{
|
|
31
|
+
task: Task.Task;
|
|
32
|
+
}>;
|
|
33
|
+
type TaskListCreateProps = ComposableProps<{
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
}>;
|
|
36
|
+
type TaskListAssigneeProps = {
|
|
34
37
|
assignee: Actor.Actor;
|
|
35
|
-
}
|
|
38
|
+
};
|
|
39
|
+
export declare const TaskList: {
|
|
40
|
+
Root: {
|
|
41
|
+
({ children, tasks, groupByStatus, statusLabel, onTaskCreate, onTaskUpdate, onTaskDelete, onTaskSelect, }: TaskListRootProps): React.JSX.Element;
|
|
42
|
+
displayName: string;
|
|
43
|
+
};
|
|
44
|
+
Viewport: React.ForwardRefExoticComponent<Omit<{}, "className"> & {
|
|
45
|
+
classNames?: import("@dxos/ui-types").ClassNameValue;
|
|
46
|
+
} & Pick<React.HTMLAttributes<Element>, "children" | "role" | "style"> & React.RefAttributes<HTMLDivElement>>;
|
|
47
|
+
Content: React.ForwardRefExoticComponent<Omit<{}, "className"> & {
|
|
48
|
+
classNames?: import("@dxos/ui-types").ClassNameValue;
|
|
49
|
+
} & Pick<React.HTMLAttributes<Element>, "children" | "role" | "style"> & React.RefAttributes<HTMLUListElement>>;
|
|
50
|
+
GroupLabel: React.ForwardRefExoticComponent<Omit<{}, "className"> & {
|
|
51
|
+
classNames?: import("@dxos/ui-types").ClassNameValue;
|
|
52
|
+
} & Pick<React.HTMLAttributes<Element>, "children" | "role" | "style"> & React.RefAttributes<HTMLDivElement>>;
|
|
53
|
+
Item: React.ForwardRefExoticComponent<Omit<{
|
|
54
|
+
task: Task.Task;
|
|
55
|
+
}, "className"> & {
|
|
56
|
+
classNames?: import("@dxos/ui-types").ClassNameValue;
|
|
57
|
+
} & Pick<React.HTMLAttributes<Element>, "children" | "role" | "style"> & React.RefAttributes<HTMLLIElement>>;
|
|
58
|
+
Create: React.ForwardRefExoticComponent<Omit<{
|
|
59
|
+
placeholder?: string;
|
|
60
|
+
}, "className"> & {
|
|
61
|
+
classNames?: import("@dxos/ui-types").ClassNameValue;
|
|
62
|
+
} & Pick<React.HTMLAttributes<Element>, "children" | "role" | "style"> & React.RefAttributes<HTMLDivElement>>;
|
|
63
|
+
Assignee: React.ForwardRefExoticComponent<Omit<TaskListAssigneeProps, "className"> & {
|
|
64
|
+
classNames?: import("@dxos/ui-types").ClassNameValue;
|
|
65
|
+
} & Pick<React.HTMLAttributes<Element>, "children" | "role" | "style"> & React.RefAttributes<HTMLSpanElement>>;
|
|
66
|
+
};
|
|
67
|
+
export type { TaskListAssigneeProps, TaskListContentProps, TaskListCreateProps, TaskListGroupLabelProps, TaskListItemProps, TaskListRootProps, TaskListViewportProps, };
|
|
36
68
|
//# sourceMappingURL=TaskList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskList.d.ts","sourceRoot":"","sources":["../../../../../src/components/TaskList/TaskList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TaskList.d.ts","sourceRoot":"","sources":["../../../../../src/components/TaskList/TaskList.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,EAAgC,KAAK,iBAAiB,EAAkC,MAAM,OAAO,CAAC;AAKpH,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAItD,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAE1D,qDAAqD;AACrD,eAAO,MAAM,YAAY,EAAE,UAAU,EAA2D,CAAC;AAUjG,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC;AA+B5G,KAAK,iBAAiB,GAAG,iBAAiB,CAAC;IACzC,KAAK,EAAE,SAAS,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gEAAgE;IAChE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC;IAC7C,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,oFAAoF;IACpF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC3D,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;IACzC,iBAAiB;IACjB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;CAC1C,CAAC,CAAC;8BAEmB,EACpB,QAAQ,EACR,KAAK,EACL,aAAoB,EACpB,WAAuD,EACvD,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,YAAY,EACb,EAAE,iBAAiB;;;;AAsBpB,KAAK,qBAAqB,GAAG,eAAe,CAAC;AAiB7C,KAAK,oBAAoB,GAAG,eAAe,CAAC;AAoC5C,KAAK,uBAAuB,GAAG,eAAe,CAAC;AAiB/C,KAAK,iBAAiB,GAAG,eAAe,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAA;CAAE,CAAC,CAAC;AAoE9D,KAAK,mBAAmB,GAAG,eAAe,CAAC;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AA8CrE,KAAK,qBAAqB,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAA;CAAE,CAAC;AA8BvD,eAAO,MAAM,QAAQ;IACnB,IAAI;mHA7OH,iBAAiB;;;IA8OlB,QAAQ;;;IACR,OAAO;;;IACP,UAAU;;;IACV,IAAI;cAnJiD,IAAI,CAAC,IAAI;;;;IAoJ9D,MAAM;sBAhF0D,MAAM;;;;IAiFtE,QAAQ;;;CACT,CAAC;AAEF,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,GACtB,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { type StoryObj } from '@storybook/react-vite';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
declare const meta: {
|
|
4
4
|
title: string;
|
|
5
|
-
render: (
|
|
5
|
+
render: ({ readonly }: {
|
|
6
6
|
readonly?: boolean;
|
|
7
7
|
}) => React.JSX.Element;
|
|
8
8
|
decorators: import("@storybook/react").Decorator[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskList.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/TaskList/TaskList.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAgC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"TaskList.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/TaskList/TaskList.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAgC,MAAM,OAAO,CAAC;AAkDrD,QAAA,MAAM,IAAI;;2BAjC0B;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE;;CAqCrB,CAAC;eAEvB,IAAI;AAEnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KA8BrB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,KAEtB,CAAC"}
|