@@ -4,12 +4,13 @@ import * as fui from "@fluentui/react-components";
44import { ReactElement } from "react" ;
55import { GeneratorProps } from "../Page" ;
66import GeneratorForm from "../components/GeneratorForm" ;
7+ import { DEFAULT_PASSWORD_LENGTH , MAX_PASSWORD_LENGTH , MIN_PASSWORD_LENGTH } from "@/utils/constants" ;
78
89// TODO: needs refactoring
910export default function PasswordSection ( props : GeneratorProps ) : ReactElement
1011{
1112 const [ state , private_setState ] = useState < PasswordSectionState > ( {
12- length : 8 ,
13+ length : DEFAULT_PASSWORD_LENGTH ,
1314 enableUppercase : true , uppercaseCount : 1 ,
1415 enableLowercase : true , lowercaseCount : 1 ,
1516 enableNumeric : true , numericCount : 1 ,
@@ -19,7 +20,11 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
1920 excludeSimilar : true ,
2021 excludeAmbiguous : true ,
2122 excludeRepeating : false ,
22- excludeCustom : false , excludeCustomSet : ""
23+ excludeCustom : false , excludeCustomSet : "" ,
24+
25+ enableSeparator : false ,
26+ separator : "-" ,
27+ separatorInterval : DEFAULT_PASSWORD_LENGTH / 2
2328 } ) ;
2429
2530 const cls = useStyles ( ) ;
@@ -31,9 +36,9 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
3136
3237 const setLength = useCallback ( ( _ : any , e : fui . InputOnChangeData ) =>
3338 {
34- const n = parseInt ( e . value ?? "" ) ;
35- setState ( { length : isNaN ( n ) || n < 1 ? null : n } ) ;
36- } , [ state ] ) ;
39+ const n = parseInt ( e . value ?? "" , 10 ) ;
40+ setState ( { length : isNaN ( n ) || n < 1 ? null : Math . min ( n , MAX_PASSWORD_LENGTH ) } ) ;
41+ } , [ setState ] ) ;
3742
3843 const saveConfiguration = useCallback (
3944 async ( ) => await browser . storage . sync . set ( { AdvancedPasswordOptions : state } ) ,
@@ -46,7 +51,7 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
4651
4752 for ( let i = 0 ; i < count ; i ++ )
4853 passwords . push ( generatePassword ( {
49- length : state . length ?? 8 ,
54+ length : state . length ?? DEFAULT_PASSWORD_LENGTH ,
5055 custom : state . enableCustom ? state . customCount ?? 1 : 0 ,
5156 customSet : state . customSet ,
5257 numeric : state . enableNumeric ? state . numericCount ?? 1 : 0 ,
@@ -57,6 +62,8 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
5762 excludeCustom : state . excludeCustom ? state . excludeCustomSet : "" ,
5863 excludeRepeating : state . excludeRepeating ,
5964 excludeSimilar : state . excludeSimilar ,
65+ separator : state . enableSeparator ? state . separator : undefined ,
66+ separatorInterval : state . separatorInterval ?? ( DEFAULT_PASSWORD_LENGTH / 2 )
6067 } ) ) ;
6168
6269 props . onGenerated ( passwords ) ;
@@ -80,10 +87,38 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
8087 onChange : ( _ , e ) => setState ( { [ key ] : parseCount ( e . value ) } )
8188 } ) , [ state ] ) ;
8289
90+ const setSeparatorInterval = ( _ : any , e : fui . InputOnChangeData ) =>
91+ {
92+ if ( ! e . value )
93+ {
94+ setState ( { separatorInterval : undefined } ) ;
95+ return ;
96+ }
97+
98+ const n = parseInt ( e . value , 10 ) ;
99+
100+ if ( ! isNaN ( n ) )
101+ setState ( { separatorInterval : n < 1 ? 1 : Math . min ( n , state . length ?? DEFAULT_PASSWORD_LENGTH ) } ) ;
102+ } ;
103+
104+ const updateLength = ( ) : void =>
105+ {
106+ const minLength = Math . max ( MIN_PASSWORD_LENGTH ,
107+ ( state . enableCustom ? state . customCount ?? 1 : 0 ) +
108+ ( state . enableNumeric ? state . numericCount ?? 1 : 0 ) +
109+ ( state . enableSpecial ? state . specialCount ?? 1 : 0 ) +
110+ ( state . enableUppercase ? state . uppercaseCount ?? 1 : 0 ) +
111+ ( state . enableLowercase ? state . lowercaseCount ?? 1 : 0 )
112+ ) ;
113+
114+ if ( ! state . length || state . length < minLength )
115+ setState ( { length : minLength } ) ;
116+ } ;
117+
83118 return (
84119 < GeneratorForm onGenerate = { generate } onSave = { saveConfiguration } >
85120 < fui . Field label = { i18n . t ( "advanced.password.length" ) } >
86- < fui . Input value = { state . length ?. toString ( ) ?? "" } onChange = { setLength } />
121+ < fui . Input value = { state . length ?. toString ( ) ?? "" } onChange = { setLength } onBlur = { updateLength } />
87122 </ fui . Field >
88123 < fui . Table size = "small" as = "div" >
89124 < fui . TableHeader as = "div" >
@@ -95,19 +130,19 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
95130 < fui . TableBody as = "div" >
96131 < Row >
97132 < fui . Checkbox label = { i18n . t ( "common.characters.uppercase" ) } { ...checkboxControls ( "enableUppercase" ) } />
98- < fui . Input { ...minInputControls ( "enableUppercase" , "uppercaseCount" ) } />
133+ < fui . Input { ...minInputControls ( "enableUppercase" , "uppercaseCount" ) } onBlur = { updateLength } />
99134 </ Row >
100135 < Row >
101136 < fui . Checkbox label = { i18n . t ( "common.characters.lowercase" ) } { ...checkboxControls ( "enableLowercase" ) } />
102- < fui . Input { ...minInputControls ( "enableLowercase" , "lowercaseCount" ) } />
137+ < fui . Input { ...minInputControls ( "enableLowercase" , "lowercaseCount" ) } onBlur = { updateLength } />
103138 </ Row >
104139 < Row >
105140 < fui . Checkbox label = { i18n . t ( "common.characters.numeric" ) } { ...checkboxControls ( "enableNumeric" ) } />
106- < fui . Input { ...minInputControls ( "enableNumeric" , "numericCount" ) } />
141+ < fui . Input { ...minInputControls ( "enableNumeric" , "numericCount" ) } onBlur = { updateLength } />
107142 </ Row >
108143 < Row >
109- < fui . Checkbox label = { infoLabel ( i18n . t ( "common.characters.special" ) , CharacterHints . special ) } { ...checkboxControls ( "enableSpecial" ) } />
110- < fui . Input { ...minInputControls ( "enableSpecial" , "specialCount" ) } />
144+ < fui . Checkbox label = { infoLabel ( i18n . t ( "common.characters.special" ) , CharacterHints . special , true ) } { ...checkboxControls ( "enableSpecial" ) } />
145+ < fui . Input { ...minInputControls ( "enableSpecial" , "specialCount" ) } onBlur = { updateLength } />
111146 </ Row >
112147 < Row >
113148 < >
@@ -116,7 +151,7 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
116151 placeholder = { i18n . t ( "common.characters.custom" ) }
117152 value = { state . customSet } onChange = { ( _ , e ) => setState ( { customSet : e . value } ) } />
118153 </ >
119- < fui . Input { ...minInputControls ( "enableCustom" , "customCount" ) } />
154+ < fui . Input { ...minInputControls ( "enableCustom" , "customCount" ) } onBlur = { updateLength } />
120155 </ Row >
121156 </ fui . TableBody >
122157 </ fui . Table >
@@ -133,13 +168,34 @@ export default function PasswordSection(props: GeneratorProps): ReactElement
133168 value = { state . excludeCustomSet } onChange = { ( _ , e ) => setState ( { excludeCustomSet : e . value } ) } />
134169 </ div >
135170 </ section >
171+
172+ < div >
173+ < fui . Checkbox
174+ { ...checkboxControls ( "enableSeparator" ) }
175+ label = {
176+ < span className = { cls . separatorLabel } >
177+ { i18n . t ( "advanced.password.separator1" ) }
178+ < fui . Input size = "small" className = { cls . separatorInput }
179+ disabled = { ! state . enableSeparator }
180+ value = { state . separator ?? "" }
181+ onChange = { ( _ , e ) => setState ( { separator : e . value ? e . value [ e . value . length - 1 ] : undefined } ) } />
182+ { i18n . t ( "advanced.password.separator2" ) }
183+ < fui . Input size = "small" className = { cls . separatorInput }
184+ disabled = { ! state . enableSeparator }
185+ value = { state . separatorInterval ?. toString ( ) ?? "" }
186+ onBlur = { ( ) => state . separatorInterval ? null : setState ( { separatorInterval : DEFAULT_PASSWORD_LENGTH / 2 } ) }
187+ onChange = { setSeparatorInterval } />
188+ { i18n . t ( "advanced.password.separator3" ) }
189+ </ span >
190+ } />
191+ </ div >
136192 </ GeneratorForm >
137193 ) ;
138194}
139195
140196function parseCount ( value : string ) : number | null
141197{
142- const n = parseInt ( value ) ;
198+ const n = parseInt ( value , 10 ) ;
143199 return isNaN ( n ) || n < 1 ? null : Math . min ( n , 100 ) ;
144200} ;
145201
@@ -162,6 +218,17 @@ const useStyles = fui.makeStyles({
162218 display : "flex" ,
163219 flexDirection : "column" ,
164220 } ,
221+ separatorLabel :
222+ {
223+ display : "inline-flex" ,
224+ flexWrap : "wrap" ,
225+ alignItems : "center" ,
226+ gap : `${ fui . tokens . spacingVerticalXXS } ${ fui . tokens . spacingHorizontalS } ` ,
227+ } ,
228+ separatorInput :
229+ {
230+ width : "4em" ,
231+ }
165232} ) ;
166233
167234type PasswordSectionState =
@@ -185,4 +252,8 @@ type PasswordSectionState =
185252
186253 excludeCustomSet : string ;
187254 customSet : string ;
255+
256+ enableSeparator : boolean ;
257+ separator ?: string ;
258+ separatorInterval ?: number ;
188259 } ;
0 commit comments