1- " use client" ;
1+ ' use client' ;
22
3- import React , { useState , useCallback , useMemo } from 'react' ;
43import classNames from 'classnames' ;
4+ import React , { useCallback , useMemo , useState } from 'react' ;
5+ import { createPortal } from 'react-dom' ;
6+ import './styles.css' ;
57import { HideMeProps } from './types' ;
68import { generateMathProblem } from './utils/captcha' ;
7- import './styles.css' ;
89
910export const HideMe : React . FC < HideMeProps > = ( {
1011 children,
@@ -43,7 +44,7 @@ export const HideMe: React.FC<HideMeProps> = ({
4344 setShowCaptcha ( false ) ;
4445 } else {
4546 // Wrong answer, generate new problem
46- setProblemKey ( prev => prev + 1 ) ;
47+ setProblemKey ( ( prev ) => prev + 1 ) ;
4748 setUserAnswer ( '' ) ;
4849 }
4950 } , [ mathProblem , userAnswer ] ) ;
@@ -53,13 +54,16 @@ export const HideMe: React.FC<HideMeProps> = ({
5354 setUserAnswer ( '' ) ;
5455 } , [ ] ) ;
5556
56- const handleKeyDown = useCallback ( ( event : React . KeyboardEvent ) => {
57- if ( event . key === 'Enter' || event . key === ' ' ) {
58- event . preventDefault ( ) ;
59- if ( isRevealed ) return ;
60- handleReveal ( ) ;
61- }
62- } , [ isRevealed , handleReveal ] ) ;
57+ const handleKeyDown = useCallback (
58+ ( event : React . KeyboardEvent ) => {
59+ if ( event . key === 'Enter' || event . key === ' ' ) {
60+ event . preventDefault ( ) ;
61+ if ( isRevealed ) return ;
62+ handleReveal ( ) ;
63+ }
64+ } ,
65+ [ isRevealed , handleReveal ]
66+ ) ;
6367
6468 // Don't render anything if no children
6569 if ( ! children ) {
@@ -69,66 +73,65 @@ export const HideMe: React.FC<HideMeProps> = ({
6973 // If revealed, just show the content
7074 if ( isRevealed ) {
7175 return (
72- < span
73- className = { classNames ( 'hide-me' , 'hide-me--revealed' , className ) }
74- style = { style }
75- >
76+ < span className = { classNames ( 'hide-me' , 'hide-me--revealed' , className ) } style = { style } >
7677 { children }
7778 </ span >
7879 ) ;
7980 }
8081
81- // Show CAPTCHA modal
82- if ( showCaptcha && mathProblem ) {
83- return (
84- < div className = "hide-me-captcha-overlay" >
85- < div className = "hide-me-captcha-modal" >
86- < div className = "hide-me-captcha-content" >
87- < div className = "hide-me-captcha-title" > Solve this math problem to reveal content:</ div >
88- < div className = "hide-me-captcha-question" >
89- What is { mathProblem . question } ?
90- </ div >
91- < div className = "hide-me-captcha-input-group" >
92- < input
93- type = "number"
94- value = { userAnswer }
95- onChange = { ( e ) => setUserAnswer ( e . target . value ) }
96- placeholder = "Your answer"
97- className = "hide-me-captcha-input"
98- autoFocus
99- onKeyDown = { ( e ) => {
100- if ( e . key === 'Enter' ) {
101- handleCaptchaSubmit ( ) ;
102- }
103- } }
104- />
105- < div className = "hide-me-captcha-buttons" >
106- < button
107- type = "button"
108- onClick = { handleCaptchaSubmit }
109- className = "hide-me-captcha-submit"
110- >
111- Submit
112- </ button >
113- < button
114- type = "button"
115- onClick = { handleCaptchaCancel }
116- className = "hide-me-captcha-cancel"
117- >
118- Cancel
119- </ button >
82+ // Render CAPTCHA modal using portal (to avoid nesting issues with <p> tags)
83+ const captchaModal =
84+ showCaptcha && mathProblem && typeof document !== 'undefined'
85+ ? createPortal (
86+ < div className = "hide-me-captcha-overlay" >
87+ < div className = "hide-me-captcha-modal" >
88+ < div className = "hide-me-captcha-content" >
89+ < div className = "hide-me-captcha-title" >
90+ Solve this math problem to reveal content:
91+ </ div >
92+ < div className = "hide-me-captcha-question" > What is { mathProblem . question } ?</ div >
93+ < div className = "hide-me-captcha-input-group" >
94+ < input
95+ type = "number"
96+ value = { userAnswer }
97+ onChange = { ( e ) => setUserAnswer ( e . target . value ) }
98+ placeholder = "Your answer"
99+ className = "hide-me-captcha-input"
100+ autoFocus
101+ onKeyDown = { ( e ) => {
102+ if ( e . key === 'Enter' ) {
103+ handleCaptchaSubmit ( ) ;
104+ }
105+ } }
106+ />
107+ < div className = "hide-me-captcha-buttons" >
108+ < button
109+ type = "button"
110+ onClick = { handleCaptchaSubmit }
111+ className = "hide-me-captcha-submit"
112+ >
113+ Submit
114+ </ button >
115+ < button
116+ type = "button"
117+ onClick = { handleCaptchaCancel }
118+ className = "hide-me-captcha-cancel"
119+ >
120+ Cancel
121+ </ button >
122+ </ div >
123+ </ div >
120124 </ div >
121125 </ div >
122- </ div >
123- </ div >
124- </ div >
125- ) ;
126- }
126+ </ div > ,
127+ document . body
128+ )
129+ : null ;
127130
128131 // Build CSS classes using classNames
129132 const classes = classNames (
130- 'hide-me' ,
131- blackOut ? 'hide-me--blackout' : ( mode === 'blur' ? 'hide-me--blur' : 'hide-me--captcha' ) ,
133+ 'hide-me' ,
134+ blackOut ? 'hide-me--blackout' : mode === 'blur' ? 'hide-me--blur' : 'hide-me--captcha' ,
132135 className
133136 ) ;
134137
@@ -138,27 +141,28 @@ export const HideMe: React.FC<HideMeProps> = ({
138141 '--hide-me-blur-amount' : `${ blurAmount } px` ,
139142 } ;
140143
141- const ariaLabel = blackOut
144+ const ariaLabel = blackOut
142145 ? 'Blacked out content. Click to reveal.'
143- : mode === 'captcha'
144- ? 'Hidden content. Click to solve CAPTCHA and reveal.'
145- : 'Hidden content. Click to reveal.' ;
146+ : mode === 'captcha'
147+ ? 'Hidden content. Click to solve CAPTCHA and reveal.'
148+ : 'Hidden content. Click to reveal.' ;
146149
147150 return (
148- < button
149- type = "button"
150- className = { classes }
151- style = { inlineStyles }
152- onClick = { handleReveal }
153- onKeyDown = { handleKeyDown }
154- aria-label = { ariaLabel }
155- aria-expanded = { isRevealed }
156- tabIndex = { 0 }
157- >
158- < span className = "sr-only" >
159- { ariaLabel }
160- </ span >
161- { blackOut ? '████████' : ( mode === 'captcha' ? '••••••••' : children ) }
162- </ button >
151+ < >
152+ < button
153+ type = "button"
154+ className = { classes }
155+ style = { inlineStyles }
156+ onClick = { handleReveal }
157+ onKeyDown = { handleKeyDown }
158+ aria-label = { ariaLabel }
159+ aria-expanded = { isRevealed }
160+ tabIndex = { 0 }
161+ >
162+ < span className = "sr-only" > { ariaLabel } </ span >
163+ { blackOut ? '████████' : mode === 'captcha' ? '••••••••' : children }
164+ </ button >
165+ { captchaModal }
166+ </ >
163167 ) ;
164168} ;
0 commit comments