refactor: DecoratedInput and add DecoratedPasswordInput (#953)
This commit is contained in:
@@ -4,8 +4,8 @@ import { observer } from 'mobx-react-lite';
|
|||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
import { Checkbox } from '../Checkbox';
|
import { Checkbox } from '../Checkbox';
|
||||||
|
import { DecoratedInput } from '../DecoratedInput';
|
||||||
import { Icon } from '../Icon';
|
import { Icon } from '../Icon';
|
||||||
import { InputWithIcon } from '../InputWithIcon';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
@@ -63,16 +63,12 @@ export const AdvancedOptions: FunctionComponent<Props> = observer(
|
|||||||
setIsVault(!isVault);
|
setIsVault(!isVault);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVaultNameChange = (e: Event) => {
|
const handleVaultNameChange = (name: string) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setVaultName(name);
|
||||||
setVaultName(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVaultUserphraseChange = (e: Event) => {
|
const handleVaultUserphraseChange = (userphrase: string) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setVaultUserphrase(userphrase);
|
||||||
setVaultUserphrase(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleServerOptionChange = (e: Event) => {
|
const handleServerOptionChange = (e: Event) => {
|
||||||
@@ -81,11 +77,9 @@ export const AdvancedOptions: FunctionComponent<Props> = observer(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSyncServerChange = (e: Event) => {
|
const handleSyncServerChange = (server: string) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setServer(server);
|
||||||
setServer(e.target.value);
|
application.setCustomHost(server);
|
||||||
application.setCustomHost(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStrictSigninChange = () => {
|
const handleStrictSigninChange = () => {
|
||||||
@@ -135,19 +129,19 @@ export const AdvancedOptions: FunctionComponent<Props> = observer(
|
|||||||
|
|
||||||
{appState.enableUnfinishedFeatures && isVault && (
|
{appState.enableUnfinishedFeatures && isVault && (
|
||||||
<>
|
<>
|
||||||
<InputWithIcon
|
<DecoratedInput
|
||||||
className={`mb-2`}
|
className={`mb-2`}
|
||||||
icon="folder"
|
left={[<Icon type="folder" className="color-neutral" />]}
|
||||||
inputType="text"
|
type="text"
|
||||||
placeholder="Vault name"
|
placeholder="Vault name"
|
||||||
value={vaultName}
|
value={vaultName}
|
||||||
onChange={handleVaultNameChange}
|
onChange={handleVaultNameChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<InputWithIcon
|
<DecoratedInput
|
||||||
className={`mb-2 `}
|
className={`mb-2`}
|
||||||
icon="server"
|
left={[<Icon type="server" className="color-neutral" />]}
|
||||||
inputType={'text'}
|
type="text"
|
||||||
placeholder="Vault userphrase"
|
placeholder="Vault userphrase"
|
||||||
value={vaultUserphrase}
|
value={vaultUserphrase}
|
||||||
onChange={handleVaultUserphraseChange}
|
onChange={handleVaultUserphraseChange}
|
||||||
@@ -183,9 +177,9 @@ export const AdvancedOptions: FunctionComponent<Props> = observer(
|
|||||||
onChange={handleServerOptionChange}
|
onChange={handleServerOptionChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<InputWithIcon
|
<DecoratedInput
|
||||||
inputType="text"
|
type="text"
|
||||||
icon="server"
|
left={[<Icon type="server" className="color-neutral" />]}
|
||||||
placeholder="https://api.standardnotes.com"
|
placeholder="https://api.standardnotes.com"
|
||||||
value={server}
|
value={server}
|
||||||
onChange={handleSyncServerChange}
|
onChange={handleSyncServerChange}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
|||||||
import { AccountMenuPane } from '.';
|
import { AccountMenuPane } from '.';
|
||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
import { Checkbox } from '../Checkbox';
|
import { Checkbox } from '../Checkbox';
|
||||||
|
import { DecoratedPasswordInput } from '../DecoratedPasswordInput';
|
||||||
|
import { Icon } from '../Icon';
|
||||||
import { IconButton } from '../IconButton';
|
import { IconButton } from '../IconButton';
|
||||||
import { InputWithIcon } from '../InputWithIcon';
|
|
||||||
import { AdvancedOptions } from './AdvancedOptions';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@@ -23,7 +23,6 @@ export const ConfirmPassword: FunctionComponent<Props> = observer(
|
|||||||
({ application, appState, setMenuPane, email, password }) => {
|
({ application, appState, setMenuPane, email, password }) => {
|
||||||
const { notesAndTagsCount } = appState.accountMenu;
|
const { notesAndTagsCount } = appState.accountMenu;
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [isRegistering, setIsRegistering] = useState(false);
|
const [isRegistering, setIsRegistering] = useState(false);
|
||||||
const [isEphemeral, setIsEphemeral] = useState(false);
|
const [isEphemeral, setIsEphemeral] = useState(false);
|
||||||
const [shouldMergeLocal, setShouldMergeLocal] = useState(true);
|
const [shouldMergeLocal, setShouldMergeLocal] = useState(true);
|
||||||
@@ -35,10 +34,8 @@ export const ConfirmPassword: FunctionComponent<Props> = observer(
|
|||||||
passwordInputRef.current?.focus();
|
passwordInputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handlePasswordChange = (e: Event) => {
|
const handlePasswordChange = (text: string) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setConfirmPassword(text);
|
||||||
setConfirmPassword(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEphemeralChange = () => {
|
const handleEphemeralChange = () => {
|
||||||
@@ -117,23 +114,15 @@ export const ConfirmPassword: FunctionComponent<Props> = observer(
|
|||||||
your data.
|
your data.
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleConfirmFormSubmit} className="px-3 mb-1">
|
<form onSubmit={handleConfirmFormSubmit} className="px-3 mb-1">
|
||||||
<InputWithIcon
|
<DecoratedPasswordInput
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
icon="password"
|
disabled={isRegistering}
|
||||||
inputType={showPassword ? 'text' : 'password'}
|
left={[<Icon type="password" className="color-neutral" />]}
|
||||||
placeholder="Confirm password"
|
|
||||||
value={confirmPassword}
|
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
toggle={{
|
placeholder="Confirm password"
|
||||||
toggleOnIcon: 'eye-off',
|
|
||||||
toggleOffIcon: 'eye',
|
|
||||||
title: 'Show password',
|
|
||||||
toggled: showPassword,
|
|
||||||
onClick: setShowPassword,
|
|
||||||
}}
|
|
||||||
ref={passwordInputRef}
|
ref={passwordInputRef}
|
||||||
disabled={isRegistering}
|
value={confirmPassword}
|
||||||
/>
|
/>
|
||||||
{error ? <div className="color-dark-red my-2">{error}</div> : null}
|
{error ? <div className="color-dark-red my-2">{error}</div> : null}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { FunctionComponent } from 'preact';
|
|||||||
import { StateUpdater, useEffect, useRef, useState } from 'preact/hooks';
|
import { StateUpdater, useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { AccountMenuPane } from '.';
|
import { AccountMenuPane } from '.';
|
||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
|
import { DecoratedInput } from '../DecoratedInput';
|
||||||
|
import { DecoratedPasswordInput } from '../DecoratedPasswordInput';
|
||||||
|
import { Icon } from '../Icon';
|
||||||
import { IconButton } from '../IconButton';
|
import { IconButton } from '../IconButton';
|
||||||
import { InputWithIcon } from '../InputWithIcon';
|
|
||||||
import { AdvancedOptions } from './AdvancedOptions';
|
import { AdvancedOptions } from './AdvancedOptions';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -29,8 +31,6 @@ export const CreateAccount: FunctionComponent<Props> = observer(
|
|||||||
password,
|
password,
|
||||||
setPassword,
|
setPassword,
|
||||||
}) => {
|
}) => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
|
|
||||||
const emailInputRef = useRef<HTMLInputElement>(null);
|
const emailInputRef = useRef<HTMLInputElement>(null);
|
||||||
const passwordInputRef = useRef<HTMLInputElement>(null);
|
const passwordInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [isVault, setIsVault] = useState(false);
|
const [isVault, setIsVault] = useState(false);
|
||||||
@@ -41,16 +41,12 @@ export const CreateAccount: FunctionComponent<Props> = observer(
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleEmailChange = (e: Event) => {
|
const handleEmailChange = (text: string) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setEmail(text);
|
||||||
setEmail(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePasswordChange = (e: Event) => {
|
const handlePasswordChange = (text: string) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setPassword(text);
|
||||||
setPassword(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
@@ -103,33 +99,25 @@ export const CreateAccount: FunctionComponent<Props> = observer(
|
|||||||
<div className="sn-account-menu-headline">Create account</div>
|
<div className="sn-account-menu-headline">Create account</div>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleRegisterFormSubmit} className="px-3 mb-1">
|
<form onSubmit={handleRegisterFormSubmit} className="px-3 mb-1">
|
||||||
<InputWithIcon
|
<DecoratedInput
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
icon="email"
|
|
||||||
inputType="email"
|
|
||||||
placeholder="Email"
|
|
||||||
value={email}
|
|
||||||
disabled={isVault}
|
disabled={isVault}
|
||||||
|
left={[<Icon type="email" className="color-neutral" />]}
|
||||||
onChange={handleEmailChange}
|
onChange={handleEmailChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="Email"
|
||||||
ref={emailInputRef}
|
ref={emailInputRef}
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
/>
|
/>
|
||||||
<InputWithIcon
|
<DecoratedPasswordInput
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
icon="password"
|
left={[<Icon type="password" className="color-neutral" />]}
|
||||||
inputType={showPassword ? 'text' : 'password'}
|
|
||||||
placeholder="Password"
|
|
||||||
value={password}
|
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
toggle={{
|
placeholder="Password"
|
||||||
toggleOnIcon: 'eye-off',
|
|
||||||
toggleOffIcon: 'eye',
|
|
||||||
title: 'Show password',
|
|
||||||
toggled: showPassword,
|
|
||||||
onClick: setShowPassword,
|
|
||||||
}}
|
|
||||||
ref={passwordInputRef}
|
ref={passwordInputRef}
|
||||||
|
value={password}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="btn-w-full mt-1"
|
className="btn-w-full mt-1"
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|||||||
import { AccountMenuPane } from '.';
|
import { AccountMenuPane } from '.';
|
||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
import { Checkbox } from '../Checkbox';
|
import { Checkbox } from '../Checkbox';
|
||||||
|
import { DecoratedInput } from '../DecoratedInput';
|
||||||
|
import { DecoratedPasswordInput } from '../DecoratedPasswordInput';
|
||||||
import { Icon } from '../Icon';
|
import { Icon } from '../Icon';
|
||||||
import { IconButton } from '../IconButton';
|
import { IconButton } from '../IconButton';
|
||||||
import { InputWithIcon } from '../InputWithIcon';
|
|
||||||
import { AdvancedOptions } from './AdvancedOptions';
|
import { AdvancedOptions } from './AdvancedOptions';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -28,7 +29,6 @@ export const SignInPane: FunctionComponent<Props> = observer(
|
|||||||
|
|
||||||
const [isStrictSignin, setIsStrictSignin] = useState(false);
|
const [isStrictSignin, setIsStrictSignin] = useState(false);
|
||||||
const [isSigningIn, setIsSigningIn] = useState(false);
|
const [isSigningIn, setIsSigningIn] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [shouldMergeLocal, setShouldMergeLocal] = useState(true);
|
const [shouldMergeLocal, setShouldMergeLocal] = useState(true);
|
||||||
const [isVault, setIsVault] = useState(false);
|
const [isVault, setIsVault] = useState(false);
|
||||||
|
|
||||||
@@ -51,19 +51,15 @@ export const SignInPane: FunctionComponent<Props> = observer(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEmailChange = (e: Event) => {
|
const handleEmailChange = (text: string) => {
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setEmail(text);
|
||||||
setEmail(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePasswordChange = (e: Event) => {
|
const handlePasswordChange = (text: string) => {
|
||||||
if (error.length) {
|
if (error.length) {
|
||||||
setError('');
|
setError('');
|
||||||
}
|
}
|
||||||
if (e.target instanceof HTMLInputElement) {
|
setPassword(text);
|
||||||
setPassword(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEphemeralChange = () => {
|
const handleEphemeralChange = () => {
|
||||||
@@ -148,10 +144,10 @@ export const SignInPane: FunctionComponent<Props> = observer(
|
|||||||
<div className="sn-account-menu-headline">Sign in</div>
|
<div className="sn-account-menu-headline">Sign in</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 mb-1">
|
<div className="px-3 mb-1">
|
||||||
<InputWithIcon
|
<DecoratedInput
|
||||||
className={`mb-2 ${error ? 'border-dark-red' : null}`}
|
className={`mb-2 ${error ? 'border-dark-red' : null}`}
|
||||||
icon="email"
|
left={[<Icon type="email" className="color-neutral" />]}
|
||||||
inputType="email"
|
type="email"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={handleEmailChange}
|
onChange={handleEmailChange}
|
||||||
@@ -160,24 +156,16 @@ export const SignInPane: FunctionComponent<Props> = observer(
|
|||||||
disabled={isSigningIn || isVault}
|
disabled={isSigningIn || isVault}
|
||||||
ref={emailInputRef}
|
ref={emailInputRef}
|
||||||
/>
|
/>
|
||||||
<InputWithIcon
|
<DecoratedPasswordInput
|
||||||
className={`mb-2 ${error ? 'border-dark-red' : null}`}
|
className={`mb-2 ${error ? 'border-dark-red' : null}`}
|
||||||
icon="password"
|
disabled={isSigningIn}
|
||||||
inputType={showPassword ? 'text' : 'password'}
|
left={[<Icon type="password" className="color-neutral" />]}
|
||||||
placeholder="Password"
|
|
||||||
value={password}
|
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
onFocus={resetInvalid}
|
onFocus={resetInvalid}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
disabled={isSigningIn}
|
placeholder="Password"
|
||||||
toggle={{
|
|
||||||
toggleOnIcon: 'eye-off',
|
|
||||||
toggleOffIcon: 'eye',
|
|
||||||
title: 'Show password',
|
|
||||||
toggled: showPassword,
|
|
||||||
onClick: setShowPassword,
|
|
||||||
}}
|
|
||||||
ref={passwordInputRef}
|
ref={passwordInputRef}
|
||||||
|
value={password}
|
||||||
/>
|
/>
|
||||||
{error ? <div className="color-dark-red my-2">{error}</div> : null}
|
{error ? <div className="color-dark-red my-2">{error}</div> : null}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,70 +1,98 @@
|
|||||||
import { FunctionalComponent, ComponentChild } from 'preact';
|
import { FunctionalComponent, ComponentChild, Ref } from 'preact';
|
||||||
import { HtmlInputTypes } from '@/enums';
|
import { forwardRef } from 'preact/compat';
|
||||||
|
|
||||||
interface Props {
|
export type DecoratedInputProps = {
|
||||||
type?: HtmlInputTypes;
|
type?: 'text' | 'email' | 'password';
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
left?: ComponentChild[];
|
left?: ComponentChild[];
|
||||||
right?: ComponentChild[];
|
right?: ComponentChild[];
|
||||||
text?: string;
|
value?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onChange?: (text: string) => void;
|
onChange?: (text: string) => void;
|
||||||
|
onFocus?: (event: FocusEvent) => void;
|
||||||
|
onKeyDown?: (event: KeyboardEvent) => void;
|
||||||
autocomplete?: boolean;
|
autocomplete?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const getClassNames = (
|
||||||
|
hasLeftDecorations: boolean,
|
||||||
|
hasRightDecorations: boolean
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
container: `flex items-stretch position-relative bg-default border-1 border-solid border-main rounded focus-within:ring-info overflow-hidden ${
|
||||||
|
!hasLeftDecorations && !hasRightDecorations ? 'px-2 py-1.5' : ''
|
||||||
|
}`,
|
||||||
|
input: `w-full border-0 focus:shadow-none ${
|
||||||
|
!hasLeftDecorations && hasRightDecorations ? 'pl-2' : ''
|
||||||
|
} ${hasRightDecorations ? 'pr-2' : ''}`,
|
||||||
|
disabled: 'bg-grey-5 cursor-not-allowed',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input that can be decorated on the left and right side
|
* Input that can be decorated on the left and right side
|
||||||
*/
|
*/
|
||||||
export const DecoratedInput: FunctionalComponent<Props> = ({
|
export const DecoratedInput: FunctionalComponent<DecoratedInputProps> =
|
||||||
type = 'text',
|
forwardRef(
|
||||||
className = '',
|
(
|
||||||
disabled = false,
|
{
|
||||||
left,
|
type = 'text',
|
||||||
right,
|
className = '',
|
||||||
text,
|
disabled = false,
|
||||||
placeholder = '',
|
left,
|
||||||
onChange,
|
right,
|
||||||
autocomplete = false,
|
value,
|
||||||
}) => {
|
placeholder = '',
|
||||||
const baseClasses =
|
onChange,
|
||||||
'rounded py-1.5 px-3 text-input my-1 h-8 flex flex-row items-center bg-contrast';
|
onFocus,
|
||||||
const stateClasses = disabled
|
onKeyDown,
|
||||||
? 'no-border'
|
autocomplete = false,
|
||||||
: 'border-solid border-1 border-main';
|
}: DecoratedInputProps,
|
||||||
const classes = `${baseClasses} ${stateClasses} ${className}`;
|
ref: Ref<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const hasLeftDecorations = Boolean(left?.length);
|
||||||
|
const hasRightDecorations = Boolean(right?.length);
|
||||||
|
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations);
|
||||||
|
|
||||||
const inputBaseClasses =
|
return (
|
||||||
'w-full no-border color-text focus:shadow-none bg-contrast';
|
<div
|
||||||
const inputStateClasses = disabled ? 'overflow-ellipsis' : '';
|
className={`${classNames.container} ${
|
||||||
return (
|
disabled ? classNames.disabled : ''
|
||||||
<div className={`${classes} focus-within:ring-info`}>
|
} ${className}`}
|
||||||
{left?.map((leftChild) => (
|
>
|
||||||
<>
|
{left && (
|
||||||
{leftChild}
|
<div className="flex items-center px-2 py-1.5">
|
||||||
<div className="min-w-2 min-h-1" />
|
{left.map((leftChild) => (
|
||||||
</>
|
<>{leftChild}</>
|
||||||
))}
|
))}
|
||||||
<div className="flex-grow">
|
</div>
|
||||||
<input
|
)}
|
||||||
type={type}
|
<input
|
||||||
className={`${inputBaseClasses} ${inputStateClasses}`}
|
type={type}
|
||||||
disabled={disabled}
|
className={`${classNames.input} ${
|
||||||
value={text}
|
disabled ? classNames.disabled : ''
|
||||||
placeholder={placeholder}
|
}`}
|
||||||
onChange={(e) =>
|
disabled={disabled}
|
||||||
onChange && onChange((e.target as HTMLInputElement).value)
|
value={value}
|
||||||
}
|
placeholder={placeholder}
|
||||||
data-lpignore={type !== 'password' ? true : false}
|
onChange={(e) =>
|
||||||
autocomplete={autocomplete ? 'on' : 'off'}
|
onChange && onChange((e.target as HTMLInputElement).value)
|
||||||
/>
|
}
|
||||||
</div>
|
onFocus={onFocus}
|
||||||
{right?.map((rightChild) => (
|
onKeyDown={onKeyDown}
|
||||||
<>
|
data-lpignore={type !== 'password' ? true : false}
|
||||||
<div className="min-w-3 min-h-1" />
|
autocomplete={autocomplete ? 'on' : 'off'}
|
||||||
{rightChild}
|
ref={ref}
|
||||||
</>
|
/>
|
||||||
))}
|
{right && (
|
||||||
</div>
|
<div className="flex items-center px-2 py-1.5">
|
||||||
|
{right.map((rightChild, index) => (
|
||||||
|
<div className={index > 0 ? 'ml-3' : ''}>{rightChild}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|||||||
42
app/assets/javascripts/components/DecoratedPasswordInput.tsx
Normal file
42
app/assets/javascripts/components/DecoratedPasswordInput.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { FunctionComponent, Ref } from 'preact';
|
||||||
|
import { forwardRef } from 'preact/compat';
|
||||||
|
import { StateUpdater, useState } from 'preact/hooks';
|
||||||
|
import { DecoratedInput, DecoratedInputProps } from './DecoratedInput';
|
||||||
|
import { IconButton } from './IconButton';
|
||||||
|
|
||||||
|
const Toggle: FunctionComponent<{
|
||||||
|
isToggled: boolean;
|
||||||
|
setIsToggled: StateUpdater<boolean>;
|
||||||
|
}> = ({ isToggled, setIsToggled }) => (
|
||||||
|
<IconButton
|
||||||
|
className="w-5 h-5 justify-center sk-circle hover:bg-grey-4"
|
||||||
|
icon={isToggled ? 'eye-off' : 'eye'}
|
||||||
|
iconClassName="sn-icon--small"
|
||||||
|
title="Show/hide password"
|
||||||
|
onClick={() => setIsToggled((isToggled) => !isToggled)}
|
||||||
|
focusable={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password input that has a toggle to show/hide password and can be decorated on the left and right side
|
||||||
|
*/
|
||||||
|
export const DecoratedPasswordInput: FunctionComponent<
|
||||||
|
Omit<DecoratedInputProps, 'type'>
|
||||||
|
> = forwardRef((props, ref: Ref<HTMLInputElement>) => {
|
||||||
|
const [isToggled, setIsToggled] = useState(false);
|
||||||
|
|
||||||
|
const rightSideDecorations = props.right ? [...props.right] : [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DecoratedInput
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
type={isToggled ? 'text' : 'password'}
|
||||||
|
right={[
|
||||||
|
...rightSideDecorations,
|
||||||
|
<Toggle isToggled={isToggled} setIsToggled={setIsToggled} />,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { FunctionComponent, Ref } from 'preact';
|
|
||||||
import { JSXInternal } from 'preact/src/jsx';
|
|
||||||
import { forwardRef } from 'preact/compat';
|
|
||||||
import { Icon } from './Icon';
|
|
||||||
import { IconButton } from './IconButton';
|
|
||||||
import { IconType } from '@standardnotes/snjs';
|
|
||||||
|
|
||||||
type ToggleProps = {
|
|
||||||
toggleOnIcon: IconType;
|
|
||||||
toggleOffIcon: IconType;
|
|
||||||
title: string;
|
|
||||||
toggled: boolean;
|
|
||||||
onClick: (toggled: boolean) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
icon: IconType;
|
|
||||||
inputType: 'text' | 'email' | 'password';
|
|
||||||
className?: string;
|
|
||||||
iconClassName?: string;
|
|
||||||
value: string | undefined;
|
|
||||||
onChange: JSXInternal.GenericEventHandler<HTMLInputElement>;
|
|
||||||
onFocus?: JSXInternal.GenericEventHandler<HTMLInputElement>;
|
|
||||||
onKeyDown?: JSXInternal.KeyboardEventHandler<HTMLInputElement>;
|
|
||||||
disabled?: boolean;
|
|
||||||
placeholder: string;
|
|
||||||
toggle?: ToggleProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DISABLED_CLASSNAME = 'bg-grey-5 cursor-not-allowed';
|
|
||||||
|
|
||||||
export const InputWithIcon: FunctionComponent<Props> = forwardRef(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
icon,
|
|
||||||
inputType,
|
|
||||||
className,
|
|
||||||
iconClassName,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onFocus,
|
|
||||||
onKeyDown,
|
|
||||||
disabled,
|
|
||||||
toggle,
|
|
||||||
placeholder,
|
|
||||||
}: Props,
|
|
||||||
ref: Ref<HTMLInputElement>
|
|
||||||
) => {
|
|
||||||
const handleToggle = () => {
|
|
||||||
if (toggle) toggle.onClick(!toggle?.toggled);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`flex items-stretch position-relative bg-default border-1 border-solid border-main rounded focus-within:ring-info overflow-hidden ${
|
|
||||||
disabled ? DISABLED_CLASSNAME : ''
|
|
||||||
} ${className}`}
|
|
||||||
>
|
|
||||||
<div className="flex px-2 py-1.5">
|
|
||||||
<Icon type={icon} className={`color-grey-1 ${iconClassName}`} />
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type={inputType}
|
|
||||||
onFocus={onFocus}
|
|
||||||
onChange={onChange}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
value={value}
|
|
||||||
className={`pr-2 w-full border-0 focus:shadow-none ${
|
|
||||||
disabled ? DISABLED_CLASSNAME : ''
|
|
||||||
}`}
|
|
||||||
spellcheck={false}
|
|
||||||
disabled={disabled}
|
|
||||||
placeholder={placeholder}
|
|
||||||
ref={ref}
|
|
||||||
/>
|
|
||||||
{toggle ? (
|
|
||||||
<div className="flex items-center justify-center px-2">
|
|
||||||
<IconButton
|
|
||||||
className="w-5 h-5 justify-center sk-circle hover:bg-grey-4"
|
|
||||||
icon={toggle.toggled ? toggle.toggleOnIcon : toggle.toggleOffIcon}
|
|
||||||
iconClassName="sn-icon--small"
|
|
||||||
title={toggle.title}
|
|
||||||
onClick={handleToggle}
|
|
||||||
focusable={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -112,7 +112,7 @@ export const Extensions: FunctionComponent<{
|
|||||||
<Title>Install Custom Extension</Title>
|
<Title>Install Custom Extension</Title>
|
||||||
<DecoratedInput
|
<DecoratedInput
|
||||||
placeholder={'Enter Extension URL'}
|
placeholder={'Enter Extension URL'}
|
||||||
text={customUrl}
|
value={customUrl}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setCustomUrl(value);
|
setCustomUrl(value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(
|
|||||||
<DecoratedInput
|
<DecoratedInput
|
||||||
onChange={(code) => setActivationCode(code)}
|
onChange={(code) => setActivationCode(code)}
|
||||||
placeholder={'Offline Subscription Code'}
|
placeholder={'Offline Subscription Code'}
|
||||||
text={activationCode}
|
value={activationCode}
|
||||||
disabled={isSuccessfullyActivated}
|
disabled={isSuccessfullyActivated}
|
||||||
className={'mb-3'}
|
className={'mb-3'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { DecoratedInput } from '@/components/DecoratedInput';
|
|
||||||
import { Icon } from '@/components/Icon';
|
import { Icon } from '@/components/Icon';
|
||||||
import {
|
import {
|
||||||
STRING_E2E_ENABLED,
|
STRING_E2E_ENABLED,
|
||||||
@@ -7,7 +6,7 @@ import {
|
|||||||
} from '@/strings';
|
} from '@/strings';
|
||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { FunctionComponent } from 'preact';
|
import { ComponentChild, FunctionComponent } from 'preact';
|
||||||
import {
|
import {
|
||||||
PreferencesGroup,
|
PreferencesGroup,
|
||||||
PreferencesSegment,
|
PreferencesSegment,
|
||||||
@@ -18,6 +17,19 @@ import {
|
|||||||
const formatCount = (count: number, itemType: string) =>
|
const formatCount = (count: number, itemType: string) =>
|
||||||
`${count} / ${count} ${itemType}`;
|
`${count} / ${count} ${itemType}`;
|
||||||
|
|
||||||
|
const EncryptionStatusItem: FunctionComponent<{
|
||||||
|
icon: ComponentChild;
|
||||||
|
status: string;
|
||||||
|
}> = ({ icon, status }) => (
|
||||||
|
<div className="w-full rounded py-1.5 px-3 text-input my-1 h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
|
||||||
|
{icon}
|
||||||
|
<div className="min-w-3 min-h-1" />
|
||||||
|
<div className="flex-grow color-text text-sm">{status}</div>
|
||||||
|
<div className="min-w-3 min-h-1" />
|
||||||
|
<Icon className="success min-w-4 min-h-4" type="check-bold" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(
|
const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(
|
||||||
({ appState }) => {
|
({ appState }) => {
|
||||||
const count = appState.accountMenu.structuredNotesAndTagsCount;
|
const count = appState.accountMenu.structuredNotesAndTagsCount;
|
||||||
@@ -26,9 +38,6 @@ const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(
|
|||||||
const archived = formatCount(count.archived, 'archived notes');
|
const archived = formatCount(count.archived, 'archived notes');
|
||||||
const deleted = formatCount(count.deleted, 'trashed notes');
|
const deleted = formatCount(count.deleted, 'trashed notes');
|
||||||
|
|
||||||
const checkIcon = (
|
|
||||||
<Icon className="success min-w-4 min-h-4" type="check-bold" />
|
|
||||||
);
|
|
||||||
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />;
|
const noteIcon = <Icon type="rich-text" className="min-w-5 min-h-5" />;
|
||||||
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />;
|
const tagIcon = <Icon type="hashtag" className="min-w-5 min-h-5" />;
|
||||||
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />;
|
const archiveIcon = <Icon type="archive" className="min-w-5 min-h-5" />;
|
||||||
@@ -36,34 +45,14 @@ const EncryptionEnabled: FunctionComponent<{ appState: AppState }> = observer(
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row pb-1 pt-1.5">
|
<div className="flex flex-row pb-1 pt-1.5">
|
||||||
<DecoratedInput
|
<EncryptionStatusItem status={notes} icon={[noteIcon]} />
|
||||||
disabled={true}
|
|
||||||
text={notes}
|
|
||||||
right={[checkIcon]}
|
|
||||||
left={[noteIcon]}
|
|
||||||
/>
|
|
||||||
<div className="min-w-3" />
|
<div className="min-w-3" />
|
||||||
<DecoratedInput
|
<EncryptionStatusItem status={tags} icon={[tagIcon]} />
|
||||||
disabled={true}
|
|
||||||
text={tags}
|
|
||||||
right={[checkIcon]}
|
|
||||||
left={[tagIcon]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<DecoratedInput
|
<EncryptionStatusItem status={archived} icon={[archiveIcon]} />
|
||||||
disabled={true}
|
|
||||||
text={archived}
|
|
||||||
right={[checkIcon]}
|
|
||||||
left={[archiveIcon]}
|
|
||||||
/>
|
|
||||||
<div className="min-w-3" />
|
<div className="min-w-3" />
|
||||||
<DecoratedInput
|
<EncryptionStatusItem status={deleted} icon={[trashIcon]} />
|
||||||
disabled={true}
|
|
||||||
text={deleted}
|
|
||||||
right={[checkIcon]}
|
|
||||||
left={[trashIcon]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const SaveSecretKey: FunctionComponent<{
|
|||||||
<DecoratedInput
|
<DecoratedInput
|
||||||
disabled={true}
|
disabled={true}
|
||||||
right={[<CopyButton copyValue={act.secretKey} />, download]}
|
right={[<CopyButton copyValue={act.secretKey} />, download]}
|
||||||
text={act.secretKey}
|
value={act.secretKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2" />
|
<div className="h-2" />
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const ScanQRCode: FunctionComponent<{
|
|||||||
<DecoratedInput
|
<DecoratedInput
|
||||||
className="ml-4 w-92"
|
className="ml-4 w-92"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
text={act.secretKey}
|
value={act.secretKey}
|
||||||
right={[<CopyButton copyValue={act.secretKey} />]}
|
right={[<CopyButton copyValue={act.secretKey} />]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const Verification: FunctionComponent<{
|
|||||||
</ModalDialogLabel>
|
</ModalDialogLabel>
|
||||||
<ModalDialogDescription className="h-33">
|
<ModalDialogDescription className="h-33">
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center mb-4">
|
||||||
<Bullet />
|
<Bullet />
|
||||||
<div className="min-w-1" />
|
<div className="min-w-1" />
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
@@ -37,7 +37,6 @@ export const Verification: FunctionComponent<{
|
|||||||
onChange={act.setInputSecretKey}
|
onChange={act.setInputSecretKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-1" />
|
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<Bullet />
|
<Bullet />
|
||||||
<div className="min-w-1" />
|
<div className="min-w-1" />
|
||||||
|
|||||||
@@ -602,6 +602,10 @@
|
|||||||
padding-bottom: 2.25rem;
|
padding-bottom: 2.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pl-2 {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.placeholder-dark-red::placeholder {
|
.placeholder-dark-red::placeholder {
|
||||||
@extend .color-dark-red;
|
@extend .color-dark-red;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user