playbook_ui 9.8.0 → 9.9.0.alpha.inline1

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_badge/_badge.jsx +26 -1
  3. data/app/pb_kits/playbook/pb_date_picker/_date_picker.jsx +6 -1
  4. data/app/pb_kits/playbook/pb_date_picker/date_picker_helper.js +3 -0
  5. data/app/pb_kits/playbook/pb_form_pill/_form_pill.jsx +12 -2
  6. data/app/pb_kits/playbook/pb_form_pill/_form_pill.scss +19 -0
  7. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_size.html.erb +13 -0
  8. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_size.jsx +25 -0
  9. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_user.html.erb +4 -5
  10. data/app/pb_kits/playbook/pb_form_pill/docs/_form_pill_user.jsx +2 -6
  11. data/app/pb_kits/playbook/pb_form_pill/docs/example.yml +2 -0
  12. data/app/pb_kits/playbook/pb_form_pill/docs/index.js +1 -0
  13. data/app/pb_kits/playbook/pb_form_pill/form_pill.html.erb +1 -1
  14. data/app/pb_kits/playbook/pb_form_pill/form_pill.rb +5 -0
  15. data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +12 -9
  16. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb +1 -0
  17. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx +24 -0
  18. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md +3 -0
  19. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +1 -0
  20. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +2 -0
  21. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
  22. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +2 -0
  23. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +12 -0
  24. data/app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js +52 -0
  25. data/app/pb_kits/playbook/pb_passphrase/useZxcvbn.js +58 -0
  26. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.jsx +10 -0
  27. data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.scss +61 -0
  28. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_inline.html.erb +6 -0
  29. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_inline.jsx +16 -0
  30. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_toolbar_bottom.html.erb +4 -0
  31. data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_toolbar_bottom.jsx +14 -0
  32. data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +4 -0
  33. data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +2 -0
  34. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +9 -2
  35. data/app/pb_kits/playbook/pb_text_input/_text_input.jsx +3 -0
  36. data/app/pb_kits/playbook/pb_text_input/_text_input.scss +8 -0
  37. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_inline.html.erb +5 -0
  38. data/app/pb_kits/playbook/pb_text_input/docs/_text_input_inline.jsx +22 -0
  39. data/app/pb_kits/playbook/pb_text_input/docs/example.yml +2 -0
  40. data/app/pb_kits/playbook/pb_text_input/docs/index.js +1 -0
  41. data/app/pb_kits/playbook/pb_text_input/text_input.rb +7 -1
  42. data/app/pb_kits/playbook/pb_typeahead/_typeahead.jsx +11 -2
  43. data/app/pb_kits/playbook/pb_typeahead/_typeahead.scss +23 -0
  44. data/app/pb_kits/playbook/pb_typeahead/components/MultiValue.jsx +23 -11
  45. data/app/pb_kits/playbook/pb_typeahead/components/Placeholder.jsx +17 -4
  46. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_inline.html.erb +36 -0
  47. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_inline.jsx +43 -0
  48. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_multi_kit.html.erb +35 -0
  49. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_multi_kit.jsx +44 -0
  50. data/app/pb_kits/playbook/pb_typeahead/docs/example.yml +4 -0
  51. data/app/pb_kits/playbook/pb_typeahead/docs/index.js +7 -5
  52. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +1 -1
  53. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +18 -2
  54. data/lib/playbook/version.rb +1 -1
  55. metadata +22 -6
@@ -121,3 +121,15 @@ test('popover target does not show when tips are not given', () => {
121
121
  const kit = screen.getByTestId(testId)
122
122
  expect(kit.querySelector('[class^=pb_popover_reference_wrapper]')).toBeNull()
123
123
  })
124
+
125
+ test('data-strength attribute exposes strength of password', () => {
126
+ render(
127
+ <Passphrase
128
+ data={{ testid: testId }}
129
+ value="correct horse battery staple"
130
+ />
131
+ )
132
+
133
+ const kit = screen.getByTestId(testId)
134
+ expect(parseInt(kit.getAttribute('data-strength'))).toBeGreaterThan(0)
135
+ })
@@ -0,0 +1,52 @@
1
+
2
+ import { useEffect, useState } from 'react'
3
+
4
+ const checkHaveIBeenPwned = async function (passphrase) {
5
+ const buffer = new TextEncoder('utf-8').encode(passphrase)
6
+ const digest = await crypto.subtle.digest('SHA-1', buffer)
7
+ const hashArray = Array.from(new Uint8Array(digest))
8
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
9
+
10
+ const firstFive = hashHex.slice(0, 5)
11
+ const endOfHash = hashHex.slice(5)
12
+
13
+ const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`)
14
+ const text = await resp.text()
15
+
16
+ const match = text.split('\n').some((line) => {
17
+ //Each line is <sha-1-hash-suffix>:<count of incidents>
18
+ return line.split(':')[0] === endOfHash.toUpperCase()
19
+ })
20
+ return match
21
+ }
22
+
23
+ /**
24
+ * If the input hasn't changed in <delay> ms,
25
+ * hit the haveibeenpwned api and check if the given passphrase is compromised
26
+ */
27
+ export default function useHaveIBeenPwned(passphrase, minLength, delay = 400) {
28
+ const [isPwned, setIsPwned] = useState(false)
29
+
30
+ useEffect(
31
+ () => {
32
+ // only check the API for passphrases above the minimum size
33
+ if (passphrase.length < minLength) {
34
+ setIsPwned(false)
35
+ return
36
+ }
37
+
38
+ const handler = setTimeout(() => {
39
+ checkHaveIBeenPwned(passphrase)
40
+ .then((pwned) => setIsPwned(pwned))
41
+ .catch(() => setIsPwned(false))
42
+ }, delay)
43
+
44
+ return () => {
45
+ clearTimeout(handler)
46
+ }
47
+ },
48
+ [passphrase, minLength, delay]
49
+ )
50
+
51
+ return isPwned
52
+ }
@@ -0,0 +1,58 @@
1
+ import { useEffect, useMemo, useState } from 'react'
2
+ import zxcvbn from 'zxcvbn'
3
+
4
+ export default function useZxcvbn(options) {
5
+ const { passphrase = '', common, isPwned, confirmation, averageThreshold, minLength, strongThreshold } = options
6
+ const calculator = useMemo(
7
+ () => confirmation ? () => ({ score: 0 }) : zxcvbn,
8
+ [confirmation]
9
+ )
10
+
11
+ const [percent, setPercent] = useState('0')
12
+ const [variant, setVariant] = useState('negative')
13
+ const [text, setText] = useState('\u00A0') //nbsp to keep height constant
14
+ const [result, setResult] = useState({})
15
+
16
+ useEffect(() => {
17
+ if (confirmation) return
18
+
19
+ setResult(calculator(passphrase))
20
+ const str = result.score
21
+
22
+ const noPassphrase = passphrase.length <= 0
23
+ const commonPassphrase = common || isPwned
24
+ const weakPassphrase = passphrase.length < minLength || str < averageThreshold
25
+ const averagePassphrase = str < strongThreshold
26
+ const strongPassphrase = str >= strongThreshold
27
+
28
+ if (noPassphrase) {
29
+ setPercent('0')
30
+ setVariant('negative')
31
+ setText('\u00A0') //nbsp to keep height constant
32
+ } else if (commonPassphrase) {
33
+ setPercent('25')
34
+ setVariant('negative')
35
+ setText('This passphrase is too common')
36
+ } else if (weakPassphrase) {
37
+ setPercent('25')
38
+ setVariant('negative')
39
+ setText('Too weak')
40
+ } else if (averagePassphrase){
41
+ setPercent('50')
42
+ setVariant('warning')
43
+ setText('Almost there, keep going!')
44
+ } else if (strongPassphrase) {
45
+ setPercent('100')
46
+ setVariant('positive')
47
+ setText('Success! Strong passphrase')
48
+ }
49
+ }, [passphrase, common, isPwned, averageThreshold, minLength, strongThreshold]
50
+ )
51
+
52
+ return {
53
+ strength: common || isPwned ? 0 : result.score,
54
+ percent,
55
+ variant,
56
+ text,
57
+ }
58
+ }
@@ -9,10 +9,12 @@ import { buildAriaProps, buildDataProps } from '../utilities/props'
9
9
 
10
10
  type RichTextEditorProps = {
11
11
  aria?: object,
12
+ toolbarBottom?: Boolean,
12
13
  className?: string,
13
14
  data?: object,
14
15
  focus?: boolean,
15
16
  id?: string,
17
+ inline?: boolean,
16
18
  name?: string,
17
19
  onChange: (string) => void,
18
20
  placeholder?: string,
@@ -25,10 +27,12 @@ type RichTextEditorProps = {
25
27
  const RichTextEditor = (props: RichTextEditorProps) => {
26
28
  const {
27
29
  aria = {},
30
+ toolbarBottom = false,
28
31
  className,
29
32
  data = {},
30
33
  focus = false,
31
34
  id,
35
+ inline = false,
32
36
  name,
33
37
  onChange,
34
38
  placeholder,
@@ -77,6 +81,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
77
81
  blockCodeButton.hidden = type == 'inline'
78
82
  inlineCodeButton.hidden = type == 'block'
79
83
  })
84
+
85
+ if (toolbarBottom) editor.element.after(toolbarElement)
80
86
  })
81
87
 
82
88
  trixRef.current.addEventListener('trix-change', (event) => {
@@ -103,6 +109,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
103
109
  const SimpleClass = simple ? 'simple' : ''
104
110
  const FocusClass = focus ? 'focus-editor-targets' : ''
105
111
  const StickyClass = sticky ? 'sticky' : ''
112
+ const InlineClass = inline ? 'inline' : ''
113
+ const ToolbarBottomClass = toolbarBottom ? 'toolbar-bottom' : ''
106
114
  const css = classnames(globalProps(props), className)
107
115
 
108
116
  return (
@@ -114,6 +122,8 @@ const RichTextEditor = (props: RichTextEditorProps) => {
114
122
  SimpleClass,
115
123
  FocusClass,
116
124
  StickyClass,
125
+ InlineClass,
126
+ ToolbarBottomClass,
117
127
  css
118
128
  )}
119
129
  >
@@ -5,6 +5,7 @@
5
5
  @import "../tokens/typography";
6
6
  @import "../tokens/opacity";
7
7
  @import "../tokens/spacing";
8
+ @import "../tokens/transition";
8
9
  @import "../pb_icon/icon";
9
10
  @import "./trix_styles";
10
11
 
@@ -228,4 +229,64 @@
228
229
  color: $white;
229
230
  }
230
231
  }
232
+ &.toolbar-bottom {
233
+ trix-editor {
234
+ border-top-left-radius: 6px;
235
+ border-top-right-radius: 6px;
236
+ border-bottom: none;
237
+ border-bottom-left-radius: 0;
238
+ border-bottom-right-radius: 0;
239
+ &:empty:not(:focus)::before {
240
+ color: $neutral !important;
241
+ }
242
+ }
243
+ trix-toolbar .trix-button-row {
244
+ border-top: none;
245
+ border-top-left-radius: 0;
246
+ border-top-right-radius: 0;
247
+ border-bottom: 1px solid $border_light;
248
+ border-bottom-left-radius: 6px;
249
+ border-bottom-right-radius: 6px;
250
+ }
251
+ trix-toolbar {
252
+ display: grid;
253
+ }
254
+ &:focus, &:focus-within {
255
+ trix-toolbar .trix-button-row {
256
+ @include transition_default;
257
+ border-color: $primary;
258
+ }
259
+ }
260
+ }
261
+ &.inline {
262
+ &:hover {
263
+ trix-editor {
264
+ @include transition_default;
265
+ border-color: $border_light;
266
+ background-color: $white;
267
+ &+ trix-toolbar .trix-button-row {
268
+ transition: all 0.3s ease-in-out 0s;
269
+ opacity: 100;
270
+ }
271
+ }
272
+ }
273
+ trix-editor {
274
+ @include transition_default;
275
+ border-color: transparent;
276
+ background-color: transparent;
277
+ &+ trix-toolbar .trix-button-row {
278
+ transition: all 0.3s ease-in-out 0s;
279
+ opacity: 0;
280
+ }
281
+ &:focus {
282
+ @include transition_default;
283
+ border-color: $primary;
284
+ background-color: $focus_input_light;
285
+ &+ trix-toolbar .trix-button-row {
286
+ transition: all 0.3s ease-in-out 0s;
287
+ opacity: 100;
288
+ }
289
+ }
290
+ }
291
+ }
231
292
  }
@@ -0,0 +1,6 @@
1
+ <%= pb_rails("rich_text_editor", props: {
2
+ id: "inline",
3
+ inline: true,
4
+ toolbar_bottom: true,
5
+ value: "Try hovering over this text. Then try modifying it or adding more of your own text."
6
+ }) %>
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+ import { RichTextEditor } from '../../'
3
+
4
+ const RichTextEditorInline = (props) => (
5
+ <div>
6
+ <RichTextEditor
7
+ id="inline"
8
+ inline
9
+ toolbarBottom
10
+ value="Try hovering over this text. Then try modifying it or adding more of your own text."
11
+ {...props}
12
+ />
13
+ </div>
14
+ )
15
+
16
+ export default RichTextEditorInline
@@ -0,0 +1,4 @@
1
+ <%= pb_rails("rich_text_editor", props: {
2
+ id: "toolbar-bottom",
3
+ toolbar_bottom: true
4
+ }) %>
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+ import { RichTextEditor } from '../../'
3
+
4
+ const RichTextEditorToolbarBottom = (props) => (
5
+ <div>
6
+ <RichTextEditor
7
+ id="bottom-toolbar"
8
+ toolbarBottom
9
+ {...props}
10
+ />
11
+ </div>
12
+ )
13
+
14
+ export default RichTextEditorToolbarBottom
@@ -7,6 +7,8 @@ examples:
7
7
  - rich_text_editor_focus: Focus
8
8
  - rich_text_editor_sticky: Sticky
9
9
  - rich_text_editor_templates: Templates
10
+ - rich_text_editor_toolbar_bottom: Toolbar Bottom
11
+ - rich_text_editor_inline: Inline
10
12
  - rich_text_editor_preview: Preview
11
13
 
12
14
  react:
@@ -16,4 +18,6 @@ examples:
16
18
  - rich_text_editor_focus: Focus
17
19
  - rich_text_editor_sticky: Sticky
18
20
  - rich_text_editor_templates: Templates
21
+ - rich_text_editor_toolbar_bottom: Toolbar Bottom
22
+ - rich_text_editor_inline: Inline
19
23
  - rich_text_editor_preview: Preview
@@ -4,4 +4,6 @@ export { default as RichTextEditorAttributes } from './_rich_text_editor_attribu
4
4
  export { default as RichTextEditorFocus } from './_rich_text_editor_focus.jsx'
5
5
  export { default as RichTextEditorSticky } from './_rich_text_editor_sticky.jsx'
6
6
  export { default as RichTextEditorTemplates } from './_rich_text_editor_templates.jsx'
7
+ export { default as RichTextEditorToolbarBottom } from './_rich_text_editor_toolbar_bottom.jsx'
8
+ export { default as RichTextEditorInline } from './_rich_text_editor_inline.jsx'
7
9
  export { default as RichTextEditorPreview } from './_rich_text_editor_preview.jsx'
@@ -6,11 +6,16 @@ module Playbook
6
6
  prop :focus, type: Playbook::Props::Boolean,
7
7
  default: false
8
8
 
9
+ prop :inline, type: Playbook::Props::Boolean,
10
+ default: false
11
+
9
12
  prop :simple, type: Playbook::Props::Boolean,
10
13
  default: false
11
14
 
12
15
  prop :sticky, type: Playbook::Props::Boolean,
13
- default: false
16
+ default: false
17
+ prop :toolbar_bottom, type: Playbook::Props::Boolean,
18
+ default: false
14
19
 
15
20
  prop :value
16
21
  prop :template
@@ -35,13 +40,15 @@ module Playbook
35
40
  def rich_text_options
36
41
  {
37
42
  id: id,
43
+ inline: inline,
38
44
  className: classname,
39
45
  focus: focus,
40
46
  simple: simple,
41
47
  sticky: sticky,
48
+ toolbarBottom: toolbar_bottom,
42
49
  value: value,
43
50
  template: template,
44
- placeholder: placeholder
51
+ placeholder: placeholder,
45
52
  }
46
53
  end
47
54
  end
@@ -17,6 +17,7 @@ type TextInputProps = {
17
17
  disabled?: boolean,
18
18
  error?: string,
19
19
  id?: string,
20
+ inline?: boolean,
20
21
  name: string,
21
22
  label: string,
22
23
  onChange: (String) => void,
@@ -39,6 +40,7 @@ const TextInput = (
39
40
  disabled,
40
41
  error,
41
42
  id,
43
+ inline = false,
42
44
  name,
43
45
  label,
44
46
  onChange = () => {},
@@ -53,6 +55,7 @@ const TextInput = (
53
55
  const dataProps = buildDataProps(data)
54
56
  const css = classnames([
55
57
  'pb_text_input_kit',
58
+ inline ? 'inline' : null,
56
59
  error ? 'error' : null,
57
60
  globalProps(props),
58
61
  className,
@@ -79,4 +79,12 @@
79
79
  }
80
80
  }
81
81
  }
82
+ &.inline {
83
+ &:not(:hover) {
84
+ .text_input_wrapper input:not(:focus) {
85
+ background-color: transparent;
86
+ border-color: transparent;
87
+ }
88
+ }
89
+ }
82
90
  }
@@ -0,0 +1,5 @@
1
+ <%= pb_rails("text_input", props: {
2
+ inline: true,
3
+ label: "Hover Over Text Below",
4
+ value: "Inline Input"
5
+ }) %>
@@ -0,0 +1,22 @@
1
+ import React, { useState } from 'react'
2
+ import { TextInput } from '../../'
3
+
4
+ const TextInputInline = (props) => {
5
+ const [value, setValue] = useState('Inline Input')
6
+ const handleValueChange = ({ target }) => {
7
+ setValue(target.value)
8
+ }
9
+ return (
10
+ <div>
11
+ <TextInput
12
+ inline
13
+ label="Hover Over Text Below"
14
+ onChange={handleValueChange}
15
+ value={value}
16
+ {...props}
17
+ />
18
+ </div>
19
+ )
20
+ }
21
+
22
+ export default TextInputInline
@@ -4,8 +4,10 @@ examples:
4
4
  - text_input_error: With Error
5
5
  - text_input_custom: Custom
6
6
  - text_input_disabled: Disabled
7
+ - text_input_inline: Inline
7
8
  react:
8
9
  - text_input_default: Default
9
10
  - text_input_error: With Error
10
11
  - text_input_custom: Custom
11
12
  - text_input_disabled: Disabled
13
+ - text_input_inline: Inline
@@ -2,3 +2,4 @@ export { default as TextInputDefault } from './_text_input_default.jsx'
2
2
  export { default as TextInputCustom } from './_text_input_custom.jsx'
3
3
  export { default as TextInputError } from './_text_input_error.jsx'
4
4
  export { default as TextInputDisabled } from './_text_input_disabled.jsx'
5
+ export { default as TextInputInline } from './_text_input_inline.jsx'