diff --git a/tdesign-component/example/assets/api/steps_api.md b/tdesign-component/example/assets/api/steps_api.md new file mode 100644 index 000000000..9e9700714 --- /dev/null +++ b/tdesign-component/example/assets/api/steps_api.md @@ -0,0 +1,16 @@ +## API +### TDSteps +#### 默认构造方法 + +| 参数 | 类型 | 默认值 | 是否必填 | 说明 | +| --- | --- | --- | -- | --- | +| steps | TDStepsItemData | - | 是 | 步骤条数据 | +| activeIndex | int | 0 | 否 | 当前激活步骤的索引 | +| direction | TDStepsDirection.horizontal/TDStepsDirection.vertical | TDStepsDirection.horizontal | 否 | 步骤条方向 | +| status | TDStepsStatus.success/TDStepsStatus.error | TDStepsStatus.success | 否 | 步骤条状态 | +| simple | bool | false | 否 | 是否简略模式 | +| readOnly | bool | false | 否 | 是否纯展示readOnly模式 | +| verticalSelect | bool | false | 否 | 是否是垂直自定义步骤条选择模式 | + + + diff --git a/tdesign-component/example/lib/config.dart b/tdesign-component/example/lib/config.dart index 0f52c8843..b262ff80b 100644 --- a/tdesign-component/example/lib/config.dart +++ b/tdesign-component/example/lib/config.dart @@ -34,6 +34,7 @@ import 'page/td_search_bar_page.dart'; import 'page/td_shadows_page.dart'; import 'page/td_slider_page.dart'; import 'page/td_stepper_page.dart'; +import 'page/td_steps_page.dart'; import 'page/td_swiper_page.dart'; import 'page/td_switch_page.dart'; import 'page/td_tabs_page.dart'; @@ -198,6 +199,7 @@ Map> exampleMap = { ExamplePageModel( text: 'Swiper 轮播图', name: 'swiper', pageBuilder: _wrapInheritedTheme((context) => const TDSwiperPage())), ExamplePageModel(text: 'Tag 标签', name: 'tag', pageBuilder: _wrapInheritedTheme((context) => const TDTagPage())), + ExamplePageModel(text: 'Steps 步骤条', name: 'steps', pageBuilder: _wrapInheritedTheme((context) => const TDStepsPage())), ], '反馈': [ ExamplePageModel( diff --git a/tdesign-component/example/lib/page/td_steps_page.dart b/tdesign-component/example/lib/page/td_steps_page.dart new file mode 100644 index 000000000..a9497d557 --- /dev/null +++ b/tdesign-component/example/lib/page/td_steps_page.dart @@ -0,0 +1,695 @@ +import 'package:flutter/material.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +import '../../base/example_widget.dart'; +import '../annotation/demo.dart'; + +class TDStepsPage extends StatefulWidget { + const TDStepsPage({Key? key}) : super(key: key); + + @override + State createState() => _TDStepsPageState(); +} + +class _TDStepsPageState extends State { + @override + Widget build(BuildContext context) { + return ExamplePage( + backgroundColor: TDTheme.of(context).whiteColor1, + title: tdTitle(), + exampleCodeGroup: 'steps', + desc: 'Steps步骤条', + children: [ + ExampleModule(title: '水平默认步骤条', children: [ + ExampleItem( + desc: '水平默认步骤条1', + builder: _buildBasicHSteps1), + ExampleItem( + desc: '水平默认步骤条2', + builder: _buildBasicHSteps2), + ExampleItem( + desc: '水平默认步骤条3', + builder: _buildBasicHSteps3), + ]), + ExampleModule(title: '水平图标步骤条', children: [ + ExampleItem( + desc: '水平图标步骤条1', + builder: _buildHIconSteps1), + ExampleItem( + desc: '水平图标步骤条2', + builder: _buildHIconSteps2), + ExampleItem( + desc: '水平图标步骤条3', + builder: _buildHIconSteps3), + ]), + ExampleModule(title: '水平简略步骤条', children: [ + ExampleItem( + desc: '水平简略步骤条1', + builder: _buildSimpleHSteps1), + ExampleItem( + desc: '水平简略步骤条2', + builder: _buildSimpleHSteps2), + ExampleItem( + desc: '水平简略步骤条3', + builder: _buildSimpleHSteps3), + ]), + ExampleModule(title: '水平错误状态步骤条', children: [ + ExampleItem( + desc: '水平错误状态基本步骤条', + builder: _buildHErrorSteps1), + ExampleItem( + desc: '水平错误状态图标步骤条', + builder: _buildHErrorSteps2), + ExampleItem( + desc: '水平错误状态简略步骤条', + builder: _buildHErrorSteps3), + ]), + + ExampleModule(title: '垂直步骤条', children: [ + ExampleItem( + desc: '垂直默认步骤条', + builder: _buildVBasicSteps), + ExampleItem( + desc: '垂直图标步骤条', + builder: _buildVIconSteps), + ExampleItem( + desc: '垂直简略步骤条', + builder: _buildVSimpleSteps), + ExampleItem( + desc: '垂直错误状态基本步骤条', + builder: _buildVErrorBasicSteps), + ExampleItem( + desc: '垂直错误状态图标步骤条', + builder: _buildVErrorIconSteps), + ExampleItem( + desc: '垂直错误状态简略步骤条', + builder: _buildVErrorSimpleSteps), + ExampleItem( + desc: '垂直自定义内容基本步骤条', + builder: _buildVCustomContentBaseSteps), + ]), + ExampleModule(title: 'Extension步骤条', children: [ + ExampleItem( + desc: 'Read-only Steps 纯展示水平步骤条', + builder: _buildHReadOnlySteps), + ExampleItem( + desc: 'Read-only Steps 纯展示垂直步骤条', + builder: _buildVReadOnlySteps), + ExampleItem( + desc: 'Vertical Customize Steps 垂直自定义步骤条', + builder: _buildVCustomizeSteps), + ]), + ], + ); + } + + List basicHStepsListData1 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1'), + TDStepsItemData(title: 'Steps2', content: 'Content2'), + ]; + /// 基本步骤1 + @Demo(group: 'steps') + Widget _buildBasicHSteps1(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: basicHStepsListData1, + ), + ) + ], + ), + ); + } + List basicHStepsListData2 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1'), + TDStepsItemData(title: 'Steps2', content: 'Content2'), + TDStepsItemData(title: 'Steps3', content: 'Content3'), + ]; + /// 基本步骤2 + @Demo(group: 'steps') + Widget _buildBasicHSteps2(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: basicHStepsListData2, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + ), + ) + ], + ), + ); + } + List basicHStepsListData3 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1'), + TDStepsItemData(title: 'Steps2', content: 'Content2'), + TDStepsItemData(title: 'Steps3', content: 'Content3'), + TDStepsItemData(title: 'Steps4', content: 'Content4'), + ]; + List basicStepsListData4 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps2', content: 'Content2'), + TDStepsItemData(title: 'Steps3', content: 'Content3', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps4', content: 'Content4', successIcon: TDIcons.call), + ]; + /// 基本步骤3 + @Demo(group: 'steps') + Widget _buildBasicHSteps3(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: basicHStepsListData3, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + ), + ) + ], + ), + ); + } + + List hIconStepsListData1 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps2', content: 'Content2', successIcon: TDIcons.call), + ]; + /// 水平图标步骤条1 + @Demo(group: 'steps') + Widget _buildHIconSteps1(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: hIconStepsListData1, + direction: TDStepsDirection.horizontal, + activeIndex: 0, + ), + ) + ], + ), + ); + } + + List hIconStepsListData2 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps2', content: 'Content2', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps3', content: 'Content3', successIcon: TDIcons.call), + ]; + /// 水平图标步骤条1 + @Demo(group: 'steps') + Widget _buildHIconSteps2(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: hIconStepsListData2, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + ), + ) + ], + ), + ); + } + + List hIconStepsListData3 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps2', content: 'Content2', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps3', content: 'Content3', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps4', content: 'Content4', successIcon: TDIcons.call), + ]; + /// 水平图标步骤条1 + @Demo(group: 'steps') + Widget _buildHIconSteps3(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: hIconStepsListData3, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + ), + ) + ], + ), + ); + } + + List simpleHStepsListData1 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1'), + TDStepsItemData(title: 'Steps2', content: 'Content2'), + ]; + /// 水平简略步骤条1 + @Demo(group: 'steps') + Widget _buildSimpleHSteps1(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: simpleHStepsListData1, + direction: TDStepsDirection.horizontal, + activeIndex: 0, + simple: true, + ), + ) + ], + ), + ); + } + + List simpleHStepsListData2 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1'), + TDStepsItemData(title: 'Steps2', content: 'Content2'), + TDStepsItemData(title: 'Steps3', content: 'Content3'), + ]; + /// 水平简略步骤条2 + @Demo(group: 'steps') + Widget _buildSimpleHSteps2(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: simpleHStepsListData2, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + simple: true, + ), + ) + ], + ), + ); + } + + List simpleHStepsListData3 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1'), + TDStepsItemData(title: 'Steps2', content: 'Content2'), + TDStepsItemData(title: 'Steps3', content: 'Content3'), + TDStepsItemData(title: 'Steps4', content: 'Content4'), + ]; + /// 水平简略步骤条3 + @Demo(group: 'steps') + Widget _buildSimpleHSteps3(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: simpleHStepsListData3, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + simple: true, + ), + ) + ], + ), + ); + } + + List hErrorStepsListData1 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1'), + TDStepsItemData(title: 'Error', content: 'Content2'), + TDStepsItemData(title: 'Steps3', content: 'Content3'), + TDStepsItemData(title: 'Steps4', content: 'Content4'), + ]; + /// 水平简略步骤条3 + @Demo(group: 'steps') + Widget _buildHErrorSteps1(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: hErrorStepsListData1, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + status: TDStepsStatus.error, + ), + ) + ], + ), + ); + } + + List hErrorStepsListData2 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1', successIcon: TDIcons.call), + TDStepsItemData(title: 'Error', content: 'Content2', successIcon: TDIcons.call, errorIcon: TDIcons.close_circle), + TDStepsItemData(title: 'Steps3', content: 'Content3', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps4', content: 'Content4', successIcon: TDIcons.call), + ]; + /// 水平简略步骤条3 + @Demo(group: 'steps') + Widget _buildHErrorSteps2(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: hErrorStepsListData2, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + status: TDStepsStatus.error, + ), + ) + ], + ), + ); + } + + List hErrorStepsListData3 = [ + TDStepsItemData(title: 'Steps1', content: 'Content1', successIcon: TDIcons.call), + TDStepsItemData(title: 'Error', content: 'Content2', successIcon: TDIcons.call, errorIcon: TDIcons.close_circle), + TDStepsItemData(title: 'Steps3', content: 'Content3', successIcon: TDIcons.call), + TDStepsItemData(title: 'Steps4', content: 'Content4', successIcon: TDIcons.call), + ]; + /// 水平简略步骤条3 + @Demo(group: 'steps') + Widget _buildHErrorSteps3(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: hErrorStepsListData3, + direction: TDStepsDirection.horizontal, + activeIndex: 1, + status: TDStepsStatus.error, + simple: true, + ), + ) + ], + ), + ); + } + + List vBasicStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content'), + TDStepsItemData(title: 'Process', content: 'Customize content'), + TDStepsItemData(title: 'Default', content: 'Customize content'), + TDStepsItemData(title: 'Default', content: 'Customize content'), + ]; + /// 垂直默认步骤条 + @Demo(group: 'steps') + Widget _buildVBasicSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vBasicStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 1, + ), + ) + ], + ), + ); + } + + List vIconStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Process', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + ]; + /// 垂直图标步骤条 + @Demo(group: 'steps') + Widget _buildVIconSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vIconStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 1, + ), + ) + ], + ), + ); + } + + List vSimpleStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Process', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + ]; + /// 垂直简略步骤条 + @Demo(group: 'steps') + Widget _buildVSimpleSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vSimpleStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 1, + simple: true, + ), + ) + ], + ), + ); + } + + List vErrorBasicStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content'), + TDStepsItemData(title: 'Process', content: 'Customize content'), + TDStepsItemData(title: 'Default', content: 'Customize content'), + TDStepsItemData(title: 'Default', content: 'Customize content'), + ]; + /// 垂直错误状态基本步骤条 + @Demo(group: 'steps') + Widget _buildVErrorBasicSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vErrorBasicStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 1, + status: TDStepsStatus.error, + ), + ) + ], + ), + ); + } + + List vErrorIconStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Process', content: 'Customize content', successIcon: TDIcons.cart, errorIcon: TDIcons.close_circle), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + ]; + /// 垂直错误状态图标步骤条 + @Demo(group: 'steps') + Widget _buildVErrorIconSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vErrorIconStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 1, + status: TDStepsStatus.error, + ), + ) + ], + ), + ); + } + + List vErrorSimpleStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Process', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + TDStepsItemData(title: 'Default', content: 'Customize content', successIcon: TDIcons.cart), + ]; + /// 垂直错误状态图标步骤条 + @Demo(group: 'steps') + Widget _buildVErrorSimpleSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vErrorSimpleStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 1, + simple: true, + status: TDStepsStatus.error, + ), + ) + ], + ), + ); + } + + List vCustomContentBasicStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content'), + TDStepsItemData(title: 'Process', content: 'Customize content', customContent: Container( + margin: const EdgeInsets.only(bottom: 16, top: 4), + child: ClipRRect( + borderRadius: BorderRadius.circular(6), + child: const TDImage( + assetUrl: 'assets/img/image.png', + type: TDImageType.square, + ), + ), + )), + TDStepsItemData(title: 'Default', content: 'Customize content'), + TDStepsItemData(title: 'Default', content: 'Customize content'), + ]; + /// 垂直自定义内容基本步骤条 + @Demo(group: 'steps') + Widget _buildVCustomContentBaseSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vCustomContentBasicStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 1, + ), + ) + ], + ), + ); + } + + List hReadOnlyStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'content'), + TDStepsItemData(title: 'Process', content: 'content'), + TDStepsItemData(title: 'Default', content: 'content'), + TDStepsItemData(title: 'Default', content: 'content'), + ]; + /// 水平自定义内容基本步骤条 + @Demo(group: 'steps') + Widget _buildHReadOnlySteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: hReadOnlyStepsListData, + readOnly: true, + ), + ) + ], + ), + ); + } + + + List vReadOnlyStepsListData = [ + TDStepsItemData(title: 'Filish', content: 'Customize content'), + TDStepsItemData(title: 'Process', content: 'Customize content'), + TDStepsItemData(title: 'Default', content: 'Customize content'), + TDStepsItemData(title: 'Default', content: 'Customize content'), + ]; + /// 垂直自定义内容基本步骤条 + @Demo(group: 'steps') + Widget _buildVReadOnlySteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vReadOnlyStepsListData, + direction: TDStepsDirection.vertical, + activeIndex: 0, + readOnly: true, + ), + ) + ], + ), + ); + } + + List vCustomizeStepsListData = [ + TDStepsItemData(title: 'Selected', content: ''), + TDStepsItemData(title: 'Selected', content: ''), + TDStepsItemData(title: 'Selected', content: ''), + TDStepsItemData(title: 'Please Selected', content: ''), + ]; + /// Vertical Customize Steps 垂直自定义步骤条 + @Demo(group: 'steps') + Widget _buildVCustomizeSteps(BuildContext context){ + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Row( + children: [ + Expanded( + flex: 1, + child: TDSteps( + steps: vCustomizeStepsListData, + direction: TDStepsDirection.vertical, + simple: true, + activeIndex: 3, + verticalSelect: true, + ), + ) + ], + ), + ); + } + +} diff --git a/tdesign-component/lib/src/components/steps/td_steps.dart b/tdesign-component/lib/src/components/steps/td_steps.dart new file mode 100644 index 000000000..b26f0031b --- /dev/null +++ b/tdesign-component/lib/src/components/steps/td_steps.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:tdesign_flutter/src/components/steps/td_steps_horizontal.dart'; +import 'package:tdesign_flutter/src/components/steps/td_steps_vertical.dart'; +import '../../../tdesign_flutter.dart'; + +/// Steps步骤条数据类型 +class TDStepsItemData { + final String title; + final String content; + + final IconData? successIcon; + final IconData? errorIcon; + + final Widget? customContent; + + TDStepsItemData({ + required this.title, + required this.content, + this.successIcon, + this.errorIcon, + this.customContent, + }); +} + +/// Steps步骤条方向 +enum TDStepsDirection { + horizontal, + vertical, +} + +/// steps步骤条状态 +enum TDStepsStatus { + success, + error, +} + +/// Steps步骤条 +class TDSteps extends StatefulWidget { + /// 步骤条数据 + final List steps; + /// 步骤条方向 + final TDStepsDirection direction; + /// 步骤条当前激活的索引 + final int activeIndex; + /// 步骤条状态 + final TDStepsStatus status; + /// 步骤条simple模式 + final bool simple; + /// 步骤条readOnly模式 + final bool readOnly; + /// 步骤条垂直自定义步骤条选择模式 + final bool verticalSelect; + + const TDSteps({ + super.key, + required this.steps, + this.activeIndex = 0, + this.direction = TDStepsDirection.horizontal, + this.status = TDStepsStatus.success, + this.simple = false, + this.readOnly = false, + this.verticalSelect = false, + }); + + @override + _TDStepsState createState() => _TDStepsState(); + +} +class _TDStepsState extends State { + @override + Widget build(BuildContext context) { + /// 当前激活的step索引 + final currentActiveIndex = widget.activeIndex < 0 ? 0 : + (widget.activeIndex >= widget.steps.length ? widget.steps.length - 1 : widget.activeIndex); + return widget.direction == TDStepsDirection.horizontal ? + TDStepsHorizontal( + steps: widget.steps, + activeIndex: currentActiveIndex, + status: widget.status, + simple: widget.simple, + readOnly: widget.readOnly + ): + TDStepsVertical( + steps: widget.steps, + activeIndex: currentActiveIndex, + status: widget.status, + simple: widget.simple, + readOnly: widget.readOnly, + verticalSelect: widget.verticalSelect, + ); + } + +} diff --git a/tdesign-component/lib/src/components/steps/td_steps_horizontal.dart b/tdesign-component/lib/src/components/steps/td_steps_horizontal.dart new file mode 100644 index 000000000..442f76a92 --- /dev/null +++ b/tdesign-component/lib/src/components/steps/td_steps_horizontal.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:tdesign_flutter/src/components/steps/td_steps_horizontal_item.dart'; +import '../../../tdesign_flutter.dart'; + +/// Steps步骤条,水平步骤 +class TDStepsHorizontal extends StatelessWidget { + final List steps; + final int activeIndex; + final TDStepsStatus status; + final bool simple; + final bool readOnly; + const TDStepsHorizontal({ + super.key, + required this.steps, + required this.activeIndex, + required this.status, + required this.simple, + required this.readOnly, + }); + + @override + Widget build(BuildContext context) { + final stepsCount = steps.length; + List stepsHorizontalItem = steps.asMap().entries.map((item){ + return Expanded( + flex: 1, + child: TDStepsHorizontalItem( + index: item.key, + data: item.value, + stepsCount: stepsCount, + activeIndex: activeIndex, + status: status, + simple: simple, + readOnly: readOnly, + ), + ); + }).toList(); + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: stepsHorizontalItem, + ); + } + +} + diff --git a/tdesign-component/lib/src/components/steps/td_steps_horizontal_item.dart b/tdesign-component/lib/src/components/steps/td_steps_horizontal_item.dart new file mode 100644 index 000000000..5fc931b3a --- /dev/null +++ b/tdesign-component/lib/src/components/steps/td_steps_horizontal_item.dart @@ -0,0 +1,204 @@ +import 'package:flutter/material.dart'; +import '../../../tdesign_flutter.dart'; + +/// Steps步骤条,水平步骤item +class TDStepsHorizontalItem extends StatelessWidget { + final TDStepsItemData data; + final int index; + final int stepsCount; + final int activeIndex; + final TDStepsStatus status; + final bool simple; + final bool readOnly; + const TDStepsHorizontalItem({ + super.key, + required this.data, + required this.index, + required this.stepsCount, + required this.activeIndex, + required this.status, + required this.simple, + required this.readOnly, + }); + + @override + Widget build(BuildContext context) { + /// 步骤条数字背景色 + var stepsNumberBgColor = TDTheme.of(context).brandNormalColor; + /// 步骤条数字颜色 + var stepsNumberTextColor = TDTheme.of(context).whiteColor1; + /// 步骤条标题颜色 + var stepsTitleColor = TDTheme.of(context).brandColor7; + /// 步骤条icon颜色 + var stepsIconColor = TDTheme.of(context).brandColor7; + /// 简略步骤条icon颜色 + var simpleStepsIconColor = TDTheme.of(context).brandColor7; + + /// 是否要设置步骤图标widget的Decoration + bool shouldSetIconWidgetDecoration = true; + + Widget? completeIconWidget; + /// 错误icon图标显示 + Widget errorIconWidget = Icon(TDIcons.close, color: TDTheme.of(context).errorColor6, size: 16,); + + /// 激活索引大于当前索引 + if (activeIndex > index) { + stepsNumberBgColor = TDTheme.of(context).brandColor1; + stepsNumberTextColor = TDTheme.of(context).brandColor7; + stepsTitleColor = TDTheme.of(context).fontGyColor1; + /// 已完成的用icon图标显示 + completeIconWidget = Icon(TDIcons.check, color: TDTheme.of(context).brandColor7, size: 16,); + }else if (activeIndex < index) { + /// 激活索引小于当前索引 + stepsNumberBgColor = TDTheme.of(context).grayColor1; + stepsNumberTextColor = TDTheme.of(context).fontGyColor3; + stepsTitleColor = TDTheme.of(context).fontGyColor3; + stepsIconColor = TDTheme.of(context).fontGyColor3; + simpleStepsIconColor = TDTheme.of(context).grayColor4; + } + + /// 步骤条icon图标组件,默认为索引文字 + Widget? stepsIconWidget = Text( + (index + 1).toString(), + style: TextStyle( + color: stepsNumberTextColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ); + + /// 已完成的用icon图标显示 + if (completeIconWidget != null) { + stepsIconWidget = completeIconWidget; + } + + /// 传递了成功的icon图标, 已完成的step都需要显示 + if (data.successIcon != null) { + stepsIconWidget = Icon(data.successIcon, color: stepsIconColor, size: 22,); + /// 传了图标则不用设置背景色 + shouldSetIconWidgetDecoration = false; + } + + /// 状态是错误状态,激活索引是当前索引,只有当前激活索引才需要显示 + if (status == TDStepsStatus.error && activeIndex == index) { + /// 显示错误颜色 + stepsNumberBgColor = TDTheme.of(context).errorColor1; + stepsTitleColor = TDTheme.of(context).errorColor6; + /// 显示错误图标 + stepsIconWidget = errorIconWidget; + if (data.errorIcon != null) { + stepsIconWidget = Icon(data.errorIcon, color: TDTheme.of(context).errorColor6, size: 22,); + } + /// 传了图标则不用设置背景色等Decoration + shouldSetIconWidgetDecoration = data.errorIcon == null; + if (simple) { + simpleStepsIconColor = TDTheme.of(context).errorColor6; + } + } + + /// 步骤条icon图标背景和形状 + BoxDecoration? iconWidgetDecoration = shouldSetIconWidgetDecoration ? BoxDecoration( + color: stepsNumberBgColor, + shape: BoxShape.circle, + ): null; + + + // icon组件容器大小 + double iconContainerSize = 22; + + /// 简略步骤条 + if (simple || readOnly) { + /// readOnly纯展示 + if (readOnly) { + simpleStepsIconColor = TDTheme.of(context).brandColor7; + stepsTitleColor = TDTheme.of(context).fontGyColor1; + } + iconContainerSize = 8; + stepsIconWidget = null; + /// 简略步骤条BoxDecoration + var simpleDecoration = BoxDecoration( + color: Colors.transparent, + shape: BoxShape.circle, + border: Border.all( + color: simpleStepsIconColor, + width: 1, + ), + ); + if (activeIndex == index && !readOnly) { + simpleDecoration = BoxDecoration( + color: simpleStepsIconColor, + shape: BoxShape.circle, + ); + } + iconWidgetDecoration = simpleDecoration; + } + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + flex: 1, + child: Opacity( + opacity: index == 0 ? 0 : 1, + child: Container( + width: double.infinity, + height: 1, + color: (activeIndex >= index || readOnly) ? TDTheme.of(context).brandColor7 : TDTheme.of(context).grayColor4 + ), + ), + ), + Container( + width: iconContainerSize, + height: iconContainerSize, + alignment: Alignment.center, + margin: const EdgeInsets.only(left: 8, right: 8), + decoration: iconWidgetDecoration, + child: stepsIconWidget, + ), + Expanded( + flex: 1, + child: Opacity( + opacity: index == stepsCount - 1 ? 0 : 1, + child: Container( + width: double.infinity, + height: 1, + color: (activeIndex > index || readOnly) ? TDTheme.of(context).brandColor7 : TDTheme.of(context).grayColor4, + ), + ), + ), + ], + ), + Container( + margin: const EdgeInsets.only(top: 8), + alignment: Alignment.center, + child: TDText( + data.title, + style: TextStyle( + fontWeight: (activeIndex == index && !readOnly) ? FontWeight.w600 : FontWeight.w400, + color: stepsTitleColor, + fontSize: 14, + ), + ), + ), + Container( + margin: const EdgeInsets.only(top: 4), + alignment: Alignment.center, + child: TDText( + data.content, + style: TextStyle( + fontWeight: FontWeight.w400, + color: TDTheme.of(context).fontGyColor3, + fontSize: 12, + ), + ), + ), + ], + ); + } + +} + diff --git a/tdesign-component/lib/src/components/steps/td_steps_vertical.dart b/tdesign-component/lib/src/components/steps/td_steps_vertical.dart new file mode 100644 index 000000000..6bcb4618b --- /dev/null +++ b/tdesign-component/lib/src/components/steps/td_steps_vertical.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:tdesign_flutter/src/components/steps/td_steps_vertical_item.dart'; +import '../../../tdesign_flutter.dart'; + +/// Steps步骤条,垂直步骤 +class TDStepsVertical extends StatelessWidget { + final List steps; + final int activeIndex; + final TDStepsStatus status; + final bool simple; + final bool readOnly; + final bool verticalSelect; + const TDStepsVertical({ + super.key, + required this.steps, + required this.activeIndex, + required this.status, + required this.simple, + required this.readOnly, + required this.verticalSelect, + }); + + @override + Widget build(BuildContext context) { + final stepsCount = steps.length; + List stepsVerticalItem = steps.asMap().entries.map((item){ + return TDStepsVerticalItem( + index: item.key, + data: item.value, + stepsCount: stepsCount, + activeIndex: activeIndex, + status: status, + simple: simple, + readOnly: readOnly, + verticalSelect: verticalSelect, + ); + }).toList(); + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: stepsVerticalItem, + ); + } + +} + diff --git a/tdesign-component/lib/src/components/steps/td_steps_vertical_item.dart b/tdesign-component/lib/src/components/steps/td_steps_vertical_item.dart new file mode 100644 index 000000000..4b213ffa4 --- /dev/null +++ b/tdesign-component/lib/src/components/steps/td_steps_vertical_item.dart @@ -0,0 +1,236 @@ +import 'package:flutter/material.dart'; +import '../../../tdesign_flutter.dart'; + +/// Steps步骤条,垂直步骤item +class TDStepsVerticalItem extends StatelessWidget { + final TDStepsItemData data; + final int index; + final int stepsCount; + final int activeIndex; + final TDStepsStatus status; + final bool simple; + final bool readOnly; + final bool verticalSelect; + const TDStepsVerticalItem({ + super.key, + required this.data, + required this.index, + required this.stepsCount, + required this.activeIndex, + required this.status, + required this.simple, + required this.readOnly, + required this.verticalSelect, + }); + + @override + Widget build(BuildContext context) { + /// 步骤条数字背景色 + var stepsNumberBgColor = TDTheme.of(context).brandNormalColor; + /// 步骤条数字颜色 + var stepsNumberTextColor = TDTheme.of(context).whiteColor1; + /// 步骤条标题颜色 + var stepsTitleColor = TDTheme.of(context).brandColor7; + /// 步骤条icon颜色 + var stepsIconColor = TDTheme.of(context).brandColor7; + /// 简略步骤条icon颜色 + var simpleStepsIconColor = TDTheme.of(context).brandColor7; + + /// 是否要设置步骤图标widget的Decoration + bool shouldSetIconWidgetDecoration = true; + + Widget? completeIconWidget; + /// 错误icon图标显示 + Widget errorIconWidget = Icon(TDIcons.close, color: TDTheme.of(context).errorColor6, size: 16,); + + /// 激活索引大于当前索引 + if (activeIndex > index) { + stepsNumberBgColor = TDTheme.of(context).brandColor1; + stepsNumberTextColor = TDTheme.of(context).brandColor7; + stepsTitleColor = TDTheme.of(context).fontGyColor1; + /// 已完成的用icon图标显示 + completeIconWidget = Icon(TDIcons.check, color: TDTheme.of(context).brandColor7, size: 16,); + }else if (activeIndex < index) { + /// 激活索引小于当前索引 + stepsNumberBgColor = TDTheme.of(context).grayColor1; + stepsNumberTextColor = TDTheme.of(context).fontGyColor3; + stepsTitleColor = TDTheme.of(context).fontGyColor3; + stepsIconColor = TDTheme.of(context).fontGyColor3; + simpleStepsIconColor = TDTheme.of(context).grayColor4; + } + + /// 步骤条icon图标组件,默认为索引文字 + Widget? stepsIconWidget = Text( + (index + 1).toString(), + style: TextStyle( + color: stepsNumberTextColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ); + + /// 已完成的用icon图标显示 + if (completeIconWidget != null) { + stepsIconWidget = completeIconWidget; + } + + /// 传递了成功的icon图标, 已完成的step都需要显示 + if (data.successIcon != null) { + stepsIconWidget = Icon(data.successIcon, color: stepsIconColor, size: 22,); + /// 传了图标则不用设置背景色 + shouldSetIconWidgetDecoration = false; + } + + /// 状态是错误状态,激活索引是当前索引,只有当前激活索引才需要显示 + if (status == TDStepsStatus.error && activeIndex == index) { + /// 显示错误颜色 + stepsNumberBgColor = TDTheme.of(context).errorColor1; + stepsTitleColor = TDTheme.of(context).errorColor6; + /// 显示错误图标 + stepsIconWidget = errorIconWidget; + if (data.errorIcon != null) { + stepsIconWidget = Icon(data.errorIcon, color: TDTheme.of(context).errorColor6, size: 22,); + } + /// 传了图标则不用设置背景色等Decoration + shouldSetIconWidgetDecoration = data.errorIcon == null; + if (simple) { + simpleStepsIconColor = TDTheme.of(context).errorColor6; + } + } + + /// 步骤条icon图标背景和形状 + var iconWidgetDecoration = shouldSetIconWidgetDecoration ? BoxDecoration( + color: stepsNumberBgColor, + shape: BoxShape.circle, + ): null; + + + /// icon组件容器大小 + double iconContainerSize = 22; + + /// icon组件容器margin + double iconMarginBottom = 8; + + /// 简略步骤条 + if (simple || readOnly) { + /// readOnly纯展示 + if (readOnly) { + simpleStepsIconColor = TDTheme.of(context).brandColor7; + stepsTitleColor = TDTheme.of(context).fontGyColor1; + } + iconContainerSize = 8; + iconMarginBottom = 4; + stepsIconWidget = null; + /// 简略步骤条BoxDecoration + var simpleDecoration = BoxDecoration( + color: Colors.transparent, + shape: BoxShape.circle, + border: Border.all( + color: simpleStepsIconColor, + width: 1, + ), + ); + if (activeIndex == index && !readOnly) { + simpleDecoration = BoxDecoration( + color: simpleStepsIconColor, + shape: BoxShape.circle, + ); + } + iconWidgetDecoration = simpleDecoration; + } + + /// 自定义内容 + var customContent = data.customContent != null ? Container( + margin: const EdgeInsets.only(top: 4), + child: data.customContent!, + ) : Container(); + + return Container( + margin: const EdgeInsets.only(bottom: 8), + child: IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.only(right: 8), + width: 22, + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 62), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: iconContainerSize, + height: 22, + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: iconMarginBottom), + decoration: iconWidgetDecoration, + child: stepsIconWidget, + ), + Expanded( + flex: 1, + child: Opacity( + opacity: index == stepsCount - 1 ? 0 : 1, + child: Container( + width: 1, + height: double.infinity, + color: (activeIndex > index || readOnly) ? TDTheme.of(context).brandColor7 : TDTheme.of(context).grayColor4, + ), + ), + ), + ], + ), + ), + ), + Expanded( + flex: 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 22, + margin: const EdgeInsets.only(bottom: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TDText( + data.title, + style: TextStyle( + fontWeight: (activeIndex == index && !readOnly) ? FontWeight.w600 : FontWeight.w400, + color: stepsTitleColor, + fontSize: 14, + height: 1.2, + ), + ), + verticalSelect ? Icon(TDIcons.chevron_right, color: TDTheme.of(context).fontGyColor1, size: 16,): Container(), + ], + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TDText( + data.content, + style: TextStyle( + fontWeight: FontWeight.w400, + color: TDTheme.of(context).fontGyColor3, + fontSize: 12, + ), + ), + customContent, + ] + ), + ], + ), + ) + ], + ), + ), + ); + } + +} + diff --git a/tdesign-component/lib/tdesign_flutter.dart b/tdesign-component/lib/tdesign_flutter.dart index daab20a59..c7b422cbc 100644 --- a/tdesign-component/lib/tdesign_flutter.dart +++ b/tdesign-component/lib/tdesign_flutter.dart @@ -36,6 +36,7 @@ export 'src/components/sidebar/td_sidebar_item.dart'; export 'src/components/slider/td_slider.dart'; export 'src/components/slider/td_slider_theme.dart'; export 'src/components/stepper/td_stepper.dart'; +export 'src/components/steps/td_steps.dart'; export 'src/components/swiper/td_page_transform.dart'; export 'src/components/swiper/td_swiper.dart'; export 'src/components/switch/td_switch.dart';