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 };