@@ -7,7 +7,6 @@ import Button from '@/components/shared/button';
77import Link from '@/components/shared/link' ;
88import MobileMenu from '@/components/shared/mobile-menu' ;
99
10- import { MENU } from '@/lib/menus' ;
1110import Route from '@/lib/route' ;
1211
1312import AboutIcon from '@/svgs/about.inline.svg' ;
@@ -56,11 +55,16 @@ const icons: {
5655 tutorial : TutorialsIcon ,
5756} ;
5857
58+ type Menu = {
59+ items : MenuItem [ ] ;
60+ title ?: string ;
61+ } ;
62+
5963type MenuItem = {
6064 name : string ;
61- description : string ;
62- iconName : string ;
6365 linkUrl : string ;
66+ description ?: string ;
67+ iconName ?: string ;
6468} ;
6569
6670type HighlightItem = {
@@ -74,10 +78,136 @@ type HighlightItem = {
7478type Header = {
7579 title : string ;
7680 href ?: string ;
77- items ?: MenuItem [ ] ;
81+ menus ?: Menu [ ] ;
7882 highlight ?: HighlightItem ;
7983} ;
8084
85+ export const HEADER_MENU : Header [ ] = [
86+ { title : 'WHY Bytebase' , href : Route . DOCS } ,
87+ {
88+ title : 'Solutions' ,
89+ menus : [
90+ {
91+ title : 'By Use Case' ,
92+ items : [
93+ {
94+ name : 'Database CI/CD' ,
95+ linkUrl : Route . DOCS_DATABASE_CI_CD ,
96+ } ,
97+ {
98+ name : 'Multi-tenant, multi-region deployment' ,
99+ linkUrl : Route . DOCS_MULTI_TENANCY_DEPLOYMENT ,
100+ } ,
101+ {
102+ name : 'Headless database workflow backend' ,
103+ linkUrl : Route . DOCS_API_OVERVIEW ,
104+ } ,
105+ ] ,
106+ } ,
107+ {
108+ title : 'By Industry' ,
109+ items : [
110+ {
111+ name : 'Financial Services' ,
112+ linkUrl : Route . INDUSTRY_FINANCIAL_SERVICES ,
113+ } ,
114+ {
115+ name : 'Technology' ,
116+ linkUrl : Route . INDUSTRY_TECHNOLOGY ,
117+ } ,
118+ {
119+ name : 'Manufacturing' ,
120+ linkUrl : Route . INDUSTRY_MANUFACTURING ,
121+ } ,
122+ {
123+ name : 'Gaming' ,
124+ linkUrl : Route . INDUSTRY_GAMING ,
125+ } ,
126+ {
127+ name : 'Web3' ,
128+ linkUrl : Route . INDUSTRY_WEB3 ,
129+ } ,
130+ ] ,
131+ } ,
132+ ] ,
133+ } ,
134+ {
135+ title : 'Features' ,
136+ menus : [
137+ {
138+ items : [
139+ {
140+ name : 'Schema Migration' ,
141+ description : 'GUI-based, database CI/CD with GitOps' ,
142+ linkUrl : Route . SCHEMA_MIGRATION ,
143+ iconName : 'migrate' ,
144+ } ,
145+ {
146+ name : 'Permission-based SQL Editor' ,
147+ description : 'Bastion-less human-to-database permission control' ,
148+ linkUrl : Route . SQL_EDITOR ,
149+ iconName : 'editor' ,
150+ } ,
151+ {
152+ name : 'Dynamic Data Masking' ,
153+ description : 'Role-based multi-level masking policy' ,
154+ linkUrl : Route . DATA_MASKING ,
155+ iconName : 'mask' ,
156+ } ,
157+ {
158+ name : 'Batch Change' ,
159+ description : 'Multi-environments, multi-regions, multi-tenants' ,
160+ linkUrl : Route . BATCH_CHANGE ,
161+ iconName : 'batch' ,
162+ } ,
163+ ] ,
164+ } ,
165+ ] ,
166+ } ,
167+ {
168+ title : 'Resources' ,
169+ menus : [
170+ {
171+ items : [
172+ {
173+ name : 'Docs' ,
174+ linkUrl : Route . DOCS ,
175+ iconName : 'intro' ,
176+ } ,
177+ {
178+ name : 'Supported Databases' ,
179+ linkUrl : Route . DOCS_DB ,
180+ iconName : 'db' ,
181+ } ,
182+ {
183+ name : 'Case Study' ,
184+ linkUrl : Route . BLOG_CASE_STUDY ,
185+ iconName : 'casestudy' ,
186+ } ,
187+ {
188+ name : 'Blog' ,
189+ linkUrl : Route . BLOG ,
190+ iconName : 'blog' ,
191+ } ,
192+ {
193+ name : 'Company' ,
194+ linkUrl : Route . ABOUT ,
195+ iconName : 'about' ,
196+ } ,
197+ ] ,
198+ } ,
199+ ] ,
200+ highlight : {
201+ name : 'Tutorial' ,
202+ description : 'Step-by-step guide through common features.' ,
203+ linkUrl : Route . TUTORIAL ,
204+ cta : 'Start Learning' ,
205+ iconName : 'tutorial' ,
206+ } ,
207+ } ,
208+ { title : 'Pricing' , href : Route . PRICING } ,
209+ ] ;
210+
81211const Header = ( { hasBanner = false } : { hasBanner ?: boolean } ) => {
82212 const topBanner = PROMO_DATA . TOP_BANNER ;
83213 const [ canShowSubmenu , setCanShowSubmenu ] = useState ( true ) ;
@@ -118,10 +248,10 @@ const Header = ({ hasBanner = false }: { hasBanner?: boolean }) => {
118248 loading = "eager"
119249 />
120250 </ Link >
121- < ul className = "ml-9 mt-0.5 flex items-center gap-1 md:hidden" >
122- { MENU . header . map ( ( { title, href = '' , items , highlight } : Header ) => {
251+ < ul className = "ml-8 mt-0.5 flex items-center gap-1 md:hidden" >
252+ { HEADER_MENU . map ( ( { title, href = '' , menus , highlight } : Header ) => {
123253 return (
124- < li key = { title } className = "group relative inline-block hover:cursor-pointer " >
254+ < li key = { title } className = "group relative inline-block" >
125255 { href ? (
126256 < Link
127257 className = "px-3 py-2.5 text-16 font-medium tracking-wider"
@@ -138,36 +268,52 @@ const Header = ({ hasBanner = false }: { hasBanner?: boolean }) => {
138268 < ChevronIcon className = "h-3 w-3 transition-transform duration-200 group-hover:-rotate-180" />
139269 </ button >
140270 ) }
141- { items ?. length && canShowSubmenu && (
142- < div className = "invisible absolute -left-5 top-6 pt-6 opacity-0 transition-[opacity,visibility] duration-200 group-hover:visible group-hover:opacity-100" >
143- < div className = "relative flex items-center gap-x-[30px] rounded-lg border border-gray-80 bg-white p-4 pl-8 shadow-menu before:absolute before:-top-[8.5px] before:left-11 before:h-4 before:w-4 before:rotate-45 before:rounded-tl before:border-l before:border-t before:border-gray-80 before:bg-white" >
144- < ul className = "flex flex-col" >
145- { items ?. map ( ( { name, linkUrl, description, iconName } ) => {
146- const Icon = iconName ? icons [ iconName ] : null ;
147- return (
148- < li key = { name } className = "pt-6 first:pt-2" >
149- < Link
150- className = "group/link block whitespace-nowrap"
151- size = "md"
152- theme = "gray"
153- href = { linkUrl }
154- prefetch = { false }
155- onClick = { handleSubmenuClick }
156- >
157- < div className = "flex flex-col gap-y-2.5" >
158- < div className = "flex items-center gap-x-2 group-hover/link:text-primary-1" >
159- { Icon && < Icon className = "h-5 w-5 shrink-0" /> }
160- < span className = "font-medium tracking-tight" > { name } </ span >
161- </ div >
162- < span className = "text-16 leading-normal text-gray-40" >
163- { description }
164- </ span >
165- </ div >
166- </ Link >
167- </ li >
168- ) ;
169- } ) }
170- </ ul >
271+ { menus ?. length && canShowSubmenu && (
272+ < div className = "invisible absolute left-0 top-6 pt-4 opacity-0 transition-[opacity,visibility] duration-200 group-hover:visible group-hover:opacity-100" >
273+ < div className = "relative -left-1/3 flex items-start gap-x-8 rounded-lg border border-gray-80 bg-white p-6 shadow-menu" >
274+ { menus . map ( ( { items, title : subtitle } ) => (
275+ < div
276+ key = { `${ title } -${ subtitle } ` }
277+ className = "flex h-full flex-col items-start justify-start"
278+ >
279+ { subtitle && (
280+ < p className = "pb-3 pt-1 text-16 font-medium leading-none text-gray-60" >
281+ { subtitle }
282+ </ p >
283+ ) }
284+ < ul className = "flex flex-col justify-between" >
285+ { items ?. map ( ( { name, linkUrl, description, iconName } ) => {
286+ const Icon = iconName ? icons [ iconName ] : null ;
287+ return (
288+ < li key = { name } className = "pb-2 pt-1" >
289+ < Link
290+ className = "group/link block whitespace-nowrap"
291+ size = "md"
292+ theme = "gray"
293+ href = { linkUrl }
294+ prefetch = { false }
295+ onClick = { handleSubmenuClick }
296+ >
297+ < div className = "flex flex-col" >
298+ < div className = "flex items-center gap-x-2 group-hover/link:text-primary-1" >
299+ { Icon && (
300+ < Icon className = "inline-block h-5 w-5 shrink-0 opacity-80" />
301+ ) }
302+ < span className = "font-medium tracking-tight" > { name } </ span >
303+ </ div >
304+ { description && (
305+ < span className = "pt-1 text-16 leading-normal text-gray-40" >
306+ { description }
307+ </ span >
308+ ) }
309+ </ div >
310+ </ Link >
311+ </ li >
312+ ) ;
313+ } ) }
314+ </ ul >
315+ </ div >
316+ ) ) }
171317 { highlight && (
172318 < Link
173319 className = "group/box flex h-full min-h-[272px] w-[244px] grow flex-col justify-between rounded-md bg-tutorials p-5 text-gray-15"
0 commit comments