1+ import { TextAttributes } from '@opentui/core'
12import open from 'open'
23import React , { useCallback , useState } from 'react'
34
@@ -10,6 +11,8 @@ import type { AdResponse } from '../hooks/use-gravity-ad'
1011
1112interface AdBannerProps {
1213 ad : AdResponse
14+ onDisableAds : ( ) => void
15+ isFreeMode : boolean
1316}
1417
1518const extractDomain = ( url : string ) : string => {
@@ -21,10 +24,14 @@ const extractDomain = (url: string): string => {
2124 }
2225}
2326
24- export const AdBanner : React . FC < AdBannerProps > = ( { ad } ) => {
27+ export const AdBanner : React . FC < AdBannerProps > = ( { ad, onDisableAds , isFreeMode } ) => {
2528 const theme = useTheme ( )
2629 const { separatorWidth, terminalWidth } = useTerminalDimensions ( )
2730 const [ isLinkHovered , setIsLinkHovered ] = useState ( false )
31+ const [ showInfoPanel , setShowInfoPanel ] = useState ( false )
32+ const [ isAdLabelHovered , setIsAdLabelHovered ] = useState ( false )
33+ const [ isHideHovered , setIsHideHovered ] = useState ( false )
34+ const [ isCloseHovered , setIsCloseHovered ] = useState ( false )
2835
2936 const handleClick = useCallback ( ( ) => {
3037 if ( ad . clickUrl ) {
@@ -40,8 +47,8 @@ export const AdBanner: React.FC<AdBannerProps> = ({ ad }) => {
4047 const ctaText = ad . cta || ad . title || 'Learn more'
4148
4249 // Calculate available width for ad text
43- // Account for: padding (2), "Ad" label with space (3 )
44- const maxTextWidth = separatorWidth - 5
50+ // Account for: padding (2), "Ad ? " label with space (5 )
51+ const maxTextWidth = separatorWidth - 7
4552
4653 return (
4754 < box
@@ -72,7 +79,20 @@ export const AdBanner: React.FC<AdBannerProps> = ({ ad }) => {
7279 >
7380 { ad . adText }
7481 </ text >
75- < text style = { { fg : theme . muted , flexShrink : 0 } } > Ad</ text >
82+ < Button
83+ onClick = { ( ) => setShowInfoPanel ( true ) }
84+ onMouseOver = { ( ) => setIsAdLabelHovered ( true ) }
85+ onMouseOut = { ( ) => setIsAdLabelHovered ( false ) }
86+ >
87+ < text
88+ style = { {
89+ fg : isAdLabelHovered && ! showInfoPanel ? theme . foreground : theme . muted ,
90+ flexShrink : 0 ,
91+ } }
92+ >
93+ { isAdLabelHovered && ! showInfoPanel ? 'Ad ?' : ' Ad' }
94+ </ text >
95+ </ Button >
7696 </ box >
7797 { /* Bottom line: button, domain, credits */ }
7898 < box
@@ -108,6 +128,82 @@ export const AdBanner: React.FC<AdBannerProps> = ({ ad }) => {
108128 < text style = { { fg : theme . muted } } > +{ ad . credits } credits</ text >
109129 ) }
110130 </ box >
131+ { /* Info panel: shown when Ad label is clicked, below the ad */ }
132+ { showInfoPanel && (
133+ < box
134+ style = { {
135+ width : '100%' ,
136+ flexDirection : 'column' ,
137+ gap : 0 ,
138+ } }
139+ >
140+ < text style = { { fg : theme . muted } } > { ' ' + '┄' . repeat ( separatorWidth - 2 ) } </ text >
141+ < box
142+ style = { {
143+ width : '100%' ,
144+ paddingLeft : 1 ,
145+ paddingRight : 1 ,
146+ flexDirection : 'row' ,
147+ justifyContent : 'space-between' ,
148+ alignItems : 'flex-start' ,
149+ } }
150+ >
151+ < text style = { { fg : theme . muted , flexShrink : 1 } } >
152+ Ads are optional and earn you credits on each impression. Feel free to hide them anytime.
153+ </ text >
154+ < Button
155+ onClick = { ( ) => setShowInfoPanel ( false ) }
156+ onMouseOver = { ( ) => setIsCloseHovered ( true ) }
157+ onMouseOut = { ( ) => setIsCloseHovered ( false ) }
158+ >
159+ < text
160+ style = { {
161+ fg : isCloseHovered ? theme . foreground : theme . muted ,
162+ flexShrink : 0 ,
163+ } }
164+ >
165+ { ' ✕' }
166+ </ text >
167+ </ Button >
168+ </ box >
169+ < box
170+ style = { {
171+ paddingLeft : 1 ,
172+ paddingRight : 1 ,
173+ flexDirection : 'row' ,
174+ alignItems : 'center' ,
175+ gap : 2 ,
176+ } }
177+ >
178+ { isFreeMode ? (
179+ < text style = { { fg : theme . muted } } >
180+ Ads are required in Free mode.
181+ </ text >
182+ ) : (
183+ < >
184+ < Button
185+ onClick = { onDisableAds }
186+ onMouseOver = { ( ) => setIsHideHovered ( true ) }
187+ onMouseOut = { ( ) => setIsHideHovered ( false ) }
188+ >
189+ < text
190+ style = { {
191+ fg : isHideHovered ? theme . link : theme . muted ,
192+ attributes : TextAttributes . UNDERLINE ,
193+ } }
194+ >
195+ Hide ads
196+ </ text >
197+ </ Button >
198+ < text style = { { fg : theme . muted } } > ·</ text >
199+ < text style = { { fg : theme . muted } } >
200+ Use /ads:enable to show again
201+ </ text >
202+ </ >
203+ ) }
204+ </ box >
205+ </ box >
206+ ) }
111207 </ box >
112208 )
113209}
0 commit comments