@fungi.computer/caps 0.3.0 → 0.4.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 +9 -6
- package/dist/blocks/command-menu.d.ts +15 -1
- package/dist/blocks/command-menu.js +8 -2
- package/dist/blocks/composer.d.ts +18 -13
- package/dist/blocks/composer.js +10 -11
- package/dist/blocks/faq-chat.js +4 -4
- package/dist/components/send-action.d.ts +9 -8
- package/dist/components/send-action.js +10 -8
- package/dist/styles.css +180 -52
- package/docs/api/blocks/command-menu.md +14 -5
- package/docs/api/blocks/composer.md +10 -7
- package/docs/api/components/send-action.md +25 -6
- package/package.json +1 -1
- package/src/app-status-bar.css +21 -0
- package/src/styles.css +185 -64
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ export function NoteComposer() {
|
|
|
72
72
|
<Composer
|
|
73
73
|
input={
|
|
74
74
|
<input
|
|
75
|
-
className="input
|
|
75
|
+
className="input w-full"
|
|
76
76
|
value={draft}
|
|
77
77
|
onChange={(event) => setDraft(event.target.value)}
|
|
78
78
|
aria-label="Note"
|
|
@@ -663,9 +663,12 @@ compose, so color belongs in `variant` or an extra daisyUI class.
|
|
|
663
663
|
|
|
664
664
|
### Composer with an async submit
|
|
665
665
|
|
|
666
|
-
`Composer` is a layout block
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
`Composer` is a layout block: one surface with optional `attachments` on top,
|
|
667
|
+
your `input` typed straight onto the host region, and a bottom row of `toolbar`
|
|
668
|
+
controls with the `action` at its end. It draws no box of its own; the host
|
|
669
|
+
region is the field. Every slot is your control, and so are its labels, disabled
|
|
670
|
+
state, keyboard behaviour and effects. A composer's send key is `SendAction`,
|
|
671
|
+
which turns into a stop key while work runs.
|
|
669
672
|
|
|
670
673
|
```tsx
|
|
671
674
|
import { useState, type FormEvent } from "react";
|
|
@@ -697,7 +700,7 @@ export function PromptComposer({
|
|
|
697
700
|
<Composer
|
|
698
701
|
input={
|
|
699
702
|
<input
|
|
700
|
-
className="input
|
|
703
|
+
className="input w-full"
|
|
701
704
|
value={value}
|
|
702
705
|
onChange={(event) => setValue(event.target.value)}
|
|
703
706
|
aria-label="Prompt"
|
|
@@ -720,7 +723,7 @@ export function PromptComposer({
|
|
|
720
723
|
```
|
|
721
724
|
|
|
722
725
|
`submit` is your effect. It can call an API, enqueue work or do something local.
|
|
723
|
-
Caps only
|
|
726
|
+
Caps only lays out the controls.
|
|
724
727
|
|
|
725
728
|
### Window
|
|
726
729
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type HTMLAttributes } from "react";
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
/** Caller-provided command menu item data. */
|
|
3
3
|
export type CommandMenuItem = {
|
|
4
4
|
description?: string;
|
|
@@ -7,6 +7,8 @@ export type CommandMenuItem = {
|
|
|
7
7
|
label: string;
|
|
8
8
|
/** Key hint shown before the label, e.g. `z` in a which-key list. */
|
|
9
9
|
shortcut?: string;
|
|
10
|
+
/** Decorative leading visual, such as a thumbnail, shown before the label. */
|
|
11
|
+
media?: ReactNode;
|
|
10
12
|
};
|
|
11
13
|
/** Props accepted by CommandMenu. */
|
|
12
14
|
export type CommandMenuProps = Omit<HTMLAttributes<HTMLUListElement>, "children" | "onSelect"> & {
|
|
@@ -15,6 +17,12 @@ export type CommandMenuProps = Omit<HTMLAttributes<HTMLUListElement>, "children"
|
|
|
15
17
|
onSelect: (id: string) => void;
|
|
16
18
|
/** Caller-owned selected state; distinct from local keyboard focus. */
|
|
17
19
|
selected?: string;
|
|
20
|
+
/**
|
|
21
|
+
* `end` (default) sets each description at the row's end. `inline` keeps the
|
|
22
|
+
* label at its own width and starts the description right after it,
|
|
23
|
+
* truncated, so long descriptions never push the row's text apart.
|
|
24
|
+
*/
|
|
25
|
+
descriptionPlacement?: "end" | "inline";
|
|
18
26
|
};
|
|
19
27
|
/** A single-focus listbox with local navigation and caller-owned selection. */
|
|
20
28
|
export declare const CommandMenu: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLUListElement>, "children" | "onSelect"> & {
|
|
@@ -23,4 +31,10 @@ export declare const CommandMenu: import("react").ForwardRefExoticComponent<Omit
|
|
|
23
31
|
onSelect: (id: string) => void;
|
|
24
32
|
/** Caller-owned selected state; distinct from local keyboard focus. */
|
|
25
33
|
selected?: string;
|
|
34
|
+
/**
|
|
35
|
+
* `end` (default) sets each description at the row's end. `inline` keeps the
|
|
36
|
+
* label at its own width and starts the description right after it,
|
|
37
|
+
* truncated, so long descriptions never push the row's text apart.
|
|
38
|
+
*/
|
|
39
|
+
descriptionPlacement?: "end" | "inline";
|
|
26
40
|
} & import("react").RefAttributes<HTMLUListElement>>;
|
|
@@ -3,7 +3,7 @@ import { forwardRef, useEffect, useId, useCallback, useImperativeHandle, useRef,
|
|
|
3
3
|
import { useGlide } from "../lib/glide.js";
|
|
4
4
|
import { useSelectableCollection } from "../components/selectable.js";
|
|
5
5
|
/** A single-focus listbox with local navigation and caller-owned selection. */
|
|
6
|
-
export const CommandMenu = forwardRef(function CommandMenu({ className, items, onKeyDown, onSelect, selected, ...props }, ref) {
|
|
6
|
+
export const CommandMenu = forwardRef(function CommandMenu({ className, descriptionPlacement = "end", items, onKeyDown, onSelect, selected, ...props }, ref) {
|
|
7
7
|
const instanceId = useId();
|
|
8
8
|
const list = useRef(null);
|
|
9
9
|
useImperativeHandle(ref, () => list.current);
|
|
@@ -66,5 +66,11 @@ export const CommandMenu = forwardRef(function CommandMenu({ className, items, o
|
|
|
66
66
|
].join(" "), "aria-keyshortcuts": item.shortcut, "data-search-option": item.id, id: optionId(item.id), onClick: () => {
|
|
67
67
|
if (!item.disabled)
|
|
68
68
|
onSelect(item.id);
|
|
69
|
-
}, role: "option", children: [item.shortcut ? (_jsx("kbd", { className: "kbd kbd-sm min-w-6 shrink-0", children: item.shortcut })) : null, _jsx("span", {
|
|
69
|
+
}, role: "option", children: [item.shortcut ? (_jsx("kbd", { className: "kbd kbd-sm min-w-6 shrink-0", children: item.shortcut })) : null, item.media ? (_jsx("span", { "aria-hidden": "true", className: "flex shrink-0", children: item.media })) : null, _jsx("span", { className: descriptionPlacement === "inline"
|
|
70
|
+
? "max-w-[50%] shrink-0 truncate"
|
|
71
|
+
: "flex-1 truncate", children: item.label }), item.description ? (_jsx("span", { className: descriptionPlacement === "inline"
|
|
72
|
+
? "min-w-0 flex-1 truncate text-xs text-base-content"
|
|
73
|
+
: "text-xs text-base-content", title: descriptionPlacement === "inline"
|
|
74
|
+
? item.description
|
|
75
|
+
: undefined, children: item.description })) : null] }) }, item.id))) }));
|
|
70
76
|
});
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
/** Props accepted by Composer. */
|
|
3
3
|
export type ComposerProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
-
/** The caller-owned
|
|
4
|
+
/** The caller-owned field, typed into directly: the surface is the field. */
|
|
5
5
|
input: ReactNode;
|
|
6
|
-
/** The caller-owned
|
|
6
|
+
/** The caller-owned key at the end of the bottom row, such as a SendAction. */
|
|
7
7
|
action?: ReactNode;
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
|
|
8
|
+
/** Caller-owned controls and readouts along the bottom edge, before the action. */
|
|
9
|
+
toolbar?: ReactNode;
|
|
10
|
+
/** Caller-owned items riding with the draft, shown above the text. */
|
|
11
|
+
attachments?: ReactNode;
|
|
12
12
|
};
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* One composer surface: attachments, then the text, then a slim row along the
|
|
15
|
+
* bottom edge holding the toolbar and, at its end, the action. It draws no box
|
|
16
|
+
* of its own: the host region is the field. Layout and slots only; it never
|
|
17
|
+
* assigns action-control semantics.
|
|
18
|
+
*/
|
|
14
19
|
export declare const Composer: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
|
|
15
|
-
/** The caller-owned
|
|
20
|
+
/** The caller-owned field, typed into directly: the surface is the field. */
|
|
16
21
|
input: ReactNode;
|
|
17
|
-
/** The caller-owned
|
|
22
|
+
/** The caller-owned key at the end of the bottom row, such as a SendAction. */
|
|
18
23
|
action?: ReactNode;
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
24
|
+
/** Caller-owned controls and readouts along the bottom edge, before the action. */
|
|
25
|
+
toolbar?: ReactNode;
|
|
26
|
+
/** Caller-owned items riding with the draft, shown above the text. */
|
|
27
|
+
attachments?: ReactNode;
|
|
23
28
|
} & import("react").RefAttributes<HTMLDivElement>>;
|
package/dist/blocks/composer.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.join(" "), children: [leading, input, action] }) }));
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
/**
|
|
5
|
+
* One composer surface: attachments, then the text, then a slim row along the
|
|
6
|
+
* bottom edge holding the toolbar and, at its end, the action. It draws no box
|
|
7
|
+
* of its own: the host region is the field. Layout and slots only; it never
|
|
8
|
+
* assigns action-control semantics.
|
|
9
|
+
*/
|
|
10
|
+
export const Composer = forwardRef(function Composer({ action, attachments, className, input, toolbar, ...rootProps }, ref) {
|
|
11
|
+
return (_jsxs("div", { ...rootProps, ref: ref, className: cn("caps-composer", className), children: [attachments === undefined ? null : (_jsx("div", { className: "caps-composer-attachments", children: attachments })), _jsx("div", { className: "caps-composer-input", children: input }), toolbar === undefined && action === undefined ? null : (_jsxs("div", { className: "caps-composer-bar", children: [_jsx("div", { className: "caps-composer-toolbar", children: toolbar }), action] }))] }));
|
|
13
12
|
});
|
package/dist/blocks/faq-chat.js
CHANGED
|
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef, useEffect, useId, useRef, useState, } from "react";
|
|
3
3
|
import { Button } from "../components/button.js";
|
|
4
4
|
import { Input } from "../components/input.js";
|
|
5
|
+
import { InputGroup } from "../components/input-group.js";
|
|
5
6
|
import { cn } from "../lib/cn.js";
|
|
6
|
-
import { Composer } from "./composer.js";
|
|
7
7
|
function words(text) {
|
|
8
8
|
return text.toLowerCase().match(/[a-z0-9$]+/g) ?? [];
|
|
9
9
|
}
|
|
@@ -71,7 +71,7 @@ export const FaqChat = forwardRef(function FaqChat({ avatar, className, entries,
|
|
|
71
71
|
}
|
|
72
72
|
return (_jsxs("section", { ...rootProps, "aria-labelledby": titleId, className: cn("caps-faq-chat", className), ref: ref, children: [_jsxs("header", { className: "caps-faq-chat-header", children: [avatar, _jsxs("div", { className: "caps-faq-chat-heading", children: [_jsx("p", { className: "caps-faq-chat-title", id: titleId, children: title }), note ? _jsx("p", { className: "caps-faq-chat-note", children: note }) : null] })] }), _jsxs("div", { "aria-live": "polite", className: "caps-faq-chat-log", ref: logRef, role: "log", children: [_jsx("div", { className: "caps-faq-chat-agent", children: greeting }), turns.map((turn) => turn.speaker === "user" ? (_jsx("p", { className: "caps-faq-chat-user", children: turn.text }, turn.key)) : (_jsxs("div", { className: "caps-faq-chat-agent", children: [turn.answer, turn.actions ? (_jsx("div", { className: "caps-faq-chat-actions", children: turn.actions })) : null] }, turn.key)))] }), _jsx("ul", { "aria-label": "Suggested questions", className: "caps-faq-chat-suggestions", children: entries.map((entry) => (_jsx("li", { children: _jsx(Button, { onClick: () => {
|
|
73
73
|
ask(entry.question, entry);
|
|
74
|
-
}, size: "xs", type: "button", variant: "outline", children: entry.question }) }, entry.id))) }), _jsx("form", { className: "caps-faq-chat-form", onSubmit: submit, children:
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
}, size: "xs", type: "button", variant: "outline", children: entry.question }) }, entry.id))) }), _jsx("form", { className: "caps-faq-chat-form", onSubmit: submit, children: _jsxs(InputGroup, { children: [_jsx(Input, { "aria-label": inputLabel, onChange: (event) => {
|
|
75
|
+
setDraft(event.target.value);
|
|
76
|
+
}, placeholder: placeholder, value: draft }), _jsx(Button, { type: "submit", children: submitLabel })] }) })] }));
|
|
77
77
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type KeycapButtonProps } from "./keycap.js";
|
|
2
2
|
/** Where a composer's send action is: nothing to send, ready, or working. */
|
|
3
3
|
export type SendActionState = "empty" | "ready" | "busy";
|
|
4
4
|
/** Props accepted by SendAction. */
|
|
@@ -12,13 +12,14 @@ export type SendActionProps = {
|
|
|
12
12
|
stopLabel?: string;
|
|
13
13
|
/** Glyph size in pixels. */
|
|
14
14
|
iconSize?: number;
|
|
15
|
-
} & Omit<
|
|
15
|
+
} & Omit<KeycapButtonProps, "label" | "icon" | "readout" | "shortcut" | "pressed" | "expanded" | "aria-label">;
|
|
16
16
|
/**
|
|
17
|
-
* A composer's send key. Empty, it rests disabled;
|
|
18
|
-
* wakes with a small pop; sending, its arrow
|
|
19
|
-
* indicator (or, with `onStop`, a stop square)
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* A composer's one send key, on the Caps keycap. Empty, it rests disabled;
|
|
18
|
+
* given something to send it wakes with a small pop; sending, its arrow
|
|
19
|
+
* launches away and a working indicator (or, with `onStop`, a stop square)
|
|
20
|
+
* takes its place in the same key; when the work ends the arrow drops back
|
|
21
|
+
* in. It never grows a second button. All timing comes from the theme's
|
|
22
|
+
* motion tokens, and reduced motion keeps short fades only.
|
|
22
23
|
*/
|
|
23
24
|
export declare const SendAction: import("react").ForwardRefExoticComponent<{
|
|
24
25
|
state: SendActionState;
|
|
@@ -30,4 +31,4 @@ export declare const SendAction: import("react").ForwardRefExoticComponent<{
|
|
|
30
31
|
stopLabel?: string;
|
|
31
32
|
/** Glyph size in pixels. */
|
|
32
33
|
iconSize?: number;
|
|
33
|
-
} & Omit<
|
|
34
|
+
} & Omit<KeycapButtonProps, "aria-label" | "label" | "icon" | "readout" | "expanded" | "pressed" | "shortcut"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -2,8 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { ArrowUp, Square } from "lucide-react";
|
|
3
3
|
import { forwardRef, useImperativeHandle, useLayoutEffect, useRef, } from "react";
|
|
4
4
|
import { canAnimate, motionEasing, readMotion, } from "../lib/motion.js";
|
|
5
|
-
import {
|
|
5
|
+
import { KeycapButton } from "./keycap.js";
|
|
6
6
|
import { Loader } from "./loader.js";
|
|
7
|
+
import { cn } from "../lib/cn.js";
|
|
7
8
|
function lift(face, motion) {
|
|
8
9
|
// The arrow launches upward and away: the prompt has gone.
|
|
9
10
|
return face.animate(motion.reduced
|
|
@@ -57,13 +58,14 @@ function charge(button, motion) {
|
|
|
57
58
|
});
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
60
|
-
* A composer's send key. Empty, it rests disabled;
|
|
61
|
-
* wakes with a small pop; sending, its arrow
|
|
62
|
-
* indicator (or, with `onStop`, a stop square)
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* A composer's one send key, on the Caps keycap. Empty, it rests disabled;
|
|
62
|
+
* given something to send it wakes with a small pop; sending, its arrow
|
|
63
|
+
* launches away and a working indicator (or, with `onStop`, a stop square)
|
|
64
|
+
* takes its place in the same key; when the work ends the arrow drops back
|
|
65
|
+
* in. It never grows a second button. All timing comes from the theme's
|
|
66
|
+
* motion tokens, and reduced motion keeps short fades only.
|
|
65
67
|
*/
|
|
66
|
-
export const SendAction = forwardRef(function SendAction({ state, onStop, stopLabel = "Stop", iconSize = 20, onClick, disabled, "aria-label": label, ...props }, ref) {
|
|
68
|
+
export const SendAction = forwardRef(function SendAction({ state, onStop, stopLabel = "Stop", iconSize = 20, onClick, disabled, className, tone = "primary", "aria-label": label, ...props }, ref) {
|
|
67
69
|
const node = useRef(null);
|
|
68
70
|
useImperativeHandle(ref, () => node.current);
|
|
69
71
|
const previous = useRef(state);
|
|
@@ -92,5 +94,5 @@ export const SendAction = forwardRef(function SendAction({ state, onStop, stopLa
|
|
|
92
94
|
}, [state]);
|
|
93
95
|
const busy = state === "busy";
|
|
94
96
|
const stoppable = busy && onStop !== undefined;
|
|
95
|
-
return (_jsx(
|
|
97
|
+
return (_jsx(KeycapButton, { ...props, ref: node, className: cn("caps-send-action", className), tone: tone, "aria-busy": busy ? true : undefined, "aria-label": stoppable ? stopLabel : label, "data-send-state": state, disabled: disabled === true || state === "empty" || (busy && !stoppable), onClick: stoppable ? onStop : onClick, label: null, icon: _jsxs("span", { className: "caps-send-glyph", "data-state": state, children: [_jsx("span", { "data-glyph": "arrow", children: _jsx(ArrowUp, { "aria-hidden": "true", size: iconSize }) }), _jsx("span", { "data-glyph": "busy", children: onStop === undefined ? (_jsx(Loader, { decorative: true, size: "sm" })) : (_jsx(Square, { "aria-hidden": "true", fill: "currentColor", size: Math.round(iconSize * 0.7) })) })] }) }));
|
|
96
98
|
});
|
package/dist/styles.css
CHANGED
|
@@ -5947,6 +5947,9 @@
|
|
|
5947
5947
|
.max-w-\[48rem\] {
|
|
5948
5948
|
max-width: 48rem;
|
|
5949
5949
|
}
|
|
5950
|
+
.max-w-\[50\%\] {
|
|
5951
|
+
max-width: 50%;
|
|
5952
|
+
}
|
|
5950
5953
|
.max-w-\[60rem\] {
|
|
5951
5954
|
max-width: 60rem;
|
|
5952
5955
|
}
|
|
@@ -6163,9 +6166,6 @@
|
|
|
6163
6166
|
margin-block-end: calc(var(--spacing-sm) * calc(1 - var(--tw-space-y-reverse)));
|
|
6164
6167
|
}
|
|
6165
6168
|
}
|
|
6166
|
-
.self-stretch {
|
|
6167
|
-
align-self: stretch;
|
|
6168
|
-
}
|
|
6169
6169
|
.truncate {
|
|
6170
6170
|
overflow: hidden;
|
|
6171
6171
|
text-overflow: ellipsis;
|
|
@@ -6177,9 +6177,6 @@
|
|
|
6177
6177
|
.overflow-hidden {
|
|
6178
6178
|
overflow: hidden;
|
|
6179
6179
|
}
|
|
6180
|
-
.overflow-visible {
|
|
6181
|
-
overflow: visible;
|
|
6182
|
-
}
|
|
6183
6180
|
.overflow-y-auto {
|
|
6184
6181
|
overflow-y: auto;
|
|
6185
6182
|
}
|
|
@@ -6222,10 +6219,6 @@
|
|
|
6222
6219
|
border-bottom-style: var(--tw-border-style);
|
|
6223
6220
|
border-bottom-width: 1px;
|
|
6224
6221
|
}
|
|
6225
|
-
.border-none {
|
|
6226
|
-
--tw-border-style: none;
|
|
6227
|
-
border-style: none;
|
|
6228
|
-
}
|
|
6229
6222
|
.badge-outline {
|
|
6230
6223
|
@layer daisyui.l1.l2 {
|
|
6231
6224
|
color: var(--badge-color);
|
|
@@ -6367,9 +6360,6 @@
|
|
|
6367
6360
|
.p-2xl {
|
|
6368
6361
|
padding: var(--spacing-2xl);
|
|
6369
6362
|
}
|
|
6370
|
-
.p-2xs {
|
|
6371
|
-
padding: var(--spacing-2xs);
|
|
6372
|
-
}
|
|
6373
6363
|
.p-4 {
|
|
6374
6364
|
padding: calc(var(--spacing) * 4);
|
|
6375
6365
|
}
|
|
@@ -8737,6 +8727,20 @@
|
|
|
8737
8727
|
white-space: nowrap;
|
|
8738
8728
|
}
|
|
8739
8729
|
}
|
|
8730
|
+
.caps-app-status-bar .badge {
|
|
8731
|
+
height: auto;
|
|
8732
|
+
min-width: 0;
|
|
8733
|
+
padding: 0;
|
|
8734
|
+
border: 0;
|
|
8735
|
+
background: none;
|
|
8736
|
+
color: var(--stipe-fg-muted);
|
|
8737
|
+
font-size: 0.625rem;
|
|
8738
|
+
font-weight: 600;
|
|
8739
|
+
line-height: 1;
|
|
8740
|
+
}
|
|
8741
|
+
.caps-app-status-bar .badge-warning {
|
|
8742
|
+
color: var(--color-warning);
|
|
8743
|
+
}
|
|
8740
8744
|
.caps-status-bar-picker-list {
|
|
8741
8745
|
min-width: min(18rem, calc(100vw - 1.5rem));
|
|
8742
8746
|
}
|
|
@@ -10987,17 +10991,61 @@
|
|
|
10987
10991
|
color: var(--color-base-content);
|
|
10988
10992
|
font-size: 0.875rem;
|
|
10989
10993
|
}
|
|
10990
|
-
.caps-composer
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
10994
|
+
.caps-composer {
|
|
10995
|
+
display: flex;
|
|
10996
|
+
min-width: 0;
|
|
10997
|
+
flex-direction: column;
|
|
10998
|
+
gap: var(--spacing-xs);
|
|
10999
|
+
}
|
|
11000
|
+
.caps-composer-attachments {
|
|
11001
|
+
display: flex;
|
|
11002
|
+
flex-wrap: wrap;
|
|
11003
|
+
gap: var(--spacing-xs);
|
|
11004
|
+
}
|
|
11005
|
+
.caps-composer-input {
|
|
11006
|
+
position: relative;
|
|
11007
|
+
min-width: 0;
|
|
11008
|
+
}
|
|
11009
|
+
.caps-composer-input :is(textarea, input), .caps-composer-input :is(textarea, input):focus, .caps-composer-input :is(textarea, input):focus-visible {
|
|
11010
|
+
width: 100%;
|
|
11011
|
+
border: 0;
|
|
11012
|
+
border-radius: 0;
|
|
11013
|
+
background: transparent;
|
|
11014
|
+
box-shadow: none;
|
|
11015
|
+
outline: none;
|
|
11016
|
+
padding-inline: 0;
|
|
11017
|
+
}
|
|
11018
|
+
.caps-composer-bar {
|
|
11019
|
+
display: flex;
|
|
11020
|
+
min-width: 0;
|
|
11021
|
+
align-items: center;
|
|
11022
|
+
gap: var(--spacing-xs);
|
|
11023
|
+
}
|
|
11024
|
+
.caps-composer-toolbar {
|
|
11025
|
+
display: flex;
|
|
11026
|
+
min-width: 0;
|
|
11027
|
+
flex: 1;
|
|
11028
|
+
align-items: center;
|
|
11029
|
+
gap: var(--spacing-xs);
|
|
11030
|
+
}
|
|
11031
|
+
.caps-composer-toolbar > .caps-status-tray {
|
|
11032
|
+
flex: 1;
|
|
11033
|
+
min-width: 0;
|
|
11034
|
+
--caps-status-tray-row-wrap: wrap;
|
|
11035
|
+
--caps-status-tray-chips-display: contents;
|
|
11036
|
+
--caps-status-tray-key-height: calc(var(--size-field) * 8);
|
|
11037
|
+
--caps-status-tray-chip-height: var(--caps-status-tray-key-height);
|
|
11038
|
+
--caps-status-tray-width: 28rem;
|
|
11039
|
+
--caps-keycap-width: auto;
|
|
11040
|
+
--caps-keycap-height: var(--caps-status-tray-key-height);
|
|
11041
|
+
}
|
|
11042
|
+
.caps-composer-toolbar > .caps-status-tray .caps-status-tray-row :is(.caps-keycap-button, .caps-status-tray-chip, .caps-select) {
|
|
11043
|
+
height: var(--caps-status-tray-key-height);
|
|
11044
|
+
min-height: var(--caps-status-tray-key-height);
|
|
11045
|
+
}
|
|
11046
|
+
.caps-composer-toolbar > .caps-status-tray .caps-status-tray-label {
|
|
11047
|
+
display: flex;
|
|
11048
|
+
align-items: center;
|
|
11001
11049
|
}
|
|
11002
11050
|
.caps-alert[data-tone="error"] h3 {
|
|
11003
11051
|
color: var(--color-error);
|
|
@@ -12640,6 +12688,13 @@
|
|
|
12640
12688
|
padding: var(--spacing-md);
|
|
12641
12689
|
text-align: center;
|
|
12642
12690
|
}
|
|
12691
|
+
.caps-send-action {
|
|
12692
|
+
--caps-keycap-width: auto;
|
|
12693
|
+
--caps-keycap-justify: center;
|
|
12694
|
+
--caps-keycap-padding: 0;
|
|
12695
|
+
--caps-keycap-min-width: var(--caps-keycap-height, 2.5rem);
|
|
12696
|
+
flex: none;
|
|
12697
|
+
}
|
|
12643
12698
|
.caps-send-glyph {
|
|
12644
12699
|
display: inline-grid;
|
|
12645
12700
|
place-items: center;
|
|
@@ -13584,15 +13639,15 @@
|
|
|
13584
13639
|
isolation: var(--caps-button-outline-isolation, auto);
|
|
13585
13640
|
}
|
|
13586
13641
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]) {
|
|
13587
|
-
background-color: var(--caps-
|
|
13588
|
-
color: var(--caps-button-outline-ink,
|
|
13642
|
+
background-color: var(--caps-key-face);
|
|
13643
|
+
color: var(--caps-button-outline-ink, var(--color-base-content));
|
|
13589
13644
|
}
|
|
13590
13645
|
.caps-button.btn-outline:is(:disabled, [aria-disabled="true"]) {
|
|
13591
13646
|
background-color: var( --caps-button-outline-disabled-face, var(--color-base-200) );
|
|
13592
13647
|
}
|
|
13593
13648
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):hover {
|
|
13594
|
-
background-color: var( --caps-button-outline-hover-face, var(--caps-
|
|
13595
|
-
color: var( --caps-button-outline-hover-ink, var(--caps-button-outline-ink,
|
|
13649
|
+
background-color: var( --caps-button-outline-hover-face, var(--caps-keycap-hover-face, var(--caps-key-face)) );
|
|
13650
|
+
color: var( --caps-button-outline-hover-ink, var(--caps-button-outline-ink, var(--color-base-content)) );
|
|
13596
13651
|
}
|
|
13597
13652
|
.caps-button.btn-outline::before {
|
|
13598
13653
|
content: var(--caps-button-outline-inset-content, none);
|
|
@@ -13605,21 +13660,36 @@
|
|
|
13605
13660
|
.caps-button.btn-outline:hover::before {
|
|
13606
13661
|
background: var(--color-base-content);
|
|
13607
13662
|
}
|
|
13608
|
-
.caps-button.btn-outline {
|
|
13609
|
-
--
|
|
13663
|
+
.caps-keycap-button, .caps-button-filled, .caps-button.btn-outline {
|
|
13664
|
+
--caps-key-lip: calc(var(--depth) * 1px);
|
|
13665
|
+
--caps-key-rest-shadow: inset 0 1px 0
|
|
13666
|
+
oklch(100% 0 0 / calc(var(--depth) * var(--caps-key-top-edge, 0.14))),
|
|
13667
|
+
0 var(--caps-key-lip) 0
|
|
13668
|
+
var(--caps-key-face);
|
|
13610
13669
|
@supports (color: color-mix(in lab, red, red)) {
|
|
13611
|
-
--
|
|
13612
|
-
|
|
13613
|
-
var(--
|
|
13614
|
-
|
|
13615
|
-
|
|
13670
|
+
--caps-key-rest-shadow: inset 0 1px 0
|
|
13671
|
+
oklch(100% 0 0 / calc(var(--depth) * var(--caps-key-top-edge, 0.14))),
|
|
13672
|
+
0 var(--caps-key-lip) 0
|
|
13673
|
+
color-mix(
|
|
13674
|
+
in oklab,
|
|
13675
|
+
var(--caps-key-face),
|
|
13676
|
+
var(--caps-key-lip-ink, var(--color-base-content)) 14%
|
|
13677
|
+
);
|
|
13616
13678
|
}
|
|
13679
|
+
--caps-key-pressed-shadow: inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08)), 0 0 0 transparent;
|
|
13680
|
+
}
|
|
13681
|
+
.caps-button.btn-outline {
|
|
13682
|
+
--btn-border: var(--stipe-linework);
|
|
13683
|
+
--caps-key-face: var(--caps-button-outline-face, var(--color-base-200));
|
|
13617
13684
|
font-weight: var(--caps-button-weight, 500);
|
|
13618
|
-
box-shadow: var(--caps-button-outline-drop, var(--caps-button-drop,
|
|
13685
|
+
box-shadow: var( --caps-button-outline-drop, var(--caps-button-drop, var(--caps-key-rest-shadow)) );
|
|
13686
|
+
transition-property: background-color, border-color, box-shadow, translate;
|
|
13687
|
+
transition-duration: var(--stipe-motion-fast);
|
|
13688
|
+
transition-timing-function: ease-out;
|
|
13619
13689
|
}
|
|
13620
13690
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):active {
|
|
13621
|
-
translate: 0 var(--caps-button-press, var(--
|
|
13622
|
-
box-shadow: var( --caps-button-outline-drop, var(--caps-button-drop-active,
|
|
13691
|
+
translate: 0 var(--caps-button-press, var(--caps-key-lip));
|
|
13692
|
+
box-shadow: var( --caps-button-outline-drop, var(--caps-button-drop-active, var(--caps-key-pressed-shadow)) );
|
|
13623
13693
|
}
|
|
13624
13694
|
.caps-button.btn-ghost {
|
|
13625
13695
|
font-weight: 500;
|
|
@@ -13629,6 +13699,9 @@
|
|
|
13629
13699
|
}
|
|
13630
13700
|
.caps-button-filled {
|
|
13631
13701
|
--btn-bg: var(--btn-color);
|
|
13702
|
+
--caps-key-face: var(--btn-color);
|
|
13703
|
+
--caps-key-lip-ink: black;
|
|
13704
|
+
--caps-key-top-edge: 0.06;
|
|
13632
13705
|
--caps-button-filled-edge: var(
|
|
13633
13706
|
--caps-button-border-color,
|
|
13634
13707
|
var(--btn-bg)
|
|
@@ -13638,7 +13711,7 @@
|
|
|
13638
13711
|
--caps-button-border-color,
|
|
13639
13712
|
color-mix(
|
|
13640
13713
|
in oklab,
|
|
13641
|
-
color-mix(in oklab, var(--btn-bg), black calc(var(--depth) *
|
|
13714
|
+
color-mix(in oklab, var(--btn-bg), black calc(var(--depth) * 6%)),
|
|
13642
13715
|
var(--stipe-linework)
|
|
13643
13716
|
calc((1 - var(--depth)) * var(--caps-button-linework, 100%))
|
|
13644
13717
|
)
|
|
@@ -13646,7 +13719,7 @@
|
|
|
13646
13719
|
}
|
|
13647
13720
|
--caps-button-filled-drop: var(
|
|
13648
13721
|
--caps-button-drop,
|
|
13649
|
-
|
|
13722
|
+
var(--caps-key-rest-shadow)
|
|
13650
13723
|
);
|
|
13651
13724
|
background-color: var(--btn-bg);
|
|
13652
13725
|
color: var(--btn-fg);
|
|
@@ -13689,9 +13762,9 @@
|
|
|
13689
13762
|
@supports (color: color-mix(in lab, red, red)) {
|
|
13690
13763
|
--btn-bg: color-mix(in oklab, var(--btn-color) 92%, black);
|
|
13691
13764
|
}
|
|
13692
|
-
translate: 0 var(--caps-button-press, var(--
|
|
13765
|
+
translate: 0 var(--caps-button-press, var(--caps-key-lip));
|
|
13693
13766
|
scale: var(--stipe-press-scale);
|
|
13694
|
-
box-shadow: var(
|
|
13767
|
+
box-shadow: var(--caps-button-drop-active, var(--caps-key-pressed-shadow));
|
|
13695
13768
|
transition-duration: var(--stipe-motion-squish);
|
|
13696
13769
|
}
|
|
13697
13770
|
.caps-button.caps-button-menubar {
|
|
@@ -14672,7 +14745,7 @@
|
|
|
14672
14745
|
scale: none;
|
|
14673
14746
|
}
|
|
14674
14747
|
.caps-button-filled:not(:disabled):not([aria-disabled="true"]):active {
|
|
14675
|
-
translate: var(--caps-button-press-reduced,
|
|
14748
|
+
translate: var(--caps-button-press-reduced, 0 var(--caps-key-lip));
|
|
14676
14749
|
}
|
|
14677
14750
|
.caps-gooey-motion, .caps-gooey-motion::before, .caps-switch, .caps-radio, .caps-checkbox, .caps-checkbox-track, .caps-input, .caps-status-bead {
|
|
14678
14751
|
animation: none !important;
|
|
@@ -15857,7 +15930,7 @@
|
|
|
15857
15930
|
}
|
|
15858
15931
|
}
|
|
15859
15932
|
@media (prefers-reduced-motion: reduce) {
|
|
15860
|
-
.caps-keycap, .caps-keycap-button {
|
|
15933
|
+
.caps-keycap, .caps-keycap-button, .caps-button.btn-outline {
|
|
15861
15934
|
transition: none;
|
|
15862
15935
|
}
|
|
15863
15936
|
}
|
|
@@ -15928,7 +16001,21 @@
|
|
|
15928
16001
|
height: 0.625rem;
|
|
15929
16002
|
}
|
|
15930
16003
|
.caps-keycap-button {
|
|
15931
|
-
--caps-
|
|
16004
|
+
--caps-key-face: var(--caps-keycap-face, var(--color-base-200));
|
|
16005
|
+
--caps-keycap-rest-shadow: inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.18)),
|
|
16006
|
+
0 var(--caps-key-lip) 0
|
|
16007
|
+
var(--caps-key-face),
|
|
16008
|
+
0 var(--caps-key-lip) calc(var(--depth) * 2px)
|
|
16009
|
+
oklch(0% 0 0 / calc(var(--depth) * 0.15));
|
|
16010
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
16011
|
+
--caps-keycap-rest-shadow: inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.18)),
|
|
16012
|
+
0 var(--caps-key-lip) 0
|
|
16013
|
+
color-mix(in oklab, var(--caps-key-face), var(--color-base-content) 14%),
|
|
16014
|
+
0 var(--caps-key-lip) calc(var(--depth) * 2px)
|
|
16015
|
+
oklch(0% 0 0 / calc(var(--depth) * 0.15));
|
|
16016
|
+
}
|
|
16017
|
+
--caps-keycap-pressed-shadow: inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08)),
|
|
16018
|
+
0 0 0 transparent, 0 0 0 transparent;
|
|
15932
16019
|
display: flex;
|
|
15933
16020
|
width: var(--caps-keycap-width, 100%);
|
|
15934
16021
|
min-width: var(--caps-keycap-min-width, 0);
|
|
@@ -15937,7 +16024,10 @@
|
|
|
15937
16024
|
justify-content: var(--caps-keycap-justify, space-between);
|
|
15938
16025
|
gap: var(--spacing-sm);
|
|
15939
16026
|
padding: var(--caps-keycap-padding, var(--spacing-xs) var(--spacing-sm));
|
|
15940
|
-
border: var( --caps-keycap-context-border, var(--border) solid var(--caps-keycap-edge, var(--stipe-linework)) );
|
|
16027
|
+
border: var( --caps-keycap-context-border, var(--border) solid var( --caps-keycap-edge, var(--stipe-linework) ) );
|
|
16028
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
16029
|
+
border: var( --caps-keycap-context-border, var(--border) solid var( --caps-keycap-edge, color-mix(in oklab, var(--stipe-linework) 80%, var(--caps-key-face)) ) );
|
|
16030
|
+
}
|
|
15941
16031
|
font-size: var(--caps-keycap-font-size, inherit);
|
|
15942
16032
|
font-weight: var( --caps-keycap-context-weight, var(--caps-keycap-font-weight, inherit) );
|
|
15943
16033
|
border-radius: var(--radius-field);
|
|
@@ -15946,12 +16036,28 @@
|
|
|
15946
16036
|
var(--caps-keycap-face, var(--color-base-200))
|
|
15947
16037
|
);
|
|
15948
16038
|
background-color: var(--caps-keycap-painted-face);
|
|
15949
|
-
--caps-keycap-default-image:
|
|
15950
|
-
|
|
15951
|
-
|
|
16039
|
+
--caps-keycap-default-image: linear-gradient(
|
|
16040
|
+
to bottom,
|
|
16041
|
+
var(--caps-keycap-painted-face),
|
|
16042
|
+
var(--caps-keycap-painted-face)
|
|
16043
|
+
);
|
|
15952
16044
|
@supports (color: color-mix(in lab, red, red)) {
|
|
15953
|
-
|
|
16045
|
+
--caps-keycap-default-image: linear-gradient(
|
|
16046
|
+
to bottom,
|
|
16047
|
+
color-mix(
|
|
16048
|
+
in oklab,
|
|
16049
|
+
var(--caps-keycap-painted-face),
|
|
16050
|
+
white calc(var(--depth) * 3%)
|
|
16051
|
+
),
|
|
16052
|
+
color-mix(
|
|
16053
|
+
in oklab,
|
|
16054
|
+
var(--caps-keycap-painted-face),
|
|
16055
|
+
black calc(var(--depth) * 3%)
|
|
16056
|
+
)
|
|
16057
|
+
);
|
|
15954
16058
|
}
|
|
16059
|
+
background-image: var( --caps-keycap-context-image, var(--caps-keycap-default-image) );
|
|
16060
|
+
box-shadow: var( --caps-keycap-context-shadow, var(--caps-keycap-rest-shadow) );
|
|
15955
16061
|
color: var(--caps-keycap-ink, var(--color-base-content));
|
|
15956
16062
|
text-align: left;
|
|
15957
16063
|
cursor: pointer;
|
|
@@ -15960,8 +16066,8 @@
|
|
|
15960
16066
|
transition-timing-function: ease-out;
|
|
15961
16067
|
}
|
|
15962
16068
|
.caps-keycap-button:active {
|
|
15963
|
-
translate: 0 var(--caps-keycap-context-travel, var(--caps-
|
|
15964
|
-
box-shadow: var( --caps-keycap-active-shadow,
|
|
16069
|
+
translate: 0 var(--caps-keycap-context-travel, var(--caps-key-lip));
|
|
16070
|
+
box-shadow: var( --caps-keycap-active-shadow, var(--caps-keycap-pressed-shadow) );
|
|
15965
16071
|
}
|
|
15966
16072
|
.caps-keycap-button:focus-visible {
|
|
15967
16073
|
outline: 2px solid var(--caps-keycap-focus-ink, var(--color-base-content));
|
|
@@ -16008,6 +16114,28 @@
|
|
|
16008
16114
|
--caps-keycap-ink: var(--color-neutral-content);
|
|
16009
16115
|
--caps-keycap-edge: var(--color-neutral);
|
|
16010
16116
|
}
|
|
16117
|
+
.caps-keycap-button.caps-send-action[data-tone="primary"] {
|
|
16118
|
+
--caps-keycap-face: var(--color-primary);
|
|
16119
|
+
--caps-keycap-ink: var(--color-primary-content);
|
|
16120
|
+
--caps-keycap-edge: var(--color-primary);
|
|
16121
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
16122
|
+
--caps-keycap-edge: color-mix(in oklab, var(--color-primary) 82%, black);
|
|
16123
|
+
}
|
|
16124
|
+
--caps-keycap-hover-face: var(--color-primary);
|
|
16125
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
16126
|
+
--caps-keycap-hover-face: color-mix(
|
|
16127
|
+
in oklab,
|
|
16128
|
+
var(--color-primary) 92%,
|
|
16129
|
+
black
|
|
16130
|
+
);
|
|
16131
|
+
}
|
|
16132
|
+
}
|
|
16133
|
+
.caps-keycap-button.caps-send-action[data-send-state="empty"] {
|
|
16134
|
+
--caps-keycap-face: var(--color-base-200);
|
|
16135
|
+
--caps-keycap-ink: var(--color-base-content);
|
|
16136
|
+
--caps-keycap-edge: var(--stipe-linework);
|
|
16137
|
+
--caps-keycap-hover-face: var(--color-base-200);
|
|
16138
|
+
}
|
|
16011
16139
|
.caps-keycap-button[data-tone="destructive"]:is(:hover, :focus-visible) {
|
|
16012
16140
|
--caps-keycap-ink: var(--color-error);
|
|
16013
16141
|
--caps-keycap-edge: var(--color-error);
|
|
@@ -52,6 +52,14 @@ optional shortcut?: string;
|
|
|
52
52
|
|
|
53
53
|
Key hint shown before the label, e.g. `z` in a which-key list.
|
|
54
54
|
|
|
55
|
+
##### media?
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
optional media?: ReactNode;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Decorative leading visual, such as a thumbnail, shown before the label.
|
|
62
|
+
|
|
55
63
|
---
|
|
56
64
|
|
|
57
65
|
### CommandMenuProps
|
|
@@ -68,11 +76,12 @@ Props accepted by CommandMenu.
|
|
|
68
76
|
|
|
69
77
|
#### Type Declaration
|
|
70
78
|
|
|
71
|
-
| Name
|
|
72
|
-
|
|
|
73
|
-
| `items`
|
|
74
|
-
| `onSelect()`
|
|
75
|
-
| `selected?`
|
|
79
|
+
| Name | Type | Description |
|
|
80
|
+
| ----------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| `items` | readonly [`CommandMenuItem`](#commandmenuitem)[] | - |
|
|
82
|
+
| `onSelect()` | (`id`) => `void` | Called after a non-disabled item is selected. |
|
|
83
|
+
| `selected?` | `string` | Caller-owned selected state; distinct from local keyboard focus. |
|
|
84
|
+
| `descriptionPlacement?` | `"end"` \| `"inline"` | `end` (default) sets each description at the row's end. `inline` keeps the label at its own width and starts the description right after it, truncated, so long descriptions never push the row's text apart. |
|
|
76
85
|
|
|
77
86
|
## Variables
|
|
78
87
|
|
|
@@ -20,12 +20,12 @@ Props accepted by Composer.
|
|
|
20
20
|
|
|
21
21
|
#### Type Declaration
|
|
22
22
|
|
|
23
|
-
| Name
|
|
24
|
-
|
|
|
25
|
-
| `input`
|
|
26
|
-
| `action?`
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
23
|
+
| Name | Type | Description |
|
|
24
|
+
| -------------- | ----------- | ---------------------------------------------------------------------------- |
|
|
25
|
+
| `input` | `ReactNode` | The caller-owned field, typed into directly: the surface is the field. |
|
|
26
|
+
| `action?` | `ReactNode` | The caller-owned key at the end of the bottom row, such as a SendAction. |
|
|
27
|
+
| `toolbar?` | `ReactNode` | Caller-owned controls and readouts along the bottom edge, before the action. |
|
|
28
|
+
| `attachments?` | `ReactNode` | Caller-owned items riding with the draft, shown above the text. |
|
|
29
29
|
|
|
30
30
|
## Variables
|
|
31
31
|
|
|
@@ -37,4 +37,7 @@ const Composer: ForwardRefExoticComponent<
|
|
|
37
37
|
>;
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
One composer surface: attachments, then the text, then a slim row along the
|
|
41
|
+
bottom edge holding the toolbar and, at its end, the action. It draws no box of
|
|
42
|
+
its own: the host region is the field. Layout and slots only; it never assigns
|
|
43
|
+
action-control semantics.
|
|
@@ -24,7 +24,16 @@ Where a composer's send action is: nothing to send, ready, or working.
|
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
26
|
type SendActionProps = object &
|
|
27
|
-
Omit<
|
|
27
|
+
Omit<
|
|
28
|
+
KeycapButtonProps,
|
|
29
|
+
| "label"
|
|
30
|
+
| "icon"
|
|
31
|
+
| "readout"
|
|
32
|
+
| "shortcut"
|
|
33
|
+
| "pressed"
|
|
34
|
+
| "expanded"
|
|
35
|
+
| "aria-label"
|
|
36
|
+
>;
|
|
28
37
|
```
|
|
29
38
|
|
|
30
39
|
Props accepted by SendAction.
|
|
@@ -46,13 +55,23 @@ Props accepted by SendAction.
|
|
|
46
55
|
```ts
|
|
47
56
|
const SendAction: ForwardRefExoticComponent<
|
|
48
57
|
object &
|
|
49
|
-
Omit<
|
|
58
|
+
Omit<
|
|
59
|
+
KeycapButtonProps,
|
|
60
|
+
| "aria-label"
|
|
61
|
+
| "label"
|
|
62
|
+
| "icon"
|
|
63
|
+
| "readout"
|
|
64
|
+
| "expanded"
|
|
65
|
+
| "pressed"
|
|
66
|
+
| "shortcut"
|
|
67
|
+
> &
|
|
50
68
|
RefAttributes<HTMLButtonElement>
|
|
51
69
|
>;
|
|
52
70
|
```
|
|
53
71
|
|
|
54
|
-
A composer's send key. Empty, it rests disabled; given
|
|
55
|
-
wakes with a small pop; sending, its arrow launches away
|
|
56
|
-
(or, with `onStop`, a stop square) takes its place
|
|
57
|
-
|
|
72
|
+
A composer's one send key, on the Caps keycap. Empty, it rests disabled; given
|
|
73
|
+
something to send it wakes with a small pop; sending, its arrow launches away
|
|
74
|
+
and a working indicator (or, with `onStop`, a stop square) takes its place in
|
|
75
|
+
the same key; when the work ends the arrow drops back in. It never grows a
|
|
76
|
+
second button. All timing comes from the theme's motion tokens, and reduced
|
|
58
77
|
motion keeps short fades only.
|
package/package.json
CHANGED
package/src/app-status-bar.css
CHANGED
|
@@ -134,6 +134,27 @@
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/*
|
|
138
|
+
* A count in a bar key is a quiet readout beside its label: plain muted
|
|
139
|
+
* numerals below the label's cap height, never a filled pill as tall as
|
|
140
|
+
* the key. Every key's CountBadge draws this way.
|
|
141
|
+
*/
|
|
142
|
+
.caps-app-status-bar .badge {
|
|
143
|
+
height: auto;
|
|
144
|
+
min-width: 0;
|
|
145
|
+
padding: 0;
|
|
146
|
+
border: 0;
|
|
147
|
+
background: none;
|
|
148
|
+
color: var(--stipe-fg-muted);
|
|
149
|
+
font-size: 0.625rem;
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
line-height: 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.caps-app-status-bar .badge-warning {
|
|
155
|
+
color: var(--color-warning);
|
|
156
|
+
}
|
|
157
|
+
|
|
137
158
|
.caps-status-bar-picker-list {
|
|
138
159
|
min-width: min(18rem, calc(100vw - 1.5rem));
|
|
139
160
|
}
|
package/src/styles.css
CHANGED
|
@@ -2422,26 +2422,73 @@
|
|
|
2422
2422
|
color: var(--color-base-content);
|
|
2423
2423
|
font-size: 0.875rem;
|
|
2424
2424
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2425
|
+
/* The composer is a surface, not a box inside a panel: the host region is
|
|
2426
|
+
the field, and the text sits directly on the host's face. A slim row along
|
|
2427
|
+
the bottom edge carries the toolbar and, at its end, the action key. */
|
|
2428
|
+
.caps-composer {
|
|
2429
|
+
display: flex;
|
|
2430
|
+
min-width: 0;
|
|
2431
|
+
flex-direction: column;
|
|
2432
|
+
gap: var(--spacing-xs);
|
|
2433
|
+
}
|
|
2434
|
+
.caps-composer-attachments {
|
|
2435
|
+
display: flex;
|
|
2436
|
+
flex-wrap: wrap;
|
|
2437
|
+
gap: var(--spacing-xs);
|
|
2438
|
+
}
|
|
2439
|
+
.caps-composer-input {
|
|
2440
|
+
position: relative;
|
|
2441
|
+
min-width: 0;
|
|
2442
|
+
}
|
|
2443
|
+
.caps-composer-input :is(textarea, input),
|
|
2444
|
+
.caps-composer-input :is(textarea, input):focus,
|
|
2445
|
+
.caps-composer-input :is(textarea, input):focus-visible {
|
|
2446
|
+
width: 100%;
|
|
2447
|
+
border: 0;
|
|
2448
|
+
border-radius: 0;
|
|
2449
|
+
background: transparent;
|
|
2450
|
+
box-shadow: none;
|
|
2451
|
+
outline: none;
|
|
2452
|
+
padding-inline: 0;
|
|
2453
|
+
}
|
|
2454
|
+
.caps-composer-bar {
|
|
2455
|
+
display: flex;
|
|
2456
|
+
min-width: 0;
|
|
2457
|
+
align-items: center;
|
|
2458
|
+
gap: var(--spacing-xs);
|
|
2459
|
+
}
|
|
2460
|
+
.caps-composer-toolbar {
|
|
2461
|
+
display: flex;
|
|
2462
|
+
min-width: 0;
|
|
2463
|
+
flex: 1;
|
|
2464
|
+
align-items: center;
|
|
2465
|
+
gap: var(--spacing-xs);
|
|
2466
|
+
}
|
|
2467
|
+
/* A StatusTray seated as the composer's toolbar: one wrapping row of keys,
|
|
2468
|
+
selects and chips on the theme's field-size grid, the same in every App. */
|
|
2469
|
+
.caps-composer-toolbar > .caps-status-tray {
|
|
2470
|
+
flex: 1;
|
|
2471
|
+
min-width: 0;
|
|
2472
|
+
--caps-status-tray-row-wrap: wrap;
|
|
2473
|
+
--caps-status-tray-chips-display: contents;
|
|
2474
|
+
--caps-status-tray-key-height: calc(var(--size-field) * 8);
|
|
2475
|
+
--caps-status-tray-chip-height: var(--caps-status-tray-key-height);
|
|
2476
|
+
--caps-status-tray-width: 28rem;
|
|
2477
|
+
--caps-keycap-width: auto;
|
|
2478
|
+
--caps-keycap-height: var(--caps-status-tray-key-height);
|
|
2479
|
+
}
|
|
2480
|
+
.caps-composer-toolbar
|
|
2481
|
+
> .caps-status-tray
|
|
2482
|
+
.caps-status-tray-row
|
|
2483
|
+
:is(.caps-keycap-button, .caps-status-tray-chip, .caps-select) {
|
|
2484
|
+
height: var(--caps-status-tray-key-height);
|
|
2485
|
+
min-height: var(--caps-status-tray-key-height);
|
|
2486
|
+
}
|
|
2487
|
+
/* A chip label is a flex row, so an inline label box adds no text strut
|
|
2488
|
+
and chip text shares the keys' baseline. */
|
|
2489
|
+
.caps-composer-toolbar > .caps-status-tray .caps-status-tray-label {
|
|
2490
|
+
display: flex;
|
|
2491
|
+
align-items: center;
|
|
2445
2492
|
}
|
|
2446
2493
|
.caps-alert[data-tone="error"] h3 {
|
|
2447
2494
|
color: var(--color-error);
|
|
@@ -4221,6 +4268,14 @@
|
|
|
4221
4268
|
text-align: center;
|
|
4222
4269
|
}
|
|
4223
4270
|
/* SendAction: the arrow and the working glyph share one cell. */
|
|
4271
|
+
/* The send key: one square keycap whose glyph changes, never a second key. */
|
|
4272
|
+
.caps-send-action {
|
|
4273
|
+
--caps-keycap-width: auto;
|
|
4274
|
+
--caps-keycap-justify: center;
|
|
4275
|
+
--caps-keycap-padding: 0;
|
|
4276
|
+
--caps-keycap-min-width: var(--caps-keycap-height, 2.5rem);
|
|
4277
|
+
flex: none;
|
|
4278
|
+
}
|
|
4224
4279
|
.caps-send-glyph {
|
|
4225
4280
|
display: inline-grid;
|
|
4226
4281
|
place-items: center;
|
|
@@ -5333,8 +5388,8 @@
|
|
|
5333
5388
|
isolation: var(--caps-button-outline-isolation, auto);
|
|
5334
5389
|
}
|
|
5335
5390
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]) {
|
|
5336
|
-
background-color: var(--caps-
|
|
5337
|
-
color: var(--caps-button-outline-ink,
|
|
5391
|
+
background-color: var(--caps-key-face);
|
|
5392
|
+
color: var(--caps-button-outline-ink, var(--color-base-content));
|
|
5338
5393
|
}
|
|
5339
5394
|
.caps-button.btn-outline:is(:disabled, [aria-disabled="true"]) {
|
|
5340
5395
|
background-color: var(
|
|
@@ -5345,11 +5400,11 @@
|
|
|
5345
5400
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):hover {
|
|
5346
5401
|
background-color: var(
|
|
5347
5402
|
--caps-button-outline-hover-face,
|
|
5348
|
-
var(--caps-
|
|
5403
|
+
var(--caps-keycap-hover-face, var(--caps-key-face))
|
|
5349
5404
|
);
|
|
5350
5405
|
color: var(
|
|
5351
5406
|
--caps-button-outline-hover-ink,
|
|
5352
|
-
var(--caps-button-outline-ink,
|
|
5407
|
+
var(--caps-button-outline-ink, var(--color-base-content))
|
|
5353
5408
|
);
|
|
5354
5409
|
}
|
|
5355
5410
|
/* A cut outline uses an inset face; an ordinary outline needs no extra face. */
|
|
@@ -5365,24 +5420,48 @@
|
|
|
5365
5420
|
background: var(--color-base-content);
|
|
5366
5421
|
}
|
|
5367
5422
|
|
|
5368
|
-
/*
|
|
5369
|
-
|
|
5423
|
+
/* The Caps key material: a flat face on a 1px lip shaded toward the ink,
|
|
5424
|
+
a faint top edge, and a press that settles the face onto its lip. Every
|
|
5425
|
+
Button reads it; KeycapButton reads its face and lip and raises its own
|
|
5426
|
+
finish on them. Everything scales with --depth, so flat themes keep a
|
|
5427
|
+
flat linework key, and a theme's --caps-button-drop/-press still win. */
|
|
5428
|
+
.caps-keycap-button,
|
|
5429
|
+
.caps-button-filled,
|
|
5370
5430
|
.caps-button.btn-outline {
|
|
5371
|
-
--
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5431
|
+
--caps-key-lip: calc(var(--depth) * 1px);
|
|
5432
|
+
--caps-key-rest-shadow:
|
|
5433
|
+
inset 0 1px 0
|
|
5434
|
+
oklch(100% 0 0 / calc(var(--depth) * var(--caps-key-top-edge, 0.14))),
|
|
5435
|
+
0 var(--caps-key-lip) 0
|
|
5436
|
+
color-mix(
|
|
5437
|
+
in oklab,
|
|
5438
|
+
var(--caps-key-face),
|
|
5439
|
+
var(--caps-key-lip-ink, var(--color-base-content)) 14%
|
|
5440
|
+
);
|
|
5441
|
+
--caps-key-pressed-shadow:
|
|
5442
|
+
inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08)), 0 0 0 transparent;
|
|
5443
|
+
}
|
|
5444
|
+
|
|
5445
|
+
/* An outline button is the neutral key: a base-200 face on linework. */
|
|
5446
|
+
.caps-button.btn-outline {
|
|
5447
|
+
--btn-border: var(--stipe-linework);
|
|
5448
|
+
--caps-key-face: var(--caps-button-outline-face, var(--color-base-200));
|
|
5376
5449
|
|
|
5377
5450
|
font-weight: var(--caps-button-weight, 500);
|
|
5378
|
-
box-shadow: var(
|
|
5451
|
+
box-shadow: var(
|
|
5452
|
+
--caps-button-outline-drop,
|
|
5453
|
+
var(--caps-button-drop, var(--caps-key-rest-shadow))
|
|
5454
|
+
);
|
|
5455
|
+
transition-property: background-color, border-color, box-shadow, translate;
|
|
5456
|
+
transition-duration: var(--stipe-motion-fast);
|
|
5457
|
+
transition-timing-function: ease-out;
|
|
5379
5458
|
}
|
|
5380
5459
|
|
|
5381
5460
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):active {
|
|
5382
|
-
translate: 0 var(--caps-button-press, var(--
|
|
5461
|
+
translate: 0 var(--caps-button-press, var(--caps-key-lip));
|
|
5383
5462
|
box-shadow: var(
|
|
5384
5463
|
--caps-button-outline-drop,
|
|
5385
|
-
var(--caps-button-drop-active,
|
|
5464
|
+
var(--caps-button-drop-active, var(--caps-key-pressed-shadow))
|
|
5386
5465
|
);
|
|
5387
5466
|
}
|
|
5388
5467
|
|
|
@@ -5394,25 +5473,28 @@
|
|
|
5394
5473
|
--radius-field: var(--radius-button);
|
|
5395
5474
|
}
|
|
5396
5475
|
|
|
5397
|
-
/* A filled button is
|
|
5398
|
-
darker than the fill,
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5476
|
+
/* A filled button is the same key in its role colour: one solid fill with
|
|
5477
|
+
no lighter ring, an edge a hair darker than the fill, a quiet lip shaded
|
|
5478
|
+
darker still, and only a very faint top edge. Flat themes edge it in
|
|
5479
|
+
linework instead; a theme sets --caps-button-linework: 0% to edge it in
|
|
5480
|
+
its own fill. */
|
|
5402
5481
|
.caps-button-filled {
|
|
5403
5482
|
--btn-bg: var(--btn-color);
|
|
5483
|
+
--caps-key-face: var(--btn-color);
|
|
5484
|
+
--caps-key-lip-ink: black;
|
|
5485
|
+
--caps-key-top-edge: 0.06;
|
|
5404
5486
|
--caps-button-filled-edge: var(
|
|
5405
5487
|
--caps-button-border-color,
|
|
5406
5488
|
color-mix(
|
|
5407
5489
|
in oklab,
|
|
5408
|
-
color-mix(in oklab, var(--btn-bg), black calc(var(--depth) *
|
|
5490
|
+
color-mix(in oklab, var(--btn-bg), black calc(var(--depth) * 6%)),
|
|
5409
5491
|
var(--stipe-linework)
|
|
5410
5492
|
calc((1 - var(--depth)) * var(--caps-button-linework, 100%))
|
|
5411
5493
|
)
|
|
5412
5494
|
);
|
|
5413
5495
|
--caps-button-filled-drop: var(
|
|
5414
5496
|
--caps-button-drop,
|
|
5415
|
-
|
|
5497
|
+
var(--caps-key-rest-shadow)
|
|
5416
5498
|
);
|
|
5417
5499
|
|
|
5418
5500
|
background-color: var(--btn-bg);
|
|
@@ -5470,12 +5552,9 @@
|
|
|
5470
5552
|
) {
|
|
5471
5553
|
--btn-bg: color-mix(in oklab, var(--btn-color) 92%, black);
|
|
5472
5554
|
|
|
5473
|
-
translate: 0 var(--caps-button-press, var(--
|
|
5555
|
+
translate: 0 var(--caps-button-press, var(--caps-key-lip));
|
|
5474
5556
|
scale: var(--stipe-press-scale);
|
|
5475
|
-
box-shadow: var(
|
|
5476
|
-
--caps-button-drop-active,
|
|
5477
|
-
inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08))
|
|
5478
|
-
);
|
|
5557
|
+
box-shadow: var(--caps-button-drop-active, var(--caps-key-pressed-shadow));
|
|
5479
5558
|
transition-duration: var(--stipe-motion-squish);
|
|
5480
5559
|
}
|
|
5481
5560
|
|
|
@@ -6686,8 +6765,9 @@
|
|
|
6686
6765
|
scale: none;
|
|
6687
6766
|
}
|
|
6688
6767
|
|
|
6768
|
+
/* The press is a state, not motion: a key still settles onto its lip. */
|
|
6689
6769
|
.caps-button-filled:not(:disabled):not([aria-disabled="true"]):active {
|
|
6690
|
-
translate: var(--caps-button-press-reduced,
|
|
6770
|
+
translate: var(--caps-button-press-reduced, 0 var(--caps-key-lip));
|
|
6691
6771
|
}
|
|
6692
6772
|
|
|
6693
6773
|
.caps-gooey-motion,
|
|
@@ -7990,7 +8070,8 @@
|
|
|
7990
8070
|
|
|
7991
8071
|
@media (prefers-reduced-motion: reduce) {
|
|
7992
8072
|
.caps-keycap,
|
|
7993
|
-
.caps-keycap-button
|
|
8073
|
+
.caps-keycap-button,
|
|
8074
|
+
.caps-button.btn-outline {
|
|
7994
8075
|
transition: none;
|
|
7995
8076
|
}
|
|
7996
8077
|
}
|
|
@@ -8071,11 +8152,23 @@
|
|
|
8071
8152
|
.caps-game-meter .progress {
|
|
8072
8153
|
height: 0.625rem;
|
|
8073
8154
|
}
|
|
8074
|
-
/* A key:
|
|
8075
|
-
|
|
8076
|
-
|
|
8155
|
+
/* A key: today's flat key on a 1px lip, a touch richer. The face carries a
|
|
8156
|
+
faint ±3% grade, the top edge is 0.18, a small soft drop sits under the
|
|
8157
|
+
lip, and the linework edge is softened 20% toward the face. It reads its
|
|
8158
|
+
face and lip from the Caps key material; everything scales with --depth,
|
|
8159
|
+
so flat themes keep a flat linework key. A host or theme picks the face
|
|
8160
|
+
through --caps-keycap-face/-ink/-edge. */
|
|
8077
8161
|
.caps-keycap-button {
|
|
8078
|
-
--caps-
|
|
8162
|
+
--caps-key-face: var(--caps-keycap-face, var(--color-base-200));
|
|
8163
|
+
--caps-keycap-rest-shadow:
|
|
8164
|
+
inset 0 1px 0 oklch(100% 0 0 / calc(var(--depth) * 0.18)),
|
|
8165
|
+
0 var(--caps-key-lip) 0
|
|
8166
|
+
color-mix(in oklab, var(--caps-key-face), var(--color-base-content) 14%),
|
|
8167
|
+
0 var(--caps-key-lip) calc(var(--depth) * 2px)
|
|
8168
|
+
oklch(0% 0 0 / calc(var(--depth) * 0.15));
|
|
8169
|
+
--caps-keycap-pressed-shadow:
|
|
8170
|
+
inset 0 1px 0 oklch(0% 0 0 / calc(var(--depth) * 0.08)),
|
|
8171
|
+
0 0 0 transparent, 0 0 0 transparent;
|
|
8079
8172
|
display: flex;
|
|
8080
8173
|
width: var(--caps-keycap-width, 100%);
|
|
8081
8174
|
min-width: var(--caps-keycap-min-width, 0);
|
|
@@ -8086,7 +8179,11 @@
|
|
|
8086
8179
|
padding: var(--caps-keycap-padding, var(--spacing-xs) var(--spacing-sm));
|
|
8087
8180
|
border: var(
|
|
8088
8181
|
--caps-keycap-context-border,
|
|
8089
|
-
var(--border) solid
|
|
8182
|
+
var(--border) solid
|
|
8183
|
+
var(
|
|
8184
|
+
--caps-keycap-edge,
|
|
8185
|
+
color-mix(in oklab, var(--stipe-linework) 80%, var(--caps-key-face))
|
|
8186
|
+
)
|
|
8090
8187
|
);
|
|
8091
8188
|
font-size: var(--caps-keycap-font-size, inherit);
|
|
8092
8189
|
font-weight: var(
|
|
@@ -8099,20 +8196,26 @@
|
|
|
8099
8196
|
var(--caps-keycap-face, var(--color-base-200))
|
|
8100
8197
|
);
|
|
8101
8198
|
background-color: var(--caps-keycap-painted-face);
|
|
8102
|
-
--caps-keycap-default-image:
|
|
8199
|
+
--caps-keycap-default-image: linear-gradient(
|
|
8200
|
+
to bottom,
|
|
8201
|
+
color-mix(
|
|
8202
|
+
in oklab,
|
|
8203
|
+
var(--caps-keycap-painted-face),
|
|
8204
|
+
white calc(var(--depth) * 3%)
|
|
8205
|
+
),
|
|
8206
|
+
color-mix(
|
|
8207
|
+
in oklab,
|
|
8208
|
+
var(--caps-keycap-painted-face),
|
|
8209
|
+
black calc(var(--depth) * 3%)
|
|
8210
|
+
)
|
|
8211
|
+
);
|
|
8103
8212
|
background-image: var(
|
|
8104
8213
|
--caps-keycap-context-image,
|
|
8105
8214
|
var(--caps-keycap-default-image)
|
|
8106
8215
|
);
|
|
8107
8216
|
box-shadow: var(
|
|
8108
8217
|
--caps-keycap-context-shadow,
|
|
8109
|
-
|
|
8110
|
-
0 var(--caps-keycap-lip) 0
|
|
8111
|
-
color-mix(
|
|
8112
|
-
in oklab,
|
|
8113
|
-
var(--caps-keycap-face, var(--color-base-200)),
|
|
8114
|
-
var(--color-base-content) 14%
|
|
8115
|
-
)
|
|
8218
|
+
var(--caps-keycap-rest-shadow)
|
|
8116
8219
|
);
|
|
8117
8220
|
color: var(--caps-keycap-ink, var(--color-base-content));
|
|
8118
8221
|
text-align: left;
|
|
@@ -8123,11 +8226,10 @@
|
|
|
8123
8226
|
transition-timing-function: ease-out;
|
|
8124
8227
|
}
|
|
8125
8228
|
.caps-keycap-button:active {
|
|
8126
|
-
translate: 0 var(--caps-keycap-context-travel, var(--caps-
|
|
8229
|
+
translate: 0 var(--caps-keycap-context-travel, var(--caps-key-lip));
|
|
8127
8230
|
box-shadow: var(
|
|
8128
8231
|
--caps-keycap-active-shadow,
|
|
8129
|
-
|
|
8130
|
-
0 0 0 transparent
|
|
8232
|
+
var(--caps-keycap-pressed-shadow)
|
|
8131
8233
|
);
|
|
8132
8234
|
}
|
|
8133
8235
|
.caps-keycap-button:focus-visible {
|
|
@@ -8164,6 +8266,25 @@
|
|
|
8164
8266
|
--caps-keycap-ink: var(--color-neutral-content);
|
|
8165
8267
|
--caps-keycap-edge: var(--color-neutral);
|
|
8166
8268
|
}
|
|
8269
|
+
/* The send key is a filled key in its role colour, like a filled Button:
|
|
8270
|
+
a solid face and a hairline edge one step darker, no light rim. With
|
|
8271
|
+
nothing to send it rests as a calm neutral key. */
|
|
8272
|
+
.caps-keycap-button.caps-send-action[data-tone="primary"] {
|
|
8273
|
+
--caps-keycap-face: var(--color-primary);
|
|
8274
|
+
--caps-keycap-ink: var(--color-primary-content);
|
|
8275
|
+
--caps-keycap-edge: color-mix(in oklab, var(--color-primary) 82%, black);
|
|
8276
|
+
--caps-keycap-hover-face: color-mix(
|
|
8277
|
+
in oklab,
|
|
8278
|
+
var(--color-primary) 92%,
|
|
8279
|
+
black
|
|
8280
|
+
);
|
|
8281
|
+
}
|
|
8282
|
+
.caps-keycap-button.caps-send-action[data-send-state="empty"] {
|
|
8283
|
+
--caps-keycap-face: var(--color-base-200);
|
|
8284
|
+
--caps-keycap-ink: var(--color-base-content);
|
|
8285
|
+
--caps-keycap-edge: var(--stipe-linework);
|
|
8286
|
+
--caps-keycap-hover-face: var(--color-base-200);
|
|
8287
|
+
}
|
|
8167
8288
|
/* Destructive keys stay quiet until the pointer or focus is on them. */
|
|
8168
8289
|
.caps-keycap-button[data-tone="destructive"]:is(:hover, :focus-visible) {
|
|
8169
8290
|
--caps-keycap-ink: var(--color-error);
|