mirror of
https://github.com/Caho1/open-claude-ui.git
synced 2026-11-07 10:26:02 -10:00
feat(cds): attachment view transitions, alpha single-expression, official corroboration
Completes the remaining P1/P2 items from the v3 audit plus the
attachment-reorder animation found in the 6.10 survey.
Attachment shared-element transitions (new, ported verbatim):
The client animates composer attachment add/remove/reorder through
View Transitions. Three layers ported from MainWindowPage:
- container [data-cds-composer-attachments] opacity/transform
transitions (.2s cubic-bezier(.215,.61,.355,1))
- per-card [data-cds-attachment]: view-transition-class +
--cds-attachment-vt-name set per card via the id prop; RTL
transform-origin through the official :lang list
- :active-view-transition-type(cds-attachments) routing: root gets
zero animation, only the attachment group animates (.18s)
New-Chromium path behind @supports (match-element +
view-transition-group: contain + group-children overflow), old path
via per-card names. @starting-style gives entering cards
scale(.92) translateY(4px); removals animate the old snapshot with
cds-attachment-out (scale .5, .13s cubic-bezier(.55,.055,.675,.19))
via ::view-transition-old:only-child. AttachmentCard exports
withAttachmentTransition() wrapping document.startViewTransition.
Alpha ramp single expression:
--cds-alpha-* now reads hsl(from var(--cds-neutral-900) ...) so the
light/dark flip happens through the neutral ramp, matching the
official vanilla tokens. The dark-mode block's white hardcodes are
deleted; they disagreed with the light percentages by 1/2/5 points
(0.09/0.18/0.30 vs 10/20/35%).
fill-secondary is white at 10% in light mode:
var(--cds-alpha-2) (black 10%) -> hsl(0 0% 100% / 0.1) with hover at
var(--cds-alpha-1), matching the live reading rgba(255,255,255,0.1).
Official corroboration recorded in AGENTS.md:
The frontend-design skill bundled with the client states #D97757 is
"Anthropic's own Claude-interaction accent". The Brand Clay Palette
section now cites it, documents the brand-vs-accent role split
(clay for Claude-initiated actions, blue #184f95/#86b6ef for
accent), and the focus-shadow fact (blue ring + glow, verified
live).
Gate: check:tokens passes (14 keyframes incl. cds-attachment-out with
its consumer), typecheck passes, and the 8-point regression suite in
headless Chrome is all green.
This commit is contained in:
1 parent
d7dc040b8f
commit
e56d40f893
3 files changed
+127
-14
No files matched your search
@@ -63,7 +63,9 @@ All tokens are defined in [`code-examples/cds-theme.css`](code-examples/cds-them
|
||||
### Brand Clay Palette
|
||||
* `--brand`: `#c6613f` (Official desktop brand primary color)
|
||||
* `--cds-clay`: `#d97757` (Light / pulse indicator) / `#cc6b49` (Dark)
|
||||
* Focus shadow: `var(--cds-focus-shadow)` (`inset 0 0 0 1px var(--cds-surface-1), 0 0 0 1.5px var(--cds-clay), 0 0 6px 1px rgba(217, 119, 87, 0.25)`)
|
||||
* **官方佐证 (v3 取证)**:Claude 桌面端 3.6556.2 内置的官方 `frontend-design` skill 原文确认 #D97757 为 "Anthropic's own Claude-interaction accent"(见 `docs/v3/06` §6.10.4),与 REA 运行时实测一致
|
||||
* 角色区分 (运行时实证 3.6556.2):`brand`=陶土(Claude 发起的动作,如发送/生成);`accent`=**蓝色**(`--cds-text-accent: #184f95`、`--cds-border-accent: #86b6ef`)——二者是不同角色,不可互换
|
||||
* Focus shadow: `var(--cds-focus-shadow)` (`inset 0 0 0 1px var(--cds-page-bg), 0 0 0 1px var(--cds-fill-accent), 0 0 6px 1px var(--cds-bg-accent)`, 即蓝色 ring + 蓝色 glow,运行时实证)
|
||||
|
||||
### Geometric Corner Radii
|
||||
* `var(--cds-radius)`: `8px` (Base component radius)
|
||||
|
||||
@@ -15,6 +15,26 @@
|
||||
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* 附件集合变更的 View Transition 包装 (REA v6.8.0 运行时实证 3.6556.2)。
|
||||
*
|
||||
* 官方在附件增删/重排时用 document.startViewTransition() 包裹 DOM 更新,
|
||||
* 配合 cds-theme.css 中的 view-transition-name/match-element 规则,
|
||||
* 使被增删的卡片产生共享元素动画。
|
||||
*
|
||||
* 用法:
|
||||
* const updateAttachments = withAttachmentTransition(() => setFiles(next));
|
||||
*
|
||||
* 不支持 startViewTransition 的环境(旧浏览器)直接同步执行更新。
|
||||
*/
|
||||
export function withAttachmentTransition(update: () => void): void {
|
||||
if (typeof document !== "undefined" && typeof (document as any).startViewTransition === "function") {
|
||||
(document as any).startViewTransition(update);
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
export type FileCategory = "code" | "pdf" | "image" | "text" | "archive" | "general";
|
||||
|
||||
export interface AttachmentCardProps {
|
||||
@@ -52,6 +72,7 @@ function truncateFileName(fileName: string, maxLen: number = 18): { head: string
|
||||
}
|
||||
|
||||
export function AttachmentCard({
|
||||
id,
|
||||
name,
|
||||
size,
|
||||
type,
|
||||
@@ -96,7 +117,10 @@ export function AttachmentCard({
|
||||
className={`group/tile relative inline-flex items-center gap-2 px-2.5 py-1 rounded-[10px] bg-surface-2 border border-neutral-200/80 dark:border-neutral-700/80 shadow-field select-none transition-all ${
|
||||
uploading ? "animate-pulse opacity-75" : "hover:border-neutral-300 dark:hover:border-neutral-600"
|
||||
} ${className}`}
|
||||
style={{ animation: "file-thumbnail-grow-in 250ms cubic-bezier(.165, .84, .44, 1)" }}
|
||||
style={{
|
||||
animation: "file-thumbnail-grow-in 250ms cubic-bezier(.165, .84, .44, 1)",
|
||||
...(id != null ? { "--cds-attachment-vt-name": `attachment-${id}` } as React.CSSProperties : {}),
|
||||
}}
|
||||
>
|
||||
<span className={`px-1 py-0.5 rounded ${badgeStyle.bg} ${badgeStyle.text} font-mono text-[9px] font-bold shrink-0 tracking-wide`}>
|
||||
{inferredBadge}
|
||||
@@ -135,6 +159,7 @@ export function AttachmentCard({
|
||||
} ${className}`}
|
||||
style={{
|
||||
animation: "file-thumbnail-grow-in 250ms cubic-bezier(.165, .84, .44, 1)",
|
||||
...(id != null ? { "--cds-attachment-vt-name": `attachment-${id}` } as React.CSSProperties : {}),
|
||||
}}
|
||||
>
|
||||
{/* 顶部徽标与文件类型 */}
|
||||
|
||||
+98
-12
@@ -94,11 +94,14 @@
|
||||
--cds-surface-popover: var(--cds-gray-0); /* #ffffff 悬浮 Popover 表面 */
|
||||
|
||||
/* 4. Alpha 透明度与边框色系 */
|
||||
--cds-alpha-0: hsla(0, 0%, 8%, 0);
|
||||
--cds-alpha-1: hsla(0, 0%, 8%, 0.05); /* Ghost hover, skeleton */
|
||||
--cds-alpha-2: hsla(0, 0%, 8%, 0.10); /* Standard border */
|
||||
--cds-alpha-3: hsla(0, 0%, 8%, 0.20); /* Border strong */
|
||||
--cds-alpha-4: hsla(0, 0%, 8%, 0.35); /* Heavy divider */
|
||||
/* 4. 透明度阶梯 (Alpha Ramps) —— 单表达式自动翻转 (官方 vanilla 实证)
|
||||
neutral-900 在暗色下解析为 gray-0(白),整个阶梯自动变白,
|
||||
无需暗色块覆盖(原来双块硬编码的 0.09/0.18/0.30 与官方 10/20/35% 有偏差) */
|
||||
--cds-alpha-0: hsl(from var(--cds-neutral-900) h s l / 0%);
|
||||
--cds-alpha-1: hsl(from var(--cds-neutral-900) h s l / 5%); /* Ghost hover, skeleton */
|
||||
--cds-alpha-2: hsl(from var(--cds-neutral-900) h s l / 10%); /* Standard border */
|
||||
--cds-alpha-3: hsl(from var(--cds-neutral-900) h s l / 20%); /* Border strong */
|
||||
--cds-alpha-4: hsl(from var(--cds-neutral-900) h s l / 35%); /* Heavy divider */
|
||||
|
||||
--cds-border: var(--cds-alpha-2);
|
||||
--cds-border-strong: var(--cds-alpha-3);
|
||||
@@ -158,8 +161,8 @@
|
||||
dark : text-primary=#f0efec(gray-50) / secondary=#c3c2b7(gray-200) / muted=#898781(gray-400 不变) */
|
||||
--cds-fill-ghost-hover: var(--cds-alpha-1);
|
||||
--cds-fill-ghost-selected: var(--cds-alpha-2);
|
||||
--cds-fill-secondary: var(--cds-alpha-2);
|
||||
--cds-fill-secondary-hover: rgba(21, 21, 21, 0.14);
|
||||
--cds-fill-secondary: hsl(0 0% 100% / 0.1); /* 官方实证:浅色为白 10% */
|
||||
--cds-fill-secondary-hover: var(--cds-alpha-1);
|
||||
|
||||
--cds-text-primary: var(--cds-neutral-900); /* #0b0b0b */
|
||||
--cds-text-secondary: var(--cds-neutral-600); /* #52514e */
|
||||
@@ -400,11 +403,8 @@
|
||||
--cds-role-accent-fill: var(--cds-role-accent-450);
|
||||
--cds-fill-accent: var(--cds-role-accent-fill);
|
||||
|
||||
--cds-alpha-0: hsla(0, 0%, 100%, 0);
|
||||
--cds-alpha-1: hsla(0, 0%, 100%, 0.05);
|
||||
--cds-alpha-2: hsla(0, 0%, 100%, 0.10);
|
||||
--cds-alpha-3: hsla(0, 0%, 100%, 0.20);
|
||||
--cds-alpha-4: hsla(0, 0%, 100%, 0.35);
|
||||
/* alpha 阶梯不再需要暗色覆盖::root 的单表达式随 neutral-900 翻转
|
||||
(此前这里的白色硬编码与浅色块的百分比不一致,已删除) */
|
||||
|
||||
/* 主题阶梯暗色翻转 (Theme Ramp Inversion)
|
||||
运行时实证 (3.6556.2):neutral-N 在暗色下 = gray-(900-N),因此
|
||||
@@ -592,6 +592,83 @@
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 8.5 附件重排 View Transitions (REA v6.8.0 + 运行时实证 3.6556.2)
|
||||
附件卡增删/重排的共享元素过渡。三层结构:
|
||||
1. 容器 [data-cds-composer-attachments]:整体 opacity/transform 过渡
|
||||
2. 每张卡 [data-cds-attachment]:注册 view-transition-name + class
|
||||
3. 旧 Chromium 回退::active-view-transition-type 分发 root 动画
|
||||
官方原文逐字移植(含新特性 @supports 探测 view-transition-group:contain)。
|
||||
JS 侧需在 document.startViewTransition() 中更新 DOM,并给每张卡
|
||||
--cds-attachment-vt-name: attachment-<id>(唯一名)。 */
|
||||
.cds-root [data-cds-composer-attachments]:has([data-cds-attachment]) {
|
||||
transition: opacity .2s cubic-bezier(.215, .61, .355, 1),
|
||||
transform .2s cubic-bezier(.215, .61, .355, 1);
|
||||
}
|
||||
|
||||
.cds-root [data-cds-composer-attachments] [data-cds-attachment] {
|
||||
--cds-attachment-vt-name: none;
|
||||
transform-origin: 0;
|
||||
view-transition-class: cds-attachment;
|
||||
transition: transform .18s cubic-bezier(.215, .61, .355, 1),
|
||||
opacity .18s cubic-bezier(.215, .61, .355, 1);
|
||||
}
|
||||
|
||||
/* RTL 侧 transform-origin 镜像(官方 :lang 列表) */
|
||||
.cds-root [data-cds-composer-attachments] [data-cds-attachment]:where(
|
||||
:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),
|
||||
:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),
|
||||
[dir=rtl] *
|
||||
) {
|
||||
transform-origin: 100%;
|
||||
}
|
||||
|
||||
/* 过渡期间才启用命名(避免常驻占用 painting 资源) */
|
||||
:root:active-view-transition-type(cds-attachments)
|
||||
[data-cds-composer-attachments] [data-cds-attachment] {
|
||||
view-transition-name: var(--cds-attachment-vt-name);
|
||||
}
|
||||
|
||||
/* Chromium 141+ 路径:match-element 自动跟随元素几何,无需逐卡命名 */
|
||||
@supports selector(::view-transition-group-children(.x)) and (view-transition-group: contain) {
|
||||
:root:active-view-transition-type(cds-attachments)
|
||||
[data-cds-composer-attachments-clip][data-cds-attachments-clipping] {
|
||||
view-transition-name: match-element;
|
||||
view-transition-class: cds-attachments-clip;
|
||||
view-transition-group: contain;
|
||||
}
|
||||
:root:active-view-transition-type(cds-attachments)::view-transition-group-children(.cds-attachments-clip) {
|
||||
overflow: clip visible;
|
||||
}
|
||||
}
|
||||
|
||||
/* 入场初始态:新附件从下方缩放浮入 */
|
||||
@starting-style {
|
||||
.cds-root [data-cds-composer-attachments]:has([data-cds-attachment]:not([data-cds-attachment-restored])) {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
.cds-root [data-cds-composer-attachments] [data-cds-attachment]:not([data-cds-attachment-restored]) {
|
||||
opacity: 0;
|
||||
transform: scale(.92) translateY(4px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 过渡路由:root 零动画,只让附件组动 */
|
||||
:root:active-view-transition-type(cds-attachments) {
|
||||
view-transition-name: none;
|
||||
}
|
||||
:root:active-view-transition-type(cds-attachments)::view-transition-group(root) {
|
||||
animation-duration: 0s;
|
||||
}
|
||||
:root:active-view-transition-type(cds-attachments)::view-transition-group(.cds-attachment) {
|
||||
animation-duration: .18s;
|
||||
animation-timing-function: cubic-bezier(.215, .61, .355, 1);
|
||||
}
|
||||
/* 移除的旧快照:缩至一半并淡出(官方实证:仅当该附件无新快照时触发) */
|
||||
:root:active-view-transition-type(cds-attachments)::view-transition-old(.cds-attachment):only-child {
|
||||
animation: .13s cubic-bezier(.55, .055, .675, .19) forwards cds-attachment-out;
|
||||
}
|
||||
|
||||
/* 9. 陶土呼吸指示灯 (REA 取证:3s ease-in-out) */
|
||||
@keyframes dframe-clay-breathe {
|
||||
0%, 100% { filter: brightness(0.96); transform: scale(0.86); }
|
||||
@@ -616,6 +693,15 @@
|
||||
100% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* 11.5 附件移除退场 (官方实证:to { opacity:0; scale(.5) },
|
||||
用于 View Transition 旧快照的 exit 动画) */
|
||||
@keyframes cds-attachment-out {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* 12. 按钮动态物理阻尼按压与回弹 (REA 取证:.cds-btn-squish) */
|
||||
.cds-btn-squish {
|
||||
--cds-btn-press-sx: 0.975;
|
||||
|
||||
Reference in new issue
Block a user