@fungi.computer/caps 0.8.0 → 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 +19 -0
- package/dist/blocks/app-bar.d.ts +4 -2
- package/dist/blocks/app-bar.js +3 -3
- package/dist/blocks/setup-notice.d.ts +32 -0
- package/dist/blocks/setup-notice.js +23 -0
- 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 +5 -2
- package/dist/components/toaster.d.ts +5 -1
- package/dist/components/toaster.js +16 -2
- package/dist/styles.css +245 -0
- package/docs/api/README.md +2 -0
- package/docs/api/blocks/app-bar.md +9 -1
- package/docs/api/blocks/setup-notice.md +82 -0
- package/docs/api/components/confirm-action.md +8 -6
- package/docs/api/components/failure-line.md +50 -0
- package/docs/api/components/keycap.md +3 -2
- package/docs/api/components/toaster.md +2 -0
- package/package.json +13 -4
- package/src/first-run.css +100 -0
- package/src/styles.css +160 -0
package/README.md
CHANGED
|
@@ -832,6 +832,17 @@ your App owns the words.
|
|
|
832
832
|
- `Keycap touch="hide"` drops a keyboard legend on touch-first devices;
|
|
833
833
|
`KeycapButton` hides its `shortcut` legend that way.
|
|
834
834
|
|
|
835
|
+
- `SetupNotice` (`blocks/setup-notice`) names a missing setup with its one fix:
|
|
836
|
+
`need` (title), `cause` (one sentence), required `fix`, optional `secondary`
|
|
837
|
+
and `available` (what still works), and `variant` `panel` (an EmptyState
|
|
838
|
+
panel), `banner` or `inline` (a warning Alert). While `fix.pending` the button
|
|
839
|
+
shows the Loader and reads "Waiting for {waitingFor}…". Render it only while
|
|
840
|
+
the setup is missing; the host unmounts it when the fact turns ready. One
|
|
841
|
+
notice per window, and the notice is the call to action.
|
|
842
|
+
- `FailureLine` (`components/failure-line`) is the item failure: an error dot,
|
|
843
|
+
one plain line, labelled `Retry` / `Dismiss`, and "Failed {n} times" once when
|
|
844
|
+
`count` is above 1. `density` is `row` or `tile`. Never use the `bang` glyph.
|
|
845
|
+
|
|
835
846
|
Which key and which status light:
|
|
836
847
|
|
|
837
848
|
| Need | Use |
|
|
@@ -843,6 +854,14 @@ Which key and which status light:
|
|
|
843
854
|
|
|
844
855
|
`bang` is for unread beads only. Never put it in a button or chip; use a dot.
|
|
845
856
|
|
|
857
|
+
### Toaster CSS placement
|
|
858
|
+
|
|
859
|
+
`Toaster` wraps Sonner, which injects its own unlayered stylesheet at runtime.
|
|
860
|
+
Anything inside a cascade layer loses to it, so the toast rules (Stipe token
|
|
861
|
+
variables, font, close button, icons) live unlayered near the top of
|
|
862
|
+
`src/styles.css`, with raised specificity. Keep new toast rules there, not in
|
|
863
|
+
`first-run.css` or a layer.
|
|
864
|
+
|
|
846
865
|
### Hairlines
|
|
847
866
|
|
|
848
867
|
`--stipe-hairline` (about 22%) outlines controls: Keycap, Button and chips keep
|
package/dist/blocks/app-bar.d.ts
CHANGED
|
@@ -15,8 +15,10 @@ export type AppBarProps = {
|
|
|
15
15
|
/** Portal container for the App dial; defaults to the document body. */
|
|
16
16
|
container?: HTMLElement | null;
|
|
17
17
|
windows: readonly AppBarItem[];
|
|
18
|
-
/**
|
|
18
|
+
/** Apps available to launch from the speed dial. */
|
|
19
19
|
launchers: readonly AppBarItem[];
|
|
20
|
+
/** The Apps section heading and its launcher name; the host owns the wording. */
|
|
21
|
+
appsLabel?: string;
|
|
20
22
|
/** Recent Sessions supplied by the shell's session owner. */
|
|
21
23
|
sessions?: readonly AppBarItem[];
|
|
22
24
|
/** Keep long launcher names readable instead of truncating them. */
|
|
@@ -40,4 +42,4 @@ export type AppBarProps = {
|
|
|
40
42
|
};
|
|
41
43
|
};
|
|
42
44
|
/** An anchored launcher between persistent running apps and a spring-out app tray. */
|
|
43
|
-
export declare function AppBar({ container, windows, launchers, sessions, launcherLabels, alwaysExpanded, isOpen, onOpenChange, emptyLabel, launcher, primaryAction, }: AppBarProps): import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
export declare function AppBar({ container, windows, launchers, sessions, appsLabel, launcherLabels, alwaysExpanded, isOpen, onOpenChange, emptyLabel, launcher, primaryAction, }: AppBarProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/blocks/app-bar.js
CHANGED
|
@@ -42,7 +42,7 @@ function AppBarItemView({ item, available, expanded, onSelect, onClose, session
|
|
|
42
42
|
}, children: [_jsx("span", { ref: icon, className: "caps-app-bar-icon", "aria-hidden": "true", children: item.icon ?? _jsx(PanelsTopLeft, {}) }), available ? (_jsx("span", { className: "caps-app-bar-item-label", children: item.label })) : null] }), item.onClose ? (_jsx("button", { type: "button", className: "caps-app-bar-close", "aria-label": `Close ${item.label}`, tabIndex: tabIndex, onClick: onClose, children: _jsx(X, { "aria-hidden": "true" }) })) : null] }));
|
|
43
43
|
}
|
|
44
44
|
/** An anchored launcher between persistent running apps and a spring-out app tray. */
|
|
45
|
-
export function AppBar({ container, windows, launchers, sessions = [], launcherLabels = "truncate", alwaysExpanded = false, isOpen, onOpenChange, emptyLabel, launcher, primaryAction, }) {
|
|
45
|
+
export function AppBar({ container, windows, launchers, sessions = [], appsLabel = "Apps", launcherLabels = "truncate", alwaysExpanded = false, isOpen, onOpenChange, emptyLabel, launcher, primaryAction, }) {
|
|
46
46
|
const popoverRoot = useOverlayRoot("popover");
|
|
47
47
|
const portalContainer = container ?? popoverRoot;
|
|
48
48
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
@@ -232,9 +232,9 @@ export function AppBar({ container, windows, launchers, sessions = [], launcherL
|
|
|
232
232
|
else
|
|
233
233
|
setOpen(next);
|
|
234
234
|
};
|
|
235
|
-
return (_jsx(BasePopover.Root, { open: expanded, onOpenChange: changeOpen, modal: false, children: _jsxs("nav", { ref: root, style: appBarStyle, className: "caps-app-bar", "aria-label":
|
|
235
|
+
return (_jsx(BasePopover.Root, { open: expanded, onOpenChange: changeOpen, modal: false, children: _jsxs("nav", { ref: root, style: appBarStyle, className: "caps-app-bar", "aria-label": `${appsLabel} and Sessions launcher`, "data-open": expanded ? "true" : "false", children: [_jsx("div", { ref: runningRef, className: "caps-app-bar-region caps-app-bar-items", role: "group", "aria-label": "Running apps", "aria-hidden": "false", children: windows.map((item) => renderItem(item, false)) }), _jsx("div", { className: "caps-app-bar-launcher", children: _jsx("button", { ref: launcherRef, type: "button", className: "caps-app-bar-launcher-button", "aria-label": launcher.label, title: launcher.label, "aria-expanded": expanded, "aria-controls": primaryAction ? actionId : availableId, onClick: () => {
|
|
236
236
|
setOpen(!open);
|
|
237
|
-
}, children: _jsx("span", { className: "caps-app-bar-mascot", "aria-hidden": "true", children: launcher.icon ?? _jsx(PanelsTopLeft, {}) }) }) }), portalContainer ? (_jsx(BasePopover.Portal, { container: portalContainer, keepMounted: true, children: _jsx(BasePopover.Positioner, { anchor: root, side: "bottom", align: "start", sideOffset: 4, collisionPadding: 8, collisionBoundary: portalContainer === document.body ? undefined : portalContainer, className: "caps-app-bar-positioner", children: _jsxs(BasePopover.Popup, { initialFocus: false, finalFocus: false, style: appBarStyle, ref: availableRef, id: availableId, className: "caps-app-bar-region caps-app-bar-launchers", role: "group", "aria-label": "Speed dial", "aria-hidden": !expanded, children: [launchers.length > 0 ? (_jsxs("div", { className: "caps-app-bar-section", role: "group", "aria-label":
|
|
237
|
+
}, children: _jsx("span", { className: "caps-app-bar-mascot", "aria-hidden": "true", children: launcher.icon ?? _jsx(PanelsTopLeft, {}) }) }) }), portalContainer ? (_jsx(BasePopover.Portal, { container: portalContainer, keepMounted: true, children: _jsx(BasePopover.Positioner, { anchor: root, side: "bottom", align: "start", sideOffset: 4, collisionPadding: 8, collisionBoundary: portalContainer === document.body ? undefined : portalContainer, className: "caps-app-bar-positioner", children: _jsxs(BasePopover.Popup, { initialFocus: false, finalFocus: false, style: appBarStyle, ref: availableRef, id: availableId, className: "caps-app-bar-region caps-app-bar-launchers", role: "group", "aria-label": "Speed dial", "aria-hidden": !expanded, children: [launchers.length > 0 ? (_jsxs("div", { className: "caps-app-bar-section", role: "group", "aria-label": appsLabel, children: [_jsx("span", { className: "caps-app-bar-section-label", children: appsLabel }), _jsx("div", { className: "caps-app-bar-app-list", children: launchers.map((item) => renderItem(item, true)) })] })) : null, sessions.length > 0 ? (_jsxs("div", { className: "caps-app-bar-section caps-app-bar-sessions", role: "group", "aria-label": "Recent Sessions", children: [_jsx("span", { className: "caps-app-bar-section-label", children: "Recent Sessions" }), _jsx("div", { className: "caps-app-bar-session-list", children: sessions.map((item) => renderItem(item, true, true)) })] })) : null, launchers.length === 0 &&
|
|
238
238
|
sessions.length === 0 &&
|
|
239
239
|
emptyLabel ? (_jsx("span", { className: "caps-app-bar-empty", role: "status", children: emptyLabel })) : null] }) }) })) : null, primaryAction && portalContainer ? (_jsx(BasePopover.Root, { open: open, onOpenChange: changeOpen, modal: false, children: _jsx(BasePopover.Portal, { container: portalContainer, keepMounted: true, children: _jsx(BasePopover.Positioner, { anchor: launcherRef, side: "bottom", align: "end", sideOffset: 8, collisionPadding: 8, collisionBoundary: portalContainer === document.body
|
|
240
240
|
? undefined
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
/** A step the person takes to finish setup. */
|
|
3
|
+
export type SetupNoticeAction = Readonly<{
|
|
4
|
+
label: string;
|
|
5
|
+
/** Called when the step is chosen. */
|
|
6
|
+
onSelect: () => void;
|
|
7
|
+
}>;
|
|
8
|
+
/** The required fix; `pending` means it opened Settings and is waiting. */
|
|
9
|
+
export type SetupNoticeFix = SetupNoticeAction & Readonly<{
|
|
10
|
+
pending?: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
/** Props accepted by SetupNotice. */
|
|
13
|
+
export type SetupNoticeProps = Omit<HTMLAttributes<HTMLElement>, "title" | "children"> & {
|
|
14
|
+
/** The title, such as "Connect a model to start chatting". */
|
|
15
|
+
need: ReactNode;
|
|
16
|
+
/** One sentence naming the real cause. */
|
|
17
|
+
cause: ReactNode;
|
|
18
|
+
/** The one primary fix; never a dead CTA. */
|
|
19
|
+
fix: SetupNoticeFix;
|
|
20
|
+
secondary?: SetupNoticeAction;
|
|
21
|
+
/** What still works meanwhile, such as "Uploads work now". */
|
|
22
|
+
available?: ReactNode;
|
|
23
|
+
/** Names what is awaited while pending, after "Waiting for". */
|
|
24
|
+
waitingFor?: string;
|
|
25
|
+
/** `panel` replaces an empty state; `banner` and `inline` sit above or in content. */
|
|
26
|
+
variant: "panel" | "inline" | "banner";
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Missing setup, named with its fix. Render it only while the setup fact is
|
|
30
|
+
* missing; when the fact turns ready the host unmounts it (and may toast).
|
|
31
|
+
*/
|
|
32
|
+
export declare function SetupNotice({ available, cause, className, fix, need, secondary, variant, waitingFor, ...props }: SetupNoticeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Alert } from "../components/alert.js";
|
|
3
|
+
import { Button } from "../components/button.js";
|
|
4
|
+
import { Status } from "../components/status.js";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { EmptyState } from "./empty-state.js";
|
|
7
|
+
/**
|
|
8
|
+
* Missing setup, named with its fix. Render it only while the setup fact is
|
|
9
|
+
* missing; when the fact turns ready the host unmounts it (and may toast).
|
|
10
|
+
*/
|
|
11
|
+
export function SetupNotice({ available, cause, className, fix, need, secondary, variant, waitingFor = "setup", ...props }) {
|
|
12
|
+
const pending = fix.pending === true;
|
|
13
|
+
const label = pending ? `Waiting for ${waitingFor}…` : fix.label;
|
|
14
|
+
const detail = available ? (_jsxs(_Fragment, { children: [cause, " ", _jsx("span", { className: "caps-setup-notice-available", children: available })] })) : (cause);
|
|
15
|
+
if (variant === "panel") {
|
|
16
|
+
return (_jsx(EmptyState, { ...props, actions: secondary ? (_jsx(Button, { size: "sm", variant: "ghost", onClick: secondary.onSelect, children: secondary.label })) : undefined, className: cn("caps-setup-notice", className), "data-variant": "panel", description: detail, primaryAction: {
|
|
17
|
+
label,
|
|
18
|
+
onSelect: fix.onSelect,
|
|
19
|
+
pending,
|
|
20
|
+
}, title: need, variant: "panel", visual: _jsx(Status, { className: "caps-setup-notice-dot", glyph: "dot", tone: "warning" }) }));
|
|
21
|
+
}
|
|
22
|
+
return (_jsx("div", { className: "caps-setup-notice-frame", children: _jsx(Alert, { ...props, action: _jsxs("span", { className: "caps-setup-notice-actions", children: [secondary ? (_jsx(Button, { size: "sm", variant: "ghost", onClick: secondary.onSelect, children: secondary.label })) : null, _jsx(Button, { loading: pending, size: "sm", variant: "outline", onClick: fix.onSelect, children: label })] }), className: cn("caps-setup-notice", className), "data-variant": variant, description: detail, role: "status", title: _jsxs("span", { className: "caps-setup-notice-title", children: [_jsx(Status, { glyph: "dot", tone: "warning" }), need] }), tone: "warning" }) }));
|
|
23
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ButtonSize, type ButtonVariant } from "./button.js";
|
|
1
2
|
/** A deliberate hold, or an exact phrase before a destructive action. */
|
|
2
3
|
export type ConfirmActionProps = {
|
|
3
4
|
/** The action's trigger label and dialog title. */
|
|
@@ -8,6 +9,10 @@ export type ConfirmActionProps = {
|
|
|
8
9
|
onConfirm: () => Promise<void>;
|
|
9
10
|
/** Caller-owned, safe-to-display failure copy; rejection details are never displayed. */
|
|
10
11
|
error?: string;
|
|
12
|
+
/** The trigger key's look; destructive by default, quiet where a toolbar needs it. */
|
|
13
|
+
variant?: ButtonVariant;
|
|
14
|
+
/** The trigger key's size. */
|
|
15
|
+
size?: ButtonSize;
|
|
11
16
|
} & ({
|
|
12
17
|
mode: "hold";
|
|
13
18
|
phrase?: never;
|
|
@@ -20,4 +25,4 @@ export type ConfirmActionProps = {
|
|
|
20
25
|
* confirmations available for retry. Type mode closes on success and restores
|
|
21
26
|
* focus through Dialog. A pending action cannot be dismissed or cancelled.
|
|
22
27
|
*/
|
|
23
|
-
export declare function ConfirmAction({ label, confirmLabel, onConfirm, error, mode, phrase, }: ConfirmActionProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function ConfirmAction({ label, confirmLabel, onConfirm, error, variant, size, mode, phrase, }: ConfirmActionProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -10,7 +10,7 @@ import { Input } from "./input.js";
|
|
|
10
10
|
* confirmations available for retry. Type mode closes on success and restores
|
|
11
11
|
* focus through Dialog. A pending action cannot be dismissed or cancelled.
|
|
12
12
|
*/
|
|
13
|
-
export function ConfirmAction({ label, confirmLabel, onConfirm, error, mode, phrase, }) {
|
|
13
|
+
export function ConfirmAction({ label, confirmLabel, onConfirm, error, variant = "destructive", size, mode, phrase, }) {
|
|
14
14
|
const [open, setOpen] = useState(false);
|
|
15
15
|
const [typed, setTyped] = useState("");
|
|
16
16
|
const [state, setState] = useState("idle");
|
|
@@ -45,7 +45,7 @@ export function ConfirmAction({ label, confirmLabel, onConfirm, error, mode, phr
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
if (mode === "hold") {
|
|
48
|
-
return (_jsxs("div", { className: "grid gap-sm", children: [_jsx(HoldButton, { variant:
|
|
48
|
+
return (_jsxs("div", { className: "grid gap-sm", children: [_jsx(HoldButton, { variant: variant, ...(size === undefined ? {} : { size }), loading: busy, result: result, holdHint: confirmLabel, onConfirm: () => void confirm(), children: label }), _jsx(FieldError, { children: message })] }));
|
|
49
49
|
}
|
|
50
50
|
return (_jsxs(Dialog, { open: open, onOpenChange: (next, details) => {
|
|
51
51
|
if (pending.current) {
|
|
@@ -55,7 +55,7 @@ export function ConfirmAction({ label, confirmLabel, onConfirm, error, mode, phr
|
|
|
55
55
|
setOpen(next);
|
|
56
56
|
setTyped("");
|
|
57
57
|
setState("idle");
|
|
58
|
-
}, children: [_jsx(DialogTrigger, { render: _jsx(Button, { variant:
|
|
58
|
+
}, children: [_jsx(DialogTrigger, { render: _jsx(Button, { variant: variant, ...(size === undefined ? {} : { size }) }), children: label }), _jsx(DialogContent, { initialFocus: input, children: _jsxs(Form, { busy: busy, onSubmit: (event) => {
|
|
59
59
|
event.preventDefault();
|
|
60
60
|
void confirm();
|
|
61
61
|
}, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: label }), _jsx(DialogDescription, { children: "Type the phrase exactly to confirm." })] }), _jsx(Field, { label: `Type ${phrase} to confirm`, children: _jsx(Input, { ref: input, autoComplete: "off", autoCapitalize: "none", spellCheck: false, value: typed, onChange: (event) => {
|
|
@@ -43,7 +43,7 @@ export const DialogPopup = forwardRef(function DialogPopup({ className, finalFoc
|
|
|
43
43
|
const dialog = useDialogContext();
|
|
44
44
|
return (_jsx(BaseDialog.Popup, { ...props, "data-size": size, className: (state) => cn("caps-dialog-popup overflow-y-auto overscroll-contain rounded-box bg-base-100 p-lg text-base-content shadow-xl", size === "fill"
|
|
45
45
|
? "fixed inset-0 h-full w-full max-w-none"
|
|
46
|
-
: "
|
|
46
|
+
: "caps-dialog-popup-default", resolveClassName(className, state)), finalFocus: dialog.interactionOwner === "host" ? false : (finalFocus ?? true), ref: ref }));
|
|
47
47
|
});
|
|
48
48
|
/**
|
|
49
49
|
* The default portal, backdrop and popup composition for a Dialog.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
/** Props accepted by FailureLine. */
|
|
3
|
+
export type FailureLineProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
4
|
+
/** One plain line naming the real cause; never a raw code. */
|
|
5
|
+
message: ReactNode;
|
|
6
|
+
/** Called when Retry is chosen; without it no Retry button shows. */
|
|
7
|
+
onRetry?: () => void;
|
|
8
|
+
/** Called when Dismiss is chosen; without it no Dismiss button shows. */
|
|
9
|
+
onDismiss?: () => void;
|
|
10
|
+
retryLabel?: string;
|
|
11
|
+
dismissLabel?: string;
|
|
12
|
+
/** A more specific accessible name for Retry, such as "Retry prompt". */
|
|
13
|
+
retryAriaLabel?: string;
|
|
14
|
+
/** A more specific accessible name for Dismiss. */
|
|
15
|
+
dismissAriaLabel?: string;
|
|
16
|
+
/** How many times it failed; above 1 the line adds a "Failed" line with the count once. */
|
|
17
|
+
count?: number;
|
|
18
|
+
/** `row` sits in a list or under a control; `tile` fills a media tile. */
|
|
19
|
+
density?: "row" | "tile";
|
|
20
|
+
/** `status` is polite; use `alert` only for a single, urgent failure. */
|
|
21
|
+
role?: "status" | "alert";
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* One item failure: an error dot, one line, and labelled Retry and Dismiss.
|
|
25
|
+
* It never uses the bang glyph; the dot is the whole signal.
|
|
26
|
+
*/
|
|
27
|
+
export declare const FailureLine: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
28
|
+
/** One plain line naming the real cause; never a raw code. */
|
|
29
|
+
message: ReactNode;
|
|
30
|
+
/** Called when Retry is chosen; without it no Retry button shows. */
|
|
31
|
+
onRetry?: () => void;
|
|
32
|
+
/** Called when Dismiss is chosen; without it no Dismiss button shows. */
|
|
33
|
+
onDismiss?: () => void;
|
|
34
|
+
retryLabel?: string;
|
|
35
|
+
dismissLabel?: string;
|
|
36
|
+
/** A more specific accessible name for Retry, such as "Retry prompt". */
|
|
37
|
+
retryAriaLabel?: string;
|
|
38
|
+
/** A more specific accessible name for Dismiss. */
|
|
39
|
+
dismissAriaLabel?: string;
|
|
40
|
+
/** How many times it failed; above 1 the line adds a "Failed" line with the count once. */
|
|
41
|
+
count?: number;
|
|
42
|
+
/** `row` sits in a list or under a control; `tile` fills a media tile. */
|
|
43
|
+
density?: "row" | "tile";
|
|
44
|
+
/** `status` is polite; use `alert` only for a single, urgent failure. */
|
|
45
|
+
role?: "status" | "alert";
|
|
46
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
import { Status } from "./status.js";
|
|
5
|
+
import { cn } from "../lib/cn.js";
|
|
6
|
+
/**
|
|
7
|
+
* One item failure: an error dot, one line, and labelled Retry and Dismiss.
|
|
8
|
+
* It never uses the bang glyph; the dot is the whole signal.
|
|
9
|
+
*/
|
|
10
|
+
export const FailureLine = forwardRef(function FailureLine({ className, count, density = "row", dismissAriaLabel, dismissLabel = "Dismiss", message, onDismiss, onRetry, retryAriaLabel, retryLabel = "Retry", role = "status", ...props }, ref) {
|
|
11
|
+
return (_jsxs("div", { ...props, className: cn("caps-failure-line", className), "data-density": density, ref: ref, role: role, children: [_jsx(Status, { glyph: "dot", tone: "error" }), _jsxs("span", { className: "caps-failure-line-copy", children: [_jsx("span", { children: message }), count !== undefined && count > 1 ? (_jsxs("span", { className: "caps-failure-line-count", children: ["Failed ", count, " times"] })) : null] }), onRetry || onDismiss ? (_jsxs("span", { className: "caps-failure-line-actions", children: [onRetry ? (_jsx(Button, { "aria-label": retryAriaLabel, size: "sm", variant: "outline", onClick: onRetry, children: retryLabel })) : null, onDismiss ? (_jsx(Button, { "aria-label": dismissAriaLabel, size: "sm", variant: "ghost", onClick: onDismiss, children: dismissLabel })) : null] })) : null] }));
|
|
12
|
+
});
|
|
@@ -4,8 +4,11 @@ import { type ButtonHTMLAttributes, type HTMLAttributes, type ReactNode } from "
|
|
|
4
4
|
* hovered or focused; the rest follow the theme.
|
|
5
5
|
*/
|
|
6
6
|
export type KeycapTone = "neutral" | "primary" | "accent" | "inverse" | "destructive";
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Keycap sizes: `hint` is a muted inline shortcut caption (caption height, no
|
|
9
|
+
* plastic), then a legend chip, a dock key and a big face key.
|
|
10
|
+
*/
|
|
11
|
+
export type KeycapSize = "hint" | "sm" | "md" | "lg";
|
|
9
12
|
/** Props accepted by Keycap. */
|
|
10
13
|
export type KeycapProps = HTMLAttributes<HTMLElement> & {
|
|
11
14
|
tone?: KeycapTone;
|
|
@@ -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
|
}
|
package/dist/styles.css
CHANGED
|
@@ -5795,6 +5795,10 @@
|
|
|
5795
5795
|
width: calc(var(--spacing) * 12);
|
|
5796
5796
|
height: calc(var(--spacing) * 12);
|
|
5797
5797
|
}
|
|
5798
|
+
.size-48 {
|
|
5799
|
+
width: calc(var(--spacing) * 48);
|
|
5800
|
+
height: calc(var(--spacing) * 48);
|
|
5801
|
+
}
|
|
5798
5802
|
.size-\[var\(--stipe-icon-size-lg\)\] {
|
|
5799
5803
|
width: var(--stipe-icon-size-lg);
|
|
5800
5804
|
height: var(--stipe-icon-size-lg);
|
|
@@ -8787,6 +8791,101 @@
|
|
|
8787
8791
|
position: static;
|
|
8788
8792
|
flex: none;
|
|
8789
8793
|
}
|
|
8794
|
+
@layer utilities {
|
|
8795
|
+
.caps-setup-notice-frame {
|
|
8796
|
+
container-type: inline-size;
|
|
8797
|
+
}
|
|
8798
|
+
.caps-setup-notice-title {
|
|
8799
|
+
display: flex;
|
|
8800
|
+
align-items: center;
|
|
8801
|
+
gap: var(--spacing-sm);
|
|
8802
|
+
}
|
|
8803
|
+
.caps-setup-notice[data-variant] {
|
|
8804
|
+
border-color: var(--color-warning);
|
|
8805
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8806
|
+
border-color: color-mix(in oklab, var(--color-warning) 45%, transparent);
|
|
8807
|
+
}
|
|
8808
|
+
background: var(--color-warning);
|
|
8809
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8810
|
+
background: color-mix( in oklab, var(--color-warning) 12%, var(--stipe-surface-raised) );
|
|
8811
|
+
}
|
|
8812
|
+
color: var(--color-base-content);
|
|
8813
|
+
}
|
|
8814
|
+
.caps-setup-notice-actions {
|
|
8815
|
+
display: flex;
|
|
8816
|
+
flex-wrap: wrap;
|
|
8817
|
+
align-items: center;
|
|
8818
|
+
justify-content: flex-end;
|
|
8819
|
+
gap: var(--spacing-xs);
|
|
8820
|
+
}
|
|
8821
|
+
.caps-setup-notice-dot {
|
|
8822
|
+
margin-block: var(--spacing-md);
|
|
8823
|
+
scale: 2.5;
|
|
8824
|
+
}
|
|
8825
|
+
.caps-setup-notice-available {
|
|
8826
|
+
color: var(--stipe-fg-muted);
|
|
8827
|
+
}
|
|
8828
|
+
.caps-setup-notice[data-variant="inline"] {
|
|
8829
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
8830
|
+
font-size: var(--text-sm);
|
|
8831
|
+
}
|
|
8832
|
+
.caps-setup-notice[data-variant="banner"] {
|
|
8833
|
+
border-radius: 0;
|
|
8834
|
+
border-inline: 0;
|
|
8835
|
+
}
|
|
8836
|
+
@container (width <= 36rem) {
|
|
8837
|
+
.caps-setup-notice[data-variant] {
|
|
8838
|
+
flex-direction: column;
|
|
8839
|
+
align-items: stretch;
|
|
8840
|
+
}
|
|
8841
|
+
.caps-setup-notice[data-variant] > * {
|
|
8842
|
+
flex: none;
|
|
8843
|
+
width: 100%;
|
|
8844
|
+
}
|
|
8845
|
+
.caps-setup-notice-actions {
|
|
8846
|
+
justify-content: flex-start;
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
.caps-failure-line {
|
|
8850
|
+
display: flex;
|
|
8851
|
+
align-items: flex-start;
|
|
8852
|
+
gap: var(--spacing-sm);
|
|
8853
|
+
min-width: 0;
|
|
8854
|
+
font-size: var(--text-sm);
|
|
8855
|
+
}
|
|
8856
|
+
.caps-failure-line-copy {
|
|
8857
|
+
display: grid;
|
|
8858
|
+
flex: 1;
|
|
8859
|
+
min-width: 0;
|
|
8860
|
+
gap: var(--spacing-2xs);
|
|
8861
|
+
}
|
|
8862
|
+
.caps-failure-line-count {
|
|
8863
|
+
color: var(--stipe-fg-muted);
|
|
8864
|
+
font-size: var(--text-xs);
|
|
8865
|
+
}
|
|
8866
|
+
.caps-failure-line > .caps-status {
|
|
8867
|
+
margin-block-start: var(--spacing-2xs);
|
|
8868
|
+
}
|
|
8869
|
+
.caps-failure-line-actions {
|
|
8870
|
+
display: flex;
|
|
8871
|
+
flex: none;
|
|
8872
|
+
gap: var(--spacing-2xs);
|
|
8873
|
+
}
|
|
8874
|
+
.caps-failure-line[data-density="tile"] {
|
|
8875
|
+
flex-direction: column;
|
|
8876
|
+
align-items: center;
|
|
8877
|
+
justify-content: center;
|
|
8878
|
+
height: 100%;
|
|
8879
|
+
padding: var(--spacing-md);
|
|
8880
|
+
text-align: center;
|
|
8881
|
+
}
|
|
8882
|
+
.caps-failure-line[data-density="tile"] > .caps-status {
|
|
8883
|
+
margin-block-start: 0;
|
|
8884
|
+
}
|
|
8885
|
+
.caps-failure-line[data-density="tile"] .caps-failure-line-copy {
|
|
8886
|
+
flex: none;
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8790
8889
|
:root, [data-theme] {
|
|
8791
8890
|
--caps-primary-text: light-dark(
|
|
8792
8891
|
oklch(from var(--color-primary) clamp(0.2, l, 0.4) c h),
|
|
@@ -8806,6 +8905,130 @@
|
|
|
8806
8905
|
.caps-toaster[data-sonner-toaster] {
|
|
8807
8906
|
z-index: var(--stipe-layer-toast);
|
|
8808
8907
|
}
|
|
8908
|
+
.caps-toaster[data-sonner-toaster][data-sonner-theme] {
|
|
8909
|
+
font-family: inherit;
|
|
8910
|
+
--border-radius: var(--radius-box);
|
|
8911
|
+
--normal-bg: var(--stipe-surface-raised);
|
|
8912
|
+
--normal-bg-hover: var(--color-base-200);
|
|
8913
|
+
--normal-border: var(--stipe-hairline-quiet);
|
|
8914
|
+
--normal-border-hover: var(--stipe-hairline);
|
|
8915
|
+
--normal-text: var(--color-base-content);
|
|
8916
|
+
--success-bg: var(--color-success);
|
|
8917
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8918
|
+
--success-bg: color-mix(
|
|
8919
|
+
in oklab,
|
|
8920
|
+
var(--color-success) 10%,
|
|
8921
|
+
var(--stipe-surface-raised)
|
|
8922
|
+
);
|
|
8923
|
+
}
|
|
8924
|
+
--success-border: var(--color-success);
|
|
8925
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8926
|
+
--success-border: color-mix(in oklab, var(--color-success) 35%, transparent);
|
|
8927
|
+
}
|
|
8928
|
+
--success-text: var(--color-base-content);
|
|
8929
|
+
--info-bg: var(--color-info);
|
|
8930
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8931
|
+
--info-bg: color-mix(
|
|
8932
|
+
in oklab,
|
|
8933
|
+
var(--color-info) 10%,
|
|
8934
|
+
var(--stipe-surface-raised)
|
|
8935
|
+
);
|
|
8936
|
+
}
|
|
8937
|
+
--info-border: var(--color-info);
|
|
8938
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8939
|
+
--info-border: color-mix(in oklab, var(--color-info) 35%, transparent);
|
|
8940
|
+
}
|
|
8941
|
+
--info-text: var(--color-base-content);
|
|
8942
|
+
--warning-bg: var(--color-warning);
|
|
8943
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8944
|
+
--warning-bg: color-mix(
|
|
8945
|
+
in oklab,
|
|
8946
|
+
var(--color-warning) 12%,
|
|
8947
|
+
var(--stipe-surface-raised)
|
|
8948
|
+
);
|
|
8949
|
+
}
|
|
8950
|
+
--warning-border: var(--color-warning);
|
|
8951
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8952
|
+
--warning-border: color-mix(in oklab, var(--color-warning) 40%, transparent);
|
|
8953
|
+
}
|
|
8954
|
+
--warning-text: var(--color-base-content);
|
|
8955
|
+
--error-bg: var(--color-error);
|
|
8956
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8957
|
+
--error-bg: color-mix(
|
|
8958
|
+
in oklab,
|
|
8959
|
+
var(--color-error) 10%,
|
|
8960
|
+
var(--stipe-surface-raised)
|
|
8961
|
+
);
|
|
8962
|
+
}
|
|
8963
|
+
--error-border: var(--color-error);
|
|
8964
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8965
|
+
--error-border: color-mix(in oklab, var(--color-error) 40%, transparent);
|
|
8966
|
+
}
|
|
8967
|
+
--error-text: var(--color-base-content);
|
|
8968
|
+
}
|
|
8969
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled="true"] {
|
|
8970
|
+
font-size: var(--text-sm);
|
|
8971
|
+
line-height: 1.35;
|
|
8972
|
+
box-shadow: 0 0.25rem 0.75rem var(--color-base-content);
|
|
8973
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8974
|
+
box-shadow: 0 0.25rem 0.75rem color-mix( in oklab, var(--color-base-content) calc(var(--depth) * 14%), transparent );
|
|
8975
|
+
}
|
|
8976
|
+
}
|
|
8977
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] {
|
|
8978
|
+
align-items: flex-start;
|
|
8979
|
+
font-family: inherit;
|
|
8980
|
+
}
|
|
8981
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] [data-icon] {
|
|
8982
|
+
align-items: center;
|
|
8983
|
+
height: calc(var(--text-sm) * 1.35);
|
|
8984
|
+
margin: 0;
|
|
8985
|
+
}
|
|
8986
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] [data-description] {
|
|
8987
|
+
color: var(--color-base-content);
|
|
8988
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
8989
|
+
color: color-mix(in oklab, var(--color-base-content) 78%, transparent);
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8992
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] [data-close-button] {
|
|
8993
|
+
position: static;
|
|
8994
|
+
order: 99;
|
|
8995
|
+
align-self: flex-start;
|
|
8996
|
+
margin-inline-start: auto;
|
|
8997
|
+
transform: none;
|
|
8998
|
+
flex: none;
|
|
8999
|
+
}
|
|
9000
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] [data-icon] {
|
|
9001
|
+
color: var(--color-base-content);
|
|
9002
|
+
}
|
|
9003
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled][data-type="success"] [data-icon] {
|
|
9004
|
+
color: var(--color-success);
|
|
9005
|
+
}
|
|
9006
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled][data-type="error"] [data-icon] {
|
|
9007
|
+
color: var(--color-error);
|
|
9008
|
+
}
|
|
9009
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled][data-type="warning"] [data-icon] {
|
|
9010
|
+
color: var(--color-warning);
|
|
9011
|
+
}
|
|
9012
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled][data-type="info"] [data-icon] {
|
|
9013
|
+
color: var(--color-info);
|
|
9014
|
+
}
|
|
9015
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] [data-close-button] {
|
|
9016
|
+
width: 1.25rem;
|
|
9017
|
+
height: 1.25rem;
|
|
9018
|
+
color: var(--color-base-content);
|
|
9019
|
+
background: var(--stipe-surface-raised);
|
|
9020
|
+
border: var(--border) solid var(--stipe-hairline);
|
|
9021
|
+
transition-duration: var(--stipe-motion-fast);
|
|
9022
|
+
}
|
|
9023
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] [data-close-button]:hover {
|
|
9024
|
+
background: var(--color-base-200);
|
|
9025
|
+
border-color: var(--stipe-hairline);
|
|
9026
|
+
}
|
|
9027
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] [data-close-button]:focus-visible {
|
|
9028
|
+
outline: 2px solid var(--caps-focus-ring);
|
|
9029
|
+
outline-offset: 1px;
|
|
9030
|
+
box-shadow: none;
|
|
9031
|
+
}
|
|
8809
9032
|
@layer caps-dropdown {
|
|
8810
9033
|
.caps-dropdown-popup {
|
|
8811
9034
|
max-height: var(--available-height);
|
|
@@ -10201,6 +10424,11 @@
|
|
|
10201
10424
|
.caps-shortcuts dt, .caps-shortcuts dd {
|
|
10202
10425
|
margin: 0;
|
|
10203
10426
|
}
|
|
10427
|
+
.caps-dialog-popup-default {
|
|
10428
|
+
width: min( var(--container-lg), calc( 100% - max(1rem, env(safe-area-inset-left)) - max(1rem, env(safe-area-inset-right)) ) );
|
|
10429
|
+
max-width: none;
|
|
10430
|
+
max-height: calc( 100dvh - max(1rem, env(safe-area-inset-top)) - max(1rem, env(safe-area-inset-bottom)) );
|
|
10431
|
+
}
|
|
10204
10432
|
dialog.caps-surface {
|
|
10205
10433
|
color: inherit;
|
|
10206
10434
|
}
|
|
@@ -16759,6 +16987,23 @@
|
|
|
16759
16987
|
min-width: var(--caps-keycap-label-min-width, auto);
|
|
16760
16988
|
line-height: var(--caps-keycap-label-line-height, inherit);
|
|
16761
16989
|
}
|
|
16990
|
+
.caps-keycap[data-size="hint"] {
|
|
16991
|
+
display: inline-block;
|
|
16992
|
+
min-width: 0;
|
|
16993
|
+
min-height: 0;
|
|
16994
|
+
margin-inline: var(--spacing-2xs);
|
|
16995
|
+
padding: 0 var(--spacing-2xs);
|
|
16996
|
+
border-radius: var(--radius-selector);
|
|
16997
|
+
border-color: var(--stipe-hairline-quiet);
|
|
16998
|
+
background: transparent;
|
|
16999
|
+
box-shadow: none;
|
|
17000
|
+
color: var(--color-base-content);
|
|
17001
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
17002
|
+
color: color-mix(in oklab, var(--color-base-content) 65%, transparent);
|
|
17003
|
+
}
|
|
17004
|
+
font-size: var(--text-xs);
|
|
17005
|
+
line-height: 1.25;
|
|
17006
|
+
}
|
|
16762
17007
|
.caps-keycap-button .caps-keycap {
|
|
16763
17008
|
flex: var(--caps-keycap-shortcut-flex, 0 1 auto);
|
|
16764
17009
|
min-width: var( --caps-keycap-shortcut-size, var(--caps-keycap-legend-min-size, 2rem) );
|
package/docs/api/README.md
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
| [blocks/result-state](blocks/result-state.md) | - |
|
|
31
31
|
| [blocks/section-card](blocks/section-card.md) | - |
|
|
32
32
|
| [blocks/section-heading](blocks/section-heading.md) | - |
|
|
33
|
+
| [blocks/setup-notice](blocks/setup-notice.md) | - |
|
|
33
34
|
| [blocks/spec-sheet](blocks/spec-sheet.md) | - |
|
|
34
35
|
| [blocks/status-tray](blocks/status-tray.md) | - |
|
|
35
36
|
| [blocks/upload-queue](blocks/upload-queue.md) | - |
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
| [components/diff-view](components/diff-view.md) | - |
|
|
54
55
|
| [components/disclosure](components/disclosure.md) | - |
|
|
55
56
|
| [components/dropdown-menu](components/dropdown-menu.md) | - |
|
|
57
|
+
| [components/failure-line](components/failure-line.md) | - |
|
|
56
58
|
| [components/field-guide](components/field-guide.md) | - |
|
|
57
59
|
| [components/field](components/field.md) | - |
|
|
58
60
|
| [components/game-feedback](components/game-feedback.md) | - |
|
|
@@ -100,7 +100,15 @@ windows: readonly AppBarItem[];
|
|
|
100
100
|
launchers: readonly AppBarItem[];
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Apps available to launch from the speed dial.
|
|
104
|
+
|
|
105
|
+
##### appsLabel?
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
optional appsLabel?: string;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The Apps section heading and its launcher name; the host owns the wording.
|
|
104
112
|
|
|
105
113
|
##### sessions?
|
|
106
114
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!-- Generated by Botanical API docs (TypeDoc + typedoc-plugin-markdown). Do not edit. -->
|
|
2
|
+
|
|
3
|
+
[**@fungi.computer/caps**](../README.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
[@fungi.computer/caps](../README.md) / blocks/setup-notice
|
|
8
|
+
|
|
9
|
+
# blocks/setup-notice
|
|
10
|
+
|
|
11
|
+
## Type Aliases
|
|
12
|
+
|
|
13
|
+
### SetupNoticeAction
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
type SetupNoticeAction = Readonly<{
|
|
17
|
+
label: string;
|
|
18
|
+
onSelect: () => void;
|
|
19
|
+
}>;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A step the person takes to finish setup.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### SetupNoticeFix
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
type SetupNoticeFix = SetupNoticeAction &
|
|
30
|
+
Readonly<{
|
|
31
|
+
pending?: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The required fix; `pending` means it opened Settings and is waiting.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### SetupNoticeProps
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
type SetupNoticeProps = Omit<
|
|
43
|
+
HTMLAttributes<HTMLElement>,
|
|
44
|
+
"title" | "children"
|
|
45
|
+
> &
|
|
46
|
+
object;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Props accepted by SetupNotice.
|
|
50
|
+
|
|
51
|
+
#### Type Declaration
|
|
52
|
+
|
|
53
|
+
| Name | Type | Description |
|
|
54
|
+
| ------------- | ----------------------------------------- | ------------------------------------------------------------------------------- |
|
|
55
|
+
| `need` | `ReactNode` | The title, such as "Connect a model to start chatting". |
|
|
56
|
+
| `cause` | `ReactNode` | One sentence naming the real cause. |
|
|
57
|
+
| `fix` | [`SetupNoticeFix`](#setupnoticefix) | The one primary fix; never a dead CTA. |
|
|
58
|
+
| `secondary?` | [`SetupNoticeAction`](#setupnoticeaction) | - |
|
|
59
|
+
| `available?` | `ReactNode` | What still works meanwhile, such as "Uploads work now". |
|
|
60
|
+
| `waitingFor?` | `string` | Names what is awaited while pending, after "Waiting for". |
|
|
61
|
+
| `variant` | `"panel"` \| `"inline"` \| `"banner"` | `panel` replaces an empty state; `banner` and `inline` sit above or in content. |
|
|
62
|
+
|
|
63
|
+
## Functions
|
|
64
|
+
|
|
65
|
+
### SetupNotice()
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
function SetupNotice(__namedParameters): Element;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Missing setup, named with its fix. Render it only while the setup fact is
|
|
72
|
+
missing; when the fact turns ready the host unmounts it (and may toast).
|
|
73
|
+
|
|
74
|
+
#### Parameters
|
|
75
|
+
|
|
76
|
+
| Parameter | Type |
|
|
77
|
+
| ------------------- | --------------------------------------- |
|
|
78
|
+
| `__namedParameters` | [`SetupNoticeProps`](#setupnoticeprops) |
|
|
79
|
+
|
|
80
|
+
#### Returns
|
|
81
|
+
|
|
82
|
+
`Element`
|
|
@@ -28,12 +28,14 @@ A deliberate hold, or an exact phrase before a destructive action.
|
|
|
28
28
|
|
|
29
29
|
#### Type Declaration
|
|
30
30
|
|
|
31
|
-
| Name | Type
|
|
32
|
-
| -------------- |
|
|
33
|
-
| `label` | `string`
|
|
34
|
-
| `confirmLabel` | `string`
|
|
35
|
-
| `onConfirm()` | () => `Promise`\<`void`\>
|
|
36
|
-
| `error?` | `string`
|
|
31
|
+
| Name | Type | Description |
|
|
32
|
+
| -------------- | ------------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
33
|
+
| `label` | `string` | The action's trigger label and dialog title. |
|
|
34
|
+
| `confirmLabel` | `string` | The destructive key's label (and the hold gesture's instruction). |
|
|
35
|
+
| `onConfirm()` | () => `Promise`\<`void`\> | Resolve on success; reject on failure. The component prevents concurrent calls. |
|
|
36
|
+
| `error?` | `string` | Caller-owned, safe-to-display failure copy; rejection details are never displayed. |
|
|
37
|
+
| `variant?` | [`ButtonVariant`](button.md#buttonvariant) | The trigger key's look; destructive by default, quiet where a toolbar needs it. |
|
|
38
|
+
| `size?` | [`ButtonSize`](button.md#buttonsize) | The trigger key's size. |
|
|
37
39
|
|
|
38
40
|
## Functions
|
|
39
41
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- Generated by Botanical API docs (TypeDoc + typedoc-plugin-markdown). Do not edit. -->
|
|
2
|
+
|
|
3
|
+
[**@fungi.computer/caps**](../README.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
[@fungi.computer/caps](../README.md) / components/failure-line
|
|
8
|
+
|
|
9
|
+
# components/failure-line
|
|
10
|
+
|
|
11
|
+
## Type Aliases
|
|
12
|
+
|
|
13
|
+
### FailureLineProps
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
type FailureLineProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> &
|
|
17
|
+
object;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Props accepted by FailureLine.
|
|
21
|
+
|
|
22
|
+
#### Type Declaration
|
|
23
|
+
|
|
24
|
+
| Name | Type | Description |
|
|
25
|
+
| ------------------- | ----------------------- | ------------------------------------------------------------------------------------ |
|
|
26
|
+
| `message` | `ReactNode` | One plain line naming the real cause; never a raw code. |
|
|
27
|
+
| `onRetry()?` | () => `void` | Called when Retry is chosen; without it no Retry button shows. |
|
|
28
|
+
| `onDismiss()?` | () => `void` | Called when Dismiss is chosen; without it no Dismiss button shows. |
|
|
29
|
+
| `retryLabel?` | `string` | - |
|
|
30
|
+
| `dismissLabel?` | `string` | - |
|
|
31
|
+
| `retryAriaLabel?` | `string` | A more specific accessible name for Retry, such as "Retry prompt". |
|
|
32
|
+
| `dismissAriaLabel?` | `string` | A more specific accessible name for Dismiss. |
|
|
33
|
+
| `count?` | `number` | How many times it failed; above 1 the line adds a "Failed" line with the count once. |
|
|
34
|
+
| `density?` | `"row"` \| `"tile"` | `row` sits in a list or under a control; `tile` fills a media tile. |
|
|
35
|
+
| `role?` | `"status"` \| `"alert"` | `status` is polite; use `alert` only for a single, urgent failure. |
|
|
36
|
+
|
|
37
|
+
## Variables
|
|
38
|
+
|
|
39
|
+
### FailureLine
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const FailureLine: ForwardRefExoticComponent<
|
|
43
|
+
Omit<HTMLAttributes<HTMLDivElement>, "children"> &
|
|
44
|
+
object &
|
|
45
|
+
RefAttributes<HTMLDivElement>
|
|
46
|
+
>;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
One item failure: an error dot, one line, and labelled Retry and Dismiss. It
|
|
50
|
+
never uses the bang glyph; the dot is the whole signal.
|
|
@@ -24,10 +24,11 @@ until hovered or focused; the rest follow the theme.
|
|
|
24
24
|
### KeycapSize
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
|
-
type KeycapSize = "sm" | "md" | "lg";
|
|
27
|
+
type KeycapSize = "hint" | "sm" | "md" | "lg";
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Keycap sizes:
|
|
30
|
+
Keycap sizes: `hint` is a muted inline shortcut caption (caption height, no
|
|
31
|
+
plastic), then a legend chip, a dock key and a big face key.
|
|
31
32
|
|
|
32
33
|
---
|
|
33
34
|
|
|
@@ -17,6 +17,8 @@ function Toaster(__namedParameters): ReactPortal | null;
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Sonner owns gestures, stacking, focus and lifetime; Caps owns the surface.
|
|
20
|
+
Colors come from the Stipe tokens in the cascade, so a `data-theme` switch
|
|
21
|
+
restyles open toasts; Sonner's own light and dark palettes are overridden.
|
|
20
22
|
|
|
21
23
|
#### Parameters
|
|
22
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/caps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Product-neutral React primitives and blocks for Fungi interfaces",
|
|
6
6
|
"type": "module",
|
|
@@ -242,6 +242,10 @@
|
|
|
242
242
|
"types": "./dist/components/alert.d.ts",
|
|
243
243
|
"import": "./dist/components/alert.js"
|
|
244
244
|
},
|
|
245
|
+
"./components/failure-line": {
|
|
246
|
+
"types": "./dist/components/failure-line.d.ts",
|
|
247
|
+
"import": "./dist/components/failure-line.js"
|
|
248
|
+
},
|
|
245
249
|
"./components/status": {
|
|
246
250
|
"types": "./dist/components/status.d.ts",
|
|
247
251
|
"import": "./dist/components/status.js"
|
|
@@ -338,6 +342,10 @@
|
|
|
338
342
|
"types": "./dist/blocks/async-state.d.ts",
|
|
339
343
|
"import": "./dist/blocks/async-state.js"
|
|
340
344
|
},
|
|
345
|
+
"./blocks/setup-notice": {
|
|
346
|
+
"types": "./dist/blocks/setup-notice.d.ts",
|
|
347
|
+
"import": "./dist/blocks/setup-notice.js"
|
|
348
|
+
},
|
|
341
349
|
"./blocks/loading-state": {
|
|
342
350
|
"types": "./dist/blocks/loading-state.d.ts",
|
|
343
351
|
"import": "./dist/blocks/loading-state.js"
|
|
@@ -465,7 +473,8 @@
|
|
|
465
473
|
"src/fonts-display.css",
|
|
466
474
|
"src/fonts-mono.css",
|
|
467
475
|
"src/status-tray.css",
|
|
468
|
-
"src/app-status-bar.css"
|
|
476
|
+
"src/app-status-bar.css",
|
|
477
|
+
"src/first-run.css"
|
|
469
478
|
],
|
|
470
479
|
"peerDependencies": {
|
|
471
480
|
"react": "^18.2.0 || ^19.0.0",
|
|
@@ -475,8 +484,8 @@
|
|
|
475
484
|
},
|
|
476
485
|
"devDependencies": {
|
|
477
486
|
"@fungi.computer/maosaico": "0.1.1",
|
|
478
|
-
"@fungi.computer/sprite": "0.2.
|
|
479
|
-
"@fungi.computer/sprite-zdog": "0.3.
|
|
487
|
+
"@fungi.computer/sprite": "0.2.2",
|
|
488
|
+
"@fungi.computer/sprite-zdog": "0.3.2",
|
|
480
489
|
"@ladle/react": "5.1.1",
|
|
481
490
|
"@tailwindcss/cli": "4.2.1",
|
|
482
491
|
"@types/react": "18.3.3",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* First-run kit: SetupNotice (alert variants) and FailureLine. The panel
|
|
3
|
+
* variant is an ordinary EmptyState panel and needs no rules here.
|
|
4
|
+
*/
|
|
5
|
+
@layer utilities {
|
|
6
|
+
.caps-setup-notice-frame {
|
|
7
|
+
container-type: inline-size;
|
|
8
|
+
}
|
|
9
|
+
.caps-setup-notice-title {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: var(--spacing-sm);
|
|
13
|
+
}
|
|
14
|
+
.caps-setup-notice[data-variant] {
|
|
15
|
+
border-color: color-mix(in oklab, var(--color-warning) 45%, transparent);
|
|
16
|
+
background: color-mix(
|
|
17
|
+
in oklab,
|
|
18
|
+
var(--color-warning) 12%,
|
|
19
|
+
var(--stipe-surface-raised)
|
|
20
|
+
);
|
|
21
|
+
color: var(--color-base-content);
|
|
22
|
+
}
|
|
23
|
+
.caps-setup-notice-actions {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-wrap: wrap;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: flex-end;
|
|
28
|
+
gap: var(--spacing-xs);
|
|
29
|
+
}
|
|
30
|
+
/* The panel's art: a warning bead at a size that holds the title. */
|
|
31
|
+
.caps-setup-notice-dot {
|
|
32
|
+
margin-block: var(--spacing-md);
|
|
33
|
+
scale: 2.5;
|
|
34
|
+
}
|
|
35
|
+
.caps-setup-notice-available {
|
|
36
|
+
color: var(--stipe-fg-muted);
|
|
37
|
+
}
|
|
38
|
+
.caps-setup-notice[data-variant="inline"] {
|
|
39
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
40
|
+
font-size: var(--text-sm);
|
|
41
|
+
}
|
|
42
|
+
.caps-setup-notice[data-variant="banner"] {
|
|
43
|
+
border-radius: 0;
|
|
44
|
+
border-inline: 0;
|
|
45
|
+
}
|
|
46
|
+
/* Narrow frame (a phone, or a narrow window): actions go under the copy. */
|
|
47
|
+
@container (width <= 36rem) {
|
|
48
|
+
.caps-setup-notice[data-variant] {
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
align-items: stretch;
|
|
51
|
+
}
|
|
52
|
+
.caps-setup-notice[data-variant] > * {
|
|
53
|
+
flex: none;
|
|
54
|
+
width: 100%;
|
|
55
|
+
}
|
|
56
|
+
.caps-setup-notice-actions {
|
|
57
|
+
justify-content: flex-start;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.caps-failure-line {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: flex-start;
|
|
64
|
+
gap: var(--spacing-sm);
|
|
65
|
+
min-width: 0;
|
|
66
|
+
font-size: var(--text-sm);
|
|
67
|
+
}
|
|
68
|
+
.caps-failure-line-copy {
|
|
69
|
+
display: grid;
|
|
70
|
+
flex: 1;
|
|
71
|
+
min-width: 0;
|
|
72
|
+
gap: var(--spacing-2xs);
|
|
73
|
+
}
|
|
74
|
+
.caps-failure-line-count {
|
|
75
|
+
color: var(--stipe-fg-muted);
|
|
76
|
+
font-size: var(--text-xs);
|
|
77
|
+
}
|
|
78
|
+
.caps-failure-line > .caps-status {
|
|
79
|
+
margin-block-start: var(--spacing-2xs);
|
|
80
|
+
}
|
|
81
|
+
.caps-failure-line-actions {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex: none;
|
|
84
|
+
gap: var(--spacing-2xs);
|
|
85
|
+
}
|
|
86
|
+
.caps-failure-line[data-density="tile"] {
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
height: 100%;
|
|
91
|
+
padding: var(--spacing-md);
|
|
92
|
+
text-align: center;
|
|
93
|
+
}
|
|
94
|
+
.caps-failure-line[data-density="tile"] > .caps-status {
|
|
95
|
+
margin-block-start: 0;
|
|
96
|
+
}
|
|
97
|
+
.caps-failure-line[data-density="tile"] .caps-failure-line-copy {
|
|
98
|
+
flex: none;
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/styles.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
@import "@fungi.computer/stipe/styles.css";
|
|
7
7
|
@import "./status-tray.css";
|
|
8
8
|
@import "./app-status-bar.css";
|
|
9
|
+
@import "./first-run.css";
|
|
9
10
|
|
|
10
11
|
@plugin "@tailwindcss/typography";
|
|
11
12
|
|
|
@@ -42,6 +43,134 @@
|
|
|
42
43
|
z-index: var(--stipe-layer-toast);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/* Unlayered on purpose: Sonner injects an unlayered sheet at runtime, which
|
|
47
|
+
would outrank anything inside a cascade layer. */
|
|
48
|
+
/* Sonner paints from --normal-* and per-type variables; point them at the
|
|
49
|
+
Stipe tokens so latte, mocha and any data-theme switch are followed. */
|
|
50
|
+
.caps-toaster[data-sonner-toaster][data-sonner-theme] {
|
|
51
|
+
font-family: inherit;
|
|
52
|
+
--border-radius: var(--radius-box);
|
|
53
|
+
--normal-bg: var(--stipe-surface-raised);
|
|
54
|
+
--normal-bg-hover: var(--color-base-200);
|
|
55
|
+
--normal-border: var(--stipe-hairline-quiet);
|
|
56
|
+
--normal-border-hover: var(--stipe-hairline);
|
|
57
|
+
--normal-text: var(--color-base-content);
|
|
58
|
+
--success-bg: color-mix(
|
|
59
|
+
in oklab,
|
|
60
|
+
var(--color-success) 10%,
|
|
61
|
+
var(--stipe-surface-raised)
|
|
62
|
+
);
|
|
63
|
+
--success-border: color-mix(in oklab, var(--color-success) 35%, transparent);
|
|
64
|
+
--success-text: var(--color-base-content);
|
|
65
|
+
--info-bg: color-mix(
|
|
66
|
+
in oklab,
|
|
67
|
+
var(--color-info) 10%,
|
|
68
|
+
var(--stipe-surface-raised)
|
|
69
|
+
);
|
|
70
|
+
--info-border: color-mix(in oklab, var(--color-info) 35%, transparent);
|
|
71
|
+
--info-text: var(--color-base-content);
|
|
72
|
+
--warning-bg: color-mix(
|
|
73
|
+
in oklab,
|
|
74
|
+
var(--color-warning) 12%,
|
|
75
|
+
var(--stipe-surface-raised)
|
|
76
|
+
);
|
|
77
|
+
--warning-border: color-mix(in oklab, var(--color-warning) 40%, transparent);
|
|
78
|
+
--warning-text: var(--color-base-content);
|
|
79
|
+
--error-bg: color-mix(
|
|
80
|
+
in oklab,
|
|
81
|
+
var(--color-error) 10%,
|
|
82
|
+
var(--stipe-surface-raised)
|
|
83
|
+
);
|
|
84
|
+
--error-border: color-mix(in oklab, var(--color-error) 40%, transparent);
|
|
85
|
+
--error-text: var(--color-base-content);
|
|
86
|
+
}
|
|
87
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled="true"] {
|
|
88
|
+
font-size: var(--text-sm);
|
|
89
|
+
line-height: 1.35;
|
|
90
|
+
box-shadow: 0 0.25rem 0.75rem
|
|
91
|
+
color-mix(
|
|
92
|
+
in oklab,
|
|
93
|
+
var(--color-base-content) calc(var(--depth) * 14%),
|
|
94
|
+
transparent
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
.caps-toaster[data-sonner-toaster] [data-sonner-toast][data-styled] {
|
|
98
|
+
align-items: flex-start;
|
|
99
|
+
font-family: inherit;
|
|
100
|
+
}
|
|
101
|
+
/* The icon sits on the title line, not centred on a two-line block. */
|
|
102
|
+
.caps-toaster[data-sonner-toaster]
|
|
103
|
+
[data-sonner-toast][data-styled]
|
|
104
|
+
[data-icon] {
|
|
105
|
+
align-items: center;
|
|
106
|
+
height: calc(var(--text-sm) * 1.35);
|
|
107
|
+
margin: 0;
|
|
108
|
+
}
|
|
109
|
+
.caps-toaster[data-sonner-toaster]
|
|
110
|
+
[data-sonner-toast][data-styled]
|
|
111
|
+
[data-description] {
|
|
112
|
+
color: color-mix(in oklab, var(--color-base-content) 78%, transparent);
|
|
113
|
+
}
|
|
114
|
+
/* One close position: inline at the end, top-aligned with the first line. */
|
|
115
|
+
.caps-toaster[data-sonner-toaster]
|
|
116
|
+
[data-sonner-toast][data-styled]
|
|
117
|
+
[data-close-button] {
|
|
118
|
+
position: static;
|
|
119
|
+
order: 99;
|
|
120
|
+
align-self: flex-start;
|
|
121
|
+
margin-inline-start: auto;
|
|
122
|
+
transform: none;
|
|
123
|
+
flex: none;
|
|
124
|
+
}
|
|
125
|
+
.caps-toaster[data-sonner-toaster]
|
|
126
|
+
[data-sonner-toast][data-styled]
|
|
127
|
+
[data-icon] {
|
|
128
|
+
color: var(--color-base-content);
|
|
129
|
+
}
|
|
130
|
+
.caps-toaster[data-sonner-toaster]
|
|
131
|
+
[data-sonner-toast][data-styled][data-type="success"]
|
|
132
|
+
[data-icon] {
|
|
133
|
+
color: var(--color-success);
|
|
134
|
+
}
|
|
135
|
+
.caps-toaster[data-sonner-toaster]
|
|
136
|
+
[data-sonner-toast][data-styled][data-type="error"]
|
|
137
|
+
[data-icon] {
|
|
138
|
+
color: var(--color-error);
|
|
139
|
+
}
|
|
140
|
+
.caps-toaster[data-sonner-toaster]
|
|
141
|
+
[data-sonner-toast][data-styled][data-type="warning"]
|
|
142
|
+
[data-icon] {
|
|
143
|
+
color: var(--color-warning);
|
|
144
|
+
}
|
|
145
|
+
.caps-toaster[data-sonner-toaster]
|
|
146
|
+
[data-sonner-toast][data-styled][data-type="info"]
|
|
147
|
+
[data-icon] {
|
|
148
|
+
color: var(--color-info);
|
|
149
|
+
}
|
|
150
|
+
.caps-toaster[data-sonner-toaster]
|
|
151
|
+
[data-sonner-toast][data-styled]
|
|
152
|
+
[data-close-button] {
|
|
153
|
+
width: 1.25rem;
|
|
154
|
+
height: 1.25rem;
|
|
155
|
+
color: var(--color-base-content);
|
|
156
|
+
background: var(--stipe-surface-raised);
|
|
157
|
+
border: var(--border) solid var(--stipe-hairline);
|
|
158
|
+
transition-duration: var(--stipe-motion-fast);
|
|
159
|
+
}
|
|
160
|
+
.caps-toaster[data-sonner-toaster]
|
|
161
|
+
[data-sonner-toast][data-styled]
|
|
162
|
+
[data-close-button]:hover {
|
|
163
|
+
background: var(--color-base-200);
|
|
164
|
+
border-color: var(--stipe-hairline);
|
|
165
|
+
}
|
|
166
|
+
.caps-toaster[data-sonner-toaster]
|
|
167
|
+
[data-sonner-toast][data-styled]
|
|
168
|
+
[data-close-button]:focus-visible {
|
|
169
|
+
outline: 2px solid var(--caps-focus-ring);
|
|
170
|
+
outline-offset: 1px;
|
|
171
|
+
box-shadow: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
45
174
|
@layer caps-dropdown {
|
|
46
175
|
.caps-dropdown-popup {
|
|
47
176
|
max-height: var(--available-height);
|
|
@@ -1568,6 +1697,22 @@
|
|
|
1568
1697
|
.caps-shortcuts dd {
|
|
1569
1698
|
margin: 0;
|
|
1570
1699
|
}
|
|
1700
|
+
/* A default dialog keeps a 16px gutter on each side (more where the device
|
|
1701
|
+
has a safe-area inset) and never outgrows the screen vertically. */
|
|
1702
|
+
.caps-dialog-popup-default {
|
|
1703
|
+
width: min(
|
|
1704
|
+
var(--container-lg),
|
|
1705
|
+
calc(
|
|
1706
|
+
100% - max(1rem, env(safe-area-inset-left)) -
|
|
1707
|
+
max(1rem, env(safe-area-inset-right))
|
|
1708
|
+
)
|
|
1709
|
+
);
|
|
1710
|
+
max-width: none;
|
|
1711
|
+
max-height: calc(
|
|
1712
|
+
100dvh - max(1rem, env(safe-area-inset-top)) -
|
|
1713
|
+
max(1rem, env(safe-area-inset-bottom))
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1571
1716
|
dialog.caps-surface {
|
|
1572
1717
|
color: inherit;
|
|
1573
1718
|
}
|
|
@@ -9052,6 +9197,21 @@
|
|
|
9052
9197
|
min-width: var(--caps-keycap-label-min-width, auto);
|
|
9053
9198
|
line-height: var(--caps-keycap-label-line-height, inherit);
|
|
9054
9199
|
}
|
|
9200
|
+
/* An inline shortcut caption: reads as text, never as another button. */
|
|
9201
|
+
.caps-keycap[data-size="hint"] {
|
|
9202
|
+
display: inline-block;
|
|
9203
|
+
min-width: 0;
|
|
9204
|
+
min-height: 0;
|
|
9205
|
+
margin-inline: var(--spacing-2xs);
|
|
9206
|
+
padding: 0 var(--spacing-2xs);
|
|
9207
|
+
border-radius: var(--radius-selector);
|
|
9208
|
+
border-color: var(--stipe-hairline-quiet);
|
|
9209
|
+
background: transparent;
|
|
9210
|
+
box-shadow: none;
|
|
9211
|
+
color: color-mix(in oklab, var(--color-base-content) 65%, transparent);
|
|
9212
|
+
font-size: var(--text-xs);
|
|
9213
|
+
line-height: 1.25;
|
|
9214
|
+
}
|
|
9055
9215
|
.caps-keycap-button .caps-keycap {
|
|
9056
9216
|
flex: var(--caps-keycap-shortcut-flex, 0 1 auto);
|
|
9057
9217
|
min-width: var(
|