@nibapp/ui 0.0.0-stage → 0.1.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 +35 -0
- package/LICENSE +21 -0
- package/README.md +124 -2
- package/TRADEMARKS.md +22 -0
- package/dist/_bundled/money/index.d.ts +98 -0
- package/dist/_bundled/money/index.js +152 -0
- package/dist/_bundled/tokens/index.d.ts +2 -0
- package/dist/_bundled/tokens/index.js +2 -0
- package/dist/_bundled/tokens/palette.gen.d.ts +51 -0
- package/dist/_bundled/tokens/palette.gen.js +206 -0
- package/dist/_bundled/tokens/spec.d.ts +291 -0
- package/dist/_bundled/tokens/spec.js +221 -0
- package/dist/components/accordion.d.ts +27 -0
- package/dist/components/accordion.js +66 -0
- package/dist/components/alert-dialog.d.ts +59 -0
- package/dist/components/alert-dialog.js +82 -0
- package/dist/components/alert.d.ts +141 -0
- package/dist/components/alert.js +55 -0
- package/dist/components/appear.d.ts +21 -0
- package/dist/components/appear.js +42 -0
- package/dist/components/avatar.d.ts +196 -0
- package/dist/components/avatar.js +135 -0
- package/dist/components/badge.d.ts +183 -0
- package/dist/components/badge.js +68 -0
- package/dist/components/block-stack.d.ts +33 -0
- package/dist/components/block-stack.js +197 -0
- package/dist/components/button.d.ts +327 -0
- package/dist/components/button.js +101 -0
- package/dist/components/card.d.ts +131 -0
- package/dist/components/card.js +100 -0
- package/dist/components/checkbox.d.ts +34 -0
- package/dist/components/checkbox.js +37 -0
- package/dist/components/choice-row.d.ts +104 -0
- package/dist/components/choice-row.js +56 -0
- package/dist/components/clamped-text.d.ts +17 -0
- package/dist/components/clamped-text.js +47 -0
- package/dist/components/code-block.d.ts +48 -0
- package/dist/components/code-block.js +124 -0
- package/dist/components/code-input.d.ts +49 -0
- package/dist/components/code-input.js +59 -0
- package/dist/components/combobox.d.ts +194 -0
- package/dist/components/combobox.js +176 -0
- package/dist/components/command-palette.d.ts +57 -0
- package/dist/components/command-palette.js +83 -0
- package/dist/components/context-menu.d.ts +24 -0
- package/dist/components/context-menu.js +30 -0
- package/dist/components/copy-button.d.ts +9 -0
- package/dist/components/copy-button.js +20 -0
- package/dist/components/copy-command.d.ts +18 -0
- package/dist/components/copy-command.js +19 -0
- package/dist/components/data-table.d.ts +240 -0
- package/dist/components/data-table.js +63 -0
- package/dist/components/dialog.d.ts +80 -0
- package/dist/components/dialog.js +85 -0
- package/dist/components/diff-lines.d.ts +20 -0
- package/dist/components/diff-lines.js +10 -0
- package/dist/components/diff-view.d.ts +128 -0
- package/dist/components/diff-view.js +675 -0
- package/dist/components/disclosure.d.ts +33 -0
- package/dist/components/disclosure.js +51 -0
- package/dist/components/drawn-check.d.ts +8 -0
- package/dist/components/drawn-check.js +16 -0
- package/dist/components/edge-resizer.d.ts +26 -0
- package/dist/components/edge-resizer.js +53 -0
- package/dist/components/empty-state.d.ts +112 -0
- package/dist/components/empty-state.js +56 -0
- package/dist/components/expand-toggle.d.ts +19 -0
- package/dist/components/expand-toggle.js +10 -0
- package/dist/components/field.d.ts +48 -0
- package/dist/components/field.js +60 -0
- package/dist/components/filter-chip.d.ts +47 -0
- package/dist/components/filter-chip.js +33 -0
- package/dist/components/group.d.ts +10 -0
- package/dist/components/group.js +9 -0
- package/dist/components/icon-text.d.ts +11 -0
- package/dist/components/icon-text.js +10 -0
- package/dist/components/image-sources.d.ts +57 -0
- package/dist/components/image-sources.js +58 -0
- package/dist/components/image-thumbnail.d.ts +34 -0
- package/dist/components/image-thumbnail.js +58 -0
- package/dist/components/image-viewer.d.ts +60 -0
- package/dist/components/image-viewer.js +411 -0
- package/dist/components/inline-edit.d.ts +22 -0
- package/dist/components/inline-edit.js +51 -0
- package/dist/components/input.d.ts +155 -0
- package/dist/components/input.js +109 -0
- package/dist/components/item-label.d.ts +11 -0
- package/dist/components/item-label.js +5 -0
- package/dist/components/item-row.d.ts +23 -0
- package/dist/components/item-row.js +19 -0
- package/dist/components/kbd.d.ts +55 -0
- package/dist/components/kbd.js +86 -0
- package/dist/components/link-policy.d.ts +24 -0
- package/dist/components/link-policy.js +32 -0
- package/dist/components/linked-text.d.ts +15 -0
- package/dist/components/linked-text.js +48 -0
- package/dist/components/loading-region.d.ts +18 -0
- package/dist/components/loading-region.js +17 -0
- package/dist/components/markdown.d.ts +34 -0
- package/dist/components/markdown.js +307 -0
- package/dist/components/menu.d.ts +132 -0
- package/dist/components/menu.js +102 -0
- package/dist/components/message-bubble.d.ts +57 -0
- package/dist/components/message-bubble.js +28 -0
- package/dist/components/meta-line.d.ts +28 -0
- package/dist/components/meta-line.js +36 -0
- package/dist/components/money-input.d.ts +23 -0
- package/dist/components/money-input.js +34 -0
- package/dist/components/native-select.d.ts +120 -0
- package/dist/components/native-select.js +85 -0
- package/dist/components/page-trail.d.ts +30 -0
- package/dist/components/page-trail.js +43 -0
- package/dist/components/page.d.ts +80 -0
- package/dist/components/page.js +60 -0
- package/dist/components/path-text.d.ts +8 -0
- package/dist/components/path-text.js +11 -0
- package/dist/components/percent-input.d.ts +20 -0
- package/dist/components/percent-input.js +32 -0
- package/dist/components/popover.d.ts +52 -0
- package/dist/components/popover.js +36 -0
- package/dist/components/progress.d.ts +389 -0
- package/dist/components/progress.js +142 -0
- package/dist/components/radio.d.ts +65 -0
- package/dist/components/radio.js +54 -0
- package/dist/components/remote-image-gate.d.ts +34 -0
- package/dist/components/remote-image-gate.js +60 -0
- package/dist/components/resizable.d.ts +23 -0
- package/dist/components/resizable.js +30 -0
- package/dist/components/rolling-number.d.ts +13 -0
- package/dist/components/rolling-number.js +69 -0
- package/dist/components/scroll-area.d.ts +17 -0
- package/dist/components/scroll-area.js +25 -0
- package/dist/components/section-label.d.ts +10 -0
- package/dist/components/section-label.js +21 -0
- package/dist/components/section-nav.d.ts +22 -0
- package/dist/components/section-nav.js +80 -0
- package/dist/components/segmented-control.d.ts +125 -0
- package/dist/components/segmented-control.js +101 -0
- package/dist/components/select.d.ts +120 -0
- package/dist/components/select.js +89 -0
- package/dist/components/separator.d.ts +10 -0
- package/dist/components/separator.js +12 -0
- package/dist/components/sheet.d.ts +101 -0
- package/dist/components/sheet.js +129 -0
- package/dist/components/shortcut-recorder.d.ts +31 -0
- package/dist/components/shortcut-recorder.js +67 -0
- package/dist/components/skeleton.d.ts +31 -0
- package/dist/components/skeleton.js +33 -0
- package/dist/components/slider.d.ts +138 -0
- package/dist/components/slider.js +98 -0
- package/dist/components/spinner.d.ts +37 -0
- package/dist/components/spinner.js +16 -0
- package/dist/components/switch.d.ts +59 -0
- package/dist/components/switch.js +56 -0
- package/dist/components/tab-strip.d.ts +29 -0
- package/dist/components/tab-strip.js +42 -0
- package/dist/components/tabs.d.ts +13 -0
- package/dist/components/tabs.js +68 -0
- package/dist/components/text-link.d.ts +54 -0
- package/dist/components/text-link.js +38 -0
- package/dist/components/textarea.d.ts +75 -0
- package/dist/components/textarea.js +90 -0
- package/dist/components/toast.d.ts +74 -0
- package/dist/components/toast.js +128 -0
- package/dist/components/tooltip.d.ts +50 -0
- package/dist/components/tooltip.js +46 -0
- package/dist/components/truncated-text.d.ts +15 -0
- package/dist/components/truncated-text.js +19 -0
- package/dist/i18n/defaults.d.ts +7 -0
- package/dist/i18n/defaults.js +7 -0
- package/dist/i18n/format.d.ts +84 -0
- package/dist/i18n/format.js +169 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/locales/ru-formatters.d.ts +3 -0
- package/dist/i18n/locales/ru-formatters.js +22 -0
- package/dist/i18n/locales/ru.d.ts +5 -0
- package/dist/i18n/locales/ru.js +185 -0
- package/dist/i18n/plural.d.ts +20 -0
- package/dist/i18n/plural.js +26 -0
- package/dist/i18n/provider.d.ts +36 -0
- package/dist/i18n/provider.js +39 -0
- package/dist/i18n/rich.d.ts +14 -0
- package/dist/i18n/rich.js +44 -0
- package/dist/i18n/ru-plural.d.ts +3 -0
- package/dist/i18n/ru-plural.js +4 -0
- package/dist/i18n/strings/alert-dialog.d.ts +5 -0
- package/dist/i18n/strings/alert-dialog.js +5 -0
- package/dist/i18n/strings/block-stack.d.ts +17 -0
- package/dist/i18n/strings/block-stack.js +11 -0
- package/dist/i18n/strings/code-block.d.ts +18 -0
- package/dist/i18n/strings/code-block.js +13 -0
- package/dist/i18n/strings/combobox.d.ts +12 -0
- package/dist/i18n/strings/combobox.js +10 -0
- package/dist/i18n/strings/command-palette.d.ts +17 -0
- package/dist/i18n/strings/command-palette.js +11 -0
- package/dist/i18n/strings/diff-view.d.ts +92 -0
- package/dist/i18n/strings/diff-view.js +68 -0
- package/dist/i18n/strings/image-viewer.d.ts +43 -0
- package/dist/i18n/strings/image-viewer.js +32 -0
- package/dist/i18n/strings/index.d.ts +314 -0
- package/dist/i18n/strings/index.js +43 -0
- package/dist/i18n/strings/kbd.d.ts +42 -0
- package/dist/i18n/strings/kbd.js +39 -0
- package/dist/i18n/strings/markdown.d.ts +23 -0
- package/dist/i18n/strings/markdown.js +18 -0
- package/dist/i18n/strings/money.d.ts +7 -0
- package/dist/i18n/strings/money.js +5 -0
- package/dist/i18n/strings/primitives.d.ts +25 -0
- package/dist/i18n/strings/primitives.js +18 -0
- package/dist/i18n/strings/progress.d.ts +4 -0
- package/dist/i18n/strings/progress.js +4 -0
- package/dist/i18n/strings/shortcut-recorder.d.ts +15 -0
- package/dist/i18n/strings/shortcut-recorder.js +11 -0
- package/dist/i18n/strings/time.d.ts +41 -0
- package/dist/i18n/strings/time.js +20 -0
- package/dist/i18n/strings/toast.d.ts +6 -0
- package/dist/i18n/strings/toast.js +6 -0
- package/dist/i18n/time-zones.d.ts +20 -0
- package/dist/i18n/time-zones.js +64 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +88 -0
- package/dist/lib/ansi.d.ts +55 -0
- package/dist/lib/ansi.js +438 -0
- package/dist/lib/clock.d.ts +32 -0
- package/dist/lib/clock.js +106 -0
- package/dist/lib/cn.d.ts +12 -0
- package/dist/lib/cn.js +28 -0
- package/dist/lib/control-props.d.ts +21 -0
- package/dist/lib/control-props.js +14 -0
- package/dist/lib/direction.d.ts +13 -0
- package/dist/lib/direction.js +25 -0
- package/dist/lib/image-size.d.ts +12 -0
- package/dist/lib/image-size.js +66 -0
- package/dist/lib/keycaps.d.ts +21 -0
- package/dist/lib/keycaps.js +62 -0
- package/dist/lib/keyed.d.ts +9 -0
- package/dist/lib/keyed.js +14 -0
- package/dist/lib/keys.d.ts +49 -0
- package/dist/lib/keys.js +159 -0
- package/dist/lib/kit-motion.d.ts +9 -0
- package/dist/lib/kit-motion.js +12 -0
- package/dist/lib/link-check-dialog.d.ts +11 -0
- package/dist/lib/link-check-dialog.js +18 -0
- package/dist/lib/motion-features.d.ts +1 -0
- package/dist/lib/motion-features.js +4 -0
- package/dist/lib/motion-provider.d.ts +5 -0
- package/dist/lib/motion-provider.js +8 -0
- package/dist/lib/motion.d.ts +39 -0
- package/dist/lib/motion.js +49 -0
- package/dist/lib/number-format.d.ts +114 -0
- package/dist/lib/number-format.js +313 -0
- package/dist/lib/patch.d.ts +72 -0
- package/dist/lib/patch.js +344 -0
- package/dist/lib/path.d.ts +34 -0
- package/dist/lib/path.js +85 -0
- package/dist/lib/search.d.ts +10 -0
- package/dist/lib/search.js +20 -0
- package/dist/lib/shiki-themes.d.ts +7 -0
- package/dist/lib/shiki-themes.js +115 -0
- package/dist/lib/shiki.d.ts +101 -0
- package/dist/lib/shiki.js +353 -0
- package/dist/lib/shortcut-context.d.ts +23 -0
- package/dist/lib/shortcut-context.js +29 -0
- package/dist/lib/text.d.ts +56 -0
- package/dist/lib/text.js +149 -0
- package/dist/lib/theme-scope.d.ts +18 -0
- package/dist/lib/theme-scope.js +24 -0
- package/dist/lib/theme.d.ts +40 -0
- package/dist/lib/theme.js +96 -0
- package/dist/lib/tree-keys.d.ts +72 -0
- package/dist/lib/tree-keys.js +126 -0
- package/dist/lib/url.d.ts +17 -0
- package/dist/lib/url.js +38 -0
- package/dist/lib/use-copy-to-clipboard.d.ts +13 -0
- package/dist/lib/use-copy-to-clipboard.js +40 -0
- package/dist/lib/use-delayed-loading.d.ts +11 -0
- package/dist/lib/use-delayed-loading.js +31 -0
- package/dist/lib/use-file-picker.d.ts +26 -0
- package/dist/lib/use-file-picker.js +43 -0
- package/dist/lib/use-hydrated.d.ts +6 -0
- package/dist/lib/use-hydrated.js +11 -0
- package/dist/lib/use-latest.d.ts +12 -0
- package/dist/lib/use-latest.js +22 -0
- package/dist/lib/use-resolved-theme.d.ts +12 -0
- package/dist/lib/use-resolved-theme.js +25 -0
- package/dist/styles/base.css +393 -0
- package/dist/styles/code.css +35 -0
- package/dist/styles/fonts.css +12 -0
- package/dist/styles/index.css +13 -0
- package/dist/styles/tokens.gen.css +494 -0
- package/package.json +208 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type ImageProblem } from "./image-thumbnail.js";
|
|
3
|
+
/** A local image as the surface could get it: a URL `<img>` loads (and its size), or why there is none. */
|
|
4
|
+
export type LocalImage = {
|
|
5
|
+
readonly kind: "ready";
|
|
6
|
+
readonly src: string;
|
|
7
|
+
readonly name?: string | undefined;
|
|
8
|
+
readonly width?: number | undefined;
|
|
9
|
+
readonly height?: number | undefined;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: "problem";
|
|
12
|
+
readonly problem: ImageProblem;
|
|
13
|
+
};
|
|
14
|
+
/** Where a message's images come from on the surface it renders on. */
|
|
15
|
+
export interface ImageSources {
|
|
16
|
+
/**
|
|
17
|
+
* The surface's hook for a local image a message names (a path, relative to the conversation's
|
|
18
|
+
* folder or absolute, or a `file:` URL) as of `scope` (the row that names it, the call that wrote it), read
|
|
19
|
+
* under the surface's access rules; undefined while it reads. Without one, a path doesn't load.
|
|
20
|
+
*/
|
|
21
|
+
readonly useLocal?: ((path: string, scope: string) => LocalImage | undefined) | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* A remote image (another site's): `ask` loads it when the reader asks, where the page may load
|
|
24
|
+
* it; `open` names its host and opens it in the browser, where the page may not (a strict CSP).
|
|
25
|
+
*/
|
|
26
|
+
readonly remote: "ask" | "open";
|
|
27
|
+
}
|
|
28
|
+
export declare function ImageSourcesProvider({ value, children, }: {
|
|
29
|
+
readonly value: ImageSources;
|
|
30
|
+
readonly children: ReactNode;
|
|
31
|
+
}): import("react").JSX.Element;
|
|
32
|
+
export declare function useImageSources(): ImageSources;
|
|
33
|
+
/** The local images below are read as of `scope` (the row that names them): a later row reads afresh. */
|
|
34
|
+
export declare function LocalImageScope({ scope, children, }: {
|
|
35
|
+
readonly scope: string;
|
|
36
|
+
readonly children: ReactNode;
|
|
37
|
+
}): import("react").JSX.Element;
|
|
38
|
+
/** The scope local images below are read as of (`LocalImageScope`). */
|
|
39
|
+
export declare function useLocalImageScope(): string;
|
|
40
|
+
/** Whether a path names a picture (by its extension), so a surface may read it as one. */
|
|
41
|
+
export declare function isImagePath(path: string): boolean;
|
|
42
|
+
/** A local image's place in its gallery: its path, as of the scope it was read in. */
|
|
43
|
+
export declare function localImageKey(path: string, scope: string): string;
|
|
44
|
+
/** A message's image source as a local path: a `file:` URL's path, or the source decoded. */
|
|
45
|
+
export declare function localImagePath(src: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* A local image (a path a message names, a file a tool wrote), read by the surface under its
|
|
48
|
+
* access rules (`ImageSources.useLocal`) as of `scope`, as an image in place. Render it only where
|
|
49
|
+
* the surface has `useLocal`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function LocalImageThumbnail({ path, alt, scope, useLocal, size, }: {
|
|
52
|
+
readonly path: string;
|
|
53
|
+
readonly alt: string;
|
|
54
|
+
readonly scope: string;
|
|
55
|
+
readonly useLocal: NonNullable<ImageSources["useLocal"]>;
|
|
56
|
+
readonly size?: "md" | "sm" | undefined;
|
|
57
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, use } from "react";
|
|
4
|
+
import { ImageThumbnail } from "./image-thumbnail.js";
|
|
5
|
+
const ImageSourcesContext = createContext({ remote: "ask" });
|
|
6
|
+
const ScopeContext = createContext("");
|
|
7
|
+
export function ImageSourcesProvider({ value, children, }) {
|
|
8
|
+
return _jsx(ImageSourcesContext, { value: value, children: children });
|
|
9
|
+
}
|
|
10
|
+
export function useImageSources() {
|
|
11
|
+
return use(ImageSourcesContext);
|
|
12
|
+
}
|
|
13
|
+
/** The local images below are read as of `scope` (the row that names them): a later row reads afresh. */
|
|
14
|
+
export function LocalImageScope({ scope, children, }) {
|
|
15
|
+
return _jsx(ScopeContext, { value: scope, children: children });
|
|
16
|
+
}
|
|
17
|
+
/** The scope local images below are read as of (`LocalImageScope`). */
|
|
18
|
+
export function useLocalImageScope() {
|
|
19
|
+
return use(ScopeContext);
|
|
20
|
+
}
|
|
21
|
+
/** Paths a message may show as a picture: anything else a message names is never read for one. */
|
|
22
|
+
const IMAGE_FILE = /\.(?:png|jpe?g|gif|webp|svg)$/iu;
|
|
23
|
+
/** Whether a path names a picture (by its extension), so a surface may read it as one. */
|
|
24
|
+
export function isImagePath(path) {
|
|
25
|
+
return IMAGE_FILE.test(path);
|
|
26
|
+
}
|
|
27
|
+
/** A local image's place in its gallery: its path, as of the scope it was read in. */
|
|
28
|
+
export function localImageKey(path, scope) {
|
|
29
|
+
return `local:${scope}:${path}`;
|
|
30
|
+
}
|
|
31
|
+
/** A message's image source as a local path: a `file:` URL's path, or the source decoded. */
|
|
32
|
+
export function localImagePath(src) {
|
|
33
|
+
if (/^file:/iu.test(src)) {
|
|
34
|
+
try {
|
|
35
|
+
return decodeURIComponent(new URL(src).pathname);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return src;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return decodeURI(src);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return src;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A local image (a path a message names, a file a tool wrote), read by the surface under its
|
|
50
|
+
* access rules (`ImageSources.useLocal`) as of `scope`, as an image in place. Render it only where
|
|
51
|
+
* the surface has `useLocal`.
|
|
52
|
+
*/
|
|
53
|
+
export function LocalImageThumbnail({ path, alt, scope, useLocal, size, }) {
|
|
54
|
+
const image = useLocal(path, scope);
|
|
55
|
+
const name = path.split(/[\\/]/u).at(-1);
|
|
56
|
+
const ready = image?.kind === "ready" ? image : undefined;
|
|
57
|
+
return (_jsx(ImageThumbnail, { src: ready?.src, alt: alt === "" ? (name ?? "") : alt, name: ready?.name ?? name, width: ready?.width, height: ready?.height, problem: image?.kind === "problem" ? image.problem : undefined, galleryKey: localImageKey(path, scope), size: size }));
|
|
58
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** Why an image shows no picture: it couldn't be had, or it's over what the list shows. */
|
|
3
|
+
export type ImageProblem = {
|
|
4
|
+
readonly kind: "failed";
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "too-large";
|
|
7
|
+
readonly bytes?: number | undefined;
|
|
8
|
+
};
|
|
9
|
+
export interface ImageThumbnailProps {
|
|
10
|
+
/** What `<img>` loads (inline data, a blob or an object URL); undefined while the host fetches it. */
|
|
11
|
+
readonly src: string | undefined;
|
|
12
|
+
/** What it shows: the text alternative, and what the viewer is titled. */
|
|
13
|
+
readonly alt: string;
|
|
14
|
+
/** The file name Save suggests. */
|
|
15
|
+
readonly name?: string | undefined;
|
|
16
|
+
/** A quiet line under the picture (where it came from). */
|
|
17
|
+
readonly caption?: ReactNode;
|
|
18
|
+
readonly problem?: ImageProblem | undefined;
|
|
19
|
+
/** `md`: one image in its place (up to 320 px tall); `sm`: one of a row of them (up to 160 px). */
|
|
20
|
+
readonly size?: "md" | "sm" | undefined;
|
|
21
|
+
/** Its own size (`imageSize`, from its header), when known: the place is held at its shape. */
|
|
22
|
+
readonly width?: number | undefined;
|
|
23
|
+
readonly height?: number | undefined;
|
|
24
|
+
/** Which image it is in its gallery (the host's list of a turn's images); default its `src`. */
|
|
25
|
+
readonly galleryKey?: string | undefined;
|
|
26
|
+
readonly className?: string | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* An image in a message list, as an image: proportional, never larger than its own size or the
|
|
30
|
+
* column, at most 320 px tall (160 in a row of them), so a tall screenshot never takes over the
|
|
31
|
+
* page. A click opens it full size (the gallery's viewer). While it loads, a placeholder holds the
|
|
32
|
+
* place; one that can't load, or is too large to show, says so in its place.
|
|
33
|
+
*/
|
|
34
|
+
export declare function ImageThumbnail({ src, alt, name, caption, problem, size, width, height, galleryKey, className, }: ImageThumbnailProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ImageIcon, ImageOff } from "lucide-react";
|
|
4
|
+
import { useMemo, useRef, useState } from "react";
|
|
5
|
+
import { useFormatters, useUiStrings } from "../i18n/provider.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import { useViewerImage } from "./image-viewer.js";
|
|
8
|
+
/** How tall a picture of each size may be, in px (`max-h-80`, `max-h-40`). */
|
|
9
|
+
const MAX_HEIGHT = { md: 320, sm: 160 };
|
|
10
|
+
const frame = "rounded-lg border border-border-subtle bg-bg-subtle";
|
|
11
|
+
/**
|
|
12
|
+
* Its shape known, the place is held at the size it will show at (its own, or smaller to fit the
|
|
13
|
+
* height and the column): nothing moves when it arrives.
|
|
14
|
+
*/
|
|
15
|
+
function heldShape(width, height, size) {
|
|
16
|
+
if (width === undefined || height === undefined || width <= 0 || height <= 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
return {
|
|
19
|
+
aspectRatio: `${width} / ${height}`,
|
|
20
|
+
// A length, not a percentage: the place sizes itself, and `max-w-full` narrows it in a narrow column.
|
|
21
|
+
width: `${Math.min(width, (width * MAX_HEIGHT[size]) / height)}px`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** An image that shows no picture, in its place: it couldn't load, or it's too large to show here. */
|
|
25
|
+
function ProblemCard({ label, problem }) {
|
|
26
|
+
const t = useUiStrings().imageViewer;
|
|
27
|
+
const formatters = useFormatters();
|
|
28
|
+
const failed = problem.kind === "failed";
|
|
29
|
+
const size = problem.kind === "too-large" && problem.bytes !== undefined ? formatters.bytes(problem.bytes) : undefined;
|
|
30
|
+
const Icon = failed ? ImageOff : ImageIcon;
|
|
31
|
+
return (_jsxs("span", { "data-slot": "transcript-image", "data-state": problem.kind, className: cn(frame, "inline-flex max-w-full items-center gap-2 px-3 py-2 text-sm"), children: [_jsx(Icon, { "aria-hidden": true, className: "size-4 shrink-0 text-fg-subtle" }), _jsx("span", { className: "min-w-0 truncate text-fg-muted", children: label }), _jsx("span", { className: "shrink-0 text-xs text-fg-subtle", children: failed ? t.failed : t.tooLarge({ size }) })] }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* An image in a message list, as an image: proportional, never larger than its own size or the
|
|
35
|
+
* column, at most 320 px tall (160 in a row of them), so a tall screenshot never takes over the
|
|
36
|
+
* page. A click opens it full size (the gallery's viewer). While it loads, a placeholder holds the
|
|
37
|
+
* place; one that can't load, or is too large to show, says so in its place.
|
|
38
|
+
*/
|
|
39
|
+
export function ImageThumbnail({ src, alt, name, caption, problem, size = "md", width, height, galleryKey, className, }) {
|
|
40
|
+
const t = useUiStrings().imageViewer;
|
|
41
|
+
const ref = useRef(null);
|
|
42
|
+
const [loaded, setLoaded] = useState(false);
|
|
43
|
+
const [broken, setBroken] = useState(false);
|
|
44
|
+
const label = alt === "" ? t.untitled : alt;
|
|
45
|
+
const image = useMemo(() => src === undefined || problem !== undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: { key: galleryKey ?? src, src, alt: label, name }, [src, problem, label, name, galleryKey]);
|
|
48
|
+
const { open, viewer } = useViewerImage(broken ? undefined : image, ref);
|
|
49
|
+
const shown = broken ? FAILED : problem;
|
|
50
|
+
const box = size === "md" ? "h-40 w-60" : "h-24 w-32";
|
|
51
|
+
const shape = heldShape(width, height, size);
|
|
52
|
+
const body = shown !== undefined ? (_jsx(ProblemCard, { label: label, problem: shown })) : src === undefined ? (_jsx("span", { "data-slot": "transcript-image", "data-state": "loading", role: "img", "aria-label": t.loading, "aria-busy": true, style: shape, className: cn(frame, "block max-w-full animate-pulse-soft", shape === undefined && box) })) : (_jsx("button", { ref: ref, type: "button", "data-slot": "transcript-image", "data-state": loaded ? "loaded" : "loading", "aria-label": t.open({ alt: label }), "aria-haspopup": "dialog", onClick: open, style: shape, className: cn(frame, "block max-w-full cursor-zoom-in overflow-hidden outline-none focus-visible:ring-focus", !loaded &&
|
|
53
|
+
cn("animate-pulse-soft", shape === undefined && (size === "md" ? "min-h-40 min-w-60" : "min-h-24 min-w-32"))), children: _jsx("img", { src: src, alt: "", loading: "lazy", decoding: "async", draggable: false, onLoad: () => setLoaded(true), onError: () => setBroken(true), className: cn("block object-contain", shape === undefined
|
|
54
|
+
? cn("h-auto w-auto max-w-full", size === "md" ? "max-h-80" : "max-h-40")
|
|
55
|
+
: "size-full") }) }));
|
|
56
|
+
return (_jsxs("span", { className: cn("inline-flex max-w-full flex-col items-start gap-1 align-top", className), children: [body, caption !== undefined && _jsx("span", { className: "max-w-full truncate text-xs text-fg-subtle", children: caption }), viewer] }));
|
|
57
|
+
}
|
|
58
|
+
const FAILED = { kind: "failed" };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ReactNode, type RefObject } from "react";
|
|
2
|
+
/** One image the viewer shows. */
|
|
3
|
+
export interface ViewerImage {
|
|
4
|
+
/** Which image it is in its gallery: what a thumbnail opens the viewer on. */
|
|
5
|
+
readonly key: string;
|
|
6
|
+
/** What `<img>` loads (inline data, a blob, an object URL); or `load` gets it when it's shown. */
|
|
7
|
+
readonly src?: string | undefined;
|
|
8
|
+
/** Gets the image when the viewer comes to it (one whose thumbnail isn't on screen). */
|
|
9
|
+
readonly load?: (() => Promise<string | undefined>) | undefined;
|
|
10
|
+
/** What it shows: the viewer's title and the image's text alternative. */
|
|
11
|
+
readonly alt: string;
|
|
12
|
+
/** The file name Save suggests; without one, a name from its type. */
|
|
13
|
+
readonly name?: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface ImageViewerProps {
|
|
16
|
+
/** The images it steps through (a turn's), in reading order. */
|
|
17
|
+
readonly images: readonly ViewerImage[];
|
|
18
|
+
/** The one on show. */
|
|
19
|
+
readonly index: number;
|
|
20
|
+
readonly onIndexChange: (index: number) => void;
|
|
21
|
+
readonly onClose: () => void;
|
|
22
|
+
/** Where the focus goes back to on close (the thumbnail that opened it, while it's still there). */
|
|
23
|
+
readonly returnFocus?: (() => HTMLElement | null) | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* An image full size over the page: the whole image in the window at first, then zoom (the wheel, a
|
|
27
|
+
* pinch, + and −, a double click) and drag to look around; ← and → step through the other images in
|
|
28
|
+
* reading order (so in a right-to-left language → goes back), Esc closes. Copy puts it on the
|
|
29
|
+
* clipboard as a PNG and says so on the button; Save downloads it. Zoom and panning move the picture
|
|
30
|
+
* by `transform` only.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ImageViewer({ images, index, onIndexChange, onClose, returnFocus }: ImageViewerProps): import("react").JSX.Element | null;
|
|
33
|
+
export interface ImageGalleryProps {
|
|
34
|
+
/**
|
|
35
|
+
* A group's images (a turn's) in reading order, from the host's own data, so ← → reach the ones
|
|
36
|
+
* scrolled out of view too; without it (or for an image it doesn't list), the ones on screen.
|
|
37
|
+
*/
|
|
38
|
+
readonly imagesOf?: ((group: string) => readonly ViewerImage[]) | undefined;
|
|
39
|
+
readonly children: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Where images open together: a thumbnail's click opens the viewer on it, and ← → step through the
|
|
43
|
+
* other images of its group (`ImageGroup`, a turn), in reading order. Without one, an image opens on
|
|
44
|
+
* its own.
|
|
45
|
+
*/
|
|
46
|
+
export declare function ImageGallery({ imagesOf, children }: ImageGalleryProps): import("react").JSX.Element;
|
|
47
|
+
/** The images below open together in the gallery (a turn's). */
|
|
48
|
+
export declare function ImageGroup({ id, children }: {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly children: ReactNode;
|
|
51
|
+
}): import("react").JSX.Element;
|
|
52
|
+
/**
|
|
53
|
+
* An image that opens in the viewer: in the gallery around it with its group, else on its own.
|
|
54
|
+
* `element` is what puts it in reading order and gets the focus back. Returns the open action and,
|
|
55
|
+
* without a gallery, the viewer to render.
|
|
56
|
+
*/
|
|
57
|
+
export declare function useViewerImage(image: ViewerImage | undefined, element: RefObject<HTMLElement | null>): {
|
|
58
|
+
readonly open: () => void;
|
|
59
|
+
readonly viewer: ReactNode;
|
|
60
|
+
};
|