diff --git a/.changeset/angry-crabs-cheer.md b/.changeset/angry-crabs-cheer.md new file mode 100644 index 00000000..a4ca014a --- /dev/null +++ b/.changeset/angry-crabs-cheer.md @@ -0,0 +1,5 @@ +--- +"@ossa/loki": major +--- + +add timeline diff --git a/packages/ossa-demo/src/app.config.ts b/packages/ossa-demo/src/app.config.ts index 59d7564a..72d65417 100644 --- a/packages/ossa-demo/src/app.config.ts +++ b/packages/ossa-demo/src/app.config.ts @@ -12,6 +12,7 @@ export default { "components/button/demo/index", "components/color/demo/index", "components/countdown/demo/index", + "components/timeline/demo/index", "components/tag/demo/index", "components/icon/demo/index", "components/radio/demo/index", diff --git a/packages/ossa-demo/src/components/timeline/__test__/e2e.js b/packages/ossa-demo/src/components/timeline/__test__/e2e.js new file mode 100644 index 00000000..35e3fa86 --- /dev/null +++ b/packages/ossa-demo/src/components/timeline/__test__/e2e.js @@ -0,0 +1,86 @@ +/// + +//author by mannix +//Date on 2024/11/12 +//Page in Timeline + +describe("Timeline", function () { + context("Timeline Basic Testing", function () { + before(function () { + // 进入Timeline页 + cy.visit("#/components/timeline/demo/index"); + }); + + it("solution #1: 基础用法", function () { + cy.get(".ossa-timeline").should(($tab) => { + expect($tab).to.have.length(1); + expect($tab.find(".ossa-timeline-item")).to.have.length(4); + }); + }); + + it("solution #2: 设置颜色", function () { + cy.get(".ossa-timeline").should(($tab) => { + expect($tab).to.have.length(1); + expect($tab.find(".ossa-timeline-item")).to.have.length(4); + expect($tab.find(".ossa-timeline-item").eq(1)).to.have.css( + "color", + "rgb(0, 128, 0)" + ); + expect($tab.find(".ossa-timeline-item").eq(2)).to.have.css( + "color", + "rgb(255, 0, 0)" + ); + expect($tab.find(".ossa-timeline-item").eq(3)).to.have.css( + "color", + "rgb(255, 255, 0)" + ); + }); + }); + + it("solution #3: 使用Icon", function () { + cy.get(".ossa-timeline").should(($tab) => { + expect($tab).to.have.length(1); + expect($tab.find(".ossa-timeline-item")).to.have.length(4); + expect($tab.find(".ossa-timeline-item").eq(0)).to.have.css( + "color", + "rgb(7, 193, 96)" + ); + expect($tab.find(".ossa-timeline-item").eq(1)).to.have.css( + "color", + "rgb(7, 193, 96)" + ); + expect($tab.find(".ossa-timeline-item").eq(2)).to.have.css( + "color", + "rgb(244, 143, 24)" + ); + expect($tab.find(".ossa-timeline-item").eq(3)).to.have.css( + "color", + "rgb(217, 217, 217)" + ); + }); + }); + + it("solution #4: 丰富内容", function () { + cy.get(".ossa-timeline").should(($tab) => { + expect($tab).to.have.length(1); + expect($tab.find(".ossa-timeline-item")).to.have.length(4); + expect($tab.find(".ossa-timeline-item").eq(0)).to.have.css( + "color", + "rgb(7, 193, 96)" + ); + expect($tab.find(".ossa-timeline-item").eq(1)).to.have.css( + "color", + "rgb(244, 143, 24)" + ); + expect($tab.find(".ossa-timeline-item").eq(2)).to.have.css( + "color", + "rgb(217, 217, 217)" + ); + expect($tab.find(".ossa-timeline-item").eq(3)).to.have.css( + "color", + "rgb(7, 193, 96)" + ); + }); + }); + }); +}); diff --git a/packages/ossa-demo/src/components/timeline/demo/index.config.ts b/packages/ossa-demo/src/components/timeline/demo/index.config.ts new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/packages/ossa-demo/src/components/timeline/demo/index.config.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/ossa-demo/src/components/timeline/demo/index.scss b/packages/ossa-demo/src/components/timeline/demo/index.scss new file mode 100644 index 00000000..1e05ea88 --- /dev/null +++ b/packages/ossa-demo/src/components/timeline/demo/index.scss @@ -0,0 +1 @@ +.demo-timeline {} diff --git a/packages/ossa-demo/src/components/timeline/demo/index.tsx b/packages/ossa-demo/src/components/timeline/demo/index.tsx new file mode 100644 index 00000000..a95ea9eb --- /dev/null +++ b/packages/ossa-demo/src/components/timeline/demo/index.tsx @@ -0,0 +1,171 @@ +import React, { useEffect, useState } from "react"; +import Taro from "@tarojs/taro"; +import { View } from "@tarojs/components"; +import classNames from "classnames"; +import DemoBlock from "../../demoBlock"; +import DemoHeader from "../../demoHeader"; +import { OsTimeline } from "ossaui"; +import DemoTable from "../../demoTable"; +import "./index.scss"; + +function getClassObject() { + const classObject = { + page: true, + ["demo-tag"]: true, + }; + return classObject; +} + +const initialListApi = { + title: "API", + head: ["参数", "说明", "类型", "默认值"], + data: [ + { + list: ["items", "时间轴数据", "Array", "[]"], + }, + { + list: ["onClick", "点击时触发,可选", "event对象", ""], + }, + { + list: ["pending", "是否有未完成项", "boolean", "false"], + }, + ], +}; + +const initialItemApi = { + title: '', + head: ["items参数", "说明", "类型", "默认值"], + data: [ + { + list: ["title", "标题", "string", ""], + }, + { + list: ["color", "颜色", "string", ""], + }, + { + list: ["icon", "图标", "string", ""], + }, + { + list: ["content", "内容", "Array", ""], + }, + { + list: ["iconColor", "图标颜色", "string", "#7F7F7F"], + }, + ], +}; +const initialListEvent = { + title: "Event", + head: ["函数名", "说明", "参数"], + data: [ + { + list: ["onClick", "点击时触发,可选", "event对象"], + }, + ], +}; + +export default function Index() { + const [listApi] = useState(initialListApi); + const [listItemApi] = useState(initialItemApi); + + const [listEvent] = useState(initialListEvent); + const classObject = getClassObject(); //组件修饰 + + useEffect(() => { + Taro.setNavigationBarTitle({ + title: "Timeline 时间轴", + }); + }, []); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/ossa-demo/src/pages/dataDisplay/index.tsx b/packages/ossa-demo/src/pages/dataDisplay/index.tsx index 2263d5ed..0ef1b958 100644 --- a/packages/ossa-demo/src/pages/dataDisplay/index.tsx +++ b/packages/ossa-demo/src/pages/dataDisplay/index.tsx @@ -38,6 +38,10 @@ export default class Index extends Component { title: "倒计时", id: "countdown", }, + { + title: "时间轴", + id: "timeline", + }, ], }; } diff --git "a/packages/ossa-doc/docs/\347\273\204\344\273\266/timeline.md" "b/packages/ossa-doc/docs/\347\273\204\344\273\266/timeline.md" new file mode 100644 index 00000000..4debca29 --- /dev/null +++ "b/packages/ossa-doc/docs/\347\273\204\344\273\266/timeline.md" @@ -0,0 +1,108 @@ +--- +sidebar_position: 30 +demo_url: "https://neteaseyanxuan.github.io/OSSA/#/components/timeline/demo/index" +--- + +# Timeline 时间轴 + +## 介绍 + +时间轴 + +## 用法 + +### 基础用法 + +```jsx + +``` + +### 设置颜色 + +```jsx + +``` + +### 使用 Icon + +```jsx + +``` + +### 丰富内容 + +```jsx + +``` + +## API + +### 属性 + +| 参数 | 说明 | 类型 | 默认值 | +| ------------ | ------------------------------------- | ------- | ------------------------------------------------------- | +| items | 时间轴的数据 | object[]| [] | +| pending | 最后一项是否为未完成态 | boolean | false | + +### items +| 参数 | 说明 | 类型 | 默认值 | +| ------------ | ------------------------------------- | ------- | ------------------------------------------------------- | +| title | 标题 | string | - | +| content | 内容 | string[]| [] | +| icon | 图标 | string | - | +| iconColor | 图标颜色 | string | #7F7F7 | +| color | 颜色 | string | - | + + +### 方法 + +| 函数名 | 说明 | 参数 | +| ------- | ---------------- | ---------- | +| onClick | 点击时触发,可选 | event 对象 | diff --git a/packages/ossa/src/components/timeline/index.tsx b/packages/ossa/src/components/timeline/index.tsx new file mode 100644 index 00000000..fb1bc7ba --- /dev/null +++ b/packages/ossa/src/components/timeline/index.tsx @@ -0,0 +1,90 @@ +import React from "react"; +import { View } from "@tarojs/components"; +import classNames from "classnames"; +import OsIcon from "../icon"; +import { OsTimelineProps } from "../../../types/index"; + +function getClassObject(props: OsTimelineProps) { + const { pending = false } = props; + + const classObject = { + ["ossa-timeline--pending"]: pending, + }; + return classObject; +} + +export default function Timeline(props: OsTimelineProps) { + const { items, onClick, className, customStyle } = props; + const rootClassName = "ossa-timeline"; //组件 + const classObject = getClassObject(props); //组件修饰 + const styleObject = customStyle; + + const itemElems = items.map((item, index) => { + const { title = "", color, icon, content = [], iconColor = '#7F7F7F' } = item; + + + const itemRootClassName = ["ossa-timeline-item"]; + if (color) itemRootClassName.push(`ossa-timeline-item--${color}`); + + const dotClass: string[] = []; + if (icon) { + dotClass.push("ossa-timeline-item__icon"); + } else { + dotClass.push("ossa-timeline-item__dot"); + } + + const handleItemClick = (idx: number, e: any) => { + if (onClick) { + onClick({ ...e, index: idx }); + } + }; + + return ( + handleItemClick(index, e)} + > + + + {icon && } + + + {title} + {content.map((sub, subIndex) => ( + + {sub} + + ))} + + + ); + }); + + return ( + + {itemElems} + + ); +} + +Timeline.defaultProps = { + pending: false, + items: [], + customStyle: {}, +}; + +Timeline.options = { + addGlobalClass: true, +}; diff --git a/packages/ossa/src/index.ts b/packages/ossa/src/index.ts index 0ecba0b7..3a3f71d3 100644 --- a/packages/ossa/src/index.ts +++ b/packages/ossa/src/index.ts @@ -34,3 +34,4 @@ export { default as OsRow } from "./components/row/index"; export { default as OsCol } from "./components/col/index"; export { default as OsCountdown } from "./components/countdown/index"; export { default as OsSwipeCell } from "./components/swipe-cell/index"; +export { default as OsTimeline } from "./components/timeline/index"; diff --git a/packages/ossa/src/style/_variable.scss b/packages/ossa/src/style/_variable.scss index 4bfea084..c551ac38 100644 --- a/packages/ossa/src/style/_variable.scss +++ b/packages/ossa/src/style/_variable.scss @@ -45,15 +45,12 @@ $--color-border-base-1:#7F7F7F !default; $--font-size-1:18px !default; // 数量角标 $--font-size-line-height-1:24px !default; - $--font-size-2:20px !default; // 标签文字 $--font-size-line-height-2:30px !default; - $--font-size-3:22px !default; // 标签文字 $--font-size-line-height-3:32px !default; - $--font-size-4:24px !default; // 次要描述 $--font-size-line-height-4:36px !default; @@ -304,5 +301,10 @@ $--fill-image-preview: $-fill-model-default !default; //蒙层 $--color-index-number: rgba(255, 255, 255, 0.8) !default; //指示器文字颜色 $--fill-index-number: rgba(255, 255, 255, 0.2) !default; //指示器背景色 - - +/* timeline */ +$--title-timeline-color: $--text-color-base !default; +$--desc-timeline-color: $--text-color-secondary !default; +$--dot-timeline-size: 15px !default; +$--timeline-dot-bg-color: $--fill-default !default; +$--timeline-dot-border-color: #78a4f4 !default; +$--line-timeline-color: $--color-line !default; diff --git a/packages/ossa/src/style/components/timeline.scss b/packages/ossa/src/style/components/timeline.scss new file mode 100644 index 00000000..26acb382 --- /dev/null +++ b/packages/ossa/src/style/components/timeline.scss @@ -0,0 +1,130 @@ +@import '../base.scss'; + +.ossa-timeline-item { + position: relative; + padding: 0 0 16px; + + /* elements */ + &__content { + margin-left: 18px; + min-height: 56px; + color: $--title-timeline-color; + font-size: $--font-size-2; + line-height: 1.1; + text-align: left; + + &--sub { + color: $--desc-timeline-color; + font-size: $--font-size-1; + margin-top: 5px; + } + } + + &__dot, + &__icon { + position: absolute; + left: 0; + top: 1px; + width: $--dot-timeline-size; + height: $--dot-timeline-size; + font-size: 0; + text-align: center; + vertical-align: middle; + border-radius: 10px; + background: $--timeline-dot-bg-color; + box-sizing: border-box; + z-index: 1; + + .ossa-icon { + font-size: $--font-size-2; + } + } + + &__dot { + &::after { + content: ''; + display: inline-block; + box-sizing: border-box; + width: $--dot-timeline-size; + height: $--dot-timeline-size; + border: 1PX solid transparent; + border-radius: 10px; + border-color: $--timeline-dot-border-color; + } + + &.ossa-timeline-item__icon { + &::after { + border-color: transparent; + } + } + } + + &__icon { + color: $--timeline-dot-border-color; + } + + &__tail { + position: absolute; + top: calc($--dot-timeline-size / 2); + bottom: calc($--dot-timeline-size / -2); + left: calc($--dot-timeline-size / 2) - 2px; + border-left: 1PX solid $--line-timeline-color; + } + + /* modifiers */ + &--green { + .ossa-timeline-item__icon { + color: $--color-green; + } + + .ossa-timeline-item__dot { + &::after { + border-color: $--color-green; + } + } + } + + &--red { + .ossa-timeline-item__icon { + color: $--color-red; + } + + .ossa-timeline-item__dot { + &::after { + border-color: $--color-red; + } + } + } + + &--yellow { + .ossa-timeline-item__icon { + color: $--color-yellow; + } + + .ossa-timeline-item__dot { + &::after { + border-color: $--color-yellow; + } + } + } +} + +.ossa-timeline { + .ossa-timeline-item:last-child { + .ossa-timeline-item__tail { + display: none; + } + } + + &--pending { + .ossa-timeline-item:nth-last-child(2) { + .ossa-timeline-item__content { + min-height: 80px; + } + + .ossa-timeline-item__tail { + border-left-style: dotted; + } + } + } +} diff --git a/packages/ossa/src/style/index.scss b/packages/ossa/src/style/index.scss index 73863923..7b7d020c 100644 --- a/packages/ossa/src/style/index.scss +++ b/packages/ossa/src/style/index.scss @@ -36,3 +36,4 @@ @import './components/row.scss'; @import './components/col.scss'; @import './components/swipe-cell.scss'; +@import './components/timeline.scss'; diff --git a/packages/ossa/types/index.d.ts b/packages/ossa/types/index.d.ts index 3193f7da..60515d66 100644 --- a/packages/ossa/types/index.d.ts +++ b/packages/ossa/types/index.d.ts @@ -93,4 +93,5 @@ export { SwitchProps as OsSwitchProps, Switch as OsSwitch } from "./switch"; export { RowProps as OsRowProps, Row as OsRow } from "./row"; export { ColProps as OsColProps, Col as OsCol } from "./col"; export { CountdownProps as OsCountdownProps, Countdown as OsCountdown, FormattedResType as OsFormattedResType } from "./countdown"; -export { SwipeCell as OsSwipeCell, SwipeCellProps as OsSwipeCellProps } from "./swipeCell"; \ No newline at end of file +export { SwipeCell as OsSwipeCell, SwipeCellProps as OsSwipeCellProps } from "./swipeCell"; +export { Timeline as OsTimeline, TimelineProps as OsTimelineProps } from "./timeline"; \ No newline at end of file diff --git a/packages/ossa/types/timeline.d.ts b/packages/ossa/types/timeline.d.ts new file mode 100644 index 00000000..ef2853e1 --- /dev/null +++ b/packages/ossa/types/timeline.d.ts @@ -0,0 +1,125 @@ +import { ComponentClass, ReactNode } from "react"; +import { CommonEventFunction } from "@tarojs/components/types/common"; +import OsComponent from "./base"; + +interface BaseTimelineItem { + /** + * 标题 + */ + title: ReactNode; + /** + * 子项内容 + */ + content?: ReactNode[]; + /** + * 预置的图标类型 + * @see https://ossa.miaode.com/docs/%E7%BB%84%E4%BB%B6/icon + */ + icon?: + | "richscan" + | "inform" + | "phone" + | "search" + | "return" + | "share" + | "share-circle" + | "close" + | "compile" + | "delete" + | "collect" + | "like" + | "upload-delete" + | "grade" + | "choose" + | "record" + | "check" + | "check-irrevocable" + | "arrows" + | "search-little" + | "detail-home" + | "detail-cart" + | "close-native" + | "pull-down-big" + | "home" + | "subject" + | "classify" + | "cart" + | "user" + | "shopping-mall" + | "my-group-buying" + | "choose-disable" + | "check-disable" + | "add-disable" + | "subtract-disable" + | "arrows-disable" + | "collect-selected" + | "choose-selected" + | "close-native-pressed" + | "grade-selected" + | "check-selected" + | "home-pressed" + | "subject-pressed" + | "cart-pressed" + | "user-pressed" + | "return-pressed" + | "share-pressed" + | "close-pressed" + | "compile-pressed" + | "delete-pressed" + | "like-selected" + | "classify-pressed" + | "share-circle-pressed" + | "detail-home-pressed" + | "detail-cart-pressed" + | "my-group-buying-select" + | "shopping-mall-select" + | "delete-input" + | "add" + | "add-disable" + | "avatar" + | "invisible" + | "service" + | "visible" + | "subtract-disable" + | "subtract" + | "voice" + | "voice-close" + | "photo" + | "sort" + | "sort-high" + | "sort-low" + | "subject" + | "close-h5" + | "add-photo"; + /** + * icon 颜色 + */ + color?: "green" | "red" | "yellow"; + /** + * 图标颜色 + * @default #7F7F7F + */ + iconColor?: string; +} +interface TimelineProps extends OsComponent { + /** + * 最后一项是否为未完成态 + * @default false + */ + pending?: boolean; + /** + * 需展示的内容 + */ + items: Array; + + /** + * 点击 item 触发的事件 + * @param {number} current 当前点击的 item 的索引值 + * @param {MouseEvent} event + */ + onClick?: CommonEventFunction; +} + +declare const Timeline: ComponentClass; + +export { Timeline, TimelineProps };