@fungi.computer/caps 0.7.1 → 0.9.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/README.md +58 -0
- package/dist/blocks/app-bar.d.ts +4 -2
- package/dist/blocks/app-bar.js +3 -3
- package/dist/blocks/empty-state.d.ts +31 -4
- package/dist/blocks/empty-state.js +14 -4
- package/dist/blocks/loading-state.d.ts +41 -0
- package/dist/blocks/loading-state.js +18 -3
- package/dist/blocks/permission-grid.js +1 -1
- package/dist/blocks/result-state.d.ts +18 -0
- package/dist/blocks/result-state.js +7 -2
- package/dist/blocks/setup-notice.d.ts +32 -0
- package/dist/blocks/setup-notice.js +23 -0
- package/dist/components/button.js +2 -2
- package/dist/components/confirm-action.d.ts +6 -1
- package/dist/components/confirm-action.js +3 -3
- package/dist/components/dialog.js +1 -1
- package/dist/components/failure-line.d.ts +46 -0
- package/dist/components/failure-line.js +12 -0
- package/dist/components/keycap.d.ts +15 -2
- package/dist/components/keycap.js +3 -3
- package/dist/components/loader-blobs.d.ts +3 -2
- package/dist/components/loader-blobs.js +5 -3
- package/dist/components/loader.d.ts +8 -2
- package/dist/components/loader.js +4 -2
- package/dist/components/toaster.d.ts +5 -1
- package/dist/components/toaster.js +16 -2
- package/dist/styles.css +485 -28
- package/docs/api/README.md +2 -0
- package/docs/api/blocks/app-bar.md +9 -1
- package/docs/api/blocks/empty-state.md +30 -11
- package/docs/api/blocks/loading-state.md +33 -0
- package/docs/api/blocks/result-state.md +30 -6
- package/docs/api/blocks/setup-notice.md +82 -0
- package/docs/api/components/confirm-action.md +8 -6
- package/docs/api/components/data-list.md +33 -0
- package/docs/api/components/failure-line.md +50 -0
- package/docs/api/components/field.md +9 -8
- package/docs/api/components/icon-checkbox.md +55 -0
- package/docs/api/components/icon-radio.md +39 -5
- package/docs/api/components/keycap.md +10 -7
- package/docs/api/components/loader.md +18 -7
- package/docs/api/components/toaster.md +2 -0
- package/package.json +15 -6
- package/src/first-run.css +100 -0
- package/src/styles.css +394 -21
|
@@ -5,8 +5,8 @@ import { cn } from "../lib/cn.js";
|
|
|
5
5
|
* One molded key legend, rendered as native `<kbd>`: the key a player presses.
|
|
6
6
|
* The key sits on its own travel so pressed and resting keys take the same room.
|
|
7
7
|
*/
|
|
8
|
-
export const Keycap = forwardRef(function Keycap({ tone = "neutral", size = "md", pressed = false, className, ...props }, ref) {
|
|
9
|
-
return (_jsx("kbd", { ...props, ref: ref, className: cn("caps-keycap", className), "data-tone": tone, "data-size": size, "data-pressed": pressed }));
|
|
8
|
+
export const Keycap = forwardRef(function Keycap({ tone = "neutral", size = "md", pressed = false, touch, className, ...props }, ref) {
|
|
9
|
+
return (_jsx("kbd", { ...props, ref: ref, className: cn("caps-keycap", className), "data-tone": tone, "data-size": size, "data-pressed": pressed, "data-touch": touch }));
|
|
10
10
|
});
|
|
11
11
|
/**
|
|
12
12
|
* A chunky action key for game docks and toy panels. The host owns the action
|
|
@@ -14,5 +14,5 @@ export const Keycap = forwardRef(function Keycap({ tone = "neutral", size = "md"
|
|
|
14
14
|
*/
|
|
15
15
|
export const KeycapButton = forwardRef(function KeycapButton({ label, shortcut, icon, readout, tone = "neutral", density = "regular", pressed, expanded, className, type = "button", ...props }, ref) {
|
|
16
16
|
const lamp = pressed ?? expanded;
|
|
17
|
-
return (_jsxs("button", { ...props, ref: ref, type: type, className: cn("caps-keycap-button", className), "data-tone": tone, "data-density": density === "compact" ? density : undefined, "aria-pressed": pressed, "aria-expanded": expanded, "aria-keyshortcuts": props["aria-keyshortcuts"] ?? shortcut, children: [lamp === undefined ? null : (_jsx("span", { className: "caps-keycap-led", "data-lit": lamp, "aria-hidden": "true" })), icon ? (_jsx("span", { className: "caps-keycap-button-icon", "aria-hidden": "true", children: icon })) : null, label === null ? null : (_jsx("span", { className: "caps-keycap-button-label", children: label })), readout, shortcut ? (_jsx(Keycap, { size: "sm", tone: "neutral", "aria-hidden": "true", children: shortcut })) : null] }));
|
|
17
|
+
return (_jsxs("button", { ...props, ref: ref, type: type, className: cn("caps-keycap-button", className), "data-tone": tone, "data-density": density === "compact" ? density : undefined, "aria-pressed": pressed, "aria-expanded": expanded, "aria-keyshortcuts": props["aria-keyshortcuts"] ?? shortcut, children: [lamp === undefined ? null : (_jsx("span", { className: "caps-keycap-led", "data-lit": lamp, "aria-hidden": "true" })), icon ? (_jsx("span", { className: "caps-keycap-button-icon", "aria-hidden": "true", children: icon })) : null, label === null ? null : (_jsx("span", { className: "caps-keycap-button-label", children: label })), readout, shortcut ? (_jsx(Keycap, { size: "sm", tone: "neutral", touch: "hide", "aria-hidden": "true", children: shortcut })) : null] }));
|
|
18
18
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { LoaderVariant } from "./loader.js";
|
|
1
|
+
import type { LoaderPace, LoaderVariant } from "./loader.js";
|
|
2
2
|
/** @internal Animate only the silhouette; lighting never overlaps at joins. */
|
|
3
|
-
export declare function LoaderBlobs({ variant }: {
|
|
3
|
+
export declare function LoaderBlobs({ variant, pace, }: {
|
|
4
4
|
variant: LoaderVariant;
|
|
5
|
+
pace?: LoaderPace;
|
|
5
6
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -83,8 +83,10 @@ const durations = {
|
|
|
83
83
|
wander: 4600,
|
|
84
84
|
bloom: 2800,
|
|
85
85
|
};
|
|
86
|
+
// Idle art breathes at roughly half speed so it never competes with real work.
|
|
87
|
+
const IDLE_SLOWDOWN = 2;
|
|
86
88
|
/** @internal Animate only the silhouette; lighting never overlaps at joins. */
|
|
87
|
-
export function LoaderBlobs({ variant }) {
|
|
89
|
+
export function LoaderBlobs({ variant, pace = "working", }) {
|
|
88
90
|
const id = `caps-loader-${useId().replaceAll(":", "")}`;
|
|
89
91
|
const geometry = useRef(null);
|
|
90
92
|
const visual = useRef(null);
|
|
@@ -103,7 +105,7 @@ export function LoaderBlobs({ variant }) {
|
|
|
103
105
|
const loops = [...geometry.current.querySelectorAll("ellipse")].map((blob, index) => animate(blob, {
|
|
104
106
|
keyframes: Array.from({ length: 48 }, (_, step) => ({
|
|
105
107
|
...pose(variant, index, (step + 1) / 48),
|
|
106
|
-
duration: motionTime(timing, durations[variant]) / 48,
|
|
108
|
+
duration: motionTime(timing, durations[variant] * (pace === "idle" ? IDLE_SLOWDOWN : 1)) / 48,
|
|
107
109
|
})),
|
|
108
110
|
ease: "linear",
|
|
109
111
|
loop: true,
|
|
@@ -121,7 +123,7 @@ export function LoaderBlobs({ variant }) {
|
|
|
121
123
|
for (const loop of loops)
|
|
122
124
|
loop.revert();
|
|
123
125
|
};
|
|
124
|
-
}, [variant, reduced, timing]);
|
|
126
|
+
}, [variant, pace, reduced, timing]);
|
|
125
127
|
return (_jsxs("svg", { ref: visual, "aria-hidden": "true", focusable: "false", viewBox: variant === "line" ? "-20 -10 40 20" : "-20 -20 40 40", children: [_jsxs("defs", { children: [_jsx(GooeyFilterDefinition, { id: id, values: STATUS_GOOEY_FILTER }), _jsx(GooeySurfaceDefinition, { id: `${id}-surface`, transform: variant === "line"
|
|
126
128
|
? "matrix(3.4 0 0 1.4 0 0)"
|
|
127
129
|
: "matrix(3 0 0 3 0 0)" }), _jsx(GooeyShadowDefinition, { id: `${id}-shadow` }), _jsx("mask", { id: `${id}-mask`, maskUnits: "userSpaceOnUse", x: "-20", y: "-20", width: "40", height: "40", style: { maskType: "alpha" }, children: _jsx("g", { ref: geometry, fill: "currentColor", filter: `url(#${id})`, children: Array.from({ length: count }, (_, index) => (_jsx("ellipse", { ...pose(variant, index, 0) }, index))) }) })] }), _jsx("g", { filter: `url(#${id}-shadow)`, children: _jsx("rect", { x: "-20", y: "-20", width: "40", height: "40", fill: `url(#${id}-surface)`, mask: `url(#${id}-mask)` }) })] }));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
/** Fluid motion formations, sharing one material. */
|
|
3
3
|
export type LoaderVariant = "line" | "wander" | "square" | "donut" | "bloom";
|
|
4
|
+
/** How briskly a formation cycles: `working` for a wait, `idle` for art. */
|
|
5
|
+
export type LoaderPace = "working" | "idle";
|
|
4
6
|
/** Props accepted by Loader. */
|
|
5
7
|
export type LoaderProps = {
|
|
6
8
|
decorative?: boolean;
|
|
@@ -8,7 +10,9 @@ export type LoaderProps = {
|
|
|
8
10
|
/** `fill` grows the formation to its container, as a full-tile placeholder. */
|
|
9
11
|
size?: "sm" | "md" | "lg" | "fill";
|
|
10
12
|
variant?: LoaderVariant;
|
|
11
|
-
|
|
13
|
+
/** `idle` slows the cycle for decorative art such as an empty state. */
|
|
14
|
+
pace?: LoaderPace;
|
|
15
|
+
tone?: "neutral" | "accent" | "primary" | "success" | "current";
|
|
12
16
|
} & HTMLAttributes<HTMLSpanElement>;
|
|
13
17
|
/** Connecting gooey beads, with a static silhouette for reduced motion. */
|
|
14
18
|
export declare const Loader: import("react").ForwardRefExoticComponent<{
|
|
@@ -17,5 +21,7 @@ export declare const Loader: import("react").ForwardRefExoticComponent<{
|
|
|
17
21
|
/** `fill` grows the formation to its container, as a full-tile placeholder. */
|
|
18
22
|
size?: "sm" | "md" | "lg" | "fill";
|
|
19
23
|
variant?: LoaderVariant;
|
|
20
|
-
|
|
24
|
+
/** `idle` slows the cycle for decorative art such as an empty state. */
|
|
25
|
+
pace?: LoaderPace;
|
|
26
|
+
tone?: "neutral" | "accent" | "primary" | "success" | "current";
|
|
21
27
|
} & HTMLAttributes<HTMLSpanElement> & import("react").RefAttributes<HTMLSpanElement>>;
|
|
@@ -14,12 +14,14 @@ const toneClasses = {
|
|
|
14
14
|
accent: "text-accent",
|
|
15
15
|
primary: "text-primary",
|
|
16
16
|
success: "text-success",
|
|
17
|
+
/** The surrounding ink, for a loader inside a button. */
|
|
18
|
+
current: "text-current",
|
|
17
19
|
};
|
|
18
20
|
/** Connecting gooey beads, with a static silhouette for reduced motion. */
|
|
19
|
-
export const Loader = forwardRef(function Loader({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className, decorative = false, label = "Loading", size = "md", variant = "line", tone = "neutral", ...props }, ref) {
|
|
21
|
+
export const Loader = forwardRef(function Loader({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className, decorative = false, label = "Loading", size = "md", variant = "line", pace = "working", tone = "neutral", ...props }, ref) {
|
|
20
22
|
const labelId = `${useId()}-label`;
|
|
21
23
|
const ownsLabel = !decorative && ariaLabel === undefined && ariaLabelledby === undefined;
|
|
22
24
|
return (_jsxs("span", { ...props, "aria-label": decorative ? undefined : ariaLabel, "aria-hidden": decorative || undefined, "aria-labelledby": decorative
|
|
23
25
|
? undefined
|
|
24
|
-
: (ariaLabelledby ?? (ariaLabel === undefined ? labelId : undefined)), className: cn("caps-loader", sizeClasses[size], toneClasses[tone], className), "data-variant": variant, role: decorative ? undefined : "status", ref: ref, children: [_jsx(LoaderBlobs, { variant: variant }), ownsLabel ? _jsx(VisuallyHidden, { id: labelId, children: label }) : null] }));
|
|
26
|
+
: (ariaLabelledby ?? (ariaLabel === undefined ? labelId : undefined)), className: cn("caps-loader", sizeClasses[size], toneClasses[tone], className), "data-variant": variant, "data-pace": pace, role: decorative ? undefined : "status", ref: ref, children: [_jsx(LoaderBlobs, { variant: variant, pace: pace }), ownsLabel ? _jsx(VisuallyHidden, { id: labelId, children: label }) : null] }));
|
|
25
27
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type ToasterProps } from "sonner";
|
|
2
2
|
export { toast } from "sonner";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Sonner owns gestures, stacking, focus and lifetime; Caps owns the surface.
|
|
5
|
+
* Colors come from the Stipe tokens in the cascade, so a `data-theme` switch
|
|
6
|
+
* restyles open toasts; Sonner's own light and dark palettes are overridden.
|
|
7
|
+
*/
|
|
4
8
|
export declare function Toaster({ className, ...props }: ToasterProps): import("react").ReactPortal | null;
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Toaster as Sonner } from "sonner";
|
|
3
|
+
import { X } from "lucide-react";
|
|
3
4
|
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { Loader } from "./loader.js";
|
|
6
|
+
import { Status } from "./status.js";
|
|
4
7
|
import { createPortal } from "react-dom";
|
|
5
8
|
import { useOverlayRoot } from "./overlay-root.js";
|
|
6
9
|
export { toast } from "sonner";
|
|
7
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Sonner owns gestures, stacking, focus and lifetime; Caps owns the surface.
|
|
12
|
+
* Colors come from the Stipe tokens in the cascade, so a `data-theme` switch
|
|
13
|
+
* restyles open toasts; Sonner's own light and dark palettes are overridden.
|
|
14
|
+
*/
|
|
8
15
|
export function Toaster({ className, ...props }) {
|
|
9
16
|
const root = useOverlayRoot("toast");
|
|
10
17
|
if (!root)
|
|
11
18
|
return null;
|
|
12
|
-
return createPortal(_jsx(Sonner, { position: "top-right", swipeDirections: ["right", "top"], offset: 16, gap: 8,
|
|
19
|
+
return createPortal(_jsx(Sonner, { position: "top-right", swipeDirections: ["right", "top"], offset: 16, gap: 8, icons: {
|
|
20
|
+
close: _jsx(X, { "aria-hidden": "true", size: 12 }),
|
|
21
|
+
success: _jsx(Status, { glyph: "dot", tone: "success" }),
|
|
22
|
+
error: _jsx(Status, { glyph: "dot", tone: "error" }),
|
|
23
|
+
warning: _jsx(Status, { glyph: "dot", tone: "warning" }),
|
|
24
|
+
info: _jsx(Status, { glyph: "dot", tone: "info" }),
|
|
25
|
+
loading: (_jsx(Loader, { decorative: true, size: "sm", tone: "current", variant: "wander" })),
|
|
26
|
+
}, className: cn("caps-toaster", className), ...props }), root);
|
|
13
27
|
}
|