Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions specs/028-stepper-demo-alignment/acceptance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 验收记录

## 2026-09-09 本地修复

- PR 基线:[GitHub #1064](https://github.com/Tencent/tdesign-flutter/pull/1064),head `5bdc6b41fc47b9115856013ca8b9ae4159431a50`,base `ccace5c61383dc2c4fd5392f41222e65b54d8010`。
- [Figma 展示目标](https://www.figma.com/design/mdBVCCVGERhxoZLle2eLT0?node-id=28591-40152):376 × 960,删除中间 99 示例,禁用值为 0;示例使用完整宽度的容器底色、16px 内边距,基础与禁用左对齐。
- 小程序参考版本:`fe14543572bc7233226b2f08db6fc1424cd81ed2`。[公开运行页](https://tdesign.tencent.com/miniprogram/live/m2w/program/miniprogram/#!pages/stepper/stepper.html) 仍有 99,本次按指定设计稿处理。
- 保留现有七个构造参数、枚举和默认值。修复草稿边界与 Theme 插值,不新增 Controller、disabled、integer 或 disableInput。

## 实际验证

| 检查 | Flutter 3.32.0 / macOS | Flutter 3.47.0 / macOS |
| --- | --- | --- |
| 组件回归 | 39 项通过 | 39 项通过 |
| 完整 Demo 回归 | 7 项通过 | 7 项通过 |
| 生成器及回归工具自测 | 18 项通过 | 18 项通过 |
| 严格 flutter analyze | 0 error / 0 warning | 0 error / 0 warning |

- 四个生产文件合计行覆盖率:`330/335 = 98.51%`,组件测试全部通过。
- 行为覆盖:两端边界草稿、父组件接受/拒绝、非法草稿、即时按钮边界更新;主题双向插值、端点、实例尺寸、继承主题、TD token、copyWith、嵌套插值及 AnimatedTheme。
- 实际打开五个代码面板,验证显示内容与生成资产一致;将五份生成资产原样写为独立 Dart 文件,在最小宿主编译并操作全部十个实例,1 项测试通过。
- iPhone 16 / iOS 18.2 模拟器运行当前提交:实际点击基础、边界、三种样式和三种尺寸实例;从 0 输入合法草稿后点击减号得到 4,从 999 输入 995 后点击加号得到 996;禁用实例保持 0 且两侧按钮不可用。明暗主题切换后已修改值保持。
- 手机验收发现开启顶部代码模式会把已操作值重置。根因是 `CodeWrapper` 在显示遮罩时临时插入 `Stack`,改变 StatefulWidget 父节点结构。修复为始终保留同一 `Stack` 子树;再次上机确认基础值 3 → 4 后,开启代码模式、打开并关闭代码面板、切换主题,值均保持 4。双 SDK Demo 回归覆盖同一路径。
- 示例生成器 `--check` 通过;Stepper API 文档从源码重新生成。
- 新增组件契约测试、生成器测试和四张视觉回归的 CI 入口登记;GitHub/CNB 工具入口同步。
- 合并 `origin/develop@335b30bc` 后,用最终合并提交重新构建 iOS 应用并复跑:基础 3 → 4、最小值 0 → 1、最大值 999 → 998,三种样式与三种尺寸均各自 3 → 4;打开和关闭代码面板、查看 API 文档、切换主题后上述值全部保留。

## Linux Golden

- 环境:已有缓存镜像 `tdesign-flutter-golden-cache:3.32.0`,Linux amd64、Flutter 3.32.0、Dart 3.8.0。离线依赖使用 Git `--no-hardlinks` 适配;未修改主工作区依赖或 lockfile。
- Demo 宽 375、DPR 1,预载中文与图标字体;完整页面高 854。先检查实际明暗图片,再更新两张 Demo 基线并精确复跑。
- 原组件基线在同一环境已有字体绘制差异:亮色 848 像素(1.82%)、暗色 835 像素(1.80%)。使用 PR head 生产源码复跑后,实际图片与修复后的图片逐字节相同,确认本次修复未改变静态视觉。
- 亮色 SHA-256:`755a5ee4676911e93f45280d0edb3f052c3ee7cf56840df2d0775ec541dd0dd8`。
- 暗色 SHA-256:`0bc5e0b70c02f557cac4f7e57b3f8280495b951ee0b1ff4bbd6e5077f0c00ee1`。
- 校正两张组件基线并纳入视觉回归入口。在 example 目录运行 `flutter test --no-pub test/stepper_demo_golden_test.dart ../test/components/stepper/t_stepper_golden_test.dart`,四项通过,精确 diff 为 0;未放宽比较器容差。

## 验证边界与交付

- 第二套本地 SDK 是 3.47.0。首次修复已推送到 `36620b81`;手机验收修复和最新 `develop` 合并已在本地完成,推送后须等待远端 actual latest 与新 head 的 CNB review,不能沿用旧 head 的结果。
- iOS 18.2 模拟器已验证实际点击、文本输入、明暗主题和代码面板;Android/iOS 真机 IME 与系统字体仍未验证,Linux Golden 不证明这些路径。
- 公开签名、枚举和默认值保持兼容,行为变化属于既有契约修复,无迁移要求。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions specs/028-stepper-demo-alignment/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Plan

1. 基于 #1064 head `5bdc6b41` 修复,保留用户当前 develop 工作区。
2. 将运行示例提取为独立 Widget;生成器提取 Widget、必要的直接关联 State 和导入,面板显式绑定类名。
3. 按 Figma 去掉 99、修正禁用初值和容器布局;保持现有尺寸和公开 API。
4. 统一草稿与按钮边界判断;主题插值保留端点配置,在内部样式解析处使用真实上下文默认值。
5. 补组件、完整 Demo、代码面板及生成器回归;代码模式保持运行示例子树稳定;验证 95% 生产覆盖率和双 SDK 严格 analyze。
6. Flutter 3.32 Linux 更新并立即复跑明暗 Golden,记录实际差异与未覆盖项。
21 changes: 21 additions & 0 deletions specs/028-stepper-demo-alignment/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Stepper 公开 Demo 对齐

## 目标

以 Figma `mdBVCCVGERhxoZLle2eLT0 / 28591:40152` 为展示目标,修复基础、边界、禁用、形态与尺寸示例,并建立独立行为与视觉回归。小程序仅作为 API 和交互参考;设计稿已删除的中间边界示例不再保留。

## API Review

- `value + onChanged` 为严格受控模式;`onChanged == null` 表示整组禁用,不引入 `defaultValue` 或重复的 `disabled`。
- `min/max/step` 负责数值约束,`TStepperVariant` 与 `TStepperSize` 负责互斥视觉语义。
- `variant/size` 的实例值、组件 Theme 与默认值优先级已收敛,无需复制小程序字符串 API。
- 保持现有公开构造参数、枚举和默认值,不新增小程序专属参数。修复编辑草稿与按钮可用状态、nullable Theme 插值;属于既有契约修复,不删除能力或修改公开签名。

## 行为契约

- Demo 保持三个分组、五个代码入口,共十个步进器;边界只展示 0 / 999,禁用值为 0。基础与禁用左对齐,各块使用完整宽度、组件容器底色和 16px 内边距。
- 最小/最大状态分别由受控值等于边界表达,禁用由空 callback 表达。
- 所有可交互实例由各自 StatefulWidget 示例持有状态,主题切换、代码模式切换和页面重建不重置编辑后的值。
- 五个面板展示实际运行的示例 Widget、对应 State 和依赖导入;页面壳不进入片段。生成器沿用 ExampleCode 注解,支持独立 Widget 及同文件直接关联 State 的提取,不添加示例框架参数。
- 编辑期间按钮启停和步进运算使用同一个有效草稿值;合法草稿即使从边界开始编辑,也可向两个方向步进。父组件仍为唯一提交值源,拒绝请求时回退,单次操作最多通知一次。
- Theme 双侧 null 保持继承;单侧 null 按组件当前生效的尺寸、Flutter 显式主题和 TD token 解析后插值,不当作 0 或透明色。插值端点保持原始配置,动态主题和实例尺寸覆盖路径均须验证。
12 changes: 12 additions & 0 deletions specs/028-stepper-demo-alignment/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Tasks

- [x] 固定 #1064 head、Figma 节点和小程序参考版本,完成 API Review。
- [x] 修正边界数量、禁用初值、容器和对齐方式。
- [x] 五份生成片段包含独立 Widget、必要 State 和导入;验证实际代码面板及最小宿主编译、交互。
- [x] 在 iPhone 16 / iOS 18.2 模拟器完成点击验收,修复开启代码模式会重置运行示例状态的问题并补回归。
- [x] 统一草稿步进与按钮边界判断,覆盖父组件接受和拒绝请求。
- [x] 修正 nullable Theme 插值,覆盖尺寸、继承、token、copyWith、嵌套插值和 AnimatedTheme。
- [x] 完成本地 Flutter 3.32.0 / 3.47.0 回归、严格 analyze、98.51% 生产覆盖率及 CI 工具自测;合并最新 `develop` 后再次通过。
- [x] 检查并更新 Linux Demo 和组件明暗 Golden;四张图精确复跑通过,登记视觉回归入口。
- [x] 提交、推送并同步 GitHub/CNB PR 标题与描述;触发 CNB 定向 review。
- [ ] 推送手机验收修复后验证远端实际 latest 并重新触发 CNB review;Android/iOS 真机键盘、IME 和系统字体仍待验证。
2 changes: 1 addition & 1 deletion tdesign-component/example/assets/api/stepper_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
| min | num | 0 | 最小值,必须小于或等于 `max`。 |
| onChanged | ValueChanged<num>? | - | 数值变化请求。 点击按钮、提交有效输入或输入框失焦时触发;一次操作最多触发一次。 为 null 时整组禁用。 |
| size | TStepperSize? | - | 组件尺寸。 为空时依次使用 `TStepperThemeData.size` 和 `TStepperSize.medium`。 |
| step | num | 1 | 加减按钮使用的步长,必须大于 0。 输入提交不要求是步长的整数倍,但会限制在 `min` 与 `max` 之间。 |
| step | num | 1 | 加减按钮使用的步长,必须大于 0。 输入提交不要求是步长的整数倍,但会限制在 `min` 与 `max` 之间。 编辑时以合法输入草稿作为步进起点,并据此判断按钮是否达到边界。 |
| value | num | - | 唯一受控数值,必须位于 `min` 与 `max` 之间。 父组件需要在 `onChanged` 后以新值重建组件,否则输入内容会恢复。 |
| variant | TStepperVariant? | - | 组件形态。 为空时依次使用 `TStepperThemeData.variant` 和 `TStepperVariant.normal`。 |

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';

/// 在已配置 TDesign 主题的应用中使用 `StepperBaseExample()`。
class StepperBaseExample extends StatefulWidget {
const StepperBaseExample({super.key});

@override
State<StepperBaseExample> createState() => _StepperBaseExampleState();
}

class _StepperBaseExampleState extends State<StepperBaseExample> {
num _base = 3;

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
TStepper(
key: const ValueKey('stepper-base'),
value: _base,
variant: TStepperVariant.filled,
onChanged: (value) => setState(() => _base = value),
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';

/// 在已配置 TDesign 主题的应用中使用 `StepperBoundsExample()`。
class StepperBoundsExample extends StatefulWidget {
const StepperBoundsExample({super.key});

@override
State<StepperBoundsExample> createState() => _StepperBoundsExampleState();
}

class _StepperBoundsExampleState extends State<StepperBoundsExample> {
num _minimum = 0;
num _maximum = 999;

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
TStepper(
key: const ValueKey('stepper-minimum'),
value: _minimum,
variant: TStepperVariant.filled,
onChanged: (value) => setState(() => _minimum = value),
),
const SizedBox(width: 32),
TStepper(
key: const ValueKey('stepper-maximum'),
value: _maximum,
max: 999,
variant: TStepperVariant.filled,
onChanged: (value) => setState(() => _maximum = value),
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';

/// 在已配置 TDesign 主题的应用中使用 `StepperDisabledExample()`。
class StepperDisabledExample extends StatelessWidget {
const StepperDisabledExample({super.key});

@override
Widget build(BuildContext context) {
return const Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
TStepper(
key: ValueKey('stepper-disabled'),
value: 0,
variant: TStepperVariant.filled,
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';

/// 在已配置 TDesign 主题的应用中使用 `StepperSizesExample()`。
class StepperSizesExample extends StatefulWidget {
const StepperSizesExample({super.key});

@override
State<StepperSizesExample> createState() => _StepperSizesExampleState();
}

class _StepperSizesExampleState extends State<StepperSizesExample> {
num _large = 3;
num _medium = 3;
num _small = 3;

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TStepper(
key: const ValueKey('stepper-sizes'),
value: _large,
size: TStepperSize.large,
variant: TStepperVariant.filled,
onChanged: (value) => setState(() => _large = value),
),
TStepper(
key: const ValueKey('stepper-medium'),
value: _medium,
size: TStepperSize.medium,
variant: TStepperVariant.filled,
onChanged: (value) => setState(() => _medium = value),
),
TStepper(
key: const ValueKey('stepper-small'),
value: _small,
size: TStepperSize.small,
variant: TStepperVariant.filled,
onChanged: (value) => setState(() => _small = value),
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';

/// 在已配置 TDesign 主题的应用中使用 `StepperVariantsExample()`。
class StepperVariantsExample extends StatefulWidget {
const StepperVariantsExample({super.key});

@override
State<StepperVariantsExample> createState() => _StepperVariantsExampleState();
}

class _StepperVariantsExampleState extends State<StepperVariantsExample> {
num _filled = 3;
num _outline = 3;
num _normal = 3;

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TStepper(
key: const ValueKey('stepper-variants'),
value: _filled,
variant: TStepperVariant.filled,
onChanged: (value) => setState(() => _filled = value),
),
TStepper(
key: const ValueKey('stepper-outline'),
value: _outline,
variant: TStepperVariant.outline,
onChanged: (value) => setState(() => _outline = value),
),
TStepper(
key: const ValueKey('stepper-normal'),
value: _normal,
variant: TStepperVariant.normal,
onChanged: (value) => setState(() => _normal = value),
),
],
);
}
}
6 changes: 0 additions & 6 deletions tdesign-component/example/assets/code/stepper._buildBase.txt

This file was deleted.

20 changes: 0 additions & 20 deletions tdesign-component/example/assets/code/stepper._buildSizes.txt

This file was deleted.

20 changes: 0 additions & 20 deletions tdesign-component/example/assets/code/stepper._buildStates.txt

This file was deleted.

18 changes: 0 additions & 18 deletions tdesign-component/example/assets/code/stepper._buildSteps.txt

This file was deleted.

19 changes: 0 additions & 19 deletions tdesign-component/example/assets/code/stepper._buildTheme.txt

This file was deleted.

17 changes: 0 additions & 17 deletions tdesign-component/example/assets/code/stepper._buildVariants.txt

This file was deleted.

5 changes: 4 additions & 1 deletion tdesign-component/example/lib/annotation/example_code.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/// Marks a method whose source is exported for the example code viewer.
/// Marks a function, method, or standalone example Widget for the code viewer.
///
/// An annotated class includes its imports and directly associated same-file
/// `State<Widget>` class. Other helpers must be self-contained in these classes.
class ExampleCode {
/// The generated snippet group. It must match the page's example code group.
final String group;
Expand Down
Loading
Loading