@abdul778/react-calendar-timeline 中文文档教程

发布于 3年前 浏览 45 项目主页 更新于 3年前

React Calendar Timeline

现代且响应迅速的 React 时间轴组件。

calendar demo

查看 示例在这里

Contents

Getting started

# via yarn
yarn add react-calendar-timeline

# via npm
npm install --save react-calendar-timeline

react-calendar-timelinereact, react-domdayjs

Usage

至少:

import Timeline from 'react-calendar-timeline'
// make sure you include the timeline stylesheet or the timeline will not be styled
import 'react-calendar-timeline/lib/Timeline.css'
import dayjs from 'dayjs'

const groups = [{ id: 1, title: 'group 1' }, { id: 2, title: 'group 2' }]

const items = [
  {
    id: 1,
    group: 1,
    title: 'item 1',
    start_time: dayjs(),
    end_time: dayjs().add(1, 'hour')
  },
  {
    id: 2,
    group: 2,
    title: 'item 2',
    start_time: dayjs().add(-0.5, 'hour'),
    end_time: dayjs().add(0.5, 'hour')
  },
  {
    id: 3,
    group: 1,
    title: 'item 3',
    start_time: dayjs().add(2, 'hour'),
    end_time: dayjs().add(3, 'hour')
  }
]

ReactDOM.render(
  <div>
    Rendered by react!
    <Timeline
      groups={groups}
      items={items}
      defaultTimeStart={dayjs().add(-12, 'hour')}
      defaultTimeEnd={dayjs().add(12, 'hour')}
    />
  </div>,
  document.getElementById('root')
)

API

注意!所有道具都必须是不可变的。 例如,这意味着如果您想更改其中一项的标题,请传入一个全新的项目数组,而不是更改旧数组中的标题。 这里有更多信息。

该组件可能需要很多props:

groups

需要一个 vanilla JS 数组或一个 immutableJS 数组,由具有以下属性的对象组成:

{
  id: 1,
  title: 'group 1',
  rightTitle: 'title in the right sidebar',
  stackItems?: true,
  height?: 30
}

如果你使用右侧边栏,你可以在这里传递可选的 rightTitle 属性。 如果您想用自定义高度覆盖计算出的高度,您可以在此处将 height 属性作为以像素为单位的 int 传递。 这对于分类组非常有用。

items

期望一个 vanilla JS 数组或一个 immutableJS 数组,由具有以下属性的对象组成:

{
  id: 1,
  group: 1,
  title: 'Random title',
  start_time: 1457902922261,
  end_time: 1457902922261 + 86400000,
  canMove: true,
  canResize: false,
  canChangeGroup: false,
  itemProps: {
    // these optional attributes are passed to the root <div /> of each item as <div {...itemProps} />
    'data-custom-attribute': 'Random content',
    'aria-hidden': true,
    onDoubleClick: () => { console.log('You clicked double!') },
    className: 'weekend',
    style: {
      background: 'fuchsia'
    }
  }
}

首选(最快)选项是为 start_timeend_time 提供以毫秒为单位的 Unix 时间戳. 转换为它们的对象(JavaScript Datedayjs())也可以工作,但会慢很多。

defaultTimeStart and defaultTimeEnd

除非被 visibleTimeStartvisibleTimeEnd 覆盖,否则请指定日历的开始和结束位置。 此参数需要一个日期。

visibleTimeStart and visibleTimeEnd

日历的确切视口。 指定这些后,日历中的滚动必须由 onTimeChange 函数编排。 此参数需要一个以毫秒为单位的 Unix 时间戳。

请注意,您需要提供 defaultTimeStart/EndvisibleTimeStart/End 才能使时间线正常运行

selected

一个数组,其 id 对应于项目中的 id( item.id)。 如果设置了此道具,您必须在 onItemSelect 处理程序中自行管理所选项目,以使用新 ID 更新属性,并使用 onItemDeselect 处理程序清除选择。 这会覆盖单击时选择一项的默认行为。

keys

指定 itemsgroups 对象中的键的数组。 默认为

{
  groupIdKey: 'id',
  groupTitleKey: 'title',
  groupRightTitleKey: 'rightTitle',
  itemIdKey: 'id',
  itemTitleKey: 'title',    // key for item div content
  itemDivTitleKey: 'title', // key for item div title (<div title="text"/>)
  itemGroupKey: 'group',
  itemTimeStartKey: 'start_time',
  itemTimeEndKey: 'end_time',
}

className

附加类名称作为根时间轴元素的字符串。

sidebarWidth

侧边栏的宽度(以像素为单位)。 如果设置为 0,则不会呈现边栏。 默认为 150

sidebarContent

此处传递的所有内容都将显示在左侧边栏上方。 使用它来显示小过滤器等。 默认为 null

rightSidebarWidth

右侧边栏的宽度(以像素为单位)。 如果设置为 0,则不呈现右侧边栏。 默认为 0

rightSidebarContent

此处传递的所有内容都将显示在右侧边栏上方。 使用它来显示小过滤器等。 默认为 null

dragSnap

拖动项目时的捕捉单位。 默认为 15 * 60 * 1000 或 15 分钟。 如果是这样,项目将在拖动时捕捉到 15 分钟的间隔。

minResizeWidth

可以调整大小时,时间线条目的最小宽度(以像素为单位)。 如果未达到,您必须放大以调整更多大小。 默认为 20

lineHeight

日历中一行的高度(以像素为单位)。 默认 30

itemHeightRatio

项目占用行高的百分比是多少? 默认 0.65

minZoom

日历可以缩放到的最短时间(以毫秒为单位)。 默认 60 * 60 * 1000(1 小时)

maxZoom

日历可以缩放到的最长时间(以毫秒为单位)。 默认 5 * 365.24 * 86400 * 1000(5 年)

clickTolerance

我们可以将背景拖动多少像素以将其计为对背景的点击。 默认 3

canMove

项目可以拖动吗? 可以在 items 数组中被覆盖。 默认为 true

canChangeGroup

项目可以在组之间移动吗? 可以在 items 数组中被覆盖。 默认为 true

canResize

项目可以调整大小吗? 可以在 items 数组中被覆盖。 接受的值:false“left”“right”“both”。 默认为 "right"。 如果您传递 true,它将被视为 “正确”,不会破坏与 0.9 及以下版本的兼容性。

useResizeHandle

向元素附加一个特殊的 .rct-drag-right 句柄,只有从那里拖动时才调整大小。 默认为 false

stackItems

项目堆叠在一起,因此在时间冲突时没有视觉重叠。 可以在 groups 数组中覆盖。 默认为 false。 需要毫秒或 Dayjs 时间戳,而不是原生 JavaScript Date 对象。

traditionalZoom

向上/向下滚动鼠标时放大。 默认为 false

itemTouchSendsClick

通常点击(触摸)一个项目将其选中。 如果设置为 true,点击将具有相同的效果,与第一次单击选择然后再次单击以打开并发送 onItemClick 事件相同。 默认为 false

timeSteps

用什么步骤来显示不同的单位。 例如,15 表示 minute 表示仅显示第 0、15、30 和 45 分钟。

默认值:

{
  second: 1,
  minute: 1,
  hour: 1,
  day: 1,
  month: 1,
  year: 1
}

scrollRef

Ref 回调,获取对滚动体元素的 DOM 引用。 可以用于以编程方式滚动。

onItemDrag(itemDragObject)

当项目正在移动或调整大小时调用。 返回具有以下属性的对象:

propertytypedescription
eventTypestringretuns either move or resize
itemIdnumberID of the item being moved or resized
timenumberUNIX timestamp in milliseconds
edgestringon resize, returns a value of either left or right
newGroupOrdernumberon move, index position of the new group that the item is moving to

onItemMove(itemId, dragTime, newGroupOrder)

移动项目时的回调。 返回 1) 项目的 ID,2) 新的开始时间和 3) groups 数组中新组的索引。

onItemResize(itemId, time, edge)

调整项目大小时回调。 返回 1) 项目的 ID,2) 项目的新开始或结束时间 3) 拖动的边缘(leftright

onItemSelect(itemId, e, time)

选择项目时调用. 这是在第一次点击一个项目时发送的。 time 是与您在时间轴中单击/选择项目的位置相对应的时间。

onItemDeselect(e)

取消选择项目时调用。 用于清除受控的选定道具。

onItemClick(itemId, e, time)

单击项目时调用。 注意:项目必须在单击之前被选中……除非它是触摸事件并且 itemTouchSendsClick 已启用。 time 是与您在时间轴中单击项目的位置相对应的时间。

onItemDoubleClick(itemId, e, time)

双击项目时调用。 time 是与您在时间轴中双击项目的位置相对应的时间。

onItemContextMenu(itemId, e, time)

当鼠标右键单击项目时调用。 time 是与您上下文单击时间轴中项目的位置相对应的时间。 注意:如果设置了此属性,则不会出现默认上下文菜单。

onCanvasClick(groupId, time, e)

单击画布上的空白点时调用。 获取组 ID 和时间作为参数。 例如,在此之后打开一个“新项目”窗口。

onCanvasDoubleClick(groupId, time, e)

当双击画布上的空白点时调用。 获取组 ID 和时间作为参数。

onCanvasContextMenu(groupId, time, e)

当鼠标右键单击画布时调用。 注意:如果设置了此属性,则默认上下文菜单不会出现

onZoom(timelineContext)

在缩放时间轴时调用,通过鼠标/捏合缩放或单击标题以更改时间轴单位

moveResizeValidator(action, itemId, time, resizeEdge)

此函数在移动项目或调整大小时调用。 当提议的移动违反业务逻辑时,由此函数返回新版本的 change

参数 actionmoveresize 之一。

参数 resizeEdge 是在调整 leftright 之一的大小时。

参数 time 描述了项目开始时间(移动)或开始或结束时间(调整大小)的建议新时间。

该函数必须以毫秒为单位返回一个新的 unix 时间戳……或者如果建议的新时间不干扰业务逻辑,则只返回 time

例如,要防止将项目移到过去,但要将它们保持在 15 分钟的间隔,请使用以下代码:

function (action, item, time, resizeEdge) {
  if (time < new Date().getTime()) {
    var newTime = Math.ceil(new Date().getTime() / (15*60*1000)) * (15*60*1000);
    return newTime;
  }

  return time
}

onTimeChange(visibleTimeStart, visibleTimeEnd, updateScrollCanvas)

当用户尝试滚动时调用的函数。 使用更新后的 visibleTimeStart 和 visibleTimeEnd(以毫秒为单位的 unix 时间戳)调用传递的 updateScrollCanvas(start, end) 以更改滚动行为,例如限制滚动。

下面是一个将时间轴限制为仅显示从现在开始 6 个月到 6 个月结束的日期的示例。

// this limits the timeline to -6 months ... +6 months
const minTime = dayjs().add(-6, 'months').valueOf()
const maxTime = dayjs().add(6, 'months').valueOf()

function (visibleTimeStart, visibleTimeEnd, updateScrollCanvas) {
  if (visibleTimeStart < minTime && visibleTimeEnd > maxTime) {
    updateScrollCanvas(minTime, maxTime)
  } else if (visibleTimeStart < minTime) {
    updateScrollCanvas(minTime, minTime + (visibleTimeEnd - visibleTimeStart))
  } else if (visibleTimeEnd > maxTime) {
    updateScrollCanvas(maxTime - (visibleTimeEnd - visibleTimeStart), maxTime)
  } else {
    updateScrollCanvas(visibleTimeStart, visibleTimeEnd)
  }
}

onBoundsChange(canvasTimeStart, canvasTimeEnd)

当日历画布中的边界发生变化时调用。 例如,使用它来加载要显示的新数据。 (参见下面的“幕后”)。 canvasTimeStartcanvasTimeEnd 是以毫秒为单位的 unix 时间戳。

itemRenderer

用于呈现自定义项目的渲染道具功能。 该函数提供了多个可用于呈现每个项目的参数。

提供给函数的参数有两种类型:具有项目和时间轴状态的上下文参数,以及道具获取函数

Render props params

context
  • item 具有我们作为道具传递给日历的项目。

  • timelineContext

propertytypedescription
timelineWidthnumberreturns the full width of the timeline.
visibleTimeStartnumberreturns the exact start of view port of the calendar
visibleTimeEndnumberreturns the exact end of view port of the calendar.
canvasTimeStartnumberdenotes the start time in ms of the canvas timeline
canvasTimeEndnumberdenotes the end time in ms of the canvas timeline
  • itemContext
propertytypedescription
dimensionsobjectreturns the dimensions of the item which includes collisionLeft, collisionWidth, height, isDragging, left, order, originalLeft, stack, top, and width
useResizeHandlebooleanreturns the prop useResizeHandle from calendar root component
titlestringreturns title to render in content element.
canMovebooleanreturns if the item is movable.
canResizeLeftbooleanreturns if the item can resize from the left
canResizeRightbooleanreturns if the item can resize from the right.
selectedbooleanreturns if the item is selected.
draggingbooleanreturns if the item is being dragged
dragStartobjectreturns x and y of the start dragging point of the item.
dragTimenumbercurrent drag time.
dragGroupDeltanumberreturns number of groups the item moved. if negative, moving was to top. If positive, moving was to down
resizingbooleanreturns if the item is being resized.
resizeEdgeleft, rightthe side from which the component is being resized form
resizeStartnumberreturns the x value from where the component start moving
resizeTimenumbercurrent resize time
widthbooleanreturns the width of the item (same as in dimensions)
prop getters functions

这些函数用于将道具应用到您呈现的元素。 这为您提供了最大的灵活性,可以在您喜欢的时间和地点渲染内容。

而不是自己在元素上应用道具并避免您的道具被覆盖(或覆盖返回的道具)。 您可以将对象传递给 prop getter 以避免任何问题。 该对象将只接受我们的组件管理的一些属性,因此组件确保正确组合它们。

propertytypedescription
getItemPropsfunction(props={})returns the props you should apply to the root item element.
getResizePropsfunction(props={})returns two sets of props to apply on the left and right elements as resizing elements if you have useResizeHandle prop set to true
  • getItemProps 返回您应该应用于根项目元素的道具。 返回的道具是:

  • 键:项目ID

  • ref:获取项目引用的函数

  • className:要应用于项目的类

  • 名 onMouseDown:事件处理程序

  • onMouseUp:事件处理程序

  • onTouchStart:事件处理程序

  • onTouchEnd:事件处理程序

  • onDoubleClick:事件处理程序

  • onContextMenu:事件处理程序

  • 样式: inline object

    ** 给定的样式只会覆盖不需要定位项目的样式。 其他样式,如 colorradius 和其他

    这些属性可以使用带有属性的 prop 参数来覆盖:

  • className:要添加的类名

  • onMouseDown:事件处理程序将在组件的事件处理程序

  • 之后调用 onMouseUp:事件处理程序将在组件的事件处理程序

  • 之后调用 onTouchStart:事件处理程序将在组件的事件处理程序

  • 之后调用 onTouchEnd:事件处理程序将在组件的事件处理程序之后调用

  • onDoubleClick:事件处理程序将被调用在组件的事件处理程序

  • :事件处理程序将在组件的事件处理程序之后调用

  • 之后

  • onContextMenu 如果 useResizeHandle 设置为 true。 返回的对象具有属性 left 下左侧元素的道具和 right 下要应用于右侧元素的道具:

  • left

    • ref: function to get element reference
    • style: style to be applied to the left element
    • className: class names to be applied to left className
  • right

    • ref: function to get element reference
    • style: style to be applied to the right element
    • className: class names to be applied to left className

这些属性可以使用道具覆盖带有属性的参数:

  • leftStyle: style to be added to left style
  • rightStyle: style to be added to right style
  • leftClassName: classes to be added to left handler
  • rightClassName: classes to be added to right handler

示例

let items = [
  {
    id: 1,
    group: 1,
    title: 'Title',
    tip: 'additional information',
    color: 'rgb(158, 14, 206)',
    selectedBgColor: 'rgba(225, 166, 244, 1)',
    bgColor : 'rgba(225, 166, 244, 0.6)',
    ...
  }
]

itemRenderer: ({
  item,
  itemContext,
  getItemProps,
  getResizeProps
}) => {
  const { left: leftResizeProps, right: rightResizeProps } = getResizeProps()
  return (
    <div {...getItemProps(item.itemProps)}>
      {itemContext.useResizeHandle ? <div {...leftResizeProps} /> : ''}

      <div
        className="rct-item-content"
        style={{ maxHeight: `${itemContext.dimensions.height}` }}
      >
        {itemContext.title}
      </div>

      {itemContext.useResizeHandle ? <div {...rightResizeProps} /> : ''}
    </div>
  )}

}

groupRenderer

React 组件,将用于呈现组中的内容 边栏。 将通过 groupisRightSidebar 作为道具。

let groups = [
  {
    id: 1,
    title: 'Title',
    tip: 'additional information'
  }
]

groupRenderer = ({ group }) => {
  return (
    <div className="custom-group">
      <span className="title">{group.title}</span>
      <p className="tip">{group.tip}</p>
    </div>
  )
}

resizeDetector

该组件会自动检测何时调整窗口大小。 您还可以选择检测何时调整组件的 DOM 元素的大小。 为此,传递一个 resizeDetector。 由于默认情况下捆绑它会添加约 18kb 的最小化 JS,因此您需要像这样选择加入:

import containerResizeDetector from 'react-calendar-timeline/lib/resize-detector/container'

<Timeline resizeDetector={containerResizeDetector} ... />

verticalLineClassNamesForTime(start, end)

呈现垂直线时调用此函数。 startend 是当前列的 unix 时间戳(以毫秒为单位)。 该函数应返回一个字符串数组,其中包含应应用于该列的类名。 这使得可以在视觉上突出显示公共假期或办公时间。 一个示例可能看起来像(参见:demo/vertical-classes):

verticalLineClassNamesForTime = (timeStart, timeEnd) => {
  const currentTimeStart = dayjs(timeStart)
  const currentTimeEnd = dayjs(timeEnd)

  for (let holiday of holidays) {
    if (
      holiday.isSame(currentTimeStart, 'day') &&
      holiday.isSame(currentTimeEnd, 'day')
    ) {
      return ['holiday']
    }
  }
}

请注意,此函数应尽可能针对性能进行优化,因为它将在时间轴的每次渲染上调用(即画布重置时、缩放时、等)

horizontalLineClassNamesForGroup(group)

渲染水平线时调用此函数。 group 是将呈现到当前行中的组。 该函数应返回一个字符串数组,其中包含应应用于该行的类名。 这使得可以在视觉上突出显示类别或重要项目。 一个示例可能如下所示:

horizontalLineClassNamesForGroup={(group) => group.root ? ["row-root"] : []}

Timeline Markers

时间轴标记是在特定日期点覆盖在画布上的标记。

Overview

通过将标记声明为 Timeline 组件的 children,可以将标记放置在时间轴中:

import Timeline, {
  TimelineMarkers,
  CustomMarker,
  TodayMarker,
  CursorMarker
} from 'react-calendar-timeline'

<Timeline>
  <TimelineMarkers>
    <TodayMarker />
    <CustomMarker date={today} />
    <CustomMarker date={tomorrow}>
      {/* custom renderer for this marker */}
      {({ styles, date }) => {
        const customStyles = {
          ...styles,
          backgroundColor: 'deeppink',
          width: '4px'
        }
        return <div style={customStyles} onClick={someCustomHandler} />
      }}
    </CustomMarker>
    <CursorMarker />
  </TimelineMarkers>
</Timeline>

每个标记都允许通过 作为子组件运行。 这允许用户呈现他们想要的任何东西(事件处理程序、自定义样式等)。 此自定义渲染器接收具有两个属性的对象:

样式:{position: 'absolute', top: 0, bottom: 0, left: number}

此对象必须 传递给根组件的 style 属性以便正确呈现。 请注意,您可以将此对象与任何其他属性合并。

日期:

此标记的 unix 时间戳中的数字日期。 这可用于更改您的标记的呈现方式(或者如果它完全呈现)

TimelineMarkers

包装您想要呈现的时间轴标记。

TodayMarker

放置在当前日期/时间的标记。

间隔:数字 | 默认值:10000

TodayMarker 刷新的频率。 值表示毫秒。

儿童:功能({样式:对象,日期:数字})=> JSX.Element

这个标记的自定义渲染器。 确保始终将 styles 传递给根组件的 style 属性,因为此对象包含标记的定位。

// custom interval
const twoSeconds = 2000

<TodayMarker interval={twoSeconds} />

//custom renderer

<TodayMarker>
  {({ styles, date }) =>
    // date is value of current date. Use this to render special styles for the marker
    // or any other custom logic based on date:
    // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'}
    <div style={styles} />
  }
</TodayMarker>

CustomMarker

放置在当前日期/时间的标记。

日期:数字 | 需要

在时间轴上放置标记的位置。 date 值是 unix 时间戳。

儿童:功能({样式:对象,日期:数字})=> JSX.Element

这个标记的自定义渲染器。 确保始终将 styles 传递给根组件的 style 属性,因为此对象包含标记的定位。

const today = Date.now()
<CustomMarker date={today} />

//custom renderer
<CustomMarker date={today}>
  {({ styles, date }) => <div style={styles} />}
</CustomMarker>

// multiple CustomMarkers
const markerDates = [
  {date: today, id: 1,},
  {date: tomorrow, id: 2,},
  {date: nextFriday, id: 3,},
]

<TimelineMarkers>
  {markerDates.map(marker => <CustomMarker key={marker.date} date={marker.date}/> )}
</TimelineMarkers>

CursorMarker

将鼠标悬停在时间轴上并匹配光标所在位置时显示的标记。

儿童:功能({样式:对象,日期:数字})=> JSX.Element

这个标记的自定义渲染器。 确保始终将 styles 传递给根组件的 style 属性,因为此对象包含标记的定位。

// render default marker for Cursor
<CursorMarker />

//custom renderer
<CursorMarker>
  {({ styles, date }) =>
    // date is value of current date. Use this to render special styles for the marker
    // or any other custom logic based on date:
    // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'}
    <div style={styles} />
  }
</CursorMarker>

Timeline Headers

时间轴标题是时间轴上方的部分,它由两个主要部分组成:首先,日历标题,它是一个可滚动的 div,包含名为 DateHeader 的日历日期。 其次,是侧边栏的标题,称为 SidebarHeader,左边一个,右边一个(可选)。

Default usage

对于默认情况,两个 DateHeader 呈现在时间轴上方,一个 primary 和一个 secondary。 辅助具有与时间线相同的日期单位和一个单位比时间线单位大一个的 primary

对于 SidebarHeader,将在左侧呈现一个空的 SidebarHeader,如果 rightSidebarWith 存在,则可选地呈现一个空的右侧边栏标题。

Overview

DateHeaderSidebarHeader 提供任何自定义标头。 您需要提供基本用法以提供任何自定义标头。 这些自定义标头应始终包含在组件子级的 TimelineHeaders 组件内。

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
  </TimelineHeaders>
<Timeline>

Components

自定义标头通过一组主要作为子组件模式的组件实现,旨在让用户最大程度地控制如何呈现标题。

TimelineHeader

是自定义headers的核心组件wrapper组件,

props

Proptypedescription
styleobjectapplied to the root component of headers
classNamestringapplied to the root component of the headers
calendarHeaderStyleobjectapplied to the root component of the calendar headers -scrolable div- DateHeader and CustomHeader)
calendarHeaderClassNamestringapplied to the root component of the calendar headers -scrolable div- DateHeader and CustomHeader)
headerReffunctionused to get the ref of the header element

SidebarHeader

负责渲染左右侧边栏上方的headers。

props

Proptypedescription
variantleft (default), rightrenders above the left or right sidebar
childrenFunctionfunction as a child component to render the header
headerDataanyContextual data to be passed to the item renderer as a data prop

Child function renderer

a Function 提供了多个参数,可用于呈现侧边栏标题

Prop getters functions

而不是自己在元素上应用道具,以避免您的道具被覆盖(或覆盖返回的道具)。 您可以将对象传递给 prop getter 以避免任何问题。 该对象将只接受我们的组件管理的一些属性,因此组件确保正确组合它们。

propertytypedescription
getRootPropsfunction(props={})returns the props you should apply to the root div element.
dataanyContextual data passed by headerData prop
  • getRootProps 返回的 props 是:

  • style: inline object style

    这些属性可以使用带有属性的 prop 参数来覆盖:

  • style: 应用于组件的额外内联样式

example

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <SidebarHeader variant="right" headerData={{someData: 'extra'}}>
      {({ getRootProps, data }) => {
        return <div {...getRootProps()}>Right {data.someData}</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
  </TimelineHeaders>
<Timeline>

注意 : Child function renderer 可以是一个组件,也可以是一个函数,为了方便

DateHeader

负责渲染时间轴日历部分上方的标题。 由按列划分标题的时间间隔组成。

props

Proptypedescription
styleobjectapplied to the root of the header
classNamestringapplied to the root of the header
unitsecond, minute, hour, day, week, month, year or primaryHeaderintervals between columns
labelFormatFunction or stringcontrols the how to format the interval label
intervalRendererFunctionrender prop to render each interval in the header
headerDataanyContextual data to be passed to the item renderer as a data prop
heightnumber default (30)height of the header in pixels

注意:将 primaryHeader 传递给 unit 标头将充当主标头,间隔单位比时间线单位大 1 个

Interval unit

间隔由 prop 决定:unit。 默认情况下,时间间隔的单位与时间轴相同。

如果 primaryHeader 被传递给 unit,它将用比时间线单位大 1 的单位覆盖该单位。

如果设置了 unit,则标头的单位将是通过 prop 传递的单位,并且可以是 dayjs 中的任何 unit of time

Label format

要格式化每个间隔标签,您可以使用 2 种类型的道具来格式化,它们是:

  • string:如果传递了一个字符串,它将被传递给 startTime 方法 format< /code> 这是一个 dayjs 对象。

  • Function:这是更强大的方法,可以最大程度地控制呈现的内容。 默认情况下,返回的 string 将在间隔内呈现

    type Unit = `second` | `minute` | `hour` | `day` | `month` | `year`
  ([startTime, endTime] : [Dayjs, Dayjs], unit: Unit, labelWidth: number, formatOptions: LabelFormat = defaultFormat ) => string
Default format

,我们根据标签宽度为日期提供响应格式。 它遵循以下规则:

longmediumLongmediumshort将通过来决定>labelWidth 值根据其在以下比例上的位置而定:

  |-----`short`-----50px-----`medium`-----100px-----`mediumLong`-----150px--------`long`-----
  // default format object
  const format : LabelFormat = {
  year: {
    long: 'YYYY',
    mediumLong: 'YYYY',
    medium: 'YYYY',
    short: 'YY'
  },
  month: {
    long: 'MMMM YYYY',
    mediumLong: 'MMMM',
    medium: 'MMMM',
    short: 'MM/YY'
  },
  week: {
    long: 'w',
    mediumLong: 'w',
    medium: 'w',
    short: 'w'
  },
  day: {
    long: 'dddd, LL',
    mediumLong: 'dddd, LL',
    medium: 'dd D',
    short: 'D'
  },
  hour: {
    long: 'dddd, LL, HH:00',
    mediumLong: 'L, HH:00',
    medium: 'HH:00',
    short: 'HH'
  },
  minute: {
    long: 'HH:mm',
    mediumLong: 'HH:mm',
    medium: 'HH:mm',
    short: 'mm',
  }
}

注意:这只是函数参数的一个实现。 您可以自己轻松地执行此操作

intervalRenderer

Render prop 函数用于渲染自定义间隔。 该函数提供了多个参数,可用于渲染每个间隔。

提供给函数的参数有两种类型:具有项目和时间轴状态的上下文参数,以及道具获取函数

注意 :为方便起见,renderProp 可以是组件或函数

interval context

一个对象包含以下内容properties:

propertytypedescription
intervalobject : {startTime, endTime, labelWidth, left}an object containing data related to the interval
intervalTextstringthe string returned from labelFormat prop
Prop getters functions

而不是自己在元素上应用道具并避免你的道具被覆盖(或覆盖返回的道具)。 您可以将对象传递给 prop getter 以避免任何问题。 该对象将只接受我们的组件管理的一些属性,因此组件确保正确组合它们。

propertytypedescription
getIntervalPropsfunction(props={})returns the props you should apply to the root div element.
  • getIntervalProps 返回的 props 是:

  • style:内联对象 style

  • onClick:事件处理程序

  • 这些属性可以使用带有以下属性的 prop 参数进行扩展:

  • style:应用于组件的额外内联样式

  • onClick:额外点击添加到正常 showPeriod 回调

data

数据的处理程序通过 headerData

example

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
    <DateHeader
      unit="day"
      labelFormat="MM/DD"
      style={{ height: 50 }}
      data={{someData: 'example'}}
      intervalRenderer={({ getIntervalProps, intervalContext, data }) => {
        return <div {...getIntervalProps()}>
          {intervalContext.intervalText}
          {data.example}
        </div>
      }}
    />
  </TimelineHeaders>
</Timeline>

CustomHeader

负责呈现时间轴日历部分上方的标题。 这是 DateHeader 的基础组件,以较少的功能提供更多的控制。

props

Proptypedescription
unitsecond, minute, hour, day, week, month, year (default timelineUnit)intervals
childrenFunctionfunction as a child component to render the header
headerDataanyContextual data to be passed to the item renderer as a data prop
heightnumber default (30)height of the header in pixels

unit

标头的单位将是通过 prop 传递的单位,并且可以是 dayjs 中的任何 unit of time。 unit 的默认值为 timelineUnit

Children

函数作为子组件渲染 header

提供给函数的参数有三种类型:上下文参数,其中包含项目和时间轴的状态,prop getters 函数和辅助函数.

注意:为方便起见,子函数渲染器可以是组件或函数

({
  timelineContext: {
    timelineWidth,
    visibleTimeStart,
    visibleTimeEnd,
    canvasTimeStart,
    canvasTimeEnd
  },
  headerContext: {
    unit,
    intervals: this.state.intervals
  },
  getRootProps: this.getRootProps,
  getIntervalProps: this.getIntervalProps,
  showPeriod,
  //contextual data passed through headerData
  data,
})=> React.Node
context

对象包含timelineheader 的上下文:

Timeline context
propertytypedescription
timelineWidthnumberwidth of timeline
visibleTimeStartnumberunix milliseconds of start visible time
visibleTimeEndnumberunix milliseconds of end visible time
canvasTimeStartnumberunix milliseconds of start buffer time
canvasTimeEndnumberunix milliseconds of end buffer time
Header context
propertytypedescription
intervalsarrayan array with all intervals
unitstringunit passed or timelineUnit

** interval: [startTime: Day, endTime: Day]

Prop getters functions

而不是自己在元素上应用 props 并避免你的 props 被覆盖(或覆盖返回的 props)。 您可以将对象传递给 prop getter 以避免任何问题。 该对象将只接受我们的组件管理的一些属性,因此组件确保正确组合它们。

propertytypedescription
getRootPropsfunction(props={})returns the props you should apply to the root div element.
getIntervalPropsfunction(props={})returns the props you should apply to the interval div element.
  • getIntervalProps 返回的 props 是:

  • style:内联对象 style

  • onClick:事件处理程序

  • 这些属性可以使用带有以下属性的 prop 参数进行扩展:

  • style:应用于组件的额外内联样式

  • onClick:额外点击添加到普通 showPeriod 回调 的处理程序

helpers:
propertytypedescription
showPeriodfunction(props={})returns the props you should apply to the root div element.
data:

通过 headerData 道具内容

example

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
    <CustomHeader height={50} headerData={{someData: 'data'}} unit="year">
      {({
        headerContext: { intervals },
        getRootProps,
        getIntervalProps,
        showPeriod,
        data,
      }) => {
        return (
          <div {...getRootProps()}>
            {intervals.map(interval => {
              const intervalStyle = {
                lineHeight: '30px',
                textAlign: 'center',
                borderLeft: '1px solid black',
                cursor: 'pointer',
                backgroundColor: 'Turquoise',
                color: 'white'
              }
              return (
                <div
                  onClick={() => {
                    showPeriod(interval.startTime, interval.endTime)
                  }}
                  {...getIntervalProps({
                    interval,
                    style: intervalStyle
                  })}
                >
                  <div className="sticky">
                    {interval.startTime.format('YYYY')}
                  </div>
                </div>
              )
            })}
          </div>
        )
      }}
    </CustomHeader>
  </TimelineHeaders>
</Timeline>

FAQ

My timeline is unstyled

您需要包含 Timeline.css 文件,通过静态文件引用或webpack 样式表捆绑。 该文件位于 lib/Timeline.css

How can I have items with different colors?

现在您可以使用项目渲染器来渲染具有不同颜色的项目 itemRenderer。 请参考 examples 沙盒示例

How can I add a sidebar on the right?

库支持右侧边栏。 right sidebar demo

要使用它,你需要在 组件中添加一个 props:

rightSidebarWidth={150}

并添加rightTitle 属性到组对象:

{
  id: 1,
  title: 'group 1',
  rightTitle: 'additional info about group 1'
}

如果您使用自定义标头,则需要在 TimelineHeader 下添加 SidebarHeader 组件,其变体 right< /code>

The timeline header doesn't fix to the top of the container when I scroll down.

你需要像 这个例子。

I'm using Babel with Rollup or Webpack 2+ and I'm getting strange bugs with click events

这些模块打包器不使用此模块的转译 (ES5) 代码。 他们加载原始的 ES2015+ 源代码。 因此你的 babel 配置需要和我们的相匹配。 我们建议将 stage-0 preset 添加到您的 。 babelrc 以确保一切按预期工作。

如果这太实验性,那么您至少需要添加 transform-class-properties插件,它处于第 2 阶段,可能还有 transform-object-rest -spread 来自第 3 阶段的插件。 但是在这种情况下,更容易确保您至少有 stage-2 启用。

有关详细信息,请参阅问题 51

或者,您可以像这样导入时间轴的转译版本:

// import Timeline from 'react-calendar-timeline'  // ESnext version
import Timeline from 'react-calendar-timeline/lib' // ES5 version

但是,这样做会失去 webpack 2 的一些功能,并且可能会获得稍大的包。

It doesn't work with create-react-app

这和上面的问题是一样的。 有关详细信息和选项,请参阅问题 134

What are the zIndex values for all the elements?

这在使用插件(作为子组件传递给组件)时很有用。 覆盖 CSS 以进行更改:

  • Horizontal Lines: 30
  • Vertical Lines: 40
  • Items: 80-88 (depending on selection, dragging, etc)
  • Header: 90

Behind the scenes

时间线在构建时考虑了速度、可用性和可扩展性。

速度:日历本身实际上是屏幕的 3 倍宽滚动画布。 所有向左和向右滚动的事件都是自然发生的,就像滚动任何网站一样。 当时间轴滚动到足够多时(一侧不可见表面的 50%),我们更改“position:absolute;left:{num}px;” 每个可见项目的变量并向后滚动画布。 发生这种情况时,将调用 onBoundsChange 道具。

这会产生具有最佳性能的视觉上无穷无尽的滚动画布。

可扩展性和可用性:虽然一些参数(onTimeChangemoveResizeValidator)可能难以配置,但这些是设计决策,旨在使其尽可能可扩展。 如果您有关于这些参数的常见任务的方法,请发送 PR 以将它们添加到本文档中。

Interaction

要在时间线内进行交互和导航,用户可以使用以下选项:

shift + mousewheel = move timeline left/right
alt + mousewheel = zoom in/out
ctrl + mousewheel = zoom in/out 10× faster
meta + mousewheel = zoom in/out 3x faster (win or cmd + mousewheel)

此外,还有一种处理捏合和捏合缩放手势(两个触摸点)。 触控板(不是触摸设备)上的捏合手势适用于 Chrome 和 Firefox (v55+),因为这些浏览器将手势映射到 ctrl + mousewheel

Contribute

如果你想改进 React Calendar Timeline fork 存储库并开始运行以下命令:

$ git clone https://github.com/namespace-ee/react-calendar-timeline.git react-calendar-timeline
$ cd react-calendar-timeline
$ yarn
$ yarn start

在你的浏览器中检查 http://0.0.0.0:8888/ 并享受乐趣!

请在发送拉取请求之前运行 npm run lintnpm run jest 运行测试。

License

MIT 许可

React Calendar Timeline

A modern and responsive React timeline component.

calendar demo

Checkout the examples here!

Contents

Getting started

# via yarn
yarn add react-calendar-timeline

# via npm
npm install --save react-calendar-timeline

react-calendar-timeline has react, react-dom and dayjs.

Usage

At the very minimum:

import Timeline from 'react-calendar-timeline'
// make sure you include the timeline stylesheet or the timeline will not be styled
import 'react-calendar-timeline/lib/Timeline.css'
import dayjs from 'dayjs'

const groups = [{ id: 1, title: 'group 1' }, { id: 2, title: 'group 2' }]

const items = [
  {
    id: 1,
    group: 1,
    title: 'item 1',
    start_time: dayjs(),
    end_time: dayjs().add(1, 'hour')
  },
  {
    id: 2,
    group: 2,
    title: 'item 2',
    start_time: dayjs().add(-0.5, 'hour'),
    end_time: dayjs().add(0.5, 'hour')
  },
  {
    id: 3,
    group: 1,
    title: 'item 3',
    start_time: dayjs().add(2, 'hour'),
    end_time: dayjs().add(3, 'hour')
  }
]

ReactDOM.render(
  <div>
    Rendered by react!
    <Timeline
      groups={groups}
      items={items}
      defaultTimeStart={dayjs().add(-12, 'hour')}
      defaultTimeEnd={dayjs().add(12, 'hour')}
    />
  </div>,
  document.getElementById('root')
)

API

NB! All props need to be immutable. For example, this means if you wish to change the title of one of your items, please pass in a whole new items array instead of changing the title in the old array. Here's more info.

The component can take many props:

groups

Expects either a vanilla JS array or an immutableJS array, consisting of objects with the following attributes:

{
  id: 1,
  title: 'group 1',
  rightTitle: 'title in the right sidebar',
  stackItems?: true,
  height?: 30
}

If you use the right sidebar, you can pass optional rightTitle property here. If you want to overwrite the calculated height with a custom height, you can pass a height property as an int in pixels here. This can be very useful for categorized groups.

items

Expects either a vanilla JS array or an immutableJS array, consisting of objects with the following attributes:

{
  id: 1,
  group: 1,
  title: 'Random title',
  start_time: 1457902922261,
  end_time: 1457902922261 + 86400000,
  canMove: true,
  canResize: false,
  canChangeGroup: false,
  itemProps: {
    // these optional attributes are passed to the root <div /> of each item as <div {...itemProps} />
    'data-custom-attribute': 'Random content',
    'aria-hidden': true,
    onDoubleClick: () => { console.log('You clicked double!') },
    className: 'weekend',
    style: {
      background: 'fuchsia'
    }
  }
}

The preferred (fastest) option is to give Unix timestamps in milliseconds for start_time and end_time. Objects that convert to them (JavaScript Date or dayjs()) will also work, but will be a lot slower.

defaultTimeStart and defaultTimeEnd

Unless overridden by visibleTimeStart and visibleTimeEnd, specify where the calendar begins and where it ends. This parameter expects a Date.

visibleTimeStart and visibleTimeEnd

The exact viewport of the calendar. When these are specified, scrolling in the calendar must be orchestrated by the onTimeChange function. This parameter expects a Unix timestamp in milliseconds.

Note that you need to provide either defaultTimeStart/End or visibleTimeStart/End for the timeline to function

selected

An array with id's corresponding to id's in items (item.id). If this prop is set you have to manage the selected items yourself within the onItemSelect handler to update the property with new id's and use onItemDeselect handler to clear selection. This overwrites the default behaviour of selecting one item on click.

keys

An array specifying keys in the items and groups objects. Defaults to

{
  groupIdKey: 'id',
  groupTitleKey: 'title',
  groupRightTitleKey: 'rightTitle',
  itemIdKey: 'id',
  itemTitleKey: 'title',    // key for item div content
  itemDivTitleKey: 'title', // key for item div title (<div title="text"/>)
  itemGroupKey: 'group',
  itemTimeStartKey: 'start_time',
  itemTimeEndKey: 'end_time',
}

className

Additional class names as a string for the root Timeline element.

sidebarWidth

Width of the sidebar in pixels. If set to 0, the sidebar is not rendered. Defaults to 150.

sidebarContent

Everything passed here will be displayed above the left sidebar. Use this to display small filters or so. Defaults to null.

rightSidebarWidth

Width of the right sidebar in pixels. If set to 0, the right sidebar is not rendered. Defaults to 0.

rightSidebarContent

Everything passed here will be displayed above the right sidebar. Use this to display small filters or so. Defaults to null.

dragSnap

Snapping unit when dragging items. Defaults to 15 * 60 * 1000 or 15min. When so, the items will snap to 15min intervals when dragging.

minResizeWidth

The minimum width, in pixels, of a timeline entry when it's possible to resize. If not reached, you must zoom in to resize more. Default to 20.

lineHeight

Height of one line in the calendar in pixels. Default 30

itemHeightRatio

What percentage of the height of the line is taken by the item? Default 0.65

minZoom

Smallest time the calendar can zoom to in milliseconds. Default 60 * 60 * 1000 (1 hour)

maxZoom

Largest time the calendar can zoom to in milliseconds. Default 5 * 365.24 * 86400 * 1000 (5 years)

clickTolerance

How many pixels we can drag the background for it to be counted as a click on the background. Default 3

canMove

Can items be dragged around? Can be overridden in the items array. Defaults to true

canChangeGroup

Can items be moved between groups? Can be overridden in the items array. Defaults to true

canResize

Can items be resized? Can be overridden in the items array. Accepted values: false, "left", "right", "both". Defaults to "right". If you pass true, it will be treated as "right" to not break compatibility with versions 0.9 and below.

useResizeHandle

Append a special .rct-drag-right handle to the elements and only resize if dragged from there. Defaults to false

stackItems

Stack items under each other, so there is no visual overlap when times collide. Can be overridden in the groups array. Defaults to false. Requires millisecond or Dayjs timestamps, not native JavaScript Date objects.

traditionalZoom

Zoom in when scrolling the mouse up/down. Defaults to false

itemTouchSendsClick

Normally tapping (touching) an item selects it. If this is set to true, a tap will have the same effect, as selecting with the first click and then clicking again to open and send the onItemClick event. Defaults to false.

timeSteps

With what step to display different units. E.g. 15 for minute means only minutes 0, 15, 30 and 45 will be shown.

Default:

{
  second: 1,
  minute: 1,
  hour: 1,
  day: 1,
  month: 1,
  year: 1
}

scrollRef

Ref callback that gets a DOM reference to the scroll body element. Can be useful to programmatically scroll.

onItemDrag(itemDragObject)

Called when an item is moving or resizing. Returns an object with the following properties:

propertytypedescription
eventTypestringretuns either move or resize
itemIdnumberID of the item being moved or resized
timenumberUNIX timestamp in milliseconds
edgestringon resize, returns a value of either left or right
newGroupOrdernumberon move, index position of the new group that the item is moving to

onItemMove(itemId, dragTime, newGroupOrder)

Callback when an item is moved. Returns 1) the item's ID, 2) the new start time and 3) the index of the new group in the groups array.

onItemResize(itemId, time, edge)

Callback when an item is resized. Returns 1) the item's ID, 2) the new start or end time of the item 3) The edge that was dragged (left or right)

onItemSelect(itemId, e, time)

Called when an item is selected. This is sent on the first click on an item. time is the time that corresponds to where you click/select on the item in the timeline.

onItemDeselect(e)

Called when deselecting an item. Used to clear controlled selected prop.

onItemClick(itemId, e, time)

Called when an item is clicked. Note: the item must be selected before it's clicked… except if it's a touch event and itemTouchSendsClick is enabled. time is the time that corresponds to where you click on the item in the timeline.

onItemDoubleClick(itemId, e, time)

Called when an item was double clicked. time is the time that corresponds to where you double click on the item in the timeline.

onItemContextMenu(itemId, e, time)

Called when the item is clicked by the right button of the mouse. time is the time that corresponds to where you context click on the item in the timeline. Note: If this property is set the default context menu doesn't appear.

onCanvasClick(groupId, time, e)

Called when an empty spot on the canvas was clicked. Get the group ID and the time as arguments. For example open a "new item" window after this.

onCanvasDoubleClick(groupId, time, e)

Called when an empty spot on the canvas was double clicked. Get the group ID and the time as arguments.

onCanvasContextMenu(groupId, time, e)

Called when the canvas is clicked by the right button of the mouse. Note: If this property is set the default context menu doesn't appear

onZoom(timelineContext)

Called when the timeline is zoomed, either via mouse/pinch zoom or clicking header to change timeline units

moveResizeValidator(action, itemId, time, resizeEdge)

This function is called when an item is being moved or resized. It's up to this function to return a new version of change, when the proposed move would violate business logic.

The argument action is one of move or resize.

The argument resizeEdge is when resizing one of left or right.

The argument time describes the proposed new time for either the start time of the item (for move) or the start or end time (for resize).

The function must return a new unix timestamp in milliseconds… or just time if the proposed new time doesn't interfere with business logic.

For example, to prevent moving of items into the past, but to keep them at 15min intervals, use this code:

function (action, item, time, resizeEdge) {
  if (time < new Date().getTime()) {
    var newTime = Math.ceil(new Date().getTime() / (15*60*1000)) * (15*60*1000);
    return newTime;
  }

  return time
}

onTimeChange(visibleTimeStart, visibleTimeEnd, updateScrollCanvas)

A function that's called when the user tries to scroll. Call the passed updateScrollCanvas(start, end) with the updated visibleTimeStart and visibleTimeEnd (as unix timestamps in milliseconds) to change the scroll behavior, for example to limit scrolling.

Here is an example that limits the timeline to only show dates starting 6 months from now and ending in 6 months.

// this limits the timeline to -6 months ... +6 months
const minTime = dayjs().add(-6, 'months').valueOf()
const maxTime = dayjs().add(6, 'months').valueOf()

function (visibleTimeStart, visibleTimeEnd, updateScrollCanvas) {
  if (visibleTimeStart < minTime && visibleTimeEnd > maxTime) {
    updateScrollCanvas(minTime, maxTime)
  } else if (visibleTimeStart < minTime) {
    updateScrollCanvas(minTime, minTime + (visibleTimeEnd - visibleTimeStart))
  } else if (visibleTimeEnd > maxTime) {
    updateScrollCanvas(maxTime - (visibleTimeEnd - visibleTimeStart), maxTime)
  } else {
    updateScrollCanvas(visibleTimeStart, visibleTimeEnd)
  }
}

onBoundsChange(canvasTimeStart, canvasTimeEnd)

Called when the bounds in the calendar's canvas change. Use it for example to load new data to display. (see "Behind the scenes" below). canvasTimeStart and canvasTimeEnd are unix timestamps in milliseconds.

itemRenderer

Render prop function used to render a customized item. The function provides multiple parameters that can be used to render each item.

Parameters provided to the function has two types: context params which have the state of the item and timeline, and prop getters functions

Render props params

context
  • item has the item we passed as a prop to the calendar.

  • timelineContext

propertytypedescription
timelineWidthnumberreturns the full width of the timeline.
visibleTimeStartnumberreturns the exact start of view port of the calendar
visibleTimeEndnumberreturns the exact end of view port of the calendar.
canvasTimeStartnumberdenotes the start time in ms of the canvas timeline
canvasTimeEndnumberdenotes the end time in ms of the canvas timeline
  • itemContext
propertytypedescription
dimensionsobjectreturns the dimensions of the item which includes collisionLeft, collisionWidth, height, isDragging, left, order, originalLeft, stack, top, and width
useResizeHandlebooleanreturns the prop useResizeHandle from calendar root component
titlestringreturns title to render in content element.
canMovebooleanreturns if the item is movable.
canResizeLeftbooleanreturns if the item can resize from the left
canResizeRightbooleanreturns if the item can resize from the right.
selectedbooleanreturns if the item is selected.
draggingbooleanreturns if the item is being dragged
dragStartobjectreturns x and y of the start dragging point of the item.
dragTimenumbercurrent drag time.
dragGroupDeltanumberreturns number of groups the item moved. if negative, moving was to top. If positive, moving was to down
resizingbooleanreturns if the item is being resized.
resizeEdgeleft, rightthe side from which the component is being resized form
resizeStartnumberreturns the x value from where the component start moving
resizeTimenumbercurrent resize time
widthbooleanreturns the width of the item (same as in dimensions)
prop getters functions

These functions are used to apply props to the elements that you render. This gives you maximum flexibility to render what, when, and wherever you like.

Rather than applying props on the element yourself and to avoid your props being overridden (or overriding the props returned). You can pass an object to the prop getters to avoid any problems. This object will only accept some properties that our component manage so the component make sure to combine them correctly.

propertytypedescription
getItemPropsfunction(props={})returns the props you should apply to the root item element.
getResizePropsfunction(props={})returns two sets of props to apply on the left and right elements as resizing elements if you have useResizeHandle prop set to true
  • getItemProps returns the props you should apply to the root item element. The returned props are:

  • key: item id

  • ref: function to get item reference

  • className: classnames to be applied to the item

  • onMouseDown: event handler

  • onMouseUp: event handler

  • onTouchStart: event handler

  • onTouchEnd: event handler

  • onDoubleClick: event handler

  • onContextMenu: event handler

  • style: inline object

    ** the given styles will only override the styles that are not a requirement for positioning the item. Other styles like color, radius and others

    These properties can be overriden using the prop argument with properties:

  • className: class names to be added

  • onMouseDown: event handler will be called after the component's event handler

  • onMouseUp: event handler will be called after the component's event handler

  • onTouchStart: event handler will be called after the component's event handler

  • onTouchEnd: event handler will be called after the component's event handler

  • onDoubleClick: event handler will be called after the component's event handler

  • onContextMenu: event handler will be called after the component's event handler

  • style: extra inline styles to be applied to the component

  • getResizeProps returns the props you should apply to the left and right resize handlers only if useResizeHandle set to true. The returned object has the props for the left element under property left and the props to be applied to the right element under right :

  • left

    • ref: function to get element reference
    • style: style to be applied to the left element
    • className: class names to be applied to left className
  • right

    • ref: function to get element reference
    • style: style to be applied to the right element
    • className: class names to be applied to left className

These properties can be override using the prop argument with properties:

  • leftStyle: style to be added to left style
  • rightStyle: style to be added to right style
  • leftClassName: classes to be added to left handler
  • rightClassName: classes to be added to right handler

example

let items = [
  {
    id: 1,
    group: 1,
    title: 'Title',
    tip: 'additional information',
    color: 'rgb(158, 14, 206)',
    selectedBgColor: 'rgba(225, 166, 244, 1)',
    bgColor : 'rgba(225, 166, 244, 0.6)',
    ...
  }
]

itemRenderer: ({
  item,
  itemContext,
  getItemProps,
  getResizeProps
}) => {
  const { left: leftResizeProps, right: rightResizeProps } = getResizeProps()
  return (
    <div {...getItemProps(item.itemProps)}>
      {itemContext.useResizeHandle ? <div {...leftResizeProps} /> : ''}

      <div
        className="rct-item-content"
        style={{ maxHeight: `${itemContext.dimensions.height}` }}
      >
        {itemContext.title}
      </div>

      {itemContext.useResizeHandle ? <div {...rightResizeProps} /> : ''}
    </div>
  )}

}

groupRenderer

React component that will be used to render the content of groups in the sidebar. Will be passed the group and isRightSidebar as props.

let groups = [
  {
    id: 1,
    title: 'Title',
    tip: 'additional information'
  }
]

groupRenderer = ({ group }) => {
  return (
    <div className="custom-group">
      <span className="title">{group.title}</span>
      <p className="tip">{group.tip}</p>
    </div>
  )
}

resizeDetector

The component automatically detects when the window has been resized. Optionally you can also detect when the component's DOM element has been resized. To do this, pass a resizeDetector. Since bundling it by default would add ~18kb of minimized JS, you need to opt in to this like so:

import containerResizeDetector from 'react-calendar-timeline/lib/resize-detector/container'

<Timeline resizeDetector={containerResizeDetector} ... />

verticalLineClassNamesForTime(start, end)

This function is called when the vertical line is rendered. start and end are unix timestamps in milliseconds for the current column. The function should return an array of strings containing the classNames which should be applied to the column. This makes it possible to visually highlight e.g. public holidays or office hours. An example could look like (see: demo/vertical-classes):

verticalLineClassNamesForTime = (timeStart, timeEnd) => {
  const currentTimeStart = dayjs(timeStart)
  const currentTimeEnd = dayjs(timeEnd)

  for (let holiday of holidays) {
    if (
      holiday.isSame(currentTimeStart, 'day') &&
      holiday.isSame(currentTimeEnd, 'day')
    ) {
      return ['holiday']
    }
  }
}

Be aware that this function should be as optimized for performance as possible as it will be called on each render of the timeline (i.e. when the canvas is reset, when zooming, etc)

horizontalLineClassNamesForGroup(group)

This function is called when the horizontal line is rendered. group is the group which will be rendered into the current row. The function should return an array of strings containing the classNames which should be applied to the row. This makes it possible to visually highlight categories or important items. An example could look like:

horizontalLineClassNamesForGroup={(group) => group.root ? ["row-root"] : []}

Timeline Markers

Timeline markers are markers that are overlayed on the canvas at specific datepoints.

Overview

Markers can be placed in the Timeline by declaring them as children of the Timeline component:

import Timeline, {
  TimelineMarkers,
  CustomMarker,
  TodayMarker,
  CursorMarker
} from 'react-calendar-timeline'

<Timeline>
  <TimelineMarkers>
    <TodayMarker />
    <CustomMarker date={today} />
    <CustomMarker date={tomorrow}>
      {/* custom renderer for this marker */}
      {({ styles, date }) => {
        const customStyles = {
          ...styles,
          backgroundColor: 'deeppink',
          width: '4px'
        }
        return <div style={customStyles} onClick={someCustomHandler} />
      }}
    </CustomMarker>
    <CursorMarker />
  </TimelineMarkers>
</Timeline>

Each marker allows for passing in a custom renderer via a function as a child component. This allows the user to render whatever they want (event handlers, custom styling, etc). This custom renderer receives an object with two properties:

styles: {position: 'absolute', top: 0, bottom: 0, left: number}

This object must be passed to the root component's style prop in order to be rendered properly. Note that you can merge this object with any other properties.

date: number

Date in unix timestamp of this marker. This can be used to change how your marker is rendered (or if its rendered at all)

TimelineMarkers

Wrapper for timeline markers that you want rendered.

TodayMarker

Marker that is placed on the current date/time.

interval: number | default: 10000

How often the TodayMarker refreshes. Value represents milliseconds.

children: function({styles: object, date: number}) => JSX.Element

Custom renderer for this marker. Ensure that you always pass styles to the root component's style prop as this object contains positioning of the marker.

// custom interval
const twoSeconds = 2000

<TodayMarker interval={twoSeconds} />

//custom renderer

<TodayMarker>
  {({ styles, date }) =>
    // date is value of current date. Use this to render special styles for the marker
    // or any other custom logic based on date:
    // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'}
    <div style={styles} />
  }
</TodayMarker>

CustomMarker

Marker that is placed on the current date/time.

date: number | required

Where to place the marker on the timeline. date value is unix timestamp.

children: function({styles: object, date: number}) => JSX.Element

Custom renderer for this marker. Ensure that you always pass styles to the root component's style prop as this object contains positioning of the marker.

const today = Date.now()
<CustomMarker date={today} />

//custom renderer
<CustomMarker date={today}>
  {({ styles, date }) => <div style={styles} />}
</CustomMarker>

// multiple CustomMarkers
const markerDates = [
  {date: today, id: 1,},
  {date: tomorrow, id: 2,},
  {date: nextFriday, id: 3,},
]

<TimelineMarkers>
  {markerDates.map(marker => <CustomMarker key={marker.date} date={marker.date}/> )}
</TimelineMarkers>

CursorMarker

Marker that is displayed when hovering over the timeline and matches where your cursor is.

children: function({styles: object, date: number}) => JSX.Element

Custom renderer for this marker. Ensure that you always pass styles to the root component's style prop as this object contains positioning of the marker.

// render default marker for Cursor
<CursorMarker />

//custom renderer
<CursorMarker>
  {({ styles, date }) =>
    // date is value of current date. Use this to render special styles for the marker
    // or any other custom logic based on date:
    // e.g. styles = {...styles, backgroundColor: isDateInAfternoon(date) ? 'red' : 'limegreen'}
    <div style={styles} />
  }
</CursorMarker>

Timeline Headers

Timeline headers are the section above the timeline which consist of two main parts: First, the calender header which is a scrolable div containing the dates of the calendar called DateHeader. Second, is the headers for the sidebars, called SidebarHeader, the left one and optionally the right one.

Default usage

For the default case, two DateHeaders are rendered above the timeline, one primary and secondary. The secondary has the same date unit as the timeline and a primary which has a unit larger than the timeline unit by one.

For the SidebarHeaders an empty SidebarHeader will be render for the left and optionally an empty right sidebar header if rightSidebarWith exists.

Overview

To provide any custom headers for DateHeader or SidebarHeader. You need to provide basic usage to provide any custom headers. These Custom headers should be always included inside TimelineHeaders component in the component's children.

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
  </TimelineHeaders>
<Timeline>

Components

Custom headers are implemented through a set of component with mostly function as a child component pattern, designed to give the user the most control on how to render the headers.

TimelineHeader

Is the core component wrapper component for custom headers

props

Proptypedescription
styleobjectapplied to the root component of headers
classNamestringapplied to the root component of the headers
calendarHeaderStyleobjectapplied to the root component of the calendar headers -scrolable div- DateHeader and CustomHeader)
calendarHeaderClassNamestringapplied to the root component of the calendar headers -scrolable div- DateHeader and CustomHeader)
headerReffunctionused to get the ref of the header element

SidebarHeader

Responsible for rendering the headers above the left and right sidebars.

props

Proptypedescription
variantleft (default), rightrenders above the left or right sidebar
childrenFunctionfunction as a child component to render the header
headerDataanyContextual data to be passed to the item renderer as a data prop

Child function renderer

a Function provides multiple parameters that can be used to render the sidebar headers

Prop getters functions

Rather than applying props on the element yourself and to avoid your props being overridden (or overriding the props returned). You can pass an object to the prop getters to avoid any problems. This object will only accept some properties that our component manage so the component make sure to combine them correctly.

propertytypedescription
getRootPropsfunction(props={})returns the props you should apply to the root div element.
dataanyContextual data passed by headerData prop
  • getRootProps The returned props are:

  • style: inline object style

    These properties can be override using the prop argument with properties:

  • style: extra inline styles to be applied to the component

example

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <SidebarHeader variant="right" headerData={{someData: 'extra'}}>
      {({ getRootProps, data }) => {
        return <div {...getRootProps()}>Right {data.someData}</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
  </TimelineHeaders>
<Timeline>

Note : the Child function renderer can be a component or a function for convenience

DateHeader

Responsible for rendering the headers above calendar part of the timeline. Consists of time intervals dividing the headers in columns.

props

Proptypedescription
styleobjectapplied to the root of the header
classNamestringapplied to the root of the header
unitsecond, minute, hour, day, week, month, year or primaryHeaderintervals between columns
labelFormatFunction or stringcontrols the how to format the interval label
intervalRendererFunctionrender prop to render each interval in the header
headerDataanyContextual data to be passed to the item renderer as a data prop
heightnumber default (30)height of the header in pixels

Note: passing primaryHeader to unit the header will act as the main header with interval unit larger than timeline unit by 1

Interval unit

intervals are decided through the prop: unit. By default, the unit of the intervals will be the same the timeline.

If primaryHeader is passed to unit, it will override the unit with a unit a unit larger by 1 of the timeline unit.

If unit is set, the unit of the header will be the unit passed though the prop and can be any unit of time from dayjs.

Label format

To format each interval label you can use 2 types of props to format which are:

  • string: if a string was passed it will be passed to startTime method format which is a dayjs object .

  • Function: This is the more powerful method and offers the most control over what is rendered. The returned string will be rendered inside the interval

    type Unit = `second` | `minute` | `hour` | `day` | `month` | `year`
  ([startTime, endTime] : [Dayjs, Dayjs], unit: Unit, labelWidth: number, formatOptions: LabelFormat = defaultFormat ) => string
Default format

by default we provide a responsive format for the dates based on the label width. it follows the following rules:

The long, mediumLong, medium and short will be be decided through the labelWidth value according to where it lays upon the following scale:

  |-----`short`-----50px-----`medium`-----100px-----`mediumLong`-----150px--------`long`-----
  // default format object
  const format : LabelFormat = {
  year: {
    long: 'YYYY',
    mediumLong: 'YYYY',
    medium: 'YYYY',
    short: 'YY'
  },
  month: {
    long: 'MMMM YYYY',
    mediumLong: 'MMMM',
    medium: 'MMMM',
    short: 'MM/YY'
  },
  week: {
    long: 'w',
    mediumLong: 'w',
    medium: 'w',
    short: 'w'
  },
  day: {
    long: 'dddd, LL',
    mediumLong: 'dddd, LL',
    medium: 'dd D',
    short: 'D'
  },
  hour: {
    long: 'dddd, LL, HH:00',
    mediumLong: 'L, HH:00',
    medium: 'HH:00',
    short: 'HH'
  },
  minute: {
    long: 'HH:mm',
    mediumLong: 'HH:mm',
    medium: 'HH:mm',
    short: 'mm',
  }
}

Note: this is only an implementation of the function param. You can do this on your own easily

intervalRenderer

Render prop function used to render a customized interval. The function provides multiple parameters that can be used to render each interval.

Paramters provided to the function has two types: context params which have the state of the item and timeline, and prop getters functions

Note : the renderProp can be a component or a function for convenience

interval context

An object contains the following properties:

propertytypedescription
intervalobject : {startTime, endTime, labelWidth, left}an object containing data related to the interval
intervalTextstringthe string returned from labelFormat prop
Prop getters functions

Rather than applying props on the element yourself and to avoid your props being overridden (or overriding the props returned). You can pass an object to the prop getters to avoid any problems. This object will only accept some properties that our component manage so the component make sure to combine them correctly.

propertytypedescription
getIntervalPropsfunction(props={})returns the props you should apply to the root div element.
  • getIntervalProps The returned props are:

  • style: inline object style

  • onClick: event handler

  • key

    These properties can be extended using the prop argument with properties:

  • style: extra inline styles to be applied to the component

  • onClick: extra click handler added to the normal showPeriod callback

data

data passed through headerData

example

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
    <DateHeader
      unit="day"
      labelFormat="MM/DD"
      style={{ height: 50 }}
      data={{someData: 'example'}}
      intervalRenderer={({ getIntervalProps, intervalContext, data }) => {
        return <div {...getIntervalProps()}>
          {intervalContext.intervalText}
          {data.example}
        </div>
      }}
    />
  </TimelineHeaders>
</Timeline>

CustomHeader

Responsible for rendering the headers above calendar part of the timeline. This is the base component for DateHeader and offers more control with less features.

props

Proptypedescription
unitsecond, minute, hour, day, week, month, year (default timelineUnit)intervals
childrenFunctionfunction as a child component to render the header
headerDataanyContextual data to be passed to the item renderer as a data prop
heightnumber default (30)height of the header in pixels

unit

The unit of the header will be the unit passed though the prop and can be any unit of time from dayjs. The default value for unit is timelineUnit

Children

Function as a child component to render the header

Paramters provided to the function has three types: context params which have the state of the item and timeline, prop getters functions and helper functions.

Note : the Child function renderer can be a component or a function for convenience

({
  timelineContext: {
    timelineWidth,
    visibleTimeStart,
    visibleTimeEnd,
    canvasTimeStart,
    canvasTimeEnd
  },
  headerContext: {
    unit,
    intervals: this.state.intervals
  },
  getRootProps: this.getRootProps,
  getIntervalProps: this.getIntervalProps,
  showPeriod,
  //contextual data passed through headerData
  data,
})=> React.Node
context

An object contains context for timeline and header:

Timeline context
propertytypedescription
timelineWidthnumberwidth of timeline
visibleTimeStartnumberunix milliseconds of start visible time
visibleTimeEndnumberunix milliseconds of end visible time
canvasTimeStartnumberunix milliseconds of start buffer time
canvasTimeEndnumberunix milliseconds of end buffer time
Header context
propertytypedescription
intervalsarrayan array with all intervals
unitstringunit passed or timelineUnit

** interval: [startTime: Day, endTime: Day]

Prop getters functions

Rather than applying props on the element yourself and to avoid your props being overridden (or overriding the props returned). You can pass an object to the prop getters to avoid any problems. This object will only accept some properties that our component manage so the component make sure to combine them correctly.

propertytypedescription
getRootPropsfunction(props={})returns the props you should apply to the root div element.
getIntervalPropsfunction(props={})returns the props you should apply to the interval div element.
  • getIntervalProps The returned props are:

  • style: inline object style

  • onClick: event handler

  • key

    These properties can be extended using the prop argument with properties:

  • style: extra inline styles to be applied to the component

  • onClick: extra click handler added to the normal showPeriod callback

helpers:
propertytypedescription
showPeriodfunction(props={})returns the props you should apply to the root div element.
data:

pass through the headerData prop content

example

import Timeline, {
  TimelineHeaders,
  SidebarHeader,
  DateHeader
} from 'react-calendar-timeline'

<Timeline>
  <TimelineHeaders>
    <SidebarHeader>
      {({ getRootProps }) => {
        return <div {...getRootProps()}>Left</div>
      }}
    </SidebarHeader>
    <DateHeader unit="primaryHeader" />
    <DateHeader />
    <CustomHeader height={50} headerData={{someData: 'data'}} unit="year">
      {({
        headerContext: { intervals },
        getRootProps,
        getIntervalProps,
        showPeriod,
        data,
      }) => {
        return (
          <div {...getRootProps()}>
            {intervals.map(interval => {
              const intervalStyle = {
                lineHeight: '30px',
                textAlign: 'center',
                borderLeft: '1px solid black',
                cursor: 'pointer',
                backgroundColor: 'Turquoise',
                color: 'white'
              }
              return (
                <div
                  onClick={() => {
                    showPeriod(interval.startTime, interval.endTime)
                  }}
                  {...getIntervalProps({
                    interval,
                    style: intervalStyle
                  })}
                >
                  <div className="sticky">
                    {interval.startTime.format('YYYY')}
                  </div>
                </div>
              )
            })}
          </div>
        )
      }}
    </CustomHeader>
  </TimelineHeaders>
</Timeline>

FAQ

My timeline is unstyled

You need to include the Timeline.css file, either via static file reference or webpack stylesheet bundling. The file is located at lib/Timeline.css

How can I have items with different colors?

Now you can use item renderer for rendering items with different colors itemRenderer. Please refer to examples for a sandbox example

How can I add a sidebar on the right?

The library supports right sidebar. right sidebar demo

To use it, you need to add a props to the <Timeline /> component:

rightSidebarWidth={150}

And add rightTitle prop to the groups objects:

{
  id: 1,
  title: 'group 1',
  rightTitle: 'additional info about group 1'
}

If you are using Custom Headers then you need to add SidebarHeader component under TimelineHeader with variant right

The timeline header doesn't fix to the top of the container when I scroll down.

you need to add sticky to the header like this example.

I'm using Babel with Rollup or Webpack 2+ and I'm getting strange bugs with click events

These module bundlers don't use the transpiled (ES5) code of this module. They load the original ES2015+ source. Thus your babel configuration needs to match ours. We recommend adding the stage-0 preset to your .babelrc to make sure everything works as intended.

If that's too experimental, then the minimum you need is to add is the transform-class-properties plugin that's in stage-2 and possibly the transform-object-rest-spread plugin from stage-3. However in this case it's easier to make sure you have at least stage-2 enabled.

See issue 51 for more details.

Alternatively you may import the transpiled version of the timeline like this:

// import Timeline from 'react-calendar-timeline'  // ESnext version
import Timeline from 'react-calendar-timeline/lib' // ES5 version

However doing so you lose on some of the features of webpack 2 and will potentially get a slightly larger bundle.

It doesn't work with create-react-app

It's the same issue as above. See issue 134 for details and options.

What are the zIndex values for all the elements?

This is useful when using the plugins (that you pass as children to the component). Override the CSS to change:

  • Horizontal Lines: 30
  • Vertical Lines: 40
  • Items: 80-88 (depending on selection, dragging, etc)
  • Header: 90

Behind the scenes

The timeline is built with speed, usability and extensibility in mind.

Speed: The calendar itself is actually a 3x wide scrolling canvas of the screen. All scroll events left and right happen naturally, like scrolling any website. When the timeline has scrolled enough (50% of the invisible surface on one side), we change the "position:absolute;left:{num}px;" variables of each of the visible items and scroll the canvas back. When this happens, the onBoundsChange prop is called.

This results in a visually endless scrolling canvas with optimal performance.

Extensibility and usability: While some parameters (onTimeChange, moveResizeValidator) might be hard to configure, these are design decisions to make it as extensible as possible. If you have recipes for common tasks regarding those parameters, send a PR to add them to this doc.

Interaction

To interact and navigate within the timeline there are the following options for the user:

shift + mousewheel = move timeline left/right
alt + mousewheel = zoom in/out
ctrl + mousewheel = zoom in/out 10× faster
meta + mousewheel = zoom in/out 3x faster (win or cmd + mousewheel)

Plus there is a handling for pinch-in and pinch-out zoom gestures (two touch points). The pinch gesture on a trackpad (not a touch device) works in Chrome and Firefox (v55+) because these browsers map the gesture to ctrl + mousewheel.

Contribute

If you like to improve React Calendar Timeline fork the repo and get started by running the following:

$ git clone https://github.com/namespace-ee/react-calendar-timeline.git react-calendar-timeline
$ cd react-calendar-timeline
$ yarn
$ yarn start

Check http://0.0.0.0:8888/ in your browser and have fun!

Please run npm run lint before you send a pull request. npm run jest runs the tests.

License

MIT licensed.

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文