@@ -60,9 +60,43 @@ const variants = {
6060 } ,
6161} ;
6262
63+ const SECURE_MASK = "••••••••••••••••" ;
64+
65+ /**
66+ * Builds the masked display string, optionally revealing the first/last few
67+ * characters in cleartext so users can confirm a copied value. A custom mask
68+ * string (when `secure` is a string) is always shown as-is.
69+ */
70+ function maskValue (
71+ value : string ,
72+ secure : boolean | string ,
73+ revealStart : number ,
74+ revealEnd : number
75+ ) {
76+ if ( typeof secure === "string" ) {
77+ return secure ;
78+ }
79+
80+ const start = Math . max ( 0 , revealStart ) ;
81+ const end = Math . max ( 0 , revealEnd ) ;
82+
83+ // Nothing to reveal, or revealing would leak the whole value: fully mask.
84+ if ( ( start === 0 && end === 0 ) || start + end >= value . length ) {
85+ return SECURE_MASK ;
86+ }
87+
88+ const revealedStart = start > 0 ? value . slice ( 0 , start ) : "" ;
89+ const revealedEnd = end > 0 ? value . slice ( - end ) : "" ;
90+ return `${ revealedStart } ${ SECURE_MASK } ${ revealedEnd } ` ;
91+ }
92+
6393type ClipboardFieldProps = {
6494 value : string ;
6595 secure ?: boolean | string ;
96+ /** When masked, reveal this many of the first characters in cleartext. */
97+ secureRevealStart ?: number ;
98+ /** When masked, reveal this many of the last characters in cleartext. */
99+ secureRevealEnd ?: number ;
66100 variant : keyof typeof variants ;
67101 className ?: string ;
68102 icon ?: React . ReactNode ;
@@ -73,6 +107,8 @@ type ClipboardFieldProps = {
73107export function ClipboardField ( {
74108 value,
75109 secure = false ,
110+ secureRevealStart = 0 ,
111+ secureRevealEnd = 0 ,
76112 variant,
77113 className,
78114 icon,
@@ -87,6 +123,8 @@ export function ClipboardField({
87123 setIsSecure ( secure !== undefined && secure ) ;
88124 } , [ secure ] ) ;
89125
126+ const maskedValue = maskValue ( value , secure , secureRevealStart , secureRevealEnd ) ;
127+
90128 return (
91129 < span className = { cn ( container , fullWidth ? "w-full" : "max-w-fit" , className ) } >
92130 { icon && (
@@ -100,7 +138,7 @@ export function ClipboardField({
100138 < input
101139 type = "text"
102140 ref = { inputIcon }
103- value = { isSecure ? ( typeof secure === "string" ? secure : "••••••••••••••••" ) : value }
141+ value = { isSecure ? maskedValue : value }
104142 readOnly = { true }
105143 className = { cn (
106144 "shrink grow select-all overflow-x-auto" ,
0 commit comments