first commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LanguagePicker from '../language-picker'
|
||||
import FacebookLogo from '@/shared/svgs/facebook-logo'
|
||||
import LinkedInLogo from '@/shared/svgs/linkedin-logo'
|
||||
import XLogo from '@/shared/svgs/x-logo'
|
||||
import classNames from 'classnames'
|
||||
|
||||
type FooterLinkProps = {
|
||||
href: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
type SocialMediaLinkProps = {
|
||||
href: string
|
||||
icon: React.ReactNode
|
||||
className: string
|
||||
accessibilityLabel: string
|
||||
}
|
||||
|
||||
function FatFooterBase() {
|
||||
const { t } = useTranslation()
|
||||
const currentYear = new Date().getFullYear()
|
||||
|
||||
return (
|
||||
<footer className="fat-footer-base">
|
||||
<div className="fat-footer-base-section fat-footer-base-meta">
|
||||
<div className="fat-footer-base-item">
|
||||
<div className="fat-footer-base-copyright">
|
||||
© {currentYear} Overleaf
|
||||
</div>
|
||||
<FooterBaseLink href="/legal">
|
||||
{t('privacy_and_terms')}
|
||||
</FooterBaseLink>
|
||||
<FooterBaseLink href="https://www.digital-science.com/security-certifications/">
|
||||
{t('compliance')}
|
||||
</FooterBaseLink>
|
||||
</div>
|
||||
<div className="fat-footer-base-item fat-footer-base-language">
|
||||
<LanguagePicker showHeader={false} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="fat-footer-base-section fat-footer-base-social">
|
||||
<div className="fat-footer-base-item">
|
||||
<SocialMediaLink
|
||||
href="https://x.com/overleaf"
|
||||
icon={<XLogo />}
|
||||
className="x-logo"
|
||||
accessibilityLabel={t('app_on_x', { social: 'X' })}
|
||||
/>
|
||||
<SocialMediaLink
|
||||
href="https://www.facebook.com/overleaf.editor"
|
||||
icon={<FacebookLogo />}
|
||||
className="facebook-logo"
|
||||
accessibilityLabel={t('app_on_x', { social: 'Facebook' })}
|
||||
/>
|
||||
<SocialMediaLink
|
||||
href="https://www.linkedin.com/company/writelatex-limited"
|
||||
icon={<LinkedInLogo />}
|
||||
className="linkedin-logo"
|
||||
accessibilityLabel={t('app_on_x', { social: 'LinkedIn' })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
function FooterBaseLink({ href, children }: FooterLinkProps) {
|
||||
return (
|
||||
<a className="fat-footer-link" href={href}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function SocialMediaLink({
|
||||
href,
|
||||
icon,
|
||||
className,
|
||||
accessibilityLabel,
|
||||
}: SocialMediaLinkProps) {
|
||||
return (
|
||||
<a className={classNames('fat-footer-social', className)} href={href}>
|
||||
{icon}
|
||||
<span className="visually-hidden">{accessibilityLabel}</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export default FatFooterBase
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FatFooterBase from './fat-footer-base'
|
||||
|
||||
type FooterLinkProps = {
|
||||
href: string
|
||||
label: string
|
||||
}
|
||||
|
||||
type FooterSectionProps = {
|
||||
title: string
|
||||
links: FooterLinkProps[]
|
||||
}
|
||||
|
||||
function FatFooter() {
|
||||
const { t } = useTranslation()
|
||||
const hideFatFooter = false
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: t('About'),
|
||||
links: [
|
||||
{ href: '/about', label: t('footer_about_us') },
|
||||
{ href: '/about/values', label: t('our_values') },
|
||||
{ href: '/about/careers', label: t('careers') },
|
||||
{ href: '/for/press', label: t('press_and_awards') },
|
||||
{ href: '/blog', label: t('blog') },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('Learn'),
|
||||
links: [
|
||||
{
|
||||
href: '/learn/latex/Learn_LaTeX_in_30_minutes',
|
||||
label: t('latex_in_thirty_minutes'),
|
||||
},
|
||||
{ href: '/latex/templates', label: t('templates') },
|
||||
{ href: '/events/webinars', label: t('webinars') },
|
||||
{ href: '/learn/latex/Tutorials', label: t('tutorials') },
|
||||
{
|
||||
href: '/learn/latex/Inserting_Images',
|
||||
label: t('how_to_insert_images'),
|
||||
},
|
||||
{ href: '/learn/latex/Tables', label: t('how_to_create_tables') },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('Plans and Pricing'),
|
||||
links: [
|
||||
{
|
||||
href: '/learn/how-to/Overleaf_premium_features',
|
||||
label: t('premium_features'),
|
||||
},
|
||||
{
|
||||
href: '/user/subscription/plans?itm_referrer=footer-for-indv-groups',
|
||||
label: t('for_individuals_and_groups'),
|
||||
},
|
||||
{ href: '/for/enterprises', label: t('for_enterprise') },
|
||||
{ href: '/for/universities', label: t('for_universities') },
|
||||
{
|
||||
href: '/user/subscription/plans?itm_referrer=footer-for-students#student-annual',
|
||||
label: t('for_students'),
|
||||
},
|
||||
{ href: '/for/government', label: t('for_government') },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('Get Involved'),
|
||||
links: [
|
||||
{ href: '/for/community/advisors', label: t('become_an_advisor') },
|
||||
{
|
||||
href: 'https://forms.gle/67PSpN1bLnjGCmPQ9',
|
||||
label: t('let_us_know_what_you_think'),
|
||||
},
|
||||
{ href: '/beta/participate', label: t('join_beta_program') },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('Help'),
|
||||
links: [
|
||||
{ href: '/about/why-latex', label: t('why_latex') },
|
||||
{ href: '/learn', label: t('Documentation') },
|
||||
{ href: '/contact', label: t('footer_contact_us') },
|
||||
{ href: 'https://status.overleaf.com/', label: t('website_status') },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<footer className="fat-footer hidden-print">
|
||||
<div
|
||||
role="navigation"
|
||||
aria-label={t('footer_navigation')}
|
||||
className="fat-footer-container"
|
||||
>
|
||||
<div className={`fat-footer-sections ${hideFatFooter ? 'hidden' : ''}`}>
|
||||
<div className="footer-section" id="footer-brand">
|
||||
<a href="/" aria-label={t('overleaf')} className="footer-brand">
|
||||
<span className="visually-hidden">{t('overleaf')}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{sections.map(section => (
|
||||
<div className="footer-section" key={section.title}>
|
||||
<FooterSection title={section.title} links={section.links} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<FatFooterBase />
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
function FooterSection({ title, links }: FooterSectionProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="footer-section-heading">{t(title)}</h2>
|
||||
<ul className="list-unstyled">
|
||||
{links.map(link => (
|
||||
<li key={link.href}>
|
||||
<a href={link.href}>{t(link.label)}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FatFooter
|
||||
@@ -0,0 +1,9 @@
|
||||
import { FooterMetadata } from '@/features/ui/components/types/footer-metadata'
|
||||
import ThinFooter from '@/features/ui/components/bootstrap-5/footer/thin-footer'
|
||||
import FatFooter from '@/features/ui/components/bootstrap-5/footer/fat-footer'
|
||||
|
||||
function Footer(props: FooterMetadata) {
|
||||
return props.showThinFooter ? <ThinFooter {...props} /> : <FatFooter />
|
||||
}
|
||||
|
||||
export default Footer
|
||||
@@ -0,0 +1,92 @@
|
||||
import type {
|
||||
FooterItem,
|
||||
FooterMetadata,
|
||||
} from '@/features/ui/components/types/footer-metadata'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import LanguagePicker from '@/features/ui/components/bootstrap-5/language-picker'
|
||||
import React from 'react'
|
||||
|
||||
function FooterItemLi({
|
||||
text,
|
||||
translatedText,
|
||||
url: href,
|
||||
class: className,
|
||||
label,
|
||||
}: FooterItem) {
|
||||
const textToDisplay = translatedText || text
|
||||
|
||||
if (!href) {
|
||||
return <li>{textToDisplay}</li>
|
||||
}
|
||||
|
||||
const linkProps = {
|
||||
href,
|
||||
className,
|
||||
'aria-label': label,
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
<a {...linkProps} dangerouslySetInnerHTML={{ __html: textToDisplay }} />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function Separator() {
|
||||
return (
|
||||
<li role="separator" className="text-muted">
|
||||
<strong>|</strong>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function ThinFooter({
|
||||
showPoweredBy,
|
||||
subdomainLang,
|
||||
leftItems,
|
||||
rightItems,
|
||||
}: FooterMetadata) {
|
||||
const showLanguagePicker = Boolean(
|
||||
subdomainLang && Object.keys(subdomainLang).length > 1
|
||||
)
|
||||
|
||||
const hasCustomLeftNav = Boolean(leftItems && leftItems.length > 0)
|
||||
|
||||
return (
|
||||
<footer className="site-footer">
|
||||
<div className="site-footer-content d-print-none">
|
||||
<OLRow>
|
||||
<ul className="site-footer-items col-lg-9">
|
||||
{showPoweredBy ? (
|
||||
<>
|
||||
<li>
|
||||
{/* year of Server Pro release, static */}© 2025{' '}
|
||||
<a href="https://www.overleaf.com/for/enterprises">
|
||||
Powered by Overleaf
|
||||
</a>
|
||||
</li>
|
||||
{showLanguagePicker || hasCustomLeftNav ? <Separator /> : null}
|
||||
</>
|
||||
) : null}
|
||||
{showLanguagePicker ? (
|
||||
<>
|
||||
<li>
|
||||
<LanguagePicker showHeader />
|
||||
</li>
|
||||
{hasCustomLeftNav ? <Separator /> : null}
|
||||
</>
|
||||
) : null}
|
||||
{leftItems?.map(item => <FooterItemLi key={item.text} {...item} />)}
|
||||
</ul>
|
||||
<ul className="site-footer-items col-lg-3 text-end">
|
||||
{rightItems?.map(item => (
|
||||
<FooterItemLi key={item.text} {...item} />
|
||||
))}
|
||||
</ul>
|
||||
</OLRow>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThinFooter
|
||||
Reference in New Issue
Block a user