playbook_ui 13.23.0.pre.alpha.PLAY1284investigation2657 β†’ 13.24.0.pre.alpha.PBNTR261NewKitDropdown2681

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 (34) 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_bar_graph/_bar_graph.tsx +16 -2
  5. data/app/pb_kits/playbook/pb_bar_graph/bar_graph.rb +2 -0
  6. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_negative_numbers.html.erb +23 -0
  7. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_negative_numbers.jsx +35 -0
  8. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.html.erb +22 -0
  9. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.jsx +34 -0
  10. data/app/pb_kits/playbook/pb_bar_graph/docs/_bar_graph_stacked.md +1 -0
  11. data/app/pb_kits/playbook/pb_bar_graph/docs/example.yml +4 -0
  12. data/app/pb_kits/playbook/pb_bar_graph/docs/index.js +2 -0
  13. data/app/pb_kits/playbook/pb_dropdown/_dropdown.scss +92 -0
  14. data/app/pb_kits/playbook/pb_dropdown/_dropdown.tsx +152 -0
  15. data/app/pb_kits/playbook/pb_dropdown/context/index.tsx +5 -0
  16. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_default.jsx +53 -0
  17. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_display.jsx +104 -0
  18. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_options.jsx +69 -0
  19. data/app/pb_kits/playbook/pb_dropdown/docs/_dropdown_with_custom_trigger.jsx +78 -0
  20. data/app/pb_kits/playbook/pb_dropdown/docs/example.yml +9 -0
  21. data/app/pb_kits/playbook/pb_dropdown/docs/index.js +4 -0
  22. data/app/pb_kits/playbook/pb_dropdown/dropdown.test.jsx +17 -0
  23. data/app/pb_kits/playbook/pb_dropdown/hooks/useDropdown.tsx +17 -0
  24. data/app/pb_kits/playbook/pb_dropdown/hooks/useHandleOnKeydown.tsx +53 -0
  25. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownContainer.tsx +95 -0
  26. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownOption.tsx +91 -0
  27. data/app/pb_kits/playbook/pb_dropdown/subcomponents/DropdownTrigger.tsx +118 -0
  28. data/app/pb_kits/playbook/pb_list/_list_item.tsx +2 -2
  29. data/app/pb_kits/playbook/playbook-doc.js +2 -0
  30. data/app/pb_kits/playbook/tokens/_colors.scss +1 -1
  31. data/dist/menu.yml +5 -1
  32. data/dist/playbook-rails.js +6 -6
  33. data/lib/playbook/version.rb +2 -2
  34. metadata +22 -2
@@ -0,0 +1,69 @@
1
+ import React, { useState } from 'react'
2
+ import { Dropdown, Icon, Body, FlexItem, Flex } from '../..'
3
+
4
+ const DropdownWithCustomOptions = (props) => {
5
+ // eslint-disable-next-line no-unused-vars
6
+ const [selectedOption, setSelectedOption] = useState();
7
+
8
+ const options = [
9
+ {
10
+ label: "United States",
11
+ value: "United States",
12
+ areaCode: "+1",
13
+ icon: "πŸ‡ΊπŸ‡Έ",
14
+ id: "United-states"
15
+ },
16
+ {
17
+ label: "Ukraine",
18
+ value: "Ukraine",
19
+ areaCode: "+380",
20
+ icon: "πŸ‡ΊπŸ‡¦",
21
+ id: "ukraine"
22
+ },
23
+ {
24
+ label: "Pakistan",
25
+ value: "Pakistan",
26
+ areaCode: "+92",
27
+ icon: "πŸ‡΅πŸ‡°",
28
+ id: "pakistan"
29
+ }
30
+ ];
31
+
32
+
33
+ return (
34
+ <div>
35
+ <Dropdown
36
+ onSelect={(selectedItem) => setSelectedOption(selectedItem)}
37
+ options={options}
38
+ {...props}
39
+ >
40
+ <Dropdown.Trigger/>
41
+ <Dropdown.Container>
42
+ {options.map((option) => (
43
+ <Dropdown.Option key={option.id}
44
+ option={option}
45
+ >
46
+ <>
47
+ <FlexItem>
48
+ <Flex>
49
+ <Icon icon={option.icon}
50
+ paddingRight="xs"
51
+ />
52
+ <Body text={option.label} />
53
+ </Flex>
54
+ </FlexItem>
55
+ <FlexItem>
56
+ <Body color="light"
57
+ text={option.areaCode}
58
+ />
59
+ </FlexItem>
60
+ </>
61
+ </Dropdown.Option>
62
+ ))}
63
+ </Dropdown.Container>
64
+ </Dropdown>
65
+ </div>
66
+ )
67
+ }
68
+
69
+ export default DropdownWithCustomOptions
@@ -0,0 +1,78 @@
1
+ import React, { useState } from 'react'
2
+ import { Dropdown, Icon, Body, FlexItem, Flex, IconCircle } from '../..'
3
+
4
+ const DropdownWithCustomTrigger = (props) => {
5
+
6
+ const [selectedOption, setSelectedOption] = useState();
7
+
8
+ const options = [
9
+ {
10
+ label: "United States",
11
+ value: "United States",
12
+ areaCode: "+1",
13
+ icon: "πŸ‡ΊπŸ‡Έ",
14
+ id: "United-states"
15
+ },
16
+ {
17
+ label: "Ukraine",
18
+ value: "Ukraine",
19
+ areaCode: "+380",
20
+ icon: "πŸ‡ΊπŸ‡¦",
21
+ id: "ukraine"
22
+ },
23
+ {
24
+ label: "Pakistan",
25
+ value: "Pakistan",
26
+ areaCode: "+92",
27
+ icon: "πŸ‡΅πŸ‡°",
28
+ id: "pakistan"
29
+ }
30
+ ];
31
+
32
+
33
+ return (
34
+ <div>
35
+ <Dropdown
36
+ onSelect={(selectedItem) => setSelectedOption(selectedItem)}
37
+ options={options}
38
+ {...props}
39
+ >
40
+ <Dropdown.Trigger>
41
+ <div key={selectedOption ? selectedOption.icon : "flag"}>
42
+ <IconCircle
43
+ cursor="pointer"
44
+ icon={selectedOption ? selectedOption.icon : "flag"}
45
+ variant="royal"
46
+ />
47
+ </div>
48
+ </Dropdown.Trigger>
49
+
50
+ <Dropdown.Container>
51
+ {options.map((option) => (
52
+ <Dropdown.Option key={option.id}
53
+ option={option}
54
+ >
55
+ <>
56
+ <FlexItem>
57
+ <Flex>
58
+ <Icon icon={option.icon}
59
+ paddingRight="xs"
60
+ />
61
+ <Body text={option.label} />
62
+ </Flex>
63
+ </FlexItem>
64
+ <FlexItem>
65
+ <Body color="light"
66
+ text={option.areaCode}
67
+ />
68
+ </FlexItem>
69
+ </>
70
+ </Dropdown.Option>
71
+ ))}
72
+ </Dropdown.Container>
73
+ </Dropdown>
74
+ </div>
75
+ )
76
+ }
77
+
78
+ export default DropdownWithCustomTrigger
@@ -0,0 +1,9 @@
1
+ examples:
2
+
3
+
4
+ react:
5
+ - dropdown_default: Default
6
+ - dropdown_with_custom_options: Custom Options
7
+ - dropdown_with_custom_display: Custom Display
8
+ - dropdown_with_custom_trigger: Custom Trigger
9
+
@@ -0,0 +1,4 @@
1
+ export { default as DropdownDefault } from './_dropdown_default.jsx'
2
+ export { default as DropdownWithCustomDisplay } from './_dropdown_with_custom_display.jsx'
3
+ export { default as DropdownWithCustomOptions } from './_dropdown_with_custom_options.jsx'
4
+ export { default as DropdownWithCustomTrigger } from './_dropdown_with_custom_trigger.jsx'
@@ -0,0 +1,17 @@
1
+ import { renderKit } from '../utilities/test-utils'
2
+
3
+ import { Dropdown } from '../'
4
+
5
+ /* See these resources for more testing info:
6
+ - https://github.com/testing-library/jest-dom#usage for useage and examples
7
+ - https://jestjs.io/docs/en/using-matchers
8
+ */
9
+
10
+ test('generated scaffold test - update me', () => {
11
+ const props = {
12
+ data: { testid: 'default' }
13
+ }
14
+
15
+ const kit = renderKit(Dropdown , props)
16
+ expect(kit).toBeInTheDocument()
17
+ })
@@ -0,0 +1,17 @@
1
+ import {useState} from 'react';
2
+
3
+
4
+ const useDropdown = (initial=true) => {
5
+
6
+ const [isDropDownClosed, setIsDropDownClosed] = useState(initial);
7
+
8
+ const toggleDropdown = () => setIsDropDownClosed(!isDropDownClosed);
9
+
10
+ return [
11
+ isDropDownClosed,
12
+ setIsDropDownClosed as any,
13
+ toggleDropdown
14
+ ]
15
+ }
16
+
17
+ export default useDropdown
@@ -0,0 +1,53 @@
1
+ import React, { useContext } from "react";
2
+ import DropdownContext from "../context";
3
+
4
+
5
+ export const useHandleOnKeyDown = () => {
6
+
7
+ const {
8
+ focusedOptionIndex,
9
+ filteredOptions,
10
+ setFocusedOptionIndex,
11
+ handleOptionClick,
12
+ setIsDropDownClosed,
13
+ handleBackspace,
14
+ selected
15
+ }= useContext(DropdownContext)
16
+
17
+ return (e: React.KeyboardEvent) => {
18
+ switch (e.key) {
19
+ case "Backspace":
20
+ case "Delete":
21
+ handleBackspace();
22
+ break;
23
+ case "ArrowDown": {
24
+ e.preventDefault();
25
+ setIsDropDownClosed(false);
26
+ const nextIndex = (focusedOptionIndex + 1) % filteredOptions.length;
27
+ setFocusedOptionIndex(nextIndex);
28
+ break;
29
+ }
30
+ case "ArrowUp": {
31
+ e.preventDefault();
32
+ const nextIndexUp =
33
+ (focusedOptionIndex - 1 + filteredOptions.length) %
34
+ filteredOptions.length;
35
+ setFocusedOptionIndex(nextIndexUp);
36
+ break;
37
+ }
38
+ case "Enter":
39
+ if (focusedOptionIndex !== -1) {
40
+ e.preventDefault();
41
+ handleOptionClick(filteredOptions[focusedOptionIndex]);
42
+ setFocusedOptionIndex(-1)
43
+ }
44
+ break;
45
+ default:
46
+ if (selected && selected.label) {
47
+ e.preventDefault();
48
+ handleBackspace();
49
+ }
50
+ break;
51
+ }
52
+ }
53
+ };
@@ -0,0 +1,95 @@
1
+ import React, { useContext } from "react";
2
+ import classnames from "classnames";
3
+ import {
4
+ buildAriaProps,
5
+ buildCss,
6
+ buildDataProps,
7
+ } from "../../utilities/props";
8
+ import { globalProps } from "../../utilities/globalProps";
9
+
10
+ import DropdownContext from "../context";
11
+
12
+ import List from "../../pb_list/_list";
13
+ import ListItem from "../../pb_list/_list_item";
14
+ import TextInput from "../../pb_text_input/_text_input";
15
+ import Body from "../../pb_body/_body";
16
+
17
+ type DropdownContainerProps = {
18
+ aria?: { [key: string]: string };
19
+ className?: string;
20
+ children?: React.ReactChild[] | React.ReactChild;
21
+ data?: { [key: string]: string };
22
+ id?: string;
23
+ searchbar?: boolean;
24
+ };
25
+
26
+ const DropdownContainer = (props: DropdownContainerProps) => {
27
+ const {
28
+ aria = {},
29
+ className,
30
+ children,
31
+ data = {},
32
+ id,
33
+ searchbar = false,
34
+ } = props;
35
+
36
+ const {
37
+ isDropDownClosed,
38
+ handleChange,
39
+ filterItem,
40
+ filteredOptions,
41
+ inputRef,
42
+ setFocusedOptionIndex,
43
+ } = useContext(DropdownContext);
44
+
45
+ const ariaProps = buildAriaProps(aria);
46
+ const dataProps = buildDataProps(data);
47
+ const classes = classnames(
48
+ buildCss("pb_dropdown_container"),
49
+ `${isDropDownClosed ? "close" : "open"}`,
50
+ globalProps(props),
51
+ className
52
+ );
53
+
54
+ return (
55
+ <div {...ariaProps}
56
+ {...dataProps}
57
+ className={classes}
58
+ id={id}
59
+ onMouseEnter={() => setFocusedOptionIndex(-1)}
60
+ >
61
+ {searchbar && (
62
+ <TextInput paddingTop="xs"
63
+ paddingX="xs"
64
+ >
65
+ <input
66
+ onChange={handleChange}
67
+ placeholder="Select..."
68
+ ref={inputRef}
69
+ value={filterItem}
70
+ />
71
+ </TextInput>
72
+ )}
73
+ <List>{
74
+ filteredOptions?.length === 0 ? (
75
+ <ListItem
76
+ display="flex"
77
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
78
+ // @ts-ignore
79
+ justifyContent="center"
80
+ padding="xs"
81
+ >
82
+ <Body color="light"
83
+ text="no option"
84
+ />
85
+ </ListItem>
86
+ ): (
87
+ children
88
+ )
89
+ }
90
+ </List>
91
+ </div>
92
+ );
93
+ };
94
+
95
+ export default DropdownContainer;
@@ -0,0 +1,91 @@
1
+ import React, { useContext } from "react";
2
+ import classnames from "classnames";
3
+ import {
4
+ buildAriaProps,
5
+ buildCss,
6
+ buildDataProps,
7
+ } from "../../utilities/props";
8
+ import { globalProps } from "../../utilities/globalProps";
9
+
10
+ import DropdownContext from "../context";
11
+
12
+ import Flex from "../../pb_flex/_flex";
13
+ import Body from "../../pb_body/_body";
14
+ import ListItem from "../../pb_list/_list_item";
15
+ import { GenericObject } from "../../types";
16
+
17
+ type DropdownOptionProps = {
18
+ aria?: { [key: string]: string };
19
+ className?: string;
20
+ children?: React.ReactChild[] | React.ReactChild;
21
+ data?: { [key: string]: string };
22
+ id?: string;
23
+ option?: GenericObject;
24
+ key?: string;
25
+ };
26
+
27
+ const DropdownOption = (props: DropdownOptionProps) => {
28
+ const { aria = {}, className, children, data = {}, id, option, key } = props;
29
+
30
+ const { handleOptionClick, selected, filterItem, filteredOptions, focusedOptionIndex } =
31
+ useContext(DropdownContext);
32
+
33
+ const isItemMatchingFilter = (option: GenericObject) =>
34
+ option?.label.toLowerCase().includes(filterItem.toLowerCase());
35
+
36
+ if (!isItemMatchingFilter(option)) {
37
+ return null;
38
+ }
39
+ const isFocused = focusedOptionIndex >= 0 && filteredOptions[focusedOptionIndex].label === option.label
40
+ const focusedClass = isFocused && "dropdown_option_focused"
41
+
42
+ const selectedClass = `${
43
+ selected.label === option.label
44
+ ? "dropdown_option_selected"
45
+ : "dropdown_option_list"
46
+ }`
47
+ const ariaProps = buildAriaProps(aria);
48
+ const dataProps = buildDataProps(data);
49
+ const classes = classnames(
50
+ buildCss("pb_dropdown_option"),
51
+ selectedClass,
52
+ focusedClass,
53
+ globalProps(props),
54
+ className
55
+ );
56
+
57
+ return (
58
+ <div {...ariaProps}
59
+ {...dataProps}
60
+ className={classes}
61
+ id={id}
62
+ key={key}
63
+ >
64
+ <ListItem
65
+ cursor="pointer"
66
+ data-name={option.value}
67
+ htmlOptions={{ onClick: () => handleOptionClick(option) }}
68
+ key={option.label}
69
+ padding="xs"
70
+ >
71
+ <Flex
72
+ align="center"
73
+ className="dropdown_option"
74
+ justify="between"
75
+ paddingX="sm"
76
+ paddingY="xxs"
77
+ >
78
+ {
79
+ children ? (
80
+ children
81
+ ) : (
82
+ <Body text={option.label}/>
83
+ )
84
+ }
85
+ </Flex>
86
+ </ListItem>
87
+ </div>
88
+ );
89
+ };
90
+
91
+ export default DropdownOption;
@@ -0,0 +1,118 @@
1
+ import React, { useContext } from "react";
2
+ import classnames from "classnames";
3
+ import {
4
+ buildAriaProps,
5
+ buildCss,
6
+ buildDataProps,
7
+ } from "../../utilities/props";
8
+ import { globalProps } from "../../utilities/globalProps";
9
+ import { useHandleOnKeyDown } from "../hooks/useHandleOnKeydown";
10
+
11
+ import DropdownContext from "../context";
12
+
13
+ import Body from "../../pb_body/_body";
14
+ import Icon from "../../pb_icon/_icon";
15
+ import Flex from "../../pb_flex/_flex";
16
+ import FlexItem from "../../pb_flex/_flex_item";
17
+
18
+ type DropdownTriggerProps = {
19
+ aria?: { [key: string]: string };
20
+ children?: React.ReactChild[] | React.ReactChild;
21
+ className?: string;
22
+ customDisplay?: React.ReactChild[] | React.ReactChild;
23
+ data?: { [key: string]: string };
24
+ id?: string;
25
+ };
26
+
27
+ const DropdownTrigger = (props: DropdownTriggerProps) => {
28
+ const { aria = {}, className, children, customDisplay, data = {}, id } = props;
29
+
30
+ const {
31
+ handleWrapperClick,
32
+ selected,
33
+ filterItem,
34
+ handleChange,
35
+ toggleDropdown,
36
+ isDropDownClosed,
37
+ inputRef,
38
+ isInputFocused,
39
+ setIsInputFocused
40
+ } = useContext(DropdownContext);
41
+
42
+ const handleKeyDown = useHandleOnKeyDown();
43
+
44
+ const ariaProps = buildAriaProps(aria);
45
+ const dataProps = buildDataProps(data);
46
+ const classes = classnames(
47
+ buildCss("pb_dropdown_trigger"),
48
+ globalProps(props),
49
+ className
50
+ );
51
+
52
+ return (
53
+ <div {...ariaProps}
54
+ {...dataProps}
55
+ className={classes}
56
+ id={id}
57
+ >
58
+ {children ? (
59
+ <div
60
+ onClick={() => toggleDropdown()}
61
+ style={{ display: "inline-block" }}
62
+ >
63
+ {children}
64
+ </div>
65
+ ) : (
66
+ <>
67
+ <Flex align="center"
68
+ borderRadius="lg"
69
+ className={`dropdown_trigger_wrapper ${isInputFocused && 'dropdown_trigger_wrapper_focus'}`}
70
+ cursor="text"
71
+ htmlOptions={{ onClick: () => handleWrapperClick(), tabIndex:"0" }}
72
+ justify="between"
73
+ paddingX="sm"
74
+ paddingY="xs"
75
+ >
76
+ <FlexItem>
77
+ <Flex align="center">
78
+ {customDisplay ? (
79
+ <Flex align="center">
80
+ {customDisplay}
81
+ <Body paddingLeft="xs">
82
+ <b>{selected.label}</b>
83
+ </Body>
84
+ </Flex>
85
+ ) : (
86
+ selected.label && <Body text={selected.label} />
87
+ )
88
+ }
89
+ <input
90
+ className="dropdown_input"
91
+ onChange={handleChange}
92
+ onClick={() => toggleDropdown()}
93
+ onFocus={() => setIsInputFocused(true)}
94
+ onKeyDown={handleKeyDown}
95
+ placeholder={selected.label ? "" : "Select..."}
96
+ ref={inputRef}
97
+ value={filterItem}
98
+ />
99
+ </Flex>
100
+ </FlexItem>
101
+ <FlexItem>
102
+ <Body color="light"
103
+ key={`${isDropDownClosed ? "chevron-down" : 'chevron-up'}`}
104
+ >
105
+ <Icon cursor="pointer"
106
+ icon={`${isDropDownClosed ? "chevron-down" : 'chevron-up'}`}
107
+ size="sm"
108
+ />
109
+ </Body>
110
+ </FlexItem>
111
+ </Flex>
112
+ </>
113
+ )}
114
+ </div>
115
+ );
116
+ };
117
+
118
+ export default DropdownTrigger;
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import classnames from 'classnames'
3
3
  import { buildAriaProps, buildCss, buildDataProps, buildHtmlProps } from '../utilities/props'
4
- import { globalProps } from '../utilities/globalProps'
4
+ import { globalProps, GlobalProps } from '../utilities/globalProps'
5
5
 
6
6
  type ListItemProps = {
7
7
  aria?: { [key: string]: string },
@@ -11,7 +11,7 @@ type ListItemProps = {
11
11
  htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
12
12
  id?: string,
13
13
  tabIndex?: number,
14
- }
14
+ } & GlobalProps
15
15
 
16
16
  const ListItem = (props: ListItemProps) => {
17
17
  const {
@@ -34,6 +34,7 @@ import * as DateYearStacked from 'pb_date_year_stacked/docs'
34
34
  import * as Detail from 'pb_detail/docs'
35
35
  import * as Dialog from 'pb_dialog/docs'
36
36
  import * as DistributionBarDocs from 'pb_distribution_bar/docs'
37
+ import * as Dropdown from 'pb_dropdown/docs'
37
38
  import * as FileUpload from 'pb_file_upload/docs'
38
39
  import * as Filter from 'pb_filter/docs'
39
40
  import * as FixedConfirmationToast from 'pb_fixed_confirmation_toast/docs'
@@ -136,6 +137,7 @@ WebpackerReact.registerComponents({
136
137
  ...Detail,
137
138
  ...Dialog,
138
139
  ...DistributionBarDocs,
140
+ ...Dropdown,
139
141
  ...FileUpload,
140
142
  ...Filter,
141
143
  ...FixedConfirmationToast,
@@ -213,7 +213,7 @@ $status_color_text: (
213
213
  warning: darken($warning, 10%),
214
214
  error: $error,
215
215
  info: $info,
216
- neutral: darken($neutral, 15%),
216
+ neutral: $text_lt_light,
217
217
  primary: $primary
218
218
  );
219
219
 
data/dist/menu.yml CHANGED
@@ -254,6 +254,10 @@ kits:
254
254
  platforms: *web
255
255
  description: Playbook's date picker is built using flatpickr, a vanilla js library. Common date picker use cases and features have been adapted into simple prop based configuration detailed in the docs below.
256
256
  status: "stable"
257
+ - name: dropdown
258
+ platforms: *react_only
259
+ description: ""
260
+ status: "beta"
257
261
  - name: "multi_level_select"
258
262
  platforms: *web
259
263
  description: The MultiLevelSelect kit renders a multi leveled select dropdown based on data from the user.
@@ -460,4 +464,4 @@ kits:
460
464
  - name: "user"
461
465
  platforms: *web
462
466
  description: This kit was created for having a systematic way of displaying users with avatar, titles, name and territory. This is a versatile kit with features than can be added to display more info.
463
- status: "stable"
467
+ status: "stable"