playbook_ui 13.23.0 → 13.24.0.pre.alpha.PBNTR261NewKitDropdown2681

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +2 -0
  3. data/app/pb_kits/playbook/index.js +1 -0
  4. data/app/pb_kits/playbook/pb_avatar/avatar.html.erb +1 -6
  5. data/app/pb_kits/playbook/pb_bar_graph/_bar_graph.tsx +41 -6
  6. data/app/pb_kits/playbook/pb_bar_graph/bar_graph.rb +4 -0
  7. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_negative_numbers.html.erb +23 -0
  8. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_negative_numbers.jsx +35 -0
  9. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_secondary_y_axis.html.erb +26 -0
  10. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_secondary_y_axis.jsx +36 -0
  11. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_secondary_y_axis.md +3 -0
  12. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.html.erb +22 -0
  13. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.jsx +34 -0
  14. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.md +1 -0
  15. data/app/pb_kits/playbook/pb_bar_graph/docs/example.yml +6 -0
  16. data/app/pb_kits/playbook/pb_bar_graph/docs/index.js +3 -0
  17. data/app/pb_kits/playbook/pb_body/_body.scss +3 -3
  18. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +92 -0
  19. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +152 -0
  20. data/app/pb_kits/playbook/pb_dropdown/context/index.tsx +5 -0
  21. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.jsx +53 -0
  22. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +104 -0
  23. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.jsx +69 -0
  24. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.jsx +78 -0
  25. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +9 -0
  26. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -0
  27. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +17 -0
  28. data/app/pb_kits/playbook/pb_dropdown/hooks/useDropdown.tsx +17 -0
  29. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +53 -0
  30. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +95 -0
  31. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +91 -0
  32. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +118 -0
  33. data/app/pb_kits/playbook/pb_list/_list_item.tsx +2 -2
  34. data/app/pb_kits/playbook/pb_typeahead/_typeahead.tsx +27 -19
  35. data/app/pb_kits/playbook/pb_typeahead/components/MenuList.tsx +4 -2
  36. data/app/pb_kits/playbook/pb_typeahead/components/index.tsx +19 -0
  37. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_custom_menu_list.jsx +51 -0
  38. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_with_highlight.jsx +1 -1
  39. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +1 -0
  40. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +1 -0
  41. data/app/pb_kits/playbook/playbook-doc.js +2 -0
  42. data/app/pb_kits/playbook/tokens/_colors.scss +1 -1
  43. data/dist/menu.yml +5 -1
  44. data/dist/playbook-rails.js +6 -6
  45. data/lib/playbook/kit_base.rb +21 -1
  46. data/lib/playbook/version.rb +2 -2
  47. metadata +30 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 452a03b346d2fe5cd79019537fb3bc6ac4bfbdaa621b3e552d898bdaaf1e7c00
4
- data.tar.gz: fd5127e3deb4e55449b11d1d989335e3b48cfda1d0064b353d615749eef417c6
3
+ metadata.gz: e7befd6847de7d677af4be77be96824572a5fc0cb5a643ea35570064fe156ccb
4
+ data.tar.gz: 67dea2e25b7861d71730c3ad905df0316806517c70f663077e506bdee9be471f
5
5
  SHA512:
6
- metadata.gz: 48edcdfd01a13c5082b670efa0a5d689cd3e3793ce9adacb1789f047bc1514f8f428194d2ea43e1e9bdef4b11d62a496a0b064d6009fed455287ca770ddb81fa
7
- data.tar.gz: 37f2323fd3f2a3b4ea76822e139a9bc2c627f7c3bc6817f1f6092c7d9ea9159db7c9916bd0737baab951f7b4aeae28f17dfe5025282ad678898b5eb40bf05ffb
6
+ metadata.gz: 18d436e20297a673fbcaa699ce41a884d43859dd76482409ffb822b1dc803ceda9b78045642ccea169fd63645b73948e96d52343f76098ac8847415f57344e59
7
+ data.tar.gz: 16b5c21300347386fc574a61fa56a46c5b3e25c38bfb3f48f9b42ddc411b4f5a5d2c9121adbaa5e69384e189dcf24c896dcdf4892d41ff0078e3eea445f7edbb
@@ -1,5 +1,6 @@
1
1
 
2
2
 
3
+
3
4
  @import 'pb_advanced_table/advanced_table';
4
5
  @import 'pb_avatar/avatar';
5
6
  @import 'pb_avatar_action_button/avatar_action_button';
@@ -30,6 +31,7 @@
30
31
  @import 'pb_detail/detail';
31
32
  @import 'pb_dialog/dialog';
32
33
  @import 'pb_distribution_bar/distribution_bar';
34
+ @import 'pb_dropdown/dropdown';
33
35
  @import 'pb_file_upload/file_upload';
34
36
  @import 'pb_filter/filter';
35
37
  @import 'pb_fixed_confirmation_toast/fixed_confirmation_toast';
@@ -35,6 +35,7 @@ export { default as DateYearStacked } from './pb_date_year_stacked/_date_year_st
35
35
  export { default as Detail} from './pb_detail/_detail'
36
36
  export { default as Dialog } from './pb_dialog/_dialog'
37
37
  export { default as DistributionBar } from './pb_distribution_bar/_distribution_bar'
38
+ export { default as Dropdown} from './pb_dropdown/_dropdown'
38
39
  export { default as FileUpload } from './pb_file_upload/_file_upload'
39
40
  export { default as Filter } from './pb_filter/_filter'
40
41
  export { default as FixedConfirmationToast } from './pb_fixed_confirmation_toast/_fixed_confirmation_toast'
@@ -1,9 +1,4 @@
1
- <%= content_tag(:div,
2
- id: object.id,
3
- data: object.data.merge(initials: object.initials),
4
- class: object.classname,
5
- aria: object.aria,
6
- **combined_html_options) do %>
1
+ <%= object.pb_content_tag(:div, data: object.data.merge(initials: object.initials)) do %>
7
2
  <%= content_tag(:div, data: { initials: object.initials }, class: "avatar_wrapper") do %>
8
3
  <%= pb_rails("image", props: { alt: object.alt_text, url: object.image_url, on_error: object.handle_img_error }) if object.image_url.present? %>
9
4
  <% end %>
@@ -13,12 +13,12 @@ import classnames from "classnames";
13
13
 
14
14
  type BarGraphProps = {
15
15
  align?: "left" | "right" | "center";
16
- axisTitle: string;
16
+ axisTitle: { name: string; }[] | string;
17
17
  dark?: boolean;
18
18
  xAxisCategories: [];
19
19
  yAxisMin: number;
20
20
  yAxisMax: number;
21
- chartData: { name: string; data: number[] }[];
21
+ chartData: { name: string; data: number[], yAxis: number }[];
22
22
  className?: string;
23
23
  customOptions?: Partial<Highcharts.Options>;
24
24
  id: string;
@@ -37,6 +37,8 @@ type BarGraphProps = {
37
37
  y?: number;
38
38
  aria?: { [key: string]: string };
39
39
  data?: { [key: string]: string };
40
+ stacking?: "normal" | "percent"
41
+ axisFormat?: { format: string; }[] | string;
40
42
  };
41
43
 
42
44
 
@@ -51,8 +53,10 @@ const BarGraph = ({
51
53
  colors,
52
54
  htmlOptions = {},
53
55
  customOptions = {},
56
+ axisFormat,
54
57
  id,
55
58
  pointStart,
59
+ stacking,
56
60
  subTitle,
57
61
  type = "column",
58
62
  title = "Title",
@@ -67,7 +71,7 @@ const BarGraph = ({
67
71
  x = 0,
68
72
  y = 0,
69
73
  ...props
70
- }: BarGraphProps): React.ReactElement => {
74
+ }: BarGraphProps): React.ReactElement => {
71
75
  const ariaProps = buildAriaProps(aria);
72
76
  const dataProps = buildDataProps(data)
73
77
  const htmlProps = buildHtmlProps(htmlOptions);
@@ -89,13 +93,23 @@ const BarGraph = ({
89
93
  subtitle: {
90
94
  text: subTitle,
91
95
  },
92
- yAxis: {
96
+ yAxis: [{
97
+ labels: {
98
+ format: typeof axisFormat === 'string' ? axisFormat : (axisFormat && axisFormat[0] ? axisFormat[0].format : "")
99
+
100
+ },
93
101
  min: yAxisMin,
94
102
  max: yAxisMax,
103
+ opposite: false,
95
104
  title: {
96
- text: axisTitle,
105
+ text: typeof axisTitle === 'string' ? axisTitle : axisTitle[0].name,
97
106
  },
98
- },
107
+ plotLines: typeof yAxisMin !== 'undefined' && yAxisMin !== null ? [] : [{
108
+ value: 0,
109
+ zIndex: 10,
110
+ color: "#E4E8F0"
111
+ }],
112
+ }],
99
113
  xAxis: {
100
114
  categories: xAxisCategories,
101
115
  },
@@ -113,7 +127,9 @@ const BarGraph = ({
113
127
  : highchartsTheme.colors,
114
128
  plotOptions: {
115
129
  series: {
130
+ stacking: stacking,
116
131
  pointStart: pointStart,
132
+ borderWidth: stacking ? 0 : "",
117
133
  events: {},
118
134
  dataLabels: {
119
135
  enabled: false,
@@ -124,6 +140,25 @@ const BarGraph = ({
124
140
  credits: false,
125
141
  };
126
142
 
143
+ if (Array.isArray(axisTitle) && axisTitle.length > 1 && axisTitle[1].name) {
144
+ staticOptions.yAxis.push({
145
+ labels: {
146
+ format: typeof axisFormat === 'string' ? axisFormat : axisFormat[1].format,
147
+ },
148
+ min: yAxisMin,
149
+ max: yAxisMax,
150
+ opposite: true,
151
+ title: {
152
+ text: axisTitle[1].name,
153
+ },
154
+ plotLines: typeof yAxisMin !== 'undefined' && yAxisMin !== null ? [] : [{
155
+ value: 0,
156
+ zIndex: 10,
157
+ color: "#E4E8F0"
158
+ }],
159
+ });
160
+ }
161
+
127
162
  if (!toggleLegendClick) {
128
163
  staticOptions.plotOptions.series.events = { legendItemClick: () => false };
129
164
  }
@@ -7,6 +7,7 @@ module Playbook
7
7
  values: %w[left right center],
8
8
  default: "center"
9
9
  prop :axis_title
10
+ prop :axis_format
10
11
  prop :chart_data, type: Playbook::Props::Array,
11
12
  default: []
12
13
  prop :custom_options, default: {}
@@ -14,6 +15,7 @@ module Playbook
14
15
  values: %w[vertical horizontal],
15
16
  default: "vertical"
16
17
  prop :point_start, type: Playbook::Props::Numeric
18
+ prop :stacking
17
19
  prop :subtitle
18
20
  prop :title
19
21
  prop :x_axis_categories, type: Playbook::Props::Array,
@@ -49,8 +51,10 @@ module Playbook
49
51
  dark: dark ? "dark" : "",
50
52
  type: chart_type,
51
53
  title: title,
54
+ stacking: stacking,
52
55
  subTitle: subtitle,
53
56
  axisTitle: axis_title,
57
+ axisFormat: axis_format,
54
58
  pointStart: point_start,
55
59
  xAxisCategories: x_axis_categories,
56
60
  yAxisMin: y_axis_min,
@@ -0,0 +1,23 @@
1
+ <% data = [{
2
+ name: 'Installation',
3
+ data: [-475, 400, -1000, 354, -856],
4
+ threshold: 0
5
+ }, {
6
+ name: 'Manufacturing',
7
+ data: [1475, 200, 1000, 654, -656],
8
+ threshold: 0
9
+ },
10
+ {
11
+ name: 'Sales & Distribution',
12
+ data: [1270, 100, -1200, 554, 756],
13
+ threshold: 0
14
+ }] %>
15
+
16
+ <%= pb_rails("bar_graph", props: {
17
+ axis_title: 'Number of Employees',
18
+ chart_data: data,
19
+ id: "bar-default",
20
+ x_axis_categories:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
21
+ title: 'Bar Graph with Negative Numbers',
22
+ legend: true,
23
+ }) %>
@@ -0,0 +1,35 @@
1
+ import React from 'react'
2
+
3
+ import BarGraph from '../_bar_graph'
4
+
5
+ const chartData = [{
6
+ name: 'Installation',
7
+ data: [-475, 400, -1000, 354, -856],
8
+ threshold: 0
9
+ }, {
10
+ name: 'Manufacturing',
11
+ data: [1475, 200, 1000, 654, -656],
12
+ threshold: 0
13
+ },
14
+ {
15
+ name: 'Sales & Distribution',
16
+ data: [1270, 100, -1200, 554, 756],
17
+ threshold: 0
18
+ }]
19
+
20
+
21
+ const BarGraphStacked = (props) => (
22
+ <div>
23
+ <BarGraph
24
+ axisTitle="Number of Employees"
25
+ chartData={chartData}
26
+ id="bar-default"
27
+ legend
28
+ title="Bar Graph with Negative Numbers"
29
+ xAxisCategories={['Jan', 'Feb', 'Mar', 'Apr', 'May']}
30
+ {...props}
31
+ />
32
+ </div>
33
+ )
34
+
35
+ export default BarGraphStacked
@@ -0,0 +1,26 @@
1
+ <% data = [{
2
+ name: 'Number of Installations',
3
+ data: [1475,200,3000,654,656]
4
+ }, {
5
+ type: 'spline',
6
+ name: 'Percentage',
7
+ data: [48, 70, 25, 55, 72],
8
+ color: '#F9BB00',
9
+ yAxis: 1
10
+ }] %>
11
+
12
+ <% axis_titles = [{name: "Number of Installations"}, {name: "Percentage"}] %>
13
+
14
+ <% axis_formats = [{format: ""}, {format: "{value}%"}] %>
15
+
16
+ <%= pb_rails("bar_graph", props: {
17
+ axis_format: axis_formats,
18
+ axis_title: axis_titles,
19
+ chart_data: data,
20
+ id: "bar-spline",
21
+ y_axis_min: 0,
22
+ x_axis_categories:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
23
+ subtitle: 'Source: thesolarfoundation.com',
24
+ title: 'Bar Graph with Secondary Y-axis',
25
+ legend: true,
26
+ }) %>
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+
3
+ import BarGraph from '../_bar_graph'
4
+
5
+ const chartData = [{
6
+ name: 'Number of Installations',
7
+ data: [1475, 200, 3000, 654, 656],
8
+ }, {
9
+ type: 'spline',
10
+ name: 'Percentage',
11
+ data: [48, 70, 25, 55, 72],
12
+ color: '#F9BB00',
13
+ yAxis: 1
14
+ }]
15
+
16
+ const axisTitles = [ {name: "Number of Installations"}, {name: "Percentage"}]
17
+
18
+ const axisFormats = [{format: ""}, {format: "{value}%"}]
19
+
20
+ const BarGraphSecondaryYAxis= (props) => (
21
+ <div>
22
+ <BarGraph
23
+ axisFormat={axisFormats}
24
+ axisTitle={axisTitles}
25
+ chartData={chartData}
26
+ id="bar-spline"
27
+ legend
28
+ title="Bar Graph with Secondary Y-axis"
29
+ xAxisCategories={['Jan', 'Feb', 'Mar', 'Apr', 'May']}
30
+ yAxisMin={0}
31
+ {...props}
32
+ />
33
+ </div>
34
+ )
35
+
36
+ export default BarGraphSecondaryYAxis
@@ -0,0 +1,3 @@
1
+ Optionally add a second yAxis to support secondary datasets (e.x., a spline) by passing` yAxis: 1` to the second node of your `chartData` array.
2
+
3
+ To customize the format and/or title of your secondary yAxis, pass your desired values as arrays through the `axisFormat` and `axisTitle` props, respectively.
@@ -0,0 +1,22 @@
1
+ <% data = [{
2
+ name: 'Installation',
3
+ data: [1475, 200, 3000, 654, 656],
4
+ },
5
+ {
6
+ name: 'Manufacturing',
7
+ data: [1270, 800, 200, 454, 956],
8
+ }, {
9
+ name: 'Sales & Distribution',
10
+ data: [975, 1600, 1500, 924, 500],
11
+ }] %>
12
+
13
+ <%= pb_rails("bar_graph", props: {
14
+ axis_title: 'Number of Employees',
15
+ chart_data: data,
16
+ id: "bar-default",
17
+ y_axis_min: 0,
18
+ x_axis_categories:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
19
+ stacking: "normal",
20
+ title: 'Bar Graph with Stacked Columns',
21
+ legend: true,
22
+ }) %>
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+
3
+ import BarGraph from '../_bar_graph'
4
+
5
+ const chartData = [{
6
+ name: 'Installation',
7
+ data: [1475, 200, 3000, 654, 656],
8
+ },
9
+ {
10
+ name: 'Manufacturing',
11
+ data: [1270, 800, 200, 454, 956],
12
+ }, {
13
+ name: 'Sales & Distribution',
14
+ data: [975, 1600, 1500, 924, 500],
15
+ }]
16
+
17
+
18
+ const BarGraphStacked = (props) => (
19
+ <div>
20
+ <BarGraph
21
+ axisTitle="Number Of Employees"
22
+ chartData={chartData}
23
+ id="bar-default"
24
+ legend
25
+ stacking="normal"
26
+ title="Bar Graph with Stacked Columns"
27
+ xAxisCategories={['Jan', 'Feb', 'Mar', 'Apr', 'May']}
28
+ yAxisMin={0}
29
+ {...props}
30
+ />
31
+ </div>
32
+ )
33
+
34
+ export default BarGraphStacked
@@ -0,0 +1 @@
1
+ The `stacking` prop can be used for a stacked bar graph. The prop allows for `normal` or `percent` as options.
@@ -9,6 +9,9 @@ examples:
9
9
  - bar_graph_spline: Spline
10
10
  - bar_graph_colors: Color Overrides
11
11
  - bar_graph_custom: Custom Overrides
12
+ - bar_graph_stacked: Stacked
13
+ - bar_graph_negative_numbers: Negative Numbers
14
+ - bar_graph_secondary_y_axis: Secondary Y-Axis
12
15
 
13
16
 
14
17
  react:
@@ -20,3 +23,6 @@ examples:
20
23
  - bar_graph_spline: Spline
21
24
  - bar_graph_colors: Color Overrides
22
25
  - bar_graph_custom: Custom Overrides
26
+ - bar_graph_stacked: Stacked
27
+ - bar_graph_negative_numbers: Negative Numbers
28
+ - bar_graph_secondary_y_axis: Secondary Y-Axis
@@ -6,3 +6,6 @@ export { default as BarGraphHeight } from './_bar_graph_height.jsx'
6
6
  export { default as BarGraphSpline } from './_bar_graph_spline.jsx'
7
7
  export { default as BarGraphColors } from './_bar_graph_colors.jsx'
8
8
  export { default as BarGraphCustom } from './_bar_graph_custom.jsx'
9
+ export { default as BarGraphStacked } from './_bar_graph_stacked.jsx'
10
+ export { default as BarGraphNegativeNumbers } from './_bar_graph_negative_numbers.jsx'
11
+ export { default as BarGraphSecondaryYAxis } from './_bar_graph_secondary_y_axis.jsx'
@@ -19,7 +19,7 @@
19
19
  }
20
20
  }
21
21
  b, strong {
22
- @include pb_title_4
22
+ font-weight: $bold;
23
23
  }
24
24
 
25
25
  a {
@@ -29,8 +29,8 @@
29
29
  }
30
30
  }
31
31
 
32
- em {
33
- font-weight: $bold;
32
+ em, i {
33
+ font-style: italic;
34
34
  }
35
35
 
36
36
  small {
@@ -0,0 +1,92 @@
1
+ @import "../tokens/colors";
2
+ @import "../tokens/spacing";
3
+ @import "../tokens/typography";
4
+ @import "../tokens/border_radius";
5
+ @import "../tokens/shadows";
6
+ @import "../tokens/positioning";
7
+ @import "../pb_body/body_mixins";
8
+
9
+ .pb_dropdown {
10
+ .dropdown_wrapper {
11
+ position: relative;
12
+ .dropdown_trigger_wrapper {
13
+ @include pb_body;
14
+ border: 1px solid $border_light;
15
+ background-color: $white;
16
+ &:hover {
17
+ background-color: rgba($focus_input_light, $opacity_5);
18
+ input {
19
+ background-color: rgba($focus_input_light, $opacity_5);
20
+ }
21
+ }
22
+
23
+ .dropdown_input {
24
+ @include pb_body;
25
+ border: unset;
26
+ border-radius: $border_rad_heavier;
27
+ padding: 0;
28
+
29
+ &:focus-visible {
30
+ outline: none;
31
+ }
32
+ }
33
+ &:focus {
34
+ box-shadow: 0px 0px 0 1px $primary;
35
+ outline: unset;
36
+ transition: box-shadow .15s ease-in-out;
37
+ }
38
+ }
39
+
40
+ .dropdown_trigger_wrapper_focus {
41
+ box-shadow: 0px 0px 0 1px $primary;
42
+ transition: box-shadow .10s ease-in-out;
43
+ }
44
+
45
+ .pb_dropdown_container {
46
+ background-color: $white;
47
+ overflow:hidden;
48
+ box-shadow: $shadow_deep;
49
+ border-radius: $border_rad_heavier;
50
+ border: 1px solid $border_light;
51
+ margin-top: $space_xs;
52
+ position: absolute;
53
+ z-index: $z_1;
54
+ width: 100%;
55
+ transition: opacity .25s ease-in-out;
56
+
57
+ .pb_dropdown_option {
58
+ :hover {
59
+ background-color: $border_light;
60
+ }
61
+ }
62
+
63
+ .dropdown_option_focused {
64
+ background-color: $border_light;
65
+ }
66
+
67
+ .dropdown_option {
68
+ width: 100%;
69
+ }
70
+
71
+ .dropdown_option_list {
72
+ border-bottom: 1px solid $border_light;
73
+ }
74
+ .dropdown_option_selected {
75
+ background-color: $primary;
76
+ [class^="pb_body"], [class^="pb_title_kit"] {
77
+ color: $white !important;
78
+ }
79
+ :hover {
80
+ background-color: unset;
81
+ }
82
+ }
83
+ }
84
+ .close {
85
+ display: none;
86
+ }
87
+
88
+ .open {
89
+ display: block;
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,152 @@
1
+ import React, {useState, useRef, useEffect} from 'react'
2
+ import classnames from 'classnames'
3
+ import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
4
+ import { globalProps } from '../utilities/globalProps'
5
+
6
+ import Body from '../pb_body/_body'
7
+
8
+ import DropdownContainer from './subcomponents/DropdownContainer'
9
+ import DropdownOption from './subcomponents/DropdownOption'
10
+ import DropdownTrigger from './subcomponents/DropdownTrigger'
11
+ import DropdownContext from './context'
12
+ import useDropdown from './hooks/useDropdown'
13
+ import { GenericObject } from '../types'
14
+
15
+ type DropdownProps = {
16
+ aria?: { [key: string]: string },
17
+ className?: string,
18
+ data?: { [key: string]: string },
19
+ id?: string,
20
+ children?: React.ReactChild[] | React.ReactChild,
21
+ options?: GenericObject,
22
+ onSelect?: (arg:GenericObject) => null
23
+ }
24
+
25
+ const Dropdown = (props: DropdownProps) => {
26
+ const {
27
+ aria = {},
28
+ children,
29
+ className,
30
+ data = {},
31
+ id,
32
+ options,
33
+ onSelect
34
+ } = props
35
+
36
+ const ariaProps = buildAriaProps(aria)
37
+ const dataProps = buildDataProps(data)
38
+ const classes = classnames(buildCss('pb_dropdown'), globalProps(props), className)
39
+
40
+ const [ isDropDownClosed, setIsDropDownClosed, toggleDropdown ] = useDropdown()
41
+
42
+ const [filterItem, setFilterItem] = useState("");
43
+ const [selected, setSelected] = useState({});
44
+ const [isInputFocused, setIsInputFocused] = useState(false);
45
+ //state for keyboard events
46
+ const [focusedOptionIndex, setFocusedOptionIndex] = useState(-1);
47
+
48
+ const dropdownRef = useRef(null);
49
+ const inputRef = useRef(null);
50
+
51
+ // useEffect to handle clicks outside the dropdown
52
+ useEffect(() => {
53
+ const handleClickOutside = (event: any) => {
54
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
55
+ setIsDropDownClosed(true);
56
+ setIsInputFocused(false)
57
+ }
58
+ };
59
+ window.addEventListener("click", handleClickOutside);
60
+ return () => {
61
+ window.removeEventListener("click", handleClickOutside);
62
+ };
63
+ }, []);
64
+
65
+
66
+ const handleChange = (e: any) => {
67
+ setFilterItem(e.target.value);
68
+ setIsDropDownClosed(false);
69
+ };
70
+
71
+ const handleOptionClick = (selectedItem: GenericObject) => {
72
+ setSelected(selectedItem);
73
+ setFilterItem("");
74
+ setIsDropDownClosed(true);
75
+ onSelect(selectedItem);
76
+ };
77
+
78
+
79
+ const handleWrapperClick = () => {
80
+ inputRef.current.focus();
81
+ toggleDropdown();
82
+ };
83
+
84
+ const handleBackspace = () => {
85
+ setSelected({})
86
+ onSelect(null)
87
+ setFocusedOptionIndex(-1)
88
+ }
89
+
90
+ const filteredOptions = options?.filter((option: GenericObject) =>
91
+ option.label.toLowerCase().includes(filterItem.toLowerCase())
92
+ );
93
+
94
+ return (
95
+ <div
96
+ {...ariaProps}
97
+ {...dataProps}
98
+ className={classes}
99
+ id={id}
100
+ >
101
+ <DropdownContext.Provider
102
+ value={{
103
+ filteredOptions,
104
+ filterItem,
105
+ focusedOptionIndex,
106
+ handleBackspace,
107
+ handleChange,
108
+ handleOptionClick,
109
+ handleWrapperClick,
110
+ inputRef,
111
+ isInputFocused,
112
+ options,
113
+ selected,
114
+ setFocusedOptionIndex,
115
+ setIsInputFocused,
116
+ setSelected,
117
+ isDropDownClosed,
118
+ setIsDropDownClosed,
119
+ toggleDropdown
120
+
121
+ }}
122
+ >
123
+ <div className="dropdown_wrapper"
124
+ ref={dropdownRef}
125
+ >
126
+ {children ? (
127
+ children
128
+ ) : (
129
+ <>
130
+ <DropdownTrigger />
131
+ <DropdownContainer>
132
+ {options && options?.map((option: GenericObject) => (
133
+ <Dropdown.Option key={option.id}
134
+ option={option}
135
+ >
136
+ <Body text={option.label}/>
137
+ </Dropdown.Option>
138
+ ))}
139
+ </DropdownContainer>
140
+ </>
141
+ )}
142
+ </div>
143
+ </DropdownContext.Provider>
144
+ </div>
145
+ )
146
+ }
147
+
148
+ Dropdown.Option = DropdownOption;
149
+ Dropdown.Trigger = DropdownTrigger;
150
+ Dropdown.Container = DropdownContainer;
151
+
152
+ export default Dropdown
@@ -0,0 +1,5 @@
1
+ import { createContext } from "react";
2
+
3
+ const DropdownContext = createContext<any>({});
4
+
5
+ export default DropdownContext;