Skip to content

Commit 6e34720

Browse files
authored
chore: add solutions menu for header (#471)
* chore: add solutions menu for header * chore: update
1 parent 2faa1b9 commit 6e34720

File tree

5 files changed

+310
-374
lines changed

5 files changed

+310
-374
lines changed

src/components/shared/footer/footer.tsx

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,82 @@ import { BsGithub, BsDiscord, BsTwitterX, BsYoutube, BsLinkedin } from 'react-ic
22

33
import Link from '@/components/shared/link';
44

5-
import { MENU } from '@/lib/menus';
65
import Route from '@/lib/route';
76

7+
const FOOTER_MENU = [
8+
{
9+
name: 'DATABASES',
10+
items: [
11+
{ name: 'MySQL', linkUrl: Route.DATABASE_MYSQL },
12+
{ name: 'PostgreSQL', linkUrl: Route.DATABASE_POSTGRES },
13+
{ name: 'Snowflake', linkUrl: Route.DATABASE_SNOWFLAKE },
14+
{ name: 'Oracle', linkUrl: Route.DATABASE_ORACLE },
15+
{ name: 'SQL Server', linkUrl: Route.DATABASE_SQLSERVER },
16+
{ name: 'MongoDB', linkUrl: Route.DATABASE_MONGO },
17+
{ name: 'Redis', linkUrl: Route.DATABASE_REDIS },
18+
{ name: 'Redshift', linkUrl: Route.DATABASE_REDSHIFT },
19+
{ name: 'ClickHouse', linkUrl: Route.DATABASE_CLICKHOUSE },
20+
{ name: 'TiDB', linkUrl: Route.DATABASE_TIDB },
21+
{ name: 'OceanBase', linkUrl: Route.DATABASE_OCEANBASE },
22+
{ name: 'Google Spanner', linkUrl: Route.DATABASE_SPANNER },
23+
{ name: 'MariaDB', linkUrl: Route.DATABASE_MARIADB },
24+
{ name: 'Databricks', linkUrl: Route.DATABASE_DATABRICKS },
25+
],
26+
},
27+
{
28+
name: 'INTEGRATIONS',
29+
items: [
30+
{ name: 'GitLab', linkUrl: Route.INTEGRATION_GITLAB },
31+
{ name: 'GitHub', linkUrl: Route.INTEGRATION_GITHUB },
32+
{ name: 'Bitbucket', linkUrl: Route.INTEGRATION_BITBUCKET },
33+
{ name: 'Azure DevOps', linkUrl: Route.INTEGRATION_AZURE_DEVOPS },
34+
{ name: 'Slack', linkUrl: Route.INTEGRATION_SLACK },
35+
{ name: 'Discord', linkUrl: Route.INTEGRATION_DISCORD },
36+
{ name: 'Teams', linkUrl: Route.INTEGRATION_TEAMS },
37+
{ name: 'DingTalk', linkUrl: Route.INTEGRATION_DINGTALK },
38+
{ name: 'Lark', linkUrl: Route.INTEGRATION_LARK },
39+
{ name: 'WeCom', linkUrl: Route.INTEGRATION_WECOM },
40+
],
41+
},
42+
{
43+
name: 'COMPARISONS',
44+
items: [
45+
{ name: 'vs. Liquibase', linkUrl: Route.VS_LIQUIBASE },
46+
{ name: 'vs. Flyway', linkUrl: Route.VS_FLYWAY },
47+
{ name: 'vs. CloudBeaver', linkUrl: Route.VS_CLOUDBEAVER },
48+
{ name: 'vs. DBeaver', linkUrl: Route.VS_DBEAVER },
49+
{ name: 'vs. Navicat', linkUrl: Route.VS_NAVICAT },
50+
{ name: 'vs. Metabase', linkUrl: Route.VS_METABASE },
51+
{ name: 'vs. schemachange', linkUrl: Route.VS_SCHEMACHANGE },
52+
{ name: 'vs. Jira', linkUrl: Route.VS_JIRA },
53+
],
54+
},
55+
{
56+
name: 'RESOURCES',
57+
items: [
58+
{ name: 'Documentation', linkUrl: Route.DOCS },
59+
{ name: 'Changelog', linkUrl: Route.CHANGELOG },
60+
{ name: 'Schema Migration', linkUrl: Route.SCHEMA_MIGRATION },
61+
{ name: 'SQL Editor', linkUrl: Route.SQL_EDITOR },
62+
{ name: 'Dynamic Data Masking', linkUrl: Route.DATA_MASKING },
63+
{ name: 'SQL Review Guide', linkUrl: Route.SQL_REVIEW_GUIDE },
64+
{ name: 'Database Glossary', linkUrl: Route.DATABASE_GLOSSARY },
65+
],
66+
},
67+
{
68+
name: 'COMPANY',
69+
items: [
70+
{ name: 'About', linkUrl: Route.ABOUT },
71+
{ name: 'Brand', linkUrl: Route.BRAND },
72+
{ name: 'Terms', linkUrl: Route.TERMS },
73+
{ name: 'Policy', linkUrl: Route.PRIVACY },
74+
{ name: 'Security', linkUrl: Route.SECURITY },
75+
{ name: 'Partners', linkUrl: Route.PARTNER },
76+
{ name: 'Contact', linkUrl: Route.CONTACTS },
77+
],
78+
},
79+
];
80+
881
const socialLinks = [
982
{
1083
name: 'Github',
@@ -43,8 +116,8 @@ const Footer = () => {
43116
return (
44117
<footer className="safe-paddings container relative z-10 shrink-0 pt-20 lg:pt-14 md:pt-12 xs:pt-10">
45118
<div className="grid grid-cols-10 gap-x-10 xl:gap-x-9 lg:gap-x-6 md:gap-x-7 sm:grid sm:grid-cols-4 sm:gap-x-4 sm:gap-y-14">
46-
{MENU.footer.map(({ name, items }, idx) => (
47-
<div className="col-span-2" key={idx}>
119+
{FOOTER_MENU.map(({ name, items }) => (
120+
<div className="col-span-2" key={name}>
48121
<h3 className="text-14 font-bold leading-none tracking-wider text-gray-60">{name}</h3>
49122
<ul className="mt-7 flex flex-col gap-[18px]">
50123
{items.map(({ name: childName, linkUrl }, childIdx) => (

src/components/shared/header/header.tsx

Lines changed: 183 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import Button from '@/components/shared/button';
77
import Link from '@/components/shared/link';
88
import MobileMenu from '@/components/shared/mobile-menu';
99

10-
import { MENU } from '@/lib/menus';
1110
import Route from '@/lib/route';
1211

1312
import 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+
5963
type MenuItem = {
6064
name: string;
61-
description: string;
62-
iconName: string;
6365
linkUrl: string;
66+
description?: string;
67+
iconName?: string;
6468
};
6569

6670
type HighlightItem = {
@@ -74,10 +78,136 @@ type HighlightItem = {
7478
type 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+
81211
const 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

Comments
 (0)