playbook_ui 9.7.0 → 9.10.0.pre.alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_background/background.rb +0 -1
  3. data/app/pb_kits/playbook/pb_circle_chart/circle_chart.rb +9 -10
  4. data/app/pb_kits/playbook/pb_circle_icon_button/circle_icon_button.rb +4 -2
  5. data/app/pb_kits/playbook/pb_date/_date.jsx +3 -3
  6. data/app/pb_kits/playbook/pb_date/date.html.erb +2 -3
  7. data/app/pb_kits/playbook/pb_date/docs/_date_variants.html.erb +8 -0
  8. data/app/pb_kits/playbook/pb_date/docs/_date_variants.jsx +10 -0
  9. data/app/pb_kits/playbook/pb_date_stacked/date_stacked.rb +7 -9
  10. data/app/pb_kits/playbook/pb_distribution_bar/distribution_bar.rb +2 -2
  11. data/app/pb_kits/playbook/pb_filter/filter.rb +1 -1
  12. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.jsx +4 -2
  13. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/_fixed_confirmation_toast.scss +7 -0
  14. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_multi_line.html.erb +2 -1
  15. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_multi_line.jsx +2 -1
  16. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/docs/_fixed_confirmation_toast_multi_line.md +1 -1
  17. data/app/pb_kits/playbook/pb_fixed_confirmation_toast/fixed_confirmation_toast.rb +7 -1
  18. data/app/pb_kits/playbook/pb_flex/_flex_item.jsx +1 -1
  19. data/app/pb_kits/playbook/pb_flex/flex.rb +4 -4
  20. data/app/pb_kits/playbook/pb_form/form.rb +1 -1
  21. data/app/pb_kits/playbook/pb_form_group/_form_group.jsx +3 -1
  22. data/app/pb_kits/playbook/pb_form_group/_form_group.scss +8 -0
  23. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_full_width.html.erb +13 -0
  24. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_full_width.jsx +43 -0
  25. data/app/pb_kits/playbook/pb_form_group/docs/_form_group_full_width.md +1 -0
  26. data/app/pb_kits/playbook/pb_form_group/docs/example.yml +2 -0
  27. data/app/pb_kits/playbook/pb_form_group/docs/index.js +1 -1
  28. data/app/pb_kits/playbook/pb_form_group/form_group.rb +10 -1
  29. data/app/pb_kits/playbook/pb_icon_stat_value/icon_stat_value.rb +0 -1
  30. data/app/pb_kits/playbook/pb_nav/_vertical_nav.scss +1 -1
  31. data/app/pb_kits/playbook/pb_nav/docs/_block_nav.html.erb +41 -5
  32. data/app/pb_kits/playbook/pb_nav/docs/_block_nav.jsx +44 -6
  33. data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +12 -9
  34. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb +1 -0
  35. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx +24 -0
  36. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md +3 -0
  37. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +1 -0
  38. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +2 -0
  39. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
  40. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +2 -0
  41. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +12 -0
  42. data/app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js +52 -0
  43. data/app/pb_kits/playbook/pb_passphrase/useZxcvbn.js +58 -0
  44. data/app/pb_kits/playbook/pb_progress_pills/progress_pills.rb +2 -1
  45. data/app/pb_kits/playbook/pb_progress_step/progress_step.rb +1 -1
  46. data/app/pb_kits/playbook/pb_progress_step/progress_step_item.rb +1 -1
  47. data/app/pb_kits/playbook/pb_rich_text_editor/rich_text_editor.rb +2 -2
  48. data/app/pb_kits/playbook/pb_selectable_card_icon/selectable_card_icon.rb +1 -1
  49. data/app/pb_kits/playbook/pb_selectable_icon/selectable_icon.rb +1 -1
  50. data/app/pb_kits/playbook/pb_stat_change/stat_change.rb +8 -12
  51. data/app/pb_kits/playbook/pb_table/docs/_table_side_highlight.md +1 -1
  52. data/app/pb_kits/playbook/pb_tooltip/tooltip.rb +1 -1
  53. data/app/pb_kits/playbook/pb_typeahead/docs/_typeahead_default.html.erb +1 -1
  54. data/app/pb_kits/playbook/pb_typeahead/typeahead.html.erb +5 -1
  55. data/app/pb_kits/playbook/pb_typeahead/typeahead.rb +5 -13
  56. data/app/pb_kits/playbook/pb_user_badge/user_badge.rb +1 -1
  57. data/lib/playbook/forms/builder/action_area.rb +2 -2
  58. data/lib/playbook/forms/builder/collection_select_field.rb +2 -2
  59. data/lib/playbook/forms/builder/select_field.rb +1 -1
  60. data/lib/playbook/kit_resolver.rb +1 -1
  61. data/lib/playbook/markdown/template_handler.rb +4 -2
  62. data/lib/playbook/pb_doc_helper.rb +4 -2
  63. data/lib/playbook/props.rb +1 -4
  64. data/lib/playbook/props/base.rb +1 -2
  65. data/lib/playbook/version.rb +1 -1
  66. metadata +38 -31
@@ -0,0 +1 @@
1
+ Full Width is a prop that can be added to any of the Form Group options. This prop allows the Form Group to stretch the full width of the div.
@@ -3,6 +3,7 @@ examples:
3
3
  rails:
4
4
  - form_group_default: Default
5
5
  - form_group_button: Button
6
+ - form_group_full_width: Full Width
6
7
  - form_group_date_picker: Date Picker
7
8
  # - form_group_typeahead: Typeahead
8
9
  - form_group_select: Select
@@ -13,6 +14,7 @@ examples:
13
14
  react:
14
15
  - form_group_default: Default
15
16
  - form_group_button: Button
17
+ - form_group_full_width: Full Width
16
18
  - form_group_date_picker: Date Picker
17
19
  # - form_group_typeahead: Typeahead
18
20
  - form_group_select: Select
@@ -1,7 +1,7 @@
1
1
  export { default as FormGroupDefault } from './_form_group_default.jsx'
2
2
  export { default as FormGroupButton } from './_form_group_button.jsx'
3
+ export { default as FormGroupFullWidth } from './_form_group_full_width.jsx'
3
4
  export { default as FormGroupDatePicker } from './_form_group_date_picker.jsx'
4
- // export { default as FormGroupTypeahead } from './_form_group_typeahead.jsx'
5
5
  export { default as FormGroupSelect } from './_form_group_select.jsx'
6
6
  export { default as FormGroupSelectableCard } from './_form_group_selectable_card.jsx'
7
7
  export { default as FormGroupSelectableCardIcon } from './_form_group_selectable_card_icon.jsx'
@@ -3,8 +3,17 @@
3
3
  module Playbook
4
4
  module PbFormGroup
5
5
  class FormGroup < Playbook::KitBase
6
+ prop :full_width, type: Playbook::Props::Boolean,
7
+ default: false
8
+
6
9
  def classname
7
- generate_classname("pb_form_group_kit")
10
+ generate_classname("pb_form_group_kit", full_width_class)
11
+ end
12
+
13
+ private
14
+
15
+ def full_width_class
16
+ full_width ? "full" : nil
8
17
  end
9
18
  end
10
19
  end
@@ -24,7 +24,6 @@ module Playbook
24
24
 
25
25
  prop :value, type: Playbook::Props::Numeric
26
26
 
27
-
28
27
  def classname
29
28
  generate_classname("pb_icon_stat_value_kit", orientation, size, variant)
30
29
  end
@@ -16,7 +16,7 @@ $selector: ".pb_nav_list";
16
16
  list-style: none;
17
17
  }
18
18
 
19
- [class*=_title] {
19
+ [class*=pb_nav_list_title] {
20
20
  padding: 0 $space_md $space_sm;
21
21
  }
22
22
 
@@ -1,6 +1,42 @@
1
- <%= pb_rails("nav", props: {title: "Menu", link: "#"}) do %>
2
- <%= pb_rails("nav/item", props: { link: "#", active: true }) do%>Photos<% end %>
3
- <%= pb_rails("nav/item", props: { link: "#" }) do%>Music<% end %>
4
- <%= pb_rails("nav/item", props: { link: "#" }) do%>Video<% end %>
5
- <%= pb_rails("nav/item", props: { link: "#" }) do%>Files<% end %>
1
+ <%= pb_rails("nav", props: {title: "Users", link: "#"}) do %>
2
+ <%= pb_rails("nav/item", props: { link: "#", active: true }) do%>
3
+ <%= pb_rails("user", props: {
4
+ name: "Anna Black",
5
+ territory: "PHL",
6
+ title: "Remodeling Consultant",
7
+ orientation: "horizontal",
8
+ align: "left",
9
+ avatar_url: "https://randomuser.me/api/portraits/women/44.jpg"
10
+ }) %>
11
+ <% end %>
12
+ <%= pb_rails("nav/item", props: { link: "#" }) do%>
13
+ <%= pb_rails("user", props: {
14
+ name: "Julie Hamilton",
15
+ territory: "PHL",
16
+ title: "Inside Sales Agent",
17
+ orientation: "horizontal",
18
+ align: "left",
19
+ avatar_url: "https://randomuser.me/api/portraits/women/45.jpg"
20
+ }) %>
21
+ <% end %>
22
+ <%= pb_rails("nav/item", props: { link: "#" }) do%>
23
+ <%= pb_rails("user", props: {
24
+ name: "Dennis Wilks",
25
+ territory: "PHL",
26
+ title: "Senior Remodeling Consultant",
27
+ orientation: "horizontal",
28
+ align: "left",
29
+ avatar_url: "https://randomuser.me/api/portraits/men/44.jpg"
30
+ }) %>
31
+ <% end %>
32
+ <%= pb_rails("nav/item", props: { link: "#" }) do%>
33
+ <%= pb_rails("user", props: {
34
+ name: "Ronnie Martin",
35
+ territory: "PHL",
36
+ title: "Customer Development Representative",
37
+ orientation: "horizontal",
38
+ align: "left",
39
+ avatar_url: "https://randomuser.me/api/portraits/men/46.jpg"
40
+ }) %>
41
+ <% end %>
6
42
  <% end %>
@@ -1,12 +1,12 @@
1
1
  import React from 'react'
2
- import { Nav } from '../../'
2
+ import { Nav, User } from '../../'
3
3
  import NavItem from '../_item.jsx'
4
4
 
5
5
  const BlockNav = (props) => {
6
6
  return (
7
7
  <Nav
8
8
  link="#"
9
- title="Menu"
9
+ title="Users"
10
10
  {...props}
11
11
  >
12
12
  <NavItem
@@ -14,11 +14,49 @@ const BlockNav = (props) => {
14
14
  link="#"
15
15
  {...props}
16
16
  >
17
- {'Photos'}
17
+ <User
18
+ align="left"
19
+ avatarUrl="https://randomuser.me/api/portraits/women/44.jpg"
20
+ name="Anna Black"
21
+ orientation="horizontal"
22
+ territory="PHL"
23
+ title="Remodeling Consultant"
24
+ {...props}
25
+ />
26
+ </NavItem>
27
+ <NavItem link="#">
28
+ <User
29
+ align="left"
30
+ avatarUrl="https://randomuser.me/api/portraits/women/45.jpg"
31
+ name="Julie Hamilton"
32
+ orientation="horizontal"
33
+ territory="PHL"
34
+ title="Inside Sales Agent"
35
+ {...props}
36
+ />
37
+ </NavItem>
38
+ <NavItem link="#">
39
+ <User
40
+ align="left"
41
+ avatarUrl="https://randomuser.me/api/portraits/men/44.jpg"
42
+ name="Dennis Wilks"
43
+ orientation="horizontal"
44
+ territory="PHL"
45
+ title="Senior Remodeling Consultant"
46
+ {...props}
47
+ />
48
+ </NavItem>
49
+ <NavItem link="#">
50
+ <User
51
+ align="left"
52
+ avatarUrl="https://randomuser.me/api/portraits/men/46.jpg"
53
+ name="Ronnie Martin"
54
+ orientation="horizontal"
55
+ territory="PHL"
56
+ title="Customer Development Representative"
57
+ {...props}
58
+ />
18
59
  </NavItem>
19
- <NavItem link="#">{'Music'}</NavItem>
20
- <NavItem link="#">{'Video'}</NavItem>
21
- <NavItem link="#">{'Files'}</NavItem>
22
60
  </Nav>
23
61
  )
24
62
  }
@@ -5,12 +5,14 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
5
5
  import classnames from 'classnames'
6
6
  import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
7
7
  import { globalProps } from '../utilities/globalProps.js'
8
- import { zxcvbnPasswordScore } from './passwordStrength.js'
8
+ import useZxcvbn from './useZxcvbn'
9
+ import useHaveIBeenPwned from './useHaveIBeenPwned'
9
10
  import { Body, Caption, Flex, Icon, PbReactPopover, ProgressSimple, TextInput } from '../'
10
11
 
11
12
  type PassphraseProps = {
12
13
  aria?: object,
13
14
  averageThreshold?: number,
15
+ checkPwned?: boolean,
14
16
  common?: boolean,
15
17
  confirmation?: boolean,
16
18
  className?: string,
@@ -33,6 +35,7 @@ const Passphrase = (props: PassphraseProps) => {
33
35
  const {
34
36
  aria = {},
35
37
  averageThreshold = 2,
38
+ checkPwned = false,
36
39
  className,
37
40
  common = false,
38
41
  confirmation = false,
@@ -41,7 +44,7 @@ const Passphrase = (props: PassphraseProps) => {
41
44
  id,
42
45
  inputProps = {},
43
46
  label = confirmation ? 'Confirm Passphrase' : 'Passphrase',
44
- minLength,
47
+ minLength = 12,
45
48
  onChange = () => {},
46
49
  showTipsBelow = 'always',
47
50
  onStrengthChange,
@@ -50,6 +53,7 @@ const Passphrase = (props: PassphraseProps) => {
50
53
  uncontrolled = false,
51
54
  value = '',
52
55
  } = props
56
+ const ariaProps = buildAriaProps(aria)
53
57
 
54
58
  const [uncontrolledValue, setUncontrolledValue] = useState('')
55
59
 
@@ -68,16 +72,11 @@ const Passphrase = (props: PassphraseProps) => {
68
72
  const [showPassphrase, setShowPassphrase] = useState(false)
69
73
  const toggleShowPassphrase = () => setShowPassphrase(!showPassphrase)
70
74
 
71
- const ariaProps = buildAriaProps(aria)
72
- const dataProps = buildDataProps(data)
73
75
  const classes = classnames(buildCss('pb_passphrase'), globalProps(props), className)
74
76
 
75
- const calculator = useMemo(
76
- () => confirmation ? { test: () => ({}) } : zxcvbnPasswordScore({ averageThreshold, strongThreshold, minLength }),
77
- [averageThreshold, confirmation, strongThreshold, minLength]
78
- )
77
+ const isPwned = checkPwned ? useHaveIBeenPwned(displayValue, minLength) : false
79
78
 
80
- const { percent: progressPercent, variant: progressVariant, text: strengthLabel, strength } = calculator.test(displayValue, common)
79
+ const { percent: progressPercent, variant: progressVariant, text: strengthLabel, strength } = useZxcvbn({ passphrase: displayValue, common, isPwned, confirmation, averageThreshold, minLength, strongThreshold })
81
80
 
82
81
  useEffect(() => {
83
82
  if (typeof onStrengthChange === 'function') {
@@ -89,6 +88,10 @@ const Passphrase = (props: PassphraseProps) => {
89
88
  (dark ? 'dark' : null),
90
89
  (showTipsBelow === 'always' ? null : `show-below-${showTipsBelow}`),
91
90
  )
91
+ const dataProps = useMemo(
92
+ () => (buildDataProps(Object.assign({}, data, { strength }))),
93
+ [data, strength]
94
+ )
92
95
 
93
96
  const popoverReference = (
94
97
  <a
@@ -0,0 +1 @@
1
+ <%= pb_rails("passphrase", props: { check_pwned: true }) %>
@@ -0,0 +1,24 @@
1
+ import React, { useState } from 'react'
2
+ import { Passphrase } from '../../'
3
+
4
+ const PassphraseBreached = (props) => {
5
+ const [input, setInput] = useState('')
6
+
7
+ const handleChange = (e) => setInput(e.target.value)
8
+
9
+ return (
10
+ <>
11
+ <div>
12
+ <br />
13
+ <Passphrase
14
+ checkPwned
15
+ onChange={handleChange}
16
+ value={input}
17
+ {...props}
18
+ />
19
+ </div>
20
+ </>
21
+ )
22
+ }
23
+
24
+ export default PassphraseBreached
@@ -0,0 +1,3 @@
1
+ Use `checkPwned | checked_pwned` prop to enable checking against <a href='https://haveibeenpwned.com/Passwords'>HaveIBeenPwned's</a> API. As the passphrase is typed, it is checked against more than half a billion breached passwords, to help ensure its not compromised.
2
+ Should it fail, the feedback will express the passphrase is too common, prompting the user to change.
3
+ This uses their k-Anonymity model, so only the first 5 characters of a hashed copy of the passphrase are sent.
@@ -12,6 +12,7 @@ const PassphraseDefault = (props) => {
12
12
  <>
13
13
  <div>
14
14
  <Passphrase
15
+ id="my-passphrase"
15
16
  onChange={handleChange}
16
17
  value={input}
17
18
  {...props}
@@ -5,6 +5,7 @@ examples:
5
5
  - passphrase_meter_settings: Meter Settings
6
6
  - passphrase_input_props: Input Props
7
7
  - passphrase_tips: Tips
8
+ - passphrase_breached: Breached Passphrases
8
9
 
9
10
  react:
10
11
  - passphrase_default: Default
@@ -13,3 +14,4 @@ examples:
13
14
  - passphrase_tips: Tips
14
15
  - passphrase_strength_change: Strength Change
15
16
  - passphrase_common: Common Passphrases
17
+ - passphrase_breached: Breached Passphrases
@@ -4,3 +4,4 @@ export { default as PassphraseInputProps } from './_passphrase_input_props'
4
4
  export { default as PassphraseTips } from './_passphrase_tips'
5
5
  export { default as PassphraseStrengthChange } from './_passphrase_strength_change'
6
6
  export { default as PassphraseCommon } from './_passphrase_common'
7
+ export { default as PassphraseBreached } from './_passphrase_breached'
@@ -4,6 +4,7 @@ module Playbook
4
4
  module PbPassphrase
5
5
  class Passphrase < Playbook::KitBase
6
6
  prop :average_threshold
7
+ prop :check_pwned
7
8
  prop :confirmation, type: Playbook::Props::Boolean, default: false
8
9
  prop :input_props, type: Playbook::Props::Hash, default: {}
9
10
  prop :label
@@ -18,6 +19,7 @@ module Playbook
18
19
 
19
20
  def passphrase_options
20
21
  {
22
+ checkPwned: check_pwned,
21
23
  dark: dark,
22
24
  id: id,
23
25
  averageThreshold: average_threshold,
@@ -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
+ }