playbook_ui 13.23.0.pre.alpha.PLAY1284investigation2657 β†’ 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 (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"