50 天计划第 2 天

准备工作

今天是 50 天计划的第 2 天。今天要完成的是第 2 个项目 Progress Steps。可以先看一下页面效果,对大体内容有个基本了解。

首先在 pages/day 目录下新建文件夹 2,并添加 index.tsx 文件。此时页面结构如下:

pages
  |-- index.tsx
  |-- day
      |-- 2
          |-- index.tsx

pages/day/2/index.tsx 中写入以下内容:

const ProgressStepsPage = () => {
  return (
    <div>hello world</div>
  )
}

export default ProgressStepsPage

此时访问路由 http://127.0.0.1:5173/day/2,可以看到一个 hello world。

项目开发

按照惯例,我们先把布局和基本的样式处理一下。

const ProgressStepsItem = ({ step }: { step: number }) => {
  return (
    <li flex items-center>
      <div className={step > 0 ? 'w-80px h-4px bg-blue' : ''}></div>
      <div w-8 h-8 rounded-full flex items-center justify-center border-blue border-3 text-bluegray>
        {step + 1}
      </div>
    </li>
  )
}

const ProgressSteps = ({ steps }: { steps: number }) => {
  return (
    <div>
      <ul flex>
        {
          new Array(steps).fill(null).map((_, index) => (
            <ProgressStepsItem step={index} key={index} />
          ))
        }
      </ul>
      <div mt-8>
        <button bg="#3498db hover:#0f82c9" text-white rounded-2 p="x-30px y-6px" mr-4>Prev</button>
        <button bg="#3498db hover:#0f82c9" text-white rounded-2 p="x-30px y-6px">Next</button>
      </div>
    </div>
  )
}

const ProgressStepsPage = () => {
  return (
    <div flex items-center justify-center h-60vh>
      <ProgressSteps steps={4} />
    </div>
  )
}

export default ProgressStepsPage

逻辑部分与上一次的很相似,我们只需要维护一个当前的 stepIndex 就可以知道当前的 UI 视图情况。这里注意边界情况,stepIndex 的范围不能超出左右边界大小。通过 lastStep 传递给子组件当前最后一个被点亮的 step 是多少,然后子组件通过它即可获取自己的视图情况。

const ProgressSteps = ({ steps }: { steps: number }) => {
  const [stepIndex, setStepIndex] = useState(0)

  return (
    <div>
      <ul flex>
        {
          new Array(steps).fill(null).map((_, index) => (
            <ProgressStepsItem step={index} key={index} lastStep={stepIndex} />
          ))
        }
      </ul>
      <div mt-8>
        <button
          text-white rounded-2 p="x-30px y-6px" mr-4
          className={stepIndex <= 0 ? 'bg-gray cursor-not-allowed' : 'bg-#3498db hover:bg-#0f82c9'}
          onClick={() => {
            if (stepIndex - 1 >= 0) {
              setStepIndex(stepIndex - 1)
            }
          }}
        >
          Prev
        </button>
        <button
          text-white rounded-2 p="x-30px y-6px"
          className={stepIndex >= steps - 1 ? 'bg-gray cursor-not-allowed' : 'bg-#3498db hover:bg-#0f82c9'}
          onClick={() => {
            if (stepIndex + 1 < steps) {
              setStepIndex(stepIndex + 1)
            }
          }}
        >
          Next
        </button>
      </div>
    </div>
  )
}

子组件实现这里需要考虑的是,原效果是通过宽度伸缩实现的,所以这里也需要实现宽度伸缩。实现的原理很简单,只要给一个 absolute 定位的子元素,然后根据条件判断修改子元素的宽度即可。

const ProgressStepsItem = ({ step, lastStep }: { step: number; lastStep: number }) => {
  return (
    <li flex items-center>
      <div relative className={step > 0 ? 'bg-gray w-80px h-4px' : '' }>
        <div absolute bg-blue top-0 left-0 h-4px className={step > 0 && lastStep >= step ? 'w-80px' : 'w-0'}></div>
      </div>
      <div
        w-8 h-8 rounded-full flex items-center justify-center border-3 text-bluegray
        className={lastStep >= step ? 'border-blue' : 'border-gray'}
      >
        {step + 1}
      </div>
    </li>
  )
}

到这里效果就已经基本实现了,其余的就是一些动画参数的调整,具体的细节可以参考源码

©2022 Flower-F. All Rights Reserved.