playbook_ui 11.13.0 → 11.15.0.pre.alpha.dependencies1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/pb_dialog/_dialog.scss +39 -0
  3. data/app/pb_kits/playbook/pb_dialog/child_kits/_dialog_header.tsx +3 -2
  4. data/app/pb_kits/playbook/pb_dialog/dialog_header.html.erb +1 -0
  5. data/app/pb_kits/playbook/pb_dialog/dialog_header.rb +4 -0
  6. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_full_height.jsx +2 -2
  7. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_scrollable.html.erb +13 -1
  8. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_scrollable.jsx +32 -8
  9. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_scrollable.md +5 -2
  10. data/app/pb_kits/playbook/pb_dialog/docs/_dialog_sizes.jsx +2 -2
  11. data/app/pb_kits/playbook/pb_nav/_subtle_mixin.scss +3 -0
  12. data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +56 -97
  13. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.html.erb +145 -1
  14. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.jsx +127 -3
  15. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_breached.md +11 -2
  16. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.html.erb +136 -0
  17. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx +90 -8
  18. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.md +5 -0
  19. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.html.erb +51 -0
  20. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_confirmation.jsx +39 -0
  21. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb +0 -2
  22. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +6 -20
  23. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.html.erb +2 -2
  24. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.jsx +1 -1
  25. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.html.erb +318 -5
  26. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +134 -48
  27. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md +11 -5
  28. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.html.erb +123 -0
  29. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx +96 -20
  30. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md +6 -2
  31. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +4 -0
  32. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +1 -0
  33. data/app/pb_kits/playbook/pb_passphrase/passphrase.html.erb +1 -1
  34. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +5 -9
  35. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +0 -47
  36. data/lib/playbook/version.rb +2 -2
  37. metadata +9 -8
  38. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md +0 -1
  39. data/app/pb_kits/playbook/pb_passphrase/passwordStrength.js +0 -55
  40. data/app/pb_kits/playbook/pb_passphrase/useHaveIBeenPwned.js +0 -52
  41. data/app/pb_kits/playbook/pb_passphrase/useZxcvbn.js +0 -58
@@ -1 +1,145 @@
1
- <%= pb_rails("passphrase", props: { check_pwned: true }) %>
1
+ <%= pb_rails("passphrase", props: { label: "Passphrase", classname: "passphrase_breached" }) %>
2
+
3
+ <%= pb_rails("progress_simple", props: { percent: 0, id: "bar_breached" }) %>
4
+
5
+ <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "caption_breached" }) %>
6
+
7
+ <%= javascript_tag do %>
8
+ window.addEventListener("DOMContentLoaded", () => {
9
+
10
+ // variables for the kits you are targeting
11
+ const passphrase = document.querySelector(".passphrase_breached").querySelector("input")
12
+ const barVariant = document.getElementById("bar_breached")
13
+ const barPercent = document.getElementById("bar_breached").querySelector("div")
14
+ const caption = document.getElementById("caption_breached")
15
+
16
+ // hide the bar and captions
17
+ barVariant.style.display = 'none';
18
+ barPercent.style.display = 'none';
19
+ caption.style.display = 'none';
20
+
21
+
22
+ const handleStrengthCalculation = (settings) => {
23
+ const {
24
+ passphrase = "",
25
+ common = false,
26
+ isPwned = false,
27
+ averageThreshold = 2,
28
+ minLength = 12,
29
+ strongThreshold = 3,
30
+ } = settings
31
+
32
+ const resultByScore = {
33
+ 0: {
34
+ variant: 'negative',
35
+ label: '',
36
+ percent: 0,
37
+ },
38
+ 1: {
39
+ variant: 'negative',
40
+ label: 'This passphrase is too common',
41
+ percent: 25,
42
+ },
43
+ 2: {
44
+ variant: 'negative',
45
+ label: 'Too weak',
46
+ percent: 25,
47
+ },
48
+ 3: {
49
+ variant: 'warning',
50
+ label: 'Almost there, keep going!',
51
+ percent: 50,
52
+ },
53
+ 4: {
54
+ variant: 'positive',
55
+ label: 'Success! Strong passphrase',
56
+ percent: 100,
57
+ }
58
+ }
59
+
60
+ const { score } = zxcvbn(passphrase);
61
+
62
+ const noPassphrase = passphrase.length <= 0
63
+ const commonPassphrase = common || isPwned
64
+ const weakPassphrase = passphrase.length < minLength || score < averageThreshold
65
+ const averagePassphrase = score < strongThreshold
66
+ const strongPassphrase = score >= strongThreshold
67
+
68
+ if (noPassphrase) {
69
+ return {...resultByScore[0], score}
70
+ } else if (commonPassphrase) {
71
+ return {...resultByScore[1], score}
72
+ } else if (weakPassphrase) {
73
+ return {...resultByScore[2], score}
74
+ } else if (averagePassphrase){
75
+ return {...resultByScore[3], score}
76
+ } else if (strongPassphrase) {
77
+ return {...resultByScore[4], score}
78
+ }
79
+ }
80
+
81
+ // event listeners attached to the input field
82
+ passphrase.addEventListener('input', (e) => {
83
+ const passphrase = e.target.value;
84
+
85
+ let pwndMatch = false
86
+
87
+ const checkHaveIBeenPwned = async function (passphrase) {
88
+ const buffer = new TextEncoder('utf-8').encode(passphrase)
89
+ const digest = await crypto.subtle.digest('SHA-1', buffer)
90
+ const hashArray = Array.from(new Uint8Array(digest))
91
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
92
+
93
+ const firstFive = hashHex.slice(0, 5)
94
+ const endOfHash = hashHex.slice(5)
95
+
96
+ const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`)
97
+ const text = await resp.text()
98
+
99
+ if (passphrase.length < 5) {
100
+ pwndMatch = false
101
+ }
102
+ else {
103
+ pwndMatch = text.split('\n').some((line) => {
104
+ return line.split(':')[0] === endOfHash.toUpperCase()
105
+ })
106
+ }
107
+
108
+ // pass in passphrase and isPwnd match to the handleStrengthCalculation and set that equal to result variable
109
+ const result = handleStrengthCalculation({ passphrase: passphrase, isPwned: pwndMatch });
110
+
111
+
112
+ // conditional statment to show or hide progress_simple bar and caption if user has entered a password
113
+ if (passphrase) {
114
+ barVariant.style.display = 'block';
115
+
116
+ barPercent.style.display = 'block';
117
+
118
+ caption.style.display = 'block';
119
+ } else {
120
+ barVariant.style.display = 'none';
121
+
122
+ barPercent.style.display = 'none';
123
+
124
+ caption.style.display = 'none';
125
+ }
126
+
127
+ // set the width of the progress_simple kit
128
+ barPercent.style.width = result.percent.toString()+ "%"
129
+
130
+
131
+ // set the variant of the progress_simple kit
132
+ barVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left");
133
+
134
+
135
+ // set the text of the caption kit
136
+ caption.textContent = result.label
137
+ }
138
+
139
+ checkHaveIBeenPwned(passphrase)
140
+
141
+
142
+ });
143
+
144
+ })
145
+ <% end %>
@@ -1,22 +1,146 @@
1
- import React, { useState } from 'react'
1
+ import React, { useState, useEffect } from 'react'
2
2
 
3
- import Passphrase from '../_passphrase'
3
+ import { Caption, Passphrase, ProgressSimple } from '../..'
4
+ import zxcvbn from 'zxcvbn'
4
5
 
5
6
  const PassphraseBreached = (props) => {
6
7
  const [input, setInput] = useState('')
8
+ const [isPwned, setIsPwned] = useState(false)
9
+ const [checkStrength, setCheckStrength] = useState({
10
+ label: '',
11
+ percent: 0,
12
+ score: 0,
13
+ variant: ''
14
+ })
15
+
7
16
 
8
17
  const handleChange = (e) => setInput(e.target.value)
9
18
 
19
+ const checkHaveIBeenPwned = async function (passphrase) {
20
+ const buffer = new TextEncoder('utf-8').encode(passphrase)
21
+ const digest = await crypto.subtle.digest('SHA-1', buffer)
22
+ const hashArray = Array.from(new Uint8Array(digest))
23
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
24
+
25
+ const firstFive = hashHex.slice(0, 5)
26
+ const endOfHash = hashHex.slice(5)
27
+
28
+ const resp = await fetch(`https://api.pwnedpasswords.com/range/${firstFive}`)
29
+ const text = await resp.text()
30
+
31
+ const match = text.split('\n').some((line) => {
32
+ //Each line is <sha-1-hash-suffix>:<count of incidents>
33
+ return line.split(':')[0] === endOfHash.toUpperCase()
34
+ })
35
+ return match
36
+ }
37
+
38
+ const handleStrengthCalculation = (settings) => {
39
+ const {
40
+ passphrase = "",
41
+ common = false,
42
+ isPwned = false,
43
+ averageThreshold = 2,
44
+ minLength = 12,
45
+ strongThreshold = 3,
46
+ } = settings
47
+
48
+ const resultByScore = {
49
+ 0: {
50
+ variant: 'negative',
51
+ label: '',
52
+ percent: 0,
53
+ },
54
+ 1: {
55
+ variant: 'negative',
56
+ label: 'This passphrase is too common',
57
+ percent: 25,
58
+ },
59
+ 2: {
60
+ variant: 'negative',
61
+ label: 'Too weak',
62
+ percent: 25,
63
+ },
64
+ 3: {
65
+ variant: 'warning',
66
+ label: 'Almost there, keep going!',
67
+ percent: 50,
68
+ },
69
+ 4: {
70
+ variant: 'positive',
71
+ label: 'Success! Strong passphrase',
72
+ percent: 100,
73
+ }
74
+ }
75
+
76
+ const { score } = zxcvbn(passphrase);
77
+
78
+ const noPassphrase = passphrase.length <= 0
79
+ const commonPassphrase = common || isPwned
80
+ const weakPassphrase = passphrase.length < minLength || score < averageThreshold
81
+ const averagePassphrase = score < strongThreshold
82
+ const strongPassphrase = score >= strongThreshold
83
+
84
+ if (noPassphrase) {
85
+ return {...resultByScore[0], score}
86
+ } else if (commonPassphrase) {
87
+ return {...resultByScore[1], score}
88
+ } else if (weakPassphrase) {
89
+ return {...resultByScore[2], score}
90
+ } else if (averagePassphrase){
91
+ return {...resultByScore[3], score}
92
+ } else if (strongPassphrase) {
93
+ return {...resultByScore[4], score}
94
+ }
95
+ }
96
+
97
+ useEffect(
98
+ () => {
99
+ const delay = 400
100
+ const result = handleStrengthCalculation({ passphrase: input, isPwned: isPwned });
101
+
102
+ setCheckStrength({ ...result })
103
+
104
+ // only check the API for passphrases above the minimum size
105
+ if (input.length < 5) {
106
+ setIsPwned(false)
107
+ return
108
+ }
109
+
110
+ const handler = setTimeout(() => {
111
+ checkHaveIBeenPwned(input)
112
+ .then((pwned) => setIsPwned(pwned))
113
+ .catch(() => setIsPwned(false))
114
+ }, delay)
115
+
116
+ return () => {
117
+ clearTimeout(handler)
118
+ }
119
+ },
120
+ [input, isPwned]
121
+ )
122
+
10
123
  return (
11
124
  <>
12
125
  <div>
13
126
  <br />
14
127
  <Passphrase
15
- checkPwned
16
128
  onChange={handleChange}
17
129
  value={input}
18
130
  {...props}
19
131
  />
132
+ {checkStrength.percent > 0 ?
133
+ <>
134
+ <ProgressSimple
135
+ className={input.length === 0 ? "progress-empty-input" : null}
136
+ percent={checkStrength.percent}
137
+ variant={checkStrength.variant}
138
+ />
139
+ <Caption size='xs'
140
+ text={checkStrength.label}
141
+ />
142
+ </>
143
+ : null}
20
144
  </div>
21
145
  </>
22
146
  )
@@ -1,3 +1,12 @@
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.
1
+ Use <a href='https://haveibeenpwned.com/Passwords'>HaveIBeenPwned's</a> API to check for breached passwords.
2
+
3
+ As the passphrase is typed, it is checked against more than half a billion breached passwords, to help ensure its not compromised.
2
4
  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.
5
+
6
+ This uses their k-Anonymity model, so only the first 5 characters of a hashed copy of the passphrase are sent.
7
+
8
+ <div class="pb_pill_kit_warning"><div class="pb_title_kit_size_4 pb_pill_text">Disclaimer</div></div>
9
+
10
+ This example depends on the `zxcvbn` library and `haveibeenpwned` API.
11
+
12
+ You can use any library to achieve the same result, this example only intends to show how to add more features to the `Passphrase` kit.
@@ -0,0 +1,136 @@
1
+ <%= pb_rails("body", props: {
2
+ margin_bottom: "md",
3
+ id: "body_common"
4
+ }) %>
5
+
6
+ <%= pb_rails("passphrase", props: { label: "Passphrase", classname: "passphrase_common" }) %>
7
+
8
+ <%= pb_rails("progress_simple", props: { percent: 0, id: "bar_common" }) %>
9
+
10
+ <%= pb_rails("caption", props: { size: 'xs', text: "hello", id: "caption_common" }) %>
11
+
12
+
13
+ <%= javascript_tag do %>
14
+ window.addEventListener("DOMContentLoaded", () => {
15
+
16
+ const commonText = document.querySelector("#body_common")
17
+
18
+ // variables for the kits you are targeting
19
+ const passphrase = document.querySelector(".passphrase_common").querySelector("input")
20
+ const barVariant = document.getElementById("bar_common")
21
+ const barPercent = document.getElementById("bar_common").querySelector("div")
22
+ const caption = document.getElementById("caption_common")
23
+
24
+ // hide the bar and captions
25
+ barVariant.style.display = 'none';
26
+ barPercent.style.display = 'none';
27
+ caption.style.display = 'none';
28
+
29
+
30
+ const handleStrengthCalculation = (settings) => {
31
+ const {
32
+ passphrase = "",
33
+ common = false,
34
+ isPwned = false,
35
+ averageThreshold = 2,
36
+ minLength = 12,
37
+ strongThreshold = 3,
38
+ } = settings
39
+
40
+ const resultByScore = {
41
+ 0: {
42
+ variant: 'negative',
43
+ label: '',
44
+ percent: 0,
45
+ },
46
+ 1: {
47
+ variant: 'negative',
48
+ label: 'This passphrase is too common',
49
+ percent: 25,
50
+ },
51
+ 2: {
52
+ variant: 'negative',
53
+ label: 'Too weak',
54
+ percent: 25,
55
+ },
56
+ 3: {
57
+ variant: 'warning',
58
+ label: 'Almost there, keep going!',
59
+ percent: 50,
60
+ },
61
+ 4: {
62
+ variant: 'positive',
63
+ label: 'Success! Strong passphrase',
64
+ percent: 100,
65
+ }
66
+ }
67
+
68
+ const { score } = zxcvbn(passphrase);
69
+
70
+ const noPassphrase = passphrase.length <= 0
71
+ const commonPassphrase = common || isPwned
72
+ const weakPassphrase = passphrase.length < minLength || score < averageThreshold
73
+ const averagePassphrase = score < strongThreshold
74
+ const strongPassphrase = score >= strongThreshold
75
+
76
+ if (noPassphrase) {
77
+ return {...resultByScore[0], score}
78
+ } else if (commonPassphrase) {
79
+ return {...resultByScore[1], score}
80
+ } else if (weakPassphrase) {
81
+ return {...resultByScore[2], score}
82
+ } else if (averagePassphrase){
83
+ return {...resultByScore[3], score}
84
+ } else if (strongPassphrase) {
85
+ return {...resultByScore[4], score}
86
+ }
87
+ }
88
+
89
+ // array that holds the common passwords you wish to target
90
+ const COMMON_PASSPHRASES = ['passphrase', 'apple', 'password', 'p@55w0rd']
91
+
92
+ commonText.textContent = `Try typing any of the following: ${COMMON_PASSPHRASES.join(', ')}`
93
+
94
+ // function that checks if the user password is in the common password list
95
+ const isCommon = (passphrase) => {
96
+ if (COMMON_PASSPHRASES.includes(passphrase))
97
+ return true
98
+ return false
99
+ }
100
+
101
+ // event listeners attached to the input field
102
+ passphrase.addEventListener('input', (e) => {
103
+ const passphrase = e.target.value;
104
+
105
+ // pass in passphrase to the handleStrengthCalculation and set that equal to result variable
106
+ const result = handleStrengthCalculation({ passphrase: passphrase, common: isCommon(passphrase) })
107
+
108
+ // conditional statment to show or hide progress_simple bar and caption if user has entered a password
109
+ if (passphrase) {
110
+ barVariant.style.display = 'block';
111
+
112
+ barPercent.style.display = 'block';
113
+
114
+ caption.style.display = 'block';
115
+ } else {
116
+ barVariant.style.display = 'none';
117
+
118
+ barPercent.style.display = 'none';
119
+
120
+ caption.style.display = 'none';
121
+ }
122
+
123
+ // set the width of the progress_simple kit
124
+ barPercent.style.width = result.percent.toString()+ "%"
125
+
126
+
127
+ // set the variant of the progress_simple kit
128
+ barVariant.setAttribute("class", "pb_progress_simple_kit_"+ result.variant +"_left");
129
+
130
+
131
+ // set the text of the caption kit
132
+ caption.textContent = result.label
133
+ });
134
+
135
+ })
136
+ <% end %>
@@ -1,32 +1,114 @@
1
- import React, { useState } from 'react'
1
+ import React, { useState, useEffect } from 'react'
2
2
 
3
- import Passphrase from '../_passphrase'
4
- import Body from '../../pb_body/_body'
3
+ import {Body, Caption, Passphrase, ProgressSimple} from '../..'
4
+ import zxcvbn from 'zxcvbn'
5
5
 
6
6
  const PassphraseCommon = (props) => {
7
7
  const [input, setInput] = useState('')
8
-
8
+ const [checkStrength, setCheckStrength] = useState({
9
+ label: '',
10
+ percent: 0,
11
+ score: 0,
12
+ variant: ''
13
+ })
9
14
  const handleChange = (e) => setInput(e.target.value)
10
15
 
16
+ const handleStrengthCalculation = (settings) => {
17
+ const {
18
+ passphrase = "",
19
+ common = false,
20
+ isPwned = false,
21
+ averageThreshold = 2,
22
+ minLength = 12,
23
+ strongThreshold = 3,
24
+ } = settings
25
+
26
+ const resultByScore = {
27
+ 0: {
28
+ variant: 'negative',
29
+ label: '',
30
+ percent: 0,
31
+ },
32
+ 1: {
33
+ variant: 'negative',
34
+ label: 'This passphrase is too common',
35
+ percent: 25,
36
+ },
37
+ 2: {
38
+ variant: 'negative',
39
+ label: 'Too weak',
40
+ percent: 25,
41
+ },
42
+ 3: {
43
+ variant: 'warning',
44
+ label: 'Almost there, keep going!',
45
+ percent: 50,
46
+ },
47
+ 4: {
48
+ variant: 'positive',
49
+ label: 'Success! Strong passphrase',
50
+ percent: 100,
51
+ }
52
+ }
53
+
54
+ const { score } = zxcvbn(passphrase);
55
+
56
+ const noPassphrase = passphrase.length <= 0
57
+ const commonPassphrase = common || isPwned
58
+ const weakPassphrase = passphrase.length < minLength || score < averageThreshold
59
+ const averagePassphrase = score < strongThreshold
60
+ const strongPassphrase = score >= strongThreshold
61
+
62
+ if (noPassphrase) {
63
+ return {...resultByScore[0], score}
64
+ } else if (commonPassphrase) {
65
+ return {...resultByScore[1], score}
66
+ } else if (weakPassphrase) {
67
+ return {...resultByScore[2], score}
68
+ } else if (averagePassphrase){
69
+ return {...resultByScore[3], score}
70
+ } else if (strongPassphrase) {
71
+ return {...resultByScore[4], score}
72
+ }
73
+ }
74
+
11
75
  const COMMON_PASSPHRASES = ['passphrase', 'apple', 'password', 'p@55w0rd']
12
76
 
13
- const commonCheck = (passphrase) => {
77
+ const isCommon = (passphrase) => {
14
78
  if (COMMON_PASSPHRASES.includes(passphrase))
15
79
  return true
16
80
  return false
17
81
  }
18
82
 
83
+ useEffect(() => {
84
+ const result = handleStrengthCalculation({ passphrase: input, common: isCommon(input) });
85
+ setCheckStrength({ ...result })
86
+ }, [input])
87
+
88
+
19
89
  return (
20
90
  <>
21
91
  <div>
22
- <Body text={`Try typing any of the following: ${COMMON_PASSPHRASES.join(' ')}`} />
23
- <br />
92
+ <Body
93
+ marginBottom='md'
94
+ text={`Try typing any of the following: ${COMMON_PASSPHRASES.join(', ')}`} />
24
95
  <Passphrase
25
- common={commonCheck(input)}
26
96
  onChange={handleChange}
27
97
  value={input}
28
98
  {...props}
29
99
  />
100
+ {input.length > 0 && (
101
+ <>
102
+ <ProgressSimple
103
+ className={input.length === 0 ? "progress-empty-input" : null}
104
+ percent={checkStrength.percent}
105
+ variant={checkStrength.variant}
106
+ />
107
+ <Caption size='xs'
108
+ text={checkStrength.label}
109
+ />
110
+ </>
111
+ )}
30
112
  </div>
31
113
  </>
32
114
  )
@@ -0,0 +1,5 @@
1
+ <div class="pb_pill_kit_warning"><div class="pb_title_kit_size_4 pb_pill_text">Disclaimer</div></div>
2
+
3
+ This example depends on the `zxcvbn` library.
4
+
5
+ You can use any library to achieve the same result, this example only intends to show how to add more features to the `Passphrase` kit.
@@ -0,0 +1,51 @@
1
+ <%= pb_rails("passphrase", props: { classname: "pass_input_1" }) %>
2
+
3
+ <%= pb_rails("passphrase", props: { confirmation: true, classname: "pass_input_2"}) %>
4
+
5
+ <div id="match"> </div>
6
+
7
+ <%= javascript_tag do %>
8
+ window.addEventListener("DOMContentLoaded", () => {
9
+
10
+ const useState = (defaultValue) => {
11
+ let value = defaultValue;
12
+ const getValue = () => value
13
+ const setValue = (newValue) => {
14
+ return value = newValue
15
+ }
16
+ return [getValue, setValue];
17
+ }
18
+
19
+ const [input, setInput] = useState('')
20
+ const [confirmationInput, setConfirmationInput] = useState('')
21
+
22
+ const match = document.querySelector("#match")
23
+
24
+ const input1 = document.querySelector(".pass_input_1").querySelector("input")
25
+ const input2 = document.querySelector(".pass_input_2").querySelector("input")
26
+
27
+ input1.addEventListener('input', (e) => {
28
+ setInput(e.target.value)
29
+ setMatchText()
30
+ });
31
+
32
+ input2.addEventListener('input', (e) => {
33
+ setConfirmationInput(e.target.value)
34
+ setMatchText()
35
+ });
36
+
37
+ const setMatchText = () => {
38
+
39
+ if (input() && confirmationInput()) {
40
+ if (input() === confirmationInput()) {
41
+ match.textContent = "They match!"
42
+ } else {
43
+ match.textContent = "They don't match!"
44
+ }
45
+ } else {
46
+ match.textContent = ""
47
+ }
48
+
49
+ }
50
+ })
51
+ <% end %>
@@ -0,0 +1,39 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import {Body, Passphrase} from '../..'
4
+
5
+ const PassphraseConfirmation = (props) => {
6
+ const [input, setInput] = useState('')
7
+ const [confirmationInput, setConfirmationInput] = useState('')
8
+
9
+ const handleChange = (e) => setInput(e.target.value)
10
+ const handleConfirmationChange = (e) => setConfirmationInput(e.target.value)
11
+
12
+ return (
13
+ <>
14
+ <div>
15
+ <Passphrase
16
+ onChange={handleChange}
17
+ value={input}
18
+ {...props}
19
+ />
20
+ <Passphrase
21
+ confirmation
22
+ onChange={handleConfirmationChange}
23
+ value={confirmationInput}
24
+ {...props}
25
+ />
26
+ {input && confirmationInput && (
27
+ <Body
28
+ text={
29
+ input === confirmationInput ? "They match!" : "They don't match!"
30
+ }
31
+ {...props}
32
+ />
33
+ )}
34
+ </div>
35
+ </>
36
+ );
37
+ }
38
+
39
+ export default PassphraseConfirmation
@@ -1,3 +1 @@
1
1
  <%= pb_rails("passphrase") %>
2
-
3
- <%= pb_rails("passphrase", props: { confirmation: true}) %>