playbook_ui 14.6.2.pre.alpha.PLAY1551tiptapextbump4350 → 14.6.2.pre.alpha.PLAY1613darkdatetimecolors4432

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +1 -1
  3. data/app/pb_kits/playbook/pb_advanced_table/Components/CustomCell.tsx +8 -1
  4. data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +21 -28
  5. data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +37 -1
  6. data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_custom_cell.jsx +13 -2
  7. data/app/pb_kits/playbook/pb_card/_card.tsx +11 -4
  8. data/app/pb_kits/playbook/pb_date/_date.scss +3 -0
  9. data/app/pb_kits/playbook/pb_dialog/_dialog.tsx +5 -1
  10. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +8 -7
  11. data/app/pb_kits/playbook/pb_flex/_flex.tsx +3 -1
  12. data/app/pb_kits/playbook/pb_flex/_flex_item.tsx +8 -2
  13. data/app/pb_kits/playbook/pb_flex/flex_item.html.erb +3 -6
  14. data/app/pb_kits/playbook/pb_flex/flex_item.rb +7 -2
  15. data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +6 -2
  16. data/app/pb_kits/playbook/pb_form_pill/_form_pill.tsx +30 -26
  17. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.html.erb +24 -1
  18. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.jsx +25 -2
  19. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text_rails.md +3 -0
  20. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text_react.md +1 -0
  21. data/app/pb_kits/playbook/pb_form_pill/form_pill.html.erb +46 -8
  22. data/app/pb_kits/playbook/pb_popover/_popover.tsx +1 -1
  23. data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.scss +37 -0
  24. data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.tsx +67 -0
  25. data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading_mixins.scss +40 -0
  26. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.jsx +51 -0
  27. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.md +1 -0
  28. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.jsx +26 -0
  29. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.md +1 -0
  30. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.html.erb +1 -0
  31. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.jsx +11 -0
  32. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.jsx +59 -0
  33. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.md +3 -0
  34. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.jsx +20 -0
  35. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.md +3 -0
  36. data/app/pb_kits/playbook/pb_skeleton_loading/docs/example.yml +13 -0
  37. data/app/pb_kits/playbook/pb_skeleton_loading/docs/index.js +5 -0
  38. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.html.erb +12 -0
  39. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.rb +8 -0
  40. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.test.jsx +81 -0
  41. data/app/pb_kits/playbook/pb_timeline/_item.tsx +59 -23
  42. data/app/pb_kits/playbook/pb_timeline/_timeline.tsx +8 -0
  43. data/app/pb_kits/playbook/pb_timeline/detail.html.erb +3 -0
  44. data/app/pb_kits/playbook/pb_timeline/detail.rb +11 -0
  45. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.html.erb +43 -0
  46. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.jsx +68 -0
  47. data/app/pb_kits/playbook/pb_timeline/docs/_timeline_with_children.md +2 -0
  48. data/app/pb_kits/playbook/pb_timeline/docs/example.yml +2 -1
  49. data/app/pb_kits/playbook/pb_timeline/docs/index.js +1 -0
  50. data/app/pb_kits/playbook/pb_timeline/item.html.erb +17 -21
  51. data/app/pb_kits/playbook/pb_timeline/item.rb +4 -0
  52. data/app/pb_kits/playbook/pb_timeline/label.html.erb +12 -0
  53. data/app/pb_kits/playbook/pb_timeline/label.rb +13 -0
  54. data/app/pb_kits/playbook/pb_timeline/step.html.erb +14 -0
  55. data/app/pb_kits/playbook/pb_timeline/step.rb +16 -0
  56. data/app/pb_kits/playbook/pb_timeline/subcomponents/Detail.tsx +29 -0
  57. data/app/pb_kits/playbook/pb_timeline/subcomponents/Label.tsx +38 -0
  58. data/app/pb_kits/playbook/pb_timeline/subcomponents/Step.tsx +42 -0
  59. data/app/pb_kits/playbook/pb_timeline/subcomponents/index.tsx +3 -0
  60. data/app/pb_kits/playbook/pb_timeline/timeline.test.js +84 -0
  61. data/app/pb_kits/playbook/utilities/_hover.scss +46 -43
  62. data/app/pb_kits/playbook/utilities/globalPropNames.mjs +4 -0
  63. data/app/pb_kits/playbook/utilities/globalProps.ts +44 -2
  64. data/dist/chunks/_typeahead-DhLic2Fe.js +22 -0
  65. data/dist/chunks/_weekday_stacked-Mx8TYP5I.js +45 -0
  66. data/dist/chunks/vendor.js +1 -1
  67. data/dist/menu.yml +4 -1
  68. data/dist/playbook-doc.js +1 -1
  69. data/dist/playbook-rails-react-bindings.js +1 -1
  70. data/dist/playbook-rails.js +1 -1
  71. data/dist/playbook.css +1 -1
  72. data/lib/playbook/hover.rb +4 -1
  73. data/lib/playbook/kit_base.rb +43 -5
  74. data/lib/playbook/version.rb +1 -1
  75. metadata +37 -5
  76. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_truncated_text.md +0 -1
  77. data/dist/chunks/_typeahead-BV_n6U5W.js +0 -22
  78. data/dist/chunks/_weekday_stacked-BLVPFG6h.js +0 -45
@@ -1,19 +1,57 @@
1
1
  <%= content_tag(:div, id: object.id, data: object.data, class: object.classname + object.size_class, tabindex: object.tabindex, **combined_html_options) do %>
2
2
  <% if object.name.present? %>
3
3
  <%= pb_rails("avatar", props: { name: object.name, image_url: object.avatar_url, size: "xxs" }) %>
4
- <%= pb_rails("title", props: { text: object.name, size: 4, classname: "pb_form_pill_text" }) %>
4
+ <% if object.truncate %>
5
+ <div>
6
+ <%= pb_rails("title", props: {
7
+ classname: "pb_form_pill_text truncate_#{object.truncate}",
8
+ id: object.id,
9
+ size: 4,
10
+ text: object.name,
11
+ }) %>
12
+ <% if object.id.present? %>
13
+ <%= pb_rails("tooltip", props: {
14
+ position: "top",
15
+ tooltip_id: "tooltip-#{object.id}",
16
+ trigger_element_selector: "##{object.id}"
17
+ }) do %>
18
+ <%= object.name %>
19
+ <% end %>
20
+ <% end %>
21
+ </div>
22
+ <% else %>
23
+ <%= pb_rails("title", props: { classname: "pb_form_pill_text", id: object.id, size: 4, text: object.name }) %>
24
+ <% end %>
5
25
  <% if object.icon.present? %>
6
26
  <%= pb_rails("icon", props: { classname: "pb_form_pill_icon", color: object.color, icon: object.icon }) %>
7
27
  <% end %>
8
28
  <% elsif object.text.present? %>
9
- <% if object.icon.present? %>
10
- <%= pb_rails("icon", props: { classname: "pb_form_pill_icon", color: object.color, icon: object.icon }) %>
11
- <% end %>
12
- <% if object.text.present? %>
13
- <%= pb_rails("title", props: { text: object.text, size: 4, classname: "pb_form_pill_tag" }) %>
14
- <% end %>
29
+ <% if object.icon.present? %>
30
+ <%= pb_rails("icon", props: { classname: "pb_form_pill_icon", color: object.color, icon: object.icon }) %>
31
+ <% end %>
32
+ <% if object.truncate %>
33
+ <div>
34
+ <%= pb_rails("title", props: {
35
+ classname: "pb_form_pill_tag truncate_#{object.truncate}",
36
+ id: object.id,
37
+ size: 4,
38
+ text: object.text,
39
+ }) %>
40
+ <% if object.id.present? %>
41
+ <%= pb_rails("tooltip", props: {
42
+ position: "top",
43
+ tooltip_id: "tooltip-#{object.id}",
44
+ trigger_element_selector: "##{object.id}"
45
+ }) do %>
46
+ <%= object.text %>
47
+ <% end %>
48
+ <% end %>
49
+ </div>
50
+ <% else %>
51
+ <%= pb_rails("title", props: { classname: "pb_form_pill_tag", id: object.id, size: 4, text: object.text, }) %>
52
+ <% end %>
15
53
  <% end %>
16
54
  <%= pb_rails("body", props: { classname: "pb_form_pill_close" }) do %>
17
55
  <%= pb_rails("icon", props: { icon: 'times', fixed_width: true, size: object.close_icon_size }) %>
18
56
  <% end %>
19
- <% end %>
57
+ <% end %>
@@ -21,7 +21,7 @@ import classnames from "classnames";
21
21
  import { globalProps, GlobalProps } from "../utilities/globalProps";
22
22
  import { uniqueId } from 'lodash';
23
23
 
24
- type ModifiedGlobalProps = Omit<GlobalProps, 'minWidth'>
24
+ type ModifiedGlobalProps = Omit<GlobalProps, 'minWidth' | 'maxHeight' | 'minHeight'>
25
25
 
26
26
  type PbPopoverProps = {
27
27
  aria?: { [key: string]: string };
@@ -0,0 +1,37 @@
1
+ @import 'skeleton_loading_mixins';
2
+
3
+ .pb_skeleton_loading {
4
+ display: "flex";
5
+ flex-direction: "column";
6
+ height: 100%;
7
+ .color_default {
8
+ @include skeleton-shimmer($silver);
9
+ }
10
+ .color_white {
11
+ @include skeleton-shimmer-light($white);
12
+ }
13
+ .dark {
14
+ @include skeleton-shimmer($border_dark);
15
+ }
16
+ .gap_xxs {
17
+ margin-top: 4px;
18
+ }
19
+ .gap_xs {
20
+ margin-top: 8px;
21
+ }
22
+ .gap_sm {
23
+ margin-top: 16px;
24
+ }
25
+ .gap_md {
26
+ margin-top: 24px;
27
+ }
28
+ .gap_lg {
29
+ margin-top: 32px;
30
+ }
31
+ .gap_xl {
32
+ margin-top: 40px;
33
+ }
34
+ .gap_xxl {
35
+ margin-top: 48px;
36
+ }
37
+ }
@@ -0,0 +1,67 @@
1
+
2
+ import React from 'react'
3
+ import classnames from 'classnames'
4
+ import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
5
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
6
+ import { Sizes } from '../types'
7
+
8
+
9
+ type SkeletonLoadingProps = {
10
+ aria?: { [key: string]: string },
11
+ className?: string,
12
+ data?: { [key: string]: string },
13
+ id?: string,
14
+ htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
15
+ height?: string,
16
+ width?: string,
17
+ borderRadius?: string,
18
+ gap?: Sizes | "none",
19
+ stack?: number,
20
+ color?: "default" | "white",
21
+ dark?: boolean,
22
+ } & GlobalProps
23
+
24
+ const SkeletonLoading = (props: SkeletonLoadingProps): React.ReactElement => {
25
+ const {
26
+ aria = {},
27
+ className,
28
+ data = {},
29
+ id,
30
+ htmlOptions = {},
31
+ height = "16px",
32
+ width = "100%",
33
+ borderRadius = "sm",
34
+ gap = "xxs",
35
+ stack = 1,
36
+ color = "default",
37
+ dark = false,
38
+ } = props
39
+
40
+ const ariaProps = buildAriaProps(aria)
41
+ const dataProps = buildDataProps(data)
42
+ const htmlProps = buildHtmlProps(htmlOptions)
43
+ const skeletonContainerCss = classnames(buildCss('pb_skeleton_loading'), globalProps(props), className)
44
+ const gapClass = gap !== 'none' ? `gap_${gap}` : ''
45
+ const innerSkeletonCss = classnames(`border_radius_${borderRadius}`,`color_${color}`, dark && 'dark', )
46
+ const innerSizeStyle = { height, width }
47
+
48
+ return (
49
+ <div
50
+ {...ariaProps}
51
+ {...dataProps}
52
+ {...htmlProps}
53
+ className={skeletonContainerCss}
54
+ id={id}
55
+ >
56
+ {Array.from({ length: Number(stack) }).map((_, index) => (
57
+ <div
58
+ className={classnames(buildCss('pb_skeleton_loading_item'), innerSkeletonCss, index > 0 && gapClass)}
59
+ key={index}
60
+ style={innerSizeStyle}
61
+ />
62
+ ))}
63
+ </div>
64
+ )
65
+ }
66
+
67
+ export default SkeletonLoading
@@ -0,0 +1,40 @@
1
+ // Animation
2
+ @keyframes wave {
3
+ 0% {
4
+ background-position: -468px 0;
5
+ }
6
+ 100% {
7
+ background-position: 468px 0;
8
+ }
9
+ }
10
+
11
+ // Shimmer animation and gradient mixin based on color
12
+ @mixin skeleton-shimmer($color) {
13
+ background: $color;
14
+ background-color: $color;
15
+ background-image: linear-gradient(
16
+ to left,
17
+ $color 0%,
18
+ lighten($color, 1%) 50%,
19
+ lighten($color, 1%) 60%,
20
+ $color 80%,
21
+ $color 100%
22
+ );
23
+ background-repeat: no-repeat;
24
+ animation: wave 1.5s linear infinite forwards;
25
+ }
26
+
27
+ @mixin skeleton-shimmer-light($color) {
28
+ background: $color;
29
+ background-color: $color;
30
+ background-image: linear-gradient(
31
+ to left,
32
+ $color 0%,
33
+ darken($color, 1%) 50%,
34
+ darken($color, 1%) 60%,
35
+ $color 80%,
36
+ $color 100%
37
+ );
38
+ background-repeat: no-repeat;
39
+ animation: wave 1.5s linear infinite forwards;
40
+ }
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+ import { Flex, SkeletonLoading } from "playbook-ui"
3
+
4
+
5
+ const SkeletonLoadingBorderRadius = (props) => (
6
+ <Flex justify="evenly">
7
+ <SkeletonLoading
8
+ borderRadius="rounded"
9
+ height="50px"
10
+ width="100px"
11
+ {...props}
12
+ />
13
+ <SkeletonLoading
14
+ borderRadius="xl"
15
+ height="50px"
16
+ width="100px"
17
+ {...props}
18
+ />
19
+ <SkeletonLoading
20
+ borderRadius="lg"
21
+ height="50px"
22
+ width="100px"
23
+ {...props}
24
+ />
25
+ <SkeletonLoading
26
+ borderRadius="md"
27
+ height="50px"
28
+ width="100px"
29
+ {...props}
30
+ />
31
+ <SkeletonLoading
32
+ height="50px"
33
+ width="100px"
34
+ {...props}
35
+ />
36
+ <SkeletonLoading
37
+ borderRadius="xs"
38
+ height="50px"
39
+ width="100px"
40
+ {...props}
41
+ />
42
+ <SkeletonLoading
43
+ borderRadius="none"
44
+ height="50px"
45
+ width="100px"
46
+ {...props}
47
+ />
48
+ </Flex>
49
+ )
50
+
51
+ export default SkeletonLoadingBorderRadius
@@ -0,0 +1 @@
1
+ The `borderRadius` prop accepts all of our [BorderRadius](https://playbook.powerapp.cloud/visual_guidelines/border_radius) tokens, with `sm` as default.
@@ -0,0 +1,26 @@
1
+ import React from 'react'
2
+ import { Card, SkeletonLoading } from "playbook-ui"
3
+
4
+
5
+ const SkeletonLoadingColor = (props) => (
6
+ <div>
7
+ <Card
8
+ borderNone
9
+ {...props}
10
+ >
11
+ <SkeletonLoading {...props}/>
12
+ </Card>
13
+ <Card
14
+ background="light"
15
+ borderNone
16
+ {...props}
17
+ >
18
+ <SkeletonLoading
19
+ color="white"
20
+ {...props}
21
+ />
22
+ </Card>
23
+ </div>
24
+ )
25
+
26
+ export default SkeletonLoadingColor
@@ -0,0 +1 @@
1
+ The SkeletonLoading component has a default and a white `color` variant.
@@ -0,0 +1 @@
1
+ <%= pb_rails("skeleton_loading") %>
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+ import { SkeletonLoading } from "playbook-ui"
3
+
4
+
5
+ const SkeletonLoadingDefault = (props) => (
6
+ <div>
7
+ <SkeletonLoading {...props} />
8
+ </div>
9
+ )
10
+
11
+ export default SkeletonLoadingDefault
@@ -0,0 +1,59 @@
1
+ import React from 'react'
2
+ import { Card, SkeletonLoading } from "playbook-ui"
3
+
4
+
5
+ const SkeletonLoadingHeightWidth = (props) => (
6
+ <div>
7
+ <SkeletonLoading
8
+ height="100px"
9
+ width="50%"
10
+ {...props}
11
+ />
12
+ <SkeletonLoading
13
+ gap="md"
14
+ height="20px"
15
+ marginY="md"
16
+ stack="3"
17
+ width="50px"
18
+ {...props}
19
+ />
20
+ <Card htmlOptions={{ style: { height: '200px', width: '100%' }}}
21
+ marginBottom="md"
22
+ padding="none"
23
+ >
24
+ <SkeletonLoading
25
+ borderRadius="md"
26
+ gap="xl"
27
+ height="50%"
28
+ width="300px"
29
+ {...props}
30
+ />
31
+ </Card>
32
+ <Card htmlOptions={{ style: { height: '200px', width: '100%' }}}
33
+ padding="none"
34
+ >
35
+ <SkeletonLoading
36
+ borderRadius="md"
37
+ gap="xl"
38
+ height="30%"
39
+ stack="2"
40
+ width="70%"
41
+ {...props}
42
+ />
43
+ </Card>
44
+ <SkeletonLoading
45
+ height="150px"
46
+ marginY="md"
47
+ width="150px"
48
+ {...props}
49
+ />
50
+ <SkeletonLoading
51
+ borderRadius="rounded"
52
+ height="150px"
53
+ width="150px"
54
+ {...props}
55
+ />
56
+ </div>
57
+ )
58
+
59
+ export default SkeletonLoadingHeightWidth
@@ -0,0 +1,3 @@
1
+ The `height` and `width` props accept pixel and percentage values. If using a percentage for `height`, the parent container must have a set height.
2
+
3
+ Set the `height` and `width` props to the same value to make a square. A `rounded` borderRadius will make a square a circle. If using percentages to make a square, your parent container must also be a square.
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { SkeletonLoading } from "playbook-ui"
3
+
4
+
5
+ const SkeletonLoadingLayout = (props) => (
6
+ <div>
7
+ <SkeletonLoading
8
+ stack="5"
9
+ {...props}
10
+ />
11
+ <SkeletonLoading
12
+ gap="md"
13
+ paddingTop="xl"
14
+ stack="3"
15
+ {...props}
16
+ />
17
+ </div>
18
+ )
19
+
20
+ export default SkeletonLoadingLayout
@@ -0,0 +1,3 @@
1
+ Use the `stack` and `gap` props in conjunction to layer multiple Skeleton loading bars on top of each other.
2
+
3
+ `stack` accepts a number that correlates to the number of rows (1 is default), and `gap` accepts a portion of our [spacing props](https://playbook.powerapp.cloud/visual_guidelines/spacing) (`xxs` as default, `xs`, `sm`, `md`, `lg`, `xl`, `xxl`) to set the pixel distance between each row. `gap` will not do anything if there is no corresponding `stack` prop set.
@@ -0,0 +1,13 @@
1
+ examples:
2
+
3
+ rails:
4
+ # - skeleton_loading_default: Default
5
+
6
+
7
+ react:
8
+ - skeleton_loading_default: Default
9
+ - skeleton_loading_color: Color
10
+ - skeleton_loading_layout: Layout
11
+ - skeleton_loading_border_radius: Border Radius
12
+ - skeleton_loading_height_width: Height & Width
13
+
@@ -0,0 +1,5 @@
1
+ export { default as SkeletonLoadingDefault } from './_skeleton_loading_default.jsx'
2
+ export { default as SkeletonLoadingColor } from './_skeleton_loading_color.jsx'
3
+ export { default as SkeletonLoadingLayout } from './_skeleton_loading_layout.jsx'
4
+ export { default as SkeletonLoadingBorderRadius } from './_skeleton_loading_border_radius.jsx'
5
+ export { default as SkeletonLoadingHeightWidth } from './_skeleton_loading_height_width.jsx'
@@ -0,0 +1,12 @@
1
+ <!-- Go to pb_content_tag definition in kit_base.rb for usage information. Commented out options are default (showing the default shape), and each can be deleted if not customizing that param. -->
2
+ <!-- If using nonstandard params please un-comment out and replace with your custom params. -->
3
+ <%= pb_content_tag(
4
+ # :div,
5
+ # aria: object.aria,
6
+ # class: object.classname,
7
+ # data: object.data,
8
+ # id: object.id,
9
+ # **combined_html_options
10
+ ) do %>
11
+ <span>SKELETON_LOADING CONTENT</span>
12
+ <% end %>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbSkeletonLoading
5
+ class SkeletonLoading < ::Playbook::KitBase
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,81 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { ensureAccessible } from '../utilities/test-utils'
4
+ import { SkeletonLoading } from 'playbook-ui'
5
+
6
+ /* See these resources for more testing info:
7
+ - https://github.com/testing-library/jest-dom#usage for useage and examples
8
+ - https://jestjs.io/docs/en/using-matchers
9
+ */
10
+
11
+ describe('SkeletonLoading', () => {
12
+ const defaultProps = {
13
+ data: { testid: 'skeleton-loading' }
14
+ }
15
+
16
+ it('should be accessible', async () => {
17
+ ensureAccessible(SkeletonLoading, defaultProps)
18
+ })
19
+
20
+ it('renders with default props', () => {
21
+ const { container } = render(<SkeletonLoading {...defaultProps} />)
22
+ const skeleton = screen.getByTestId('skeleton-loading')
23
+
24
+ expect(skeleton).toBeInTheDocument()
25
+ expect(skeleton).toHaveClass('pb_skeleton_loading')
26
+ expect(container.querySelectorAll('div[class*="border_radius_"]')).toHaveLength(1)
27
+ })
28
+
29
+ it('renders multiple skeleton items based on stack prop', () => {
30
+ const props = {
31
+ ...defaultProps,
32
+ stack: 3
33
+ }
34
+ const { container } = render(<SkeletonLoading {...props} />)
35
+
36
+ expect(container.querySelectorAll('div[class*="border_radius_"]')).toHaveLength(3)
37
+ })
38
+
39
+ it('applies custom styles correctly', () => {
40
+ const props = {
41
+ ...defaultProps,
42
+ height: '24px',
43
+ width: '50%',
44
+ borderRadius: 'lg',
45
+ color: 'light',
46
+ dark: true
47
+ }
48
+ const { container } = render(<SkeletonLoading {...props} />)
49
+ const skeletonItem = container.querySelector('div[class*="border_radius_"]')
50
+
51
+ expect(skeletonItem).toHaveClass('border_radius_lg')
52
+ expect(skeletonItem).toHaveClass('dark')
53
+ })
54
+
55
+ it('applies gap class to items after first one', () => {
56
+ const props = {
57
+ ...defaultProps,
58
+ stack: 3,
59
+ gap: 'md'
60
+ }
61
+ const { container } = render(<SkeletonLoading {...props} />)
62
+ const skeletonItems = container.querySelectorAll('div[class*="border_radius_"]')
63
+
64
+ expect(skeletonItems[0]).not.toHaveClass('gap_md')
65
+ expect(skeletonItems[1]).toHaveClass('gap_md')
66
+ expect(skeletonItems[2]).toHaveClass('gap_md')
67
+ })
68
+
69
+ it('handles no gap properly', () => {
70
+ const props = {
71
+ ...defaultProps,
72
+ stack: 2,
73
+ gap: 'none'
74
+ }
75
+ const { container } = render(<SkeletonLoading {...props} />)
76
+ const skeletonItems = container.querySelectorAll('div[class*="border_radius_"]')
77
+
78
+ expect(skeletonItems[0]).not.toHaveClass('gap_none')
79
+ expect(skeletonItems[1]).not.toHaveClass('gap_none')
80
+ })
81
+ })
@@ -1,12 +1,15 @@
1
1
  import React from 'react'
2
2
  import classnames from 'classnames'
3
-
4
3
  import { buildCss, buildHtmlProps } from '../utilities/props'
5
- import { globalProps, GlobalProps } from "../utilities/globalProps";
4
+ import { globalProps, GlobalProps } from "../utilities/globalProps"
6
5
 
7
6
  import DateStacked from '../pb_date_stacked/_date_stacked'
8
7
  import IconCircle from '../pb_icon_circle/_icon_circle'
9
8
 
9
+ import TimelineLabel from './subcomponents/Label'
10
+ import TimelineStep from './subcomponents/Step'
11
+ import TimelineDetail from './subcomponents/Detail'
12
+
10
13
  type ItemProps = {
11
14
  className?: string,
12
15
  children?: React.ReactNode[] | React.ReactNode,
@@ -17,6 +20,13 @@ type ItemProps = {
17
20
  lineStyle?: 'solid' | 'dotted',
18
21
  } & GlobalProps
19
22
 
23
+ function isElementOfType<P>(
24
+ element: React.ReactNode,
25
+ component: React.ComponentType<P>
26
+ ): element is React.ReactElement<P> {
27
+ return React.isValidElement<P>(element) && element.type === component
28
+ }
29
+
20
30
  const TimelineItem = ({
21
31
  className,
22
32
  children,
@@ -31,31 +41,57 @@ const TimelineItem = ({
31
41
 
32
42
  const htmlProps = buildHtmlProps(htmlOptions)
33
43
 
44
+ const childrenArray = React.Children.toArray(children)
45
+
46
+ const labelChild = childrenArray.find(
47
+ (child): child is React.ReactElement => isElementOfType(child, TimelineLabel)
48
+ )
49
+
50
+ const stepChild = childrenArray.find(
51
+ (child): child is React.ReactElement => isElementOfType(child, TimelineStep)
52
+ )
53
+
54
+ const detailChild = childrenArray.find(
55
+ (child): child is React.ReactElement => isElementOfType(child, TimelineDetail)
56
+ )
57
+
58
+ const otherChildren = childrenArray.filter(
59
+ (child) =>
60
+ !isElementOfType(child, TimelineLabel) &&
61
+ !isElementOfType(child, TimelineStep) &&
62
+ !isElementOfType(child, TimelineDetail)
63
+ )
64
+
34
65
  return (
35
- <div
66
+ <div
36
67
  {...htmlProps}
37
68
  className={classnames(timelineItemCss, globalProps(props), className)}
38
69
  >
39
- <div className="pb_timeline_item_left_block">
40
- {date &&
41
- <DateStacked
42
- align="center"
43
- date={date}
44
- size="sm"
45
- />
46
- }
47
- </div>
48
- <div className="pb_timeline_item_step">
49
- <IconCircle
50
- icon={icon}
51
- size="xs"
52
- variant={iconColor}
53
- />
54
- <div className="pb_timeline_item_connector" />
55
- </div>
56
- <div className="pb_timeline_item_right_block">
57
- {children}
58
- </div>
70
+ {labelChild || (
71
+ <div className="pb_timeline_item_left_block">
72
+ {date && (
73
+ <DateStacked
74
+ align="center"
75
+ date={date}
76
+ size="sm"
77
+ />
78
+ )}
79
+ </div>
80
+ )}
81
+ {stepChild || (
82
+ <div className="pb_timeline_item_step">
83
+ <IconCircle icon={icon}
84
+ size="xs"
85
+ variant={iconColor}
86
+ />
87
+ <div className="pb_timeline_item_connector" />
88
+ </div>
89
+ )}
90
+ {detailChild || (
91
+ <div className="pb_timeline_item_right_block">
92
+ { otherChildren }
93
+ </div>
94
+ )}
59
95
  </div>
60
96
  )
61
97
  }
@@ -5,6 +5,11 @@ import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../uti
5
5
  import { GlobalProps, globalProps } from '../utilities/globalProps'
6
6
 
7
7
  import TimelineItem from './_item'
8
+ import {
9
+ TimelineStep,
10
+ TimelineLabel,
11
+ TimelineDetail,
12
+ } from './subcomponents'
8
13
 
9
14
  type TimelineProps = {
10
15
  aria?: { [key: string]: string },
@@ -47,5 +52,8 @@ const Timeline = ({
47
52
  }
48
53
 
49
54
  Timeline.Item = TimelineItem
55
+ Timeline.Step = TimelineStep
56
+ Timeline.Label = TimelineLabel
57
+ Timeline.Detail = TimelineDetail
50
58
 
51
59
  export default Timeline
@@ -0,0 +1,3 @@
1
+ <%= pb_content_tag do %>
2
+ <%= content.presence %>
3
+ <% end %>