playbook_ui 12.5.0 → 12.6.0
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/_playbook.scss +2 -0
- data/app/pb_kits/playbook/data/menu.yml +2 -1
- data/app/pb_kits/playbook/index.js +1 -0
- data/app/pb_kits/playbook/pb_button/_button_mixins.scss +2 -2
- data/app/pb_kits/playbook/pb_circle_icon_button/_circle_icon_button.scss +1 -1
- data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.tsx +72 -0
- data/app/pb_kits/playbook/pb_filter/Filter/{FilterBackground.jsx → FilterBackground.tsx} +12 -14
- data/app/pb_kits/playbook/pb_filter/Filter/{FilterDouble.jsx → FilterDouble.tsx} +7 -8
- data/app/pb_kits/playbook/pb_filter/Filter/{FilterSingle.jsx → FilterSingle.tsx} +25 -25
- data/app/pb_kits/playbook/pb_filter/Filter/{FiltersPopover.jsx → FiltersPopover.tsx} +13 -11
- data/app/pb_kits/playbook/pb_filter/Filter/{ResultsCount.jsx → ResultsCount.tsx} +39 -14
- data/app/pb_kits/playbook/pb_filter/Filter/{SortMenu.jsx → SortMenu.tsx} +6 -6
- data/app/pb_kits/playbook/pb_filter/Filter/{index.jsx → index.tsx} +17 -10
- data/app/pb_kits/playbook/pb_filter/{_filter.jsx → _filter.tsx} +0 -2
- data/app/pb_kits/playbook/pb_list/_list.tsx +2 -2
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +86 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +81 -0
- data/app/pb_kits/playbook/pb_multi_level_select/_multi_select_helper.tsx +30 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.jsx +86 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_default.md +3 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/example.yml +6 -0
- data/app/pb_kits/playbook/pb_multi_level_select/docs/index.js +1 -0
- data/app/pb_kits/playbook/pb_multi_level_select/helper_functions.ts +60 -0
- data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.test.jsx +40 -0
- data/app/pb_kits/playbook/pb_person_contact/{_person_contact.jsx → _person_contact.tsx} +19 -22
- data/app/pb_kits/playbook/pb_person_contact/person_contact.test.js +112 -0
- data/app/pb_kits/playbook/pb_phone_number_input/_phone_number_input.tsx +61 -47
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_initial_country.html.erb +3 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_only_countries.html.erb +4 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/_phone_number_input_preferred_countries.html.erb +4 -0
- data/app/pb_kits/playbook/pb_phone_number_input/docs/example.yml +4 -1
- data/app/pb_kits/playbook/pb_phone_number_input/phone_number_input.html.erb +1 -15
- data/app/pb_kits/playbook/pb_popover/_popover.tsx +33 -32
- data/app/pb_kits/playbook/playbook-doc.js +2 -0
- data/app/pb_kits/playbook/tokens/_animation-curves.scss +30 -30
- data/app/pb_kits/playbook/tokens/_border_radius.scss +15 -16
- data/app/pb_kits/playbook/tokens/_colors.scss +3 -1
- data/app/pb_kits/playbook/tokens/_display.scss +6 -6
- data/app/pb_kits/playbook/tokens/_line_height.scss +7 -7
- data/app/pb_kits/playbook/tokens/_opacity.scss +10 -10
- data/app/pb_kits/playbook/tokens/_positioning.scss +11 -11
- data/app/pb_kits/playbook/tokens/_screen_sizes.scss +10 -10
- data/app/pb_kits/playbook/tokens/_shadows.scss +4 -4
- data/app/pb_kits/playbook/tokens/_spacing.scss +6 -6
- data/app/pb_kits/playbook/tokens/_transition.scss +3 -3
- data/app/pb_kits/playbook/tokens/_typography.scss +35 -46
- data/lib/playbook/version.rb +2 -2
- metadata +25 -12
- data/app/pb_kits/playbook/pb_filter/Filter/CurrentFilters.jsx +0 -76
@@ -0,0 +1,81 @@
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
2
|
+
import classnames from "classnames";
|
3
|
+
import { buildAriaProps, buildCss, buildDataProps } from "../utilities/props";
|
4
|
+
import { globalProps } from "../utilities/globalProps";
|
5
|
+
import { findItemById, checkIt, unCheckIt } from "./helper_functions";
|
6
|
+
import MultiSelectHelper from "./_multi_select_helper";
|
7
|
+
|
8
|
+
type MultiLevelSelectProps = {
|
9
|
+
aria?: { [key: string]: string };
|
10
|
+
className?: string;
|
11
|
+
data?: { [key: string]: string };
|
12
|
+
id?: string;
|
13
|
+
treeData?: { [key: string]: string }[];
|
14
|
+
onChange?: any;
|
15
|
+
onSelect?: (SelectedNodes: { [key: string]: any }) => void;
|
16
|
+
};
|
17
|
+
|
18
|
+
const MultiLevelSelect = (props: MultiLevelSelectProps) => {
|
19
|
+
const { aria = {}, className, data = {}, id, treeData, onSelect } = props;
|
20
|
+
|
21
|
+
const ariaProps = buildAriaProps(aria);
|
22
|
+
const dataProps = buildDataProps(data);
|
23
|
+
const classes = classnames(
|
24
|
+
buildCss("pb_multi_level_select"),
|
25
|
+
globalProps(props),
|
26
|
+
className
|
27
|
+
);
|
28
|
+
|
29
|
+
const [formattedData, setFormattedData] = useState(treeData);
|
30
|
+
const [selectedItems, setSelectedItems] = useState([]);
|
31
|
+
|
32
|
+
const onChange = (currentNode: { [key: string]: any }) => {
|
33
|
+
const updatedData = formattedData.map((item: any) => {
|
34
|
+
if (item.id === currentNode._id) {
|
35
|
+
if (currentNode.checked) {
|
36
|
+
checkIt(item, selectedItems, setSelectedItems);
|
37
|
+
} else {
|
38
|
+
unCheckIt(item, selectedItems, setSelectedItems);
|
39
|
+
}
|
40
|
+
} else if (item.children) {
|
41
|
+
const foundItem = findItemById(item.children, currentNode._id);
|
42
|
+
if (foundItem) {
|
43
|
+
if (currentNode.checked) {
|
44
|
+
checkIt(foundItem, selectedItems, setSelectedItems);
|
45
|
+
} else {
|
46
|
+
unCheckIt(foundItem, selectedItems, setSelectedItems);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
return item;
|
52
|
+
});
|
53
|
+
|
54
|
+
setFormattedData(updatedData);
|
55
|
+
};
|
56
|
+
|
57
|
+
useEffect(() => {
|
58
|
+
const selected = selectedItems.filter(
|
59
|
+
(item: { [key: string]: any }) => item.checked
|
60
|
+
);
|
61
|
+
//filter to remove duplicate items
|
62
|
+
const uniqueSelected = selected.filter(
|
63
|
+
(obj, index, self) => index === self.findIndex((t) => t.id === obj.id)
|
64
|
+
);
|
65
|
+
onSelect(uniqueSelected);
|
66
|
+
}, [selectedItems]);
|
67
|
+
|
68
|
+
return (
|
69
|
+
<div {...ariaProps} {...dataProps} className={classes} id={id}>
|
70
|
+
<MultiSelectHelper
|
71
|
+
treeData={formattedData}
|
72
|
+
id={id}
|
73
|
+
onChange={onChange}
|
74
|
+
onSelect={onSelect}
|
75
|
+
{...props}
|
76
|
+
/>
|
77
|
+
</div>
|
78
|
+
);
|
79
|
+
};
|
80
|
+
|
81
|
+
export default MultiLevelSelect;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React from "react"
|
2
|
+
import DropdownTreeSelect from "react-dropdown-tree-select"
|
3
|
+
import "react-dropdown-tree-select/dist/styles.css"
|
4
|
+
|
5
|
+
type HelperProps = {
|
6
|
+
id?: string
|
7
|
+
treeData?: { [key: string]: string }[]
|
8
|
+
onChange?: () => {}
|
9
|
+
|
10
|
+
}
|
11
|
+
|
12
|
+
const MultiSelectHelper = (props: HelperProps) => {
|
13
|
+
const { id, treeData, onChange } = props
|
14
|
+
|
15
|
+
|
16
|
+
return (
|
17
|
+
<DropdownTreeSelect
|
18
|
+
data={treeData}
|
19
|
+
id={id}
|
20
|
+
keepOpenOnSelect
|
21
|
+
keepTreeOnSearch
|
22
|
+
keepChildrenOnSearch
|
23
|
+
onChange={onChange}
|
24
|
+
texts={{ placeholder: "Select..." }}
|
25
|
+
mode='hierarchical'
|
26
|
+
/>
|
27
|
+
)
|
28
|
+
}
|
29
|
+
|
30
|
+
export default MultiSelectHelper
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import MultiLevelSelect from "../_multi_level_select";
|
3
|
+
|
4
|
+
const treeData = [
|
5
|
+
{
|
6
|
+
label: "Power Home Remodeling",
|
7
|
+
value: "Power Home Remodeling",
|
8
|
+
id: "powerhome1",
|
9
|
+
expanded: true,
|
10
|
+
children: [
|
11
|
+
{
|
12
|
+
label: "People",
|
13
|
+
value: "People",
|
14
|
+
id: "people1",
|
15
|
+
children: [
|
16
|
+
{
|
17
|
+
label: "Talent Acquisition",
|
18
|
+
value: "Talent Acquisition",
|
19
|
+
id: "talent1",
|
20
|
+
},
|
21
|
+
{
|
22
|
+
label: "Business Affairs",
|
23
|
+
value: "Business Affairs",
|
24
|
+
id: "business1",
|
25
|
+
children: [
|
26
|
+
{
|
27
|
+
label: "Initiatives",
|
28
|
+
value: "Initiatives",
|
29
|
+
id: "initiative1",
|
30
|
+
},
|
31
|
+
{
|
32
|
+
label: "Learning & Development",
|
33
|
+
value: "Learning & Development",
|
34
|
+
id: "development1",
|
35
|
+
},
|
36
|
+
],
|
37
|
+
},
|
38
|
+
{
|
39
|
+
label: "People Experience",
|
40
|
+
value: "People Experience",
|
41
|
+
id: "experience1",
|
42
|
+
},
|
43
|
+
],
|
44
|
+
},
|
45
|
+
{
|
46
|
+
label: "Contact Center",
|
47
|
+
value: "Contact Center",
|
48
|
+
id: "contact1",
|
49
|
+
children: [
|
50
|
+
{
|
51
|
+
label: "Appointment Management",
|
52
|
+
value: "Appointment Management",
|
53
|
+
id: "appointment1",
|
54
|
+
},
|
55
|
+
{
|
56
|
+
label: "Customer Service",
|
57
|
+
value: "Customer Service",
|
58
|
+
id: "customer1",
|
59
|
+
},
|
60
|
+
{
|
61
|
+
label: "Energy",
|
62
|
+
value: "Energy",
|
63
|
+
id: "energy1",
|
64
|
+
},
|
65
|
+
],
|
66
|
+
},
|
67
|
+
],
|
68
|
+
},
|
69
|
+
];
|
70
|
+
|
71
|
+
const MultiLevelSelectDefault = (props) => {
|
72
|
+
return (
|
73
|
+
<div>
|
74
|
+
<MultiLevelSelect
|
75
|
+
id="multiselect-default"
|
76
|
+
onSelect={(selectedNodes) =>
|
77
|
+
console.log("Selected Items", selectedNodes)
|
78
|
+
}
|
79
|
+
treeData={treeData}
|
80
|
+
{...props}
|
81
|
+
/>
|
82
|
+
</div>
|
83
|
+
);
|
84
|
+
};
|
85
|
+
|
86
|
+
export default MultiLevelSelectDefault;
|
@@ -0,0 +1,3 @@
|
|
1
|
+
The MultiLevelSelect kit renders a multi leveled select dropdown based on data from the user. `treeData` is a required prop that is expected to contain the data in the form of an array of objects. See code snippet for an example data array.
|
2
|
+
|
3
|
+
The `onSelect` prop returns an array of all checked items, irrespective of whether it is a parent, child or grandchild. Open the console on this example and check and uncheck checkboxes to see this is action!
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as MultiLevelSelectDefault } from './_multi_level_select_default.jsx'
|
@@ -0,0 +1,60 @@
|
|
1
|
+
export const findItemById = (
|
2
|
+
items: { [key: string]: any }[],
|
3
|
+
id: string
|
4
|
+
): any => {
|
5
|
+
for (const item of items) {
|
6
|
+
if (item.id === id) {
|
7
|
+
return item;
|
8
|
+
}
|
9
|
+
if (item.children) {
|
10
|
+
const found = findItemById(item.children, id);
|
11
|
+
if (found) {
|
12
|
+
return found;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
16
|
+
return null;
|
17
|
+
};
|
18
|
+
|
19
|
+
export const checkIt = (
|
20
|
+
foundItem: { [key: string]: any },
|
21
|
+
selectedItems: any[],
|
22
|
+
setSelectedItems: Function
|
23
|
+
) => {
|
24
|
+
if (!foundItem) {
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
|
28
|
+
foundItem.checked = true;
|
29
|
+
foundItem.expanded = true;
|
30
|
+
selectedItems.push(foundItem);
|
31
|
+
|
32
|
+
if (foundItem.children) {
|
33
|
+
foundItem.children.map((x: any) => {
|
34
|
+
checkIt(x, selectedItems, setSelectedItems);
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
setSelectedItems([...selectedItems]);
|
39
|
+
};
|
40
|
+
|
41
|
+
export const unCheckIt = (
|
42
|
+
foundItem: { [key: string]: any },
|
43
|
+
selectedItems: any,
|
44
|
+
setSelectedItems: any
|
45
|
+
) => {
|
46
|
+
if (!foundItem) {
|
47
|
+
return;
|
48
|
+
}
|
49
|
+
|
50
|
+
foundItem.checked = false;
|
51
|
+
const newSelectedItems = selectedItems.filter(
|
52
|
+
(item: any) => item.id !== foundItem.id
|
53
|
+
);
|
54
|
+
if (foundItem.children) {
|
55
|
+
foundItem.children.map((x: any) => {
|
56
|
+
unCheckIt(x, selectedItems, setSelectedItems);
|
57
|
+
});
|
58
|
+
}
|
59
|
+
setSelectedItems([...newSelectedItems]);
|
60
|
+
};
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { render, screen } from '../utilities/test-utils'
|
3
|
+
|
4
|
+
import { MultiLevelSelect } from '../'
|
5
|
+
|
6
|
+
const treeData = {
|
7
|
+
label: 'search me',
|
8
|
+
value: 'searchme',
|
9
|
+
id:'default1',
|
10
|
+
children: [
|
11
|
+
{
|
12
|
+
label: 'search me too',
|
13
|
+
value: 'searchmetoo',
|
14
|
+
id:'default2',
|
15
|
+
children: [
|
16
|
+
{
|
17
|
+
label: 'No one can get me',
|
18
|
+
value: 'anonymous',
|
19
|
+
id:'default2',
|
20
|
+
},
|
21
|
+
],
|
22
|
+
},
|
23
|
+
],
|
24
|
+
}
|
25
|
+
|
26
|
+
const testId = "multiselect-test"
|
27
|
+
test('should render custom class', () => {
|
28
|
+
render(
|
29
|
+
<MultiLevelSelect
|
30
|
+
className='custom-class'
|
31
|
+
data={{ testid: testId}}
|
32
|
+
onSelect={()=> console.log("hello")}
|
33
|
+
treeData={treeData}
|
34
|
+
/>
|
35
|
+
)
|
36
|
+
|
37
|
+
const kit = screen.getByTestId(testId)
|
38
|
+
expect(kit).toHaveClass('custom-class')
|
39
|
+
})
|
40
|
+
|
@@ -1,5 +1,3 @@
|
|
1
|
-
/* @flow */
|
2
|
-
|
3
1
|
import React from 'react'
|
4
2
|
import classnames from 'classnames'
|
5
3
|
|
@@ -17,14 +15,13 @@ type ContactItem = {
|
|
17
15
|
}
|
18
16
|
|
19
17
|
type PersonContactProps = {
|
20
|
-
aria?:
|
21
|
-
className?: string |
|
22
|
-
dark?: boolean,
|
18
|
+
aria?: { [key: string]: string },
|
19
|
+
className?: string | string[],
|
23
20
|
data?: object,
|
24
21
|
firstName: string,
|
25
22
|
id?: string,
|
26
23
|
lastName: string,
|
27
|
-
contacts?:
|
24
|
+
contacts?: ContactItem[],
|
28
25
|
}
|
29
26
|
|
30
27
|
const PersonContact = (props: PersonContactProps) => {
|
@@ -60,34 +57,34 @@ const PersonContact = (props: PersonContactProps) => {
|
|
60
57
|
|
61
58
|
return (
|
62
59
|
<div
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
{...ariaProps}
|
61
|
+
{...dataProps}
|
62
|
+
className={classes}
|
63
|
+
id={id}
|
67
64
|
>
|
68
65
|
<Person
|
69
|
-
|
70
|
-
|
66
|
+
firstName={firstName}
|
67
|
+
lastName={lastName}
|
71
68
|
/>
|
72
69
|
{validContacts().map((contactObject, index) => (
|
73
70
|
<Contact
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
71
|
+
contactDetail={contactObject.contactDetail}
|
72
|
+
contactType={contactObject.contactType}
|
73
|
+
contactValue={contactObject.contactValue}
|
74
|
+
key={`valid-contact-${index}`}
|
78
75
|
/>
|
79
76
|
))}
|
80
77
|
{wrongContacts().map((contactObject, index) => (
|
81
78
|
<div key={`wrong-contact-caption-wrapper-${index}`}>
|
82
79
|
<Caption
|
83
|
-
|
84
|
-
|
85
|
-
|
80
|
+
className="wrong_numbers"
|
81
|
+
key={`wrong-contact-caption-${index}`}
|
82
|
+
text="wrong number"
|
86
83
|
/>
|
87
84
|
<Contact
|
88
|
-
|
89
|
-
|
90
|
-
|
85
|
+
contactType={contactObject.contactType}
|
86
|
+
contactValue={contactObject.contactValue}
|
87
|
+
key={`wrong-contact-${index}`}
|
91
88
|
/>
|
92
89
|
</div>
|
93
90
|
))}
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import { render, screen } from '../utilities/test-utils'
|
3
|
+
import PersonContact from './_person_contact'
|
4
|
+
|
5
|
+
const testId = 'personContact'
|
6
|
+
const multipleTestId = 'personContactMultiple'
|
7
|
+
|
8
|
+
const PersonContactTest = (props) => {
|
9
|
+
return (
|
10
|
+
<>
|
11
|
+
<PersonContact
|
12
|
+
aria={{ label: testId }}
|
13
|
+
className={'custom-class'}
|
14
|
+
contacts={[
|
15
|
+
{
|
16
|
+
contactType: 'email',
|
17
|
+
contactValue: 'email@example.com',
|
18
|
+
},
|
19
|
+
{
|
20
|
+
contactValue: '5555555555',
|
21
|
+
contactDetail: 'Home',
|
22
|
+
},
|
23
|
+
{
|
24
|
+
contactType: 'work',
|
25
|
+
contactValue: '3245627482',
|
26
|
+
contactDetail: 'Work',
|
27
|
+
},
|
28
|
+
]}
|
29
|
+
data={{ testid: testId }}
|
30
|
+
firstName="Jose"
|
31
|
+
id={testId}
|
32
|
+
lastName="da Silva"
|
33
|
+
{...props}
|
34
|
+
/>
|
35
|
+
<PersonContact
|
36
|
+
contacts={[
|
37
|
+
{
|
38
|
+
contactValue: '5555555555',
|
39
|
+
contactType: 'wrong-phone',
|
40
|
+
},
|
41
|
+
]}
|
42
|
+
data={{ testid: multipleTestId }}
|
43
|
+
firstName="Brenda"
|
44
|
+
lastName="Walters"
|
45
|
+
{...props}
|
46
|
+
/>
|
47
|
+
</>
|
48
|
+
)
|
49
|
+
}
|
50
|
+
|
51
|
+
test('should render custom class and data', () => {
|
52
|
+
render(<PersonContactTest />)
|
53
|
+
|
54
|
+
const kit = screen.getByTestId(testId)
|
55
|
+
expect(kit).toHaveClass('custom-class')
|
56
|
+
})
|
57
|
+
|
58
|
+
test('should render id', () => {
|
59
|
+
render(<PersonContactTest />)
|
60
|
+
|
61
|
+
const kit = screen.getByTestId(testId)
|
62
|
+
expect(kit).toHaveProperty('id', testId)
|
63
|
+
})
|
64
|
+
|
65
|
+
test('should render aria-label', () => {
|
66
|
+
render(<PersonContactTest />)
|
67
|
+
|
68
|
+
const kit = screen.getByTestId(testId)
|
69
|
+
expect(kit).toHaveAttribute('aria-label', testId)
|
70
|
+
})
|
71
|
+
|
72
|
+
test('should render firstName', () => {
|
73
|
+
render(<PersonContactTest />)
|
74
|
+
|
75
|
+
const kit = screen.getByTestId(testId)
|
76
|
+
expect(kit).toHaveTextContent('Jose')
|
77
|
+
})
|
78
|
+
|
79
|
+
test('should render lastName', () => {
|
80
|
+
render(<PersonContactTest />)
|
81
|
+
|
82
|
+
const kit = screen.getByTestId(testId)
|
83
|
+
expect(kit).toHaveTextContent('da Silva')
|
84
|
+
})
|
85
|
+
|
86
|
+
test('should render contact value', () => {
|
87
|
+
render(<PersonContactTest />)
|
88
|
+
|
89
|
+
const kit = screen.getByTestId(testId)
|
90
|
+
expect(kit).toHaveTextContent('(555) 555-5555')
|
91
|
+
})
|
92
|
+
|
93
|
+
test('should render contact detail', () => {
|
94
|
+
render(<PersonContactTest />)
|
95
|
+
|
96
|
+
const kit = screen.getByTestId(testId)
|
97
|
+
expect(kit).toHaveTextContent('Home')
|
98
|
+
})
|
99
|
+
|
100
|
+
test('should render multiple contacts', () => {
|
101
|
+
render(<PersonContactTest />)
|
102
|
+
|
103
|
+
const kit = screen.getByTestId(multipleTestId)
|
104
|
+
expect(kit).toHaveTextContent('Brenda Walters')
|
105
|
+
})
|
106
|
+
|
107
|
+
test('should render wrong number', () => {
|
108
|
+
render(<PersonContactTest />)
|
109
|
+
|
110
|
+
const kit = screen.getByTestId(multipleTestId)
|
111
|
+
expect(kit).toHaveTextContent('wrong number')
|
112
|
+
})
|
@@ -1,32 +1,32 @@
|
|
1
1
|
/* @flow */
|
2
|
-
import React, { useEffect, useRef, useState } from
|
3
|
-
import classnames from
|
4
|
-
import { buildAriaProps, buildCss, buildDataProps } from
|
5
|
-
import { globalProps } from
|
6
|
-
import intlTelInput from
|
7
|
-
import
|
8
|
-
import TextInput from
|
2
|
+
import React, { useEffect, useRef, useState } from "react"
|
3
|
+
import classnames from "classnames"
|
4
|
+
import { buildAriaProps, buildCss, buildDataProps } from "../utilities/props"
|
5
|
+
import { globalProps } from "../utilities/globalProps"
|
6
|
+
import intlTelInput from "intl-tel-input"
|
7
|
+
import "intl-tel-input/build/css/intlTelInput.css"
|
8
|
+
import TextInput from "../pb_text_input/_text_input"
|
9
9
|
|
10
10
|
declare global {
|
11
11
|
interface Window {
|
12
|
-
intlTelInputGlobals: any
|
12
|
+
intlTelInputGlobals: any
|
13
13
|
}
|
14
14
|
}
|
15
15
|
|
16
16
|
type PhoneNumberInputProps = {
|
17
|
-
aria?: { [key: string]: string }
|
18
|
-
className?: string
|
19
|
-
data?: { [key: string]: string }
|
20
|
-
disabled?: boolean
|
21
|
-
id?: string
|
22
|
-
initialCountry?: string
|
23
|
-
isValid?: (valid: boolean) => void
|
24
|
-
label?: string
|
25
|
-
name?: string
|
26
|
-
onChange?: (e: React.FormEvent<HTMLInputElement>) => void
|
27
|
-
onlyCountries: string[]
|
28
|
-
preferredCountries?: string[]
|
29
|
-
value?: string
|
17
|
+
aria?: { [key: string]: string }
|
18
|
+
className?: string
|
19
|
+
data?: { [key: string]: string }
|
20
|
+
disabled?: boolean
|
21
|
+
id?: string
|
22
|
+
initialCountry?: string
|
23
|
+
isValid?: (valid: boolean) => void
|
24
|
+
label?: string
|
25
|
+
name?: string
|
26
|
+
onChange?: (e: React.FormEvent<HTMLInputElement>) => void
|
27
|
+
onlyCountries: string[]
|
28
|
+
preferredCountries?: string[]
|
29
|
+
value?: string
|
30
30
|
}
|
31
31
|
|
32
32
|
enum ValidationError {
|
@@ -35,17 +35,20 @@ enum ValidationError {
|
|
35
35
|
}
|
36
36
|
|
37
37
|
const formatToGlobalCountryName = (countryName: string) => {
|
38
|
-
return countryName.split(
|
38
|
+
return countryName.split("(")[0].trim()
|
39
39
|
}
|
40
40
|
|
41
41
|
const formatAllCountries = () => {
|
42
42
|
let countryData = window.intlTelInputGlobals.getCountryData()
|
43
|
+
|
43
44
|
for (let i = 0; i < countryData.length; i++) {
|
44
45
|
let country = countryData[i]
|
45
46
|
country.name = formatToGlobalCountryName(country.name)
|
46
47
|
}
|
47
48
|
}
|
48
49
|
|
50
|
+
formatAllCountries()
|
51
|
+
|
49
52
|
const containOnlyNumbers = (value: string) => {
|
50
53
|
return /^(\++)*(\d+)$/.test(value)
|
51
54
|
}
|
@@ -56,25 +59,33 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
56
59
|
className,
|
57
60
|
data = {},
|
58
61
|
disabled = false,
|
59
|
-
id =
|
60
|
-
initialCountry =
|
61
|
-
isValid = () => {
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
id = "",
|
63
|
+
initialCountry = "",
|
64
|
+
isValid = () => {
|
65
|
+
void 0
|
66
|
+
},
|
67
|
+
label = "",
|
68
|
+
name = "",
|
69
|
+
onChange = () => {
|
70
|
+
void 0
|
71
|
+
},
|
65
72
|
onlyCountries = [],
|
66
73
|
preferredCountries = [],
|
67
|
-
value =
|
74
|
+
value = "",
|
68
75
|
} = props
|
69
76
|
|
70
77
|
const ariaProps = buildAriaProps(aria)
|
71
78
|
const dataProps = buildDataProps(data)
|
72
|
-
const classes = classnames(
|
79
|
+
const classes = classnames(
|
80
|
+
buildCss("pb_phone_number_input"),
|
81
|
+
globalProps(props),
|
82
|
+
className
|
83
|
+
)
|
73
84
|
|
74
85
|
const inputRef = useRef<HTMLInputElement>()
|
75
86
|
const [inputValue, setInputValue] = useState(value)
|
76
87
|
const [itiInit, setItiInit] = useState<any>()
|
77
|
-
const [error, setError] = useState(
|
88
|
+
const [error, setError] = useState("")
|
78
89
|
|
79
90
|
const validateTooLongNumber = (itiInit: any) => {
|
80
91
|
const error = itiInit.getValidationError()
|
@@ -83,7 +94,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
83
94
|
const countryName = itiInit.getSelectedCountryData().name
|
84
95
|
setError(`Invalid ${countryName} phone number (too long)`)
|
85
96
|
} else {
|
86
|
-
setError(
|
97
|
+
setError("")
|
87
98
|
}
|
88
99
|
}
|
89
100
|
|
@@ -98,7 +109,7 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
98
109
|
|
99
110
|
const validateOnlyNumbers = () => {
|
100
111
|
if (inputValue && !containOnlyNumbers(inputValue)) {
|
101
|
-
setError(
|
112
|
+
setError("Invalid phone number. Enter numbers only.")
|
102
113
|
}
|
103
114
|
}
|
104
115
|
|
@@ -114,29 +125,32 @@ const PhoneNumberInput = (props: PhoneNumberInputProps) => {
|
|
114
125
|
isValid(itiInit.isValidNumber())
|
115
126
|
}
|
116
127
|
|
128
|
+
// Separating Concerns as React Docs Recommend
|
129
|
+
// This also Fixes things for our react_component rendering on the Rails Side
|
117
130
|
useEffect(() => {
|
118
131
|
formatAllCountries()
|
132
|
+
}, [])
|
119
133
|
|
134
|
+
useEffect(() => {
|
120
135
|
const telInputInit = new intlTelInput(inputRef.current, {
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
136
|
+
utilsScript:
|
137
|
+
"https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.19/js/utils.js",
|
138
|
+
separateDialCode: true,
|
139
|
+
preferredCountries,
|
140
|
+
allowDropdown: !disabled,
|
141
|
+
initialCountry,
|
142
|
+
onlyCountries,
|
143
|
+
})
|
144
|
+
|
145
|
+
inputRef.current.addEventListener("countrychange", () =>
|
146
|
+
validateTooLongNumber(telInputInit)
|
128
147
|
)
|
129
|
-
|
130
|
-
inputRef.current.addEventListener("countrychange", () => validateTooLongNumber(telInputInit))
|
148
|
+
|
131
149
|
setItiInit(telInputInit)
|
132
150
|
}, [])
|
133
151
|
|
134
152
|
return (
|
135
|
-
<div
|
136
|
-
{...ariaProps}
|
137
|
-
{...dataProps}
|
138
|
-
className={classes}
|
139
|
-
>
|
153
|
+
<div {...ariaProps} {...dataProps} className={classes}>
|
140
154
|
<TextInput
|
141
155
|
disabled={disabled}
|
142
156
|
error={error}
|