playbook_ui 9.5.0 → 9.6.0

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/app/pb_kits/playbook/_playbook.scss +1 -0
  3. data/app/pb_kits/playbook/data/menu.yml +1 -0
  4. data/app/pb_kits/playbook/index.js +4 -3
  5. data/app/pb_kits/playbook/pb_passphrase/_passphrase.jsx +205 -0
  6. data/app/pb_kits/playbook/pb_passphrase/_passphrase.scss +73 -0
  7. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx +33 -0
  8. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb +3 -0
  9. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx +31 -0
  10. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md +1 -0
  11. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.html.erb +16 -0
  12. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.jsx +56 -0
  13. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.md +1 -0
  14. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.html.erb +10 -0
  15. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx +68 -0
  16. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md +9 -0
  17. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx +33 -0
  18. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md +3 -0
  19. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_tips.html.erb +26 -0
  20. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_tips.jsx +54 -0
  21. data/app/pb_kits/playbook/pb_passphrase/docs/_passphrase_tips.md +1 -0
  22. data/app/pb_kits/playbook/pb_passphrase/docs/example.yml +15 -0
  23. data/app/pb_kits/playbook/pb_passphrase/docs/index.js +6 -0
  24. data/app/pb_kits/playbook/pb_passphrase/passphrase.html.erb +1 -0
  25. data/app/pb_kits/playbook/pb_passphrase/passphrase.rb +36 -0
  26. data/app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx +123 -0
  27. data/app/pb_kits/playbook/pb_passphrase/passwordStrength.js +55 -0
  28. data/app/pb_kits/playbook/react_rails_kits.js +1 -0
  29. data/lib/playbook/version.rb +1 -1
  30. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2381b18440b9c161bb5fedd7adaa62cb6231ef81aff16a6078c37bd2a08368fa
4
- data.tar.gz: 7d969a36b50458b303453f005471dd10def62fafbcfa358ca12f15125536b32e
3
+ metadata.gz: d5c9c206b5e2196df28e9fdb9088f32c40c5e3c1813d6f540080c2d57274639c
4
+ data.tar.gz: af75f14e148548a6ac02dff2ccf0d3cd5a91606e77b9e2b0a99a2a99494b8bbe
5
5
  SHA512:
6
- metadata.gz: 171386b3c28a4b2a9f20fa038b9d61030baf429093a489162045633500356b0a05d8b63a6f735932adc95fac7acec4b61b309d01468d495e67ecde820f88689d
7
- data.tar.gz: ad65ae5c98a64bf1a09742ba5d5cd7ea94bebefedfba1ed00961c995d311eff8f116866d28f997761c813dbbfbf99809bf3e70c3d0a75d882c2dcc4263d38dd0
6
+ metadata.gz: 35e89c59f507a8b8b9066fd62ea2dbd957da3c13ff6ff28680eeed2cb6df74f131e08223be7e463373ee00d9383048e1158bf9a68dbd11d16f898cbc7597a20c
7
+ data.tar.gz: a930c3874d040650eed126e1ac89d75767bc2aa8a76a9b9235039b15eb2173e8b89c9f87966ad79859341010b699dfca023f9a62f1f6fd6a6b17430a349ee55e
@@ -55,6 +55,7 @@
55
55
  @import 'pb_multiple_users_stacked/multiple_users_stacked';
56
56
  @import 'pb_nav/nav';
57
57
  @import 'pb_online_status/online_status';
58
+ @import 'pb_passphrase/passphrase';
58
59
  @import 'pb_person/person';
59
60
  @import 'pb_person_contact/person_contact';
60
61
  @import 'pb_pill/pill';
@@ -26,6 +26,7 @@ kits:
26
26
  - form
27
27
  - form_group
28
28
  - form_pill
29
+ - passphrase
29
30
  - radio
30
31
  - rich_text_editor
31
32
  - select
@@ -61,6 +61,7 @@ export MultipleUsersStacked from './pb_multiple_users_stacked/_multiple_users_st
61
61
  export Nav from './pb_nav/_nav.jsx'
62
62
  export NavItem from './pb_nav/_item.jsx'
63
63
  export OnlineStatus from './pb_online_status/_online_status.jsx'
64
+ export Passphrase from './pb_passphrase/_passphrase.jsx'
64
65
  export PbReactPopover from './pb_popover/_popover.jsx'
65
66
  export Person from './pb_person/_person.jsx'
66
67
  export PersonContact from './pb_person_contact/_person_contact.jsx'
@@ -84,13 +85,13 @@ export StatChange from './pb_stat_change/_stat_change.jsx'
84
85
  export StatValue from './pb_stat_value/_stat_value.jsx'
85
86
  export Table from './pb_table/_table.jsx'
86
87
  export TableRow from './pb_table/_table_row.jsx'
87
- export Textarea from './pb_textarea/_textarea.jsx'
88
88
  export TextInput from './pb_text_input/_text_input.jsx'
89
+ export Textarea from './pb_textarea/_textarea.jsx'
89
90
  export Time from './pb_time/_time.jsx'
90
- export Timeline from './pb_timeline/_timeline.jsx'
91
+ export TimeRangeInline from './pb_time_range_inline/_time_range_inline.jsx'
91
92
  export TimeStacked from './pb_time_stacked/_time_stacked.jsx'
93
+ export Timeline from './pb_timeline/_timeline.jsx'
92
94
  export Timestamp from './pb_timestamp/_timestamp.jsx'
93
- export TimeRangeInline from './pb_time_range_inline/_time_range_inline.jsx'
94
95
  export Title from './pb_title/_title.jsx'
95
96
  export TitleCount from './pb_title_count/_title_count.jsx'
96
97
  export TitleDetail from './pb_title_detail/_title_detail.jsx'
@@ -0,0 +1,205 @@
1
+
2
+ /* @flow */
3
+
4
+ import React, { useCallback, useEffect, useMemo, useState } from 'react'
5
+ import classnames from 'classnames'
6
+ import { buildAriaProps, buildCss, buildDataProps } from '../utilities/props'
7
+ import { globalProps } from '../utilities/globalProps.js'
8
+ import { zxcvbnPasswordScore } from './passwordStrength.js'
9
+ import { Body, Caption, Flex, Icon, PbReactPopover, ProgressSimple, TextInput } from '../'
10
+
11
+ type PassphraseProps = {
12
+ aria?: object,
13
+ averageThreshold?: number,
14
+ common?: boolean,
15
+ confirmation?: boolean,
16
+ className?: string,
17
+ data?: object,
18
+ dark?: boolean,
19
+ id?: string,
20
+ inputProps?: {},
21
+ label?: string,
22
+ minLength?: number,
23
+ onChange: (String) => void,
24
+ showTipsBelow?: 'always' | 'xs' | 'sm' | 'md' | 'lg' | 'xl',
25
+ onStrengthChange?: (number) => void,
26
+ strongThreshold?: number,
27
+ tips?: Array<string>,
28
+ uncontrolled?: boolean,
29
+ value: string,
30
+ }
31
+
32
+ const Passphrase = (props: PassphraseProps) => {
33
+ const {
34
+ aria = {},
35
+ averageThreshold = 2,
36
+ className,
37
+ common = false,
38
+ confirmation = false,
39
+ dark = false,
40
+ data = {},
41
+ id,
42
+ inputProps = {},
43
+ label = confirmation ? 'Confirm Passphrase' : 'Passphrase',
44
+ minLength,
45
+ onChange = () => {},
46
+ showTipsBelow = 'always',
47
+ onStrengthChange,
48
+ strongThreshold = 3,
49
+ tips = [],
50
+ uncontrolled = false,
51
+ value = '',
52
+ } = props
53
+
54
+ const [uncontrolledValue, setUncontrolledValue] = useState('')
55
+
56
+ const handleChange = useCallback(
57
+ (e) => uncontrolled ? setUncontrolledValue(e.target.value) : onChange(e),
58
+ [uncontrolled, onChange]
59
+ )
60
+
61
+ const displayValue = useMemo(
62
+ () => (uncontrolled ? uncontrolledValue : value),
63
+ [value, uncontrolledValue, uncontrolled],
64
+ )
65
+
66
+ const [showPopover, setShowPopover] = useState(false)
67
+ const toggleShowPopover = () => setShowPopover(!showPopover)
68
+ const [showPassphrase, setShowPassphrase] = useState(false)
69
+ const toggleShowPassphrase = () => setShowPassphrase(!showPassphrase)
70
+
71
+ const ariaProps = buildAriaProps(aria)
72
+ const dataProps = buildDataProps(data)
73
+ const classes = classnames(buildCss('pb_passphrase'), globalProps(props), className)
74
+
75
+ const calculator = useMemo(
76
+ () => confirmation ? { test: () => ({}) } : zxcvbnPasswordScore({ averageThreshold, strongThreshold, minLength }),
77
+ [averageThreshold, confirmation, strongThreshold, minLength]
78
+ )
79
+
80
+ const { percent: progressPercent, variant: progressVariant, text: strengthLabel, strength } = calculator.test(displayValue, common)
81
+
82
+ useEffect(() => {
83
+ if (typeof onStrengthChange === 'function') {
84
+ onStrengthChange(strength)
85
+ }
86
+ }, [strength])
87
+
88
+ const tipClass = classnames(
89
+ (dark ? 'dark' : null),
90
+ (showTipsBelow === 'always' ? null : `show-below-${showTipsBelow}`),
91
+ )
92
+
93
+ const popoverReference = (
94
+ <a
95
+ className={tipClass}
96
+ onClick={toggleShowPopover}
97
+ >
98
+ <Icon
99
+ dark={dark}
100
+ icon="info-circle"
101
+ size="xs"
102
+ variant="link"
103
+ />
104
+ </a>
105
+ )
106
+
107
+ return (
108
+ <div
109
+ {...ariaProps}
110
+ {...dataProps}
111
+ className={classes}
112
+ id={id}
113
+ >
114
+ <label>
115
+ <Flex align="baseline">
116
+ <Caption
117
+ className="passphrase-label"
118
+ text={label}
119
+ />
120
+ <If condition={tips.length > 0 && !confirmation}>
121
+ <PbReactPopover
122
+ placement="right"
123
+ reference={popoverReference}
124
+ show={showPopover}
125
+ >
126
+ <Flex
127
+ align="center"
128
+ orientation="column"
129
+ >
130
+ <Caption
131
+ marginBottom="xs"
132
+ text="Tips for a good passphrase"
133
+ />
134
+ <div>
135
+ {
136
+ tips.map((tip, i) => (
137
+ <Caption
138
+ key={i}
139
+ marginBottom="xs"
140
+ size="xs"
141
+ >
142
+ <Icon
143
+ icon="shield-check"
144
+ marginRight="xs"
145
+ />
146
+ {tip}
147
+ </Caption>
148
+ ))
149
+ }
150
+ </div>
151
+ </Flex>
152
+ </PbReactPopover>
153
+ </If>
154
+ </Flex>
155
+ <div className="passphrase-text-input-wrapper">
156
+ <TextInput
157
+ className="passphrase-text-input"
158
+ dark={dark}
159
+ marginBottom="xs"
160
+ onChange={handleChange}
161
+ placeholder="Enter a passphrase..."
162
+ type={showPassphrase ? 'text' : 'password'}
163
+ value={displayValue}
164
+ {...inputProps}
165
+ />
166
+ <span
167
+ className="show-passphrase-icon"
168
+ dark={dark}
169
+ onClick={toggleShowPassphrase}
170
+ >
171
+ <Body
172
+ className={showPassphrase ? 'hide-icon' : ''}
173
+ color="light"
174
+ dark={dark}
175
+ >
176
+ <Icon icon="eye-slash" />
177
+ </Body>
178
+ <Body
179
+ className={showPassphrase ? '' : 'hide-icon'}
180
+ color="light"
181
+ dark={dark}
182
+ >
183
+ <Icon icon="eye" />
184
+ </Body>
185
+ </span>
186
+ </div>
187
+ </label>
188
+ <If condition={!confirmation}>
189
+ <ProgressSimple
190
+ className={displayValue.length === 0 ? 'progress-empty-input' : null}
191
+ dark={dark}
192
+ percent={progressPercent}
193
+ variant={progressVariant}
194
+ />
195
+ <Caption
196
+ dark={dark}
197
+ size="xs"
198
+ text={strengthLabel}
199
+ />
200
+ </If>
201
+ </div>
202
+ )
203
+ }
204
+
205
+ export default Passphrase
@@ -0,0 +1,73 @@
1
+ @import "../tokens/colors";
2
+ @import "../tokens/spacing";
3
+ @import "../tokens/screen_sizes";
4
+
5
+ .pb_passphrase {
6
+ margin-bottom: $space_sm;
7
+
8
+ label {
9
+ display: inline;
10
+ }
11
+
12
+ a.dark {
13
+ color: $white;
14
+ }
15
+
16
+ a {
17
+
18
+ &[class*=show-below-] {
19
+ display: none;
20
+ }
21
+ @each $breakpoint_name, $breakpoint in $breakpoints {
22
+ &.show-below-#{$breakpoint_name} {
23
+ @include break_at($breakpoint) {
24
+ display: inline;
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ .passphrase-label {
31
+ margin-right: ($space_xs/2);
32
+ }
33
+
34
+ .passphrase-text-input-wrapper {
35
+ position: relative;
36
+
37
+ .pb_text_input_kit_label {
38
+ margin-bottom: ($space_xs/2);
39
+ }
40
+
41
+ .passphrase-text-input input {
42
+ padding-right: 42px;
43
+ }
44
+
45
+ .passphrase-text-input .text_input_wrapper {
46
+ margin-bottom: 0;
47
+ }
48
+
49
+ .show-passphrase-icon {
50
+ position: absolute;
51
+ right: 11px;
52
+ top: 11px;
53
+ cursor: pointer;
54
+ @media (hover:hover) {
55
+ &:hover .pb_body_kit_light {
56
+ color: $primary;
57
+ }
58
+ }
59
+
60
+ .hide-icon {
61
+ display: none;
62
+ }
63
+ }
64
+ }
65
+
66
+ .pb_progress_simple_wrapper, .pb_progress_simple_wrapper_dark {
67
+ margin-bottom: ($space_xs/2);
68
+
69
+ &.progress-empty-input {
70
+ visibility: hidden;
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,33 @@
1
+ import React, { useState } from 'react'
2
+ import { Body, Passphrase } from '../../'
3
+
4
+ const PassphraseCommon = (props) => {
5
+ const [input, setInput] = useState('')
6
+
7
+ const handleChange = (e) => setInput(e.target.value)
8
+
9
+ const COMMON_PASSPHRASES = ['passphrase', 'apple', 'password', 'p@55w0rd']
10
+
11
+ const commonCheck = (passphrase) => {
12
+ if (COMMON_PASSPHRASES.includes(passphrase))
13
+ return true
14
+ return false
15
+ }
16
+
17
+ return (
18
+ <>
19
+ <div>
20
+ <Body text={`Try typing any of the following: ${COMMON_PASSPHRASES.join(' ')}`} />
21
+ <br />
22
+ <Passphrase
23
+ common={commonCheck(input)}
24
+ onChange={handleChange}
25
+ value={input}
26
+ {...props}
27
+ />
28
+ </div>
29
+ </>
30
+ )
31
+ }
32
+
33
+ export default PassphraseCommon
@@ -0,0 +1,3 @@
1
+ <%= pb_rails("passphrase") %>
2
+
3
+ <%= pb_rails("passphrase", props: { confirmation: true}) %>
@@ -0,0 +1,31 @@
1
+ import React, { useState } from 'react'
2
+ import { Passphrase } from '../../'
3
+
4
+ const PassphraseDefault = (props) => {
5
+ const [input, setInput] = useState('')
6
+ const handleChange = (e) => setInput(e.target.value)
7
+
8
+ const [confoInput, setConfoInput] = useState('')
9
+ const handleConfoChange = (e) => setConfoInput(e.target.value)
10
+
11
+ return (
12
+ <>
13
+ <div>
14
+ <Passphrase
15
+ onChange={handleChange}
16
+ value={input}
17
+ {...props}
18
+ />
19
+ <Passphrase
20
+ confirmation
21
+ onChange={handleConfoChange}
22
+ value={confoInput}
23
+ {...props}
24
+ />
25
+ <span>{input === confoInput ? 'They match!' : 'They don\'t match!'}</span>
26
+ </div>
27
+ </>
28
+ )
29
+ }
30
+
31
+ export default PassphraseDefault
@@ -0,0 +1 @@
1
+ Use the `confirmation` prop to only include the label and show/hide icon.
@@ -0,0 +1,16 @@
1
+ <%= pb_rails("passphrase", props: {
2
+ input_props: {
3
+ disabled: true,
4
+ id: "my-disabled-passphrase",
5
+ name: "my-disabled-field",
6
+ },
7
+ label: "Input props passed directly to input kit"
8
+ }) %>
9
+
10
+ <%= pb_rails("passphrase", props: {
11
+ input_props: {
12
+ id: "my-custome-id",
13
+ name: "my-value-name",
14
+ },
15
+ label: "Set name, id, etc for use in forms"
16
+ }) %>
@@ -0,0 +1,56 @@
1
+ import React, { useState } from 'react'
2
+ import { Passphrase } from '../../'
3
+
4
+ const PassphraseInputProps = (props) => {
5
+ const [input, setInput] = useState('')
6
+
7
+ const handleChange = (e) => setInput(e.target.value)
8
+
9
+ return (
10
+ <>
11
+ <div>
12
+ <Passphrase
13
+ inputProps={{
14
+ name: 'my-disabled-field',
15
+ id: 'my-value-id',
16
+ disabled: true,
17
+ }}
18
+ label="Pass props directly to input kit"
19
+ onChange={handleChange}
20
+ value={input}
21
+ {...props}
22
+ />
23
+ <Passphrase
24
+ inputProps={{
25
+ children: (
26
+ <input
27
+ onChange={handleChange}
28
+ type="password"
29
+ value={input}
30
+ />),
31
+ }}
32
+ label="Custom input"
33
+ onChange={handleChange}
34
+ value={input}
35
+ {...props}
36
+ />
37
+ <Passphrase
38
+ inputProps={{ name: 'my-value-name', id: 'my-value-id' }}
39
+ label="Set name and ID for use in form libraries"
40
+ onChange={handleChange}
41
+ value={input}
42
+ {...props}
43
+ />
44
+ <Passphrase
45
+ confirmation
46
+ inputProps={{ name: 'my-value-confirmation-name', id: 'my-value-confirmation-id' }}
47
+ onChange={handleChange}
48
+ value={input}
49
+ {...props}
50
+ />
51
+ </div>
52
+ </>
53
+ )
54
+ }
55
+
56
+ export default PassphraseInputProps
@@ -0,0 +1 @@
1
+ `inputProps` is passed directly to an underlying Text Input kit. See the specific docs <a href="/kits/text_input/react" target="_blank">here</a> for more details.
@@ -0,0 +1,10 @@
1
+
2
+ <%= pb_rails("passphrase", props: { label: "Default Settings"}) %>
3
+
4
+ <%= pb_rails("passphrase", props: { label: "Min length = 5", min_length: 5}) %>
5
+
6
+ <%= pb_rails("passphrase", props: { label: "Min length = 30", min_length: 30}) %>
7
+
8
+ <%= pb_rails("passphrase", props: { average_threshold: 1, label: "Average Threshold = 1"}) %>
9
+
10
+ <%= pb_rails("passphrase", props: { label: "Strong Threshold = 4", strong_threshold: 4}) %>
@@ -0,0 +1,68 @@
1
+ import React, { useState } from 'react'
2
+ import { Body, Passphrase, TextInput } from '../../'
3
+
4
+ const PassphraseMeterSettings = (props) => {
5
+ const [input, setInput] = useState('')
6
+
7
+ const handleChange = (e) => setInput(e.target.value)
8
+
9
+ const [strength, setStrength] = useState(0)
10
+ const handleStrengthChange = (str) => setStrength(str)
11
+ return (
12
+ <>
13
+ <div>
14
+ <Body>
15
+ {'These examples will all share the same input value. Type in any of the inputs to see how the strength meter changes in response to different settings.'}
16
+ </Body>
17
+ <br />
18
+ <TextInput
19
+ disabled
20
+ label="Calculated Strength"
21
+ readOnly
22
+ value={strength}
23
+ />
24
+
25
+ <Passphrase
26
+ label="Default settings"
27
+ onChange={handleChange}
28
+ onStrengthChange={handleStrengthChange}
29
+ value={input}
30
+ {...props}
31
+ />
32
+
33
+ <Passphrase
34
+ label="Min length = 5"
35
+ minLength={5}
36
+ onChange={handleChange}
37
+ value={input}
38
+ {...props}
39
+ />
40
+ <Passphrase
41
+ label="Min length = 30"
42
+ minLength={30}
43
+ onChange={handleChange}
44
+ value={input}
45
+ {...props}
46
+ />
47
+
48
+ <Passphrase
49
+ averageThreshold={1}
50
+ label="Average threshold = 1"
51
+ onChange={handleChange}
52
+ value={input}
53
+ {...props}
54
+ />
55
+
56
+ <Passphrase
57
+ label="Strong Threshold = 4"
58
+ onChange={handleChange}
59
+ strongThreshold={4}
60
+ value={input}
61
+ {...props}
62
+ />
63
+ </div>
64
+ </>
65
+ )
66
+ }
67
+
68
+ export default PassphraseMeterSettings
@@ -0,0 +1,9 @@
1
+ By default, the minimum length is 12 and the strength meter will show a strength of 1 if not met. Notice the bar won't change from red until the minimum is met
2
+ Use the `minLength` prop to adjust.
3
+
4
+
5
+ The meter also response to `averageThreshold` and `strongTreshold` props. `averageThresold` defaults to 2, and `strongThreshold` defaults to 3.
6
+ This means that the bar will turn yellow when the strength of the passphrase is calculated to be 2 on a 0-4 scale, and green when 3.
7
+
8
+ Adjust these props to tune the sensitivity of the bar.
9
+ Note: minimum length trumps strength and will set the bar to a red color, despite whatever strength is calculated.
@@ -0,0 +1,33 @@
1
+ import React, { useState } from 'react'
2
+ import { Passphrase, TextInput } from '../../'
3
+
4
+ const PassphraseStrengthChange = (props) => {
5
+ const [input, setInput] = useState('')
6
+
7
+ const handleChange = (e) => setInput(e.target.value)
8
+
9
+ const [strength, setStrength] = useState(0)
10
+ const handleStrengthChange = (str) => setStrength(str)
11
+
12
+ return (
13
+ <>
14
+ <div>
15
+ <Passphrase
16
+ label="Passphrase"
17
+ onChange={handleChange}
18
+ onStrengthChange={handleStrengthChange}
19
+ value={input}
20
+ {...props}
21
+ />
22
+ <TextInput
23
+ disabled
24
+ label="Passphrase Strength"
25
+ readOnly
26
+ value={strength}
27
+ />
28
+ </div>
29
+ </>
30
+ )
31
+ }
32
+
33
+ export default PassphraseStrengthChange
@@ -0,0 +1,3 @@
1
+ As the strength of the entered passphrase changes, the optional `onStrengthChange` callback is called with the new strength value. This exposes the calculated strength.
2
+
3
+ Strength is calculated on a 0-4 scale by the <a href="https://github.com/dropbox/zxcvbn" target="_blank"> Zxcvbn package</a>
@@ -0,0 +1,26 @@
1
+ <%= pb_rails("passphrase", props: {
2
+ label: "Pass an array of strings to the tips prop",
3
+ tips: ['And the info icon will appear.', 'Each string will be displayed as its own tip'],
4
+ }) %>
5
+
6
+ <%= pb_rails("passphrase", props: {
7
+ label: "Omit the prop to hide the icon"
8
+ }) %>
9
+
10
+ <%= pb_rails("passphrase", props: {
11
+ label: "Only show tips at small screen size",
12
+ show_tips_below: "sm",
13
+ tips: ['Make the password longer', 'Type more things', 'Use something else'],
14
+ }) %>
15
+
16
+ <%= pb_rails("passphrase", props: {
17
+ label: "Only show tips at medium screen size",
18
+ show_tips_below: "md",
19
+ tips: ['Make the password longer', 'Type more things', 'Use something else'],
20
+ }) %>
21
+
22
+ <%= pb_rails("passphrase", props: {
23
+ label: "Only show tips at large screen size",
24
+ show_tips_below: "lg",
25
+ tips: ['Make the password longer', 'Type more things', 'Use something else'],
26
+ }) %>
@@ -0,0 +1,54 @@
1
+ import React, { useState } from 'react'
2
+ import { Passphrase } from '../../'
3
+
4
+ const PassphraseTips = (props) => {
5
+ const [input, setInput] = useState('')
6
+
7
+ const handleChange = (e) => setInput(e.target.value)
8
+
9
+ return (
10
+ <>
11
+ <div>
12
+ <Passphrase
13
+ label="Pass an array of strings to the tips prop"
14
+ onChange={handleChange}
15
+ tips={['And the info icon will appear.', 'Each string will be displayed as its own tip']}
16
+ value={input}
17
+ {...props}
18
+ />
19
+ <Passphrase
20
+ label="Omit the prop to hide the icon"
21
+ onChange={handleChange}
22
+ value={input}
23
+ {...props}
24
+ />
25
+ <Passphrase
26
+ label="Only show tips at small screen size"
27
+ onChange={handleChange}
28
+ showTipsBelow="xs"
29
+ tips={['Make the password longer', 'Type more things', 'Use something else']}
30
+ value={input}
31
+ {...props}
32
+ />
33
+ <Passphrase
34
+ label="Only show tips at medium screen size"
35
+ onChange={handleChange}
36
+ showTipsBelow="md"
37
+ tips={['Make the password longer', 'Type more things', 'Use something else']}
38
+ value={input}
39
+ {...props}
40
+ />
41
+ <Passphrase
42
+ label="Only show tips at large screen size"
43
+ onChange={handleChange}
44
+ showTipsBelow="lg"
45
+ tips={['Make the password longer', 'Type more things', 'Use something else']}
46
+ value={input}
47
+ {...props}
48
+ />
49
+ </div>
50
+ </>
51
+ )
52
+ }
53
+
54
+ export default PassphraseTips
@@ -0,0 +1 @@
1
+ `showTipsBelow`(react) / `show_tips_below`(rails) takes 'xs', 'sm', 'md', 'lg', 'xl' and only show the tips below the given screen size. Similar to the <a href="/kits/table/react" target="_blank">responsive table breakpoints.</a> Omit this prop to always show.
@@ -0,0 +1,15 @@
1
+ examples:
2
+
3
+ rails:
4
+ - passphrase_default: Default
5
+ - passphrase_meter_settings: Meter Settings
6
+ - passphrase_input_props: Input Props
7
+ - passphrase_tips: Tips
8
+
9
+ react:
10
+ - passphrase_default: Default
11
+ - passphrase_meter_settings: Meter Settings
12
+ - passphrase_input_props: Input Props
13
+ - passphrase_tips: Tips
14
+ - passphrase_strength_change: Strength Change
15
+ - passphrase_common: Common Passphrases
@@ -0,0 +1,6 @@
1
+ export { default as PassphraseDefault } from './_passphrase_default.jsx'
2
+ export { default as PassphraseMeterSettings } from './_passphrase_meter_settings'
3
+ export { default as PassphraseInputProps } from './_passphrase_input_props'
4
+ export { default as PassphraseTips } from './_passphrase_tips'
5
+ export { default as PassphraseStrengthChange } from './_passphrase_strength_change'
6
+ export { default as PassphraseCommon } from './_passphrase_common'
@@ -0,0 +1 @@
1
+ <%= react_component('Passphrase', object.passphrase_options) %>
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playbook
4
+ module PbPassphrase
5
+ class Passphrase < Playbook::KitBase
6
+ prop :average_threshold
7
+ prop :confirmation, type: Playbook::Props::Boolean, default: false
8
+ prop :input_props, type: Playbook::Props::Hash, default: {}
9
+ prop :label
10
+ prop :min_length
11
+ prop :show_tips_below
12
+ prop :strong_threshold
13
+ prop :tips, type: Playbook::Props::Array, default: []
14
+
15
+ def classname
16
+ generate_classname("pb_passphrase")
17
+ end
18
+
19
+ def passphrase_options
20
+ {
21
+ dark: dark,
22
+ id: id,
23
+ averageThreshold: average_threshold,
24
+ confirmation: confirmation,
25
+ inputProps: input_props,
26
+ label: label,
27
+ minLength: min_length,
28
+ showTipsBelow: show_tips_below,
29
+ strongThreshold: strong_threshold,
30
+ tips: tips,
31
+ uncontrolled: true,
32
+ }.compact
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,123 @@
1
+ import React from 'react'
2
+ import { render, screen } from '../utilities/test-utils'
3
+ import { Passphrase } from '../'
4
+
5
+ const testId = 'text-input1',
6
+ kitClass = 'pb_passphrase'
7
+
8
+ /* See these resources for more testing info:
9
+ - https://github.com/testing-library/jest-dom#usage for useage and examples
10
+ - https://jestjs.io/docs/en/using-matchers
11
+ */
12
+
13
+ test('returns namespaced class name', () => {
14
+ render(
15
+ <Passphrase
16
+ data={{ testid: testId }}
17
+ />
18
+ )
19
+
20
+ const kit = screen.getByTestId(testId)
21
+ expect(kit).toHaveClass(kitClass)
22
+ })
23
+
24
+ test('returns additional class name', () => {
25
+ render(
26
+ <Passphrase
27
+ className="additional_class"
28
+ data={{ testid: testId }}
29
+ />
30
+ )
31
+
32
+ const kit = screen.getByTestId(testId)
33
+ expect(kit).toHaveClass(`${kitClass} additional_class`)
34
+ })
35
+
36
+ test('returns dark class name', () => {
37
+ render(
38
+ <Passphrase
39
+ dark
40
+ data={{ testid: testId }}
41
+ />
42
+ )
43
+
44
+ const kit = screen.getByTestId(testId)
45
+ expect(kit).toHaveClass(`${kitClass} dark`)
46
+ })
47
+
48
+ test('passes input props to input element', () => {
49
+ render(
50
+ <Passphrase
51
+ data={{ testid: testId }}
52
+ inputProps={{
53
+ name: 'test-name',
54
+ id: 'test-input-id',
55
+ disabled: true,
56
+ }}
57
+ />
58
+ )
59
+
60
+ const kit = screen.getByTestId(testId)
61
+ const input = kit.getElementsByTagName('input')[0]
62
+ expect(input).toHaveAttribute('name', 'test-name')
63
+ expect(input).toHaveAttribute('id', 'test-input-id')
64
+ expect(input).toBeDisabled()
65
+ })
66
+
67
+ test('progress bar is invisible when value is empty', () => {
68
+ render(
69
+ <Passphrase
70
+ data={{ testid: testId }}
71
+ />
72
+ )
73
+
74
+ const kit = screen.getByTestId(testId)
75
+ expect(kit.querySelector('[class^=pb_progress_simple_wrapper]')).toHaveClass('progress-empty-input')
76
+ })
77
+
78
+ test('progress bar is visible when value is not empty', () => {
79
+ render(
80
+ <Passphrase
81
+ data={{ testid: testId }}
82
+ value="test_password_input"
83
+ />
84
+ )
85
+
86
+ const kit = screen.getByTestId(testId)
87
+ expect(kit.querySelector('[class^=pb_progress_simple_wrapper]')).not.toHaveClass('progress-empty-input')
88
+ })
89
+
90
+ test('no progress bar is show when confirmation is true', () => {
91
+ render(
92
+ <Passphrase
93
+ confirmation
94
+ data={{ testid: testId }}
95
+ />
96
+ )
97
+
98
+ const kit = screen.getByTestId(testId)
99
+ expect(kit.querySelector('[class^=pb_progress_simple_wrapper]')).toBeNull()
100
+ })
101
+
102
+ test('popover target shows when tips are given', () => {
103
+ render(
104
+ <Passphrase
105
+ data={{ testid: testId }}
106
+ tips={['some helpful tips']}
107
+ />
108
+ )
109
+
110
+ const kit = screen.getByTestId(testId)
111
+ expect(kit.querySelector('[class^=pb_popover_reference_wrapper]')).toBeDefined()
112
+ })
113
+
114
+ test('popover target does not show when tips are not given', () => {
115
+ render(
116
+ <Passphrase
117
+ data={{ testid: testId }}
118
+ />
119
+ )
120
+
121
+ const kit = screen.getByTestId(testId)
122
+ expect(kit.querySelector('[class^=pb_popover_reference_wrapper]')).toBeNull()
123
+ })
@@ -0,0 +1,55 @@
1
+ import zxcvbn from 'zxcvbn'
2
+
3
+ export const zxcvbnPasswordScore = (options) => {
4
+ const {
5
+ calculate = zxcvbn,
6
+ averageThreshold = 2,
7
+ strongThreshold = 3,
8
+ minLength = 12,
9
+ } = options
10
+
11
+ return {
12
+ minLength,
13
+ averageThreshold,
14
+ strongThreshold,
15
+ test: function (password = '', common = false) {
16
+ const feedbackValues = (str) => {
17
+ let percent, variant, text
18
+
19
+ if (password.length <= 0) {
20
+ percent = '0'
21
+ variant = 'negative'
22
+ text = '\u00A0' //nbsp to keep form from jumping when typing beings
23
+ } else if (common) {
24
+ percent = '25'
25
+ variant = 'negative'
26
+ text = 'This passphrase is too common'
27
+ } else if (password.length < this.minLength || str < this.averageThreshold) {
28
+ percent = '25'
29
+ variant = 'negative'
30
+ text = 'Too weak'
31
+ } else if (str < this.strongThreshold){
32
+ percent = '50'
33
+ variant = 'warning'
34
+ text = 'Almost there, keep going!'
35
+ } else if (str >= this.strongThreshold) {
36
+ percent = '100'
37
+ variant = 'positive'
38
+ text = 'Success! Strong passphrase'
39
+ }
40
+ return { percent, variant, text }
41
+ }
42
+
43
+ const result = calculate(password)
44
+
45
+ return (
46
+ {
47
+ suggestions: result.feedback.suggestions,
48
+ warning: result.feedback.warning,
49
+ strength: result.score,
50
+ ...feedbackValues(result.score),
51
+ }
52
+ )
53
+ },
54
+ }
55
+ }
@@ -3,6 +3,7 @@ export { default as BarGraph } from './pb_bar_graph/_bar_graph.jsx'
3
3
  export { default as DistributionBar } from './pb_distribution_bar/_distribution_bar.jsx'
4
4
  export { default as Legend } from './pb_legend/_legend.jsx'
5
5
  export { default as LineGraph } from './pb_line_graph/_line_graph.jsx'
6
+ export { default as Passphrase } from './pb_passphrase/_passphrase.jsx'
6
7
  export { default as Typeahead } from './pb_typeahead/_typeahead.jsx'
7
8
  export { default as RichTextEditor } from './pb_rich_text_editor/_rich_text_editor.jsx'
8
9
  export { default as Dialog } from './pb_dialog/_dialog.jsx'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playbook
4
- VERSION = "9.5.0"
4
+ VERSION = "9.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: playbook_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.5.0
4
+ version: 9.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Power UX
@@ -1378,6 +1378,29 @@ files:
1378
1378
  - app/pb_kits/playbook/pb_online_status/docs/index.js
1379
1379
  - app/pb_kits/playbook/pb_online_status/online_status.html.erb
1380
1380
  - app/pb_kits/playbook/pb_online_status/online_status.rb
1381
+ - app/pb_kits/playbook/pb_passphrase/_passphrase.jsx
1382
+ - app/pb_kits/playbook/pb_passphrase/_passphrase.scss
1383
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_common.jsx
1384
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.html.erb
1385
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.jsx
1386
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_default.md
1387
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.html.erb
1388
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.jsx
1389
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_input_props.md
1390
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.html.erb
1391
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.jsx
1392
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_meter_settings.md
1393
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.jsx
1394
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_strength_change.md
1395
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_tips.html.erb
1396
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_tips.jsx
1397
+ - app/pb_kits/playbook/pb_passphrase/docs/_passphrase_tips.md
1398
+ - app/pb_kits/playbook/pb_passphrase/docs/example.yml
1399
+ - app/pb_kits/playbook/pb_passphrase/docs/index.js
1400
+ - app/pb_kits/playbook/pb_passphrase/passphrase.html.erb
1401
+ - app/pb_kits/playbook/pb_passphrase/passphrase.rb
1402
+ - app/pb_kits/playbook/pb_passphrase/passphrase.test.jsx
1403
+ - app/pb_kits/playbook/pb_passphrase/passwordStrength.js
1381
1404
  - app/pb_kits/playbook/pb_person/_person.jsx
1382
1405
  - app/pb_kits/playbook/pb_person/_person.scss
1383
1406
  - app/pb_kits/playbook/pb_person/docs/_description.md
@@ -2107,7 +2130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
2107
2130
  - !ruby/object:Gem::Version
2108
2131
  version: '0'
2109
2132
  requirements: []
2110
- rubygems_version: 3.1.4
2133
+ rubyforge_project:
2134
+ rubygems_version: 2.7.3
2111
2135
  signing_key:
2112
2136
  specification_version: 4
2113
2137
  summary: Playbook Design System