@cosmicdrift/kumiko-renderer-web 0.191.0 → 0.192.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer-web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.192.0",
|
|
4
4
|
"description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"./styles.css": "./src/styles.css"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
19
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.192.0",
|
|
20
|
+
"@cosmicdrift/kumiko-headless": "0.192.0",
|
|
21
|
+
"@cosmicdrift/kumiko-renderer": "0.192.0",
|
|
22
22
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
23
23
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
24
24
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { FileUploadInput } from "../file-upload";
|
|
4
|
+
|
|
5
|
+
const noop = () => {};
|
|
6
|
+
|
|
7
|
+
// alt="" makes the <img> presentational (no accessible role), so
|
|
8
|
+
// getByRole("img") wouldn't find it — query the DOM directly instead.
|
|
9
|
+
describe("FileUploadInput — image variant preview src (#1950)", () => {
|
|
10
|
+
test("with imageVariant set, the <img> src requests the named variant", () => {
|
|
11
|
+
const { container } = render(
|
|
12
|
+
<FileUploadInput kind="image" id="avatar" value="abc" onChange={noop} imageVariant="thumb" />,
|
|
13
|
+
);
|
|
14
|
+
expect(container.querySelector("img")?.getAttribute("src")).toBe(
|
|
15
|
+
"/api/files/abc/variant/thumb",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("without imageVariant, the <img> src loads the original", () => {
|
|
20
|
+
const { container } = render(
|
|
21
|
+
<FileUploadInput kind="image" id="avatar" value="abc" onChange={noop} />,
|
|
22
|
+
);
|
|
23
|
+
expect(container.querySelector("img")?.getAttribute("src")).toBe("/api/files/abc");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// File/
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// File/image upload widget for the auto-edit form. Uploads via multipart POST
|
|
2
|
+
// to /api/files (mounted by the stack when a storageProvider is set), gets the
|
|
3
|
+
// FileRef UUID back and lifts it up as the field value. Image → round avatar
|
|
4
|
+
// preview (GET /api/files/:id, or the declared variant), file → file name.
|
|
5
5
|
|
|
6
6
|
import { CSRF_HEADER_NAME, readCsrfToken } from "@cosmicdrift/kumiko-dispatcher-live";
|
|
7
7
|
import { ImageIcon, Loader2, Upload } from "lucide-react";
|
|
@@ -17,6 +17,7 @@ export type FileUploadInputProps = {
|
|
|
17
17
|
readonly disabled?: boolean;
|
|
18
18
|
readonly entityType?: string;
|
|
19
19
|
readonly fieldName?: string;
|
|
20
|
+
readonly imageVariant?: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
// "jpg" → ".jpg", "image/png" bleibt. Leere Liste → kein accept-Attribut.
|
|
@@ -34,6 +35,7 @@ export function FileUploadInput({
|
|
|
34
35
|
disabled,
|
|
35
36
|
entityType,
|
|
36
37
|
fieldName,
|
|
38
|
+
imageVariant,
|
|
37
39
|
}: FileUploadInputProps): ReactNode {
|
|
38
40
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
39
41
|
const [uploading, setUploading] = useState(false);
|
|
@@ -79,7 +81,11 @@ export function FileUploadInput({
|
|
|
79
81
|
{kind === "image" &&
|
|
80
82
|
(value !== null ? (
|
|
81
83
|
<img
|
|
82
|
-
src={
|
|
84
|
+
src={
|
|
85
|
+
imageVariant !== undefined
|
|
86
|
+
? `/api/files/${value}/variant/${imageVariant}`
|
|
87
|
+
: `/api/files/${value}`
|
|
88
|
+
}
|
|
83
89
|
alt=""
|
|
84
90
|
className="size-16 rounded-full border object-cover"
|
|
85
91
|
/>
|
package/src/primitives/index.tsx
CHANGED
|
@@ -433,6 +433,7 @@ function DefaultInput(props: InputProps): ReactNode {
|
|
|
433
433
|
{...(props.disabled !== undefined && { disabled: props.disabled })}
|
|
434
434
|
{...(props.entityType !== undefined && { entityType: props.entityType })}
|
|
435
435
|
{...(props.fieldName !== undefined && { fieldName: props.fieldName })}
|
|
436
|
+
{...(props.imageVariant !== undefined && { imageVariant: props.imageVariant })}
|
|
436
437
|
/>
|
|
437
438
|
);
|
|
438
439
|
case "date":
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { cn } from "../lib/cn";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Progress bar, `value` 0..1 (clamped).
|
|
6
|
+
*
|
|
7
|
+
* The track lives in its own inner element so a parent that pads or
|
|
8
|
+
* stretches its direct children (e.g. RenderEdit's wizard chrome) never
|
|
9
|
+
* touches the track's own `h-2` — a caller's `className` reaches the
|
|
10
|
+
* outer wrapper, not the height-bearing track.
|
|
11
|
+
*/
|
|
5
12
|
export function ProgressBar({
|
|
6
13
|
value,
|
|
7
14
|
className,
|
|
@@ -13,18 +20,20 @@ export function ProgressBar({
|
|
|
13
20
|
}): ReactNode {
|
|
14
21
|
const pct = Math.max(0, Math.min(1, value));
|
|
15
22
|
return (
|
|
16
|
-
<div
|
|
17
|
-
data-testid={testId}
|
|
18
|
-
role="progressbar"
|
|
19
|
-
aria-valuenow={Math.round(pct * 100)}
|
|
20
|
-
aria-valuemin={0}
|
|
21
|
-
aria-valuemax={100}
|
|
22
|
-
className={cn("relative h-2 w-full overflow-hidden rounded-full bg-muted", className)}
|
|
23
|
-
>
|
|
23
|
+
<div className={cn("w-full", className)}>
|
|
24
24
|
<div
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
data-testid={testId}
|
|
26
|
+
role="progressbar"
|
|
27
|
+
aria-valuenow={Math.round(pct * 100)}
|
|
28
|
+
aria-valuemin={0}
|
|
29
|
+
aria-valuemax={100}
|
|
30
|
+
className="relative h-2 w-full overflow-hidden rounded-full bg-muted"
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
className="absolute inset-y-0 left-0 rounded-full bg-primary"
|
|
34
|
+
style={{ width: `${pct * 100}%` }}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
28
37
|
</div>
|
|
29
38
|
);
|
|
30
39
|
}
|