@tangle-network/ui 11.30.0 → 11.30.2
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/primitives/sidebar-drop-zone.js +2 -2
- package/dist/primitives/toolbar.d.ts +15 -13
- package/dist/primitives/toolbar.js +2 -2
- package/dist/primitives/upload-progress.js +2 -2
- package/package.json +2 -2
- package/src/primitives/sidebar-drop-zone.tsx +2 -2
- package/src/primitives/toolbar.test.tsx +16 -13
- package/src/primitives/toolbar.tsx +22 -19
- package/src/primitives/upload-progress.tsx +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 11.30.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- df8514f: `Toolbar` keeps its filters at content width on one `lg` line while they fit, and gives search the rest between a 16rem floor and `max-w-sm`. The even split had clipped the Assets bar's third field (Schedule for) at the scroll edge at 1440px while search sat at 384px. Filters now shrink and scroll only once search is at its floor.
|
|
8
|
+
|
|
9
|
+
## 11.30.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0b6d379: `Toolbar` wraps its filters below `lg` instead of scrolling them as one line, so a phone shows every filter: at 390px the second select had started at 403px, off screen. From `lg` up the filters still share one scrollable line. `SidebarDropZone` sets its title on the label size (`--font-size-label`, 14px) and its description on the help size (`--font-size-help`, 12px) instead of 12px and 10px literals, and `UploadProgress` sets its size and error lines on the help size.
|
|
14
|
+
|
|
3
15
|
## 11.30.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -73,11 +73,11 @@ function SidebarDropZone({ onDrop, accept, disabled, title = "Drop files here",
|
|
|
73
73
|
children: icon ?? /* @__PURE__ */ jsx(Upload, { className: "h-4 w-4" })
|
|
74
74
|
}),
|
|
75
75
|
/* @__PURE__ */ jsx("p", {
|
|
76
|
-
className: cn("text-
|
|
76
|
+
className: cn("text-[length:var(--font-size-label,0.875rem)] font-medium", dragOver ? "text-[var(--text-primary,hsl(var(--foreground)))]" : "text-[var(--text-muted,hsl(var(--muted-foreground)))]"),
|
|
77
77
|
children: title
|
|
78
78
|
}),
|
|
79
79
|
description && /* @__PURE__ */ jsx("p", {
|
|
80
|
-
className: "text-[
|
|
80
|
+
className: "text-[length:var(--font-size-help,0.75rem)] text-[var(--text-muted,hsl(var(--muted-foreground)))]",
|
|
81
81
|
children: description
|
|
82
82
|
})
|
|
83
83
|
]
|
|
@@ -10,20 +10,22 @@ import * as React from "react";
|
|
|
10
10
|
* own visual language, so one row carried a native `<select>`, a custom input
|
|
11
11
|
* and a segmented control side by side.
|
|
12
12
|
*
|
|
13
|
-
* So
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* So on one `lg` line the filters keep their content width while it fits,
|
|
14
|
+
* search takes what is left between a 16rem floor and `max-w-sm`, and `actions`
|
|
15
|
+
* stays at its content width. The row is the one place those controls are
|
|
16
|
+
* composed, which is what keeps them looking like one set.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* then stops search claiming more than it can use when there are few filters.
|
|
18
|
+
* Both earlier layouts failed one side. Filters at content width with no floor
|
|
19
|
+
* starved search: a 537px filter row in an 832px toolbar left it 89px. Splitting
|
|
20
|
+
* the row evenly clipped filters that would have fitted: at 1440px the Assets
|
|
21
|
+
* bar cut its third field at the scroll edge while search sat at 384px. With the
|
|
22
|
+
* floor, filters shrink and scroll only once search is down to 16rem.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* the
|
|
24
|
+
* Below `lg` the row becomes a column and the filters wrap onto as many lines
|
|
25
|
+
* as they need. Scrolling them as one line hid every filter past the first
|
|
26
|
+
* behind the screen edge on a phone: at 390px the second select started at
|
|
27
|
+
* 403px and a third at 652px, with nothing to say more were there. From `lg`
|
|
28
|
+
* up they sit on one line as above.
|
|
27
29
|
*/
|
|
28
30
|
/**
|
|
29
31
|
* Slots only — `children` is omitted deliberately. A free child would render as
|
|
@@ -34,7 +36,7 @@ import * as React from "react";
|
|
|
34
36
|
export interface ToolbarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
35
37
|
/** The one control that expands to fill the row. */
|
|
36
38
|
search?: React.ReactNode;
|
|
37
|
-
/** Filters, in reading order.
|
|
39
|
+
/** Filters, in reading order. Wrapped below `lg`; one scrollable line from `lg` up. */
|
|
38
40
|
filters?: React.ReactNode;
|
|
39
41
|
/** View switches and exports, pinned to the end. */
|
|
40
42
|
actions?: React.ReactNode;
|
|
@@ -8,11 +8,11 @@ const Toolbar = React.forwardRef(({ className, search, filters, actions, ...prop
|
|
|
8
8
|
...props,
|
|
9
9
|
children: [
|
|
10
10
|
search && /* @__PURE__ */ jsx("div", {
|
|
11
|
-
className: "min-w-0 lg:max-w-sm lg:flex-1",
|
|
11
|
+
className: "min-w-0 lg:min-w-64 lg:max-w-sm lg:flex-1",
|
|
12
12
|
children: search
|
|
13
13
|
}),
|
|
14
14
|
filters && /* @__PURE__ */ jsx("div", {
|
|
15
|
-
className: "flex min-w-0 items-center gap-
|
|
15
|
+
className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 py-1 lg:flex-initial lg:flex-nowrap lg:gap-2 lg:overflow-x-auto",
|
|
16
16
|
children: filters
|
|
17
17
|
}),
|
|
18
18
|
actions && /* @__PURE__ */ jsx("div", {
|
|
@@ -33,7 +33,7 @@ function UploadProgress({ files, onRemove, onRetry, className }) {
|
|
|
33
33
|
className: "truncate font-medium text-foreground",
|
|
34
34
|
children: file.name
|
|
35
35
|
}), /* @__PURE__ */ jsx("span", {
|
|
36
|
-
className: "shrink-0 text-
|
|
36
|
+
className: "shrink-0 text-[length:var(--font-size-help,0.75rem)] text-muted-foreground",
|
|
37
37
|
children: formatSize(file.size)
|
|
38
38
|
})]
|
|
39
39
|
}),
|
|
@@ -45,7 +45,7 @@ function UploadProgress({ files, onRemove, onRetry, className }) {
|
|
|
45
45
|
})
|
|
46
46
|
}),
|
|
47
47
|
file.status === "error" && file.error && /* @__PURE__ */ jsx("p", {
|
|
48
|
-
className: "mt-0.5 text-
|
|
48
|
+
className: "mt-0.5 text-[length:var(--font-size-help,0.75rem)] text-[var(--surface-danger-text)]",
|
|
49
49
|
children: file.error
|
|
50
50
|
})
|
|
51
51
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "11.30.
|
|
3
|
+
"version": "11.30.2",
|
|
4
4
|
"description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"peerDependencies": {
|
|
151
151
|
"@hocuspocus/provider": ">=2.15.0 <5.0.0",
|
|
152
152
|
"@nanostores/react": ">=0.8.4 <3.0.0",
|
|
153
|
-
"@tangle-network/brand": "^1.19.
|
|
153
|
+
"@tangle-network/brand": "^1.19.3",
|
|
154
154
|
"@tiptap/core": ">=3.20.4 <4.0.0",
|
|
155
155
|
"@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
|
|
156
156
|
"@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
|
|
@@ -130,7 +130,7 @@ export function SidebarDropZone({
|
|
|
130
130
|
{icon ?? <Upload className="h-4 w-4" />}
|
|
131
131
|
</div>
|
|
132
132
|
<p className={cn(
|
|
133
|
-
"text-
|
|
133
|
+
"text-[length:var(--font-size-label,0.875rem)] font-medium",
|
|
134
134
|
dragOver
|
|
135
135
|
? "text-[var(--text-primary,hsl(var(--foreground)))]"
|
|
136
136
|
: "text-[var(--text-muted,hsl(var(--muted-foreground)))]",
|
|
@@ -138,7 +138,7 @@ export function SidebarDropZone({
|
|
|
138
138
|
{title}
|
|
139
139
|
</p>
|
|
140
140
|
{description && (
|
|
141
|
-
<p className="text-[
|
|
141
|
+
<p className="text-[length:var(--font-size-help,0.75rem)] text-[var(--text-muted,hsl(var(--muted-foreground)))]">
|
|
142
142
|
{description}
|
|
143
143
|
</p>
|
|
144
144
|
)}
|
|
@@ -24,11 +24,10 @@ describe("Toolbar", () => {
|
|
|
24
24
|
expect(row.children).toHaveLength(1);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
it("lets search and filters share the row, holding actions at content width", () => {
|
|
27
|
+
// Filters keep their content width while it fits and shrink (scrolling) only
|
|
28
|
+
// once search reaches its 16rem floor; an even split clipped a third filter at
|
|
29
|
+
// 1440px, and no floor left search 89px wide in an 832px toolbar.
|
|
30
|
+
it("keeps filters at content width above a search floor, actions at content width", () => {
|
|
32
31
|
const { container } = render(
|
|
33
32
|
<Toolbar
|
|
34
33
|
actions={<button type="button">Export</button>}
|
|
@@ -39,21 +38,25 @@ describe("Toolbar", () => {
|
|
|
39
38
|
const [searchSlot, filterSlot, actionsSlot] = Array.from(
|
|
40
39
|
(container.firstElementChild as HTMLElement).children,
|
|
41
40
|
) as HTMLElement[];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
expect(
|
|
46
|
-
expect(
|
|
41
|
+
const search = searchSlot.className.split(" ");
|
|
42
|
+
const filters = filterSlot.className.split(" ");
|
|
43
|
+
expect(search).toEqual(expect.arrayContaining(["lg:flex-1", "lg:min-w-64", "lg:max-w-sm", "min-w-0"]));
|
|
44
|
+
expect(filters).toEqual(expect.arrayContaining(["lg:flex-initial", "min-w-0", "lg:overflow-x-auto"]));
|
|
45
|
+
expect(filters).not.toContain("lg:flex-1");
|
|
47
46
|
expect(actionsSlot.className).toContain("shrink-0");
|
|
48
47
|
expect(actionsSlot.className).not.toContain("flex-1");
|
|
49
48
|
});
|
|
50
49
|
|
|
51
|
-
it("
|
|
50
|
+
it("wraps the filters on a phone and scrolls one line from lg up", () => {
|
|
52
51
|
const { container } = render(<Toolbar filters={<span>Product</span>} />);
|
|
53
52
|
const filterSlot = (container.firstElementChild as HTMLElement)
|
|
54
53
|
.children[0] as HTMLElement;
|
|
55
|
-
|
|
56
|
-
expect(
|
|
54
|
+
const classes = filterSlot.className.split(" ");
|
|
55
|
+
expect(classes).toContain("flex-wrap");
|
|
56
|
+
expect(classes).not.toContain("overflow-x-auto");
|
|
57
|
+
expect(classes).toContain("lg:flex-nowrap");
|
|
58
|
+
expect(classes).toContain("lg:overflow-x-auto");
|
|
59
|
+
expect(classes).toContain("min-w-0");
|
|
57
60
|
});
|
|
58
61
|
|
|
59
62
|
// A free child would render as a bare flex item with none of the slots'
|
|
@@ -11,20 +11,22 @@ import { cn } from "../lib/utils";
|
|
|
11
11
|
* own visual language, so one row carried a native `<select>`, a custom input
|
|
12
12
|
* and a segmented control side by side.
|
|
13
13
|
*
|
|
14
|
-
* So
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* So on one `lg` line the filters keep their content width while it fits,
|
|
15
|
+
* search takes what is left between a 16rem floor and `max-w-sm`, and `actions`
|
|
16
|
+
* stays at its content width. The row is the one place those controls are
|
|
17
|
+
* composed, which is what keeps them looking like one set.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* then stops search claiming more than it can use when there are few filters.
|
|
19
|
+
* Both earlier layouts failed one side. Filters at content width with no floor
|
|
20
|
+
* starved search: a 537px filter row in an 832px toolbar left it 89px. Splitting
|
|
21
|
+
* the row evenly clipped filters that would have fitted: at 1440px the Assets
|
|
22
|
+
* bar cut its third field at the scroll edge while search sat at 384px. With the
|
|
23
|
+
* floor, filters shrink and scroll only once search is down to 16rem.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* the
|
|
25
|
+
* Below `lg` the row becomes a column and the filters wrap onto as many lines
|
|
26
|
+
* as they need. Scrolling them as one line hid every filter past the first
|
|
27
|
+
* behind the screen edge on a phone: at 390px the second select started at
|
|
28
|
+
* 403px and a third at 652px, with nothing to say more were there. From `lg`
|
|
29
|
+
* up they sit on one line as above.
|
|
28
30
|
*/
|
|
29
31
|
/**
|
|
30
32
|
* Slots only — `children` is omitted deliberately. A free child would render as
|
|
@@ -36,7 +38,7 @@ export interface ToolbarProps
|
|
|
36
38
|
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
37
39
|
/** The one control that expands to fill the row. */
|
|
38
40
|
search?: React.ReactNode;
|
|
39
|
-
/** Filters, in reading order.
|
|
41
|
+
/** Filters, in reading order. Wrapped below `lg`; one scrollable line from `lg` up. */
|
|
40
42
|
filters?: React.ReactNode;
|
|
41
43
|
/** View switches and exports, pinned to the end. */
|
|
42
44
|
actions?: React.ReactNode;
|
|
@@ -54,13 +56,14 @@ const Toolbar = React.forwardRef<HTMLDivElement, ToolbarProps>(
|
|
|
54
56
|
)}
|
|
55
57
|
{...props}
|
|
56
58
|
>
|
|
57
|
-
{search && <div className="min-w-0 lg:max-w-sm lg:flex-1">{search}</div>}
|
|
59
|
+
{search && <div className="min-w-0 lg:min-w-64 lg:max-w-sm lg:flex-1">{search}</div>}
|
|
58
60
|
{filters && (
|
|
59
|
-
// `py-1` leaves room for a focus ring above and below;
|
|
60
|
-
// scroll container clips it, which is the
|
|
61
|
-
// makes. No negative margin — pulling the
|
|
62
|
-
// what made every page report 4px of
|
|
63
|
-
|
|
61
|
+
// `py-1` leaves room for a focus ring above and below; on the single
|
|
62
|
+
// `lg` line the scroll container clips it horizontally, which is the
|
|
63
|
+
// trade a one-line filter row makes. No negative margin — pulling the
|
|
64
|
+
// row wider than its parent is what made every page report 4px of
|
|
65
|
+
// horizontal overflow.
|
|
66
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 py-1 lg:flex-initial lg:flex-nowrap lg:gap-2 lg:overflow-x-auto">
|
|
64
67
|
{filters}
|
|
65
68
|
</div>
|
|
66
69
|
)}
|
|
@@ -65,7 +65,7 @@ export function UploadProgress({ files, onRemove, onRetry, className }: UploadPr
|
|
|
65
65
|
<div className="min-w-0 flex-1">
|
|
66
66
|
<div className="flex items-center gap-2">
|
|
67
67
|
<span className="truncate font-medium text-foreground">{file.name}</span>
|
|
68
|
-
<span className="shrink-0 text-
|
|
68
|
+
<span className="shrink-0 text-[length:var(--font-size-help,0.75rem)] text-muted-foreground">{formatSize(file.size)}</span>
|
|
69
69
|
</div>
|
|
70
70
|
{/* Progress bar */}
|
|
71
71
|
{file.status === "uploading" && file.progress !== undefined && (
|
|
@@ -78,7 +78,7 @@ export function UploadProgress({ files, onRemove, onRetry, className }: UploadPr
|
|
|
78
78
|
)}
|
|
79
79
|
{/* Error message */}
|
|
80
80
|
{file.status === "error" && file.error && (
|
|
81
|
-
<p className="mt-0.5 text-
|
|
81
|
+
<p className="mt-0.5 text-[length:var(--font-size-help,0.75rem)] text-[var(--surface-danger-text)]">{file.error}</p>
|
|
82
82
|
)}
|
|
83
83
|
</div>
|
|
84
84
|
|