@@ -8,134 +8,94 @@ import blurBean from "@/app/conf/2025/components/footer/blur-bean.webp"
8
8
9
9
import { renderComponent } from "../utils"
10
10
import { Anchor } from "@/app/conf/_design-system/anchor"
11
+ import type { ReactNode } from "react"
12
+ import { ConferenceFooterBox } from "./conference-footer-box"
11
13
12
14
interface FooterLink {
13
- title : string
15
+ title : ReactNode
14
16
route : string
15
17
}
16
18
17
19
interface FooterSection {
18
- title ?: string
20
+ title ?: ReactNode
19
21
route ?: string
20
22
links : FooterLink [ ]
21
23
}
22
24
23
25
const FOOTER_SECTIONS_COUNT = 4
24
26
const MAX_LINKS_PER_SECTION = 5
27
+ const CONFERENCE_YEAR = 2025
25
28
26
- function useFooterSections ( ) : FooterSection [ ] {
27
- const { normalizePagesResult } = useConfig ( )
28
-
29
- const sections : FooterSection [ ] = [ ]
30
- const singleLinks : FooterLink [ ] = [ ]
31
-
32
- for ( const item of normalizePagesResult . topLevelNavbarItems ) {
33
- if (
34
- ( item . type === "page" || item . type === "menu" ) &&
35
- item . children ?. length &&
36
- sections . length < FOOTER_SECTIONS_COUNT - 1
37
- ) {
38
- sections . push ( {
39
- title : item . title ,
40
- route : item . route ,
41
- links : ( item . children || [ ] )
42
- . slice ( 0 , MAX_LINKS_PER_SECTION )
43
- . map ( child => ( { title : child . title , route : child . route } ) ) ,
44
- } )
45
- } else if ( singleLinks . length < MAX_LINKS_PER_SECTION ) {
46
- if ( ! item . route ?. startsWith ( "/conf/" ) ) {
47
- singleLinks . push ( { title : item . title , route : item . route } )
48
- }
49
- }
50
- }
51
-
52
- sections . push ( { links : singleLinks } )
53
- return sections
54
- }
55
-
56
- export function Footer ( ) {
57
- const sections = useFooterSections ( )
29
+ export function Footer ( { extraLinks } : { extraLinks : FooterLink [ ] } ) {
30
+ const { sections, hasConferenceBox } = useFooterSections ( extraLinks )
58
31
const themeConfig = useThemeConfig ( )
59
32
60
- // const footerSections: FooterSection[] =
61
- // normalizePagesResult.topLevelNavbarItems
62
- // .filter(item => item.type === "page" || item.type === "menu")
63
- // .filter(item => item.children)
64
- // .map(item => {
65
- // return {
66
- // title: item.title,
67
- // route: item.route,
68
- // links: (item.children || [])
69
- // .filter(
70
- // child =>
71
- // child.type === "doc" &&
72
- // child.route &&
73
- // !child.name.startsWith("--"),
74
- // )
75
- // .slice(0, MAX_LINKS_PER_SECTION)
76
- // .map(child => ({
77
- // title: child.title || child.name,
78
- // route: child.route,
79
- // })),
80
- // }
81
- // })
82
-
83
33
return (
84
- < footer className = "typography-menu relative !bg-neu-100 text-neu-900 dark:!bg-neu-0 max-md:px-0 max-md:pt-0" >
34
+ < footer className = "relative !bg-neu-100 text-neu-900 dark:!bg-neu-0 max-md:px-0 max-md:pt-0" >
85
35
< Stripes />
86
36
87
37
< div className = "mx-auto max-w-[120rem] border-neu-400 dark:border-neu-100 3xl:border-x" >
88
- { /* TODO: Move themeSwitch to the bottom section and remove padding. */ }
89
- { /* Top section with logo and theme switch */ }
90
38
< div className = "flex flex-wrap justify-between gap-4 p-4 max-md:w-full md:p-6 2xl:px-10" >
91
39
< NextLink href = "/" className = "nextra-logo flex items-center" >
92
40
< GraphQLWordmarkLogo className = "h-6" title = "GraphQL" />
93
41
</ NextLink >
94
- { themeConfig . darkMode && (
95
- < div className = "flex items-center" >
96
- { renderComponent ( themeConfig . themeSwitch . component ) }
97
- </ div >
98
- ) }
99
42
</ div >
100
43
101
- { /* Navigation grid */ }
102
- < ul className = "grid grid-cols-2 gap-px bg-neu-400 py-px dark:bg-neu-100 lg:grid-cols-4" >
44
+ < div className = "grid grid-cols-2 gap-px bg-neu-400 py-px dark:bg-neu-100 lg:grid-cols-5" >
103
45
{ sections . map ( ( section , i ) => (
104
- < li className = "bg-neu-100 dark:bg-neu-0" key = { i } >
105
- < div className = "relative flex flex-col px-4 py-8 3xl:px-6 3xl:py-10" >
106
- { section . title && (
107
- < h3 className = "block h-full font-bold lg:mb-4 3xl:mb-10" >
108
- { section . route ? (
109
- < Anchor
110
- className = "gql-focus-visible block p-3 underline-offset-4 hover:underline"
111
- href = { section . route }
112
- >
113
- { section . title }
114
- </ Anchor >
115
- ) : (
116
- < span className = "block p-3" > { section . title } </ span >
117
- ) }
118
- </ h3 >
119
- ) }
120
- { section . links . map ( link => (
121
- < Anchor
122
- key = { link . route }
123
- href = { link . route }
124
- className = "gql-focus-visible block h-full p-3 underline-offset-4 hover:underline"
125
- >
126
- { link . title }
127
- </ Anchor >
128
- ) ) }
129
- </ div >
130
- </ li >
46
+ < div
47
+ className = "typography-menu relative bg-neu-100 py-4 dark:bg-neu-0 lg:py-6 3xl:py-10"
48
+ key = { i }
49
+ >
50
+ { section . title && (
51
+ < h3 className = "font-bold lg:mb-4 3xl:mb-10" >
52
+ { section . route ? (
53
+ < Anchor
54
+ className = "gql-focus-visible block p-4 underline-offset-4 hover:underline md:px-6 2xl:px-10"
55
+ href = { section . route }
56
+ >
57
+ { section . title }
58
+ </ Anchor >
59
+ ) : (
60
+ < span className = "block p-4 3xl:px-10" > { section . title } </ span >
61
+ ) }
62
+ </ h3 >
63
+ ) }
64
+ { section . links . map ( link => (
65
+ < Anchor
66
+ key = { link . route }
67
+ href = { link . route }
68
+ className = "gql-focus-visible block p-4 underline-offset-4 hover:underline md:px-6 2xl:px-10"
69
+ >
70
+ { link . title }
71
+ </ Anchor >
72
+ ) ) }
73
+ </ div >
131
74
) ) }
132
- </ ul >
133
-
134
- < div className = "relative flex items-center justify-between gap-10 p-4 max-lg:flex-col md:px-6 2xl:px-10" >
135
- < div className = "flex flex-col font-light max-md:gap-5" >
136
- < p > { renderComponent ( themeConfig . footer . content ) } </ p >
75
+ < div className = "flex flex-col max-lg:contents" >
76
+ { hasConferenceBox && (
77
+ < ConferenceFooterBox
78
+ href = { `/conf/${ CONFERENCE_YEAR } ` }
79
+ className = "z-[2] col-span-full flex-1 max-lg:row-start-1"
80
+ />
81
+ ) }
82
+ < SocialIcons
83
+ count = { 4 }
84
+ className = "col-span-full gap-px text-pri-base *:flex *:flex-1 *:items-center *:justify-center *:bg-neu-100 *:dark:bg-neu-0 max-sm:*:aspect-square lg:*:aspect-square [&>a:focus]:text-current [&>a:focus]:ring-transparent [&>a:hover]:bg-neu-0/90 [&>a:hover]:text-current [&_svg]:!h-8"
85
+ />
137
86
</ div >
138
- < SocialIcons className = "[&>a:focus]:text-current [&>a:focus]:ring-transparent [&>a:hover]:bg-neu-900/10 [&>a:hover]:text-current" />
87
+ </ div >
88
+
89
+ < div className = "relative flex items-center justify-between gap-4 p-4 md:px-6 2xl:px-10" >
90
+ { themeConfig . darkMode && (
91
+ // todo: new theme switch component
92
+ < div className = "typography-menu flex items-center *:rounded-none dark:*:text-neu-900" >
93
+ { renderComponent ( themeConfig . themeSwitch . component ) }
94
+ </ div >
95
+ ) }
96
+ < p className = "typography-body-xs flex flex-col text-pretty max-md:gap-5" >
97
+ { renderComponent ( themeConfig . footer . content ) }
98
+ </ p >
139
99
</ div >
140
100
</ div >
141
101
</ footer >
@@ -149,7 +109,7 @@ function Stripes() {
149
109
// prettier-ignore
150
110
// false positive
151
111
// eslint-disable-next-line tailwindcss/no-contradicting-classname
152
- className = "pointer-events-none absolute inset-0
112
+ className = "pointer-events-none absolute inset-0 z-[1]
153
113
[--start-1:rgba(255,204,239,.05)]
154
114
[--end-1:hsl(var(--color-pri-base)/.8)]
155
115
dark:[--start-1:hsl(var(--color-pri-darker))]
@@ -162,6 +122,15 @@ function Stripes() {
162
122
163
123
mix-blend-darken
164
124
dark:mix-blend-lighten
125
+
126
+ max-sm:![mask-size:300vw_200px]
127
+ max-sm:![mask-position:center_calc(100%-100px)]
128
+
129
+ sm:max-lg:![mask-origin:bottom]
130
+ sm:max-lg:![mask-size:250%_300px]
131
+ sm:max-lg:![mask-position:center_calc(100%-30px)]
132
+
133
+ max-lg:rotate-180
165
134
"
166
135
style = { {
167
136
maskImage : `url(${ blurBean . src } )` ,
@@ -182,3 +151,42 @@ function Stripes() {
182
151
</ div >
183
152
)
184
153
}
154
+
155
+ function useFooterSections ( extraLinks : FooterLink [ ] ) : {
156
+ sections : FooterSection [ ]
157
+ hasConferenceBox : boolean
158
+ } {
159
+ const { normalizePagesResult } = useConfig ( )
160
+
161
+ const sections : FooterSection [ ] = [ ]
162
+ const singleLinks : FooterLink [ ] = [ ]
163
+ let hasConferenceBox = false
164
+
165
+ for ( const item of normalizePagesResult . topLevelNavbarItems ) {
166
+ if (
167
+ ( item . type === "page" || item . type === "menu" ) &&
168
+ item . children ?. length &&
169
+ sections . length < FOOTER_SECTIONS_COUNT - 1
170
+ ) {
171
+ sections . push ( {
172
+ title : item . title ,
173
+ route : item . route ,
174
+ links : ( item . children || [ ] )
175
+ . filter ( child => child . route )
176
+ . slice ( 0 , MAX_LINKS_PER_SECTION )
177
+ . map ( child => ( { title : child . title , route : child . route } ) ) ,
178
+ } )
179
+ } else if ( singleLinks . length < MAX_LINKS_PER_SECTION ) {
180
+ if ( item . route && item . route . startsWith ( "/conf/" ) ) {
181
+ hasConferenceBox = true
182
+ } else {
183
+ singleLinks . push ( { title : item . title , route : item . route } )
184
+ }
185
+ }
186
+ }
187
+
188
+ singleLinks . push ( ...extraLinks )
189
+ sections . push ( { links : singleLinks } )
190
+
191
+ return { sections, hasConferenceBox }
192
+ }
0 commit comments