From a36fd7ad3a8a52996d1a557635cd87592e7fbae9 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 3 Dec 2025 23:33:58 +0100 Subject: [PATCH] Add hidden/masked field type definition (#1404) --- .../Credentials/Details/FieldBlock.tsx | 1 + .../popup/components/Forms/HiddenField.tsx | 99 +++++++++++++++++++ .../popup/pages/credentials/ItemAddEdit.tsx | 13 ++- .../utils/dist/shared/models/vault/index.d.ts | 2 +- .../utils/dist/shared/models/vault/index.d.ts | 2 +- shared/models/src/vault/Item.ts | 1 + 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 apps/browser-extension/src/entrypoints/popup/components/Forms/HiddenField.tsx diff --git a/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldBlock.tsx b/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldBlock.tsx index 56923d98e..733090124 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldBlock.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Credentials/Details/FieldBlock.tsx @@ -54,6 +54,7 @@ const FieldBlock: React.FC = ({ field }) => { // Render based on field type switch (field.FieldType) { case 'Password': + case 'Hidden': return ( void; + placeholder?: string; + error?: string; + showValue?: boolean; + onShowValueChange?: (show: boolean) => void; +} + +/** + * Hidden field component with show/hide toggle (like password field but without generation). + * Used for sensitive data that doesn't need password generation features. + */ +const HiddenField: React.FC = ({ + id, + label, + value, + onChange, + placeholder, + error, + showValue: controlledShowValue, + onShowValueChange +}) => { + const { t } = useTranslation(); + const [internalShowValue, setInternalShowValue] = useState(false); + + // Use controlled or uncontrolled showValue state + const showValue = controlledShowValue !== undefined ? controlledShowValue : internalShowValue; + + /** + * Set the showValue state. + */ + const setShowValue = useCallback((show: boolean): void => { + if (controlledShowValue !== undefined) { + onShowValueChange?.(show); + } else { + setInternalShowValue(show); + } + }, [controlledShowValue, onShowValueChange]); + + const toggleValueVisibility = useCallback(() => { + setShowValue(!showValue); + }, [showValue, setShowValue]); + + return ( +
+ {/* Label */} + + + {/* Hidden Input with Show/Hide Button */} +
+
+ onChange(e.target.value)} + placeholder={placeholder} + className="outline-0 text-sm shadow-sm border border-gray-300 bg-gray-50 text-gray-900 sm:text-sm rounded-l-lg block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" + /> +
+
+ {/* Show/Hide Value Button */} + +
+
+ + {/* Error Message */} + {error && ( +

{error}

+ )} +
+ ); +}; + +export default HiddenField; diff --git a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx index 95eebeeff..b5da58c0d 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx @@ -7,6 +7,7 @@ import { sendMessage } from 'webext-bridge/popup'; import Modal from '@/entrypoints/popup/components/Dialogs/Modal'; import EditableFieldLabel from '@/entrypoints/popup/components/Forms/EditableFieldLabel'; import { FormInput } from '@/entrypoints/popup/components/Forms/FormInput'; +import HiddenField from '@/entrypoints/popup/components/Forms/HiddenField'; import PasswordField from '@/entrypoints/popup/components/Forms/PasswordField'; import HeaderButton from '@/entrypoints/popup/components/HeaderButton'; import { HeaderIconType } from '@/entrypoints/popup/components/Icons/HeaderIcons'; @@ -384,6 +385,16 @@ const ItemAddEdit: React.FC = () => { /> ); + case 'Hidden': + return ( + handleFieldChange(fieldKey, val)} + /> + ); + case 'TextArea': return (
@@ -556,7 +567,7 @@ const ItemAddEdit: React.FC = () => { className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white" > - + diff --git a/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts b/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts index dc0a33223..af7d3508d 100644 --- a/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts +++ b/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts @@ -358,7 +358,7 @@ type ItemField = { /** * Field types for rendering and validation */ -type FieldType = 'Text' | 'Password' | 'Email' | 'URL' | 'Date' | 'Number' | 'Phone' | 'TextArea'; +type FieldType = 'Text' | 'Password' | 'Hidden' | 'Email' | 'URL' | 'Date' | 'Number' | 'Phone' | 'TextArea'; /** * Tag reference for display within an item */ diff --git a/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts b/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts index dc0a33223..af7d3508d 100644 --- a/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts +++ b/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts @@ -358,7 +358,7 @@ type ItemField = { /** * Field types for rendering and validation */ -type FieldType = 'Text' | 'Password' | 'Email' | 'URL' | 'Date' | 'Number' | 'Phone' | 'TextArea'; +type FieldType = 'Text' | 'Password' | 'Hidden' | 'Email' | 'URL' | 'Date' | 'Number' | 'Phone' | 'TextArea'; /** * Tag reference for display within an item */ diff --git a/shared/models/src/vault/Item.ts b/shared/models/src/vault/Item.ts index 0f9b95095..5bf8b6ba1 100644 --- a/shared/models/src/vault/Item.ts +++ b/shared/models/src/vault/Item.ts @@ -45,6 +45,7 @@ export type ItemField = { export type FieldType = | 'Text' | 'Password' + | 'Hidden' | 'Email' | 'URL' | 'Date'