@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.
@@ -1,9 +1,12 @@
1
- import React, { useCallback, useMemo, useState } from "react";
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
- * List of durable {@link Task} objects with optional CRUD affordances — every mutation is
24
- * delegated to a callback (typically a `TaskOperation` invocation), never performed here.
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 TaskList = ({ classNames, tasks, groupByStatus = true, statusLabel = (status) => DEFAULT_STATUS_LABELS[status], onTaskCreate, onTaskUpdate, onTaskDelete, onTaskSelect, createPlaceholder = "Add task" }) => {
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__ */ jsxs("div", {
38
- className: "flex flex-col min-w-0",
39
- children: [/* @__PURE__ */ jsx(Listbox.Root, { children: /* @__PURE__ */ jsx(Listbox.Viewport, {
40
- classNames: ["dx-container", classNames],
41
- children: /* @__PURE__ */ jsx(Listbox.Content, {
42
- "aria-label": "Tasks",
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
- var TaskRow = ({ task, onUpdate, onDelete, onSelect }) => {
60
- const done = task.status === "done";
61
- const handleToggle = useCallback(() => onUpdate?.(task, { status: done ? "todo" : "done" }), [
62
- onUpdate,
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
- className: "flex items-center gap-2 min-w-0",
104
+ role: "none",
105
+ "data-testid": "taskList.item",
106
+ className: ROW_GRID,
71
107
  children: [
72
- onUpdate ? /* @__PURE__ */ jsx(IconButton, {
108
+ onTaskUpdate ? /* @__PURE__ */ jsx(IconButton, {
73
109
  variant: "ghost",
74
- density: "xs",
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 ? "text-success-text" : void 0,
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 ? "text-success-text" : void 0,
118
+ classNames: mx("justify-self-center", done && "text-success-text"),
83
119
  size: 4
84
120
  }),
85
121
  /* @__PURE__ */ jsx("span", {
86
- className: onSelect ? "truncate flex-1 cursor-pointer" : "truncate flex-1",
87
- onClick: onSelect ? () => onSelect(task) : void 0,
88
- children: task.title
122
+ className: onTaskSelect ? "truncate cursor-pointer" : "truncate",
123
+ onClick: onTaskSelect ? () => onTaskSelect(task) : void 0,
124
+ children: current.title
89
125
  }),
90
- task.priority && task.priority !== "none" && /* @__PURE__ */ jsx(Tag, {
91
- hue: "neutral",
92
- children: task.priority
93
- }),
94
- task.assignee && /* @__PURE__ */ jsx(AssigneeChip, { assignee: task.assignee }),
95
- onDelete && /* @__PURE__ */ jsx(IconButton, {
96
- variant: "ghost",
97
- density: "xs",
98
- icon: "ph--x--regular",
99
- iconOnly: true,
100
- label: "Delete task",
101
- classNames: "invisible group-hover:visible",
102
- onClick: () => onDelete(task)
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
- var TaskCreateRow = ({ placeholder, onCreate }) => {
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
- onCreate(trimmed);
157
+ onTaskCreate?.(trimmed);
114
158
  setTitle("");
115
159
  }
116
- }, [title, onCreate]);
160
+ }, [title, onTaskCreate]);
161
+ if (!onTaskCreate) return null;
117
162
  return /* @__PURE__ */ jsxs("div", {
118
- className: "flex items-center gap-2 px-2 py-1",
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
- * Actor-aware assignee chip: a Person ref resolves to the contact's name; otherwise fall back to
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 { AssigneeChip, STATUS_ORDER, TaskList };
207
+ export { STATUS_ORDER, TaskList };
153
208
 
154
209
  //# sourceMappingURL=index.mjs.map
@@ -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
- export type TaskListProps = ThemedClassName<{
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
- /** Renders an add row; called with the trimmed title on Enter. */
14
+ /** Enables `Create`; called with the trimmed title. */
15
15
  onTaskCreate?: (title: string) => void;
16
- /** Enables the done toggle (status patch) and any future field affordances. */
16
+ /** Enables the done toggle. Every mutation is delegated — the list never writes. */
17
17
  onTaskUpdate?: (task: Task.Task, patch: TaskPatch) => void;
18
- /** Renders a per-row delete affordance. */
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
- * List of durable {@link Task} objects with optional CRUD affordances — every mutation is
26
- * delegated to a callback (typically a `TaskOperation` invocation), never performed here.
27
- */
28
- export declare const TaskList: ({ classNames, tasks, groupByStatus, statusLabel, onTaskCreate, onTaskUpdate, onTaskDelete, onTaskSelect, createPlaceholder, }: TaskListProps) => React.JSX.Element;
29
- /**
30
- * Actor-aware assignee chip: a Person ref resolves to the contact's name; otherwise fall back to
31
- * name, email, or a shortened DID; agents (`role: 'assistant'`) are marked with a sparkle.
32
- */
33
- export declare const AssigneeChip: ({ assignee }: {
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
- }) => React.JSX.Element | null;
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":"AAIA,OAAO,KAA6D,MAAM,OAAO,CAAC;AAGlF,OAAO,EAAgC,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpF,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAEpD,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;AAE5G,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;IAC1C,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,kEAAkE;IAClE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC3D,2CAA2C;IAC3C,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;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,QAAQ,kIAUlB,aAAa,sBAoCf,CAAC;AA2FF;;;GAGG;AACH,eAAO,MAAM,YAAY,iBAAkB;IAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAA;CAAE,6BAkBnE,CAAC"}
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: (args: {
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;AA8CrD,QAAA,MAAM,IAAI;;;mBA9BuC,OAAO;;;CAkCnB,CAAC;eAEvB,IAAI;AAEnB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,OAAO,EAAE,KAAU,CAAC;AAEjC,eAAO,MAAM,QAAQ,EAAE,KAEtB,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"}