playbook_ui 16.8.0.pre.alpha.play293416802 → 16.8.0.pre.alpha.play293416882
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 +42 -2
- data/app/pb_kits/playbook/pb_date_picker/date_picker.html.erb +8 -5
- data/app/pb_kits/playbook/pb_date_picker/date_picker.rb +12 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker.test.js +60 -0
- data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.ts +3 -1
- data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.tsx +14 -59
- data/app/pb_kits/playbook/pb_filter/Filter/InteractiveFilter.tsx +1 -1
- data/app/pb_kits/playbook/pb_filter/_filter.scss +0 -20
- data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive.md +1 -3
- data/app/pb_kits/playbook/pb_filter/docs/_filter_interactive_react.md +1 -1
- data/app/pb_kits/playbook/pb_filter/filter.html.erb +4 -9
- data/app/pb_kits/playbook/pb_filter/filter.test.js +0 -60
- data/app/pb_kits/playbook/pb_filter/index.ts +0 -3
- data/app/pb_kits/playbook/pb_message/_message.scss +1 -1
- data/dist/chunks/{_pb_line_graph-Cb55jfIQ.js → _pb_line_graph-D8koup5H.js} +1 -1
- data/dist/chunks/{_typeahead-TVQIJBcl.js → _typeahead-BU-3yCgY.js} +1 -1
- data/dist/chunks/{globalProps-Dgb9uSU9.js → globalProps-dmEeu7jO.js} +1 -1
- data/dist/chunks/{lib-DUPzOYd7.js → lib-CpaLlY1W.js} +1 -1
- data/dist/chunks/vendor.js +2 -2
- 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/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1caeb431628b18480502df25ac193c5805acf46b88d5a43268fe923ac49ee33d
|
|
4
|
+
data.tar.gz: 38c003583580e19d571e70c4c5ea1d2e80d6f7bbbcbea996aa9d251c647f2410
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f3ce2910ea7cd2f3de885fe6dd0509d23f34d6429bad08f78bdadd5bf8bfced12860bc16810ead019728fb4781c33f251661833046135306f026d75d12eb69f
|
|
7
|
+
data.tar.gz: a03a5495e8c1752c6d9be8b1edd6d2f7ad943dacfe3070ddf130b6ef0f2994299bc6bb028dde7963a6d875b64cb4e830a8073f7903a9d2ca85e59af6630ec15a
|
|
@@ -12,6 +12,41 @@ import Caption from '../pb_caption/_caption'
|
|
|
12
12
|
import Body from '../pb_body/_body'
|
|
13
13
|
import colors from "../tokens/exports/_colors.module.scss"
|
|
14
14
|
|
|
15
|
+
function isValidDateInstance(value: Date): boolean {
|
|
16
|
+
return !Number.isNaN(value.getTime())
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeDefaultDate<T>(defaultDate: T): T {
|
|
20
|
+
if (defaultDate instanceof Date) {
|
|
21
|
+
return (isValidDateInstance(defaultDate) ? defaultDate : '') as unknown as T
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(defaultDate)) {
|
|
24
|
+
const normalized = defaultDate.filter(
|
|
25
|
+
(d) => !(d instanceof Date) || isValidDateInstance(d)
|
|
26
|
+
)
|
|
27
|
+
return (normalized.length ? normalized : '') as unknown as T
|
|
28
|
+
}
|
|
29
|
+
return defaultDate
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function serializeDefaultDateForFilterReset(defaultDate: unknown): string | undefined {
|
|
33
|
+
if (defaultDate === '' || defaultDate == null) return undefined
|
|
34
|
+
if (Array.isArray(defaultDate)) {
|
|
35
|
+
const parts = defaultDate.map((d) => {
|
|
36
|
+
if (d == null || d === '') return ''
|
|
37
|
+
if (d instanceof Date) {
|
|
38
|
+
return isValidDateInstance(d) ? d.toISOString() : ''
|
|
39
|
+
}
|
|
40
|
+
return String(d)
|
|
41
|
+
}).filter(Boolean)
|
|
42
|
+
return parts.length ? parts.join(',') : undefined
|
|
43
|
+
}
|
|
44
|
+
if (defaultDate instanceof Date) {
|
|
45
|
+
return isValidDateInstance(defaultDate) ? defaultDate.toISOString() : undefined
|
|
46
|
+
}
|
|
47
|
+
return String(defaultDate)
|
|
48
|
+
}
|
|
49
|
+
|
|
15
50
|
type DatePickerProps = {
|
|
16
51
|
allowInput?: boolean,
|
|
17
52
|
aria?: { [key: string]: string },
|
|
@@ -113,7 +148,12 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
|
|
|
113
148
|
} = props
|
|
114
149
|
|
|
115
150
|
const ariaProps = buildAriaProps(aria)
|
|
116
|
-
const
|
|
151
|
+
const normalizedDefaultDate = normalizeDefaultDate(defaultDate)
|
|
152
|
+
const filterResetDefaultSerialized = serializeDefaultDateForFilterReset(normalizedDefaultDate)
|
|
153
|
+
const dataProps = buildDataProps({
|
|
154
|
+
...data,
|
|
155
|
+
...(filterResetDefaultSerialized ? { 'default-value': filterResetDefaultSerialized } : {}),
|
|
156
|
+
})
|
|
117
157
|
const htmlProps = buildHtmlProps(htmlOptions)
|
|
118
158
|
const inputAriaProps = buildAriaProps(inputAria)
|
|
119
159
|
const inputDataProps = buildDataProps(inputData)
|
|
@@ -136,7 +176,7 @@ const DatePicker = (props: DatePickerProps): React.ReactElement => {
|
|
|
136
176
|
datePickerHelper({
|
|
137
177
|
allowInput,
|
|
138
178
|
customQuickPickDates,
|
|
139
|
-
defaultDate,
|
|
179
|
+
defaultDate: normalizedDefaultDate,
|
|
140
180
|
disableDate,
|
|
141
181
|
disableRange,
|
|
142
182
|
disableWeekdays,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
<% date_picker_root_attrs = {
|
|
2
|
+
class: object.classname + object.error_class,
|
|
3
|
+
'data-pb-date-picker' => true,
|
|
4
|
+
'data-validation-message' => object.validation_message,
|
|
5
|
+
}
|
|
6
|
+
date_picker_root_attrs['data-default-value'] = object.serialized_default_date_for_dom if object.serialized_default_date_for_dom.present?
|
|
7
|
+
%>
|
|
8
|
+
<%= pb_content_tag(:div, date_picker_root_attrs) do %>
|
|
6
9
|
<div class="input_wrapper">
|
|
7
10
|
<% if !object.hide_label && object.label %>
|
|
8
11
|
<label for="<%= object.picker_id %>">
|
|
@@ -145,6 +145,18 @@ module Playbook
|
|
|
145
145
|
def angle_down_path
|
|
146
146
|
"app/pb_kits/playbook/utilities/icons/angle-down.svg"
|
|
147
147
|
end
|
|
148
|
+
|
|
149
|
+
# Serialized business default for opt-in smart filter reset (Nitro / data-default-value).
|
|
150
|
+
def serialized_default_date_for_dom
|
|
151
|
+
case default_date
|
|
152
|
+
when nil, ""
|
|
153
|
+
nil
|
|
154
|
+
when Array
|
|
155
|
+
default_date.compact_blank.map(&:to_s).join(",")
|
|
156
|
+
else
|
|
157
|
+
default_date.to_s.presence
|
|
158
|
+
end
|
|
159
|
+
end
|
|
148
160
|
end
|
|
149
161
|
end
|
|
150
162
|
end
|
|
@@ -40,6 +40,66 @@ describe('DatePicker Kit', () => {
|
|
|
40
40
|
expect(kit).toHaveClass('pb_date_picker_kit mb_sm')
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
+
test('exposes data-default-value on kit root when defaultDate is set', () => {
|
|
44
|
+
const testId = 'datepicker-def-attr'
|
|
45
|
+
render(
|
|
46
|
+
<DatePicker
|
|
47
|
+
data={{ testid: testId }}
|
|
48
|
+
defaultDate={DEFAULT_DATE}
|
|
49
|
+
pickerId="date-picker-def-attr"
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
const kit = screen.getByTestId(testId)
|
|
53
|
+
expect(kit).toHaveAttribute('data-default-value', DEFAULT_DATE.toISOString())
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('omits data-default-value when defaultDate is empty', () => {
|
|
57
|
+
const testId = 'datepicker-no-def-attr'
|
|
58
|
+
render(
|
|
59
|
+
<DatePicker
|
|
60
|
+
data={{ testid: testId }}
|
|
61
|
+
defaultDate=""
|
|
62
|
+
pickerId="date-picker-no-def-attr"
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
const kit = screen.getByTestId(testId)
|
|
66
|
+
expect(kit).not.toHaveAttribute('data-default-value')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('renders without error when defaultDate is an invalid Date', () => {
|
|
70
|
+
const testId = 'datepicker-invalid-date'
|
|
71
|
+
const invalidDate = new Date('UndefinedT00:00')
|
|
72
|
+
|
|
73
|
+
expect(() => {
|
|
74
|
+
render(
|
|
75
|
+
<DatePicker
|
|
76
|
+
data={{ testid: testId }}
|
|
77
|
+
defaultDate={invalidDate}
|
|
78
|
+
pickerId="date-picker-invalid-date"
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}).not.toThrow()
|
|
82
|
+
|
|
83
|
+
const kit = screen.getByTestId(testId)
|
|
84
|
+
expect(kit).not.toHaveAttribute('data-default-value')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('omits data-default-value when defaultDate is constructed from undefined', () => {
|
|
88
|
+
const testId = 'datepicker-undefined-date'
|
|
89
|
+
const invalidDate = new Date(undefined + 'T00:00')
|
|
90
|
+
|
|
91
|
+
render(
|
|
92
|
+
<DatePicker
|
|
93
|
+
data={{ testid: testId }}
|
|
94
|
+
defaultDate={invalidDate}
|
|
95
|
+
pickerId="date-picker-undefined-date"
|
|
96
|
+
/>
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const kit = screen.getByTestId(testId)
|
|
100
|
+
expect(kit).not.toHaveAttribute('data-default-value')
|
|
101
|
+
})
|
|
102
|
+
|
|
43
103
|
test('inLine alone adds inline-date-picker class and inline control icons, not the calendar icon', () => {
|
|
44
104
|
const testId = 'datepicker-inline-only'
|
|
45
105
|
render(
|
|
@@ -278,7 +278,9 @@ const datePickerHelper = (config: DatePickerConfig, scrollContainer: string | HT
|
|
|
278
278
|
// Default Date + Min/Max Date Initialization Helper Functions section ----/
|
|
279
279
|
const toDateObject = (dateValue: any): Date | null => {
|
|
280
280
|
if (!dateValue) return null
|
|
281
|
-
if (dateValue instanceof Date)
|
|
281
|
+
if (dateValue instanceof Date) {
|
|
282
|
+
return isNaN(dateValue.getTime()) ? null : dateValue
|
|
283
|
+
}
|
|
282
284
|
if (typeof dateValue === 'string') {
|
|
283
285
|
const parsed = new Date(dateValue)
|
|
284
286
|
return isNaN(parsed.getTime()) ? null : parsed
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import React, { useEffect
|
|
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, {
|
|
8
|
-
InteractiveFilterConfig,
|
|
9
|
-
labelFor,
|
|
10
|
-
} from './InteractiveFilter'
|
|
7
|
+
import InteractiveFilter, { InteractiveFilterConfig } from './InteractiveFilter'
|
|
11
8
|
|
|
12
9
|
export type FilterDescription = {
|
|
13
10
|
[key: string]: string | null | boolean,
|
|
@@ -24,13 +21,6 @@ export type CurrentFiltersProps = {
|
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
const hiddenFilters = (value: any) => isEmpty(value) && value !== true
|
|
27
|
-
const INTERACTIVE_FILTER_BREAKPOINT = 960
|
|
28
|
-
const MAX_INTERACTIVE_FILTERS = 4
|
|
29
|
-
|
|
30
|
-
const supportsInteractiveFilters = () =>
|
|
31
|
-
typeof window === 'undefined' ||
|
|
32
|
-
typeof window.matchMedia !== 'function' ||
|
|
33
|
-
window.matchMedia(`(min-width: ${INTERACTIVE_FILTER_BREAKPOINT}px)`).matches
|
|
34
24
|
|
|
35
25
|
const CurrentFilters = ({
|
|
36
26
|
dark,
|
|
@@ -38,28 +28,6 @@ const CurrentFilters = ({
|
|
|
38
28
|
interactiveFilters = {},
|
|
39
29
|
}: CurrentFiltersProps): React.ReactElement => {
|
|
40
30
|
const displayableFilters = omitBy(filters, hiddenFilters)
|
|
41
|
-
const [interactiveFiltersEnabled, setInteractiveFiltersEnabled] = useState(
|
|
42
|
-
supportsInteractiveFilters
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (typeof window.matchMedia !== 'function') return
|
|
47
|
-
|
|
48
|
-
const mediaQuery = window.matchMedia(
|
|
49
|
-
`(min-width: ${INTERACTIVE_FILTER_BREAKPOINT}px)`
|
|
50
|
-
)
|
|
51
|
-
const handleChange = () => setInteractiveFiltersEnabled(mediaQuery.matches)
|
|
52
|
-
|
|
53
|
-
handleChange()
|
|
54
|
-
|
|
55
|
-
if (mediaQuery.addEventListener) {
|
|
56
|
-
mediaQuery.addEventListener('change', handleChange)
|
|
57
|
-
return () => mediaQuery.removeEventListener('change', handleChange)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
mediaQuery.addListener(handleChange)
|
|
61
|
-
return () => mediaQuery.removeListener(handleChange)
|
|
62
|
-
}, [])
|
|
63
31
|
|
|
64
32
|
return (
|
|
65
33
|
<div className="maskContainer">
|
|
@@ -77,41 +45,28 @@ const CurrentFilters = ({
|
|
|
77
45
|
{ !isEmpty(filters) &&
|
|
78
46
|
<div className="filters">
|
|
79
47
|
<div className="left_gradient" />
|
|
80
|
-
{map(displayableFilters, (value, name
|
|
48
|
+
{map(displayableFilters, (value, name) => {
|
|
81
49
|
const interactiveConfig = interactiveFilters[name]
|
|
82
|
-
const filterNames = Object.keys(collection)
|
|
83
|
-
const currentFilterIndex = filterNames.indexOf(String(name))
|
|
84
|
-
const filterValue = value === true ? '' : String(value)
|
|
85
|
-
const interactiveValue =
|
|
86
|
-
interactiveConfig?.value !== undefined
|
|
87
|
-
? interactiveConfig.value
|
|
88
|
-
: filterValue
|
|
89
|
-
const displayValue = interactiveConfig
|
|
90
|
-
? labelFor(interactiveConfig, interactiveValue)
|
|
91
|
-
: value
|
|
92
|
-
const interactiveFilterIndex = filterNames
|
|
93
|
-
.slice(0, currentFilterIndex + 1)
|
|
94
|
-
.filter((key) => interactiveFilters[key]).length - 1
|
|
95
|
-
const isInteractive = Boolean(
|
|
96
|
-
interactiveFiltersEnabled &&
|
|
97
|
-
interactiveConfig &&
|
|
98
|
-
interactiveFilterIndex < MAX_INTERACTIVE_FILTERS
|
|
99
|
-
)
|
|
100
|
-
|
|
101
50
|
return (
|
|
102
51
|
<div
|
|
103
|
-
className={`filter${
|
|
52
|
+
className={`filter${interactiveConfig ? ' interactive' : ''}`}
|
|
104
53
|
key={`filter-${name}`}
|
|
105
54
|
>
|
|
106
|
-
{
|
|
55
|
+
{ interactiveConfig ?
|
|
107
56
|
<InteractiveFilter
|
|
108
57
|
config={interactiveConfig}
|
|
109
58
|
dark={dark}
|
|
110
59
|
editorValue={interactiveConfig.editorValue}
|
|
111
60
|
name={String(name)}
|
|
112
|
-
value={
|
|
61
|
+
value={
|
|
62
|
+
interactiveConfig.value !== undefined
|
|
63
|
+
? interactiveConfig.value
|
|
64
|
+
: value === true
|
|
65
|
+
? ''
|
|
66
|
+
: (value as string)
|
|
67
|
+
}
|
|
113
68
|
/> :
|
|
114
|
-
|
|
69
|
+
value === true ?
|
|
115
70
|
<Title
|
|
116
71
|
dark={dark}
|
|
117
72
|
size={4}
|
|
@@ -127,7 +82,7 @@ const CurrentFilters = ({
|
|
|
127
82
|
dark={dark}
|
|
128
83
|
size={4}
|
|
129
84
|
tag="h4"
|
|
130
|
-
text={
|
|
85
|
+
text={value}
|
|
131
86
|
/>
|
|
132
87
|
</div>
|
|
133
88
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
@import "../tokens/colors";
|
|
3
3
|
@import "../tokens/border_radius";
|
|
4
4
|
|
|
5
|
-
$interactive_filter_breakpoint: 960px;
|
|
6
|
-
|
|
7
5
|
.pb_filter_sort_menu {
|
|
8
6
|
li {
|
|
9
7
|
padding: 0 $space_sm !important;
|
|
@@ -78,10 +76,6 @@ $interactive_filter_breakpoint: 960px;
|
|
|
78
76
|
padding-top: 2px;
|
|
79
77
|
padding-bottom: 2px;
|
|
80
78
|
}
|
|
81
|
-
|
|
82
|
-
.pb_interactive_filter_mobile_static {
|
|
83
|
-
display: none;
|
|
84
|
-
}
|
|
85
79
|
}
|
|
86
80
|
|
|
87
81
|
// Interactive filter chip: clickable button that opens a popover whose
|
|
@@ -132,20 +126,6 @@ $interactive_filter_breakpoint: 960px;
|
|
|
132
126
|
}
|
|
133
127
|
}
|
|
134
128
|
|
|
135
|
-
@media screen and (max-width: ($interactive_filter_breakpoint - 1px)) {
|
|
136
|
-
.pb_filter_kit {
|
|
137
|
-
.filters {
|
|
138
|
-
.pb_interactive_filter_desktop {
|
|
139
|
-
display: none;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.pb_interactive_filter_mobile_static {
|
|
143
|
-
display: block;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
129
|
.pb_popover_tooltip.pb_interactive_filter_editor {
|
|
150
130
|
position: fixed;
|
|
151
131
|
top: 0;
|
|
@@ -9,6 +9,4 @@ For date ranges, use `type: 'dropdown'` with `variant: 'quickpick'`. The Filter
|
|
|
9
9
|
|
|
10
10
|
Chip edits update the linked control inside the filter popover. Click **Apply** to submit, then pass the submitted values back into `filters` so chips re-render with the latest labels.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Use `auto_submit: true` to submit the form immediately after a chip value is picked.
|
|
12
|
+
Optional `auto_submit: true` on a chip entry submits the form immediately when a chip value is picked.
|
|
@@ -7,4 +7,4 @@ For date ranges, use `type: 'dropdown'` with `variant: 'quickpick'`. The Filter
|
|
|
7
7
|
|
|
8
8
|
Keep `filters`, `interactiveFilters.value`, and the filter popover fields backed by the same state so chip labels and form controls stay in sync.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
On Rails, pass `interactive_filters` with `target_input` so chip picks update the linked form control. Re-read submitted params in your view after Apply so `filters` reflects the new values.
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
<div class="maskContainer">
|
|
7
7
|
<div class="filters">
|
|
8
8
|
<div class="left_gradient"></div>
|
|
9
|
-
<% interactive_filters_rendered = 0 %>
|
|
10
9
|
<% if object.filters&.none? { |filter| filter[:name].present? } %>
|
|
11
10
|
<div>
|
|
12
11
|
<%= pb_rails("body", props: {
|
|
@@ -21,9 +20,8 @@
|
|
|
21
20
|
<% object.filters&.each do |filter| %>
|
|
22
21
|
<% next unless filter[:name].present? %>
|
|
23
22
|
<% interactive_config = object.interactive_config_for(filter[:name]) %>
|
|
24
|
-
<% if interactive_config.present?
|
|
23
|
+
<% if interactive_config.present? %>
|
|
25
24
|
<%
|
|
26
|
-
interactive_filters_rendered += 1
|
|
27
25
|
chip_id = "pb-interactive-filter-#{object.id || 'kit'}-#{filter[:name].to_s.parameterize}"
|
|
28
26
|
popover_id = "#{chip_id}-popover"
|
|
29
27
|
type = object.interactive_value(interactive_config, :type).to_s
|
|
@@ -49,7 +47,7 @@
|
|
|
49
47
|
)
|
|
50
48
|
end
|
|
51
49
|
%>
|
|
52
|
-
<div class="filter interactive
|
|
50
|
+
<div class="filter interactive">
|
|
53
51
|
<button type="button"
|
|
54
52
|
class="pb_interactive_filter_trigger"
|
|
55
53
|
id="<%= chip_id %>"
|
|
@@ -82,6 +80,7 @@
|
|
|
82
80
|
<% is_active = opt_value.to_s == filter[:value].to_s %>
|
|
83
81
|
<% opt_label = object.interactive_option_label(opt, opt_value) %>
|
|
84
82
|
<li class="pb_interactive_filter_option<%= ' active' if is_active %>"
|
|
83
|
+
tabindex="0"
|
|
85
84
|
role="option"
|
|
86
85
|
aria-selected="<%= is_active %>"
|
|
87
86
|
data-pb-filter-option-value="<%= opt_value %>"
|
|
@@ -96,14 +95,10 @@
|
|
|
96
95
|
</div>
|
|
97
96
|
</div>
|
|
98
97
|
</div>
|
|
99
|
-
<div class="filter pb_interactive_filter_mobile_static">
|
|
100
|
-
<%= pb_rails("caption", props: { text: filter[:name] }) %>
|
|
101
|
-
<%= pb_rails("title", props: { size: 4, tag: "h4", text: display_value }) %>
|
|
102
|
-
</div>
|
|
103
98
|
<% else %>
|
|
104
99
|
<div class="filter">
|
|
105
100
|
<%= pb_rails("caption", props: { text: filter[:name] }) %>
|
|
106
|
-
<%= pb_rails("title", props: { size: 4, tag: "h4", text:
|
|
101
|
+
<%= pb_rails("title", props: { size: 4, tag: "h4", text: filter[:value] }) %>
|
|
107
102
|
</div>
|
|
108
103
|
<% end %>
|
|
109
104
|
<% end %>
|
|
@@ -88,34 +88,6 @@ test("triggers popover on filter button click", () => {
|
|
|
88
88
|
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
test("limits interactive filters to four at desktop sizes", () => {
|
|
92
|
-
mockMatchMedia(true);
|
|
93
|
-
|
|
94
|
-
render(
|
|
95
|
-
<FilterTest
|
|
96
|
-
filters={{
|
|
97
|
-
One: "1",
|
|
98
|
-
Two: "2",
|
|
99
|
-
Three: "3",
|
|
100
|
-
Four: "4",
|
|
101
|
-
Five: "5",
|
|
102
|
-
}}
|
|
103
|
-
interactiveFilters={{
|
|
104
|
-
One: { type: "select", options: [{ value: "1" }], onChange: jest.fn() },
|
|
105
|
-
Two: { type: "select", options: [{ value: "2" }], onChange: jest.fn() },
|
|
106
|
-
Three: { type: "select", options: [{ value: "3" }], onChange: jest.fn() },
|
|
107
|
-
Four: { type: "select", options: [{ value: "4" }], onChange: jest.fn() },
|
|
108
|
-
Five: { type: "select", options: [{ value: "5" }], onChange: jest.fn() },
|
|
109
|
-
}}
|
|
110
|
-
/>
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
const interactiveButtons = screen
|
|
114
|
-
.getAllByRole("button")
|
|
115
|
-
.filter((button) => button.getAttribute("aria-haspopup") === "dialog");
|
|
116
|
-
|
|
117
|
-
expect(interactiveButtons).toHaveLength(4);
|
|
118
|
-
});
|
|
119
91
|
|
|
120
92
|
test("calls onChange and closes editor when an interactive option is selected", () => {
|
|
121
93
|
mockMatchMedia(true);
|
|
@@ -180,35 +152,3 @@ test("generates quickpick options for interactive dropdown filters", () => {
|
|
|
180
152
|
|
|
181
153
|
expect(handleChange).toHaveBeenCalledWith("quickpick-last-month");
|
|
182
154
|
});
|
|
183
|
-
|
|
184
|
-
test("renders interactive filters as static labels below 960px", () => {
|
|
185
|
-
mockMatchMedia(false);
|
|
186
|
-
|
|
187
|
-
render(
|
|
188
|
-
<FilterTest
|
|
189
|
-
filters={{
|
|
190
|
-
Territory: "USA",
|
|
191
|
-
Status: "open",
|
|
192
|
-
}}
|
|
193
|
-
interactiveFilters={{
|
|
194
|
-
Territory: {
|
|
195
|
-
type: "select",
|
|
196
|
-
options: [{ value: "USA" }],
|
|
197
|
-
onChange: jest.fn(),
|
|
198
|
-
},
|
|
199
|
-
Status: {
|
|
200
|
-
type: "dropdown",
|
|
201
|
-
options: [{ value: "open", label: "Open" }],
|
|
202
|
-
onChange: jest.fn(),
|
|
203
|
-
},
|
|
204
|
-
}}
|
|
205
|
-
/>
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const interactiveButtons = screen
|
|
209
|
-
.getAllByRole("button")
|
|
210
|
-
.filter((button) => button.getAttribute("aria-haspopup") === "dialog");
|
|
211
|
-
|
|
212
|
-
expect(interactiveButtons).toHaveLength(0);
|
|
213
|
-
expect(screen.getByText("Open")).toBeInTheDocument();
|
|
214
|
-
});
|
|
@@ -5,7 +5,6 @@ import { createPopper, Instance as PopperInstance } from "@popperjs/core";
|
|
|
5
5
|
import flatpickr from "flatpickr";
|
|
6
6
|
|
|
7
7
|
const INTERACTIVE_FILTER_SELECTOR = "[data-pb-interactive-filter]";
|
|
8
|
-
const INTERACTIVE_FILTER_BREAKPOINT = 960;
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Rails-side enhanced behavior for interactive applied-filter chips.
|
|
@@ -95,8 +94,6 @@ export default class PbFilter extends PbEnhancedElement {
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
toggle() {
|
|
98
|
-
if (window.innerWidth < INTERACTIVE_FILTER_BREAKPOINT) return;
|
|
99
|
-
|
|
100
97
|
this._isOpen ? this.hide() : this.show();
|
|
101
98
|
}
|
|
102
99
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx}from"react/jsx-runtime";import{useMemo}from"react";import{b as buildAriaProps,a as buildDataProps,c as buildHtmlProps,d as classnames,e as buildCss,g as globalProps}from"./globalProps-
|
|
1
|
+
import{jsx}from"react/jsx-runtime";import{useMemo}from"react";import{b as buildAriaProps,a as buildDataProps,c as buildHtmlProps,d as classnames,e as buildCss,g as globalProps}from"./globalProps-dmEeu7jO.js";import Highcharts from"highcharts";import HighchartsReact from"highcharts-react-official";import{t as typography,c as colors}from"./lib-CpaLlY1W.js";import highchartsMore from"highcharts/highcharts-more";import solidGauge from"highcharts/modules/solid-gauge";const barGraphTheme={title:{text:"",style:{color:colors.text_lt_default,fontFamily:typography.font_family_base,fontWeight:typography.bold,fontSize:typography.heading_3}},subtitle:{text:"",style:{fontFamily:typography.font_family_base,color:colors.text_lt_light,fontWeight:typography.regular,fontSize:typography.text_base}},chart:{type:"column"},tooltip:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,colors.bg_dark],[1,colors.bg_dark]]},style:{fontFamily:typography.font_family_base,color:colors.text_dk_default,fontWeight:typography.regular,fontSize:typography.text_smaller}},colors:[colors.data_1,colors.data_2,colors.data_3,colors.data_4,colors.data_5,colors.data_6,colors.data_7],credits:{enabled:false},legend:{enabled:false,itemStyle:{color:colors.text_lt_light,fill:colors.text_lt_light,fontSize:typography.text_smaller}},xAxis:{gridLineWidth:0,lineColor:colors.border_light,tickColor:colors.border_light,labels:{style:{fontFamily:typography.font_family_base,color:colors.text_lt_lighter,fontWeight:typography.bold,fontSize:typography.text_smaller}},title:{style:{color:colors.text_lt_default,fontFamily:typography.font_family_base,fontWeight:typography.regular,fontSize:typography.heading_4}}},yAxis:{alternateGridColor:void 0,minorTickInterval:null,gridLineColor:colors.border_light,minorGridLineColor:colors.border_light,lineWidth:0,tickWidth:0,labels:{style:{fontFamily:typography.font_family_base,color:colors.text_lt_lighter,fontWeight:typography.bold,fontSize:typography.text_smaller}},title:{style:{fontFamily:typography.font_family_base,color:colors.text_lt_lighter,fontWeight:typography.bold,fontSize:typography.text_smaller}}}};const PbBarGraph=props=>{const{aria:aria={},data:data={},id:id,htmlOptions:htmlOptions={},options:options,className:className}=props;const ariaProps=buildAriaProps(aria);const dataProps=buildDataProps(data);const htmlProps=buildHtmlProps(htmlOptions);const classes=classnames(buildCss("pb_pb_bar_graph"),globalProps(props),className);const mergedOptions=useMemo((()=>{if(!options||typeof options!=="object"){console.error("❌ Invalid options passed to <PbBarGraph />",options);return{}}return Highcharts.merge({},barGraphTheme,options)}),[options]);return jsx("div",{children:jsx(HighchartsReact,{containerProps:{className:classnames(classes),id:id,...ariaProps,...dataProps,...htmlProps},highcharts:Highcharts,options:mergedOptions})})};const pbCircleChartTheme={title:{text:"",style:{color:colors.text_lt_default,fontFamily:typography.font_family_base,fontWeight:typography.bold,fontSize:typography.heading_3}},chart:{type:"pie"},tooltip:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,colors.bg_dark],[1,colors.bg_dark]]},pointFormat:'<span style="font-weight: bold; color:{point.color};">●</span>{point.name}: <b>{point.y}</b>',followPointer:true,shadow:false,borderWidth:0,borderRadius:10,style:{fontFamily:typography.font_family_base,color:colors.text_dk_default,fontWeight:typography.regular,fontSize:typography.text_smaller}},plotOptions:{pie:{dataLabels:{enabled:false,connectorShape:"straight",connectorWidth:3,format:"<div>{point.name}</div>",style:{fontFamily:typography.font_family_base,fontSize:typography.text_smaller,color:colors.text_lt_light,fontWeight:typography.regular,textOutline:"2px $white"}},innerSize:"50%",borderColor:"",borderWidth:null,colors:[colors.data_1,colors.data_2,colors.data_3,colors.data_4,colors.data_5,colors.data_6,colors.data_7]}},legend:{layout:"horizontal",align:"center",verticalAlign:"bottom",itemStyle:{fontFamily:typography.font_family_base,color:colors.text_lt_light,fontWeight:typography.regular,fontSize:typography.text_smaller},itemHoverStyle:{color:colors.text_lt_default},itemHiddenStyle:{color:colors.text_lt_lighter}},credits:{enabled:false}};const PbCircleChart=props=>{const{aria:aria={},className:className,data:data={},id:id,htmlOptions:htmlOptions={},options:options}=props;const ariaProps=buildAriaProps(aria);const dataProps=buildDataProps(data);const htmlProps=buildHtmlProps(htmlOptions);const classes=classnames(buildCss("pb_pb_circle_chart"),globalProps(props),className);const mergedOptions=useMemo((()=>{if(!options||typeof options!=="object"){console.error("❌ Invalid options passed to <PbCircleChart />",options);return{}}return Highcharts.merge({},pbCircleChartTheme,options)}),[options]);return jsx("div",{children:jsx(HighchartsReact,{containerProps:{className:classnames(classes),id:id,...ariaProps,...dataProps,...htmlProps},highcharts:Highcharts,options:mergedOptions})})};const pbGaugeGraphTheme={title:{text:"",style:{fontFamily:typography.font_family_base,fontSize:typography.text_larger}},chart:{type:"solidgauge",events:{render(){this.container;const arc=this.container.querySelector("path.gauge-pane");if(arc)arc.setAttribute("stroke-linejoin","round")}}},pane:{size:"90%",startAngle:-100,endAngle:100,background:[{borderWidth:20,innerRadius:"90%",outerRadius:"90%",shape:"arc",className:"gauge-pane",borderColor:colors.border_light,borderRadius:"50%"}]},tooltip:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,colors.bg_dark],[1,colors.bg_dark]]},pointFormat:'<span style="font-weight: bold; color:{point.color};">●</span>{point.name}: <b>{point.y}</b>',followPointer:true,shadow:false,borderWidth:0,borderRadius:10,style:{fontFamily:typography.font_family_base,color:colors.text_dk_default,fontWeight:typography.regular,fontSize:typography.text_smaller}},yAxis:{min:0,max:100,lineWidth:0,tickPositions:[]},plotOptions:{solidgauge:{borderColor:colors.data_1,borderWidth:20,color:colors.data_1,radius:90,innerRadius:"90%",y:-26,dataLabels:{borderWidth:0,color:colors.text_lt_default,enabled:true,format:'<span class="fix">{y:,f}</span>',style:{fontFamily:typography.font_family_base,fontWeight:typography.regular,fontSize:typography.heading_2},y:-26}}},credits:{enabled:false}};const PbGaugeChart=props=>{const{aria:aria={},className:className,data:data={},htmlOptions:htmlOptions={},id:id,ref:ref,options:options={}}=props;const ariaProps=buildAriaProps(aria);const dataProps=buildDataProps(data);const htmlProps=buildHtmlProps(htmlOptions);const classes=classnames(buildCss("pb_pb_gauge_chart"),globalProps(props),className);const mergedOptions=useMemo((()=>{if(!options||typeof options!=="object"){console.error("❌ Invalid options passed to <PbLineGraph />",options);return{}}return Highcharts.merge({},pbGaugeGraphTheme,options)}),[options]);highchartsMore(Highcharts);solidGauge(Highcharts);return jsx("div",{children:jsx(HighchartsReact,{containerProps:{className:classnames(classes),id:id,ref:ref,...ariaProps,...dataProps,...htmlProps},highcharts:Highcharts,options:mergedOptions})})};const pbLineGraphTheme={title:{text:"",style:{color:colors.text_lt_default,fontFamily:typography.font_family_base,fontWeight:typography.bold,fontSize:typography.heading_3}},subtitle:{text:"",style:{fontFamily:typography.font_family_base,color:colors.text_lt_light,fontWeight:typography.regular,fontSize:typography.text_base}},chart:{type:"line"},tooltip:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,colors.bg_dark],[1,colors.bg_dark]]},followPointer:true,shadow:false,borderWidth:0,borderRadius:10,style:{fontFamily:typography.font_family_base,color:colors.text_dk_default,fontWeight:typography.regular,fontSize:typography.text_smaller}},plotOptions:{line:{dataLabels:{enabled:false}}},credits:{enabled:false},legend:{enabled:false,itemStyle:{fontFamily:typography.font_family_base,color:colors.text_lt_light,fontWeight:typography.regular,fontSize:typography.text_smaller},itemHoverStyle:{color:colors.text_lt_default},itemHiddenStyle:{color:colors.text_lt_lighter}},colors:[colors.data_1,colors.data_2,colors.data_3,colors.data_4,colors.data_5,colors.data_6,colors.data_7],xAxis:{gridLineWidth:0,lineColor:colors.border_light,tickColor:colors.border_light,labels:{style:{fontFamily:typography.font_family_base,color:colors.text_lt_lighter,fontWeight:typography.bold,fontSize:typography.text_smaller}},title:{style:{color:colors.text_lt_default,fontFamily:typography.font_family_base,fontWeight:typography.regular,fontSize:typography.heading_4}}},yAxis:{alternateGridColor:void 0,minorTickInterval:null,gridLineColor:colors.border_light,minorGridLineColor:colors.border_light,lineWidth:0,tickWidth:0,tickPixelInterval:50,labels:{style:{fontFamily:typography.font_family_base,color:colors.text_lt_lighter,fontWeight:typography.bold,fontSize:typography.text_smaller}},title:{style:{fontFamily:typography.font_family_base,color:colors.text_lt_lighter,fontWeight:typography.bold,fontSize:typography.text_smaller}}}};const PbLineGraph=props=>{const{aria:aria={},className:className,data:data={},id:id,htmlOptions:htmlOptions={},options:options}=props;const ariaProps=buildAriaProps(aria);const dataProps=buildDataProps(data);const htmlProps=buildHtmlProps(htmlOptions);const classes=classnames(buildCss("pb_pb_line_graph"),globalProps(props),className);const mergedOptions=useMemo((()=>{if(!options||typeof options!=="object"){console.error("❌ Invalid options passed to <PbLineGraph />",options);return{}}return Highcharts.merge({},pbLineGraphTheme,options)}),[options]);return jsx("div",{children:jsx(HighchartsReact,{containerProps:{className:classnames(classes),id:id,...ariaProps,...dataProps,...htmlProps},highcharts:Highcharts,options:mergedOptions})})};export{PbBarGraph as P,PbCircleChart as a,PbGaugeChart as b,PbLineGraph as c};
|