@fungi.computer/caps 0.3.1 → 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/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 +177 -52
- 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,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
|
@@ -6166,9 +6166,6 @@
|
|
|
6166
6166
|
margin-block-end: calc(var(--spacing-sm) * calc(1 - var(--tw-space-y-reverse)));
|
|
6167
6167
|
}
|
|
6168
6168
|
}
|
|
6169
|
-
.self-stretch {
|
|
6170
|
-
align-self: stretch;
|
|
6171
|
-
}
|
|
6172
6169
|
.truncate {
|
|
6173
6170
|
overflow: hidden;
|
|
6174
6171
|
text-overflow: ellipsis;
|
|
@@ -6180,9 +6177,6 @@
|
|
|
6180
6177
|
.overflow-hidden {
|
|
6181
6178
|
overflow: hidden;
|
|
6182
6179
|
}
|
|
6183
|
-
.overflow-visible {
|
|
6184
|
-
overflow: visible;
|
|
6185
|
-
}
|
|
6186
6180
|
.overflow-y-auto {
|
|
6187
6181
|
overflow-y: auto;
|
|
6188
6182
|
}
|
|
@@ -6225,10 +6219,6 @@
|
|
|
6225
6219
|
border-bottom-style: var(--tw-border-style);
|
|
6226
6220
|
border-bottom-width: 1px;
|
|
6227
6221
|
}
|
|
6228
|
-
.border-none {
|
|
6229
|
-
--tw-border-style: none;
|
|
6230
|
-
border-style: none;
|
|
6231
|
-
}
|
|
6232
6222
|
.badge-outline {
|
|
6233
6223
|
@layer daisyui.l1.l2 {
|
|
6234
6224
|
color: var(--badge-color);
|
|
@@ -6370,9 +6360,6 @@
|
|
|
6370
6360
|
.p-2xl {
|
|
6371
6361
|
padding: var(--spacing-2xl);
|
|
6372
6362
|
}
|
|
6373
|
-
.p-2xs {
|
|
6374
|
-
padding: var(--spacing-2xs);
|
|
6375
|
-
}
|
|
6376
6363
|
.p-4 {
|
|
6377
6364
|
padding: calc(var(--spacing) * 4);
|
|
6378
6365
|
}
|
|
@@ -8740,6 +8727,20 @@
|
|
|
8740
8727
|
white-space: nowrap;
|
|
8741
8728
|
}
|
|
8742
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
|
+
}
|
|
8743
8744
|
.caps-status-bar-picker-list {
|
|
8744
8745
|
min-width: min(18rem, calc(100vw - 1.5rem));
|
|
8745
8746
|
}
|
|
@@ -10990,17 +10991,61 @@
|
|
|
10990
10991
|
color: var(--color-base-content);
|
|
10991
10992
|
font-size: 0.875rem;
|
|
10992
10993
|
}
|
|
10993
|
-
.caps-composer
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
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;
|
|
11004
11049
|
}
|
|
11005
11050
|
.caps-alert[data-tone="error"] h3 {
|
|
11006
11051
|
color: var(--color-error);
|
|
@@ -12643,6 +12688,13 @@
|
|
|
12643
12688
|
padding: var(--spacing-md);
|
|
12644
12689
|
text-align: center;
|
|
12645
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
|
+
}
|
|
12646
12698
|
.caps-send-glyph {
|
|
12647
12699
|
display: inline-grid;
|
|
12648
12700
|
place-items: center;
|
|
@@ -13587,15 +13639,15 @@
|
|
|
13587
13639
|
isolation: var(--caps-button-outline-isolation, auto);
|
|
13588
13640
|
}
|
|
13589
13641
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]) {
|
|
13590
|
-
background-color: var(--caps-
|
|
13591
|
-
color: var(--caps-button-outline-ink,
|
|
13642
|
+
background-color: var(--caps-key-face);
|
|
13643
|
+
color: var(--caps-button-outline-ink, var(--color-base-content));
|
|
13592
13644
|
}
|
|
13593
13645
|
.caps-button.btn-outline:is(:disabled, [aria-disabled="true"]) {
|
|
13594
13646
|
background-color: var( --caps-button-outline-disabled-face, var(--color-base-200) );
|
|
13595
13647
|
}
|
|
13596
13648
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):hover {
|
|
13597
|
-
background-color: var( --caps-button-outline-hover-face, var(--caps-
|
|
13598
|
-
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)) );
|
|
13599
13651
|
}
|
|
13600
13652
|
.caps-button.btn-outline::before {
|
|
13601
13653
|
content: var(--caps-button-outline-inset-content, none);
|
|
@@ -13608,21 +13660,36 @@
|
|
|
13608
13660
|
.caps-button.btn-outline:hover::before {
|
|
13609
13661
|
background: var(--color-base-content);
|
|
13610
13662
|
}
|
|
13611
|
-
.caps-button.btn-outline {
|
|
13612
|
-
--
|
|
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);
|
|
13613
13669
|
@supports (color: color-mix(in lab, red, red)) {
|
|
13614
|
-
--
|
|
13615
|
-
|
|
13616
|
-
var(--
|
|
13617
|
-
|
|
13618
|
-
|
|
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
|
+
);
|
|
13619
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));
|
|
13620
13684
|
font-weight: var(--caps-button-weight, 500);
|
|
13621
|
-
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;
|
|
13622
13689
|
}
|
|
13623
13690
|
.caps-button.btn-outline:not(:disabled):not([aria-disabled="true"]):active {
|
|
13624
|
-
translate: 0 var(--caps-button-press, var(--
|
|
13625
|
-
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)) );
|
|
13626
13693
|
}
|
|
13627
13694
|
.caps-button.btn-ghost {
|
|
13628
13695
|
font-weight: 500;
|
|
@@ -13632,6 +13699,9 @@
|
|
|
13632
13699
|
}
|
|
13633
13700
|
.caps-button-filled {
|
|
13634
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;
|
|
13635
13705
|
--caps-button-filled-edge: var(
|
|
13636
13706
|
--caps-button-border-color,
|
|
13637
13707
|
var(--btn-bg)
|
|
@@ -13641,7 +13711,7 @@
|
|
|
13641
13711
|
--caps-button-border-color,
|
|
13642
13712
|
color-mix(
|
|
13643
13713
|
in oklab,
|
|
13644
|
-
color-mix(in oklab, var(--btn-bg), black calc(var(--depth) *
|
|
13714
|
+
color-mix(in oklab, var(--btn-bg), black calc(var(--depth) * 6%)),
|
|
13645
13715
|
var(--stipe-linework)
|
|
13646
13716
|
calc((1 - var(--depth)) * var(--caps-button-linework, 100%))
|
|
13647
13717
|
)
|
|
@@ -13649,7 +13719,7 @@
|
|
|
13649
13719
|
}
|
|
13650
13720
|
--caps-button-filled-drop: var(
|
|
13651
13721
|
--caps-button-drop,
|
|
13652
|
-
|
|
13722
|
+
var(--caps-key-rest-shadow)
|
|
13653
13723
|
);
|
|
13654
13724
|
background-color: var(--btn-bg);
|
|
13655
13725
|
color: var(--btn-fg);
|
|
@@ -13692,9 +13762,9 @@
|
|
|
13692
13762
|
@supports (color: color-mix(in lab, red, red)) {
|
|
13693
13763
|
--btn-bg: color-mix(in oklab, var(--btn-color) 92%, black);
|
|
13694
13764
|
}
|
|
13695
|
-
translate: 0 var(--caps-button-press, var(--
|
|
13765
|
+
translate: 0 var(--caps-button-press, var(--caps-key-lip));
|
|
13696
13766
|
scale: var(--stipe-press-scale);
|
|
13697
|
-
box-shadow: var(
|
|
13767
|
+
box-shadow: var(--caps-button-drop-active, var(--caps-key-pressed-shadow));
|
|
13698
13768
|
transition-duration: var(--stipe-motion-squish);
|
|
13699
13769
|
}
|
|
13700
13770
|
.caps-button.caps-button-menubar {
|
|
@@ -14675,7 +14745,7 @@
|
|
|
14675
14745
|
scale: none;
|
|
14676
14746
|
}
|
|
14677
14747
|
.caps-button-filled:not(:disabled):not([aria-disabled="true"]):active {
|
|
14678
|
-
translate: var(--caps-button-press-reduced,
|
|
14748
|
+
translate: var(--caps-button-press-reduced, 0 var(--caps-key-lip));
|
|
14679
14749
|
}
|
|
14680
14750
|
.caps-gooey-motion, .caps-gooey-motion::before, .caps-switch, .caps-radio, .caps-checkbox, .caps-checkbox-track, .caps-input, .caps-status-bead {
|
|
14681
14751
|
animation: none !important;
|
|
@@ -15860,7 +15930,7 @@
|
|
|
15860
15930
|
}
|
|
15861
15931
|
}
|
|
15862
15932
|
@media (prefers-reduced-motion: reduce) {
|
|
15863
|
-
.caps-keycap, .caps-keycap-button {
|
|
15933
|
+
.caps-keycap, .caps-keycap-button, .caps-button.btn-outline {
|
|
15864
15934
|
transition: none;
|
|
15865
15935
|
}
|
|
15866
15936
|
}
|
|
@@ -15931,7 +16001,21 @@
|
|
|
15931
16001
|
height: 0.625rem;
|
|
15932
16002
|
}
|
|
15933
16003
|
.caps-keycap-button {
|
|
15934
|
-
--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;
|
|
15935
16019
|
display: flex;
|
|
15936
16020
|
width: var(--caps-keycap-width, 100%);
|
|
15937
16021
|
min-width: var(--caps-keycap-min-width, 0);
|
|
@@ -15940,7 +16024,10 @@
|
|
|
15940
16024
|
justify-content: var(--caps-keycap-justify, space-between);
|
|
15941
16025
|
gap: var(--spacing-sm);
|
|
15942
16026
|
padding: var(--caps-keycap-padding, var(--spacing-xs) var(--spacing-sm));
|
|
15943
|
-
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
|
+
}
|
|
15944
16031
|
font-size: var(--caps-keycap-font-size, inherit);
|
|
15945
16032
|
font-weight: var( --caps-keycap-context-weight, var(--caps-keycap-font-weight, inherit) );
|
|
15946
16033
|
border-radius: var(--radius-field);
|
|
@@ -15949,12 +16036,28 @@
|
|
|
15949
16036
|
var(--caps-keycap-face, var(--color-base-200))
|
|
15950
16037
|
);
|
|
15951
16038
|
background-color: var(--caps-keycap-painted-face);
|
|
15952
|
-
--caps-keycap-default-image:
|
|
15953
|
-
|
|
15954
|
-
|
|
16039
|
+
--caps-keycap-default-image: linear-gradient(
|
|
16040
|
+
to bottom,
|
|
16041
|
+
var(--caps-keycap-painted-face),
|
|
16042
|
+
var(--caps-keycap-painted-face)
|
|
16043
|
+
);
|
|
15955
16044
|
@supports (color: color-mix(in lab, red, red)) {
|
|
15956
|
-
|
|
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
|
+
);
|
|
15957
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) );
|
|
15958
16061
|
color: var(--caps-keycap-ink, var(--color-base-content));
|
|
15959
16062
|
text-align: left;
|
|
15960
16063
|
cursor: pointer;
|
|
@@ -15963,8 +16066,8 @@
|
|
|
15963
16066
|
transition-timing-function: ease-out;
|
|
15964
16067
|
}
|
|
15965
16068
|
.caps-keycap-button:active {
|
|
15966
|
-
translate: 0 var(--caps-keycap-context-travel, var(--caps-
|
|
15967
|
-
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) );
|
|
15968
16071
|
}
|
|
15969
16072
|
.caps-keycap-button:focus-visible {
|
|
15970
16073
|
outline: 2px solid var(--caps-keycap-focus-ink, var(--color-base-content));
|
|
@@ -16011,6 +16114,28 @@
|
|
|
16011
16114
|
--caps-keycap-ink: var(--color-neutral-content);
|
|
16012
16115
|
--caps-keycap-edge: var(--color-neutral);
|
|
16013
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
|
+
}
|
|
16014
16139
|
.caps-keycap-button[data-tone="destructive"]:is(:hover, :focus-visible) {
|
|
16015
16140
|
--caps-keycap-ink: var(--color-error);
|
|
16016
16141
|
--caps-keycap-edge: var(--color-error);
|
|
@@ -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);
|