playbook_ui 12.33.1 → 12.34.0.pre.alpha.play716popoverkitcloseonclickissue998

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1468abb19e4ac525eed445f416c38c07658000bf453c5a0b874be54c64ecdb3
4
- data.tar.gz: b74c0cb81586fcb391013b1f6d46414a36dc98196fbec2d933359b978fee1483
3
+ metadata.gz: de8314a6b967b5e972e46892edd4fe1c7b9dfcb9ca199120cc898e92724b137a
4
+ data.tar.gz: 4f323f8d98c5219d26de002ed897bf31ab5dee71af172727559e3b1aca47b04c
5
5
  SHA512:
6
- metadata.gz: 4e2bb9dad3c8d08921eea44a7e9a3d8ec5832bbcce47f81b75c65fc0bd7eda0218ce328b9820f4bd530a2fb28233816bfdf404762700cb48ab98aee2c16d4308
7
- data.tar.gz: 2c4fb8680f9f5e13e9462bc1ec206edf07a773739c7c282b32d7ed1c279ac3ab0ed42d4da46829328010f4e33159823325188e99941e8d144807384be861f794
6
+ metadata.gz: 2fb6010127b3e609045f7b7b6ba41aa650ed266e00b8b0660a4b9e544568300b2a7a7722a6b43f9913bc2791ef7234522b303433a566fa52bf95345ecf13ef89
7
+ data.tar.gz: 4bf867ee4ee1ef8fa70d12a0fd5bd61efbdbab94d0a7b03f3060857eac345c75c4de7a02bb65ef200f5a5c1e801317d6e8208e55cba22f8fee32a553728860ae
@@ -1,5 +1,9 @@
1
1
  @import "./button_mixins";
2
2
  @import "../tokens/colors";
3
+ @import "../tokens/border_radius";
4
+ @import "../tokens/colors";
5
+ @import "../tokens/spacing";
6
+ @import "../tokens/typography";
3
7
 
4
8
  $pb_button_sizes: (
5
9
  "sm": 0.75rem,
@@ -30,6 +34,34 @@ $pb_button_sizes: (
30
34
  &[class*=_link] {
31
35
  @include pb_button_link;
32
36
  }
37
+ &[class*=_reaction] {
38
+ background-color: $card_light;
39
+ min-width: 40px;
40
+ border-radius: $border_radius_rounded;
41
+ border: 1px solid $border_light;
42
+ color: $text_lt_light;
43
+ padding: ($space_xxs - 2) $space_xs;
44
+ min-height: $space_md + 4;
45
+
46
+ .pb_icon_kit_emoji, .pb_icon_kit {
47
+ font-size: $font_base;
48
+ }
49
+ &:hover {
50
+ background-color: $bg_light;
51
+ }
52
+ &.active {
53
+ border-color:transparent;
54
+ box-shadow: 0px 0px 0 2px $primary_action;
55
+ &:hover {
56
+ background-color: rgba($primary, 0.03);
57
+ }
58
+ }
59
+ }
60
+
61
+ &.reaction_default {
62
+ padding: ($space_xxs + 1) ($space_sm - 4);
63
+ color: $text_lt_lighter;
64
+ }
33
65
 
34
66
  // Disabled =================
35
67
  &[class*=_disabled] {
@@ -4,6 +4,8 @@ import { buildAriaProps, buildDataProps } from '../utilities/props'
4
4
  import { GlobalProps, globalProps } from '../utilities/globalProps'
5
5
 
6
6
  import Icon from '../pb_icon/_icon'
7
+ import Caption from "../pb_caption/_caption"
8
+ import Flex from "../pb_flex/_flex"
7
9
 
8
10
  type EventHandler = (React.MouseEventHandler<HTMLElement>)
9
11
 
@@ -11,11 +13,13 @@ type ButtonPropTypes = {
11
13
  aria?: { [key: string]: string },
12
14
  children?: React.ReactChild[] | React.ReactChild,
13
15
  className?: string | string[],
16
+ count?: number,
14
17
  data?: { [key: string]: string },
15
18
  disabled?: boolean,
16
19
  fixedWidth?: boolean,
17
20
  form?: string,
18
21
  fullWidth?: boolean,
22
+ highlight?: boolean,
19
23
  icon?: string,
20
24
  iconRight?: boolean,
21
25
  id?: string,
@@ -30,14 +34,22 @@ type ButtonPropTypes = {
30
34
  type?: 'inline' | null,
31
35
  htmlType?: 'submit' | 'reset' | 'button' | undefined,
32
36
  value?: string | null,
33
- variant?: 'primary' | 'secondary' | 'link',
37
+ variant?: 'primary' | 'secondary' | 'link'| 'reaction',
34
38
  wrapperClass?: string,
35
39
  } & GlobalProps
36
40
 
41
+ const isValidEmoji = (emoji: string) => {
42
+ // Using regular expression to check if the string is a valid emoji/emoji Unicode
43
+ const emojiRegex = /^(\p{Emoji}|\uFE0F)+$/u;
44
+ return emojiRegex.test(emoji);
45
+ };
46
+
37
47
  const buttonClassName = (props: ButtonPropTypes) => {
38
48
  const {
39
49
  disabled = false,
40
50
  fullWidth = false,
51
+ highlight,
52
+ icon,
41
53
  loading = false,
42
54
  type = 'inline',
43
55
  variant = 'primary',
@@ -52,6 +64,8 @@ const buttonClassName = (props: ButtonPropTypes) => {
52
64
  className += disabled ? '_disabled' : '_enabled'
53
65
  className += loading ? '_loading' : ''
54
66
  className += `${size !== null ? ` size_${size}` : ''}`
67
+ className += `${variant === 'reaction' && !isValidEmoji(icon) ? ` reaction_default` : ''}`
68
+ className += `${variant === 'reaction' && highlight ? ` active` : ''}`
55
69
 
56
70
  return className
57
71
  }
@@ -61,6 +75,7 @@ const Button = (props: ButtonPropTypes) => {
61
75
  aria = {},
62
76
  children,
63
77
  className,
78
+ count,
64
79
  data = {},
65
80
  disabled,
66
81
  icon = null,
@@ -75,6 +90,7 @@ const Button = (props: ButtonPropTypes) => {
75
90
  text,
76
91
  htmlType = 'button',
77
92
  value,
93
+ variant,
78
94
  form = null
79
95
  } = props
80
96
 
@@ -131,41 +147,77 @@ const Button = (props: ButtonPropTypes) => {
131
147
  return null
132
148
  }
133
149
 
134
- const displayButton = () => {
135
- if (link)
150
+ const displayButton = () => {
151
+ if (link) {
136
152
  return (
137
153
  <a
138
- {...ariaProps}
139
- {...dataProps}
140
- className={css}
141
- href={link}
142
- id={id}
143
- rel={target !== 'child' ? 'noreferrer' : null}
144
- role="link"
145
- tabIndex={tabIndex}
146
- target={getTargetAttribute()}
154
+ {...ariaProps}
155
+ {...dataProps}
156
+ className={css}
157
+ href={link}
158
+ id={id}
159
+ rel={target !== "child" ? "noreferrer" : null}
160
+ role="link"
161
+ tabIndex={tabIndex}
162
+ target={getTargetAttribute()}
147
163
  >
148
164
  {ifLoading()}
149
165
  </a>
150
- )
151
- else
166
+ );
167
+ } else if (variant === "reaction") {
152
168
  return (
153
169
  <button
154
- {...ariaProps}
155
- {...dataProps}
156
- className={css}
157
- disabled={disabled}
158
- form={form}
159
- id={id}
160
- onClick={onClick}
161
- role="button"
162
- tabIndex={tabIndex}
163
- type={htmlType}
164
- value={value}
170
+ {...ariaProps}
171
+ {...dataProps}
172
+ className={css}
173
+ disabled={disabled}
174
+ form={form}
175
+ id={id}
176
+ onClick={onClick}
177
+ role="button"
178
+ tabIndex={tabIndex}
179
+ type={htmlType}
180
+ value={value}
181
+ >
182
+ {icon && isValidEmoji(icon) && (
183
+ <Flex align='center'>
184
+ <Icon icon={icon} />
185
+ {count && (
186
+ <Caption paddingLeft="xxs" size="xs">
187
+ {count}
188
+ </Caption>
189
+ )}
190
+ </Flex>
191
+ )
192
+ }
193
+ {
194
+ !isValidEmoji(icon) && (
195
+ <Icon icon={icon ? icon : "face-smile-plus"} />
196
+ )
197
+ }
198
+
199
+
200
+ </button>
201
+ );
202
+ } else {
203
+ return (
204
+ <button
205
+ {...ariaProps}
206
+ {...dataProps}
207
+ className={css}
208
+ disabled={disabled}
209
+ form={form}
210
+ id={id}
211
+ onClick={onClick}
212
+ role="button"
213
+ tabIndex={tabIndex}
214
+ type={htmlType}
215
+ value={value}
165
216
  >
166
217
  {ifLoading()}
167
218
  </button>
168
- )
219
+ );
220
+ }
169
221
  }
170
222
 
171
223
  return (
@@ -1,5 +1,21 @@
1
1
  <%= content_tag(object.tag,
2
2
  object.tag == "button" ? object.options : object.link_options) do %>
3
+ <% if object.variant === "reaction" %>
4
+ <% if icon && object.valid_emoji(object.icon) %>
5
+ <%= pb_rails("flex", props:{ align: "center" }) do %>
6
+ <%= pb_rails("icon", props: { icon: "#{icon}"}) %>
7
+ <% if object.count %>
8
+ <%= pb_rails("caption", props: { text: "#{count}", size: "xs", padding_left:"xxs" }) %>
9
+ <% end %>
10
+ <% end %>
11
+ <% elsif !object.valid_emoji(object.icon) %>
12
+ <% if object.icon %>
13
+ <%= pb_rails("icon", props: { icon: "#{icon}" }) %>
14
+ <% else %>
15
+ <%= pb_rails("icon", props: { icon: "face-smile-plus" }) %>
16
+ <% end %>
17
+ <% end %>
18
+ <% else %>
3
19
  <% if object.icon && !object.icon_right %>
4
20
  <span>
5
21
  <%= pb_rails("icon", props: { icon: "#{icon}", fixed_width: true, margin_right: "xs" }) %>
@@ -12,5 +28,6 @@
12
28
  <%= pb_rails("icon", props: { icon: "#{icon}", fixed_width: true, margin_left: "xs" }) %>
13
29
  </span>
14
30
  <% end %>
31
+ <% end %>
15
32
 
16
33
  <% end %>
@@ -16,8 +16,11 @@ module Playbook
16
16
  prop :new_window, type: Playbook::Props::Boolean,
17
17
  default: false
18
18
  prop :variant, type: Playbook::Props::Enum,
19
- values: %w[primary secondary link],
19
+ values: %w[primary secondary link reaction],
20
20
  default: "primary"
21
+ prop :count, type: Playbook::Props::Number
22
+ prop :highlight, type: Playbook::Props::Boolean,
23
+ default: false
21
24
  prop :target
22
25
  prop :text
23
26
  prop :type
@@ -63,9 +66,14 @@ module Playbook
63
66
  link ? "a" : "button"
64
67
  end
65
68
 
69
+ def valid_emoji(icon)
70
+ emoji_regex = /\p{Emoji}/
71
+ emoji_regex.match?(icon)
72
+ end
73
+
66
74
  def classname
67
75
  button_class = generate_classname("pb_button_kit", variant, full_width_class, disabled_class, loading_class)
68
- button_class + size_class
76
+ button_class + size_class + default_reaction_class + highlight_active
69
77
  end
70
78
 
71
79
  private
@@ -85,6 +93,14 @@ module Playbook
85
93
  def size_class
86
94
  size ? " size_#{size}" : ""
87
95
  end
96
+
97
+ def default_reaction_class
98
+ variant === "reaction" && !object.valid_emoji(object.icon) ? " reaction_default" : ""
99
+ end
100
+
101
+ def highlight_active
102
+ variant === "reaction" && object.highlight ? " active" : ""
103
+ end
88
104
  end
89
105
  end
90
106
  end
@@ -0,0 +1,30 @@
1
+ <%= pb_rails("button", props: { count: 153, highlight: false, icon: "&#127881;", classname: "count", id: "reaction-button-highlight", variant: "reaction" }) %>
2
+ <%= pb_rails("button", props: { count: 5, icon: "😍", variant: "reaction", margin_left: "lg" }) %>
3
+ <%= pb_rails("button", props: { variant: "reaction", margin_left: "lg" }) %>
4
+ <%= pb_rails("button", props: { icon: "user", variant: "reaction", margin_left: "lg" }) %>
5
+
6
+
7
+ <script>
8
+ function renderButtonReaction() {
9
+
10
+ let highlightActive = false;
11
+
12
+ function toggleHighlight() {
13
+ let reactionCount = 153;
14
+ console.log("toggleHighlight", highlightActive)
15
+ highlightActive = !highlightActive;
16
+ const innerCountElement = document.querySelector(".count .pb_caption_kit_xs.pl_xxs")
17
+ const firstButton = document.getElementById("reaction-button-highlight")
18
+ firstButton.classList.add(highlightActive && "active")
19
+ firstButton.classList.remove(!highlightActive && "active")
20
+ innerCountElement.innerHTML = highlightActive ? reactionCount + 1 : reactionCount;
21
+ console.log("element", innerCountElement)
22
+ }
23
+
24
+ const button1 = document.getElementById("reaction-button-highlight")
25
+ button1.addEventListener("click", toggleHighlight);
26
+
27
+ }
28
+ renderButtonReaction();
29
+ </script>
30
+
@@ -0,0 +1,47 @@
1
+ import React, {useState} from "react"
2
+ import { Button } from "../../"
3
+
4
+ const ButtonReaction = (props) => {
5
+
6
+ const [highlightActive, setHighlightActive] =useState(false)
7
+ const reactionCount = 153
8
+
9
+ return (
10
+ <div>
11
+ <Button
12
+ count={highlightActive ? (reactionCount + 1) : reactionCount}
13
+ highlight = {highlightActive}
14
+ icon="&#127881;"
15
+ onClick={()=> setHighlightActive(!highlightActive)}
16
+ tabIndex={0}
17
+ variant="reaction"
18
+ {...props}
19
+ />
20
+ <Button
21
+ count={5}
22
+ icon="😍"
23
+ marginLeft='lg'
24
+ tabIndex={0}
25
+ variant="reaction"
26
+ {...props}
27
+ />
28
+ <Button
29
+ marginLeft='lg'
30
+ tabIndex={0}
31
+ variant="reaction"
32
+ {...props}
33
+ />
34
+ <Button
35
+ icon="user"
36
+ marginLeft='lg'
37
+ tabIndex={0}
38
+ variant="reaction"
39
+ {...props}
40
+ />
41
+
42
+
43
+ </div>
44
+ )
45
+ }
46
+
47
+ export default ButtonReaction
@@ -0,0 +1,3 @@
1
+ The `reaction` variant accepts any HTML Emoji or it's hexa/decimal ref (see [here](https://www.w3schools.com/charsets/ref_emoji.asp)) as a string within the `icon` prop. If nothing is passed to the icon prop, the default reaction button will be displayed as seen in the third example. The default reaction button will also be rendered if a Fontawesome icon (not an Emoji) is passed to the `icon` prop of a `reaction` variant, but the default "smiley +" icon will be replaced with the named icon.
2
+
3
+ Reaction buttons also accept two additional (optional) props: `count`, which accepts a number (i.e., a count of reactions) to be displayed next to the Emoji; and `highlight`, which is a boolean that if true, displays the 'active' state for the button. Click the first reaction button to see this in action!
@@ -1,6 +1,7 @@
1
1
  examples:
2
2
  rails:
3
3
  - button_default: Button Variants
4
+ - button_reaction: Reaction Button
4
5
  - button_full_width: Button Full Width
5
6
  - button_link: Button Links
6
7
  - button_loading: Button Loading
@@ -13,6 +14,7 @@ examples:
13
14
 
14
15
  react:
15
16
  - button_default: Button Variants
17
+ - button_reaction: Reaction Button
16
18
  - button_full_width: Button Full Width
17
19
  - button_link: Button Links
18
20
  - button_loading: Button Loading
@@ -8,4 +8,5 @@ export { default as ButtonAccessibility } from './_button_accessibility.jsx'
8
8
  export { default as ButtonOptions } from './_button_options.jsx'
9
9
  export { default as ButtonSize } from './_button_size.jsx'
10
10
  export { default as ButtonForm } from './_button_form.jsx'
11
- export { default as ButtonHover } from './_button_hover.jsx'
11
+ export { default as ButtonHover } from './_button_hover.jsx'
12
+ export {default as ButtonReaction } from './_button_reaction.jsx'
@@ -6,3 +6,7 @@ svg.pb_custom_icon {
6
6
  fill: currentColor;
7
7
  }
8
8
  }
9
+
10
+ .pb_icon_kit_emoji {
11
+ font-family: monospace;
12
+ }
@@ -95,9 +95,8 @@ const Icon = (props: IconProps) => {
95
95
  )
96
96
 
97
97
  const classesEmoji = classnames(
98
- 'pb_icon_kit',
98
+ 'pb_icon_kit_emoji',
99
99
  globalProps(props),
100
- 'icon_circle_emoji',
101
100
  className
102
101
  )
103
102
 
@@ -1,7 +1,7 @@
1
1
  <% if object.custom_icon %>
2
2
  <%= object.render_svg(object.custom_icon) %>
3
3
  <% elsif object.valid_emoji(object.icon) %>
4
- <span class="pb_icon_kit icon_circle_emoji"><%= object.icon.html_safe %></span>
4
+ <span class="pb_icon_kit_emoji"><%= object.icon.html_safe %></span>
5
5
  <% else %>
6
6
  <%= content_tag(:i, nil,
7
7
  id: object.id,
@@ -1,4 +1,4 @@
1
- import React, { useEffect } from "react";
1
+ import React, { useEffect, useState } from "react";
2
2
  import ReactDOM from "react-dom";
3
3
  import {
4
4
  Popper,
@@ -17,6 +17,7 @@ import {
17
17
 
18
18
  import classnames from "classnames";
19
19
  import { globalProps, GlobalProps } from "../utilities/globalProps";
20
+ import _uniqueId from 'lodash/uniqueId';
20
21
 
21
22
  type PbPopoverProps = {
22
23
  aria?: { [key: string]: string };
@@ -72,6 +73,7 @@ const Popover = (props: PbPopoverProps) => {
72
73
  maxWidth,
73
74
  minHeight,
74
75
  minWidth,
76
+ targetId,
75
77
  } = props;
76
78
 
77
79
  const popoverSpacing =
@@ -123,6 +125,7 @@ const Popover = (props: PbPopoverProps) => {
123
125
  popoverSpacing,
124
126
  overflowHandling
125
127
  )}
128
+ id={targetId}
126
129
  style={widthHeightStyles()}
127
130
  >
128
131
  {children}
@@ -136,6 +139,7 @@ const Popover = (props: PbPopoverProps) => {
136
139
  };
137
140
 
138
141
  const PbReactPopover = (props: PbPopoverProps) => {
142
+ const [targetId] = useState(_uniqueId('id-'))
139
143
  const {
140
144
  className,
141
145
  children,
@@ -163,25 +167,27 @@ const PbReactPopover = (props: PbPopoverProps) => {
163
167
  "click",
164
168
  ({ target }) => {
165
169
  const targetIsPopover =
166
- (target as HTMLElement).closest("[class^=pb_popover_tooltip]") !==
170
+ (target as HTMLElement).closest("#" + targetId) !==
167
171
  null;
168
172
  const targetIsReference =
169
- (target as HTMLElement).closest(".pb_popover_reference_wrapper") !==
173
+ (target as HTMLElement).closest("#reference-" + targetId) !==
170
174
  null;
171
175
 
172
176
  switch (closeOnClick) {
173
177
  case "outside":
174
- if (!targetIsPopover || targetIsReference) {
178
+ if (!targetIsPopover && !targetIsReference) {
175
179
  shouldClosePopover(true);
176
180
  }
177
181
  break;
178
182
  case "inside":
179
- if (targetIsPopover || targetIsReference) {
183
+ if (targetIsPopover) {
180
184
  shouldClosePopover(true);
181
185
  }
182
186
  break;
183
187
  case "any":
184
- shouldClosePopover(true);
188
+ if (targetIsPopover || !targetIsPopover && !targetIsReference) {
189
+ shouldClosePopover(true);
190
+ }
185
191
  break;
186
192
  }
187
193
  },
@@ -200,6 +206,7 @@ const PbReactPopover = (props: PbPopoverProps) => {
200
206
  offset={offset}
201
207
  placement={placement}
202
208
  referenceElement={referenceElement}
209
+ targetId={targetId}
203
210
  zIndex={zIndex}
204
211
  {...props}
205
212
  >
@@ -214,6 +221,7 @@ const PbReactPopover = (props: PbPopoverProps) => {
214
221
  <PopperReference>
215
222
  {({ ref }) => (
216
223
  <span
224
+ id={"reference-" + targetId}
217
225
  className="pb_popover_reference_wrapper"
218
226
  ref={ref}>
219
227
  <reference.type {...reference.props} />
@@ -24,6 +24,7 @@ const PopoverClose = (props) => {
24
24
 
25
25
  const handleOutsideTogglePopover = () => {
26
26
  setOutsideShowPopover(!showOutsidePopover)
27
+ setAnyShowPopover(false)
27
28
  }
28
29
 
29
30
  const handleAnyShouldClosePopover = (shouldClosePopover) => {
@@ -32,6 +33,7 @@ const PopoverClose = (props) => {
32
33
 
33
34
  const handleAnyTogglePopover = () => {
34
35
  setAnyShowPopover(!showAnyPopover)
36
+ setOutsideShowPopover(false)
35
37
  }
36
38
 
37
39
  const insidePopoverTrigger = (
@@ -49,13 +49,16 @@ export default class PbPopover extends PbEnhancedElement {
49
49
  checkCloseTooltip() {
50
50
  document.querySelector('body').addEventListener('click', ({ target } ) => {
51
51
  const isTooltipElement = (target as HTMLElement).closest(`#${this.tooltipId}`) !== null
52
+ const isTriggerElement = (target as HTMLElement).closest(`#${this.triggerElementId}`) !== null
52
53
 
53
54
  switch (this.closeOnClick) {
54
55
  case 'any':
55
- this.hideTooltip()
56
+ if (isTooltipElement || !isTooltipElement && !isTriggerElement) {
57
+ this.hideTooltip()
58
+ }
56
59
  break
57
60
  case 'outside':
58
- if (!isTooltipElement) {
61
+ if (!isTooltipElement && !isTriggerElement) {
59
62
  this.hideTooltip()
60
63
  }
61
64
  break