Repository: react-component/steps Branch: master Commit: 674846852bbb Files: 71 Total size: 101.5 KB Directory structure: gitextract_zrdxiemy/ ├── .dumirc.ts ├── .editorconfig ├── .eslintrc.js ├── .fatherrc.ts ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ └── main.yml ├── .gitignore ├── .prettierrc ├── HISTORY.md ├── LICENSE.md ├── README.md ├── assets/ │ ├── custom-icon.less │ ├── iconfont.less │ ├── index.less │ ├── inline.less │ ├── label-placement.less │ ├── nav.less │ ├── progress-dot.less │ ├── small.less │ ├── variables.less │ └── vertical.less ├── bunfig.toml ├── docs/ │ ├── demo/ │ │ ├── alternativeLabel.md │ │ ├── composable.md │ │ ├── custom-svg-icon.md │ │ ├── customIcon.md │ │ ├── dynamic.md │ │ ├── errorStep.md │ │ ├── inline.md │ │ ├── nav-base.md │ │ ├── nextStep.md │ │ ├── progressDot.md │ │ ├── simple.md │ │ ├── smallSize.md │ │ ├── stepIcon.md │ │ ├── vertical.md │ │ └── verticalSmall.md │ ├── examples/ │ │ ├── alternativeLabel.jsx │ │ ├── composable.jsx │ │ ├── custom-svg-icon.jsx │ │ ├── customIcon.jsx │ │ ├── dynamic.jsx │ │ ├── errorStep.jsx │ │ ├── inline.jsx │ │ ├── nav-base.jsx │ │ ├── nextStep.css │ │ ├── nextStep.jsx │ │ ├── progressDot.jsx │ │ ├── simple.jsx │ │ ├── smallSize.jsx │ │ ├── stepIcon.jsx │ │ ├── vertical.jsx │ │ └── verticalSmall.jsx │ └── index.md ├── index.js ├── jest.config.js ├── package.json ├── script/ │ └── update-content.js ├── src/ │ ├── Context.ts │ ├── Rail.tsx │ ├── Step.tsx │ ├── StepIcon.tsx │ ├── Steps.tsx │ ├── UnstableContext.ts │ ├── index.ts │ └── interface.ts ├── tests/ │ ├── __snapshots__/ │ │ └── index.test.tsx.snap │ ├── index.test.tsx │ ├── semantic.test.tsx │ └── setup.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dumirc.ts ================================================ import { defineConfig } from 'dumi'; import path from 'path'; export default defineConfig({ alias: { '@rc-component/steps$': path.resolve('src'), '@rc-component/steps/es': path.resolve('src'), }, mfsu: false, favicons: ['https://avatars0.githubusercontent.com/u/9441414?s=200&v=4'], themeConfig: { name: 'Steps', logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4', }, }); ================================================ FILE: .editorconfig ================================================ # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*.{js,css}] end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 ================================================ FILE: .eslintrc.js ================================================ const base = require('@umijs/fabric/dist/eslint'); module.exports = { ...base, rules: { ...base.rules, 'arrow-parens': 0, 'react/sort-comp': 0, }, }; ================================================ FILE: .fatherrc.ts ================================================ import { defineConfig } from 'father'; export default defineConfig({ plugins: ['@rc-component/father-plugin'], }); ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: npm directory: "/" schedule: interval: daily time: "21:00" open-pull-requests-limit: 10 ignore: - dependency-name: "@types/react-dom" versions: - 17.0.0 - 17.0.1 - 17.0.2 - dependency-name: "@types/react" versions: - 17.0.0 - 17.0.1 - 17.0.2 - 17.0.3 - dependency-name: less versions: - 4.1.0 ================================================ FILE: .github/workflows/main.yml ================================================ name: ✅ test on: [push, pull_request] jobs: test: uses: react-component/rc-test/.github/workflows/test.yml@main secrets: inherit ================================================ FILE: .gitignore ================================================ .iml *.log .idea/ .ipr .iws *~ ~* *.diff *.patch *.bak .DS_Store Thumbs.db .project .*proj .svn/ *.swp *.swo *.pyc *.pyo .build node_modules .cache dist assets/**/*.css build lib es coverage package-lock.json pnpm-lock.yaml yarn.lock .doc .umi .npmrc # dumi .dumi/tmp .dumi/tmp-test .dumi/tmp-production .env.local bun.lockb ================================================ FILE: .prettierrc ================================================ { "singleQuote": true, "trailingComma": "all", "proseWrap": "never", "printWidth": 100 } ================================================ FILE: HISTORY.md ================================================ # History ---- ## 3.6.0 - Remove babel-runtime and prop-types - Fix icon missing #85 ## 3.5.0 - Support `navigation` type & `disabled` prop. ## 3.4.0 - Support `onChange` event. ## 3.3.0 - Add `icons` prop for change preset icon. ## 3.2.0 - Add `initial` prop. ## 3.1.0 - Add `tailContent`. ## 3.0.0 - Rewrite from bottom. ## 2.5.1 * Support react@15.5 ## 2.5.0 * Refactor for last tail style. ## 2.4.0 * Refactor for extra width of tail. https://github.com/ant-design/ant-design/issues/5083 ## 2.3.0 * Add new step style of prop `progressDot`. ## 2.2.0 * `icon` can be React.Node now. ## 2.1.0 * Add `labelPlacement`, support vertial title and description ## 2.0.0 * Refactor for better layout ## 1.5 * add `status` property of `Steps` ## 1.4 * update react to 0.14 ## 1.3 * add `current` property of `Steps` ## 1.2.3 * fix publish ## 1.2.2 * remove vertical `maxDescriptionWidth` ## 1.2.1 * fix vertical `maxDescriptionWidth` ## 1.2.0 * add vertical steps ## 1.1.4 * fix layout algorithm ## 1.1.3 * support `iconPrefix` property, default is `rc` ## 1.1.2 * fix bugs ## 1.1.1 * support `maxDescriptionWidth` property, default is 120 ## 1.1.0 * support `prefixCls` property, default is `rc-steps` ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2014-present yiminghe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # @rc-component/steps --- React steps component. [![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] [![Test coverage][codecov-image]][codecov-url] [![npm download][download-image]][download-url] [![bundle size][bundlephobia-image]][bundlephobia-url] [npm-image]: http://img.shields.io/npm/v/@rc-component/steps.svg?style=flat-square [npm-url]: http://npmjs.org/package/@rc-component/steps [travis-image]: https://img.shields.io/travis/react-component/steps.svg?style=flat-square [travis-url]: https://travis-ci.org/react-component/steps [codecov-image]: https://img.shields.io/codecov/c/github/react-component/steps/master.svg?style=flat-square [codecov-url]: https://codecov.io/gh/react-component/steps/branch/master [download-image]: https://img.shields.io/npm/dm/@rc-component/steps.svg?style=flat-square [download-url]: https://npmjs.org/package/@rc-component/steps [bundlephobia-url]: https://bundlephobia.com/result?p=@rc-component/steps [bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/steps ## Usage ```bash npm install @rc-component/steps ```
```jsx | pure ``` ## Example https://steps.vercel.app/ ## API
name type default description
type string default diretypetion of Steps, could be `default` `navigation` `inline`
direction string horizontal direction of Steps, enum: `horizontal` or `vertical`
current number 0 index of current step
initial number 0 index initial
size string size of Steps, could be `small`
titlePlacement string placement of step title, could be `vertical`
status string wait status of current Steps, could be `error` `process` `finish` `wait`
icons { finish: ReactNode, error: ReactNode } specify the default finish icon and error icon
itemRender (item: StepProps, stepItem: React.ReactNode) => React.ReactNode custom step item renderer
onChange (current: number) => void Trigger when Step changed
### Steps.Step
name type default description
title ReactNode title of step item
subTitle ReactNode subTitle of step item
description ReactNode description of step item
icon ReactNode set icon of step item
status string status of current Steps, could be `error` `process` `finish` `wait`
tailContent ReactNode content above tail
disabled bool false disabled step when onChange exist
render (stepItem: React.ReactNode) => React.ReactNode custom step item renderer
## Development ```bash npm install npm start ``` ## License @rc-component/steps is released under the MIT license. ================================================ FILE: assets/custom-icon.less ================================================ @import 'variables'; .@{stepsPrefixClass}-item-custom { .@{stepsPrefixClass}-item-icon { background: none; border: 0; width: auto; height: auto; > .@{stepsPrefixClass}-icon { font-size: 20px; top: 1px; width: 20px; height: 20px; } } &.@{stepsPrefixClass}-item-process { .@{stepsPrefixClass}-item-icon > .@{stepsPrefixClass}-icon { color: @process-icon-color; } } } ================================================ FILE: assets/iconfont.less ================================================ @icon-url : "//at.alicdn.com/t/font_1434092639_4910953"; .ie-rotate(@rotation) { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); } .rotate(@degrees) { -webkit-transform: rotate(@degrees); -ms-transform: rotate(@degrees); // IE9 only -o-transform: rotate(@degrees); transform: rotate(@degrees); } .animation(@animation) { -webkit-animation: @animation; -o-animation: @animation; animation: @animation; } // font-face // @icon-url: 字体源文件的地址 @font-face { font-family: 'anticon'; src: url('@{icon-url}.eot'); /* IE9*/ src: url('@{icon-url}.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('@{icon-url}.woff') format('woff'), /* chrome、firefox */ url('@{icon-url}.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ url('@{icon-url}.svg#iconfont') format('svg'); /* iOS 4.1- */ } .rcicon { position: relative; display: inline-block; font-style: normal; vertical-align: baseline; text-align: center; text-transform: none; text-rendering: auto; // 更好地渲染字体 -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0px; -moz-osx-font-smoothing: grayscale; &:before { display: block; font-family: "anticon" !important; } } // 方向性图标 .rcicon-step-backward:before {content:"\e662";} .rcicon-step-forward {.ie-rotate(2);} .rcicon-step-forward:before {content:"\e662";.rotate(180deg);} .rcicon-fast-backward:before {content:"\e62a";} .rcicon-fast-forward {.ie-rotate(2);} .rcicon-fast-forward:before {content:"\e62a";.rotate(180deg);} .rcicon-shrink:before {content:"\e65f";} .rcicon-arrow-salt:before {content:"\e608";} .rcicon-caret-down:before {content:"\e60f";} .rcicon-caret-left {.ie-rotate(1);} .rcicon-caret-left:before {content:"\e60f";.rotate(90deg);} .rcicon-caret-up {.ie-rotate(2);} .rcicon-caret-up:before {content:"\e60f";.rotate(180deg);} .rcicon-caret-right {.ie-rotate(3);} .rcicon-caret-right:before {content:"\e60f";.rotate(270deg);} .rcicon-caret-circle-right:before {content:"\e60d";} .rcicon-caret-circle-left {.ie-rotate(2);} .rcicon-caret-circle-left:before {content:"\e60d";.rotate(180deg);} .rcicon-caret-circle-o-right:before {content:"\e60e";} .rcicon-caret-circle-o-left {.ie-rotate(2);} .rcicon-caret-circle-o-left:before {content:"\e60e";.rotate(180deg);} .rcicon-circle-right:before {content:"\e602";} .rcicon-circle-left {.ie-rotate(2);} .rcicon-circle-left:before {content:"\e602";.rotate(180deg);} .rcicon-circle-o-right:before {content:"\e603";} .rcicon-circle-o-left {.ie-rotate(2);} .rcicon-circle-o-left:before {content:"\e603";.rotate(180deg);} .rcicon-double-right:before {content:"\e604";} .rcicon-double-left {.ie-rotate(2);} .rcicon-double-left:before {content:"\e604";.rotate(180deg);} .rcicon-verticle-right:before {content:"\e605";} .rcicon-verticle-left {.ie-rotate(2);} .rcicon-verticle-left:before {content:"\e605";.rotate(180deg);} .rcicon-forward:before {content:"\e630";} .rcicon-backward {.ie-rotate(2);} .rcicon-backward:before {content:"\e630";.rotate(180deg);} .rcicon-rollback:before {content:"\e65a";} .rcicon-retweet:before {content:"\e659";} .rcicon-right:before {content:"\e611";} .rcicon-down {.ie-rotate(1);} .rcicon-down:before {content:"\e611";.rotate(90deg);} .rcicon-left {.ie-rotate(2);} .rcicon-left:before {content:"\e611";.rotate(180deg);} .rcicon-up {.ie-rotate(3);} .rcicon-up:before {content:"\e611";.rotate(270deg);} // 提示性图标 .rcicon-question:before {content:"\e655";} .rcicon-question-circle:before {content:"\e656";} .rcicon-question-circle-o:before {content:"\e657";} .rcicon-plus:before {content:"\e651";} .rcicon-plus-circle:before {content:"\e652";} .rcicon-plus-circle-o:before {content:"\e653";} .rcicon-pause:before {content:"\e64c";} .rcicon-pause-circle:before {content:"\e64d";} .rcicon-pause-circle-o:before {content:"\e64e";} .rcicon-minus:before {content:"\e646";} .rcicon-minus-circle:before {content:"\e647";} .rcicon-minus-circle-o:before {content:"\e648";} .rcicon-info-circle:before {content:"\e637";} .rcicon-info-circle-o:before {content:"\e638";} .rcicon-info:before {content:"\e63a";} .rcicon-exclamation:before {content:"\e627";} .rcicon-exclamation-circle:before {content:"\e628";} .rcicon-exclamation-circle-o:before {content:"\e629";} .rcicon-cross:before {content:"\e61e";} .rcicon-cross-circle:before {content:"\e61f";} .rcicon-cross-circle-o:before {content:"\e620";} .rcicon-check:before {content:"\e613";} .rcicon-check-circle:before {content:"\e614";} .rcicon-check-circle-o:before {content:"\e615";} .rcicon-clock-circle:before {content:"\e616";} .rcicon-clock-circle-o:before {content:"\e617";} // 网站通用图标 .rcicon-lock:before {content:"\e641";} .rcicon-android:before {content:"\e601";} .rcicon-apple:before {content:"\e606";} .rcicon-area-chart:before {content:"\e607";} .rcicon-bar-chart:before {content:"\e609";} .rcicon-bars:before {content:"\e60a";} .rcicon-book:before {content:"\e60b";} .rcicon-calendar:before {content:"\e60c";} .rcicon-cloud:before {content:"\e618";} .rcicon-cloud-download:before {content:"\e619";} .rcicon-code:before {content:"\e61a";} .rcicon-copy:before {content:"\e61c";} .rcicon-credit-card:before {content:"\e61d";} .rcicon-delete:before {content:"\e621";} .rcicon-desktop:before {content:"\e622";} .rcicon-download-line:before {content:"\e623";} .rcicon-edit:before {content:"\e624";} .rcicon-ellipsis:before {content:"\e625";} .rcicon-environment:before {content:"\e626";} .rcicon-file:before {content:"\e62c";} .rcicon-file-text:before {content:"\e62d";} .rcicon-folder:before {content:"\e62e";} .rcicon-folder-open:before {content:"\e62f";} .rcicon-github:before {content:"\e631";} .rcicon-hdd:before {content:"\e632";} .rcicon-frown:before {content:"\e633";} .rcicon-meh:before {content:"\e634";} .rcicon-inbox:before {content:"\e635";} .rcicon-laptop:before {content:"\e63d";} .rcicon-large:before {content:"\e63e";} .rcicon-line-chart:before {content:"\e63f";} .rcicon-link:before {content:"\e640";} .rcicon-logout:before {content:"\e642";} .rcicon-mail:before {content:"\e643";} .rcicon-menu-fold:before {content:"\e644";} .rcicon-menu-unfold:before {content:"\e645";} .rcicon-mobile:before {content:"\e649";} .rcicon-notification:before {content:"\e64a";} .rcicon-paper-clip:before {content:"\e64b";} .rcicon-picture:before {content:"\e64f";} .rcicon-pie-chart:before {content:"\e650";} .rcicon-poweroff:before {content:"\e654";} .rcicon-reload:before {content:"\e658";} .rcicon-search:before {content:"\e65b";} .rcicon-setting:before {content:"\e65c";} .rcicon-share-alt:before {content:"\e65d";} .rcicon-shopping-cart:before {content:"\e65e";} .rcicon-smile:before {content:"\e661";} .rcicon-tablet:before {content:"\e664";} .rcicon-tag:before {content:"\e665";} .rcicon-tags:before {content:"\e666";} .rcicon-to-top:before {content:"\e667";} .rcicon-unlock:before {content:"\e668";} .rcicon-upload:before {content:"\e669";} .rcicon-user:before {content:"\e66a";} .rcicon-video-camera:before {content:"\e66b";} .rcicon-windows:before {content:"\e66c";} .rcicon-loading:before { display: inline-block; .animation(loadingCircle 1.0s infinite linear); content:"\e610"; } :root { .rcicon-step-forward, .rcicon-fast-forward, .rcicon-left, .rcicon-up, .rcicon-down, .rcicon-caret-left, .rcicon-caret-up, .rcicon-caret-right, .rcicon-caret-circle-left, .rcicon-caret-circle-o-left, .rcicon-circle-left, .rcicon-circle-o-left, .rcicon-double-left, .rcicon-verticle-left, .rcicon-backward { filter: none; } } ================================================ FILE: assets/index.less ================================================ @import 'variables'; .@{stepsPrefixClass} { font-size: 0; width: 100%; line-height: 1.5; display: flex; &, * { box-sizing: border-box; } } .@{stepsPrefixClass}-item { position: relative; display: inline-block; vertical-align: top; flex: 1; // overflow: hidden; &:last-child { flex: none; } &:last-child &-title:after { display: none; } &-icon, &-section { display: inline-block; vertical-align: top; } &-icon { flex: none; border: 1px solid @wait-icon-color; width: 26px; height: 26px; line-height: 26px; text-align: center; border-radius: 26px; font-size: 14px; transition: background-color 0.3s, border-color 0.3s; > .@{stepsPrefixClass}-icon { line-height: 1; top: -1px; color: @primary-color; position: relative; &.rcicon { font-size: 12px; position: relative; top: -2px; } } } &-section { margin-top: 3px; } &-title { font-size: 14px; color: #666; font-weight: bold; display: inline-block; position: relative; } &-subtitle { font-size: 12px; display: inline-block; color: #999; } &-description { font-size: 12px; color: #999; } .step-item-status(wait); .step-item-status(process); &-process &-icon { background: @process-icon-color; > .@{stepsPrefixClass}-icon { color: #fff; } } .step-item-status(finish); .step-item-status(error); &.@{stepsPrefixClass}-next-error .@{stepsPrefixClass}-item-title:after { background: @error-icon-color; } } .@{stepsPrefixClass}-horizontal:not(.@{stepsPrefixClass}-label-vertical) { .@{stepsPrefixClass}-item { &-description { max-width: @stepDescriptionMaxWidth; } } } .step-item-status(@status) { @icon-color: '@{status}-icon-color'; @title-color: '@{status}-title-color'; @description-color: '@{status}-description-color'; @tail-color: '@{status}-tail-color'; &-@{status} &-icon { border-color: @@icon-color; background-color: #fff; > .@{stepsPrefixClass}-icon { color: @@icon-color; .@{stepsPrefixClass}-icon-dot { background: @@icon-color; } } } &-@{status} &-title { color: @@title-color; } &-@{status} &-description { color: @@description-color; } } // @import 'custom-icon'; // @import 'small'; // @import 'vertical'; // @import 'label-placement'; // @import 'progress-dot'; // @import 'nav'; // @import 'inline'; // ======================= Horizontal ======================= .verticalFlex() { display: flex; flex-direction: column; align-items: center; } .@{stepsPrefixClass} { .@{stepsPrefixClass}-item { &-section { min-width: 0; } &-header { display: flex; gap: 8px; align-items: center; } // Ellipsis &-title, &-subtitle, &-description { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } } .@{stepsPrefixClass}-horizontal { .@{stepsPrefixClass}-item { flex: 1; position: relative; min-width: 0; &-rail { height: 1px; background: @process-tail-color; } } // Label Vertical &.@{stepsPrefixClass}-label-vertical { .@{stepsPrefixClass}-item { .verticalFlex(); padding-inline: 8px; &-section { .verticalFlex(); } &-rail { position: absolute; top: 13px; left: calc(50% + 13px); width: 100%; } } } // Label Horizontal &.@{stepsPrefixClass}-label-horizontal { .@{stepsPrefixClass}-item { display: flex; &:last-child { flex: none; } &-section { flex: 1; } &-rail { flex: 1; min-width: 0; } } } } // ======================== Vertical ======================== .@{stepsPrefixClass}-vertical { } ================================================ FILE: assets/inline.less ================================================ @import 'variables'; .@{stepsPrefixClass}-inline { width: auto; display: inline-flex; .@{stepsPrefixClass}-item { flex: none; &-icon { width: 6px; height: 6px; margin-left: calc(50% - 3px); > .@{stepsPrefixClass}-icon { top: 0; } .@{stepsPrefixClass}-icon-dot { border-radius: 3px; } } &-section { width: auto; margin-top: 7px; } &-title { color: rgba(0, 0, 0, 0.25); font-size: 12px; line-height: 20px; font-weight: normal; margin-bottom: 2px; } &-description { display: none; } &-finish { .@{stepsPrefixClass}-item-icon .@{stepsPrefixClass}-icon .@{stepsPrefixClass}-icon-dot { background-color: @process-tail-color; } } &-wait { .@{stepsPrefixClass}-item-icon .@{stepsPrefixClass}-icon .@{stepsPrefixClass}-icon-dot { background-color: #fff; border: 1px solid @process-tail-color; } } } } ================================================ FILE: assets/label-placement.less ================================================ @import 'variables'; .@{stepsPrefixClass}-label-vertical { .@{stepsPrefixClass}-item { overflow: visible; &-section { display: block; text-align: center; margin-top: 8px; width: @stepDescriptionMaxWidth; } &-icon { display: inline-block; margin-left: 36px; } &-title { padding-right: 0; &:after { display: none; } } &-description { text-align: left; } } } ================================================ FILE: assets/nav.less ================================================ @import 'variables'; .@{stepsPrefixClass}-navigation { padding-top: 8px; &.@{stepsPrefixClass}-horizontal { .@{stepsPrefixClass}-item-description { max-width: @stepNavContentMaxWidth; } } .@{stepsPrefixClass}-item { box-sizing: border-box; text-align: center; overflow: visible; &-title { max-width: @stepNavContentMaxWidth; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; &:after { display: none; } } &:last-child { flex: 1; &:after { display: none; } } &:after { content: ''; display: inline-block; width: 16px; height: 16px; border: 1px solid #ccc; border-bottom: none; border-left: none; transform: rotate(45deg); position: absolute; top: 50%; left: 100%; margin-top: -12px; margin-left: -8px; } } } ================================================ FILE: assets/progress-dot.less ================================================ @import 'variables'; .@{stepsPrefixClass}-dot { .@{stepsPrefixClass}-item { &-icon { padding-right: 0; width: 5px; height: 5px; line-height: 5px; border: 0; margin-left: 48px; .@{stepsPrefixClass}-icon-dot { float: left; width: 100%; height: 100%; border-radius: 2.5px; } } &-process &-icon { top: -1px; width: 7px; height: 7px; line-height: 7px; .@{stepsPrefixClass}-icon-dot { border-radius: 3.5px; } } } } ================================================ FILE: assets/small.less ================================================ @import 'variables'; .@{stepsPrefixClass}-small { .@{stepsPrefixClass}-item-icon { width: 18px; height: 18px; line-height: 18px; text-align: center; border-radius: 18px; font-size: 12px; margin-right: 10px; > .@{stepsPrefixClass}-icon { font-size: 12px; font-size: ~"9px \9"; // ie8-9 transform: scale(.75); top: -1px; } } .@{stepsPrefixClass}-item-section { margin-top: 0; } .@{stepsPrefixClass}-item-title { font-size: 12px; margin-bottom: 4px; color: #666; font-weight: bold; } .@{stepsPrefixClass}-item-description { font-size: 12px; color: #999; } .@{stepsPrefixClass}-item-custom .@{stepsPrefixClass}-item-icon { width: inherit; height: inherit; line-height: inherit; border-radius: 0; border: 0; background: none; > .@{stepsPrefixClass}-icon { font-size: 20px; top: -2.5px; transform: none; } } } ================================================ FILE: assets/variables.less ================================================ @stepsPrefixClass: ~"rc-steps"; @stepDescriptionMaxWidth: 100px; @stepNavContentMaxWidth: 140px; @primary-color: #108ee9; @process-icon-color: @primary-color; @process-title-color: rgba(0,0,0,.65); @process-description-color: @process-title-color; @process-tail-color: #e9e9e9; @wait-icon-color: #ccc; @wait-title-color: rgba(0,0,0,.43); @wait-description-color: @wait-title-color; @wait-tail-color: @process-tail-color; @finish-icon-color: @process-icon-color; @finish-title-color: @wait-title-color; @finish-description-color: @finish-title-color; @finish-tail-color: @process-icon-color; @error-icon-color: #f50; @error-title-color: @error-icon-color; @error-description-color: @error-icon-color; @error-tail-color: @process-tail-color; ================================================ FILE: assets/vertical.less ================================================ @import 'variables'; .@{stepsPrefixClass}-vertical { display: block; .@{stepsPrefixClass}-item { display: block; overflow: visible; &-icon { float: left; &-inner { margin-right: 16px; } } &-section { min-height: 48px; overflow: hidden; display: block; } &-title { line-height: 26px; &:after { display: none; } } &-description { padding-bottom: 12px; } } &.@{stepsPrefixClass}-small { .@{stepsPrefixClass}-item-title { line-height: 18px; } } } ================================================ FILE: bunfig.toml ================================================ [install] peer = false ================================================ FILE: docs/demo/alternativeLabel.md ================================================ --- title: alternativeLabel nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/composable.md ================================================ --- title: composable nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/custom-svg-icon.md ================================================ --- title: custom-svg-icon nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/customIcon.md ================================================ --- title: customIcon nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/dynamic.md ================================================ --- title: dynamic nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/errorStep.md ================================================ --- title: errorStep nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/inline.md ================================================ --- title: inline nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/nav-base.md ================================================ --- title: nav-base nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/nextStep.md ================================================ --- title: nextStep nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/progressDot.md ================================================ --- title: progressDot nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/simple.md ================================================ --- title: simple nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/smallSize.md ================================================ --- title: smallSize nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/stepIcon.md ================================================ --- title: stepIcon nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/vertical.md ================================================ --- title: vertical nav: title: Demo path: /demo --- ================================================ FILE: docs/demo/verticalSmall.md ================================================ --- title: verticalSmall nav: title: Demo path: /demo --- ================================================ FILE: docs/examples/alternativeLabel.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; const description = '这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊'; export default () => ( ); ================================================ FILE: docs/examples/composable.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; const description = '这里是多信息的描述啊描述啊描述啊描述啊哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶'; export default () => ( ); ================================================ FILE: docs/examples/custom-svg-icon.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; function getFinishIcon() { const path = 'M923 283.6c-13.4-31.1-32.6-58.9-56.9-82.8-24.3-23.8-52.' + '5-42.4-84-55.5-32.5-13.5-66.9-20.3-102.4-20.3-49.3 0-97.4 13.5-139' + '.2 39-10 6.1-19.5 12.8-28.5 20.1-9-7.3-18.5-14-28.5-20.1-41.8-25.5' + '-89.9-39-139.2-39-35.5 0-69.9 6.8-102.4 20.3-31.4 13-59.7 31.7-84 ' + '55.5-24.4 23.9-43.5 51.7-56.9 82.8-13.9 32.3-21 66.6-21 101.9 0 33' + '.3 6.8 68 20.3 103.3 11.3 29.5 27.5 60.1 48.2 91 32.8 48.9 77.9 99' + '.9 133.9 151.6 92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.' + '7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3 56-51.7 10' + '1.1-102.7 133.9-151.6 20.7-30.9 37-61.5 48.2-91 13.5-35.3 20.3-70 ' + '20.3-103.3 0.1-35.3-7-69.6-20.9-101.9z'; return ( ); } function getErrorIcon() { const path1 = 'M512 0C229.2 0 0 229.2 0 512s229.2 512 512 512 512-229' + '.2 512-512S794.8 0 512 0zm311.1 823.1c-40.4 40.4-87.5 72.2-139.9 9' + '4.3C629 940.4 571.4 952 512 952s-117-11.6-171.2-34.5c-52.4-22.2-99' + '.4-53.9-139.9-94.3-40.4-40.4-72.2-87.5-94.3-139.9C83.6 629 72 571.' + '4 72 512s11.6-117 34.5-171.2c22.2-52.4 53.9-99.4 94.3-139.9 40.4-4' + '0.4 87.5-72.2 139.9-94.3C395 83.6 452.6 72 512 72s117 11.6 171.2 3' + '4.5c52.4 22.2 99.4 53.9 139.9 94.3 40.4 40.4 72.2 87.5 94.3 139.9C' + '940.4 395 952 452.6 952 512s-11.6 117-34.5 171.2c-22.2 52.4-53.9 9' + '9.5-94.4 139.9z'; const path2 = 'M640.3 765.5c-19.9 0-36-16.1-36-36 0-50.9-41.4-92.3-92' + '.3-92.3s-92.3 41.4-92.3 92.3c0 19.9-16.1 36-36 36s-36-16.1-36-36c0' + '-90.6 73.7-164.3 164.3-164.3s164.3 73.7 164.3 164.3c0 19.9-16.1 36' + '-36 36zM194.2 382.4a60 60 0 1 0 120 0 60 60 0 1 0-120 0zM709.5 382' + '.4a60 60 0 1 0 120 0 60 60 0 1 0-120 0z'; return ( ); } const icons = { finish: getFinishIcon(), error: getErrorIcon(), }; const description = 'This is a description'; export default () => ( ); ================================================ FILE: docs/examples/customIcon.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; // eslint-disable-next-line react/prop-types const Icon = ({ type }) => ; export default () => ( }, { title: '步骤2', icon: 'apple' }, { title: '步骤1', icon: 'github' }, ]} /> ); ================================================ FILE: docs/examples/dynamic.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React, { useState } from 'react'; import Steps from '@rc-component/steps'; export default () => { const [items, setItems] = useState([ { title: '已完成', description: '这里是多信息的描述啊描述啊描述啊描述啊哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶', }, { title: '进行中', description: '这里是多信息的描述啊描述啊描述啊描述啊哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶', }, { title: '待运行', description: '这里是多信息的描述啊描述啊描述啊描述啊哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶', }, { title: '待运行', description: '这里是多信息的描述啊描述啊描述啊描述啊哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶', }, ]); const addStep = () => { const newSteps = [...items]; newSteps.push({ title: '待运行', description: '新的节点', }); setItems(newSteps); }; return (
); }; ================================================ FILE: docs/examples/errorStep.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; const description = '这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊'; export default () => ( ); ================================================ FILE: docs/examples/inline.jsx ================================================ import '../../assets/index.less'; import React, { useState } from 'react'; import Steps from '@rc-component/steps'; export default () => { const [current, setCurrent] = useState(0); return ( <>
React.cloneElement(stepItem, { title: item.description })} /> ); }; ================================================ FILE: docs/examples/nav-base.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React, { useState } from 'react'; import Steps from '@rc-component/steps'; export default () => { const [current, setCurrent] = useState(0); const onChange = (current) => { // eslint-disable-next-line no-console console.log('onChange:', current); setCurrent(current); }; const containerStyle = { border: '1px solid rgb(235, 237, 240)', marginBottom: 24, }; const description = 'This is a description.'; return (
); }; ================================================ FILE: docs/examples/nextStep.css ================================================ .my-step-form { width: 100%; } .my-step-form > div { margin-bottom: 20px; } .my-step-container { width: 100%; } ================================================ FILE: docs/examples/nextStep.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import './nextStep.css'; import React from 'react'; import Steps from '@rc-component/steps'; function generateRandomSteps() { const n = Math.floor(Math.random() * 3) + 3; const arr = []; for (let i = 0; i < n; i++) { arr.push({ title: `步骤${i + 1}`, }); } return arr; } const steps = generateRandomSteps(); class MyForm extends React.Component { state = { currentStep: Math.floor(Math.random() * steps.length), }; nextStep = () => { const { currentStep } = this.state; let s = currentStep + 1; if (s === steps.length) { s = 0; } this.setState({ currentStep: s, }); }; render() { const { currentStep: cs } = this.state; this.stepsRefs = []; return (
这个demo随机生成3~6个步骤,初始随机进行到其中一个步骤
当前正在执行第{cs + 1}步
({ ref: (c) => { this.stepsRefs[i] = c; }, key: i, title: s.title, }))} />
); } } export default MyForm; ================================================ FILE: docs/examples/progressDot.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; const description = '这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊'; export default () => ( ); ================================================ FILE: docs/examples/simple.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; const description = '这里是多信息的描述啊描述啊描述啊描述啊哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶哦耶'; const ControlSteps = () => { const [current, setCurrent] = React.useState(0); return ( { // eslint-disable-next-line no-console console.log('Change:', val); setCurrent(val); }} items={[ { title: '已完成', }, { title: '进行中', }, { title: '待运行', description: 'Hello World!', }, { title: '待运行', }, ]} /> ); }; export default () => (
); ================================================ FILE: docs/examples/smallSize.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; // eslint-disable-next-line react/prop-types const Icon = ({ type }) => ; export default () => (
, }, { title: '步骤3', icon: 'apple', }, { title: '待运行', icon: 'github', }, ]} />
); ================================================ FILE: docs/examples/stepIcon.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; function stepIcon({ status, node }) { const isProcessing = status === 'process'; return isProcessing ?
{node}
: node; } export default () => { const [current, setCurrent] = React.useState(0); return ( <> ); }; ================================================ FILE: docs/examples/vertical.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; const description = '这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊'; export default () => ( ); ================================================ FILE: docs/examples/verticalSmall.jsx ================================================ import '../../assets/index.less'; import '../../assets/iconfont.less'; import React from 'react'; import Steps from '@rc-component/steps'; const description = '这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊这里是多信息的描述啊'; export default () => ( ); ================================================ FILE: docs/index.md ================================================ --- hero: title: "@rc-component/steps" description: React Steps Component --- ================================================ FILE: index.js ================================================ 'use strict'; module.exports = require('./src/'); ================================================ FILE: jest.config.js ================================================ module.exports = { setupFiles: ['./tests/setup.js'], }; ================================================ FILE: package.json ================================================ { "name": "@rc-component/steps", "version": "1.2.2", "description": "steps ui component for react", "keywords": [ "react", "react-component", "react-steps" ], "homepage": "http://github.com/react-component/steps", "bugs": { "url": "http://github.com/react-component/steps/issues" }, "repository": { "type": "git", "url": " git+ssh://git@github.com/react-component/steps.git" }, "license": "MIT", "maintainers": [ { "name": "afc163", "email": "afc163@gmail.com" } ], "main": "./lib/index", "module": "./es/index", "types": "./lib/index.d.ts", "files": [ "assets/*.css", "es", "lib" ], "scripts": { "compile": "father build && lessc assets/index.less assets/index.css", "coverage": "rc-test --coverage", "docs:build": "dumi build", "docs:deploy": "gh-pages -d .doc", "gh-pages": "npm run docs:build && npm run docs:deploy", "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md", "prepare": "husky install && dumi setup", "prepublishOnly": "npm run compile && rc-np", "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", "postpublish": "npm run gh-pages", "start": "dumi dev", "test": "rc-test", "now-build": "npm run docs:build" }, "lint-staged": { "**/*.{js,jsx,tsx,ts,md,json}": [ "prettier --write" ] }, "dependencies": { "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "devDependencies": { "@rc-component/father-plugin": "^2.0.2", "@rc-component/np": "^1.0.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.6", "@types/jest": "^29.4.0", "@types/node": "^24.5.2", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@umijs/fabric": "^4.0.1", "dumi": "^2.0.0", "eslint": "^8.55.0", "eslint-plugin-jest": "^27.6.0", "eslint-plugin-unicorn": "^50.0.1", "father": "^4", "gh-pages": "^6.1.0", "glob": "^10.0.0", "husky": "^8.0.1", "less": "^4.1.3", "lint-staged": "^15.2.0", "prettier": "^3.1.0", "rc-test": "^7.0.9", "react": "^18.0.0", "react-dom": "^18.0.0", "typescript": "^5.0.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" }, "engines": { "node": ">=8.x" } } ================================================ FILE: script/update-content.js ================================================ /* 用于 dumi 改造使用, 可用于将 examples 的文件批量修改为 demo 引入形式, 其他项目根据具体情况使用。 */ const fs = require('fs'); const glob = require('glob'); const paths = glob.sync('./docs/examples/*.jsx'); paths.forEach(path => { const name = path.split('/').pop().split('.')[0]; fs.writeFile( `./docs/demo/${name}.md`, `--- title: ${name} nav: title: Demo path: /demo --- `, 'utf8', function(error) { if(error){ console.log(error); return false; } console.log(`${name} 更新成功~`); } ) }); ================================================ FILE: src/Context.ts ================================================ import * as React from 'react'; import type { ComponentType, StepsProps } from './Steps'; export interface StepsContextProps { prefixCls: string; classNames: NonNullable; styles: NonNullable; ItemComponent: ComponentType; } export const StepsContext = React.createContext(null!); ================================================ FILE: src/Rail.tsx ================================================ import * as React from 'react'; import { clsx } from 'clsx'; import type { Status } from './Steps'; export interface RailProps { prefixCls: string; className: string; style: React.CSSProperties; status: Status; } export default function Rail(props: RailProps) { const { prefixCls, className, style, status } = props; const railCls = `${prefixCls}-rail`; // ============================= render ============================= return
; } ================================================ FILE: src/Step.tsx ================================================ /* eslint react/prop-types: 0 */ import * as React from 'react'; import { clsx } from 'clsx'; import KeyCode from '@rc-component/util/lib/KeyCode'; import type { Status, StepItem, StepsProps } from './Steps'; import Rail from './Rail'; import { UnstableContext } from './UnstableContext'; import StepIcon, { StepIconSemanticContext } from './StepIcon'; import { StepsContext } from './Context'; function hasContent(value: T) { return value !== undefined && value !== null; } export interface StepProps { // style prefixCls?: string; classNames: StepsProps['classNames']; styles: StepsProps['styles']; // data data: StepItem; nextStatus?: Status; active?: boolean; index: number; last: boolean; // render iconRender?: StepsProps['iconRender']; icon?: React.ReactNode; itemRender?: StepsProps['itemRender']; itemWrapperRender?: StepsProps['itemWrapperRender']; // Event onClick: (index: number) => void; } export default function Step(props: StepProps) { const { // style prefixCls, classNames, styles, // data data, last, nextStatus, active, index, // render itemRender, iconRender, itemWrapperRender, // events onClick, } = props; const itemCls = `${prefixCls}-item`; // ======================== Contexts ======================== const { railFollowPrevStatus } = React.useContext(UnstableContext); const { ItemComponent } = React.useContext(StepsContext); // ========================== Data ========================== const { onClick: onItemClick, title, subTitle, content, description, disabled, icon, status, className, style, classNames: itemClassNames = {}, styles: itemStyles = {}, ...restItemProps } = data; const mergedContent = content ?? description; const renderInfo = { item: { ...data, content: mergedContent, }, index, active, }; // ========================= Click ========================== const clickable = !!(onClick || onItemClick) && !disabled; const accessibilityProps: { role?: string; tabIndex?: number; onClick?: React.MouseEventHandler; onKeyDown?: React.KeyboardEventHandler; } = {}; if (clickable) { accessibilityProps.role = 'button'; accessibilityProps.tabIndex = 0; accessibilityProps.onClick = (e) => { onItemClick?.(e); onClick(index); }; accessibilityProps.onKeyDown = (e) => { const { which } = e; if (which === KeyCode.ENTER || which === KeyCode.SPACE) { onClick(index); } }; } // ========================= Render ========================= const mergedStatus = status || 'wait'; const hasTitle = hasContent(title); const hasSubTitle = hasContent(subTitle); const classString = clsx( itemCls, `${itemCls}-${mergedStatus}`, { [`${itemCls}-custom`]: icon, [`${itemCls}-active`]: active, [`${itemCls}-disabled`]: disabled === true, [`${itemCls}-empty-header`]: !hasTitle && !hasSubTitle, }, className, classNames.item, itemClassNames.root, ); let iconNode = ; if (iconRender) { iconNode = iconRender(iconNode, { ...renderInfo, components: { Icon: StepIcon, }, }) as React.ReactElement; } const wrapperNode = (
{/* Icon */} {iconNode}
{hasTitle && (
{title}
)} {hasSubTitle && (
{subTitle}
)} {!last && ( )}
{hasContent(mergedContent) && (
{mergedContent}
)}
); let stepNode: React.ReactNode = ( {itemWrapperRender ? itemWrapperRender(wrapperNode) : wrapperNode} ); if (itemRender) { stepNode = (itemRender(stepNode, renderInfo) || null) as React.ReactElement; } return stepNode; } ================================================ FILE: src/StepIcon.tsx ================================================ import * as React from 'react'; import { clsx } from 'clsx'; import { StepsContext } from './Context'; import pickAttrs from '@rc-component/util/lib/pickAttrs'; export interface StepIconSemanticContextProps { className?: string; style?: React.CSSProperties; } export const StepIconSemanticContext = React.createContext({}); export type StepIconProps = React.HTMLAttributes; const StepIcon = React.forwardRef((props, ref) => { const { className, style, children, ...restProps } = props; const { prefixCls, classNames, styles } = React.useContext(StepsContext); const { className: itemClassName, style: itemStyle } = React.useContext(StepIconSemanticContext); const itemCls = `${prefixCls}-item`; return (
{children}
); }); export default StepIcon; ================================================ FILE: src/Steps.tsx ================================================ /* eslint react/no-did-mount-set-state: 0, react/prop-types: 0 */ import { clsx } from 'clsx'; import React from 'react'; import Step from './Step'; import { StepsContext, type StepsContextProps } from './Context'; import type StepIcon from './StepIcon'; export type Status = 'error' | 'process' | 'finish' | 'wait'; const EmptyObject = {}; export type SemanticName = | 'root' | 'item' | 'itemWrapper' | 'itemHeader' | 'itemTitle' | 'itemSubtitle' | 'itemSection' | 'itemContent' | 'itemIcon' | 'itemRail'; export type ItemSemanticName = | 'root' | 'wrapper' | 'header' | 'title' | 'subtitle' | 'section' | 'content' | 'icon' | 'rail'; export type ComponentType = React.ComponentType | string; export type StepItem = { /** @deprecated Please use `content` instead. */ description?: React.ReactNode; content?: React.ReactNode; disabled?: boolean; icon?: React.ReactNode; status?: Status; subTitle?: React.ReactNode; title?: React.ReactNode; classNames?: Partial>; styles?: Partial>; } & Pick, 'onClick' | 'className' | 'style'>; export type StepIconRender = (info: { index: number; status: Status; title: React.ReactNode; // @deprecated Please use `content` instead. description: React.ReactNode; content: React.ReactNode; node: React.ReactNode; }) => React.ReactNode; export type RenderInfo = { index: number; active: boolean; item: StepItem; }; export interface StepsProps { // style prefixCls?: string; style?: React.CSSProperties; className?: string; classNames?: Partial>; styles?: Partial>; rootClassName?: string; // layout orientation?: 'horizontal' | 'vertical'; titlePlacement?: 'horizontal' | 'vertical'; // a11y /** Internal usage of antd. Do not deps on this. */ components?: { root?: ComponentType; item?: ComponentType; }; // data status?: Status; current?: number; initial?: number; items?: StepItem[]; onChange?: (current: number) => void; // render iconRender?: ( originNode: React.ReactElement, info: RenderInfo & { components: { Icon: typeof StepIcon; }; }, ) => React.ReactNode; itemRender?: (originNode: React.ReactElement, info: RenderInfo) => React.ReactNode; itemWrapperRender?: (originNode: React.ReactElement) => React.ReactNode; } export default function Steps(props: StepsProps) { const { // style prefixCls = 'rc-steps', style, className, classNames = EmptyObject as NonNullable, styles = EmptyObject as NonNullable, rootClassName, // layout orientation, titlePlacement, components, // data status = 'process', current = 0, initial = 0, onChange, items, // render iconRender, itemRender, itemWrapperRender, ...restProps } = props; // ============================= layout ============================= const isVertical = orientation === 'vertical'; const mergedOrientation = isVertical ? 'vertical' : 'horizontal'; const mergeTitlePlacement = !isVertical && titlePlacement === 'vertical' ? 'vertical' : 'horizontal'; // ============================= styles ============================= const classString = clsx( prefixCls, `${prefixCls}-${mergedOrientation}`, `${prefixCls}-title-${mergeTitlePlacement}`, rootClassName, className, classNames.root, ); // ============================== Data ============================== const mergedItems = React.useMemo(() => (items || []).filter(Boolean), [items]); const statuses = React.useMemo( () => mergedItems.map(({ status: itemStatus }, index) => { const stepNumber = initial + index; if (!itemStatus) { if (stepNumber === current) { return status; } else if (stepNumber < current) { return 'finish'; } return 'wait'; } return itemStatus; }), [mergedItems, status, current, initial], ); // ============================= events ============================= const onStepClick = (next: number) => { if (onChange && current !== next) { onChange(next); } }; // =========================== components =========================== const { root: RootComponent = 'div', item: ItemComponent = 'div' } = components || {}; // ============================ contexts ============================ const stepIconContext = React.useMemo( () => ({ prefixCls, classNames, styles, ItemComponent, }), [prefixCls, classNames, styles, ItemComponent], ); // ============================= render ============================= const renderStep = (item: StepItem, index: number) => { const stepIndex = initial + index; const itemStatus = statuses[index]; const nextStatus = statuses[index + 1]; const data = { ...item, status: itemStatus, }; return ( ); }; return ( {mergedItems.map(renderStep)} ); } ================================================ FILE: src/UnstableContext.ts ================================================ import * as React from 'react'; export interface UnstableContextProps { /** * Used for Timeline component `reverse` prop. * Safe to remove if refactor. */ railFollowPrevStatus?: boolean; } export const UnstableContext = React.createContext({}); ================================================ FILE: src/index.ts ================================================ import Steps, { type StepsProps } from './Steps'; import Step from './Step'; export { Step }; export type { StepsProps }; export { UnstableContext } from './UnstableContext'; export default Steps; ================================================ FILE: src/interface.ts ================================================ ================================================ FILE: tests/__snapshots__/index.test.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Steps components 1`] = `
  1. test
`; exports[`Steps render renders correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders current correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders progressDot correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders progressDot function correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders status correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders step with description 1`] = `
已完成
xx
进行中
xx
待运行
xx
待运行
xx
`; exports[`Steps render renders step with description and status 1`] = `
已完成
xx
进行中
xx
待运行
xx
待运行
xx
`; exports[`Steps render renders stepIcon function correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders titlePlacement correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders vertical correctly 1`] = `
已完成
进行中
待运行
待运行
`; exports[`Steps render renders with falsy children 1`] = `
已完成
xx
进行中
剩余 00:00:07
xx
待运行
xx
待运行
xx
`; exports[`Steps should render customIcon correctly 1`] = `
步骤1
步骤2
步骤3
`; ================================================ FILE: tests/index.test.tsx ================================================ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import Steps from '../src'; describe('Steps', () => { describe('render', () => { const description = 'xx'; const setSteps = (props) => ( ); it('renders correctly', () => { const { container } = render(setSteps({})); expect(container.firstChild).toMatchSnapshot(); }); it('renders without items', () => { expect(() => { render(setSteps({ items: undefined })); }).not.toThrow(); }); it('renders current correctly', () => { const { container } = render(setSteps({ current: 2 })); expect(container.firstChild).toMatchSnapshot(); }); it('renders status correctly', () => { const { container } = render(setSteps({ current: 2, status: 'error' })); expect(container.firstChild).toMatchSnapshot(); }); it('renders vertical correctly', () => { const { container } = render(setSteps({ direction: 'vertical' })); expect(container.firstChild).toMatchSnapshot(); }); it('renders titlePlacement correctly', () => { const { container } = render(setSteps({ titlePlacement: 'vertical' })); expect(container.firstChild).toMatchSnapshot(); }); it('renders progressDot correctly', () => { const { container } = render(setSteps({ progressDot: true })); expect(container.firstChild).toMatchSnapshot(); }); it('renders progressDot function correctly', () => { const { container } = render(setSteps({ progressDot: () => a })); expect(container.firstChild).toMatchSnapshot(); }); it('renders stepIcon function correctly', () => { const { container } = render(setSteps({ stepIcon: () => a })); expect(container.firstChild).toMatchSnapshot(); }); it('renders step with description', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); it('renders step with description and status', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); it('renders with falsy children', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); }); it('should render customIcon correctly', () => { const Icon = ({ type }) => ; const { container } = render( , }, { title: '步骤2', icon: 'apple', }, { title: '步骤3', icon: 'github', }, ]} />, ); expect(container.firstChild).toMatchSnapshot(); }); it('onChange', () => { const onChange = jest.fn(); const { container } = render( , ); const items = container.querySelectorAll('.rc-steps-item'); fireEvent.click(items[1]); expect(onChange).toHaveBeenCalledTimes(1); }); it('items out of render function', () => { const items = [ { title: '已完成', }, { title: '进行中', }, ]; let current = 0; const onChange = (val) => { current = val; }; const { container, rerender } = render( , ); const step = container.querySelectorAll('.rc-steps-item')[1]; fireEvent.click(step); rerender(); expect(container.querySelectorAll('.rc-steps-item')[1].classList).toContain( 'rc-steps-item-process', ); }); it('onClick', () => { const onClick = jest.fn(); const onChange = jest.fn(); const { container } = render( , ); const btn = container.querySelectorAll('.rc-steps-item')[0]; fireEvent.click(btn); expect(onClick).toHaveBeenCalled(); }); it('disabled', () => { const onChange = jest.fn(); const { container } = render( , ); const items = container.querySelectorAll('.rc-steps-item'); fireEvent.click(items[2]); expect(onChange).not.toBeCalled(); }); it('key board support', () => { const onChange = jest.fn(); const { container } = render( , ); const button = container.querySelectorAll('[role="button"]')[1]; fireEvent.keyDown(button, { key: 'Enter', keyCode: 13, which: 13 }); expect(onChange).toHaveBeenCalledWith(1); }); it('itemRender', () => { const { container } = render( { return
{oriNode}
; }} />, ); expect(container.querySelector('.bamboo')).toBeTruthy(); expect(container.querySelectorAll('.bamboo')).toHaveLength(1); expect(container.querySelector('.rc-steps-item')).toBeTruthy(); }); it('itemWrapperRender', () => { const { container } = render( { return
{oriNode}
; }} />, ); expect(container.querySelector('.bamboo')).toBeTruthy(); expect(container.querySelectorAll('.bamboo')).toHaveLength(1); expect(container.querySelector('.rc-steps-item')).toBeTruthy(); expect(container.querySelector('.rc-steps-item > .bamboo')).toBeTruthy(); }); it('iconRender', () => { const { container } = render( { return little; }} />, ); const iconEle = container.querySelector('.rc-steps-item-icon')!; expect(iconEle).toHaveClass('bamboo'); expect(iconEle.textContent).toBe('little'); }); it('components', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); }); ================================================ FILE: tests/semantic.test.tsx ================================================ import React from 'react'; import { render } from '@testing-library/react'; import Steps, { type StepsProps } from '../src'; import type { ItemSemanticName, SemanticName } from '../src/Steps'; describe('Steps.Semantic', () => { const renderSteps = (props: Partial) => ( ({ title: `Step ${index + 1}`, subTitle: `SubTitle ${index + 1}`, content: `Content ${index + 1}`, }))} {...props} /> ); it('semantic structure', () => { const classNames: Record = { root: 'custom-root', item: 'custom-item', itemWrapper: 'custom-item-wrapper', itemIcon: 'custom-item-icon', itemSection: 'custom-item-section', itemHeader: 'custom-item-header', itemTitle: 'custom-item-title', itemSubtitle: 'custom-item-subtitle', itemContent: 'custom-item-content', itemRail: 'custom-item-rail', }; const classNamesTargets: Record = { root: 'rc-steps', item: 'rc-steps-item', itemWrapper: 'rc-steps-item-wrapper', itemIcon: 'rc-steps-item-icon', itemSection: 'rc-steps-item-section', itemHeader: 'rc-steps-item-header', itemTitle: 'rc-steps-item-title', itemSubtitle: 'rc-steps-item-subtitle', itemContent: 'rc-steps-item-content', itemRail: 'rc-steps-item-rail', }; const styles: Record> = { root: { color: 'red' }, item: { color: 'blue' }, itemWrapper: { color: 'green' }, itemIcon: { color: 'yellow' }, itemSection: { color: 'purple' }, itemHeader: { color: 'orange' }, itemTitle: { color: 'pink' }, itemSubtitle: { color: 'cyan' }, itemContent: { color: 'magenta' }, itemRail: { color: 'lime' }, }; const { container } = render( renderSteps({ classNames, styles, }), ); Object.keys(classNames).forEach((key) => { const className = classNames[key as SemanticName]; const oriClassName = classNamesTargets[key as SemanticName]; const style = styles[key as SemanticName]; const element = container.querySelector(`.${className}`); expect(element).toBeTruthy(); expect(element).toHaveClass(oriClassName); expect(element).toHaveStyle(style); }); }); it('item semantic structure', () => { const classNames: Record = { root: 'custom-root', wrapper: 'custom-wrapper', header: 'custom-header', title: 'custom-title', subtitle: 'custom-subtitle', section: 'custom-section', content: 'custom-content', icon: 'custom-icon', rail: 'custom-rail', }; const classNamesTargets: Record = { root: 'rc-steps-item', wrapper: 'rc-steps-item-wrapper', header: 'rc-steps-item-header', title: 'rc-steps-item-title', subtitle: 'rc-steps-item-subtitle', section: 'rc-steps-item-section', content: 'rc-steps-item-content', icon: 'rc-steps-item-icon', rail: 'rc-steps-item-rail', }; const styles: Record> = { root: { color: 'red' }, wrapper: { color: 'green' }, header: { color: 'orange' }, title: { color: 'pink' }, subtitle: { color: 'cyan' }, section: { color: 'purple' }, content: { color: 'magenta' }, icon: { color: 'yellow' }, rail: { color: 'lime' }, }; const { container } = render( renderSteps({ items: Array.from({ length: 2 }, (_, index) => ({ title: `Title ${index + 1}`, subTitle: `SubTitle ${index + 1}`, content: `Content ${index + 1}`, classNames, styles, })), }), ); Object.keys(classNames).forEach((key) => { const className = classNames[key as SemanticName]; const oriClassName = classNamesTargets[key as SemanticName]; const style = styles[key as SemanticName]; const element = container.querySelector(`.${className}`); expect(element).toBeTruthy(); expect(element).toHaveClass(oriClassName); expect(element).toHaveStyle(style); }); }); }); ================================================ FILE: tests/setup.js ================================================ global.requestAnimationFrame = global.requestAnimationFrame || function raf(cb) { return setTimeout(cb, 0); }; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "esnext", "moduleResolution": "node", "baseUrl": "./", "jsx": "preserve", "declaration": true, "skipLibCheck": true, "esModuleInterop": true, "paths": { "@/*": ["src/*"], "@@/*": ["dumi/tmp/*"], "@rc-component/steps": ["src/index.ts"] } } }