playbook_ui 9.5.0 → 9.6.0

Sign up to get free protection for your applications and to get access to all the features.
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