playbook_ui 16.9.0 → 16.10.0.pre.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +3 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +3 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +18 -10
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_year_asc.html.erb +1 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_year_asc.jsx +15 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_year_asc.md +1 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/_playground.json +1 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_date_picker/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_date_picker/kit.schema.json +8 -0
- data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.tsx +55 -28
- data/app/pb_kits/playbook/pb_filter/Filter/FilterDouble.tsx +4 -1
- data/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx +4 -1
- data/app/pb_kits/playbook/pb_filter/Filter/InteractiveFilter.tsx +277 -0
- data/app/pb_kits/playbook/pb_filter/_filter.scss +88 -1
- data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive.html.erb +116 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive.md +12 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive_react.jsx +153 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive_react.md +10 -0
- data/app/pb_kits/playbook/pb_filter/docs/_filter_no_background.html.erb +1 -1
- data/app/pb_kits/playbook/pb_filter/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_filter/docs/index.js +2 -1
- data/app/pb_kits/playbook/pb_filter/filter.html.erb +80 -3
- data/app/pb_kits/playbook/pb_filter/filter.rb +71 -0
- data/app/pb_kits/playbook/pb_filter/filter.test.js +78 -0
- data/app/pb_kits/playbook/pb_filter/index.ts +349 -0
- data/app/pb_kits/playbook/pb_filter/kit.schema.json +6 -0
- data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +20 -19
- data/app/pb_kits/playbook/pb_icon/docs/_icon_default.jsx +1 -1
- data/app/pb_kits/playbook/pb_loading_inline/_loading_inline.tsx +3 -1
- data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_color.html.erb +21 -0
- data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_color.jsx +55 -0
- data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_color.md +1 -0
- data/app/pb_kits/playbook/pb_loading_inline/docs/example.yml +2 -0
- data/app/pb_kits/playbook/pb_loading_inline/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_loading_inline/kit.schema.json +18 -2
- data/app/pb_kits/playbook/pb_loading_inline/loading_inline.html.erb +1 -1
- data/app/pb_kits/playbook/pb_loading_inline/loading_inline.rb +3 -0
- data/app/pb_kits/playbook/pb_popover/docs/_popover_list.jsx +9 -11
- data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/ToolbarDropdown.tsx +12 -24
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +4 -1
- data/app/pb_kits/playbook/utilities/icons/allicons.tsx +38 -0
- data/dist/chunks/{_pb_line_graph-B870s9g1.js → _pb_line_graph-D8PSzzEY.js} +1 -1
- data/dist/chunks/_typeahead-CuXG_NFx.js +5 -0
- data/dist/chunks/{globalProps-BInmh-C7.js → globalProps-B8stOeTI.js} +1 -1
- data/dist/chunks/lib-BAri19Ko.js +29 -0
- data/dist/chunks/vendor.js +5 -5
- 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/form_field_builder.rb +2 -0
- data/lib/playbook/version.rb +2 -2
- metadata +18 -6
- data/dist/chunks/_typeahead-jMN-A0_C.js +0 -5
- data/dist/chunks/lib-CVYIIiwI.js +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c56cffd7ae20d303e7ebfb111960cd77e6b3ef3020867b335b91ad0743ec88c
|
|
4
|
+
data.tar.gz: 12ed387ffcb0607f46a4ef6f231bb7823b5b3c81f7a988783b8b652491ef29cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc097cb2fe1d11fefe23ea335188808e0a15abcf214e0de13c7f8b6f1320974af4f1d33c813bf46c3650007ab0e903e838e91738ae8cb48c93c9875f28ccfc66
|
|
7
|
+
data.tar.gz: 2ef66b6d238eb564c593ceb9eaad541f898a5bbad90afd2c834b53dfbcf4624ce56e350eae499b674f7cccece8ff40d1c50201838529782fbf330a92c920b86a
|
|
@@ -87,6 +87,7 @@ type DatePickerProps = {
|
|
|
87
87
|
thisRangesEndToday?: boolean,
|
|
88
88
|
timeFormat?: string,
|
|
89
89
|
type?: string,
|
|
90
|
+
yearAscending?: boolean,
|
|
90
91
|
yearRange?: number[],
|
|
91
92
|
controlsStartId?: string,
|
|
92
93
|
controlsEndId?: string,
|
|
@@ -140,6 +141,7 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
|
|
|
140
141
|
showTimezone = false,
|
|
141
142
|
staticPosition = true,
|
|
142
143
|
thisRangesEndToday = false,
|
|
144
|
+
yearAscending = false,
|
|
143
145
|
yearRange = [1900, 2100],
|
|
144
146
|
controlsStartId,
|
|
145
147
|
controlsEndId,
|
|
@@ -199,6 +201,7 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
|
|
|
199
201
|
showTimezone,
|
|
200
202
|
staticPosition,
|
|
201
203
|
thisRangesEndToday,
|
|
204
|
+
yearAscending,
|
|
202
205
|
yearRange,
|
|
203
206
|
controlsStartId,
|
|
204
207
|
controlsEndId,
|
|
@@ -89,6 +89,8 @@ module Playbook
|
|
|
89
89
|
default: ""
|
|
90
90
|
prop :cursor, type: Playbook::Props::String,
|
|
91
91
|
default: "pointer"
|
|
92
|
+
prop :year_ascending, type: Playbook::Props::Boolean,
|
|
93
|
+
default: false
|
|
92
94
|
|
|
93
95
|
def classname
|
|
94
96
|
default_margin_bottom = margin_bottom.present? ? "" : " mb_sm"
|
|
@@ -122,6 +124,7 @@ module Playbook
|
|
|
122
124
|
staticPosition: static_position,
|
|
123
125
|
thisRangesEndToday: this_ranges_end_today,
|
|
124
126
|
yearRange: year_range,
|
|
127
|
+
yearAscending: year_ascending,
|
|
125
128
|
controlsStartId: controls_start_id,
|
|
126
129
|
controlsEndId: controls_end_id,
|
|
127
130
|
syncStartWith: sync_start_with,
|
|
@@ -7,7 +7,10 @@ import timeSelectPlugin from './plugins/timeSelect'
|
|
|
7
7
|
import quickPickPlugin from './plugins/quickPick'
|
|
8
8
|
import { getAllIcons } from '../utilities/icons/allicons';
|
|
9
9
|
|
|
10
|
-
const angleDown = getAllIcons()
|
|
10
|
+
const { angleDown, angleLeft, angleRight } = getAllIcons()
|
|
11
|
+
const angleDownString = angleDown.string
|
|
12
|
+
const angleLeftString = angleLeft.string
|
|
13
|
+
const angleRightString = angleRight.string
|
|
11
14
|
|
|
12
15
|
const getPositionElement = (element: string | Element) => {
|
|
13
16
|
return (typeof element === 'string') ? document.querySelectorAll(element)[0] : element
|
|
@@ -32,6 +35,7 @@ type DatePickerConfig = {
|
|
|
32
35
|
thisRangesEndToday?: boolean,
|
|
33
36
|
timeCaption?: string,
|
|
34
37
|
timeFormat?: string,
|
|
38
|
+
yearAscending?: boolean,
|
|
35
39
|
yearRange: number[],
|
|
36
40
|
controlsStartId?: string,
|
|
37
41
|
controlsEndId?: string,
|
|
@@ -70,6 +74,7 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
|
|
|
70
74
|
thisRangesEndToday = false,
|
|
71
75
|
timeCaption = 'Select Time',
|
|
72
76
|
timeFormat = 'at h:i K',
|
|
77
|
+
yearAscending,
|
|
73
78
|
yearRange,
|
|
74
79
|
controlsStartId,
|
|
75
80
|
controlsEndId,
|
|
@@ -365,7 +370,7 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
|
|
|
365
370
|
maxDate: effectiveMaxDate,
|
|
366
371
|
minDate: effectiveMinDate,
|
|
367
372
|
mode,
|
|
368
|
-
nextArrow:
|
|
373
|
+
nextArrow: `<div style="height: 14px;">${angleRightString}</div>`,
|
|
369
374
|
onOpen: [(_selectedDates, _dateStr, fp) => {
|
|
370
375
|
// If defaultDate was out of range of a dev set min/max date, restore it when calendar opens (in situation where the input was manually cleared or the calendar was closed without selection)
|
|
371
376
|
if (hasOutOfRangeDefault) {
|
|
@@ -437,7 +442,7 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
|
|
|
437
442
|
plugins: setPlugins(thisRangesEndToday, customQuickPickDates),
|
|
438
443
|
position,
|
|
439
444
|
positionElement: getPositionElement(positionElement),
|
|
440
|
-
prevArrow:
|
|
445
|
+
prevArrow: `<div style="height: 14px;">${angleLeftString}</div>`,
|
|
441
446
|
static: staticPosition,
|
|
442
447
|
})
|
|
443
448
|
|
|
@@ -515,8 +520,14 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
|
|
|
515
520
|
|
|
516
521
|
// create html option tags for desired years
|
|
517
522
|
let years = ''
|
|
518
|
-
|
|
519
|
-
|
|
523
|
+
if (yearAscending) {
|
|
524
|
+
for (let year = setMinYear; year <= setMaxYear; year++) {
|
|
525
|
+
years += `<option value="${year}">${year}</option>`
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
for (let year = setMaxYear; year >= setMinYear; year--) {
|
|
529
|
+
years += `<option value="${year}">${year}</option>`
|
|
530
|
+
}
|
|
520
531
|
}
|
|
521
532
|
|
|
522
533
|
// variablize each dropdown selector
|
|
@@ -651,12 +662,9 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
|
|
|
651
662
|
}
|
|
652
663
|
|
|
653
664
|
// Adding dropdown icons to year and month select
|
|
654
|
-
dropdown.insertAdjacentHTML('afterend', `<i class="year-dropdown-icon">${
|
|
665
|
+
dropdown.insertAdjacentHTML('afterend', `<i class="year-dropdown-icon">${angleDownString}</i>`)
|
|
655
666
|
if (picker.monthElements[0].parentElement) {
|
|
656
|
-
return picker.monthElements[0].insertAdjacentHTML('afterend', `<i class="month-dropdown-icon">${
|
|
657
|
-
// if (picker.weekElements[0].parentElement){
|
|
658
|
-
// return picker.weekElements[0].insertAdjacentHTML('afterend', '<i class="far fa-angle-down year-dropdown-icon" id="test-id"></i>')
|
|
659
|
-
// }
|
|
667
|
+
return picker.monthElements[0].insertAdjacentHTML('afterend', `<i class="month-dropdown-icon">${angleDownString}</i>`)}
|
|
660
668
|
|
|
661
669
|
// Remove readonly attribute for validation and or text input
|
|
662
670
|
if (allowInput){
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= pb_rails("date_picker", props: { picker_id: "date-picker-date-asc", year_ascending: true}) %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import DatePicker from '../_date_picker'
|
|
4
|
+
|
|
5
|
+
const DatePickerYearAsc = (props) => (
|
|
6
|
+
<div>
|
|
7
|
+
<DatePicker
|
|
8
|
+
pickerId="date-picker-date-asc"
|
|
9
|
+
yearAscending
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
</div>
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
export default DatePickerYearAsc
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
`yearAscending`/`year_ascending` is a boolean prop that if set to true, will render the years in the year dropdown in chronological order. This prop is set to false by default.
|
|
@@ -23,6 +23,7 @@ examples:
|
|
|
23
23
|
- date_picker_flatpickr_methods: Flatpickr Methods
|
|
24
24
|
- date_picker_hooks: Hooks
|
|
25
25
|
- date_picker_year_range: Year Range
|
|
26
|
+
- date_picker_year_asc: Year Ascending
|
|
26
27
|
- date_picker_anti_patterns: Anti-Patterns
|
|
27
28
|
- date_picker_margin_bottom: Margin Bottom
|
|
28
29
|
- date_picker_inline: Inline
|
|
@@ -58,6 +59,7 @@ examples:
|
|
|
58
59
|
- date_picker_flatpickr_methods: Flatpickr Methods
|
|
59
60
|
- date_picker_hooks: Hooks
|
|
60
61
|
- date_picker_year_range: Year Range
|
|
62
|
+
- date_picker_year_asc: Year Ascending
|
|
61
63
|
- date_picker_margin_bottom: Margin Bottom
|
|
62
64
|
- date_picker_inline: Inline
|
|
63
65
|
- date_picker_month_and_year: Month & Year Only
|
|
@@ -30,3 +30,4 @@ export { default as DatePickerRangePattern } from './_date_picker_range_pattern'
|
|
|
30
30
|
export { default as DatePickerAndDropdownRange } from './_date_picker_and_dropdown_range.jsx'
|
|
31
31
|
export { default as DatePickerRequiredIndicator } from "./_date_picker_required_indicator.jsx";
|
|
32
32
|
export { default as DatePickerDialogSubmission } from "./_date_picker_dialog_submission.jsx";
|
|
33
|
+
export { default as DatePickerYearAsc } from './_date_picker_year_asc.jsx'
|
|
@@ -1,22 +1,32 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
2
|
import { isEmpty, omitBy, map } from '../../utilities/object'
|
|
3
3
|
|
|
4
4
|
import Body from '../../pb_body/_body'
|
|
5
5
|
import Caption from '../../pb_caption/_caption'
|
|
6
6
|
import Title from '../../pb_title/_title'
|
|
7
|
+
import InteractiveFilter, { InteractiveFilterConfig } from './InteractiveFilter'
|
|
7
8
|
|
|
8
9
|
export type FilterDescription = {
|
|
9
10
|
[key: string]: string | null | boolean,
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
export type InteractiveFilters = {
|
|
14
|
+
[key: string]: InteractiveFilterConfig,
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
export type CurrentFiltersProps = {
|
|
13
18
|
dark: boolean,
|
|
14
19
|
filters: FilterDescription,
|
|
20
|
+
interactiveFilters?: InteractiveFilters,
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
const hiddenFilters = (value: any) => isEmpty(value) && value !== true
|
|
18
24
|
|
|
19
|
-
const CurrentFilters = ({
|
|
25
|
+
const CurrentFilters = ({
|
|
26
|
+
dark,
|
|
27
|
+
filters,
|
|
28
|
+
interactiveFilters = {},
|
|
29
|
+
}: CurrentFiltersProps): React.ReactElement => {
|
|
20
30
|
const displayableFilters = omitBy(filters, hiddenFilters)
|
|
21
31
|
|
|
22
32
|
return (
|
|
@@ -35,33 +45,50 @@ const CurrentFilters = ({ dark, filters }: CurrentFiltersProps): React.ReactElem
|
|
|
35
45
|
{ !isEmpty(filters) &&
|
|
36
46
|
<div className="filters">
|
|
37
47
|
<div className="left_gradient" />
|
|
38
|
-
{map(displayableFilters, (value, name) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
{map(displayableFilters, (value, name) => {
|
|
49
|
+
const interactiveConfig = interactiveFilters[name]
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className={`filter${interactiveConfig ? ' interactive' : ''}`}
|
|
53
|
+
key={`filter-${name}`}
|
|
54
|
+
>
|
|
55
|
+
{ interactiveConfig ?
|
|
56
|
+
<InteractiveFilter
|
|
57
|
+
config={interactiveConfig}
|
|
58
|
+
dark={dark}
|
|
59
|
+
editorValue={interactiveConfig.editorValue}
|
|
60
|
+
name={String(name)}
|
|
61
|
+
value={
|
|
62
|
+
interactiveConfig.value !== undefined
|
|
63
|
+
? interactiveConfig.value
|
|
64
|
+
: value === true
|
|
65
|
+
? ''
|
|
66
|
+
: (value as string)
|
|
67
|
+
}
|
|
68
|
+
/> :
|
|
69
|
+
value === true ?
|
|
70
|
+
<Title
|
|
71
|
+
dark={dark}
|
|
72
|
+
size={4}
|
|
73
|
+
tag="h4"
|
|
74
|
+
text={`${name}`}
|
|
75
|
+
/> :
|
|
76
|
+
<div>
|
|
77
|
+
<Caption
|
|
78
|
+
dark={dark}
|
|
79
|
+
text={`${name}`}
|
|
80
|
+
/>
|
|
81
|
+
<Title
|
|
82
|
+
dark={dark}
|
|
83
|
+
size={4}
|
|
84
|
+
tag="h4"
|
|
85
|
+
text={value}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
}
|
|
61
89
|
</div>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
))}
|
|
90
|
+
)
|
|
91
|
+
})}
|
|
65
92
|
<div className="right_gradient" />
|
|
66
93
|
</div>
|
|
67
94
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
import CurrentFilters, { FilterDescription } from './CurrentFilters'
|
|
3
|
+
import CurrentFilters, { FilterDescription, InteractiveFilters } from './CurrentFilters'
|
|
4
4
|
import FilterBackground, { FilterBackgroundProps } from './FilterBackground'
|
|
5
5
|
import FiltersPopover from './FiltersPopover'
|
|
6
6
|
import ResultsCount from './ResultsCount'
|
|
@@ -17,6 +17,7 @@ import SectionSeparator from '../../pb_section_separator/_section_separator'
|
|
|
17
17
|
export type FilterDoubleProps = {
|
|
18
18
|
children?: React.ReactChild[] | React.ReactChild,
|
|
19
19
|
filters?: FilterDescription,
|
|
20
|
+
interactiveFilters?: InteractiveFilters,
|
|
20
21
|
onSortChange?: SortingChangeCallback,
|
|
21
22
|
results?: number,
|
|
22
23
|
sortOptions?: SortOptions,
|
|
@@ -28,6 +29,7 @@ const FilterDouble = ({
|
|
|
28
29
|
sortOptions,
|
|
29
30
|
sortValue,
|
|
30
31
|
filters,
|
|
32
|
+
interactiveFilters,
|
|
31
33
|
results,
|
|
32
34
|
children,
|
|
33
35
|
dark,
|
|
@@ -57,6 +59,7 @@ const FilterDouble = ({
|
|
|
57
59
|
<CurrentFilters
|
|
58
60
|
dark={dark}
|
|
59
61
|
filters={filters}
|
|
62
|
+
interactiveFilters={interactiveFilters}
|
|
60
63
|
/>
|
|
61
64
|
</Flex>
|
|
62
65
|
<SectionSeparator dark={dark} />
|
|
@@ -3,7 +3,7 @@ import { isEmpty } from '../../utilities/object'
|
|
|
3
3
|
|
|
4
4
|
import Flex from '../../pb_flex/_flex'
|
|
5
5
|
|
|
6
|
-
import CurrentFilters, { FilterDescription } from './CurrentFilters'
|
|
6
|
+
import CurrentFilters, { FilterDescription, InteractiveFilters } from './CurrentFilters'
|
|
7
7
|
import FilterBackground, { FilterBackgroundProps } from './FilterBackground'
|
|
8
8
|
import FiltersPopover from './FiltersPopover'
|
|
9
9
|
import ResultsCount from './ResultsCount'
|
|
@@ -16,6 +16,7 @@ import SortMenu, {
|
|
|
16
16
|
export type FilterSingleProps = {
|
|
17
17
|
children?: React.ReactChild[] | React.ReactChild,
|
|
18
18
|
filters?: FilterDescription,
|
|
19
|
+
interactiveFilters?: InteractiveFilters,
|
|
19
20
|
onSortChange?: SortingChangeCallback,
|
|
20
21
|
results?: number,
|
|
21
22
|
sortOptions?: SortOptions,
|
|
@@ -27,6 +28,7 @@ const FilterSingle = ({
|
|
|
27
28
|
sortOptions,
|
|
28
29
|
sortValue,
|
|
29
30
|
filters,
|
|
31
|
+
interactiveFilters,
|
|
30
32
|
results,
|
|
31
33
|
children,
|
|
32
34
|
dark,
|
|
@@ -60,6 +62,7 @@ const FilterSingle = ({
|
|
|
60
62
|
<CurrentFilters
|
|
61
63
|
dark={dark}
|
|
62
64
|
filters={filters}
|
|
65
|
+
interactiveFilters={interactiveFilters}
|
|
63
66
|
/>
|
|
64
67
|
</>
|
|
65
68
|
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import flatpickr from 'flatpickr'
|
|
3
|
+
import { Instance } from 'flatpickr/dist/types/instance'
|
|
4
|
+
|
|
5
|
+
import Flex from '../../pb_flex/_flex'
|
|
6
|
+
import Caption from '../../pb_caption/_caption'
|
|
7
|
+
import Icon from '../../pb_icon/_icon'
|
|
8
|
+
import PbReactPopover from '../../pb_popover/_popover'
|
|
9
|
+
import getQuickPickOptions from '../../pb_dropdown/quickpick'
|
|
10
|
+
import { uniqueId } from '../../utilities/object'
|
|
11
|
+
import Title from '../../pb_title/_title'
|
|
12
|
+
|
|
13
|
+
export type SelectInteractiveConfig = {
|
|
14
|
+
type: 'select',
|
|
15
|
+
options: { value: string, text?: string }[],
|
|
16
|
+
value?: string,
|
|
17
|
+
/** Draft value for the inline editor active state (chip label uses `value`). */
|
|
18
|
+
editorValue?: string,
|
|
19
|
+
onChange: (value: string) => void,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DropdownInteractiveConfig = {
|
|
23
|
+
type: 'dropdown',
|
|
24
|
+
options?: { value?: string, label: string, id?: string }[],
|
|
25
|
+
variant?: 'default' | 'quickpick',
|
|
26
|
+
rangeEndsToday?: boolean,
|
|
27
|
+
customQuickPickDates?: {
|
|
28
|
+
override?: boolean,
|
|
29
|
+
dates: {
|
|
30
|
+
label: string,
|
|
31
|
+
value: string[] | { timePeriod: string, amount: number },
|
|
32
|
+
}[],
|
|
33
|
+
},
|
|
34
|
+
value?: string,
|
|
35
|
+
editorValue?: string,
|
|
36
|
+
onChange: (value: string) => void,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type DatePickerInteractiveConfig = {
|
|
40
|
+
type: 'date-picker',
|
|
41
|
+
value?: string,
|
|
42
|
+
editorValue?: string,
|
|
43
|
+
onChange: (value: string) => void,
|
|
44
|
+
format?: string,
|
|
45
|
+
minDate?: string,
|
|
46
|
+
maxDate?: string,
|
|
47
|
+
mode?: 'single' | 'range' | 'multiple',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type InteractiveFilterConfig =
|
|
51
|
+
| SelectInteractiveConfig
|
|
52
|
+
| DropdownInteractiveConfig
|
|
53
|
+
| DatePickerInteractiveConfig
|
|
54
|
+
|
|
55
|
+
const dropdownOptionsFor = (
|
|
56
|
+
config: DropdownInteractiveConfig
|
|
57
|
+
): { value: string, label: string }[] => {
|
|
58
|
+
if (config.variant === 'quickpick' && !config.options?.length) {
|
|
59
|
+
return getQuickPickOptions(
|
|
60
|
+
config.rangeEndsToday,
|
|
61
|
+
config.customQuickPickDates
|
|
62
|
+
).map((opt) => ({
|
|
63
|
+
value: opt.id || opt.label,
|
|
64
|
+
label: opt.label,
|
|
65
|
+
}))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (config.options || []).map((opt) => ({
|
|
69
|
+
value: opt.id || opt.value || opt.label,
|
|
70
|
+
label: opt.label,
|
|
71
|
+
}))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type InteractiveFilterProps = {
|
|
75
|
+
dark?: boolean,
|
|
76
|
+
name: string,
|
|
77
|
+
value: string,
|
|
78
|
+
editorValue?: string,
|
|
79
|
+
config: InteractiveFilterConfig,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const labelFor = (
|
|
83
|
+
config: InteractiveFilterConfig,
|
|
84
|
+
value: string
|
|
85
|
+
): string => {
|
|
86
|
+
if (config.type === 'select') {
|
|
87
|
+
const match = config.options.find((opt) => opt.value === value)
|
|
88
|
+
if (match) return match.text || match.value
|
|
89
|
+
}
|
|
90
|
+
if (config.type === 'dropdown') {
|
|
91
|
+
const match = dropdownOptionsFor(config).find((opt) => opt.value === value)
|
|
92
|
+
if (match) return match.label || match.value
|
|
93
|
+
}
|
|
94
|
+
return value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type ChipVisualProps = {
|
|
98
|
+
dark: boolean,
|
|
99
|
+
name: string,
|
|
100
|
+
displayValue: string,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const ChipVisual = ({
|
|
104
|
+
dark,
|
|
105
|
+
name,
|
|
106
|
+
displayValue,
|
|
107
|
+
}: ChipVisualProps): React.ReactElement => (
|
|
108
|
+
<div className="pb_interactive_filter_container">
|
|
109
|
+
<Caption
|
|
110
|
+
dark={dark}
|
|
111
|
+
text={name}
|
|
112
|
+
/>
|
|
113
|
+
<Flex
|
|
114
|
+
alignItems="center"
|
|
115
|
+
gap="xxs"
|
|
116
|
+
>
|
|
117
|
+
<Title
|
|
118
|
+
dark={dark}
|
|
119
|
+
size={4}
|
|
120
|
+
text={displayValue || '—'}
|
|
121
|
+
/>
|
|
122
|
+
<Icon
|
|
123
|
+
color="primary"
|
|
124
|
+
fixedWidth
|
|
125
|
+
icon="angle-down"
|
|
126
|
+
size="xs"
|
|
127
|
+
/>
|
|
128
|
+
</Flex>
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
type InlineCalendarProps = {
|
|
133
|
+
calendarValue: string,
|
|
134
|
+
config: DatePickerInteractiveConfig,
|
|
135
|
+
onSinglePick: () => void,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const InlineCalendar = ({
|
|
139
|
+
calendarValue,
|
|
140
|
+
config,
|
|
141
|
+
onSinglePick,
|
|
142
|
+
}: InlineCalendarProps): React.ReactElement => {
|
|
143
|
+
const containerRef = useRef<HTMLDivElement | null>(null)
|
|
144
|
+
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (!containerRef.current) return
|
|
147
|
+
const fp = flatpickr(containerRef.current, {
|
|
148
|
+
inline: true,
|
|
149
|
+
defaultDate: calendarValue || undefined,
|
|
150
|
+
mode: config.mode || 'single',
|
|
151
|
+
dateFormat: config.format || 'm/d/Y',
|
|
152
|
+
minDate: config.minDate,
|
|
153
|
+
maxDate: config.maxDate,
|
|
154
|
+
nextArrow: '<i class="far fa-angle-right"></i>',
|
|
155
|
+
prevArrow: '<i class="far fa-angle-left"></i>',
|
|
156
|
+
onChange: (_selectedDates, dateStr) => {
|
|
157
|
+
config.onChange(dateStr)
|
|
158
|
+
if ((config.mode || 'single') === 'single') onSinglePick()
|
|
159
|
+
},
|
|
160
|
+
}) as Instance
|
|
161
|
+
return () => fp.destroy()
|
|
162
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
163
|
+
}, [calendarValue])
|
|
164
|
+
|
|
165
|
+
return <div ref={containerRef} />
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const InteractiveFilter = ({
|
|
169
|
+
dark = false,
|
|
170
|
+
name,
|
|
171
|
+
value,
|
|
172
|
+
editorValue,
|
|
173
|
+
config,
|
|
174
|
+
}: InteractiveFilterProps): React.ReactElement => {
|
|
175
|
+
const [open, setOpen] = useState(false)
|
|
176
|
+
const pickerId = useMemo(() => uniqueId('interactive-filter-date-'), [])
|
|
177
|
+
const displayValue = labelFor(config, value)
|
|
178
|
+
const activeValue =
|
|
179
|
+
editorValue ??
|
|
180
|
+
config.editorValue ??
|
|
181
|
+
value
|
|
182
|
+
|
|
183
|
+
const renderEditor = () => {
|
|
184
|
+
switch (config.type) {
|
|
185
|
+
case 'select':
|
|
186
|
+
case 'dropdown': {
|
|
187
|
+
const options =
|
|
188
|
+
config.type === 'dropdown'
|
|
189
|
+
? dropdownOptionsFor(config)
|
|
190
|
+
: config.options.map((opt) => ({
|
|
191
|
+
value: opt.value,
|
|
192
|
+
label: opt.text || opt.value,
|
|
193
|
+
}))
|
|
194
|
+
return (
|
|
195
|
+
<ul
|
|
196
|
+
className="pb_interactive_filter_options"
|
|
197
|
+
role="listbox"
|
|
198
|
+
>
|
|
199
|
+
{options.map((option) => {
|
|
200
|
+
const isActive = option.value === activeValue
|
|
201
|
+
return (
|
|
202
|
+
<li
|
|
203
|
+
aria-selected={isActive}
|
|
204
|
+
className={`pb_interactive_filter_option${
|
|
205
|
+
isActive ? ' active' : ''
|
|
206
|
+
}`}
|
|
207
|
+
key={option.value}
|
|
208
|
+
onClick={() => {
|
|
209
|
+
config.onChange(option.value)
|
|
210
|
+
setOpen(false)
|
|
211
|
+
}}
|
|
212
|
+
onKeyDown={(event) => {
|
|
213
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
214
|
+
event.preventDefault()
|
|
215
|
+
config.onChange(option.value)
|
|
216
|
+
setOpen(false)
|
|
217
|
+
}
|
|
218
|
+
}}
|
|
219
|
+
role="option"
|
|
220
|
+
tabIndex={0}
|
|
221
|
+
>
|
|
222
|
+
<span>{option.label}</span>
|
|
223
|
+
</li>
|
|
224
|
+
)
|
|
225
|
+
})}
|
|
226
|
+
</ul>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case 'date-picker':
|
|
231
|
+
return (
|
|
232
|
+
<InlineCalendar
|
|
233
|
+
calendarValue={activeValue}
|
|
234
|
+
config={config}
|
|
235
|
+
onSinglePick={() => setOpen(false)}
|
|
236
|
+
/>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const trigger = (
|
|
242
|
+
<button
|
|
243
|
+
aria-expanded={open}
|
|
244
|
+
aria-haspopup="dialog"
|
|
245
|
+
className={`pb_interactive_filter_trigger${dark ? ' dark' : ''}`}
|
|
246
|
+
data-picker-id={pickerId}
|
|
247
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
248
|
+
type="button"
|
|
249
|
+
>
|
|
250
|
+
<ChipVisual
|
|
251
|
+
dark={dark}
|
|
252
|
+
displayValue={displayValue}
|
|
253
|
+
name={name}
|
|
254
|
+
/>
|
|
255
|
+
</button>
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<PbReactPopover
|
|
260
|
+
closeOnClick="outside"
|
|
261
|
+
offset
|
|
262
|
+
placement="bottom-start"
|
|
263
|
+
reference={trigger}
|
|
264
|
+
shouldClosePopover={() => setOpen(false)}
|
|
265
|
+
show={open}
|
|
266
|
+
zIndex={10}
|
|
267
|
+
>
|
|
268
|
+
<div
|
|
269
|
+
className={`pb_interactive_filter_editor pb_interactive_filter_editor--${config.type}`}
|
|
270
|
+
>
|
|
271
|
+
{renderEditor()}
|
|
272
|
+
</div>
|
|
273
|
+
</PbReactPopover>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export default InteractiveFilter
|