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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/_playbook.scss +1 -1
- data/app/pb_kits/playbook/pb_advanced_table/Components/CustomCell.tsx +8 -1
- data/app/pb_kits/playbook/pb_advanced_table/_advanced_table.tsx +21 -28
- data/app/pb_kits/playbook/pb_advanced_table/advanced_table.test.jsx +37 -1
- data/app/pb_kits/playbook/pb_advanced_table/docs/_advanced_table_custom_cell.jsx +13 -2
- data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +8 -7
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with.html.erb +2 -2
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with_loading.html.erb +1 -1
- data/app/pb_kits/playbook/pb_form/docs/_form_form_with_validate.html.erb +63 -12
- data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.scss +37 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading.tsx +67 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/_skeleton_loading_mixins.scss +40 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.jsx +51 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_border_radius.md +1 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.jsx +26 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_color.md +1 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.html.erb +1 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_default.jsx +11 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.jsx +59 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_height_width.md +3 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.jsx +20 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/_skeleton_loading_layout.md +3 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/example.yml +13 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/docs/index.js +5 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.html.erb +12 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.rb +8 -0
- data/app/pb_kits/playbook/pb_skeleton_loading/skeleton_loading.test.jsx +81 -0
- data/app/pb_kits/playbook/pb_typeahead/index.ts +36 -2
- data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +5 -2
- data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +4 -0
- data/app/pb_kits/playbook/utilities/_hover.scss +46 -43
- data/app/pb_kits/playbook/utilities/globalPropNames.mjs +1 -0
- data/app/pb_kits/playbook/utilities/globalProps.ts +5 -0
- data/dist/chunks/{_typeahead-C-6MLSyC.js → _typeahead-B-juiSkw.js} +2 -2
- data/dist/chunks/_weekday_stacked-DPqQHJ1l.js +45 -0
- data/dist/chunks/{lib-D-mTv-kp.js → lib-DpxYMiKe.js} +1 -1
- data/dist/chunks/{pb_form_validation-BkWGwJsl.js → pb_form_validation-LqRlnmi6.js} +1 -1
- data/dist/chunks/vendor.js +1 -1
- data/dist/menu.yml +4 -1
- data/dist/playbook-doc.js +1 -1
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/forms/builder/typeahead_field.rb +13 -0
- data/lib/playbook/hover.rb +4 -1
- data/lib/playbook/version.rb +2 -2
- metadata +24 -6
- data/dist/chunks/_weekday_stacked-CFhGhr9V.js +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ba8b6ebdd4078924cdcecd475be6892960449af516b17143f79b6475a4f0162
|
4
|
+
data.tar.gz: ab99598eb62828f045a9f61622b5b0d036e07a410a410004b9ed0ca29ab1630c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 ?
|
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
|
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
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
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
|
-
<
|
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
|
-
}
|
281
|
+
}
|
282
282
|
|
283
|
-
Dropdown
|
284
|
-
Dropdown.
|
285
|
-
Dropdown.
|
286
|
-
Dropdown.
|
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 :
|
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=
|
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 :
|
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.
|
26
|
-
<%= form.
|
27
|
-
<%= form.
|
28
|
-
<%= form.
|
29
|
-
<%= form.
|
30
|
-
<%= form.
|
31
|
-
<%= form.
|
32
|
-
<%= form.
|
33
|
-
<%= form.
|
34
|
-
<%= form.
|
35
|
-
<%= form.
|
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 :
|
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") %>
|