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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_date_picker/_date_picker.tsx +3 -0
  3. data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +3 -0
  4. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +18 -10
  5. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_year_asc.html.erb +1 -0
  6. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_year_asc.jsx +15 -0
  7. data/app/pb_kits/playbook/pb_date_picker/docs/_date_picker_year_asc.md +1 -0
  8. data/app/pb_kits/playbook/pb_date_picker/docs/_playground.json +1 -0
  9. data/app/pb_kits/playbook/pb_date_picker/docs/example.yml +2 -0
  10. data/app/pb_kits/playbook/pb_date_picker/docs/index.js +1 -0
  11. data/app/pb_kits/playbook/pb_date_picker/kit.schema.json +8 -0
  12. data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.tsx +55 -28
  13. data/app/pb_kits/playbook/pb_filter/Filter/FilterDouble.tsx +4 -1
  14. data/app/pb_kits/playbook/pb_filter/Filter/FilterSingle.tsx +4 -1
  15. data/app/pb_kits/playbook/pb_filter/Filter/InteractiveFilter.tsx +277 -0
  16. data/app/pb_kits/playbook/pb_filter/_filter.scss +88 -1
  17. data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive.html.erb +116 -0
  18. data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive.md +12 -0
  19. data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive_react.jsx +153 -0
  20. data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive_react.md +10 -0
  21. data/app/pb_kits/playbook/pb_filter/docs/_filter_no_background.html.erb +1 -1
  22. data/app/pb_kits/playbook/pb_filter/docs/example.yml +2 -0
  23. data/app/pb_kits/playbook/pb_filter/docs/index.js +2 -1
  24. data/app/pb_kits/playbook/pb_filter/filter.html.erb +80 -3
  25. data/app/pb_kits/playbook/pb_filter/filter.rb +71 -0
  26. data/app/pb_kits/playbook/pb_filter/filter.test.js +78 -0
  27. data/app/pb_kits/playbook/pb_filter/index.ts +349 -0
  28. data/app/pb_kits/playbook/pb_filter/kit.schema.json +6 -0
  29. data/app/pb_kits/playbook/pb_form/docs/_form_with_required_indicator.html.erb +20 -19
  30. data/app/pb_kits/playbook/pb_icon/docs/_icon_default.jsx +1 -1
  31. data/app/pb_kits/playbook/pb_loading_inline/_loading_inline.tsx +3 -1
  32. data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_color.html.erb +21 -0
  33. data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_color.jsx +55 -0
  34. data/app/pb_kits/playbook/pb_loading_inline/docs/_loading_inline_color.md +1 -0
  35. data/app/pb_kits/playbook/pb_loading_inline/docs/example.yml +2 -0
  36. data/app/pb_kits/playbook/pb_loading_inline/docs/index.js +1 -0
  37. data/app/pb_kits/playbook/pb_loading_inline/kit.schema.json +18 -2
  38. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.html.erb +1 -1
  39. data/app/pb_kits/playbook/pb_loading_inline/loading_inline.rb +3 -0
  40. data/app/pb_kits/playbook/pb_popover/docs/_popover_list.jsx +9 -11
  41. data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/ToolbarDropdown.tsx +12 -24
  42. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_playground.json +4 -1
  43. data/app/pb_kits/playbook/utilities/icons/allicons.tsx +38 -0
  44. data/dist/chunks/{_pb_line_graph-B870s9g1.js → _pb_line_graph-D8PSzzEY.js} +1 -1
  45. data/dist/chunks/_typeahead-CuXG_NFx.js +5 -0
  46. data/dist/chunks/{globalProps-BInmh-C7.js → globalProps-B8stOeTI.js} +1 -1
  47. data/dist/chunks/lib-BAri19Ko.js +29 -0
  48. data/dist/chunks/vendor.js +5 -5
  49. data/dist/playbook-rails-react-bindings.js +1 -1
  50. data/dist/playbook-rails.js +1 -1
  51. data/dist/playbook.css +1 -1
  52. data/lib/playbook/forms/builder/form_field_builder.rb +2 -0
  53. data/lib/playbook/version.rb +2 -2
  54. metadata +18 -6
  55. data/dist/chunks/_typeahead-jMN-A0_C.js +0 -5
  56. data/dist/chunks/lib-CVYIIiwI.js +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1bf2d157b05e934cbfa3297c60f74cabb576373b5cb3b4a2b61d48df0ed27a3
4
- data.tar.gz: 91635a39004a1f4169780f5065c21207b407308d0509d88710e9ad55b5eefc3c
3
+ metadata.gz: 0c56cffd7ae20d303e7ebfb111960cd77e6b3ef3020867b335b91ad0743ec88c
4
+ data.tar.gz: 12ed387ffcb0607f46a4ef6f231bb7823b5b3c81f7a988783b8b652491ef29cf
5
5
  SHA512:
6
- metadata.gz: f9d9fe0b6fea54f0eb1a347d9dd1b7d6c4d7a930825e5818c781c406edd89d444675b5772e3cba27e4d4de5f60acdf6e536a7f98dd905d91669dc95d616db777
7
- data.tar.gz: 37fa1f6e6c2610ecfd26f259be1e048053c37a562511007f05efb577cc3659ba9745518ff9214d63e92da275b45210228d86fe2f3e2500e5e91b1f5f9edc404c
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().angleDown.string
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: '<i class="far fa-angle-right"></i>',
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: '<i class="far fa-angle-left"></i>',
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
- for (let year = setMaxYear; year >= setMinYear; year--) {
519
- years += `<option value="${year}">${year}</option>`
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">${angleDown}</i>`)
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">${angleDown}</i>`)}
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.
@@ -16,6 +16,7 @@
16
16
  "showTimezone": false,
17
17
  "staticPosition": true,
18
18
  "thisRangesEndToday": false,
19
+ "yearAscending": false,
19
20
  "controlsStartId": "",
20
21
  "controlsEndId": "",
21
22
  "syncStartWith": "",
@@ -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'
@@ -257,6 +257,14 @@
257
257
  "react"
258
258
  ]
259
259
  },
260
+ "yearAscending": {
261
+ "type": "boolean",
262
+ "platforms": [
263
+ "react",
264
+ "rails"
265
+ ],
266
+ "default": false
267
+ },
260
268
  "yearRange": {
261
269
  "type": "array",
262
270
  "platforms": [
@@ -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 = ({ dark, filters }: CurrentFiltersProps): React.ReactElement => {
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
- <div
40
- className="filter"
41
- key={`filter-${name}`}
42
- >
43
- { value === true ?
44
- <Title
45
- dark={dark}
46
- size={4}
47
- tag="h4"
48
- text={`${name}`}
49
- /> :
50
- <div>
51
- <Caption
52
- dark={dark}
53
- text={`${name}`}
54
- />
55
- <Title
56
- dark={dark}
57
- size={4}
58
- tag="h4"
59
- text={value}
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
- </div>
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