playbook_ui 14.6.2.pre.alpha.PLAY15814384 → 14.7.0.pre.alpha.PBNTR667railstypeaheadformintegration4454

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) 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_dropdown/_dropdown.tsx +8 -7
  8. data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +2 -2
  9. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb +1 -1
  10. data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +63 -12
  11. data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.scss +37 -0
  12. data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.tsx +67 -0
  13. data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading_mixins.scss +40 -0
  14. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.jsx +51 -0
  15. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.md +1 -0
  16. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.jsx +26 -0
  17. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.md +1 -0
  18. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.html.erb +1 -0
  19. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.jsx +11 -0
  20. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.jsx +59 -0
  21. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.md +3 -0
  22. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.jsx +20 -0
  23. data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.md +3 -0
  24. data/app/pb_kits/playbook/pb_skeleton_loading/docs/example.yml +13 -0
  25. data/app/pb_kits/playbook/pb_skeleton_loading/docs/index.js +5 -0
  26. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.html.erb +12 -0
  27. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.rb +8 -0
  28. data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.test.jsx +81 -0
  29. data/app/pb_kits/playbook/pb_typeahead/index.ts +36 -2
  30. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +5 -2
  31. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +4 -0
  32. data/app/pb_kits/playbook/utilities/_hover.scss +46 -43
  33. data/app/pb_kits/playbook/utilities/globalPropNames.mjs +1 -0
  34. data/app/pb_kits/playbook/utilities/globalProps.ts +5 -0
  35. data/dist/chunks/{_typeahead-C-6MLSyC.js → _typeahead-B-juiSkw.js} +2 -2
  36. data/dist/chunks/_weekday_stacked-DPqQHJ1l.js +45 -0
  37. data/dist/chunks/{lib-D-mTv-kp.js → lib-DpxYMiKe.js} +1 -1
  38. data/dist/chunks/{pb_form_validation-BkWGwJsl.js → pb_form_validation-LqRlnmi6.js} +1 -1
  39. data/dist/chunks/vendor.js +1 -1
  40. data/dist/menu.yml +4 -1
  41. data/dist/playbook-doc.js +1 -1
  42. data/dist/playbook-rails-react-bindings.js +1 -1
  43. data/dist/playbook-rails.js +1 -1
  44. data/dist/playbook.css +1 -1
  45. data/lib/playbook/forms/builder/typeahead_field.rb +13 -0
  46. data/lib/playbook/hover.rb +4 -1
  47. data/lib/playbook/version.rb +2 -2
  48. metadata +24 -6
  49. data/dist/chunks/_weekday_stacked-CFhGhr9V.js +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99a985521715b9d9f16885c5dc378e0d7ef8eebcbad2a611a07b1d64430497ca
4
- data.tar.gz: 746ddd3bb48bac086703ef46dc0733126e3a192ea50be712662ebf9c2db4d77b
3
+ metadata.gz: 5ba8b6ebdd4078924cdcecd475be6892960449af516b17143f79b6475a4f0162
4
+ data.tar.gz: ab99598eb62828f045a9f61622b5b0d036e07a410a410004b9ed0ca29ab1630c
5
5
  SHA512:
6
- metadata.gz: 1b78546cabdd8d4372db3fa8031a6d738d1357ddb8d0e860ed080b2f12fcdafc8ede1cfe0bbc8454465c488fdff6a373be95d70a81cca91711a966b0152cbb08
7
- data.tar.gz: 9fa3a3f4a1beb10625b7bd7774c9b9029436487c75b817d9e71667d3876fdd16c611cb3bbfed67ddec48e9d46ff260c795344df9d35a94f3b7bc11ef58360ddb
6
+ metadata.gz: 644594e1a5ff791f14904f59c7b959d9d22bb22f7ef5c148dd37449608a2b86248cdf452a1a6b3eecf3a826c0867f10709c5464588a61cec763c5fc1bf7acbd2
7
+ data.tar.gz: 2c26b39a37968e5cc44bd2bdb135bad81d67c624b9ae3b47bdcb2f58e76efbb965ae6d510bbec45f859c2a0a0401661dd3149a3a748a91a9b613eb98593a4408
@@ -1,4 +1,3 @@
1
-
2
1
  @import 'pb_advanced_table/advanced_table';
3
2
  @import 'pb_avatar/avatar';
4
3
  @import 'pb_avatar_action_button/avatar_action_button';
@@ -85,6 +84,7 @@
85
84
  @import 'pb_selectable_card_icon/selectable_card_icon';
86
85
  @import 'pb_selectable_icon/selectable_icon';
87
86
  @import 'pb_selectable_list/selectable_list';
87
+ @import 'pb_skeleton_loading/skeleton_loading';
88
88
  @import 'pb_source/source';
89
89
  @import 'pb_star_rating/star_rating';
90
90
  @import 'pb_stat_change/stat_change';
@@ -16,6 +16,7 @@ interface CustomCellProps {
16
16
  onRowToggleClick?: (arg: Row<GenericObject>) => void
17
17
  row: Row<GenericObject>
18
18
  value?: string
19
+ customRenderer?: (row: Row<GenericObject>, value: string | undefined) => React.ReactNode
19
20
  }
20
21
 
21
22
  export const CustomCell = ({
@@ -23,6 +24,7 @@ export const CustomCell = ({
23
24
  onRowToggleClick,
24
25
  row,
25
26
  value,
27
+ customRenderer,
26
28
  }: CustomCellProps & GlobalProps) => {
27
29
  const { setExpanded, expanded, expandedControl, inlineRowLoading } = useContext(AdvancedTableContext);
28
30
 
@@ -61,7 +63,12 @@ export const CustomCell = ({
61
63
  </button>
62
64
  ) : null}
63
65
  <FlexItem paddingLeft={renderButton? "none" : "xs"}>
64
- {row.depth === 0 ? getValue() : value}
66
+ {row.depth === 0 ? (
67
+ customRenderer ? customRenderer(row, getValue()) : getValue()
68
+ ) :(
69
+ customRenderer ? customRenderer(row, value) : value
70
+ )
71
+ }
65
72
  </FlexItem>
66
73
  </Flex>
67
74
  </div>
@@ -90,8 +90,8 @@ const AdvancedTable = (props: AdvancedTableProps) => {
90
90
 
91
91
  const columnHelper = createColumnHelper()
92
92
 
93
- //Create cells for first columns
94
- const createCellFunction = (cellAccessors: string[], customRenderer?: (row: Row<GenericObject>, value: any) => JSX.Element) => {
93
+ //Create cells for columns, with customization for first column
94
+ const createCellFunction = (cellAccessors: string[], customRenderer?: (row: Row<GenericObject>, value: any) => JSX.Element, index?: number) => {
95
95
  const columnCells = ({
96
96
  row,
97
97
  getValue,
@@ -101,19 +101,16 @@ const AdvancedTable = (props: AdvancedTableProps) => {
101
101
  }) => {
102
102
  const rowData = row.original
103
103
 
104
- // Use customRenderer if provided, otherwise default rendering
105
- if (customRenderer) {
106
- return customRenderer(row, getValue())
107
- }
108
-
104
+ if (index === 0) {
109
105
  switch (row.depth) {
110
106
  case 0: {
111
107
  return (
112
- <CustomCell
113
- getValue={getValue}
114
- onRowToggleClick={onRowToggleClick}
115
- row={row}
116
- />
108
+ <CustomCell
109
+ customRenderer={customRenderer}
110
+ getValue={getValue}
111
+ onRowToggleClick={onRowToggleClick}
112
+ row={row}
113
+ />
117
114
  )
118
115
  }
119
116
  default: {
@@ -122,6 +119,7 @@ const AdvancedTable = (props: AdvancedTableProps) => {
122
119
  const accessorValue = rowData[depthAccessor]
123
120
  return accessorValue ? (
124
121
  <CustomCell
122
+ customRenderer={customRenderer}
125
123
  onRowToggleClick={onRowToggleClick}
126
124
  row={row}
127
125
  value={accessorValue}
@@ -132,11 +130,13 @@ const AdvancedTable = (props: AdvancedTableProps) => {
132
130
  }
133
131
  }
134
132
  }
135
-
133
+ return customRenderer
134
+ ? customRenderer(row, getValue())
135
+ : getValue()
136
+ }
136
137
  return columnCells
137
138
  }
138
-
139
- //Create column array in format needed by Tanstack
139
+ //Create column array in format needed by Tanstack
140
140
  const columns =
141
141
  columnDefinitions &&
142
142
  columnDefinitions.map((column, index) => {
@@ -147,19 +147,12 @@ const AdvancedTable = (props: AdvancedTableProps) => {
147
147
  }),
148
148
  }
149
149
 
150
- // Use the custom renderer if provided, EXCEPT for the first column
151
- if (index !== 0) {
152
- if (column.cellAccessors || column.customRenderer) {
153
- columnStructure.cell = createCellFunction(
154
- column.cellAccessors,
155
- column.customRenderer
156
- )
157
- }
158
- } else {
159
- // For the first column, apply createCellFunction without customRenderer
160
- if (column.cellAccessors) {
161
- columnStructure.cell = createCellFunction(column.cellAccessors)
162
- }
150
+ if (column.cellAccessors || column.customRenderer) {
151
+ columnStructure.cell = createCellFunction(
152
+ column.cellAccessors,
153
+ column.customRenderer,
154
+ index
155
+ )
163
156
  }
164
157
 
165
158
  return columnStructure
@@ -1,7 +1,7 @@
1
1
  import React, {useState} from "react"
2
2
  import { render, screen, waitFor } from "../utilities/test-utils"
3
3
 
4
- import { AdvancedTable } from "playbook-ui"
4
+ import { AdvancedTable, Pill } from "playbook-ui"
5
5
 
6
6
  const MOCK_DATA = [
7
7
  {
@@ -88,6 +88,28 @@ const columnDefinitions = [
88
88
  },
89
89
  ]
90
90
 
91
+ const columnDefinitionsCustomRenderer = [
92
+ {
93
+ accessor: "year",
94
+ label: "Year",
95
+ cellAccessors: ["quarter", "month", "day"],
96
+ },
97
+ {
98
+ accessor: "newEnrollments",
99
+ label: "New Enrollments",
100
+ customRenderer: (row, value) => (
101
+ <Pill text={value}
102
+ variant="success"
103
+ />
104
+ ),
105
+ },
106
+ {
107
+ accessor: "scheduledMeetings",
108
+ label: "Scheduled Meetings",
109
+ },
110
+ ]
111
+
112
+
91
113
  const subRowHeaders = ["Quarter"]
92
114
 
93
115
  const testId = "advanced_table"
@@ -463,3 +485,17 @@ test("responsive none prop functions as expected", () => {
463
485
  const kit = screen.getByTestId(testId)
464
486
  expect(kit).toHaveClass("pb_advanced_table table-responsive-none")
465
487
  })
488
+
489
+ test("customRenderer prop functions as expected", () => {
490
+ render(
491
+ <AdvancedTable
492
+ columnDefinitions={columnDefinitionsCustomRenderer}
493
+ data={{ testid: testId }}
494
+ tableData={MOCK_DATA}
495
+ />
496
+ )
497
+
498
+ const kit = screen.getByTestId(testId)
499
+ const pill = kit.querySelector(".pb_pill_kit_success_lowercase")
500
+ expect(pill).toBeInTheDocument()
501
+ })
@@ -1,5 +1,5 @@
1
1
  import React from "react"
2
- import { AdvancedTable, Pill, Body, Flex, Detail, Caption } from "playbook-ui"
2
+ import { AdvancedTable, Pill, Body, Flex, Detail, Caption, Badge, Title } from "playbook-ui"
3
3
  import MOCK_DATA from "./advanced_table_mock_data.json"
4
4
 
5
5
  const AdvancedTableCustomCell = (props) => {
@@ -8,7 +8,18 @@ const AdvancedTableCustomCell = (props) => {
8
8
  accessor: "year",
9
9
  label: "Year",
10
10
  cellAccessors: ["quarter", "month", "day"],
11
-
11
+ customRenderer: (row, value) => (
12
+ <Flex>
13
+ <Title size={4}
14
+ text={value}
15
+ />
16
+ <Badge dark
17
+ marginLeft="xxs"
18
+ text={row.original.newEnrollments > 20 ? "High" : "Low"}
19
+ variant="neutral"
20
+ />
21
+ </Flex>
22
+ ),
12
23
  },
13
24
  {
14
25
  accessor: "newEnrollments",
@@ -47,7 +47,7 @@ interface DropdownComponent
47
47
  Container: typeof DropdownContainer;
48
48
  }
49
49
 
50
- const Dropdown = forwardRef((props: DropdownProps, ref: any) => {
50
+ let Dropdown = (props: DropdownProps, ref: any): React.ReactElement | null => {
51
51
  const {
52
52
  aria = {},
53
53
  autocomplete = false,
@@ -260,7 +260,7 @@ const Dropdown = forwardRef((props: DropdownProps, ref: any) => {
260
260
  <DropdownContainer>
261
261
  {optionsWithBlankSelection &&
262
262
  optionsWithBlankSelection?.map((option: GenericObject) => (
263
- <Dropdown.Option key={option.id}
263
+ <DropdownOption key={option.id}
264
264
  option={option}
265
265
  />
266
266
  ))}
@@ -278,11 +278,12 @@ const Dropdown = forwardRef((props: DropdownProps, ref: any) => {
278
278
  </DropdownContext.Provider>
279
279
  </div>
280
280
  )
281
- }) as DropdownComponent
281
+ }
282
282
 
283
- Dropdown.displayName = "Dropdown";
284
- Dropdown.Option = DropdownOption;
285
- Dropdown.Trigger = DropdownTrigger;
286
- Dropdown.Container = DropdownContainer;
283
+ Dropdown = forwardRef(Dropdown) as unknown as DropdownComponent;
284
+ (Dropdown as DropdownComponent).displayName = "Dropdown";
285
+ (Dropdown as DropdownComponent).Option = DropdownOption;
286
+ (Dropdown as DropdownComponent).Trigger = DropdownTrigger;
287
+ (Dropdown as DropdownComponent).Container = DropdownContainer;
287
288
 
288
289
  export default Dropdown;
@@ -23,7 +23,7 @@
23
23
  %>
24
24
 
25
25
  <%= pb_form_with(scope: :example, url: "", method: :get) do |form| %>
26
- <%= form.typeahead :example_user, props: { data: { typeahead_example1: true, user: {} }, placeholder: "Search for a user" } %>
26
+ <%= form.typeahead :example_typeahead, props: { data: { typeahead_example1: true, user: {} }, label: true, placeholder: "Search for a user" } %>
27
27
  <%= form.text_field :example_text_field, props: { label: true } %>
28
28
  <%= form.phone_number_field :example_phone_number_field, props: { label: "Example phone field" } %>
29
29
  <%= form.email_field :example_email_field, props: { label: true } %>
@@ -92,7 +92,7 @@
92
92
  const selectedUserData = JSON.parse(selectedUserJSON)
93
93
 
94
94
  // set the input field's value
95
- event.target.querySelector('input[name=example_user]').value = selectedUserData.login
95
+ event.target.querySelector('input[name=example_typeahead]').value = selectedUserData.login
96
96
 
97
97
  // log the selected option's dataset
98
98
  console.log('The selected user data:')
@@ -1,5 +1,5 @@
1
1
  <%= pb_form_with(scope: :example, url: "", method: :get, loading: true) do |form| %>
2
- <%= form.text_field :example_text_field, props: { label: true } %>
2
+ <%= form.text_field :example_text_field_loading, props: { label: true } %>
3
3
 
4
4
  <%= form.actions do |action| %>
5
5
  <%= action.submit %>
@@ -22,23 +22,74 @@
22
22
  %>
23
23
 
24
24
  <%= pb_form_with(scope: :example, method: :get, url: "", validate: true) do |form| %>
25
- <%= form.text_field :example_text_field, props: { label: true, required: true } %>
26
- <%= form.phone_number_field :example_phone_number_field, props: { label: "Example phone field" } %>
27
- <%= form.email_field :example_email_field, props: { label: true, required: true } %>
28
- <%= form.number_field :example_number_field, props: { label: true, required: true } %>
29
- <%= form.search_field :example_project_number, props: { label: true, required: true, validation: { pattern: "[0-9]{2}-[0-9]{5}", message: "Please enter a valid project number (example: 33-12345)." } } %>
30
- <%= form.password_field :example_password_field, props: { label: true, required: true } %>
31
- <%= form.url_field :example_url_field, props: { label: true, required: true } %>
32
- <%= form.text_area :example_text_area, props: { label: true, required: true } %>
33
- <%= form.dropdown_field :example_dropdown, props: { label: true, options: example_dropdown_options, required: true } %>
34
- <%= form.select :example_select, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true } %>
35
- <%= form.collection_select :example_collection_select, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %>
25
+ <%= form.typeahead :example_typeahead_validation, props: { data: { typeahead_example2: true, user: {} }, label: true, placeholder: "Search for a user", required: true, validation: { message: "Please select a user." } } %>
26
+ <%= form.text_field :example_text_field_validation, props: { label: true, required: true } %>
27
+ <%= form.phone_number_field :example_phone_number_field_validation, props: { label: "Example phone field" } %>
28
+ <%= form.email_field :example_email_field_validation, props: { label: true, required: true } %>
29
+ <%= form.number_field :example_number_field_validation, props: { label: true, required: true } %>
30
+ <%= form.search_field :example_project_number_validation, props: { label: true, required: true, validation: { pattern: "[0-9]{2}-[0-9]{5}", message: "Please enter a valid project number (example: 33-12345)." } } %>
31
+ <%= form.password_field :example_password_field_validation, props: { label: true, required: true } %>
32
+ <%= form.url_field :example_url_field_validation, props: { label: true, required: true } %>
33
+ <%= form.text_area :example_text_area_validation, props: { label: true, required: true } %>
34
+ <%= form.dropdown_field :example_dropdown_validation, props: { label: true, options: example_dropdown_options, required: true } %>
35
+ <%= form.select :example_select_validation, [ ["Yes", 1], ["No", 2] ], props: { label: true, blank_selection: "Select One...", required: true } %>
36
+ <%= form.collection_select :example_collection_select_validation, example_collection, :value, :name, props: { label: true, blank_selection: "Select One...", required: true } %>
36
37
  <%= form.check_box :example_checkbox, props: { text: "Example Checkbox", label: true, required: true } %>
37
38
  <%= form.date_picker :example_date_picker_2, props: { label: true, required: true } %>
38
- <%= form.star_rating_field :example_star_rating, props: { variant: "interactive", label: true, required: true } %>
39
+ <%= form.star_rating_field :example_star_rating_validation, props: { variant: "interactive", label: true, required: true } %>
39
40
 
40
41
  <%= form.actions do |action| %>
41
42
  <%= action.submit %>
42
43
  <%= action.button props: { type: "reset", text: "Cancel", variant: "secondary" } %>
43
44
  <% end %>
44
45
  <% end %>
46
+
47
+ <!-- form.typeahead user results example template -->
48
+ <template data-typeahead-example-result-option>
49
+ <%= pb_rails("user", props: {
50
+ name: tag(:slot, name: "name"),
51
+ orientation: "horizontal",
52
+ align: "left",
53
+ avatar_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGP6zwAAAgcBApocMXEAAAAASUVORK5CYII=",
54
+ avatar: true
55
+ }) %>
56
+ </template>
57
+
58
+ <!-- form.typeahead JS example implementation -->
59
+ <%= javascript_tag defer: "defer" do %>
60
+ document.addEventListener("pb-typeahead-kit-search", function(event) {
61
+ if (!event.target.dataset || !event.target.dataset.typeaheadExample2) return
62
+
63
+ fetch(`https://api.github.com/search/users?q=${encodeURIComponent(event.detail.searchingFor)}`)
64
+ .then(response => response.json())
65
+ .then((result) => {
66
+ const resultOptionTemplate = document.querySelector("[data-typeahead-example-result-option]")
67
+
68
+ event.detail.setResults((result.items || []).map((user) => {
69
+ const wrapper = resultOptionTemplate.content.cloneNode(true)
70
+ wrapper.children[0].dataset.user = JSON.stringify(user)
71
+ wrapper.querySelector('slot[name="name"]').replaceWith(user.login)
72
+ wrapper.querySelector('img').dataset.src = user.avatar_url
73
+ return wrapper
74
+ }))
75
+ })
76
+ })
77
+
78
+
79
+ document.addEventListener("pb-typeahead-kit-result-option-selected", function(event) {
80
+ if (!event.target.dataset.typeaheadExample2) return
81
+
82
+ const selectedUserJSON = event.detail.selected.firstElementChild.dataset.user
83
+ const selectedUserData = JSON.parse(selectedUserJSON)
84
+
85
+ // set the input field's value
86
+ event.target.querySelector('input[name=example_typeahead_validation]').value = selectedUserData.login
87
+
88
+ // log the selected option's dataset
89
+ console.log('The selected user data:')
90
+ console.dir(selectedUserData)
91
+
92
+ // do even more with the data later - TBD
93
+ event.target.dataset.user = selectedUserJSON
94
+ })
95
+ <% end %>
@@ -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