From 0b5d9dae31b3004a354afc5519e4f07794471a9d Mon Sep 17 00:00:00 2001 From: Lan_zhijiang Date: Wed, 23 Sep 2026 21:03:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=9C=BA=E6=99=AF=E5=8C=96?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E3=80=81=E5=AF=B9=E8=AF=9D=E6=A1=86=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C=E7=8A=B6=E6=80=81=E4=B8=8E=E7=9B=B8=E5=AF=B9=E9=97=B4?= =?UTF-8?q?=E8=B7=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/contextual-loading-spacing.md | 5 ++ .changeset/dropdown-search-affordance.md | 5 ++ docs/design/composition.md | 12 ++++ packages/web/MIGRATION.md | 8 +++ packages/web/component-manifest.json | 5 ++ packages/web/docs/design/composition.md | 12 ++++ packages/web/skill.seed.json | 25 +++++--- packages/web/skills/ui-web/SKILL.md | 2 +- .../ui-web/references/common-mistakes.md | 4 ++ .../skills/ui-web/references/component-map.md | 1 + .../ui-web/references/components/InkButton.md | 2 +- .../ui-web/references/components/InkDialog.md | 6 +- .../references/components/InkLoading.md | 9 ++- .../references/components/InkSkeleton.md | 57 +++++++++++++++++++ .../ui-web/references/composition-recipes.md | 4 +- packages/web/src/components.d.ts | 1 + .../src/components/inkButton/inkButton.scss | 6 ++ .../web/src/components/inkButton/inkButton.ts | 4 ++ .../src/components/inkButton/inkButton.vue | 20 +++---- .../src/components/inkDialog/inkDialog.vue | 9 +-- .../components/inkDropdown/inkDropdown.scss | 20 +++++++ .../components/inkDropdown/inkDropdown.vue | 29 ++++++---- .../src/components/inkLoading/inkLoading.scss | 39 ++++++++++--- .../src/components/inkLoading/inkLoading.ts | 2 + .../src/components/inkLoading/inkLoading.vue | 27 +++++++-- .../components/inkSkeleton/inkSkeleton.scss | 18 ++++++ .../src/components/inkSkeleton/inkSkeleton.ts | 2 + .../components/inkSkeleton/inkSkeleton.vue | 9 +++ packages/web/src/public-components.ts | 4 ++ .../web/stories/controls/inkButton.story.md | 2 + .../web/stories/feedback/inkDialog.story.md | 6 +- .../web/stories/feedback/inkDialog.story.vue | 57 ++++++++++++++----- .../web/stories/feedback/inkLoading.story.md | 8 ++- .../web/stories/feedback/inkLoading.story.vue | 19 ++++++- .../web/stories/feedback/inkSkeleton.story.md | 7 +++ .../stories/feedback/inkSkeleton.story.vue | 16 ++++++ .../web/stories/forms/inkDropdown.story.md | 2 + packages/web/styles/tokens/_ref.scss | 10 ++-- scripts/build-agent-skills.ts | 2 +- scripts/build-tokens.md | 4 +- scripts/prepare-token-update.ts | 23 +++++++- tasks/dropdown-search/packet.md | 25 ++++++++ tokens/inkcre.tokens.json | 10 ++-- tokens/tokens.md | 2 + 44 files changed, 446 insertions(+), 94 deletions(-) create mode 100644 .changeset/contextual-loading-spacing.md create mode 100644 .changeset/dropdown-search-affordance.md create mode 100644 packages/web/skills/ui-web/references/components/InkSkeleton.md create mode 100644 packages/web/src/components/inkSkeleton/inkSkeleton.scss create mode 100644 packages/web/src/components/inkSkeleton/inkSkeleton.ts create mode 100644 packages/web/src/components/inkSkeleton/inkSkeleton.vue create mode 100644 packages/web/stories/feedback/inkSkeleton.story.md create mode 100644 packages/web/stories/feedback/inkSkeleton.story.vue create mode 100644 tasks/dropdown-search/packet.md diff --git a/.changeset/contextual-loading-spacing.md b/.changeset/contextual-loading-spacing.md new file mode 100644 index 0000000..4968576 --- /dev/null +++ b/.changeset/contextual-loading-spacing.md @@ -0,0 +1,5 @@ +--- +"@inkcre/ui-web": minor +--- + +新增 InkLoading 行内 spinner、可见 label 与 InkSkeleton,等待图形响应减少动态效果偏好。Dialog 保留提交期间交互锁,只有执行动作显示 loading;自定义 footer 通过槽状态显式绑定。五个 spacing Token 改为 rem,Figma 导入拒绝单位回退,并同步组件示例与消费者迁移说明。 diff --git a/.changeset/dropdown-search-affordance.md b/.changeset/dropdown-search-affordance.md new file mode 100644 index 0000000..e34221a --- /dev/null +++ b/.changeset/dropdown-search-affordance.md @@ -0,0 +1,5 @@ +--- +"@inkcre/ui-web": patch +--- + +为 Dropdown 搜索区增加内边距、前缀搜索图标和键盘焦点反馈,使空搜索框的用途可辨认。 diff --git a/docs/design/composition.md b/docs/design/composition.md index e6af3cb..380631f 100644 --- a/docs/design/composition.md +++ b/docs/design/composition.md @@ -12,6 +12,8 @@ 整理内容先于调整间距。重复的标题、复述同一状态的标签、没有帮助的说明和纯粹填空的图标应直接移除;需要保留的内容,再按当前任务决定出现的位置与时机。已有空间不要求被填满。 +默认契约本就应成立的承诺,无须再放入帮助入口。确有按需阅读价值的短说明优先使用原生展开区,并给入口明确名称;影响当前选择的限制、可操作错误和不可逆后果仍紧邻操作呈现。短提示不能成为触屏或键盘用户获取必要信息的唯一途径。 + 列表帮助识别和比较对象,详情承载进一步阅读,编辑态提供修改所需的信息。不要把三个层级的全部内容同时摆在列表里。有用但暂时次要的信息可放到展开区、详情或次级操作入口中,入口应可发现,并让人知道能在那里找到什么。若用户必须反复展开才能完成主要任务,就需要重新考虑哪些内容应直接出现。 界面随状态改变当前呈现的内容:保存期间保留输入与进度,失败后及时显示对应问题和恢复动作,无须同时陈列所有可能状态。影响当前判断的警告、校验错误和操作后果直接呈现;不能为了视觉简洁藏到容易错过的入口。 @@ -34,6 +36,8 @@ 主要提交应能被辨认为这次修改的完成动作,取消和危险操作与其保持可理解的区别。校验失败应说明需要修改什么;保存失败应保留已经输入的内容,让重试有连续的上下文。保存期间保留动作文字与可读配色,并防止重复执行。 +禁用表示当前不能操作,等待图标表示该动作正在执行,两者不能混用。保存期间仅保存按钮显示 loading;无法中止请求时取消按钮可禁用,但不显示执行动画。提交、导入和刷新等动作各自拥有等待状态,结束或失败后恢复相应操作。 + 固定业务流程与 schema 生成表单承担不同的数据责任。先决定用户如何理解和修改内容,再选择实现;具体组合从 Web 的 InkForm、内置字段、InkField 以及现有配方进入。 ## 浮层延续当前任务 @@ -44,6 +48,14 @@ 加载、空结果、失败与确认有不同含义,应分别表达。错误反馈不要抹掉键盘焦点,进行状态也不应伪装成普通不可用状态。 +## 按内容结构表达等待 + +已知列表、标题或属性行的结构时,使用少量骨架块保留阅读位置;结构未知的图谱、内容预览和独立内容区域使用三方块等待。字段选项读取、日志追加等紧凑状态使用行内 spinner 与必要的短说明。骨架不制造假的可点击按钮,宿主负责组合结构和外部留白。 + +刷新已有内容时保留内容和展开状态,在刷新动作或区域附近表达等待;失败后也保留已有内容,附上错误和重试。首次读取完成后才能判断空结果,失败不能继续显示加载。日志轮询间隔仅呈现静态更新说明,请求实际进行时才播放动画。 + +每个等待区域只有一次可访问状态播报,骨架与装饰图形不分别播报,不移动焦点。减少动态效果偏好下停止动画,保留状态文字和静态图形。只有真实可测的进度才显示百分比,不为等待增加全页遮罩或虚构进度。 + ## 容器变化时保留关系 宿主决定页面列数、外部留白和阅读区域,组件负责内部内容与操作布局。确定当前需要呈现的内容后,可用空间缩小时让相关内容换行或按既有阅读顺序排列;不要通过缩小正文、遮掉必要操作或裁切说明来维持双栏外观。 diff --git a/packages/web/MIGRATION.md b/packages/web/MIGRATION.md index caffce9..b75f765 100644 --- a/packages/web/MIGRATION.md +++ b/packages/web/MIGRATION.md @@ -1,3 +1,11 @@ +# 未发布:场景化加载、Dialog 动作与相对间距 + +InkLoading 默认仍为三个方块,用于内容区、图谱和预览等待。新增 `variant="spinner"` 与可见 `label`;没有可见标签时继续支持 aria-label。新增 InkSkeleton 单块骨架,用 class/style 定义具体宽高,整个区域由宿主提供一次加载说明。Loading、Skeleton 与 Button 等待图形响应 prefers-reduced-motion。 + +InkDialog 的 pending 继续锁住内部 InkButton、确认、取消、Escape 和遮罩,但不再让所有按钮显示执行动画。默认 Confirm 显式显示 loading,Cancel 只禁用。自定义 footer 将 `:is-loading` 传给正在执行的按钮;默认槽和 footer 槽均提供 cancel、confirm、isLoading。直接依赖内部字符串 `isLoading` 注入的代码应移除,改用公开 prop/slot。验证保存、失败恢复和 Promise 拒绝后重试,不能把取消显示为正在提交。 + +五个 ref.space 源值改为 rem:xs/sm/md/lg/xl 为 0.25/0.5/1/2/3.5rem。Token 路径、sys 引用与 Sass/Uno 用法不变,16px 根字号下等大,20px 时间距按比例放大;复核表单、按钮、Dropdown、Tabs、Dialog、Header 的换行。radius 和 ref.size 不变。Figma 的 dimension 提议须保留已有单位,单位变更改为显式仓库迁移。 + # 未发布:默认配色与阴影 浅深主题的主动作、普通表面与 Switch 采用中性灰配对;危险按钮底色保持灰红,需要注意的反馈文字使用较纯的语义色。覆盖层阴影缩小偏移与模糊范围。公开角色、组件 API 与调用方式保持不变,升级后重新构建即可获得默认值。 diff --git a/packages/web/component-manifest.json b/packages/web/component-manifest.json index aed5de2..e01437d 100644 --- a/packages/web/component-manifest.json +++ b/packages/web/component-manifest.json @@ -91,6 +91,11 @@ "source": "inkScrim", "category": "overlays" }, + { + "name": "InkSkeleton", + "source": "inkSkeleton", + "category": "feedback" + }, { "name": "InkSwitch", "source": "inkSwitch", diff --git a/packages/web/docs/design/composition.md b/packages/web/docs/design/composition.md index e6af3cb..380631f 100644 --- a/packages/web/docs/design/composition.md +++ b/packages/web/docs/design/composition.md @@ -12,6 +12,8 @@ 整理内容先于调整间距。重复的标题、复述同一状态的标签、没有帮助的说明和纯粹填空的图标应直接移除;需要保留的内容,再按当前任务决定出现的位置与时机。已有空间不要求被填满。 +默认契约本就应成立的承诺,无须再放入帮助入口。确有按需阅读价值的短说明优先使用原生展开区,并给入口明确名称;影响当前选择的限制、可操作错误和不可逆后果仍紧邻操作呈现。短提示不能成为触屏或键盘用户获取必要信息的唯一途径。 + 列表帮助识别和比较对象,详情承载进一步阅读,编辑态提供修改所需的信息。不要把三个层级的全部内容同时摆在列表里。有用但暂时次要的信息可放到展开区、详情或次级操作入口中,入口应可发现,并让人知道能在那里找到什么。若用户必须反复展开才能完成主要任务,就需要重新考虑哪些内容应直接出现。 界面随状态改变当前呈现的内容:保存期间保留输入与进度,失败后及时显示对应问题和恢复动作,无须同时陈列所有可能状态。影响当前判断的警告、校验错误和操作后果直接呈现;不能为了视觉简洁藏到容易错过的入口。 @@ -34,6 +36,8 @@ 主要提交应能被辨认为这次修改的完成动作,取消和危险操作与其保持可理解的区别。校验失败应说明需要修改什么;保存失败应保留已经输入的内容,让重试有连续的上下文。保存期间保留动作文字与可读配色,并防止重复执行。 +禁用表示当前不能操作,等待图标表示该动作正在执行,两者不能混用。保存期间仅保存按钮显示 loading;无法中止请求时取消按钮可禁用,但不显示执行动画。提交、导入和刷新等动作各自拥有等待状态,结束或失败后恢复相应操作。 + 固定业务流程与 schema 生成表单承担不同的数据责任。先决定用户如何理解和修改内容,再选择实现;具体组合从 Web 的 InkForm、内置字段、InkField 以及现有配方进入。 ## 浮层延续当前任务 @@ -44,6 +48,14 @@ 加载、空结果、失败与确认有不同含义,应分别表达。错误反馈不要抹掉键盘焦点,进行状态也不应伪装成普通不可用状态。 +## 按内容结构表达等待 + +已知列表、标题或属性行的结构时,使用少量骨架块保留阅读位置;结构未知的图谱、内容预览和独立内容区域使用三方块等待。字段选项读取、日志追加等紧凑状态使用行内 spinner 与必要的短说明。骨架不制造假的可点击按钮,宿主负责组合结构和外部留白。 + +刷新已有内容时保留内容和展开状态,在刷新动作或区域附近表达等待;失败后也保留已有内容,附上错误和重试。首次读取完成后才能判断空结果,失败不能继续显示加载。日志轮询间隔仅呈现静态更新说明,请求实际进行时才播放动画。 + +每个等待区域只有一次可访问状态播报,骨架与装饰图形不分别播报,不移动焦点。减少动态效果偏好下停止动画,保留状态文字和静态图形。只有真实可测的进度才显示百分比,不为等待增加全页遮罩或虚构进度。 + ## 容器变化时保留关系 宿主决定页面列数、外部留白和阅读区域,组件负责内部内容与操作布局。确定当前需要呈现的内容后,可用空间缩小时让相关内容换行或按既有阅读顺序排列;不要通过缩小正文、遮掉必要操作或裁切说明来维持双栏外观。 diff --git a/packages/web/skill.seed.json b/packages/web/skill.seed.json index bf7dd6f..d63075d 100644 --- a/packages/web/skill.seed.json +++ b/packages/web/skill.seed.json @@ -12,7 +12,7 @@ "JSON configuration editing": "InkJsonEditor", "Destructive confirmation": "InkDoubleCheck 提供确认弹层;InkDialog 支持异步等待和较复杂决策", "Custom or positioned overlay": "InkPopup; add InkScrim only when the composition does not already own one", - "Loading state": "InkLoading", + "Loading state": "InkLoading;已知结构首次读取使用 InkSkeleton", "Empty or error state": "InkPlaceholder", "Expandable media": "InkImage", "Paged collection": "InkPagination" @@ -73,7 +73,7 @@ "composeWith": ["InkDialog", "InkDoubleCheck", "InkHeader", "InkTooltip"], "apiCaveats": [ "type 控制 default/square 外形;nativeType 控制 button/submit/reset,默认 button。提交必须使用 nativeType=\"submit\"。", - "click 传出 MouseEvent;disabled、isLoading 或 Dialog 的 pending 禁用动作。图标按钮必须提供 aria-label。", + "click 传出 MouseEvent;disabled、isLoading 或 Dialog 的 pending 禁用动作,仅自身 isLoading 显示等待动画。图标按钮必须提供 aria-label。", "默认 theme=\"subtle\";主动作显式 primary。文字可换行,最小高度 md=36px、sm=24px;pending 保留标签与相邻 spinner,不使用遮盖标签的覆盖层。" ], "commonMistakes": [ @@ -113,7 +113,7 @@ "apiCaveats": [ "modelValue 为 boolean 或 Promise;也支持布尔模型配合 isLoading。只应用最新 Promise,拒绝发出 error。", "pending 阻止确认、取消、遮罩和 Escape;confirm 只发事件,取消发出 cancel 和 update:modelValue(false)。", - "title 提供名称;自定义 header 或无 title 时提供 aria-label/aria-labelledby。默认槽获得 cancel/confirm/isLoading。" + "title 提供名称;自定义 header 或无 title 时提供 aria-label/aria-labelledby。默认槽与 footer 槽获得 cancel/confirm/isLoading;自定义执行按钮显式绑定 isLoading,取消只禁用。" ], "commonMistakes": [ "Do not nest a second independently controlled scrim beneath the dialog.", @@ -292,16 +292,27 @@ ], "avoidWhen": [ "Content is empty or failed; use InkPlaceholder.", - "A static skeleton layout is required." + "A known content structure needs placeholders; use InkSkeleton." ], "composeWith": ["InkButton", "InkDialog", "InkImage"], "apiCaveats": [ - "提供 role=status 和默认 Loading 名称,可透传 aria-label;不管理请求和相邻按钮状态。" + "默认 blocks 用于内容区/预览,spinner 用于紧凑行内等待;size/density 沿用。label 同时作为可见说明及 status 的 aria-label,重复文字节点 aria-hidden;无 label 时透传 aria-label,默认 Loading。只提供一次 role=status;不管理请求和相邻按钮状态。", + "减少动态效果偏好下停止动画,保留图形与说明;已有内容刷新保留内容,失败后提供局部重试。" ], "commonMistakes": [ "Do not leave a loading indicator active after the operation reaches an empty or error state." ] }, + "InkSkeleton": { + "intent": ["skeleton", "known content placeholder", "initial loading"], + "preferWhen": ["首次读取的列表、标题或属性行已有明确结构。"], + "avoidWhen": ["结构未知的预览使用 InkLoading blocks;已有内容刷新保留内容。"], + "composeWith": ["InkLoading", "InkButton"], + "apiCaveats": [ + "单块默认宽 100%、高 1em,通过 class/style 组合尺寸;aria-hidden,宿主只为整个区域提供一次状态说明。响应浅深主题及 prefers-reduced-motion。" + ], + "commonMistakes": ["不要画假的可点击操作,不要在错误或空态保留骨架。"] + }, "InkPagination": { "intent": ["pagination", "previous and next navigation", "paged collection"], "preferWhen": [ @@ -548,9 +559,9 @@ { "name": "Collection Feedback", "intent": "Represent pending, empty, error, and paged collection states without conflating them.", - "components": ["InkLoading", "InkPlaceholder", "InkPagination", "InkButton"], + "components": ["InkLoading", "InkSkeleton", "InkPlaceholder", "InkPagination", "InkButton"], "steps": [ - "Show InkLoading only while a request is pending.", + "只在请求期间显示等待。已知结构首次加载用 InkSkeleton,内容预览用 InkLoading 默认 blocks,紧凑状态用 spinner。已有内容刷新及失败保留内容。", "After completion, choose a specific empty, filtered-empty, permission, or error InkPlaceholder.", "Render InkPagination only when a valid bounded page model exists.", "Offer a retry or recovery InkButton only when the action is available." diff --git a/packages/web/skills/ui-web/SKILL.md b/packages/web/skills/ui-web/SKILL.md index 895dc4b..6bbc5fa 100644 --- a/packages/web/skills/ui-web/SKILL.md +++ b/packages/web/skills/ui-web/SKILL.md @@ -32,7 +32,7 @@ matches a product task. - JSON configuration editing: InkJsonEditor - Destructive confirmation: InkDoubleCheck 提供确认弹层;InkDialog 支持异步等待和较复杂决策 - Custom or positioned overlay: InkPopup; add InkScrim only when the composition does not already own one -- Loading state: InkLoading +- Loading state: InkLoading;已知结构首次读取使用 InkSkeleton - Empty or error state: InkPlaceholder - Expandable media: InkImage - Paged collection: InkPagination diff --git a/packages/web/skills/ui-web/references/common-mistakes.md b/packages/web/skills/ui-web/references/common-mistakes.md index dd28a87..84f0365 100644 --- a/packages/web/skills/ui-web/references/common-mistakes.md +++ b/packages/web/skills/ui-web/references/common-mistakes.md @@ -91,6 +91,10 @@ - Do not render an orphan scrim without an overlay lifecycle. - Do not rely on visual dimming alone for modal accessibility. +## InkSkeleton + +- 不要画假的可点击操作,不要在错误或空态保留骨架。 + ## InkSwitch - Do not optimistically show a successful state when the async change can still fail. diff --git a/packages/web/skills/ui-web/references/component-map.md b/packages/web/skills/ui-web/references/component-map.md index 47b5ca6..4a232f6 100644 --- a/packages/web/skills/ui-web/references/component-map.md +++ b/packages/web/skills/ui-web/references/component-map.md @@ -20,6 +20,7 @@ component references relevant to the task. - [InkLoading](components/InkLoading.md): loading indicator; async progress feedback; pending state - [InkPlaceholder](components/InkPlaceholder.md): empty state; error state; unavailable content guidance - [InkPopup](components/InkPopup.md): positioned popup; low-level overlay surface; custom controlled overlay +- [InkSkeleton](components/InkSkeleton.md): skeleton; known content placeholder; initial loading - [InkTooltip](components/InkTooltip.md): contextual hint; hover explanation; supplementary label ## forms diff --git a/packages/web/skills/ui-web/references/components/InkButton.md b/packages/web/skills/ui-web/references/components/InkButton.md index f9956c4..bf60546 100644 --- a/packages/web/skills/ui-web/references/components/InkButton.md +++ b/packages/web/skills/ui-web/references/components/InkButton.md @@ -63,7 +63,7 @@ ## API Caveats - type 控制 default/square 外形;nativeType 控制 button/submit/reset,默认 button。提交必须使用 nativeType="submit"。 -- click 传出 MouseEvent;disabled、isLoading 或 Dialog 的 pending 禁用动作。图标按钮必须提供 aria-label。 +- click 传出 MouseEvent;disabled、isLoading 或 Dialog 的 pending 禁用动作,仅自身 isLoading 显示等待动画。图标按钮必须提供 aria-label。 - 默认 theme="subtle";主动作显式 primary。文字可换行,最小高度 md=36px、sm=24px;pending 保留标签与相邻 spinner,不使用遮盖标签的覆盖层。 ## Common Mistakes diff --git a/packages/web/skills/ui-web/references/components/InkDialog.md b/packages/web/skills/ui-web/references/components/InkDialog.md index 0d890b5..e6c3b17 100644 --- a/packages/web/skills/ui-web/references/components/InkDialog.md +++ b/packages/web/skills/ui-web/references/components/InkDialog.md @@ -58,17 +58,17 @@ - `header`: `{}` - `default`: `{ cancel: () => void; confirm: () => void; isLoading: boolean; }` -- `footer`: `{}` +- `footer`: `{ cancel: () => void; confirm: () => void; isLoading: boolean; }` - Public types: None -- Story variants: `Basic`, `with Custom Slots`, `Async`, `Without Cancel` +- Story variants: `Basic`, `Custom footer failure and retry`, `Async`, `Without Cancel` ## API Caveats - modelValue 为 boolean 或 Promise;也支持布尔模型配合 isLoading。只应用最新 Promise,拒绝发出 error。 - pending 阻止确认、取消、遮罩和 Escape;confirm 只发事件,取消发出 cancel 和 update:modelValue(false)。 -- title 提供名称;自定义 header 或无 title 时提供 aria-label/aria-labelledby。默认槽获得 cancel/confirm/isLoading。 +- title 提供名称;自定义 header 或无 title 时提供 aria-label/aria-labelledby。默认槽与 footer 槽获得 cancel/confirm/isLoading;自定义执行按钮显式绑定 isLoading,取消只禁用。 ## Common Mistakes diff --git a/packages/web/skills/ui-web/references/components/InkLoading.md b/packages/web/skills/ui-web/references/components/InkLoading.md index 32ea332..a04313b 100644 --- a/packages/web/skills/ui-web/references/components/InkLoading.md +++ b/packages/web/skills/ui-web/references/components/InkLoading.md @@ -16,7 +16,7 @@ ## Avoid When - Content is empty or failed; use InkPlaceholder. -- A static skeleton layout is required. +- A known content structure needs placeholders; use InkSkeleton. ## Compose With @@ -36,6 +36,8 @@ | 名称 | 类型 | 必需 | 默认表达式 | | --- | --- | --- | --- | +| `variant` | `undefined \| "blocks" \| "spinner"` | 否 | `"blocks"` | +| `label` | `undefined \| string` | 否 | `""` | | `size` | `undefined \| "md" \| "sm" \| "xs"` | 否 | `"md"` | | `density` | `undefined \| "md" \| "sm"` | 否 | `"md"` | @@ -48,12 +50,13 @@ - None. - Public types: None -- Story variants: `Basic` +- Story variants: `Content waiting`, `Inline spinner`, `Sizes and density` ## API Caveats -- 提供 role=status 和默认 Loading 名称,可透传 aria-label;不管理请求和相邻按钮状态。 +- 默认 blocks 用于内容区/预览,spinner 用于紧凑行内等待;size/density 沿用。label 同时作为可见说明及 status 的 aria-label,重复文字节点 aria-hidden;无 label 时透传 aria-label,默认 Loading。只提供一次 role=status;不管理请求和相邻按钮状态。 +- 减少动态效果偏好下停止动画,保留图形与说明;已有内容刷新保留内容,失败后提供局部重试。 ## Common Mistakes diff --git a/packages/web/skills/ui-web/references/components/InkSkeleton.md b/packages/web/skills/ui-web/references/components/InkSkeleton.md new file mode 100644 index 0000000..dbf42fe --- /dev/null +++ b/packages/web/skills/ui-web/references/components/InkSkeleton.md @@ -0,0 +1,57 @@ + + +# InkSkeleton + +## Intent + +- skeleton +- known content placeholder +- initial loading + +## Prefer When + +- 首次读取的列表、标题或属性行已有明确结构。 + +## Avoid When + +- 结构未知的预览使用 InkLoading blocks;已有内容刷新保留内容。 + +## Compose With + +`InkLoading`, `InkButton` + +## Public API Facts + +- Import: `import { InkSkeleton } from "@inkcre/ui-web";` + +### Models + +- None. + +### Props + +默认列是声明的值或表达式;默认工厂按组件实例求值。组件内的显示回退见 API Caveats。 + +| 名称 | 类型 | 必需 | 默认表达式 | +| --- | --- | --- | --- | + + +### Events + +- None. + +### Slots + +- None. + +- Public types: None +- Story variants: `Known list structure` + + +## API Caveats + +- 单块默认宽 100%、高 1em,通过 class/style 组合尺寸;aria-hidden,宿主只为整个区域提供一次状态说明。响应浅深主题及 prefers-reduced-motion。 + +## Common Mistakes + +- 不要画假的可点击操作,不要在错误或空态保留骨架。 diff --git a/packages/web/skills/ui-web/references/composition-recipes.md b/packages/web/skills/ui-web/references/composition-recipes.md index e705e37..67661d7 100644 --- a/packages/web/skills/ui-web/references/composition-recipes.md +++ b/packages/web/skills/ui-web/references/composition-recipes.md @@ -147,9 +147,9 @@ async function submit() { **Intent:** Represent pending, empty, error, and paged collection states without conflating them. -**Components:** `InkLoading`, `InkPlaceholder`, `InkPagination`, `InkButton` +**Components:** `InkLoading`, `InkSkeleton`, `InkPlaceholder`, `InkPagination`, `InkButton` -1. Show InkLoading only while a request is pending. +1. 只在请求期间显示等待。已知结构首次加载用 InkSkeleton,内容预览用 InkLoading 默认 blocks,紧凑状态用 spinner。已有内容刷新及失败保留内容。 2. After completion, choose a specific empty, filtered-empty, permission, or error InkPlaceholder. 3. Render InkPagination only when a valid bounded page model exists. 4. Offer a retry or recovery InkButton only when the action is available. diff --git a/packages/web/src/components.d.ts b/packages/web/src/components.d.ts index 4aba881..4e7eead 100644 --- a/packages/web/src/components.d.ts +++ b/packages/web/src/components.d.ts @@ -21,6 +21,7 @@ declare module "vue" { InkPlaceholder: typeof import("./index.js")["InkPlaceholder"]; InkPopup: typeof import("./index.js")["InkPopup"]; InkScrim: typeof import("./index.js")["InkScrim"]; + InkSkeleton: typeof import("./index.js")["InkSkeleton"]; InkSwitch: typeof import("./index.js")["InkSwitch"]; InkTabs: typeof import("./index.js")["InkTabs"]; InkTextarea: typeof import("./index.js")["InkTextarea"]; diff --git a/packages/web/src/components/inkButton/inkButton.scss b/packages/web/src/components/inkButton/inkButton.scss index 35a4125..2012b0f 100644 --- a/packages/web/src/components/inkButton/inkButton.scss +++ b/packages/web/src/components/inkButton/inkButton.scss @@ -84,3 +84,9 @@ } } } + +@media (prefers-reduced-motion: reduce) { + .ink-button__loading { + animation: none; + } +} diff --git a/packages/web/src/components/inkButton/inkButton.ts b/packages/web/src/components/inkButton/inkButton.ts index 28a617a..1641ae5 100644 --- a/packages/web/src/components/inkButton/inkButton.ts +++ b/packages/web/src/components/inkButton/inkButton.ts @@ -1,4 +1,8 @@ import { makeStringProp, makeBooleanProp } from "../../utils/vue-props"; +import type { InjectionKey, Ref } from "vue"; + +export const buttonDisabledKey: InjectionKey>> = + Symbol("ink-button-disabled"); // --- Types --- type ButtonTheme = "subtle" | "primary" | "danger"; diff --git a/packages/web/src/components/inkButton/inkButton.vue b/packages/web/src/components/inkButton/inkButton.vue index 706cfd8..13b3af3 100644 --- a/packages/web/src/components/inkButton/inkButton.vue +++ b/packages/web/src/components/inkButton/inkButton.vue @@ -1,29 +1,23 @@ diff --git a/packages/web/src/components/inkSkeleton/inkSkeleton.scss b/packages/web/src/components/inkSkeleton/inkSkeleton.scss new file mode 100644 index 0000000..1f9112f --- /dev/null +++ b/packages/web/src/components/inkSkeleton/inkSkeleton.scss @@ -0,0 +1,18 @@ +@keyframes ink-skeleton-pulse { + 50% { + opacity: 0.5; + } +} + +.ink-skeleton { + width: 100%; + height: 1em; + background-color: sys-var(color, border, subtle); + animation: ink-skeleton-pulse 1.8s ease-in-out infinite; +} + +@media (prefers-reduced-motion: reduce) { + .ink-skeleton { + animation: none; + } +} diff --git a/packages/web/src/components/inkSkeleton/inkSkeleton.ts b/packages/web/src/components/inkSkeleton/inkSkeleton.ts new file mode 100644 index 0000000..7b9582d --- /dev/null +++ b/packages/web/src/components/inkSkeleton/inkSkeleton.ts @@ -0,0 +1,2 @@ +// InkSkeleton has no component props; consumers size its single block with class/style. +export {}; diff --git a/packages/web/src/components/inkSkeleton/inkSkeleton.vue b/packages/web/src/components/inkSkeleton/inkSkeleton.vue new file mode 100644 index 0000000..f02a00d --- /dev/null +++ b/packages/web/src/components/inkSkeleton/inkSkeleton.vue @@ -0,0 +1,9 @@ + + + + +