diff --git a/playbook/app/pb_kits/playbook/pb_timeline/_item.tsx b/playbook/app/pb_kits/playbook/pb_timeline/_item.tsx
index 3d6ef9eafd..f5aa0d6d2b 100644
--- a/playbook/app/pb_kits/playbook/pb_timeline/_item.tsx
+++ b/playbook/app/pb_kits/playbook/pb_timeline/_item.tsx
@@ -1,12 +1,15 @@
import React from 'react'
import classnames from 'classnames'
-
import { buildCss, buildHtmlProps } from '../utilities/props'
-import { globalProps, GlobalProps } from "../utilities/globalProps";
+import { globalProps, GlobalProps } from "../utilities/globalProps"
import DateStacked from '../pb_date_stacked/_date_stacked'
import IconCircle from '../pb_icon_circle/_icon_circle'
+import TimelineLabel from './subcomponents/Label'
+import TimelineStep from './subcomponents/Step'
+import TimelineDetail from './subcomponents/Detail'
+
type ItemProps = {
className?: string,
children?: React.ReactNode[] | React.ReactNode,
@@ -17,6 +20,13 @@ type ItemProps = {
lineStyle?: 'solid' | 'dotted',
} & GlobalProps
+function isElementOfType
(
+ element: React.ReactNode,
+ component: React.ComponentType
+): element is React.ReactElement
{
+ return React.isValidElement
(element) && element.type === component
+}
+
const TimelineItem = ({
className,
children,
@@ -31,31 +41,57 @@ const TimelineItem = ({
const htmlProps = buildHtmlProps(htmlOptions)
+ const childrenArray = React.Children.toArray(children)
+
+ const labelChild = childrenArray.find(
+ (child): child is React.ReactElement => isElementOfType(child, TimelineLabel)
+ )
+
+ const stepChild = childrenArray.find(
+ (child): child is React.ReactElement => isElementOfType(child, TimelineStep)
+ )
+
+ const detailChild = childrenArray.find(
+ (child): child is React.ReactElement => isElementOfType(child, TimelineDetail)
+ )
+
+ const otherChildren = childrenArray.filter(
+ (child) =>
+ !isElementOfType(child, TimelineLabel) &&
+ !isElementOfType(child, TimelineStep) &&
+ !isElementOfType(child, TimelineDetail)
+ )
+
return (
-
-
- {date &&
-
- }
-
-
-
- {children}
-
+ {labelChild || (
+
+ {date && (
+
+ )}
+
+ )}
+ {stepChild || (
+
+ )}
+ {detailChild || (
+
+ { otherChildren }
+
+ )}
)
}
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/_timeline.tsx b/playbook/app/pb_kits/playbook/pb_timeline/_timeline.tsx
index e58d9f8acb..a0e3f53af6 100644
--- a/playbook/app/pb_kits/playbook/pb_timeline/_timeline.tsx
+++ b/playbook/app/pb_kits/playbook/pb_timeline/_timeline.tsx
@@ -5,6 +5,11 @@ import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../uti
import { GlobalProps, globalProps } from '../utilities/globalProps'
import TimelineItem from './_item'
+import {
+ TimelineStep,
+ TimelineLabel,
+ TimelineDetail,
+} from './subcomponents'
type TimelineProps = {
aria?: { [key: string]: string },
@@ -47,5 +52,8 @@ const Timeline = ({
}
Timeline.Item = TimelineItem
+Timeline.Step = TimelineStep
+Timeline.Label = TimelineLabel
+Timeline.Detail = TimelineDetail
export default Timeline
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/detail.html.erb b/playbook/app/pb_kits/playbook/pb_timeline/detail.html.erb
new file mode 100644
index 0000000000..919544cc91
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/detail.html.erb
@@ -0,0 +1,3 @@
+<%= pb_content_tag do %>
+ <%= content.presence %>
+<% end %>
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/detail.rb b/playbook/app/pb_kits/playbook/pb_timeline/detail.rb
new file mode 100644
index 0000000000..810248dae4
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/detail.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Playbook
+ module PbTimeline
+ class Detail < Playbook::KitBase
+ def classname
+ generate_classname("pb_timeline_item_right_block")
+ end
+ end
+ end
+end
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.html.erb b/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.html.erb
new file mode 100644
index 0000000000..1725989c17
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.html.erb
@@ -0,0 +1,43 @@
+<%= pb_rails("timeline", props: {orientation: "horizontal", show_date: true}) do %>
+ <%= pb_rails("timeline/item", props: { line_style: "solid"}) do |item| %>
+
+ <% item.label do %>
+ <%= pb_rails("timeline/label") do %>
+ <%= pb_rails("title", props: { text: "Any Kit Here", size: 2 }) %>
+ <% end %>
+ <% end %>
+
+ <% item.step do %>
+ <%= pb_rails("timeline/step", props: { icon: 'check', icon_color: 'teal' }) %>
+ <% end %>
+
+ <% item.detail do %>
+ <%= pb_rails("title_detail", props: {
+ title: "Jackson Heights",
+ detail: "37-27 74th Street"
+ }) %>
+ <% end %>
+ <% end %>
+ <%= pb_rails("timeline/item", props: { line_style: "dotted"}) do |item| %>
+
+ <% item.step do %>
+ <%= pb_rails("timeline/step") do %>
+ <%= pb_rails("pill", props: { text: "Any Kit" , variant: "success" }) %>
+ <% end %>
+ <% end %>
+
+ <% item.detail do %>
+ <%= pb_rails("title_detail", props: {
+ title: "Greenpoint",
+ detail: "81 Gate St Brooklyn"
+ }) %>
+ <% end %>
+ <% end %>
+
+ <%= pb_rails("timeline/item", props: {icon: "map-marker-alt", icon_color: "purple", date: Date.today+1 }) do |item| %>
+ <%= pb_rails("title_detail", props: {
+ title: "Society Hill",
+ detail: "72 E St Astoria"
+ }) %>
+ <% end %>
+<% end %>
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.jsx b/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.jsx
new file mode 100644
index 0000000000..c426fbc066
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.jsx
@@ -0,0 +1,68 @@
+import React from 'react'
+
+import Timeline from '../_timeline'
+import Title from '../../pb_title/_title'
+import Pill from '../../pb_pill/_pill'
+
+import TitleDetail from '../../pb_title_detail/_title_detail'
+
+const TimelineWithChildren = (props) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
+
+export default TimelineWithChildren
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.md b/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.md
new file mode 100644
index 0000000000..dc0264dd9e
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.md
@@ -0,0 +1,2 @@
+Any kit can be used inside of our compound components of label, step, or detail. Expand the code snippet below to see how to use these children elements.
+
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/docs/example.yml b/playbook/app/pb_kits/playbook/pb_timeline/docs/example.yml
index 2cf2a99a73..1cd961b5d5 100644
--- a/playbook/app/pb_kits/playbook/pb_timeline/docs/example.yml
+++ b/playbook/app/pb_kits/playbook/pb_timeline/docs/example.yml
@@ -4,10 +4,11 @@ examples:
- timeline_default: Default
- timeline_vertical: Vertical
- timeline_with_date: With Date
+ - timeline_with_children: With Children
react:
- timeline_default: Default
- timeline_vertical: Vertical
- timeline_with_date: With Date
-
+ - timeline_with_children: With Children
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/docs/index.js b/playbook/app/pb_kits/playbook/pb_timeline/docs/index.js
index 35398d22d6..8da0d1e1f0 100644
--- a/playbook/app/pb_kits/playbook/pb_timeline/docs/index.js
+++ b/playbook/app/pb_kits/playbook/pb_timeline/docs/index.js
@@ -1,3 +1,4 @@
export { default as TimelineDefault } from './_timeline_default.jsx'
export { default as TimelineVertical } from './_timeline_vertical.jsx'
export { default as TimelineWithDate } from './_timeline_with_date.jsx'
+export { default as TimelineWithChildren } from './_timeline_with_children.jsx'
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/item.html.erb b/playbook/app/pb_kits/playbook/pb_timeline/item.html.erb
index cb815cb6e2..8f7153b22b 100644
--- a/playbook/app/pb_kits/playbook/pb_timeline/item.html.erb
+++ b/playbook/app/pb_kits/playbook/pb_timeline/item.html.erb
@@ -1,25 +1,21 @@
<%= pb_content_tag do %>
+ <% if label %>
+ <%= label %>
+ <% else %>
+ <%= pb_rails("timeline/label", props: { date: date }) %>
+ <% end %>
-
- <% if object.date.present? %>
- <%= pb_rails("date_stacked", props: {
- date: object.date,
- size: "sm",
- align: "center"
- }) %>
- <% end %>
-
-
-
- <%= pb_rails("icon_circle", props: {
- icon: object.icon,
- variant: object.icon_color,
- size: "xs"
- }) %>
-
-
+ <% if step %>
+ <%= step %>
+ <% else %>
+ <%= pb_rails("timeline/step", props: { icon: icon, icon_color: icon_color }) %>
+ <% end %>
-
- <%= content.presence %>
-
+ <% if detail%>
+ <%= detail%>
+ <% else %>
+ <%= pb_rails("timeline/detail") do %>
+ <%= content %>
+ <% end %>
+ <% end %>
<% end %>
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/item.rb b/playbook/app/pb_kits/playbook/pb_timeline/item.rb
index f5c5830777..9a954cc413 100644
--- a/playbook/app/pb_kits/playbook/pb_timeline/item.rb
+++ b/playbook/app/pb_kits/playbook/pb_timeline/item.rb
@@ -13,6 +13,10 @@ class Item < Playbook::KitBase
values: %w[solid dotted],
default: "solid"
+ renders_one :label
+ renders_one :step
+ renders_one :detail
+
def classname
generate_classname("pb_timeline_item_kit", line_style)
end
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/label.html.erb b/playbook/app/pb_kits/playbook/pb_timeline/label.html.erb
new file mode 100644
index 0000000000..c7b4b3b18b
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/label.html.erb
@@ -0,0 +1,12 @@
+<%= pb_content_tag do %>
+ <% if object.date.present? %>
+ <%= pb_rails("date_stacked", props: {
+ date: object.date,
+ size: "sm",
+ align: "center"
+ }) %>
+ <% else %>
+ <%= content.presence %>
+ <% end %>
+<% end %>
+
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/label.rb b/playbook/app/pb_kits/playbook/pb_timeline/label.rb
new file mode 100644
index 0000000000..bfb74a469f
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/label.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Playbook
+ module PbTimeline
+ class Label < Playbook::KitBase
+ prop :date
+
+ def classname
+ generate_classname("pb_timeline_item_left_block")
+ end
+ end
+ end
+end
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/step.html.erb b/playbook/app/pb_kits/playbook/pb_timeline/step.html.erb
new file mode 100644
index 0000000000..533e1cb383
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/step.html.erb
@@ -0,0 +1,14 @@
+<%= pb_content_tag do %>
+ <% if object.icon.present? %>
+ <%= pb_rails("icon_circle", props: {
+ icon: object.icon,
+ variant: object.icon_color,
+ size: "xs"
+ }) %>
+ <% else %>
+ <%= content.presence %>
+ <% end %>
+
+<% end %>
+
+
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/step.rb b/playbook/app/pb_kits/playbook/pb_timeline/step.rb
new file mode 100644
index 0000000000..a80d28d51a
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/step.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Playbook
+ module PbTimeline
+ class Step < Playbook::KitBase
+ prop :icon, type: Playbook::Props::String
+ prop :icon_color, type: Playbook::Props::Enum,
+ values: %w[default royal blue purple teal red yellow green],
+ default: "default"
+
+ def classname
+ generate_classname("pb_timeline_item_step")
+ end
+ end
+ end
+end
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Detail.tsx b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Detail.tsx
new file mode 100644
index 0000000000..0365376132
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Detail.tsx
@@ -0,0 +1,29 @@
+import React from 'react'
+import classnames from 'classnames'
+import { buildHtmlProps } from '../../utilities/props'
+import { globalProps, GlobalProps } from "../../utilities/globalProps"
+
+type TimelineDetailProps = {
+ children?: React.ReactNode,
+ className?: string,
+ htmlOptions?: { [key: string]: any },
+} & GlobalProps
+
+const TimelineDetail: React.FC = ({
+ children,
+ className,
+ htmlOptions = {},
+ ...props
+}) => {
+ const htmlProps = buildHtmlProps(htmlOptions)
+ return (
+
+ {children}
+
+ )
+}
+
+export default TimelineDetail
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Label.tsx b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Label.tsx
new file mode 100644
index 0000000000..717e92aeaf
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Label.tsx
@@ -0,0 +1,38 @@
+import React from 'react'
+import classnames from 'classnames'
+import { buildHtmlProps } from '../../utilities/props'
+import { globalProps, GlobalProps } from "../../utilities/globalProps"
+import DateStacked from '../../pb_date_stacked/_date_stacked'
+
+type TimelineLabelProps = {
+ date?: Date,
+ children?: React.ReactNode,
+ className?: string,
+ htmlOptions?: { [key: string]: any },
+} & GlobalProps
+
+const TimelineLabel: React.FC = ({
+ date,
+ children,
+ className,
+ htmlOptions = {},
+ ...props
+}) => {
+ const htmlProps = buildHtmlProps(htmlOptions)
+ return (
+
+ {children}
+ {date && (
+
+ )}
+
+ )
+}
+
+export default TimelineLabel
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Step.tsx b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Step.tsx
new file mode 100644
index 0000000000..648c1d9b0a
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/Step.tsx
@@ -0,0 +1,42 @@
+import React from 'react'
+import classnames from 'classnames'
+import { buildHtmlProps } from '../../utilities/props'
+import { globalProps, GlobalProps } from "../../utilities/globalProps"
+import IconCircle from '../../pb_icon_circle/_icon_circle'
+
+type TimelineStepProps = {
+ icon?: string,
+ iconColor?: 'default' | 'royal' | 'blue' | 'purple' | 'teal' | 'red' | 'yellow' | 'green',
+ children?: React.ReactNode,
+ className?: string,
+ htmlOptions?: { [key: string]: any },
+} & GlobalProps
+
+const TimelineStep: React.FC = ({
+ icon = 'user',
+ iconColor = 'default',
+ children,
+ className,
+ htmlOptions = {},
+ ...props
+}) => {
+ const htmlProps = buildHtmlProps(htmlOptions)
+ return (
+
+ {children ? (
+ children
+ ) : (
+
+ )}
+
+
+ )
+}
+
+export default TimelineStep
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/index.tsx b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/index.tsx
new file mode 100644
index 0000000000..693e027a7d
--- /dev/null
+++ b/playbook/app/pb_kits/playbook/pb_timeline/subcomponents/index.tsx
@@ -0,0 +1,3 @@
+export { default as TimelineLabel } from './Label';
+export { default as TimelineDetail } from './Detail';
+export { default as TimelineStep } from './Step';
diff --git a/playbook/app/pb_kits/playbook/pb_timeline/timeline.test.js b/playbook/app/pb_kits/playbook/pb_timeline/timeline.test.js
index fa71db5bad..9468e2070d 100644
--- a/playbook/app/pb_kits/playbook/pb_timeline/timeline.test.js
+++ b/playbook/app/pb_kits/playbook/pb_timeline/timeline.test.js
@@ -2,6 +2,10 @@ import React from 'react'
import { render, screen } from '../utilities/test-utils'
import Timeline from './_timeline'
+import TimelineItem from './_item'
+import TimelineLabel from './subcomponents/Label'
+import TimelineStep from './subcomponents/Step'
+import TimelineDetail from './subcomponents/Detail'
import TitleDetail from '../pb_title_detail/_title_detail'
const testId = 'timeline'
@@ -43,18 +47,91 @@ const TimelineDefault = (props) => (
>
)
+const TimelineWithChildren = (props) => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+)
+
test('should pass data prop', () => {
render()
const kit = screen.getByTestId(testId)
expect(kit).toBeInTheDocument()
})
+test('should pass data prop using children', () => {
+ render()
+ const kit = screen.getByTestId(testId)
+ expect(kit).toBeInTheDocument()
+})
+
test('should pass className prop', () => {
render()
const kit = screen.getByTestId(testId)
expect(kit).toHaveClass(className)
})
+test('should pass className prop with children', () => {
+ render()
+ const kit = screen.getByTestId(testId)
+ expect(kit).toHaveClass(className)
+})
+
test('should pass aria prop', () => {
render()
const kit = screen.getByTestId(testId)
@@ -86,3 +163,10 @@ test('should pass showDate prop', () => {
const kit = screen.getByTestId(testId)
expect(kit).toHaveClass('pb_timeline_kit__horizontal__with_date')
})
+
+test('should pass showDate prop with Children', () => {
+ const props = { showDate: true }
+ render()
+ const kit = screen.getByTestId(testId)
+ expect(kit).toHaveClass('pb_timeline_kit__horizontal__with_date')
+})
diff --git a/playbook/spec/pb_kits/playbook/kits/timeline_detail_spec.rb b/playbook/spec/pb_kits/playbook/kits/timeline_detail_spec.rb
new file mode 100644
index 0000000000..fc77b24b5c
--- /dev/null
+++ b/playbook/spec/pb_kits/playbook/kits/timeline_detail_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require_relative "../../../../app/pb_kits/playbook/pb_timeline/detail"
+
+RSpec.describe Playbook::PbTimeline::Detail do
+ subject { Playbook::PbTimeline::Detail }
+
+ describe "#classname" do
+ it "returns the correct class name" do
+ expect(subject.new({}).classname).to eq "pb_timeline_item_right_block"
+ end
+ end
+end
diff --git a/playbook/spec/pb_kits/playbook/kits/timeline_label_spec.rb b/playbook/spec/pb_kits/playbook/kits/timeline_label_spec.rb
new file mode 100644
index 0000000000..555837360d
--- /dev/null
+++ b/playbook/spec/pb_kits/playbook/kits/timeline_label_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require_relative "../../../../app/pb_kits/playbook/pb_timeline/label"
+
+RSpec.describe Playbook::PbTimeline::Label do
+ subject { Playbook::PbTimeline::Label }
+
+ it { is_expected.to define_prop(:date) }
+
+ describe "#classname" do
+ it "returns the correct class name" do
+ expect(subject.new.classname).to eq "pb_timeline_item_left_block"
+ end
+ end
+end
diff --git a/playbook/spec/pb_kits/playbook/kits/timeline_step_spec.rb b/playbook/spec/pb_kits/playbook/kits/timeline_step_spec.rb
new file mode 100644
index 0000000000..ecbe365c72
--- /dev/null
+++ b/playbook/spec/pb_kits/playbook/kits/timeline_step_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "../../../../app/pb_kits/playbook/pb_timeline/step"
+
+RSpec.describe Playbook::PbTimeline::Step do
+ subject { Playbook::PbTimeline::Step }
+
+ it { is_expected.to define_prop(:icon) }
+
+ it {
+ is_expected.to define_enum_prop(:icon_color)
+ .with_default("default")
+ .with_values("default", "royal", "blue", "purple", "teal", "red", "yellow", "green")
+ }
+
+ describe "#classname" do
+ it "returns the correct class name" do
+ expect(subject.new.classname).to eq "pb_timeline_item_step"
+ end
+ end
+end