11import { Theme } from '@logto/schemas' ;
2- import { useContext } from 'react' ;
2+ import { useContext , useEffect , useRef } from 'react' ;
33
44import PageContext from '@/Providers/PageContextProvider/PageContext' ;
55import LogtoLogtoDark from '@/assets/icons/logto-logo-dark.svg?react' ;
@@ -13,6 +13,68 @@ const logtoUrl = `https://logto.io/?${new URLSearchParams({
1313 utm_medium : 'powered_by' ,
1414} ) . toString ( ) } `;
1515
16+ const guardStyleSelector = 'style[data-logto-signature-guard="true"]' ;
17+
18+ const signatureGuardStyle = `
19+ [data-logto-signature-container="secured"][data-logto-signature-container="secured"] {
20+ display: block !important;
21+ visibility: visible !important;
22+ opacity: 1 !important;
23+ }
24+
25+ [data-logto-signature="secured"][data-logto-signature="secured"] {
26+ display: flex !important;
27+ align-items: center !important;
28+ justify-content: flex-start !important;
29+ font: var(--font-label-2) !important;
30+ font-weight: normal !important;
31+ color: var(--color-neutral-variant-60) !important;
32+ padding: 4px 8px !important;
33+ text-decoration: none !important;
34+ opacity: 75% !important;
35+ direction: ltr !important;
36+ position: relative !important;
37+ inset: auto !important;
38+ left: auto !important;
39+ right: auto !important;
40+ top: auto !important;
41+ bottom: auto !important;
42+ transform: none !important;
43+ pointer-events: auto !important;
44+ }
45+
46+ [data-logto-signature="secured"][data-logto-signature="secured"]:is(:hover, :active, :focus-visible) {
47+ opacity: 100% !important;
48+ }
49+
50+ [data-logto-signature="secured"][data-logto-signature="secured"] [data-logto-signature-icon="static"] {
51+ display: block !important;
52+ }
53+
54+ [data-logto-signature="secured"][data-logto-signature="secured"] [data-logto-signature-icon="highlight"] {
55+ display: none !important;
56+ }
57+
58+ [data-logto-signature="secured"][data-logto-signature="secured"]:is(:hover, :active, :focus-visible)
59+ [data-logto-signature-icon="static"] {
60+ display: none !important;
61+ }
62+
63+ [data-logto-signature="secured"][data-logto-signature="secured"]:is(:hover, :active, :focus-visible)
64+ [data-logto-signature-icon="highlight"] {
65+ display: block !important;
66+ }
67+
68+ [data-logto-signature-text] {
69+ margin-inline-end: 4px !important;
70+ }
71+
72+ body.mobile [data-logto-signature="secured"][data-logto-signature="secured"] {
73+ color: var(--color-neutral-variant-80) !important;
74+ font: var(--font-label-3) !important;
75+ }
76+ ` ;
77+
1678type Props = {
1779 readonly className ?: string ;
1880} ;
@@ -21,18 +83,107 @@ const LogtoSignature = ({ className }: Props) => {
2183 const { theme } = useContext ( PageContext ) ;
2284 const LogtoLogo = theme === Theme . Light ? LogtoLogoLight : LogtoLogtoDark ;
2385
86+ const containerRef = useRef < HTMLDivElement > ( null ) ;
87+ const anchorRef = useRef < HTMLAnchorElement > ( null ) ;
88+
89+ useEffect ( ( ) => {
90+ if ( typeof document === 'undefined' ) {
91+ return ;
92+ }
93+
94+ const { current : container } = containerRef ;
95+ const { current : anchor } = anchorRef ;
96+
97+ if ( ! anchor ) {
98+ return ;
99+ }
100+
101+ const ensureGuardStyle = ( ) : { created : boolean ; element : HTMLStyleElement } => {
102+ const existing = document . head . querySelector < HTMLStyleElement > ( guardStyleSelector ) ;
103+
104+ if ( existing ) {
105+ return { created : false , element : existing } ;
106+ }
107+
108+ const createdElement = document . createElement ( 'style' ) ;
109+ Reflect . set ( createdElement . dataset , 'logtoSignatureGuard' , 'true' ) ;
110+ createdElement . append ( signatureGuardStyle ) ;
111+ document . head . append ( createdElement ) ;
112+
113+ return { created : true , element : createdElement } ;
114+ } ;
115+
116+ const { created, element : guardStyleElement } = ensureGuardStyle ( ) ;
117+
118+ const enforceIntegrity = ( ) => {
119+ if ( container ) {
120+ container . removeAttribute ( 'hidden' ) ;
121+ container . style . setProperty ( 'display' , 'block' , 'important' ) ;
122+ container . style . setProperty ( 'visibility' , 'visible' , 'important' ) ;
123+ container . style . setProperty ( 'opacity' , '1' , 'important' ) ;
124+ container . style . setProperty ( 'position' , 'static' , 'important' ) ;
125+ }
126+
127+ anchor . removeAttribute ( 'hidden' ) ;
128+
129+ if ( styles . signature && ! anchor . classList . contains ( styles . signature ) ) {
130+ anchor . classList . add ( styles . signature ) ;
131+ }
132+
133+ anchor . style . removeProperty ( 'display' ) ;
134+ anchor . style . removeProperty ( 'visibility' ) ;
135+ anchor . style . removeProperty ( 'opacity' ) ;
136+ anchor . style . removeProperty ( 'position' ) ;
137+ anchor . style . removeProperty ( 'left' ) ;
138+ anchor . style . removeProperty ( 'right' ) ;
139+ anchor . style . removeProperty ( 'top' ) ;
140+ anchor . style . removeProperty ( 'bottom' ) ;
141+ anchor . style . removeProperty ( 'transform' ) ;
142+ } ;
143+
144+ enforceIntegrity ( ) ;
145+
146+ const observer = new MutationObserver ( ( ) => {
147+ enforceIntegrity ( ) ;
148+ } ) ;
149+
150+ observer . observe ( anchor , { attributes : true , attributeFilter : [ 'class' , 'style' , 'hidden' ] } ) ;
151+
152+ if ( container ) {
153+ observer . observe ( container , {
154+ attributes : true ,
155+ attributeFilter : [ 'class' , 'style' , 'hidden' ] ,
156+ } ) ;
157+ }
158+
159+ const intervalId = window . setInterval ( enforceIntegrity , 2000 ) ;
160+
161+ return ( ) => {
162+ observer . disconnect ( ) ;
163+ window . clearInterval ( intervalId ) ;
164+
165+ if ( created ) {
166+ guardStyleElement . remove ( ) ;
167+ }
168+ } ;
169+ } , [ className ] ) ;
170+
24171 return (
25- < div className = { className } >
172+ < div ref = { containerRef } className = { className } data-logto-signature-container = "secured" >
26173 < a
27- className = { styles . signature }
174+ ref = { anchorRef }
28175 aria-label = "Powered By Logto"
176+ className = { styles . signature }
177+ data-logto-signature = "secured"
29178 href = { logtoUrl . toString ( ) }
30- target = "_blank"
31179 rel = "noopener"
180+ target = "_blank"
32181 >
33- < span className = { styles . text } > Powered by</ span >
34- < LogtoLogoShadow className = { styles . staticIcon } />
35- < LogtoLogo className = { styles . highlightIcon } />
182+ < span data-logto-signature-text className = { styles . text } >
183+ Powered by
184+ </ span >
185+ < LogtoLogoShadow data-logto-signature-icon = "static" className = { styles . staticIcon } />
186+ < LogtoLogo data-logto-signature-icon = "highlight" className = { styles . highlightIcon } />
36187 </ a >
37188 </ div >
38189 ) ;
0 commit comments