@djangocfg/ui-tools 2.1.465 → 2.1.468
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/tree/index.cjs +8 -6
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.mjs +8 -6
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/tools/chat/README.md +5 -1
- package/src/tools/chat/messages/MessageBubble.tsx +5 -4
- package/src/tools/chat/messages/MessageDateSeparator.tsx +36 -0
- package/src/tools/chat/messages/MessageList.tsx +81 -5
- package/src/tools/chat/messages/__tests__/messageDate.test.ts +50 -0
- package/src/tools/chat/messages/index.ts +7 -0
- package/src/tools/chat/messages/messageDate.ts +79 -0
- package/src/tools/chat/shell/SuggestedPrompts.tsx +26 -33
- package/src/tools/chat/shell/__tests__/SuggestedPrompts.test.tsx +51 -0
- package/src/tools/chat/styles/bubbleTokens.ts +9 -10
- package/src/tools/data/Tree/README.md +3 -0
- package/src/tools/data/Tree/TreeRoot.tsx +13 -6
- package/src/tools/data/Tree/__tests__/TreeRoot.accessibility.test.tsx +36 -0
- package/src/tools/data/Tree/components/TreeEmpty.tsx +1 -0
|
@@ -219,6 +219,7 @@ function TreeRootShell<T>({
|
|
|
219
219
|
// nav, type-ahead, programmatic). Centralised so every focus source gets
|
|
220
220
|
// consistent scrolling — previously only type-ahead scrolled.
|
|
221
221
|
const focusedId = ctx.focused;
|
|
222
|
+
const isEmpty = ctx.flatRows.length === 0;
|
|
222
223
|
useEffect(() => {
|
|
223
224
|
if (!focusedId) return;
|
|
224
225
|
const el = containerRef.current?.querySelector<HTMLElement>(
|
|
@@ -249,11 +250,15 @@ function TreeRootShell<T>({
|
|
|
249
250
|
const treeBody = (
|
|
250
251
|
<div
|
|
251
252
|
ref={setContainerRef}
|
|
252
|
-
tabIndex={0}
|
|
253
|
-
role=
|
|
254
|
-
aria-label={ctx.labels.ariaLabel}
|
|
255
|
-
aria-multiselectable={
|
|
256
|
-
|
|
253
|
+
tabIndex={isEmpty ? undefined : 0}
|
|
254
|
+
role={isEmpty ? undefined : 'tree'}
|
|
255
|
+
aria-label={isEmpty ? undefined : ctx.labels.ariaLabel}
|
|
256
|
+
aria-multiselectable={
|
|
257
|
+
!isEmpty && ctx.selectionMode === 'multiple' ? true : undefined
|
|
258
|
+
}
|
|
259
|
+
aria-activedescendant={
|
|
260
|
+
!isEmpty && focusedId ? treeRowDomId(focusedId) : undefined
|
|
261
|
+
}
|
|
257
262
|
className={cn(
|
|
258
263
|
'group/tree flex h-full w-full flex-col gap-2 rounded-sm outline-none',
|
|
259
264
|
'focus-visible:ring-1 focus-visible:ring-ring/50',
|
|
@@ -264,7 +269,9 @@ function TreeRootShell<T>({
|
|
|
264
269
|
>
|
|
265
270
|
{enableSearch ? <TreeSearchInput className="mx-2 mt-2" /> : null}
|
|
266
271
|
<div className="flex min-h-0 flex-1 flex-col overflow-auto px-1">
|
|
267
|
-
<TreeContent<T> role="group"
|
|
272
|
+
<TreeContent<T> role={isEmpty ? "presentation" : "group"}>
|
|
273
|
+
{renderRow}
|
|
274
|
+
</TreeContent>
|
|
268
275
|
{/* Empty-area: catches right-clicks on whitespace below the
|
|
269
276
|
last row + acts as the root drop target for DnD. Always
|
|
270
277
|
rendered — it self-disables when there's nothing to do. */}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act } from 'react';
|
|
4
|
+
import { createRoot } from 'react-dom/client';
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { TreeRoot } from '../TreeRoot';
|
|
8
|
+
|
|
9
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
10
|
+
|
|
11
|
+
describe('TreeRoot empty semantics', () => {
|
|
12
|
+
const mounted: Array<ReturnType<typeof createRoot>> = [];
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
act(() => mounted.splice(0).forEach((root) => root.unmount()));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('announces the empty state without exposing an invalid empty tree', () => {
|
|
19
|
+
const container = document.createElement('div');
|
|
20
|
+
const root = createRoot(container);
|
|
21
|
+
mounted.push(root);
|
|
22
|
+
|
|
23
|
+
act(() => {
|
|
24
|
+
root.render(
|
|
25
|
+
<TreeRoot<{ name: string }>
|
|
26
|
+
data={[]}
|
|
27
|
+
getItemName={(node) => node.data.name}
|
|
28
|
+
/>,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(container.querySelector('[role="tree"]')).toBeNull();
|
|
33
|
+
expect(container.querySelector('[data-tree-root]')?.hasAttribute('tabindex')).toBe(false);
|
|
34
|
+
expect(container.querySelector('[role="status"]')?.textContent).toBe('Nothing to show');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -10,6 +10,7 @@ export interface TreeEmptyProps {
|
|
|
10
10
|
export function TreeEmpty({ children, className }: TreeEmptyProps) {
|
|
11
11
|
return (
|
|
12
12
|
<div
|
|
13
|
+
role="status"
|
|
13
14
|
className={cn(
|
|
14
15
|
'flex h-full min-h-32 items-center justify-center px-4 py-6 text-sm text-muted-foreground',
|
|
15
16
|
className,
|