Compound Timeline
A collection of composable and presentation agnostic Compound Components, Hooks and a Context Provider, to help aid in the creation of scheduling based user-interfaces.
A collection of composable and presentation agnostic Compound Components, Hooks and a Context Provider, to help aid in the creation of scheduling based user-interfaces.
Identify commonality across applications utilising scheduling related patterns, with the aim to abstract out a library that helps to ensure the integrity and future maintainability of applications with disparate sets of scheduling related use cases.
We identified commonality across applications in two key areas:
Somewhat but nuanced based on problem domain
DateTime manipulation
Positioning and sizing arbitrary components across a timeline
Generated data structures and state
Common but extensible interfaces
Interact with the Live Example, or view more stories in our Storybook.
1// npm2npm install @royalnavy/react-component-library34// pnpm5pnpm add @royalnavy/react-component-library
In React, composition is a natural pattern of the component model. It's how we build components from other components, of varying complexity and specialisation.
The consumer can pick and choose what functionality to include in their Timeline via the declarative JSX API.
We aim to empower the consumer by enabling them to override the presentation of the exposed compound components:
Full control over look and feel (no opinion about markup or styles)
Consistent underlying implementation across applications
Single set of robust automated tests
Render props allow us to provide custom presentation layers to our compound components by exposing any relevant internal state. See the example usage for the TimelineMonths component.
1import React from 'react'23import {4 Timeline,5 TimelineMonths,6 TimelineRows7} from '@royalnavy/react-component-library'89const CustomTimelineMonth = ({ index, dayWidth, daysTotal, startDate }) => {10 return (11 <span12 style={{13 display: 'inline-block',14 width: `${dayWidth * daysTotal}px`,15 // ...16 }}17 >18 {startDate.getMonth() + 1}/{startDate.getFullYear()}19 </span>20 )21}2223const ExampleTimeline = () => {24 return (25 <Timeline startDate={new Date(2022, 0, 1)}>26 <TimelineMonths render={CustomTimelineMonth} />27 <TimelineRows>{}</TimelineRows>28 </Timeline>29 )30}
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
We expose the TimelineContext provider so that a consumer can create their own application specific components. The context provider exposes Timeline state and a dispatch function for dispatching reducer actions against the store.
In this example we have created a custom component that consumes Timeline-related state and dispatches a reducer action when a button is clicked.
1import React, { useContext } from 'react'23import {4 Timeline,5 TimelineDays,6 TimelineHours,7 TimelineRows,8 TimelineContext,9 TIMELINE_ACTIONS,10} from '@royalnavy/react-component-library'1112const CustomTimelineComponent = () => {13 const {14 state: { months, weeks, days, options, scaleOptions },15 dispatch,16 } = useContext(TimelineContext)1718 return (19 <div>20 <button21 onClick={(_) =>22 dispatch({ type: TIMELINE_ACTIONS.SCALE, getScaleIndex: () => 0 })23 }24 >25 Hourly26 </button>27 </div>28 )29}3031const ExampleTimeline = () => {32 return (33 <Timeline startDate={new Date(2022, 0, 1)}>34 <CustomTimelineComponent />35 <TimelineDays />36 <TimelineHours />37 <TimelineRows>{}</TimelineRows>38 </Timeline>39 )40}
We expose some hooks in order to aid in the creation of your own custom Timeline components.
This hook does not take any parameters and in return exposes moveNext and movePrevious which can be used to navigate between frames at the current zoom level.
This hook takes startDate and endDate and in return exposes the width and position (in the form of an offset) of an item relative to the date range currently displayed by the Timeline.
This hook does not take any parameters and in return exposes fields for zooming in and out. canZoomIn and canZoomOut are booleans that return whether the Timeline can zoom in or out further. zoomIn and zoomOut are functions which decrease or increase the amount of time visible in a frame of the Timeline.
1import React from 'react'23import {4 useTimelineFrame,5 useTimelinePosition,6 useTimelineZoom,7} from '@royalnavy/react-component-library'89const CustomTimelineFrameComponent = () => {10 const { moveNext, movePrevious } = useTimelineFrame()1112 return (13 <>14 <button onClick={movePrevious}>Previous</button>15 <button onClick={moveNext}>Next</button>16 </>17 )18}1920const CustomTimelinePositionComponent = ({ startDate, endDate }) => {21 const { width, offset, endsBeforeStart, startsAfterEnd } =22 useTimelinePosition(startDate, endDate)2324 if (endsBeforeStart || startsAfterEnd) return null2526 return (27 <div28 style={{29 position: 'absolute',30 display: 'inline-block',31 width,32 left: offset,33 // ...34 }}35 />36 )37}3839const CustomTimelineZoomComponent = () => {40 const { canZoomIn, canZoomOut, zoomIn, zoomOut } = useTimelineZoom()4142 if (!canZoomIn && !canZoomOut) return null4344 return (45 <>46 <button onClick={zoomIn}>Zoom in</button>47 <button onClick={zoomOut}>Zoom out</button>48 </>49 )50}51
Through the use of clever composition and custom styling, it's possible to create layouts that are either nuanced or high in complexity. Here is an example of a custom layout that adds groupings to rows:
Here you will find comprehensive API documentation for the Timeline Hooks.
Provides the ability to navigate Timeline frames.
ObjectundefinedMove to the next frame of the Timeline.
undefinedMove to the previous frame of the Timeline.
Provides fields to facilitate positioning items in the Timeline.
undefinedThe start date of the event.
undefinedThe end date of the event.
ObjectundefinedWhether the element end date is after the end date of the current frame.
undefinedWhether the element end date is before the start date of the current frame.
undefinedWidth of the element formatted as a pixel string taking into consideration the dates being displayed.
undefinedDistance of the element from the left of the Timeline as a pixel string.
undefinedWhether the element start date is after the end date of the current frame.
undefinedWhether the element start date is before the start date of the current frame.
undefinedWidth of the element formatted as a pixel string.
Provides the ability to zoom in and out of the Timeline.
ObjectundefinedWhether it is possible to zoom in further.
undefinedWhether it is possible to zoom out further.
undefinedView a lesser amount of time in the current frame.
undefinedView a larger amount of time in the current frame.
Here you will find comprehensive API documentation for the Timeline Components.
undefinedA month will display either side of this start date.
undefinedBound the timeline by the specified start and end dates.
new Date()Today's current date - default is the system date time.
1The number of months to display at any one time.
30The fixed width value of a single day (in pixels).
falseSpecify whether or not to output sidebar headings.
falseSpecify whether to hide the scaling buttons.
falseSpecify whether to hide the toolbar.
undefinedCustom CSS class to add to the component.
(props: { today, offset }) => ReactNodetodaydateoffsetstringSupply a custom presentation layer.
(props: { index, dayWidth, daysTotal, startDate }) => ReactElementindex numberdayWidthnumberdaysTotalnumberstartDatedateSupply a custom presentation layer.
(props: { index, isOddNumber, offsetPx, widthPx, startDate }) => ReactElementindexnumberisOddNumberbooleanoffsetPxstringwidthPxstringstartDatedateSupply a custom presentation layer.
(props: { index, dayWidth, date }) => ReactElementindexnumberdayWidthnumberdatedateSupply a custom presentation layer.
Hour blocks will not be visible at the default scale level and will be visible when scaling in from the week level.
6Number of hours per block in a day.
(props: { width, time }) => ReactElementwidthnumbertimestringSupply a custom presentation layer.
undefinedSupply children to be rendered.
(props: { index, isOddNumber, offsetPx, widthPx }) => ReactElementindexnumberisOddNumberbooleanoffsetPxstringwidthPxstringSupply a custom presentation layer.
undefinedSupply children to be rendered.
undefinedundefinedA styled-components css`` value to modify the CSS.
undefinedAbility to pass props to the content div of the row.
undefinedAbility to pass props to the header div of the row.
(props: { name }) => ReactElementnamestring?Supply a custom presentation layer for the row header.
undefinedSupply children to be rendered.
undefinedThe start date of the event.
undefinedThe end date of the event.
undefinedSupply children to be rendered.
(props: { startDate, endDate, widthPx, offsetPx, maxWidthPx, startsBeforeStart, endsAfterEnd }) => ReactNodestartDatedateendDatedatewidthPxstringoffsetPxstringmaxWidthPxstringstartsBeforeStartbooleanendsAfterEndbooleanSupply a custom presentation layer.
ColorSuccess500The colour of the bar.
Iterate upon default presentation (user research and design)
Investigate support for advanced features:
Infinite scroll
Lazy loading
Drag and drop
The contributing guide resource presents information about our development process.
If you have recently updated then read the release notes.
The Royal Navy Design System is licensed under the Apache License 2.0.