@@ -2,13 +2,21 @@ import { useGetContents } from '@/services/hooks/content/get-content.hook'
22import { useRef , useState , useEffect } from 'react'
33import Analytics from '@/analytics'
44import { getFaviconFromUrl } from '@/common/utils/icon'
5+ import { useTheme } from '@/context/theme.context'
6+ import { useAppearanceSetting } from '@/context/appearance.context'
57
68interface LinkItem {
79 name : string
810 url : string
11+ type ?: 'SITE' | 'REMOTE_IFRAME'
912 icon ?: string
1013 badge ?: string
1114 badgeColor ?: string
15+ span ?: {
16+ col ?: number | null
17+ row ?: number | null
18+ }
19+ height ?: number
1220}
1321
1422interface CategoryItem {
@@ -17,9 +25,14 @@ interface CategoryItem {
1725 banner ?: string
1826 links : LinkItem [ ]
1927 icon ?: string
28+ span ?: {
29+ col ?: number | null
30+ row ?: number | null
31+ }
2032}
21-
2233export function ExplorerContent ( ) {
34+ const { theme } = useTheme ( )
35+ const { fontFamily } = useAppearanceSetting ( )
2336 const { data : catalogData } = useGetContents ( )
2437 const [ activeCategory , setActiveCategory ] = useState < string | null > ( null )
2538 const categoryRefs = useRef < { [ key : string ] : HTMLDivElement | null } > ( { } )
@@ -66,16 +79,16 @@ export function ExplorerContent() {
6679
6780 return (
6881 < div className = "flex flex-row w-full h-screen overflow-hidden" >
69- < aside className = "flex-col items-center hidden w-20 gap-3 py-4 md:flex bg-white/[0.02] backdrop-blur-sm border border-white/[0.08] rounded-[2.5rem] lg:mt-4 h-fit max-h-[calc(100vh-160px)] sticky top-4" >
82+ < aside className = "flex-col items-center hidden w-20 gap-3 py-4 md:flex bg-white/2 backdrop-blur-sm border border-white/8 rounded-3xl lg:mt-4 h-fit max-h-[calc(100vh-160px)] sticky top-4" >
7083 < div className = "flex flex-col items-center w-full gap-2 px-2 py-2 overflow-x-hidden overflow-y-auto scrollbar-none" >
7184 { catalogData ?. contents ?. map ( ( cat : CategoryItem ) => (
7285 < button
7386 key = { cat . id }
7487 onClick = { ( ) => scrollToCategory ( cat . id ) }
75- className = { `relative group flex flex-col items-center justify-center w-14 min-h-14 max-h-14 rounded-[1.5rem] transition-all duration-500 cursor-pointer border ${
88+ className = { `relative group flex flex-col items-center justify-center w-14 min-h-14 max-h-14 rounded-3xl transition-all duration-500 cursor-pointer border ${
7689 activeCategory === cat . id
7790 ? 'bg-primary text-white shadow-md shadow-primary/40 scale-110 border-primary/50'
78- : 'bg-white/[0.03] border-white/5 text-base-content/60 hover:bg-primary/10 hover:text-primary hover:scale-105 hover:border-primary/30'
91+ : 'bg-white/3 border-white/5 text-base-content/60 hover:bg-primary/10 hover:text-primary hover:scale-105 hover:border-primary/30'
7992 } `}
8093 >
8194 < div
@@ -111,7 +124,7 @@ export function ExplorerContent() {
111124 </ div >
112125 </ aside >
113126
114- < div className = "flex flex-col w-full h-full gap-3 px-2 py-3 overflow-hidden md:px-24 " >
127+ < div className = "flex flex-col w-full h-full gap-3 px-2 py-3 overflow-hidden md:px-10 " >
115128 < div className = "md:hidden sticky top-0 z-50 flex items-center w-full gap-2 p-1.5 overflow-x-auto bg-base-100/80 backdrop-blur-xl rounded-2xl border border-white/10 shadow-lg no-scrollbar flex-nowrap" >
116129 { catalogData ?. contents ?. map ( ( cat : CategoryItem ) => (
117130 < button
@@ -137,7 +150,7 @@ export function ExplorerContent() {
137150 ref = { scrollContainerRef }
138151 className = "flex-1 pb-10 pr-1 overflow-y-auto scrollbar-none scroll-smooth"
139152 >
140- < div className = "grid max-w-5xl grid-cols-1 gap-4 pb-[50vh] mx-auto md:grid-cols-2 " >
153+ < div className = "grid max-w-5xl grid-cols-1 gap-4 pb-[50vh] mx-auto md:grid-cols-3 " >
141154 { catalogData ?. contents ?. map (
142155 ( category : CategoryItem , index : number ) => (
143156 < div
@@ -146,9 +159,17 @@ export function ExplorerContent() {
146159 ref = { ( el ) => {
147160 categoryRefs . current [ category . id ] = el
148161 } }
162+ style = { {
163+ gridColumn : category . span ?. col
164+ ? `span ${ category . span . col } / span ${ category . span . col } `
165+ : undefined ,
166+ gridRow : category . span ?. row
167+ ? `span ${ category . span . row } / span ${ category . span . row } `
168+ : undefined ,
169+ } }
149170 className = { `relative overflow-hidden border scroll-mt-4 bg-content bg-glass border-base-300 rounded-3xl transition-all duration-300 ${
150171 index === 0
151- ? 'md:col-span-2' // آیتم اول همیشه تمام عرض
172+ ? 'md:col-span-2'
152173 : (
153174 index ===
154175 catalogData . contents
@@ -159,7 +180,7 @@ export function ExplorerContent() {
159180 2 ===
160181 0
161182 )
162- ? 'md:col-span-2' // اگر تعداد کل زوج بود (مثل ۶ تا)، آخری تمام عرض شود تا جای خالی پر شود
183+ ? 'md:col-span-2'
163184 : 'md:col-span-1'
164185 } `}
165186 >
@@ -179,7 +200,7 @@ export function ExplorerContent() {
179200 </ div >
180201 ) }
181202 < div className = "p-5" >
182- < div className = "flex items-center gap-4 mb-6 " >
203+ < div className = "flex items-center gap-4 mb-4 " >
183204 < div className = "flex items-center gap-2.5" >
184205 { category . icon ? (
185206 < img
@@ -190,54 +211,18 @@ export function ExplorerContent() {
190211 ) : (
191212 < div className = "w-1 h-3.5 rounded-full bg-primary" />
192213 ) }
193- < h3 className = "text-[10px] font-black tracking-widest uppercase opacity-40 " >
214+ < h3 className = "text-xs font-black tracking-widest uppercase opacity-70 " >
194215 { category . category }
195216 </ h3 >
196217 </ div >
197218 < div className = "flex-1 h-px bg-linear-to-r from-base-content/10 to-transparent" />
198219 </ div >
199- < div
200- className = { `grid gap-y-6 gap-x-2 grid-cols-3 sm:grid-cols-5` }
201- >
202- { category . links ?. map ( ( link , idx ) => (
203- < a
204- key = { idx }
205- href = { getUrl ( link . url ) }
206- target = "_blank"
207- rel = "noopener noreferrer"
208- className = "flex flex-col items-center gap-3 group/item active:scale-95"
209- >
210- < div className = "relative flex items-center justify-center w-12 h-12 transition-all duration-500 bg-base-200/40 rounded-2xl group-hover/item:bg-primary/20 group-hover/item:shadow-lg group-hover/item:shadow-primary/20 group-hover/item:-translate-y-1.5 border border-transparent group-hover/item:border-primary/20" >
211- { link . badge && (
212- < span
213- className = "absolute -top-1 -right-1 z-20 px-1.5 py-0.5 rounded-lg text-[8px] font-black border border-white/10 shadow-sm"
214- style = { {
215- backgroundColor :
216- link . badgeColor ||
217- 'var(--p)' ,
218- color : '#fff' ,
219- } }
220- >
221- { link . badge }
222- </ span >
223- ) }
224- < img
225- src = {
226- link . icon ||
227- getFaviconFromUrl (
228- link . url
229- )
230- }
231- className = "object-contain w-6 h-6 transition-all duration-500 rounded group-hover/item:scale-110 group-hover/item:brightness-110"
232- alt = { link . name }
233- />
234- </ div >
235- < span className = "text-[10px] font-semibold tracking-tighter text-center truncate w-full opacity-40 group-hover/item:opacity-100 group-hover/item:text-primary transition-all duration-300" >
236- { link . name }
237- </ span >
238- </ a >
239- ) ) }
240- </ div >
220+
221+ < HandleCatalogs
222+ category = { category }
223+ theme = { theme }
224+ fontFamily = { fontFamily }
225+ />
241226 </ div >
242227 </ div >
243228 )
@@ -254,3 +239,109 @@ export function ExplorerContent() {
254239function getUrl ( url : string ) {
255240 return url . startsWith ( 'http' ) ? url : `https://${ url } `
256241}
242+
243+ interface Prop {
244+ category : CategoryItem
245+ theme : string
246+ fontFamily : string
247+ }
248+ function HandleCatalogs ( { category, theme, fontFamily } : Prop ) {
249+ return (
250+ < div className = { `grid gap-y-6 gap-x-2 grid-cols-3 sm:grid-cols-5` } >
251+ { category . links ?. map ( ( link ) =>
252+ link . type === 'REMOTE_IFRAME' ? (
253+ < RenderIframeLinks
254+ key = { link . url }
255+ link = { link }
256+ theme = { theme }
257+ fontFamily = { fontFamily }
258+ />
259+ ) : (
260+ < RenderSiteLinks key = { link . url } link = { link } />
261+ )
262+ ) }
263+ </ div >
264+ )
265+ }
266+
267+ interface SiteProp {
268+ link : LinkItem
269+ }
270+ function RenderSiteLinks ( { link } : SiteProp ) {
271+ return (
272+ < a
273+ href = { getUrl ( link . url ) }
274+ target = "_blank"
275+ rel = "noopener noreferrer"
276+ className = "flex flex-col items-center gap-3 group/item active:scale-95"
277+ style = { {
278+ gridColumn : link . span ?. col
279+ ? `span ${ link . span . col } / span ${ link . span . col } `
280+ : undefined ,
281+ gridRow : link . span ?. row
282+ ? `span ${ link . span . row } / span ${ link . span . row } `
283+ : undefined ,
284+ } }
285+ >
286+ < div className = "relative flex items-center justify-center w-12 h-12 transition-all duration-500 bg-base-200/40 rounded-2xl group-hover/item:bg-primary/20 group-hover/item:shadow-lg group-hover/item:shadow-primary/20 group-hover/item:-translate-y-1.5 border border-transparent group-hover/item:border-primary/20" >
287+ { link . badge && (
288+ < span
289+ className = "absolute -top-1 -right-1 z-20 px-1.5 py-0.5 rounded-lg text-[8px] font-black border border-white/10 shadow-sm"
290+ style = { {
291+ backgroundColor : link . badgeColor || 'var(--p)' ,
292+ color : '#fff' ,
293+ } }
294+ >
295+ { link . badge }
296+ </ span >
297+ ) }
298+ < img
299+ src = { link . icon || getFaviconFromUrl ( link . url ) }
300+ className = "object-contain w-6 h-6 transition-all duration-500 rounded group-hover/item:scale-110 group-hover/item:brightness-110"
301+ alt = { link . name }
302+ />
303+ </ div >
304+ < span className = "text-[10px] font-semibold tracking-tighter text-center truncate w-full opacity-40 group-hover/item:opacity-100 group-hover/item:text-primary transition-all duration-300" >
305+ { link . name }
306+ </ span >
307+ </ a >
308+ )
309+ }
310+
311+ interface IframeProp {
312+ link : LinkItem
313+ theme : string
314+ fontFamily : string
315+ }
316+ function RenderIframeLinks ( { link, theme, fontFamily } : IframeProp ) {
317+ const urlObj = new URL ( link . url )
318+ urlObj . searchParams . set ( 'theme' , encodeURIComponent ( theme ) )
319+ urlObj . searchParams . set ( 'font' , encodeURIComponent ( fontFamily ) )
320+ urlObj . searchParams . set ( 'referrer' , 'extension' )
321+ const url = urlObj . toString ( )
322+
323+ return (
324+ < div
325+ className = "w-full"
326+ style = { {
327+ gridColumn : link . span ?. col
328+ ? `span ${ link . span . col } / span ${ link . span . col } `
329+ : 'span 3 / span 3' ,
330+ gridRow : link . span ?. row
331+ ? `span ${ link . span . row } / span ${ link . span . row } `
332+ : undefined ,
333+ } }
334+ >
335+ < iframe
336+ src = { url }
337+ height = { link . height }
338+ style = { {
339+ border : 'none' ,
340+ borderRadius : '1.5rem' ,
341+ width : '100%' ,
342+ } }
343+ title = { link . name }
344+ />
345+ </ div >
346+ )
347+ }
0 commit comments