playbook_ui 12.29.0.pre.alpha.play716popoverkitcloseonclickissue893 → 12.30.0.pre.alpha.PLAY906multilevelselectedidsprop902

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/pb_icon/_icon.tsx +27 -1
  3. data/app/pb_kits/playbook/pb_icon/icon.html.erb +2 -0
  4. data/app/pb_kits/playbook/pb_icon/icon.rb +5 -0
  5. data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.scss +5 -0
  6. data/app/pb_kits/playbook/pb_icon_circle/_icon_circle.tsx +5 -3
  7. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_color.html.erb +13 -7
  8. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_default.html.erb +1 -1
  9. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_emoji.html.erb +16 -0
  10. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_emoji.jsx +31 -0
  11. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_emoji.md +1 -0
  12. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_sizes.html.erb +5 -5
  13. data/app/pb_kits/playbook/pb_icon_circle/docs/_icon_circle_sizes.jsx +6 -0
  14. data/app/pb_kits/playbook/pb_icon_circle/docs/example.yml +3 -0
  15. data/app/pb_kits/playbook/pb_icon_circle/docs/index.js +1 -0
  16. data/app/pb_kits/playbook/pb_icon_circle/icon_circle.html.erb +1 -1
  17. data/app/pb_kits/playbook/pb_icon_circle/icon_circle.rb +2 -0
  18. data/app/pb_kits/playbook/pb_icon_circle/icon_circle.test.js +97 -0
  19. data/app/pb_kits/playbook/pb_multi_level_select/_helper_functions.tsx +56 -27
  20. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.scss +1 -2
  21. data/app/pb_kits/playbook/pb_multi_level_select/_multi_level_select.tsx +19 -31
  22. data/app/pb_kits/playbook/pb_multi_level_select/docs/_multi_level_select_return_all_selected.md +3 -1
  23. data/app/pb_kits/playbook/pb_multi_level_select/multi_level_select.rb +7 -0
  24. data/app/pb_kits/playbook/pb_popover/_popover.tsx +3 -5
  25. data/app/pb_kits/playbook/pb_popover/index.ts +2 -5
  26. data/app/pb_kits/playbook/pb_table/docs/_table_data_table.html.erb +0 -14
  27. data/app/pb_kits/playbook/pb_table/docs/_table_data_table.md +1 -0
  28. data/app/pb_kits/playbook/pb_table/styles/_structure.scss +12 -0
  29. data/app/pb_kits/playbook/utilities/globalProps.ts +9 -4
  30. data/app/pb_kits/playbook/utilities/test/globalProps/flexGrow.test.js +1 -1
  31. data/app/pb_kits/playbook/utilities/test/globalProps/flexShrink.test.js +1 -1
  32. data/dist/playbook-rails.js +6 -6
  33. data/lib/playbook/version.rb +2 -2
  34. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15f7ff554db86a7218c690ce296e7aeff840ebc5272b964807f8ea89da3841b8
4
- data.tar.gz: fdb097512bdebc712087f5a852aa1edd4ff901ab6a32c2896a28acf013db087f
3
+ metadata.gz: a8d8c8cd4aa096608acbf4eed675c36c238baa86d159aaefbc0f757084a5a4a4
4
+ data.tar.gz: 287dd12a2a579011dd57ae22d51ee874b1d3823647e3d95701a201507afb02e7
5
5
  SHA512:
6
- metadata.gz: 703a84e571f96f23c843b702bf30f9ddb9a4300dd2d3de88573f1530d16992d3531c9a4cc799c9723f93808f0c5dff4450668c74eef560d5f2d83eba55418eb7
7
- data.tar.gz: c8cffa2d650853fb72982270243a7812eeb2d9c1b6627a5e2c2255c036e27ca9edfab64e2ebca2cd824ec264c23edcb14cb0c4d2e0ca2eee11923fe76d395f3c
6
+ metadata.gz: 18f3b68f512177e50e2146c9018e52535928c142d69f3e54b2c368d7655d1c638c5432d2e23ade2f4de5c41862a349dbf6b8325cd4d7cd9515ea0ea2b242a6fe
7
+ data.tar.gz: 2a44c4ecbf272b2ae90b49616048b8bbc6f7f641e0741dbdb26cfc92c5dce5b49724611c4b9aacb7f74017c89d1f891d2d54a148279322b80e54d455cb674738
@@ -93,10 +93,23 @@ const Icon = (props: IconProps) => {
93
93
  className
94
94
  )
95
95
 
96
+ const classesEmoji = classnames(
97
+ 'pb_icon_kit',
98
+ globalProps(props),
99
+ 'icon_circle_emoji',
100
+ className
101
+ )
102
+
96
103
  aria.label ? null : aria.label = `${icon} icon`
97
104
  const ariaProps: {[key: string]: any} = buildAriaProps(aria)
98
105
  const dataProps: {[key: string]: any} = buildDataProps(data)
99
106
 
107
+ const isValidEmoji = (emoji: string) => {
108
+ // Using regular expression to check if the string is a valid emoji/emoji Unicode
109
+ const emojiRegex = /^(\p{Emoji}|\uFE0F)+$/u;
110
+ return emojiRegex.test(emoji);
111
+ };
112
+
100
113
  // Add a conditional here to show only the SVG if custom
101
114
  const displaySVG = (customIcon: any) => {
102
115
  if (customIcon)
@@ -111,6 +124,19 @@ const Icon = (props: IconProps) => {
111
124
  }
112
125
  </>
113
126
  )
127
+ else if (isValidEmoji(icon))
128
+ return (
129
+ <>
130
+ <span
131
+ {...dataProps}
132
+ className={classesEmoji}
133
+ id={id}
134
+ >
135
+ {icon}
136
+ </span>
137
+ </>
138
+ )
139
+
114
140
  else
115
141
  return (
116
142
  <>
@@ -134,4 +160,4 @@ const Icon = (props: IconProps) => {
134
160
  )
135
161
  }
136
162
 
137
- export default Icon
163
+ export default Icon
@@ -1,5 +1,7 @@
1
1
  <% if object.custom_icon %>
2
2
  <%= object.render_svg(object.custom_icon) %>
3
+ <% elsif object.valid_emoji(object.icon) %>
4
+ <span class="pb_icon_kit icon_circle_emoji"><%= object.icon.html_safe %></span>
3
5
  <% else %>
4
6
  <%= content_tag(:i, nil,
5
7
  id: object.id,
@@ -38,6 +38,11 @@ module Playbook
38
38
  prop :spin, type: Playbook::Props::Boolean,
39
39
  default: false
40
40
 
41
+ def valid_emoji(icon)
42
+ emoji_regex = /\p{Emoji}/
43
+ emoji_regex.match?(icon)
44
+ end
45
+
41
46
  def classname
42
47
  generate_classname(
43
48
  "pb_icon_kit",
@@ -20,6 +20,11 @@ $pb_icon_circle_sizes: (
20
20
  object-fit: cover;
21
21
  overflow: hidden;
22
22
  position: relative;
23
+ // font-family set to sans-serif ONLY for emojis.
24
+ //Needed to fix misalignment issue caused by Proxima font
25
+ .icon_circle_emoji {
26
+ font-family: monospace;
27
+ }
23
28
 
24
29
  i,
25
30
  svg {
@@ -32,6 +32,7 @@ const IconCircle = (props: IconCircleProps) => {
32
32
  const dataProps = buildDataProps(data)
33
33
  const classes = classnames(buildCss('pb_icon_circle_kit', size, variant), globalProps(props), className)
34
34
 
35
+
35
36
  return (
36
37
  <div
37
38
  {...ariaProps}
@@ -39,12 +40,13 @@ const IconCircle = (props: IconCircleProps) => {
39
40
  className={classes}
40
41
  id={id}
41
42
  >
42
- <Icon
43
+ <Icon
43
44
  dark={dark}
44
45
  icon={icon}
45
- />
46
+ />
47
+
46
48
  </div>
47
49
  )
48
50
  }
49
51
 
50
- export default IconCircle
52
+ export default IconCircle
@@ -1,35 +1,41 @@
1
1
  <%= pb_rails("icon_circle", props: {
2
- icon: "comment",
2
+ icon: "rocket",
3
3
  variant: "royal",
4
4
  size: "sm"
5
5
  }) %>
6
+ <br />
6
7
  <%= pb_rails("icon_circle", props: {
7
- icon: "archive",
8
+ icon: "rocket",
8
9
  variant: "orange",
9
10
  size: "sm"
10
11
  }) %>
12
+ <br />
11
13
  <%= pb_rails("icon_circle", props: {
12
- icon: "arrow-alt-right",
14
+ icon: "rocket",
13
15
  variant: "purple",
14
16
  size: "sm"
15
17
  }) %>
18
+ <br />
16
19
  <%= pb_rails("icon_circle", props: {
17
- icon: "cloud",
20
+ icon: "rocket",
18
21
  variant: "teal",
19
22
  size: "sm"
20
23
  }) %>
24
+ <br />
21
25
  <%= pb_rails("icon_circle", props: {
22
- icon: "award",
26
+ icon: "rocket",
23
27
  variant: "red",
24
28
  size: "sm"
25
29
  }) %>
30
+ <br />
26
31
  <%= pb_rails("icon_circle", props: {
27
- icon: "bolt",
32
+ icon: "rocket",
28
33
  variant: "yellow",
29
34
  size: "sm"
30
35
  }) %>
36
+ <br />
31
37
  <%= pb_rails("icon_circle", props: {
32
- icon: "calendar",
38
+ icon: "rocket",
33
39
  variant: "green",
34
40
  size: "sm"
35
41
  }) %>
@@ -1,3 +1,3 @@
1
1
  <%= pb_rails("icon_circle", props: {
2
- icon: "user"
2
+ icon: "rocket"
3
3
  }) %>
@@ -0,0 +1,16 @@
1
+ <%= pb_rails("icon_circle", props: {
2
+ icon: "😁",
3
+ size:"sm"
4
+ }) %>
5
+ <br />
6
+ <%= pb_rails("icon_circle", props: {
7
+ icon: "&#128525;",
8
+ size:"md",
9
+ variant: "red"
10
+ }) %>
11
+ <br />
12
+ <%= pb_rails("icon_circle", props: {
13
+ icon: '&#x1F389;',
14
+ size: "lg",
15
+ variant: "teal"
16
+ }) %>
@@ -0,0 +1,31 @@
1
+ import React from 'react'
2
+
3
+ import IconCircle from '../_icon_circle'
4
+
5
+ const IconCircleEmoji = (props) => {
6
+ return (
7
+ <div>
8
+ <IconCircle
9
+ icon="😁"
10
+ size="sm"
11
+ {...props}
12
+ />
13
+ <br/>
14
+ <IconCircle
15
+ icon="&#128525;"
16
+ size="md"
17
+ variant="red"
18
+ {...props}
19
+ />
20
+ <br/>
21
+ <IconCircle
22
+ icon="&#x1F389;"
23
+ size="lg"
24
+ variant="teal"
25
+ {...props}
26
+ />
27
+ </div>
28
+ )
29
+ }
30
+
31
+ export default IconCircleEmoji
@@ -0,0 +1 @@
1
+ The Icon Circle also allows you to pass in an HTML emoji in place of an icon if needed. To do so, pass any emoji or its hexa/decimal ref (see [here](https://www.w3schools.com/charsets/ref_emoji.asp)) as a string to the `icon` prop as shown in the code snippet below.
@@ -1,24 +1,24 @@
1
1
  <%= pb_rails("icon_circle", props: {
2
- icon: "comment",
2
+ icon: "rocket",
3
3
  size: "xs"
4
4
  }) %>
5
5
  <br />
6
6
  <%= pb_rails("icon_circle", props: {
7
- icon: "comment",
7
+ icon: "rocket",
8
8
  size: "sm"
9
9
  }) %>
10
10
  <br />
11
11
  <%= pb_rails("icon_circle", props: {
12
- icon: "comment",
12
+ icon: "rocket",
13
13
  size: "md"
14
14
  }) %>
15
15
  <br />
16
16
  <%= pb_rails("icon_circle", props: {
17
- icon: "comment",
17
+ icon: "rocket",
18
18
  size: "lg"
19
19
  }) %>
20
20
  <br />
21
21
  <%= pb_rails("icon_circle", props: {
22
- icon: "comment",
22
+ icon: "rocket",
23
23
  size: "xl"
24
24
  }) %>
@@ -4,6 +4,12 @@ import { IconCircle } from '../..'
4
4
  const IconCircleSizes = (props) => {
5
5
  return (
6
6
  <div>
7
+ <IconCircle
8
+ icon="rocket"
9
+ size="xs"
10
+ {...props}
11
+ />
12
+ <br />
7
13
  <IconCircle
8
14
  icon="rocket"
9
15
  size="sm"
@@ -4,8 +4,11 @@ examples:
4
4
  - icon_circle_default: Default
5
5
  - icon_circle_sizes: Size
6
6
  - icon_circle_color: Color
7
+ - icon_circle_emoji: With Emoji
8
+
7
9
 
8
10
  react:
9
11
  - icon_circle_default: Default
10
12
  - icon_circle_sizes: Size
11
13
  - icon_circle_color: Color
14
+ - icon_circle_emoji: With Emoji
@@ -1,3 +1,4 @@
1
1
  export { default as IconCircleColor } from './_icon_circle_color.jsx'
2
2
  export { default as IconCircleDefault } from './_icon_circle_default.jsx'
3
3
  export { default as IconCircleSizes } from './_icon_circle_sizes.jsx'
4
+ export { default as IconCircleEmoji } from './_icon_circle_emoji.jsx'
@@ -3,5 +3,5 @@
3
3
  class: object.classname,
4
4
  data: object.data,
5
5
  id: object.id) do %>
6
- <%= pb_rails("icon", props: { dark: object.dark, icon: object.icon, fixed_width: true }) %>
6
+ <%= pb_rails("icon", props: { dark: object.dark, icon: object.icon, fixed_width: true }) %>
7
7
  <% end %>
@@ -4,6 +4,8 @@ module Playbook
4
4
  module PbIconCircle
5
5
  class IconCircle < Playbook::KitBase
6
6
  prop :icon, required: true
7
+ prop :emoji, type: Playbook::Props::String,
8
+ default: ""
7
9
  prop :size, type: Playbook::Props::Enum,
8
10
  values: %w[xs sm md base lg xl],
9
11
  default: "md"
@@ -0,0 +1,97 @@
1
+ import React from 'react'
2
+ import { render, screen, cleanup } from '../utilities/test-utils'
3
+
4
+ import IconCircle from './_icon_circle'
5
+
6
+ const testId = "icon-circle-kit"
7
+
8
+ describe("IconCircle Kit", () => {
9
+ test("renders classname", () => {
10
+ render(
11
+ <IconCircle
12
+ data={{ testid: testId }}
13
+ icon="user"
14
+ size="md"
15
+ />
16
+ )
17
+
18
+ const kit = screen.getByTestId(testId)
19
+ expect(kit).toHaveClass("pb_icon_circle_kit_md_default")
20
+ })
21
+
22
+ test("renders icon", () => {
23
+ render(
24
+ <IconCircle
25
+ data={{ testid: testId }}
26
+ icon="user"
27
+ size="md"
28
+ />
29
+ )
30
+
31
+ const kit = screen.getByTestId(testId)
32
+ const icon = kit.querySelector('.pb_icon_kit')
33
+ expect(icon).toBeInTheDocument()
34
+ })
35
+
36
+ test("renders emoji", () => {
37
+ render(
38
+ <IconCircle
39
+ data={{ testid: testId }}
40
+ icon="&#128525;"
41
+ size="md"
42
+ />
43
+ )
44
+
45
+ const kit = screen.getByTestId(testId)
46
+ expect(kit).toHaveTextContent("😍")
47
+ })
48
+
49
+ test('displays color variants', () => {
50
+ [
51
+ "default",
52
+ "royal",
53
+ "blue",
54
+ "purple",
55
+ "teal",
56
+ "red",
57
+ "yellow",
58
+ "green"
59
+ ].forEach((colorVariant) => {
60
+ render(
61
+ <IconCircle
62
+ data={{ testid: testId }}
63
+ icon="rocket"
64
+ size="sm"
65
+ variant={colorVariant}
66
+ />
67
+ )
68
+ const kit = screen.getByTestId(testId)
69
+ expect(kit).toHaveClass(`pb_icon_circle_kit_sm_${colorVariant}`)
70
+
71
+ cleanup()
72
+ })
73
+ })
74
+
75
+ test('displays size as expected', () => {
76
+ [
77
+ "xs",
78
+ "sm",
79
+ "md",
80
+ "lg",
81
+ "xl"
82
+ ].forEach((sizeVariant) => {
83
+ render(
84
+ <IconCircle
85
+ data={{ testid: testId }}
86
+ icon="rocket"
87
+ size={sizeVariant}
88
+ />
89
+ )
90
+ const kit = screen.getByTestId(testId)
91
+ expect(kit).toHaveClass(`pb_icon_circle_kit_${sizeVariant}_default`)
92
+
93
+ cleanup()
94
+ })
95
+ })
96
+
97
+ })
@@ -6,11 +6,11 @@ export const getAncestorsOfUnchecked = (
6
6
  if (item.parent_id) {
7
7
  const ancestor = filterFormattedDataById(data, item.parent_id);
8
8
  ancestor[0].checked = false;
9
- ancestor[0].parent_id && getAncestorsOfUnchecked(data, ancestor[0])
9
+ ancestor[0].parent_id && getAncestorsOfUnchecked(data, ancestor[0]);
10
10
  }
11
11
  return data;
12
12
  };
13
-
13
+
14
14
  //function is going over formattedData and returning all objects that match the
15
15
  //id of the clicked item from the dropdown
16
16
  export const filterFormattedDataById = (
@@ -20,9 +20,9 @@ export const filterFormattedDataById = (
20
20
  const matched: { [key: string]: any }[] = [];
21
21
  const recursiveSearch = (data: { [key: string]: any }[], term: string) => {
22
22
  for (const item of data) {
23
- if (item.id.toLowerCase() === (term.toLowerCase())) {
23
+ if (item.id.toLowerCase() === term.toLowerCase()) {
24
24
  matched.push(item);
25
- return
25
+ return;
26
26
  }
27
27
 
28
28
  if (item.children && item.children.length > 0) {
@@ -75,24 +75,29 @@ export const getCheckedItems = (
75
75
  });
76
76
  return checkedItems;
77
77
  };
78
-
79
- export const getDefaultCheckedItems = (treeData:{ [key: string]: any }[]) => {
78
+
79
+ export const getDefaultCheckedItems = (treeData: { [key: string]: any }[]) => {
80
80
  const checkedDefault: { [key: string]: any }[] = [];
81
81
 
82
- const traverseTree = (items:{ [key: string]: any }[]) => {
82
+ const traverseTree = (items: { [key: string]: any }[]) => {
83
83
  if (!Array.isArray(items)) {
84
84
  return;
85
85
  }
86
- items.forEach((item:{ [key: string]: any }) => {
86
+ items.forEach((item: { [key: string]: any }) => {
87
87
  if (item.checked) {
88
88
  if (item.children && item.children.length > 0) {
89
- const uncheckedChildren = item.children.filter((child:{ [key: string]: any }) => !child.checked);
89
+ const uncheckedChildren = item.children.filter(
90
+ (child: { [key: string]: any }) => !child.checked
91
+ );
90
92
  if (uncheckedChildren.length === 0) {
91
93
  checkedDefault.push(item);
92
94
  return;
93
95
  }
94
96
  } else {
95
- const parent = items.find((parentItem:{ [key: string]: any }) => parentItem.id === item.parentId);
97
+ const parent = items.find(
98
+ (parentItem: { [key: string]: any }) =>
99
+ parentItem.id === item.parentId
100
+ );
96
101
  if (!parent || !parent.checked) {
97
102
  checkedDefault.push(item);
98
103
  }
@@ -112,23 +117,47 @@ export const getDefaultCheckedItems = (treeData:{ [key: string]: any }[]) => {
112
117
 
113
118
  export const recursiveCheckParent = (
114
119
  item: { [key: string]: any },
115
- data:any
120
+ data: any
116
121
  ) => {
117
122
  if (item.parent_id !== null) {
118
- const parent = filterFormattedDataById(data, item.parent_id);
119
- const allChildrenChecked = parent[0].children.every(
120
- (child: { [key: string]: any }) => child.checked
121
- );
122
- if (allChildrenChecked) {
123
- parent[0].checked = true;
124
- const parentHasParent = parent[0].parent_id !== null;
125
- if (parentHasParent) {
126
- recursiveCheckParent(
127
- parent[0],
128
- data
129
- );
123
+ const parent = filterFormattedDataById(data, item.parent_id);
124
+ const allChildrenChecked = parent[0].children.every(
125
+ (child: { [key: string]: any }) => child.checked
126
+ );
127
+ if (allChildrenChecked) {
128
+ parent[0].checked = true;
129
+ const parentHasParent = parent[0].parent_id !== null;
130
+ if (parentHasParent) {
131
+ recursiveCheckParent(parent[0], data);
132
+ }
133
+ }
134
+ }
135
+ return data;
136
+ };
137
+
138
+ export const getExpandedItems = (treeData: { [key: string]: string }[]) => {
139
+ let expandedItems: any[] = [];
140
+
141
+ const traverse = (items: string | any[], ancestors: any[] = []) => {
142
+ for (let i = 0; i < items.length; i++) {
143
+ const item = items[i];
144
+ const itemAncestors = [...ancestors, item];
145
+
146
+ if (item.expanded) {
147
+ expandedItems.push(item.id);
148
+ }
149
+ if (Array.isArray(item.children)) {
150
+ const hasCheckedChildren = item.children.some(
151
+ (child: { [key: string]: string }) => child.checked
152
+ );
153
+ if (hasCheckedChildren) {
154
+ expandedItems.push(...itemAncestors.map((ancestor) => ancestor.id));
155
+ }
156
+ traverse(item.children, itemAncestors);
157
+ }
130
158
  }
131
- }
132
- }
133
- return data;
134
- }
159
+ };
160
+
161
+ traverse(treeData);
162
+ return expandedItems;
163
+ };
@@ -23,7 +23,7 @@
23
23
  .input_wrapper {
24
24
  background-color: $white;
25
25
  cursor: pointer;
26
- padding: $space_xs + 4 $space_sm;
26
+ padding: $space_xs + 1 $space_sm;
27
27
  border: 1px solid #e4e8f0;
28
28
  border-radius: $border_rad_heavier;
29
29
  display: flex;
@@ -36,7 +36,6 @@
36
36
  input {
37
37
  border: none;
38
38
  font-family: $font_family_base;
39
- padding: $space_xs;
40
39
  &:focus {
41
40
  outline: none;
42
41
  }