@tangle-network/ui 11.23.0 → 11.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/chat/agent-timeline.d.ts +13 -1
- package/dist/chat/agent-timeline.js +25 -4
- package/dist/chat/chat-message.d.ts +13 -1
- package/dist/chat/chat-message.js +9 -5
- package/dist/chat/message-author.d.ts +24 -0
- package/dist/chat/message-author.js +53 -0
- package/dist/chat/user-message.d.ts +12 -2
- package/dist/chat/user-message.js +52 -16
- package/dist/chat.d.ts +3 -1
- package/dist/chat.js +2 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/primitives/pagination.d.ts +47 -9
- package/dist/primitives/pagination.js +50 -20
- package/dist/types/message.d.ts +19 -0
- package/dist/types.d.ts +2 -2
- package/package.json +2 -2
- package/src/chat/agent-timeline.tsx +33 -3
- package/src/chat/chat-message.tsx +37 -10
- package/src/chat/index.ts +2 -0
- package/src/chat/message-author.stories.tsx +170 -0
- package/src/chat/message-author.test.tsx +189 -0
- package/src/chat/message-author.tsx +52 -0
- package/src/chat/user-message.tsx +63 -8
- package/src/markdown/code-block.test.tsx +2 -1
- package/src/primitives/pagination-alert.stories.tsx +35 -0
- package/src/primitives/pagination-alert.test.tsx +90 -0
- package/src/primitives/pagination.tsx +141 -40
- package/src/types/message.ts +20 -0
|
@@ -3,23 +3,64 @@ import * as React from "react";
|
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
4
|
import { buttonVariants } from "./button";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* Page controls for a paged list or table: previous, the page numbers around
|
|
8
|
-
* the current one with the first and last always reachable, and next.
|
|
9
|
-
*
|
|
10
|
-
* Pages are zero-based in the API and one-based on screen. With one page or
|
|
11
|
-
* none there is nothing to choose, so it renders nothing.
|
|
12
|
-
*/
|
|
13
|
-
export interface PaginationProps
|
|
6
|
+
interface PaginationBaseProps
|
|
14
7
|
extends Omit<React.HTMLAttributes<HTMLElement>, "onChange" | "children"> {
|
|
15
8
|
/** The current page, zero-based. */
|
|
16
9
|
page: number;
|
|
17
10
|
pageCount: number;
|
|
18
|
-
onPageChange: (page: number) => void;
|
|
19
11
|
/** Pages shown on each side of the current one before an ellipsis. */
|
|
20
12
|
siblings?: number;
|
|
21
13
|
}
|
|
22
14
|
|
|
15
|
+
export interface PaginationButtonProps extends PaginationBaseProps {
|
|
16
|
+
onPageChange: (page: number) => void;
|
|
17
|
+
hrefFor?: never;
|
|
18
|
+
linkComponent?: never;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PaginationLinkProps extends PaginationBaseProps {
|
|
22
|
+
/** The URL of a page, given the zero-based page. */
|
|
23
|
+
hrefFor: (page: number) => string;
|
|
24
|
+
/** Renders each enabled control. Receives `href`; defaults to `"a"`. */
|
|
25
|
+
linkComponent?: React.ElementType;
|
|
26
|
+
/**
|
|
27
|
+
* Called with the target page when a link to another page is clicked, unless
|
|
28
|
+
* the click opens it in another tab or window.
|
|
29
|
+
*/
|
|
30
|
+
onPageChange?: (page: number) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Page controls for a paged list or table: previous, the page numbers around
|
|
35
|
+
* the current one with the first and last always reachable, and next.
|
|
36
|
+
*
|
|
37
|
+
* Pages are zero-based in the API and one-based on screen. With one page or
|
|
38
|
+
* none there is nothing to choose, so it renders nothing.
|
|
39
|
+
*
|
|
40
|
+
* Give `onPageChange` to render buttons, or `hrefFor` to render links for a
|
|
41
|
+
* list paged by URL. In link mode each enabled control is rendered by
|
|
42
|
+
* `linkComponent` (an `<a>` by default) with `href`, `className`,
|
|
43
|
+
* `aria-label`, `aria-current` and children; a disabled previous or next is a
|
|
44
|
+
* `<span aria-disabled="true">` with no `href`. A router whose link takes `to`
|
|
45
|
+
* instead of `href` needs a small adapter:
|
|
46
|
+
*
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { Link } from "react-router";
|
|
49
|
+
*
|
|
50
|
+
* const RouterLink = ({ href, ...props }: React.ComponentProps<"a">) => (
|
|
51
|
+
* <Link to={href ?? ""} {...props} />
|
|
52
|
+
* );
|
|
53
|
+
*
|
|
54
|
+
* <Pagination
|
|
55
|
+
* page={page}
|
|
56
|
+
* pageCount={pageCount}
|
|
57
|
+
* hrefFor={(p) => `?page=${p + 1}`}
|
|
58
|
+
* linkComponent={RouterLink}
|
|
59
|
+
* />
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export type PaginationProps = PaginationButtonProps | PaginationLinkProps;
|
|
63
|
+
|
|
23
64
|
type PageSlot = number | "gap-start" | "gap-end";
|
|
24
65
|
|
|
25
66
|
/**
|
|
@@ -43,10 +84,26 @@ export function pageSlots(page: number, pageCount: number, siblings = 1): PageSl
|
|
|
43
84
|
|
|
44
85
|
const pageButton = cn(buttonVariants({ variant: "ghost", size: "sm" }), "min-w-8 px-2 tabular-nums");
|
|
45
86
|
|
|
87
|
+
/** A click that opens the link in another tab or window leaves this page where it is. */
|
|
88
|
+
function opensElsewhere(event: React.MouseEvent) {
|
|
89
|
+
return event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface ControlProps {
|
|
93
|
+
target: number;
|
|
94
|
+
label: string;
|
|
95
|
+
className: string;
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
current?: boolean;
|
|
98
|
+
children: React.ReactNode;
|
|
99
|
+
}
|
|
100
|
+
|
|
46
101
|
export function Pagination({
|
|
47
102
|
page,
|
|
48
103
|
pageCount,
|
|
49
104
|
onPageChange,
|
|
105
|
+
hrefFor,
|
|
106
|
+
linkComponent: LinkComponent = "a",
|
|
50
107
|
siblings = 1,
|
|
51
108
|
className,
|
|
52
109
|
"aria-label": ariaLabel = "Pagination",
|
|
@@ -58,40 +115,82 @@ export function Pagination({
|
|
|
58
115
|
const current = Math.min(Math.max(page, 0), last);
|
|
59
116
|
const go = (next: number) => {
|
|
60
117
|
const target = Math.min(Math.max(next, 0), last);
|
|
61
|
-
if (target !== current) onPageChange(target);
|
|
118
|
+
if (target !== current) onPageChange?.(target);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const control = ({ target, label, className: controlClass, disabled, current: isCurrent, children }: ControlProps) => {
|
|
122
|
+
const ariaCurrent = isCurrent ? ("page" as const) : undefined;
|
|
123
|
+
if (!hrefFor) {
|
|
124
|
+
return (
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
aria-label={label}
|
|
128
|
+
aria-current={ariaCurrent}
|
|
129
|
+
disabled={disabled}
|
|
130
|
+
onClick={() => go(target)}
|
|
131
|
+
className={controlClass}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</button>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (disabled) {
|
|
138
|
+
return (
|
|
139
|
+
<span role="link" aria-label={label} aria-disabled="true" className={cn(controlClass, "pointer-events-none")}>
|
|
140
|
+
{children}
|
|
141
|
+
</span>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return (
|
|
145
|
+
<LinkComponent
|
|
146
|
+
href={hrefFor(target)}
|
|
147
|
+
aria-label={label}
|
|
148
|
+
aria-current={ariaCurrent}
|
|
149
|
+
onClick={
|
|
150
|
+
onPageChange
|
|
151
|
+
? (event: React.MouseEvent) => {
|
|
152
|
+
if (!opensElsewhere(event)) go(target);
|
|
153
|
+
}
|
|
154
|
+
: undefined
|
|
155
|
+
}
|
|
156
|
+
className={controlClass}
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</LinkComponent>
|
|
160
|
+
);
|
|
62
161
|
};
|
|
63
162
|
|
|
64
163
|
return (
|
|
65
164
|
<nav aria-label={ariaLabel} className={cn("flex items-center", className)} {...rest}>
|
|
66
165
|
<ul className="flex flex-wrap items-center gap-1">
|
|
67
166
|
<li>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
disabled
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
167
|
+
{control({
|
|
168
|
+
target: current - 1,
|
|
169
|
+
label: "Previous page",
|
|
170
|
+
disabled: current === 0,
|
|
171
|
+
className: cn(pageButton, "gap-1 pl-1.5"),
|
|
172
|
+
children: (
|
|
173
|
+
<>
|
|
174
|
+
<ChevronLeft aria-hidden="true" />
|
|
175
|
+
<span className="hidden sm:inline">Previous</span>
|
|
176
|
+
</>
|
|
177
|
+
),
|
|
178
|
+
})}
|
|
78
179
|
</li>
|
|
79
180
|
{pageSlots(current, pageCount, siblings).map((slot) =>
|
|
80
181
|
typeof slot === "number" ? (
|
|
81
182
|
<li key={slot}>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
className={cn(
|
|
183
|
+
{control({
|
|
184
|
+
target: slot,
|
|
185
|
+
label: `Page ${slot + 1}`,
|
|
186
|
+
current: slot === current,
|
|
187
|
+
className: cn(
|
|
88
188
|
pageButton,
|
|
89
189
|
slot === current &&
|
|
90
190
|
"border-border bg-card font-semibold text-foreground hover:bg-card",
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
</button>
|
|
191
|
+
),
|
|
192
|
+
children: slot + 1,
|
|
193
|
+
})}
|
|
95
194
|
</li>
|
|
96
195
|
) : (
|
|
97
196
|
<li key={slot} aria-hidden="true" className="flex min-w-8 justify-center text-sm text-muted-foreground">
|
|
@@ -100,16 +199,18 @@ export function Pagination({
|
|
|
100
199
|
),
|
|
101
200
|
)}
|
|
102
201
|
<li>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
disabled
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
202
|
+
{control({
|
|
203
|
+
target: current + 1,
|
|
204
|
+
label: "Next page",
|
|
205
|
+
disabled: current === last,
|
|
206
|
+
className: cn(pageButton, "gap-1 pr-1.5"),
|
|
207
|
+
children: (
|
|
208
|
+
<>
|
|
209
|
+
<span className="hidden sm:inline">Next</span>
|
|
210
|
+
<ChevronRight aria-hidden="true" />
|
|
211
|
+
</>
|
|
212
|
+
),
|
|
213
|
+
})}
|
|
113
214
|
</li>
|
|
114
215
|
</ul>
|
|
115
216
|
</nav>
|
package/src/types/message.ts
CHANGED
|
@@ -11,3 +11,23 @@ export interface SessionMessage {
|
|
|
11
11
|
/** Monotonically increasing insertion index for stable ordering. */
|
|
12
12
|
_insertionIndex?: number;
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Who wrote a chat message, for a transcript with more than one person beside
|
|
17
|
+
* the agent: a client, an attorney and the agent in one thread.
|
|
18
|
+
*/
|
|
19
|
+
export interface ChatAuthor {
|
|
20
|
+
/**
|
|
21
|
+
* Stable participant id. A transcript's `viewerId` is compared with it to
|
|
22
|
+
* tell the reader's own messages apart, and it keys the avatar's tone.
|
|
23
|
+
*/
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
/**
|
|
27
|
+
* The participant's part in the conversation, shown as a tag beside the
|
|
28
|
+
* name, such as "Attorney". Unrelated to the message's `role`.
|
|
29
|
+
*/
|
|
30
|
+
role?: string;
|
|
31
|
+
/** Falls back to initials when absent or when the image fails to load. */
|
|
32
|
+
avatarUrl?: string;
|
|
33
|
+
}
|