@candor-ui/react 2.2.0 → 2.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 +3 -3
- package/dist/adapters/use-ag-ui.d.ts +1 -1
- package/dist/adapters/use-candor-chat.d.ts +1 -1
- package/dist/adapters/use-claude-agent.d.ts +1 -1
- package/dist/blocks/copy-field.js +4 -1
- package/dist/blocks/degraded-panel.js +3 -1
- package/dist/blocks/locked.js +23 -2
- package/dist/blocks/relative-time.js +1 -1
- package/dist/blocks/status-dot.d.ts +14 -3
- package/dist/blocks/status-dot.js +17 -22
- package/dist/blocks/step-list.d.ts +2 -1
- package/dist/blocks/step-list.js +19 -27
- package/dist/blocks/streaming-text.js +3 -1
- package/dist/blocks/surface.d.ts +1 -1
- package/dist/chat/attachment-preview.d.ts +6 -1
- package/dist/chat/attachment-preview.js +9 -3
- package/dist/chat/card-registry.js +6 -4
- package/dist/chat/citation.js +3 -3
- package/dist/chat/composer.js +19 -53
- package/dist/chat/context-meter.d.ts +5 -0
- package/dist/chat/context-meter.js +15 -1
- package/dist/chat/entity-chip.d.ts +3 -1
- package/dist/chat/entity-chip.js +2 -2
- package/dist/chat/folded-turns.js +3 -1
- package/dist/chat/message-actions.js +5 -2
- package/dist/chat/message.js +9 -5
- package/dist/chat/payload.js +1 -1
- package/dist/chat/permission-prompt.d.ts +5 -0
- package/dist/chat/permission-prompt.js +36 -2
- package/dist/chat/prompt-suggestions.js +4 -1
- package/dist/chat/reasoning.js +6 -1
- package/dist/chat/thread.js +37 -9
- package/dist/chat/tool-call.js +6 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/lib/motion.d.ts +466 -0
- package/dist/lib/motion.js +380 -0
- package/dist/lib/sound.d.ts +44 -0
- package/dist/lib/sound.js +63 -0
- package/dist/styles.css +562 -45
- package/dist/ui/accordion.d.ts +23 -0
- package/dist/ui/accordion.js +45 -0
- package/dist/ui/alert.d.ts +22 -0
- package/dist/ui/alert.js +59 -0
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/button.js +5 -3
- package/dist/ui/checkbox.d.ts +14 -0
- package/dist/ui/checkbox.js +32 -0
- package/dist/ui/collapsible.d.ts +3 -2
- package/dist/ui/collapsible.js +6 -3
- package/dist/ui/label.d.ts +10 -0
- package/dist/ui/label.js +16 -0
- package/dist/ui/popover.d.ts +1 -1
- package/dist/ui/popover.js +4 -2
- package/dist/ui/progress.d.ts +29 -0
- package/dist/ui/progress.js +27 -0
- package/dist/ui/skeleton.d.ts +1 -1
- package/dist/ui/skeleton.js +5 -2
- package/dist/ui/slider.d.ts +28 -0
- package/dist/ui/slider.js +41 -0
- package/dist/ui/switch.d.ts +14 -0
- package/dist/ui/switch.js +23 -0
- package/dist/ui/tooltip.d.ts +1 -1
- package/dist/ui/tooltip.js +4 -2
- package/package.json +2 -3
package/dist/chat/thread.js
CHANGED
|
@@ -5,6 +5,23 @@ import { ArrowDown } from "lucide-react";
|
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
6
|
import { useStrings } from "../blocks/locale.js";
|
|
7
7
|
import { useVirtualList } from "../lib/use-virtual-list.js";
|
|
8
|
+
import { motion, useMotionAllowed } from "../lib/motion.js";
|
|
9
|
+
/** The smooth scroll to the latest message. */
|
|
10
|
+
const scrollMotion = motion("thread", "scroll", "move");
|
|
11
|
+
/** Jump to latest appearing while the reader is scrolled up: 2.2's 150 ms and 4 px rise. */
|
|
12
|
+
/** How long the animations running on `el` take in all, in milliseconds (the longest of duration × iteration count, capped at 6 s); undefined when it can't be read. */
|
|
13
|
+
function animationMs(el) {
|
|
14
|
+
if (!el || typeof getComputedStyle === "undefined")
|
|
15
|
+
return undefined;
|
|
16
|
+
const style = getComputedStyle(el);
|
|
17
|
+
const durations = style.animationDuration.split(",").map((t) => (t.trim().endsWith("ms") ? Number.parseFloat(t) : Number.parseFloat(t) * 1000));
|
|
18
|
+
const counts = style.animationIterationCount.split(",").map((c) => (c.trim() === "infinite" ? Number.POSITIVE_INFINITY : Number.parseFloat(c)));
|
|
19
|
+
const totals = durations.map((d, i) => d * (counts[i % counts.length] || 1)).filter((n) => Number.isFinite(n) || n === Number.POSITIVE_INFINITY);
|
|
20
|
+
return totals.length ? Math.min(6000, Math.max(...totals)) : undefined;
|
|
21
|
+
}
|
|
22
|
+
const jumpMotion = motion("thread", "jump", "arrive", { dur: 150 });
|
|
23
|
+
/** Its ring when a message arrives while the reader is scrolled up. */
|
|
24
|
+
const arrivalMotion = motion("thread", "arrival", "attention");
|
|
8
25
|
/** Thread's fixed words; override them under `thread` in LocaleProvider's `strings`. */
|
|
9
26
|
export const threadStrings = {
|
|
10
27
|
messages: "Messages",
|
|
@@ -37,19 +54,21 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
37
54
|
const pinnedRef = React.useRef(!anchoring);
|
|
38
55
|
const [pinned, setPinned] = React.useState(!anchoring);
|
|
39
56
|
React.useImperativeHandle(scrollerRef, () => scroller.current, []);
|
|
57
|
+
// A smooth jump only when nothing asks for stillness: the OS setting (the calm level), data-motion="calm" or "none"
|
|
58
|
+
// around the thread, or a brand that turned the scroll off.
|
|
59
|
+
const smooth = useMotionAllowed(scroller, scrollMotion);
|
|
40
60
|
const toBottom = React.useCallback((behavior = "auto") => {
|
|
41
61
|
const el = scroller.current;
|
|
42
62
|
if (!el)
|
|
43
63
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
el.scrollTo({ top: el.scrollHeight, behavior: still ? "auto" : behavior });
|
|
47
|
-
}, []);
|
|
64
|
+
el.scrollTo({ top: el.scrollHeight, behavior: smooth() ? behavior : "auto" });
|
|
65
|
+
}, [smooth]);
|
|
48
66
|
// A new message while the reader is scrolled up: the Jump to latest control pulses once for each arrival, even when
|
|
49
67
|
// the messages sit inside a wrapper component or a fragment. A second arrival while it still pulses restarts it.
|
|
50
68
|
const [arrived, setArrived] = React.useState(false);
|
|
51
69
|
const arrivedRef = React.useRef(false);
|
|
52
70
|
const frame = React.useRef(0);
|
|
71
|
+
const jump = React.useRef(null);
|
|
53
72
|
React.useEffect(() => {
|
|
54
73
|
arrivedRef.current = arrived;
|
|
55
74
|
}, [arrived]);
|
|
@@ -98,8 +117,11 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
98
117
|
const had = new Set(before);
|
|
99
118
|
const survivors = before.filter((node) => node.isConnected);
|
|
100
119
|
const added = known.filter((node) => !had.has(node));
|
|
101
|
-
const
|
|
102
|
-
|
|
120
|
+
const arrivals = added.filter((node) => survivors.every((old) => old.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING));
|
|
121
|
+
// A message that arrived after the thread first drew rises in (Message reads data-arrived); loaded history is still.
|
|
122
|
+
for (const node of arrivals)
|
|
123
|
+
node.setAttribute("data-arrived", "");
|
|
124
|
+
if (arrivals.length)
|
|
103
125
|
arrive();
|
|
104
126
|
});
|
|
105
127
|
observer.observe(el, { childList: true, subtree: true });
|
|
@@ -108,7 +130,10 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
108
130
|
React.useEffect(() => {
|
|
109
131
|
if (!arrived)
|
|
110
132
|
return;
|
|
111
|
-
|
|
133
|
+
// The ring ends when its animation does (onAnimationEnd on the button). This timer is only the fallback for when
|
|
134
|
+
// no animation runs (motion off, or no Candor CSS), set from the duration and count the page resolved, so a brand's
|
|
135
|
+
// longer ring isn't cut short.
|
|
136
|
+
const timer = window.setTimeout(() => setArrived(false), (animationMs(jump.current) ?? 1200) + 100);
|
|
112
137
|
return () => window.clearTimeout(timer);
|
|
113
138
|
}, [arrived]);
|
|
114
139
|
// Open at the anchor once, when it first appears.
|
|
@@ -177,10 +202,13 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
177
202
|
}
|
|
178
203
|
: undefined, onBlur: virtualize ? (e) => !e.currentTarget.contains(e.relatedTarget) && setFocused(null) : undefined, className: "h-full scroll-pb-14 overflow-y-auto overscroll-contain", children: _jsx("div", { ref: content, role: "log", "aria-label": t("messages"), className: cn("mx-auto flex w-full max-w-(--cd-thread-width) flex-col gap-7 px-4 py-6", contentClassName), children: !hasMessages ? (empty) : virtualize ? (_jsx(_Fragment, { children: list.rows.map((row) => "spacer" in row ? (_jsx("div", { "aria-hidden": true, style: { height: row.spacer } }, row.key)) : (_jsx("div", { ref: list.measure, "data-virtual-key": String(keyOf(row.index)), className: "flex flex-col", children: items[row.index] }, keyOf(row.index)))) })) : (children) }) }), !pinned && hasMessages && (
|
|
179
204
|
// The entrance is the wrapper's, so the pulse on the button never replays it.
|
|
180
|
-
_jsx("span", { className: "absolute bottom-3 left-1/2 z-10 -translate-x-1/2
|
|
205
|
+
_jsx("span", { style: jumpMotion, className: "absolute bottom-3 left-1/2 z-10 -translate-x-1/2 cd-arrive", children: _jsxs("button", { type: "button", ref: jump, "data-slot": "thread-jump", onAnimationEnd: (e) => {
|
|
206
|
+
if (e.target === e.currentTarget && e.animationName.startsWith("cd-"))
|
|
207
|
+
setArrived(false);
|
|
208
|
+
}, "data-arrived": arrived ? "" : undefined, onClick: () => {
|
|
181
209
|
pinnedRef.current = true;
|
|
182
210
|
setPinned(true);
|
|
183
211
|
toBottom("smooth");
|
|
184
|
-
}, className: cn("inline-flex items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float [--cd-attention-color:var(--primary)]", arrived && "
|
|
212
|
+
}, style: arrivalMotion, className: cn("inline-flex items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float [--cd-attention-color:var(--primary)]", arrived && "cd-attention"), children: [_jsx(ArrowDown, { "aria-hidden": true, className: "size-3" }), " ", t("jumpToLatest")] }) })), fade && _jsx("div", { "aria-hidden": true, "data-slot": "thread-fade", className: "pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-background to-transparent" })] }), footer && _jsx("div", { className: "mx-auto w-full max-w-(--cd-thread-width) px-4 pb-4", children: footer })] }));
|
|
185
213
|
}
|
|
186
214
|
export { Thread };
|
package/dist/chat/tool-call.js
CHANGED
|
@@ -9,6 +9,10 @@ import { useStatusLabel } from "../lib/status.js";
|
|
|
9
9
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
|
|
10
10
|
import { Payload } from "./payload.js";
|
|
11
11
|
import { useStrings } from "../blocks/locale.js";
|
|
12
|
+
import { motion } from "../lib/motion.js";
|
|
13
|
+
/** A call's detail opening, and a group of calls. */
|
|
14
|
+
const openMotion = motion("tool-call", "open", "reveal");
|
|
15
|
+
const groupMotion = motion("tool-call", "group-open", "reveal");
|
|
12
16
|
/** ToolCall's fixed words; override them under `toolCall` in LocaleProvider's `strings`. */
|
|
13
17
|
export const toolCallStrings = {
|
|
14
18
|
input: "Input",
|
|
@@ -35,7 +39,7 @@ function ToolCall({ call, className, }) {
|
|
|
35
39
|
const failed = call.status === "failed";
|
|
36
40
|
const [open, setOpen] = React.useState(failed);
|
|
37
41
|
const hasDetail = Boolean(call.input || call.output || call.error || call.approval?.note || call.approval?.originalInput);
|
|
38
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none
|
|
42
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none cd-tint enabled:hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx(StepMark, { status: call.status }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [call.status === "running" ? _jsx(ShimmerLabel, { className: "font-normal", children: call.title }) : call.title, _jsxs("span", { className: "sr-only", children: [" \u2014 ", statusLabel(call.status)] })] }), call.approval?.originalInput !== undefined && (_jsx("span", { "data-slot": "tool-call-edited", className: "shrink-0 rounded-xs bg-warning-weak px-1 text-2xs text-warning-text", children: t("editedBeforeRun") })), call.approval?.by === "automatic" && (_jsx("span", { "data-slot": "tool-call-approval", className: "shrink-0 rounded-xs bg-muted px-1 text-2xs text-muted-foreground", children: t("approvedAutomatically") })), _jsx("code", { className: "hidden shrink-0 font-mono text-2xs text-faint-foreground sm:inline", children: call.tool }), call.duration && _jsx("span", { className: "shrink-0 text-xs tabular-nums text-faint-foreground", children: call.duration }), hasDetail && (_jsx(ChevronRight, { "aria-hidden": true, style: openMotion, className: "rtl:-scale-x-100 size-3.5 shrink-0 text-faint-foreground cd-reveal-turn group-data-[state=open]/tool:[transform:rotate(90deg)]" }))] }), _jsx(CollapsibleContent, { style: openMotion, children: _jsxs("div", { className: "mt-1 mb-2 ms-[1.375rem] flex flex-col gap-2 border-s ps-3.5", children: [call.error && (_jsx("p", { className: "rounded-md bg-destructive-weak px-2.5 py-1.5 text-xs text-destructive-text", children: call.error })), call.approval?.note && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [call.approval.by === "automatic" ? t("approvedAutomatically") : t("approvedByPerson"), ": ", call.approval.note] })), call.approval?.originalInput !== undefined && _jsx("p", { className: "text-xs text-muted-foreground", children: t("editedNote") }), call.input && _jsx(Payload, { language: "json", title: t("input"), text: call.input }), call.approval?.originalInput !== undefined && _jsx(Payload, { language: "json", title: t("proposed"), text: call.approval.originalInput }), call.output && _jsx(Payload, { language: call.outputLanguage ?? "text", title: t("output"), text: call.output })] }) })] }));
|
|
39
43
|
}
|
|
40
44
|
/** A run of tool calls, with a count when it's long enough to fold. */
|
|
41
45
|
function ToolCallGroup({ calls, className, }) {
|
|
@@ -47,6 +51,6 @@ function ToolCallGroup({ calls, className, }) {
|
|
|
47
51
|
if (calls.length <= 2) {
|
|
48
52
|
return (_jsx("div", { "data-slot": "tool-call-group", className: cn("flex flex-col", className), children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }));
|
|
49
53
|
}
|
|
50
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call-group", className: cn("group/tools rounded-lg border bg-card", className), children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 px-3 py-2 text-start text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80 rounded-lg", children: [_jsx(Wrench, { "aria-hidden": true, className: "size-3.5" }), _jsxs("span", { className: "flex-1", children: [t("calls", { count: calls.length }), done > 0 && ` · ${t("done", { count: done })}`, failed > 0 && _jsxs("span", { className: "text-destructive-text", children: [" \u00B7 ", t("failed", { count: failed })] }), running && ` · ${t("working")}`] }), _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5
|
|
54
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call-group", className: cn("group/tools rounded-lg border bg-card", className), children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 px-3 py-2 text-start text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80 rounded-lg", children: [_jsx(Wrench, { "aria-hidden": true, className: "size-3.5" }), _jsxs("span", { className: "flex-1", children: [t("calls", { count: calls.length }), done > 0 && ` · ${t("done", { count: done })}`, failed > 0 && _jsxs("span", { className: "text-destructive-text", children: [" \u00B7 ", t("failed", { count: failed })] }), running && ` · ${t("working")}`] }), _jsx(ChevronRight, { "aria-hidden": true, style: groupMotion, className: "rtl:-scale-x-100 size-3.5 cd-reveal-turn group-data-[state=open]/tools:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { style: groupMotion, children: _jsx("div", { className: "flex flex-col border-t px-1.5 py-1.5", children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }) })] }));
|
|
51
55
|
}
|
|
52
56
|
export { ToolCall, ToolCallGroup };
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,15 @@ export * from "./lib/render-prop.js";
|
|
|
43
43
|
export * from "./chat/ai-disclosure.js";
|
|
44
44
|
export * from "./chat/permission-prompt.js";
|
|
45
45
|
export * from "./lib/mcp-input.js";
|
|
46
|
+
export * from "./lib/motion.js";
|
|
47
|
+
export * from "./ui/label.js";
|
|
48
|
+
export * from "./ui/checkbox.js";
|
|
49
|
+
export * from "./ui/switch.js";
|
|
50
|
+
export * from "./ui/slider.js";
|
|
51
|
+
export * from "./ui/progress.js";
|
|
52
|
+
export * from "./ui/alert.js";
|
|
53
|
+
export * from "./ui/accordion.js";
|
|
54
|
+
export * from "./lib/sound.js";
|
|
46
55
|
export * from "./lib/utils.js";
|
|
47
56
|
export { ToolCall } from "./chat/tool-call.js";
|
|
48
57
|
export type { ToolCall as ToolCallData } from "./lib/chat.js";
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,15 @@ export * from "./lib/render-prop.js";
|
|
|
43
43
|
export * from "./chat/ai-disclosure.js";
|
|
44
44
|
export * from "./chat/permission-prompt.js";
|
|
45
45
|
export * from "./lib/mcp-input.js";
|
|
46
|
+
export * from "./lib/motion.js";
|
|
47
|
+
export * from "./ui/label.js";
|
|
48
|
+
export * from "./ui/checkbox.js";
|
|
49
|
+
export * from "./ui/switch.js";
|
|
50
|
+
export * from "./ui/slider.js";
|
|
51
|
+
export * from "./ui/progress.js";
|
|
52
|
+
export * from "./ui/alert.js";
|
|
53
|
+
export * from "./ui/accordion.js";
|
|
54
|
+
export * from "./lib/sound.js";
|
|
46
55
|
export * from "./lib/utils.js";
|
|
47
56
|
export { ToolCall } from "./chat/tool-call.js";
|
|
48
57
|
export { Message } from "./chat/message.js";
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { RunStatus } from "./status.js";
|
|
3
|
+
/**
|
|
4
|
+
* Candor's motion layer, the part that runs in components. Motion is organised by fourteen families, one per kind of
|
|
5
|
+
* change; each component moment (copy-field's tick, the dialog opening) belongs to one family and can be tuned on its
|
|
6
|
+
* own by a brand file. The rule the layer keeps: **a theme tunes how a change looks, never when it happens**. The
|
|
7
|
+
* trigger stays in the component (a class added at an honest moment); this file only builds the chain of CSS
|
|
8
|
+
* variables the family utility in `candor.css` reads, and the two hooks that decide "seen settling" and "arrived after
|
|
9
|
+
* the first draw".
|
|
10
|
+
*
|
|
11
|
+
* Precedence, highest first: the OS's reduced-motion setting (the calm level), then the app's `data-motion`
|
|
12
|
+
* (`none`, `calm`, `subtle`, `expressive`), then the brand file's `--cd-motion-*` values, then the defaults below.
|
|
13
|
+
*
|
|
14
|
+
* `motion()` is pure and safe to call at module level, so server components can use it; the hooks need a client
|
|
15
|
+
* component.
|
|
16
|
+
*/
|
|
17
|
+
/** The fourteen families: one per kind of change a person can see. */
|
|
18
|
+
export type MotionFamily = "reveal" | "arrive" | "pop" | "mark" | "fill" | "value" | "move" | "overlay" | "press" | "tint" | "refuse" | "attention" | "live" | "clock";
|
|
19
|
+
/** A motion level: 0 off (the end state appears, events still fire), 1 subtle, 2 expressive. */
|
|
20
|
+
export type MotionLevel = 0 | 1 | 2;
|
|
21
|
+
/** The easings a family may allow. Spring overshoots, so only spatial families accept it. */
|
|
22
|
+
export type MotionEase = "standard" | "inout" | "spring" | "linear";
|
|
23
|
+
/** The CSS each easing name stands for, as a brand file writes it. */
|
|
24
|
+
export declare const motionEases: Readonly<Record<MotionEase, string>>;
|
|
25
|
+
/** A family's amplitude: what its expressive level moves by. */
|
|
26
|
+
export type MotionAmplitude = {
|
|
27
|
+
/** The token suffix: `--cd-motion-<family>-<name>`. */
|
|
28
|
+
readonly name: "distance" | "scale" | "count";
|
|
29
|
+
/** The value at the expressive level, as CSS. */
|
|
30
|
+
readonly default: string;
|
|
31
|
+
/** The lowest value `theme check` accepts. */
|
|
32
|
+
readonly min: number;
|
|
33
|
+
/** The highest value `theme check` accepts. */
|
|
34
|
+
readonly max: number;
|
|
35
|
+
/** The unit `min` and `max` are in ("" for a plain number). */
|
|
36
|
+
readonly unit: "px" | "" | "%";
|
|
37
|
+
/** How much of the amplitude the subtle level uses: 0 none, 1 all of it. */
|
|
38
|
+
readonly subtle: number;
|
|
39
|
+
};
|
|
40
|
+
/** A duration with the range `theme check` accepts, in milliseconds. */
|
|
41
|
+
export type MotionDuration = {
|
|
42
|
+
readonly default: number;
|
|
43
|
+
readonly min: number;
|
|
44
|
+
readonly max: number;
|
|
45
|
+
};
|
|
46
|
+
/** Everything Candor knows about one family. `check:motion` checks `candor.css` against these. */
|
|
47
|
+
export type MotionFamilySpec = {
|
|
48
|
+
/** What the family reports, in words, for the docs and the theme builder. */
|
|
49
|
+
readonly reports: string;
|
|
50
|
+
/** Moves things in space (scale, translate, size). Spring easing is accepted only here. */
|
|
51
|
+
readonly spatial: boolean;
|
|
52
|
+
/** The information itself (a hold, a deadline): a brand can't turn it below subtle. */
|
|
53
|
+
readonly essential: boolean;
|
|
54
|
+
/** How long one play takes; `null` when the clock decides. */
|
|
55
|
+
readonly dur: MotionDuration | null;
|
|
56
|
+
/** How long leaving takes, for families with an out moment (`-out-dur`). */
|
|
57
|
+
readonly outDur: MotionDuration | null;
|
|
58
|
+
/** The easing used when nothing sets one. */
|
|
59
|
+
readonly ease: MotionEase;
|
|
60
|
+
/** The easings a brand may choose. */
|
|
61
|
+
readonly eases: readonly MotionEase[];
|
|
62
|
+
/** The amplitude token, when the family has one. */
|
|
63
|
+
readonly amp: MotionAmplitude | null;
|
|
64
|
+
/** The closed set of variants: how the change is drawn, never when. The first is the default. */
|
|
65
|
+
readonly variants: readonly [string, ...string[]];
|
|
66
|
+
/** What the calm level (reduced motion) does with it, in words. */
|
|
67
|
+
readonly calm: string;
|
|
68
|
+
};
|
|
69
|
+
/** The family defaults. Durations, easings and amplitudes are 2.2's, so an app with no brand file looks the same. */
|
|
70
|
+
export declare const motionFamilies: {
|
|
71
|
+
readonly reveal: {
|
|
72
|
+
readonly reports: "Content opened or closed to its measured size; a chevron turns.";
|
|
73
|
+
readonly spatial: true;
|
|
74
|
+
readonly essential: false;
|
|
75
|
+
readonly dur: {
|
|
76
|
+
readonly default: 200;
|
|
77
|
+
readonly min: 150;
|
|
78
|
+
readonly max: 300;
|
|
79
|
+
};
|
|
80
|
+
readonly outDur: null;
|
|
81
|
+
readonly ease: "standard";
|
|
82
|
+
readonly eases: readonly ["standard", "inout"];
|
|
83
|
+
readonly amp: null;
|
|
84
|
+
readonly variants: readonly ["size", "fade"];
|
|
85
|
+
readonly calm: "Snaps to its measured size.";
|
|
86
|
+
};
|
|
87
|
+
readonly arrive: {
|
|
88
|
+
readonly reports: "Something new, added after the first draw.";
|
|
89
|
+
readonly spatial: true;
|
|
90
|
+
readonly essential: false;
|
|
91
|
+
readonly dur: {
|
|
92
|
+
readonly default: 240;
|
|
93
|
+
readonly min: 150;
|
|
94
|
+
readonly max: 300;
|
|
95
|
+
};
|
|
96
|
+
readonly outDur: null;
|
|
97
|
+
readonly ease: "standard";
|
|
98
|
+
readonly eases: readonly ["standard", "spring"];
|
|
99
|
+
readonly amp: {
|
|
100
|
+
readonly name: "distance";
|
|
101
|
+
readonly default: "8px";
|
|
102
|
+
readonly min: 0;
|
|
103
|
+
readonly max: 16;
|
|
104
|
+
readonly unit: "px";
|
|
105
|
+
readonly subtle: 0.5;
|
|
106
|
+
};
|
|
107
|
+
readonly variants: readonly ["rise", "fade", "pop"];
|
|
108
|
+
readonly calm: "Fades in, up to 150 ms.";
|
|
109
|
+
};
|
|
110
|
+
readonly pop: {
|
|
111
|
+
readonly reports: "Added, removed, or a small mark appearing.";
|
|
112
|
+
readonly spatial: true;
|
|
113
|
+
readonly essential: false;
|
|
114
|
+
readonly dur: {
|
|
115
|
+
readonly default: 200;
|
|
116
|
+
readonly min: 100;
|
|
117
|
+
readonly max: 300;
|
|
118
|
+
};
|
|
119
|
+
readonly outDur: {
|
|
120
|
+
readonly default: 120;
|
|
121
|
+
readonly min: 80;
|
|
122
|
+
readonly max: 300;
|
|
123
|
+
};
|
|
124
|
+
readonly ease: "standard";
|
|
125
|
+
readonly eases: readonly ["standard", "spring"];
|
|
126
|
+
readonly amp: {
|
|
127
|
+
readonly name: "scale";
|
|
128
|
+
readonly default: "0.85";
|
|
129
|
+
readonly min: 0.8;
|
|
130
|
+
readonly max: 1;
|
|
131
|
+
readonly unit: "";
|
|
132
|
+
readonly subtle: 0;
|
|
133
|
+
};
|
|
134
|
+
readonly variants: readonly ["scale", "fade"];
|
|
135
|
+
readonly calm: "Fades, up to 150 ms.";
|
|
136
|
+
};
|
|
137
|
+
readonly mark: {
|
|
138
|
+
readonly reports: "A tick or cross drawn when a result settles.";
|
|
139
|
+
readonly spatial: true;
|
|
140
|
+
readonly essential: false;
|
|
141
|
+
readonly dur: {
|
|
142
|
+
readonly default: 360;
|
|
143
|
+
readonly min: 200;
|
|
144
|
+
readonly max: 500;
|
|
145
|
+
};
|
|
146
|
+
readonly outDur: null;
|
|
147
|
+
readonly ease: "standard";
|
|
148
|
+
readonly eases: readonly ["standard"];
|
|
149
|
+
readonly amp: null;
|
|
150
|
+
readonly variants: readonly ["draw", "pop", "drop"];
|
|
151
|
+
readonly calm: "The finished mark fades in, up to 150 ms.";
|
|
152
|
+
};
|
|
153
|
+
readonly fill: {
|
|
154
|
+
readonly reports: "A bar or ring moving between measured values.";
|
|
155
|
+
readonly spatial: true;
|
|
156
|
+
readonly essential: false;
|
|
157
|
+
readonly dur: {
|
|
158
|
+
readonly default: 360;
|
|
159
|
+
readonly min: 200;
|
|
160
|
+
readonly max: 600;
|
|
161
|
+
};
|
|
162
|
+
readonly outDur: null;
|
|
163
|
+
readonly ease: "standard";
|
|
164
|
+
readonly eases: readonly ["standard", "inout"];
|
|
165
|
+
readonly amp: null;
|
|
166
|
+
readonly variants: readonly ["grow"];
|
|
167
|
+
readonly calm: "Snaps to the new value; its colour may fade.";
|
|
168
|
+
};
|
|
169
|
+
readonly value: {
|
|
170
|
+
readonly reports: "A number or word changed in place.";
|
|
171
|
+
readonly spatial: false;
|
|
172
|
+
readonly essential: false;
|
|
173
|
+
readonly dur: {
|
|
174
|
+
readonly default: 120;
|
|
175
|
+
readonly min: 80;
|
|
176
|
+
readonly max: 200;
|
|
177
|
+
};
|
|
178
|
+
readonly outDur: null;
|
|
179
|
+
readonly ease: "standard";
|
|
180
|
+
readonly eases: readonly ["standard"];
|
|
181
|
+
readonly amp: {
|
|
182
|
+
readonly name: "distance";
|
|
183
|
+
readonly default: "2px";
|
|
184
|
+
readonly min: 0;
|
|
185
|
+
readonly max: 4;
|
|
186
|
+
readonly unit: "px";
|
|
187
|
+
readonly subtle: 0;
|
|
188
|
+
};
|
|
189
|
+
readonly variants: readonly ["fade", "slide"];
|
|
190
|
+
readonly calm: "Crossfades, up to 150 ms.";
|
|
191
|
+
};
|
|
192
|
+
readonly move: {
|
|
193
|
+
readonly reports: "A thing changed place: a reorder, a tab indicator, a toast restacking, a scroll.";
|
|
194
|
+
readonly spatial: true;
|
|
195
|
+
readonly essential: false;
|
|
196
|
+
readonly dur: {
|
|
197
|
+
readonly default: 200;
|
|
198
|
+
readonly min: 150;
|
|
199
|
+
readonly max: 300;
|
|
200
|
+
};
|
|
201
|
+
readonly outDur: null;
|
|
202
|
+
readonly ease: "standard";
|
|
203
|
+
readonly eases: readonly ["standard", "spring"];
|
|
204
|
+
readonly amp: null;
|
|
205
|
+
readonly variants: readonly ["slide"];
|
|
206
|
+
readonly calm: "Jumps to its new place.";
|
|
207
|
+
};
|
|
208
|
+
readonly overlay: {
|
|
209
|
+
readonly reports: "A dialog, sheet, popover, menu or tooltip opened or closed.";
|
|
210
|
+
readonly spatial: true;
|
|
211
|
+
readonly essential: false;
|
|
212
|
+
readonly dur: {
|
|
213
|
+
readonly default: 150;
|
|
214
|
+
readonly min: 100;
|
|
215
|
+
readonly max: 500;
|
|
216
|
+
};
|
|
217
|
+
readonly outDur: {
|
|
218
|
+
readonly default: 150;
|
|
219
|
+
readonly min: 80;
|
|
220
|
+
readonly max: 300;
|
|
221
|
+
};
|
|
222
|
+
readonly ease: "standard";
|
|
223
|
+
readonly eases: readonly ["standard", "inout", "spring"];
|
|
224
|
+
readonly amp: {
|
|
225
|
+
readonly name: "scale";
|
|
226
|
+
readonly default: "0.95";
|
|
227
|
+
readonly min: 0.9;
|
|
228
|
+
readonly max: 1;
|
|
229
|
+
readonly unit: "";
|
|
230
|
+
readonly subtle: 1;
|
|
231
|
+
};
|
|
232
|
+
readonly variants: readonly ["scale", "slide", "fade"];
|
|
233
|
+
readonly calm: "Fades, up to 150 ms.";
|
|
234
|
+
};
|
|
235
|
+
readonly press: {
|
|
236
|
+
readonly reports: "A press registered.";
|
|
237
|
+
readonly spatial: true;
|
|
238
|
+
readonly essential: false;
|
|
239
|
+
readonly dur: {
|
|
240
|
+
readonly default: 150;
|
|
241
|
+
readonly min: 80;
|
|
242
|
+
readonly max: 200;
|
|
243
|
+
};
|
|
244
|
+
readonly outDur: null;
|
|
245
|
+
readonly ease: "standard";
|
|
246
|
+
readonly eases: readonly ["standard"];
|
|
247
|
+
readonly amp: {
|
|
248
|
+
readonly name: "scale";
|
|
249
|
+
readonly default: "0.96";
|
|
250
|
+
readonly min: 0.9;
|
|
251
|
+
readonly max: 1;
|
|
252
|
+
readonly unit: "";
|
|
253
|
+
readonly subtle: 0.5;
|
|
254
|
+
};
|
|
255
|
+
readonly variants: readonly ["scale"];
|
|
256
|
+
readonly calm: "Doesn't scale; the colour still answers.";
|
|
257
|
+
};
|
|
258
|
+
readonly tint: {
|
|
259
|
+
readonly reports: "Hover, focus, selected, a drop target: a colour or opacity change.";
|
|
260
|
+
readonly spatial: false;
|
|
261
|
+
readonly essential: false;
|
|
262
|
+
readonly dur: {
|
|
263
|
+
readonly default: 150;
|
|
264
|
+
readonly min: 0;
|
|
265
|
+
readonly max: 200;
|
|
266
|
+
};
|
|
267
|
+
readonly outDur: null;
|
|
268
|
+
readonly ease: "standard";
|
|
269
|
+
readonly eases: readonly ["standard", "inout"];
|
|
270
|
+
readonly amp: null;
|
|
271
|
+
readonly variants: readonly ["fade"];
|
|
272
|
+
readonly calm: "Colour and opacity only, up to 150 ms.";
|
|
273
|
+
};
|
|
274
|
+
readonly refuse: {
|
|
275
|
+
readonly reports: "A locked control was pressed: the reason is shown, and under expressive it shakes.";
|
|
276
|
+
readonly spatial: true;
|
|
277
|
+
readonly essential: false;
|
|
278
|
+
readonly dur: {
|
|
279
|
+
readonly default: 360;
|
|
280
|
+
readonly min: 200;
|
|
281
|
+
readonly max: 500;
|
|
282
|
+
};
|
|
283
|
+
readonly outDur: null;
|
|
284
|
+
readonly ease: "standard";
|
|
285
|
+
readonly eases: readonly ["standard"];
|
|
286
|
+
readonly amp: {
|
|
287
|
+
readonly name: "distance";
|
|
288
|
+
readonly default: "3px";
|
|
289
|
+
readonly min: 0;
|
|
290
|
+
readonly max: 6;
|
|
291
|
+
readonly unit: "px";
|
|
292
|
+
readonly subtle: 0;
|
|
293
|
+
};
|
|
294
|
+
readonly variants: readonly ["shake"];
|
|
295
|
+
readonly calm: "No shake; the reason's colour still changes.";
|
|
296
|
+
};
|
|
297
|
+
readonly attention: {
|
|
298
|
+
readonly reports: "Something new waits on the person: a ring that grows and fades.";
|
|
299
|
+
readonly spatial: false;
|
|
300
|
+
readonly essential: false;
|
|
301
|
+
readonly dur: {
|
|
302
|
+
readonly default: 900;
|
|
303
|
+
readonly min: 600;
|
|
304
|
+
readonly max: 1600;
|
|
305
|
+
};
|
|
306
|
+
readonly outDur: null;
|
|
307
|
+
readonly ease: "standard";
|
|
308
|
+
readonly eases: readonly ["standard"];
|
|
309
|
+
readonly amp: {
|
|
310
|
+
readonly name: "count";
|
|
311
|
+
readonly default: "1";
|
|
312
|
+
readonly min: 1;
|
|
313
|
+
readonly max: 3;
|
|
314
|
+
readonly unit: "";
|
|
315
|
+
readonly subtle: 1;
|
|
316
|
+
};
|
|
317
|
+
readonly variants: readonly ["ring"];
|
|
318
|
+
readonly calm: "One ring that fades without growing, up to 150 ms.";
|
|
319
|
+
};
|
|
320
|
+
readonly live: {
|
|
321
|
+
readonly reports: "Work that is being heard from: a dot's pulse, a caret, a spinner, a shimmer, a skeleton.";
|
|
322
|
+
readonly spatial: false;
|
|
323
|
+
readonly essential: false;
|
|
324
|
+
readonly dur: {
|
|
325
|
+
readonly default: 1600;
|
|
326
|
+
readonly min: 1000;
|
|
327
|
+
readonly max: 3000;
|
|
328
|
+
};
|
|
329
|
+
readonly outDur: null;
|
|
330
|
+
readonly ease: "standard";
|
|
331
|
+
readonly eases: readonly ["standard", "inout", "linear"];
|
|
332
|
+
readonly amp: null;
|
|
333
|
+
readonly variants: readonly ["pulse"];
|
|
334
|
+
readonly calm: "Still, with its word.";
|
|
335
|
+
};
|
|
336
|
+
readonly clock: {
|
|
337
|
+
readonly reports: "Real time passing: a hold, a deadline, a replay. Painted from the clock.";
|
|
338
|
+
readonly spatial: true;
|
|
339
|
+
readonly essential: true;
|
|
340
|
+
readonly dur: null;
|
|
341
|
+
readonly outDur: null;
|
|
342
|
+
readonly ease: "linear";
|
|
343
|
+
readonly eases: readonly ["linear"];
|
|
344
|
+
readonly amp: null;
|
|
345
|
+
readonly variants: readonly ["glide"];
|
|
346
|
+
readonly calm: "Steps instead of gliding; never off.";
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
/** The family names, in the order the docs and the theme builder list them. */
|
|
350
|
+
export declare const motionFamilyNames: MotionFamily[];
|
|
351
|
+
/** A family's variants, by name: `MotionVariant<"mark">` is `"draw" | "pop" | "drop"`. */
|
|
352
|
+
export type MotionVariant<F extends MotionFamily> = (typeof motionFamilies)[F]["variants"][number];
|
|
353
|
+
/**
|
|
354
|
+
* True when a family has one way of drawing its change (fill, move, press, tint, refuse, attention, live and clock).
|
|
355
|
+
* Its single variant is fixed: there is no `-variant` token to set, and `motion()` writes none, because the family's
|
|
356
|
+
* utility has nothing to choose between. The docs mark such a family "(fixed)" instead of listing a value that does
|
|
357
|
+
* nothing. A later release that adds a second word turns the token on without a new name.
|
|
358
|
+
*/
|
|
359
|
+
export declare const motionVariantIsFixed: (family: MotionFamily) => boolean;
|
|
360
|
+
/**
|
|
361
|
+
* The CSS value a variant is written as. CSS can only choose a drawing by keyframe name, so a variant's value is its
|
|
362
|
+
* keyframe's: the word with Candor's prefix, which keeps an app's own `@keyframes rise` from taking over.
|
|
363
|
+
*/
|
|
364
|
+
export declare const motionVariantValue: (variant: string) => string;
|
|
365
|
+
/** What a moment may set as its own default, where it differs from its family's. */
|
|
366
|
+
export type MomentDefaults<F extends MotionFamily> = {
|
|
367
|
+
/** The moment's duration, in milliseconds, within its family's range. */
|
|
368
|
+
dur?: number | undefined;
|
|
369
|
+
/** How long leaving takes, for families with an out moment. */
|
|
370
|
+
outDur?: number | undefined;
|
|
371
|
+
/** The moment's easing, from its family's allowed set. */
|
|
372
|
+
ease?: (typeof motionFamilies)[F]["eases"][number] | undefined;
|
|
373
|
+
/** The amplitude at the expressive level, as CSS (`"0.9"`, `"0.25rem"`, `"3"`). */
|
|
374
|
+
amp?: string | undefined;
|
|
375
|
+
/** The moment's variant, from its family's set. */
|
|
376
|
+
variant?: MotionVariant<F> | undefined;
|
|
377
|
+
};
|
|
378
|
+
/** The moment a chain belongs to, kept on the style object (not enumerable, so it never reaches the DOM). */
|
|
379
|
+
export type MotionMoment = {
|
|
380
|
+
readonly item: string;
|
|
381
|
+
readonly moment: string;
|
|
382
|
+
readonly family: MotionFamily;
|
|
383
|
+
};
|
|
384
|
+
declare const momentKey: unique symbol;
|
|
385
|
+
/**
|
|
386
|
+
* A moment's chain: CSS variables to spread into the moving element's `style`. Each resolves the moment's own token,
|
|
387
|
+
* then its family's, then the default, on that element, so a `data-motion` container anywhere above it still applies.
|
|
388
|
+
*/
|
|
389
|
+
export type MotionStyle = React.CSSProperties & {
|
|
390
|
+
readonly [momentKey]?: MotionMoment | undefined;
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* Builds a moment's chain. Call it once at module level and spread the result into the element's `style`, beside the
|
|
394
|
+
* family utility that the component adds at the honest moment:
|
|
395
|
+
*
|
|
396
|
+
* ```tsx
|
|
397
|
+
* const tick = motion("copy-field", "tick", "pop")
|
|
398
|
+
* <Check style={tick} className={cn(copied && "cd-pop")} />
|
|
399
|
+
* ```
|
|
400
|
+
*
|
|
401
|
+
* The brand reaches the moment with `--cd-motion-copy-field-tick` (its level), `-dur`, `-ease`, the family's amplitude
|
|
402
|
+
* suffix and `-variant`; anything it doesn't set falls back to the family (`--cd-motion-pop-*`), then to `defaults`,
|
|
403
|
+
* then to Candor's. Two families on one element compose: `style={{ ...press, ...tint }}`.
|
|
404
|
+
*/
|
|
405
|
+
export declare function motion<F extends MotionFamily>(item: string, moment: string, family: F, defaults?: MomentDefaults<F>): MotionStyle;
|
|
406
|
+
/** The moment a chain was built for, or `undefined` for a plain style object. */
|
|
407
|
+
export declare const motionMomentOf: (style: MotionStyle | undefined) => MotionMoment | undefined;
|
|
408
|
+
/** How `useSettled` tells a finished value from an unfinished one; Candor's run statuses when left out. */
|
|
409
|
+
export type SettledTest<T> = {
|
|
410
|
+
/** Reached an outcome worth showing settling (done or failed). */
|
|
411
|
+
settled: (value: T) => boolean;
|
|
412
|
+
/** Over in any way, including ones that don't animate (cancelled, skipped); `settled` when left out. */
|
|
413
|
+
over?: ((value: T) => boolean) | undefined;
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* How many times `value` reached a settled outcome from an unfinished one while the component was on screen. Zero
|
|
417
|
+
* for something that mounted already settled, so its mark is drawn still: motion reports a change that was seen,
|
|
418
|
+
* never one that happened before the reader arrived. Key the moving element on the count to replay it.
|
|
419
|
+
*/
|
|
420
|
+
export declare function useSettled(value: RunStatus): number;
|
|
421
|
+
export declare function useSettled<T>(value: T, test: SettledTest<T>): number;
|
|
422
|
+
/**
|
|
423
|
+
* Which keys were added after the component first drew. Keys present at the first render answer false, so a list
|
|
424
|
+
* that loads with fifty entries draws them still; a key added later answers true until it leaves (and true again if
|
|
425
|
+
* it comes back).
|
|
426
|
+
*/
|
|
427
|
+
export declare function useArrived(keys: readonly string[]): (key: string) => boolean;
|
|
428
|
+
/**
|
|
429
|
+
* Items that just left, kept as inert ghosts for the length of their out moment. Each ghost goes when `left(item)` is
|
|
430
|
+
* called (from its `onAnimationEnd`), or after `fallbackMs` when nothing animated. The default, 600 ms, is longer than
|
|
431
|
+
* the longest out moment a brand can set (pop's 300 ms at the slowest speed, 1.5×, is 450 ms). An item that comes back while its
|
|
432
|
+
* ghost is still leaving takes the ghost's place.
|
|
433
|
+
*/
|
|
434
|
+
export declare function useLeaving<T>(items: readonly T[], keyOf: (item: T) => string, fallbackMs?: number): {
|
|
435
|
+
leaving: readonly T[];
|
|
436
|
+
left: (item: T) => void;
|
|
437
|
+
};
|
|
438
|
+
/** What the person allows here: everything, the calm level (reduced motion), or nothing. */
|
|
439
|
+
export type MotionMode = "full" | "calm" | "none";
|
|
440
|
+
/**
|
|
441
|
+
* The person's setting where `el` is: `none` under `data-motion="none"`, `calm` under the OS's reduced-motion setting
|
|
442
|
+
* or `data-motion="calm"`, else `full`. `none` or `calm` anywhere above wins (the same reach as the CSS variants), the
|
|
443
|
+
* OS setting can't be raised by the app, and values that aren't Candor's (Radix writes its own `data-motion` on menu
|
|
444
|
+
* panels) are ignored.
|
|
445
|
+
*/
|
|
446
|
+
export declare function motionMode(el: Element | null | undefined): MotionMode;
|
|
447
|
+
/**
|
|
448
|
+
* `motionMode` for a component that has to react to it: the person's setting where `ref`'s element is, read after
|
|
449
|
+
* mount and again whenever the app changes `data-motion` or the OS setting changes. It starts as `full` for the first
|
|
450
|
+
* render, since there is nothing to read until the element exists. Use it for behaviour (a timer that must stop),
|
|
451
|
+
* not for styling, which the CSS variants already answer.
|
|
452
|
+
*/
|
|
453
|
+
export declare function useMotionMode(ref: React.RefObject<Element | null>): MotionMode;
|
|
454
|
+
/**
|
|
455
|
+
* The level a moment resolves to on `el`, read from its computed style: the moment's token, its family's, the preset,
|
|
456
|
+
* then capped by `data-motion`. A page without Candor's CSS reads as subtle.
|
|
457
|
+
*/
|
|
458
|
+
export declare function motionLevel(el: Element | null | undefined, moment: MotionMoment | MotionFamily): MotionLevel;
|
|
459
|
+
/**
|
|
460
|
+
* For motion that CSS can't stop (smooth scrolling, the Web Animations API, a bar painted from the clock): a stable
|
|
461
|
+
* function that answers, at the moment of the motion, whether it may play on `ref`'s element. False under
|
|
462
|
+
* `data-motion="none"`, under the calm level (reduced motion), and when the moment's resolved level is 0. Pass the
|
|
463
|
+
* moment's chain from `motion()`, or a family name for the family's level.
|
|
464
|
+
*/
|
|
465
|
+
export declare function useMotionAllowed(ref: React.RefObject<Element | null>, moment: MotionStyle | MotionFamily): () => boolean;
|
|
466
|
+
export {};
|