@signalcore/ui 1.0.0-beta.3 → 1.0.0-beta.4

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 (71) hide show
  1. package/README.md +79 -0
  2. package/dist/AgentChat.d.ts +19 -3
  3. package/dist/AgentChat.js +149 -27
  4. package/dist/AgentChat.js.map +1 -1
  5. package/dist/AgentSpace.d.ts +14 -2
  6. package/dist/AgentSpace.js +3 -1
  7. package/dist/AgentSpace.js.map +1 -1
  8. package/dist/View.d.ts +48 -0
  9. package/dist/View.js +101 -0
  10. package/dist/View.js.map +1 -0
  11. package/dist/blocks.d.ts +83 -2
  12. package/dist/client.d.ts +9 -1
  13. package/dist/client.js +1 -0
  14. package/dist/client.js.map +1 -1
  15. package/dist/es.js +12 -0
  16. package/dist/es.js.map +1 -1
  17. package/dist/index.d.ts +11 -5
  18. package/dist/index.js +7 -1
  19. package/dist/kit/Agenda.d.ts +25 -0
  20. package/dist/kit/Agenda.js +119 -0
  21. package/dist/kit/Agenda.js.map +1 -0
  22. package/dist/kit/Combobox.js +1 -1
  23. package/dist/kit/DateField.js +1 -1
  24. package/dist/kit/Kanban.d.ts +17 -0
  25. package/dist/kit/Kanban.js +157 -0
  26. package/dist/kit/Kanban.js.map +1 -0
  27. package/dist/kit/TimeField.js +1 -1
  28. package/dist/kit/Timeline.d.ts +14 -0
  29. package/dist/kit/Timeline.js +71 -0
  30. package/dist/kit/Timeline.js.map +1 -0
  31. package/dist/kit/context.d.ts +1 -1
  32. package/dist/kit/context.js.map +1 -1
  33. package/dist/kit/messages.d.ts +22 -0
  34. package/dist/kit/messages.js +10 -0
  35. package/dist/kit/messages.js.map +1 -1
  36. package/dist/surface/Placed.d.ts +20 -0
  37. package/dist/surface/Placed.js +70 -0
  38. package/dist/surface/Placed.js.map +1 -0
  39. package/dist/surface/Surface.d.ts +4 -2
  40. package/dist/surface/Surface.js +78 -2
  41. package/dist/surface/Surface.js.map +1 -1
  42. package/dist/surface/places.d.ts +16 -0
  43. package/dist/surface/places.js +15 -0
  44. package/dist/surface/places.js.map +1 -0
  45. package/dist/surface/scope.d.ts +17 -1
  46. package/dist/surface/scope.js +1 -0
  47. package/dist/surface/scope.js.map +1 -1
  48. package/dist/types.d.ts +12 -0
  49. package/dist/types.js.map +1 -1
  50. package/dist/widget.js +17 -17
  51. package/package.json +1 -1
  52. package/src/AgentChat.tsx +215 -34
  53. package/src/AgentSpace.tsx +17 -1
  54. package/src/View.tsx +117 -0
  55. package/src/blocks.ts +100 -2
  56. package/src/client.ts +16 -0
  57. package/src/contract.ts +166 -5
  58. package/src/es.ts +9 -0
  59. package/src/index.ts +27 -2
  60. package/src/kit/Agenda.tsx +161 -0
  61. package/src/kit/Kanban.tsx +182 -0
  62. package/src/kit/Timeline.tsx +58 -0
  63. package/src/kit/context.tsx +45 -1
  64. package/src/kit/messages.ts +31 -0
  65. package/src/styles.css +568 -0
  66. package/src/surface/Placed.tsx +98 -0
  67. package/src/surface/Surface.tsx +92 -1
  68. package/src/surface/places.ts +19 -0
  69. package/src/surface/scope.ts +24 -1
  70. package/src/types.ts +13 -0
  71. package/src/widget/element.tsx +41 -3
@@ -0,0 +1,182 @@
1
+ import { ArrowsLeftRightIcon } from "@phosphor-icons/react";
2
+ import { useState, type DragEvent } from "react";
3
+
4
+ import type { BoardCard, BoardColumn } from "../blocks";
5
+ import { useKit, useParts } from "./context";
6
+ import { Popover, usePopover } from "./Popover";
7
+
8
+ /** What a dragged card carries, so a drop elsewhere on the page takes nothing of it. */
9
+ const CARD = "application/x-signalcore-card";
10
+
11
+ /** The columns with each card where the person last put it (`moved`, card id to column id). */
12
+ export function placed(
13
+ columns: readonly BoardColumn[],
14
+ moved: Readonly<Record<string, string>>,
15
+ ): BoardColumn[] {
16
+ const cards = columns.flatMap((column) =>
17
+ column.cards.map((card) => ({ card, column: moved[card.id] ?? column.id })),
18
+ );
19
+ return columns.map((column) => ({
20
+ ...column,
21
+ cards: cards.filter((each) => each.column === column.id).map((each) => each.card),
22
+ }));
23
+ }
24
+
25
+ /**
26
+ * Cards in columns, a column per stage, side by side, scrolling across when they don't fit: each
27
+ * column's title and how many it holds over its cards (a title, a quieter line, a detail at its
28
+ * foot). With `onMove`, the person moves a card to another column by dragging it there, or from
29
+ * its button's list of the other columns (the keyboard's way); the card goes there at once.
30
+ */
31
+ export function Kanban({
32
+ columns,
33
+ onMove,
34
+ disabled = false,
35
+ }: {
36
+ columns: BoardColumn[];
37
+ onMove?: ((card: BoardCard, column: BoardColumn) => void) | undefined;
38
+ disabled?: boolean | undefined;
39
+ }) {
40
+ const part = useParts();
41
+ const { messages } = useKit();
42
+ const [moved, setMoved] = useState<Record<string, string>>({});
43
+ const [dragging, setDragging] = useState<string | null>(null);
44
+ const [over, setOver] = useState<string | null>(null);
45
+ const movable = onMove !== undefined && !disabled;
46
+ const shown = placed(columns, moved);
47
+ const move = (card: BoardCard, to: BoardColumn) => {
48
+ const from = shown.find((column) => column.cards.some((each) => each.id === card.id));
49
+ if (!onMove || from?.id === to.id) return;
50
+ setMoved((before) => ({ ...before, [card.id]: to.id }));
51
+ onMove(card, to);
52
+ };
53
+ const dropOn = (column: BoardColumn) => (event: DragEvent) => {
54
+ event.preventDefault();
55
+ setOver(null);
56
+ setDragging(null);
57
+ const id = event.dataTransfer.getData(CARD);
58
+ const card = columns.flatMap((each) => each.cards).find((each) => each.id === id);
59
+ if (card) move(card, column);
60
+ };
61
+ return (
62
+ <div part="board" className={part("sc-board")}>
63
+ {shown.map((column) => (
64
+ <section
65
+ key={column.id}
66
+ aria-label={column.title}
67
+ className={part(
68
+ "sc-board__column",
69
+ ...(over === column.id ? (["sc-board__column--over"] as const) : []),
70
+ )}
71
+ onDragOver={(event) => {
72
+ if (!movable || dragging === null) return;
73
+ event.preventDefault();
74
+ event.dataTransfer.dropEffect = "move";
75
+ setOver(column.id);
76
+ }}
77
+ onDragLeave={(event) => {
78
+ const to = event.relatedTarget;
79
+ if (!(to instanceof Node && event.currentTarget.contains(to))) setOver(null);
80
+ }}
81
+ onDrop={movable ? dropOn(column) : undefined}
82
+ >
83
+ <h4 className={part("sc-board__head")}>
84
+ <span className={part("sc-board__title")}>{column.title}</span>
85
+ <span className={part("sc-board__count")}>{column.cards.length}</span>
86
+ </h4>
87
+ {column.cards.length === 0 ? (
88
+ <p className={part("sc-board__empty")}>{messages.board.empty}</p>
89
+ ) : (
90
+ <ul className={part("sc-board__cards")}>
91
+ {column.cards.map((card) => (
92
+ <li
93
+ key={card.id}
94
+ draggable={movable}
95
+ onDragStart={(event) => {
96
+ event.dataTransfer.setData(CARD, card.id);
97
+ event.dataTransfer.effectAllowed = "move";
98
+ setDragging(card.id);
99
+ }}
100
+ onDragEnd={() => {
101
+ setDragging(null);
102
+ setOver(null);
103
+ }}
104
+ className={part(
105
+ "sc-board__card",
106
+ ...(dragging === card.id ? (["sc-board__card--dragging"] as const) : []),
107
+ )}
108
+ >
109
+ <span className={part("sc-board__card-title")}>{card.title}</span>
110
+ {card.subtitle !== undefined && (
111
+ <span className={part("sc-board__card-subtitle")}>{card.subtitle}</span>
112
+ )}
113
+ {card.meta !== undefined && (
114
+ <span className={part("sc-board__card-meta")}>{card.meta}</span>
115
+ )}
116
+ {movable && (
117
+ <Move
118
+ card={card}
119
+ to={shown.filter((other) => other.id !== column.id)}
120
+ onMove={move}
121
+ />
122
+ )}
123
+ </li>
124
+ ))}
125
+ </ul>
126
+ )}
127
+ </section>
128
+ ))}
129
+ </div>
130
+ );
131
+ }
132
+
133
+ /** A card's button that opens the other columns, to move it to one of them. */
134
+ function Move({
135
+ card,
136
+ to,
137
+ onMove,
138
+ }: {
139
+ card: BoardCard;
140
+ to: BoardColumn[];
141
+ onMove: (card: BoardCard, column: BoardColumn) => void;
142
+ }) {
143
+ const part = useParts();
144
+ const { messages } = useKit();
145
+ const [open, setOpen] = useState(false);
146
+ const popover = usePopover(open, setOpen, { width: "own", role: "dialog" });
147
+ return (
148
+ <>
149
+ <button
150
+ ref={popover.refs.setReference}
151
+ type="button"
152
+ aria-label={messages.board.move(card.title)}
153
+ title={messages.board.move(card.title)}
154
+ aria-expanded={open}
155
+ className={part("sc-board__move")}
156
+ {...popover.getReferenceProps({ onClick: () => setOpen(!open) })}
157
+ >
158
+ <ArrowsLeftRightIcon aria-hidden />
159
+ </button>
160
+ {open && (
161
+ <Popover popover={popover} focus label={messages.board.moveTo}>
162
+ <div className={part("sc-board__menu")}>
163
+ <p>{messages.board.moveTo}</p>
164
+ {to.map((column) => (
165
+ <button
166
+ key={column.id}
167
+ type="button"
168
+ className={part("sc-listbox__item")}
169
+ onClick={() => {
170
+ setOpen(false);
171
+ onMove(card, column);
172
+ }}
173
+ >
174
+ {column.title}
175
+ </button>
176
+ ))}
177
+ </div>
178
+ </Popover>
179
+ )}
180
+ </>
181
+ );
182
+ }
@@ -0,0 +1,58 @@
1
+ import type { TimelineItem } from "../blocks";
2
+ import { useKit, useParts } from "./context";
3
+ import { dayOf, momentParts, TIME } from "./dates";
4
+
5
+ /** When a timeline's item was, short: "20 oct, 16:00"; its year when not this one's. */
6
+ export function formatWhen(at: string, code: string, today: Date = new Date()): string {
7
+ const [date, time] = momentParts(at);
8
+ const day = dayOf(date);
9
+ if (!day) return at;
10
+ const timed = TIME.test(time);
11
+ if (timed) {
12
+ const [hours = 0, minutes = 0] = time.split(":").map(Number);
13
+ day.setHours(hours, minutes);
14
+ }
15
+ return new Intl.DateTimeFormat(code, {
16
+ day: "numeric",
17
+ month: "short",
18
+ ...(day.getFullYear() !== today.getFullYear() && { year: "numeric" }),
19
+ ...(timed && { hour: "numeric", minute: "2-digit" }),
20
+ }).format(day);
21
+ }
22
+
23
+ /**
24
+ * Dated things, one under another along a hairline, the newest first: each a dot on the line (in
25
+ * its tone, when it has one), its title with where it stands in a quiet word at its right, when
26
+ * it was under it, and a line about it.
27
+ */
28
+ export function Timeline({ items }: { items: TimelineItem[] }) {
29
+ const part = useParts();
30
+ const { code } = useKit();
31
+ return (
32
+ <ol part="timeline" className={part("sc-timeline")}>
33
+ {items.map((item, index) => (
34
+ <li
35
+ key={item.id ?? index}
36
+ className={part(
37
+ "sc-timeline__item",
38
+ ...(item.tone ? ([`sc-timeline__item--${item.tone}`] as const) : []),
39
+ )}
40
+ >
41
+ <span aria-hidden className={part("sc-timeline__dot")} />
42
+ <div className={part("sc-timeline__body")}>
43
+ <p className={part("sc-timeline__head")}>
44
+ <span className={part("sc-timeline__title")}>{item.title}</span>
45
+ {item.status !== undefined && (
46
+ <span className={part("sc-timeline__status")}>{item.status}</span>
47
+ )}
48
+ </p>
49
+ <time dateTime={item.at} className={part("sc-timeline__when")}>
50
+ {formatWhen(item.at, code)}
51
+ </time>
52
+ {item.text !== undefined && <p className={part("sc-timeline__text")}>{item.text}</p>}
53
+ </div>
54
+ </li>
55
+ ))}
56
+ </ol>
57
+ );
58
+ }
@@ -186,7 +186,51 @@ export type KitPart =
186
186
  | "sc-run__error"
187
187
  | "sc-run__live"
188
188
  | "sc-run__links"
189
- | "sc-run__empty";
189
+ | "sc-run__empty"
190
+ | "sc-agenda"
191
+ | "sc-agenda--day"
192
+ | "sc-agenda__days"
193
+ | "sc-agenda__day"
194
+ | "sc-agenda__day--today"
195
+ | "sc-agenda__day--empty"
196
+ | "sc-agenda__date"
197
+ | "sc-agenda__weekday"
198
+ | "sc-agenda__number"
199
+ | "sc-agenda__events"
200
+ | "sc-agenda__event"
201
+ | "sc-agenda__time"
202
+ | "sc-agenda__what"
203
+ | "sc-agenda__title"
204
+ | "sc-agenda__detail"
205
+ | "sc-agenda__empty"
206
+ | "sc-timeline"
207
+ | "sc-timeline__item"
208
+ | "sc-timeline__item--info"
209
+ | "sc-timeline__item--success"
210
+ | "sc-timeline__item--warning"
211
+ | "sc-timeline__item--danger"
212
+ | "sc-timeline__dot"
213
+ | "sc-timeline__body"
214
+ | "sc-timeline__head"
215
+ | "sc-timeline__title"
216
+ | "sc-timeline__status"
217
+ | "sc-timeline__when"
218
+ | "sc-timeline__text"
219
+ | "sc-board"
220
+ | "sc-board__column"
221
+ | "sc-board__column--over"
222
+ | "sc-board__head"
223
+ | "sc-board__title"
224
+ | "sc-board__count"
225
+ | "sc-board__cards"
226
+ | "sc-board__card"
227
+ | "sc-board__card--dragging"
228
+ | "sc-board__card-title"
229
+ | "sc-board__card-subtitle"
230
+ | "sc-board__card-meta"
231
+ | "sc-board__move"
232
+ | "sc-board__menu"
233
+ | "sc-board__empty";
190
234
 
191
235
  export type KitClassNames = Partial<Record<KitPart, string>> | undefined;
192
236
 
@@ -97,6 +97,30 @@ export interface KitMessages {
97
97
  automation: AutomationMessages;
98
98
  /** An app for the person to connect, as its connection stands. */
99
99
  connect: ConnectMessages;
100
+ /** A calendar's words. */
101
+ calendar: CalendarMessages;
102
+ /** A board's words. */
103
+ board: BoardMessages;
104
+ }
105
+
106
+ /** What a calendar says besides its events. */
107
+ export interface CalendarMessages {
108
+ /** An event that takes the whole day, where its hours would be. */
109
+ allDay: string;
110
+ /** A day with nothing on it. */
111
+ empty: string;
112
+ }
113
+
114
+ /** What a board says besides its cards. */
115
+ export interface BoardMessages {
116
+ /** A card's button that moves it to another column, given the card, for screen readers. */
117
+ move: (card: string) => string;
118
+ /** Over the columns it may go to. */
119
+ moveTo: string;
120
+ /** A card moved, as the person's answer reads. */
121
+ moved: (card: string, column: string) => string;
122
+ /** A column with no cards. */
123
+ empty: string;
100
124
  }
101
125
 
102
126
  /** The words of an app's card: its button, and what it says where the button isn't. */
@@ -177,4 +201,11 @@ export const KIT_MESSAGES: KitMessages = {
177
201
  failed: "It couldn't be connected",
178
202
  preparing: "Getting it ready",
179
203
  },
204
+ calendar: { allDay: "All day", empty: "Nothing planned" },
205
+ board: {
206
+ move: (card) => `Move ${card}`,
207
+ moveTo: "Move to",
208
+ moved: (card, column) => `${card} to ${column}`,
209
+ empty: "Nothing here",
210
+ },
180
211
  };