Skip to content

Commit 9d73570

Browse files
Ebonsignoriheiskr
andauthored
update CTA and add it to footer (#55865)
Co-authored-by: Kevin Heis <heiskr@users.noreply.github.com>
1 parent eeeabea commit 9d73570

9 files changed

Lines changed: 285 additions & 173 deletions

File tree

data/ui.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ search:
6666
ai_title: There was an error loading Copilot.
6767
description: You can still use this field to search our docs.
6868
cta:
69-
heading: New! Copilot for Docs
70-
description: Ask your question in the search bar and get help in seconds.
69+
heading: Get quick answers!
70+
description: Ask Copilot your question.
71+
dismiss: Dismiss
72+
ask_copilot: Ask Copilot
7173
old_search:
7274
description: Enter a search term to find it in the GitHub Docs.
7375
placeholder: Search GitHub Docs

src/fixtures/fixtures/data/ui.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ search:
6666
ai_title: There was an error loading Copilot.
6767
description: You can still use this field to search our docs.
6868
cta:
69-
heading: New! Copilot for Docs
70-
description: Ask your question in the search bar and get help in seconds.
69+
heading: Get quick answers!
70+
description: Ask Copilot your question.
71+
dismiss: Dismiss
72+
ask_copilot: Ask Copilot
7173
old_search:
7274
description: Enter a search term to find it in the GitHub Docs.
7375
placeholder: Search GitHub Docs

src/frame/components/DefaultLayout.tsx

Lines changed: 91 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Breadcrumbs } from 'src/frame/components/page-header/Breadcrumbs'
1414
import { useLanguages } from 'src/languages/components/LanguagesContext'
1515
import { ClientSideLanguageRedirect } from './ClientSideLanguageRedirect'
1616
import { DomainNameEditProvider } from 'src/links/components/useEditableDomainContext'
17+
import { SearchOverlayContextProvider } from '@/search/components/context/SearchOverlayContext'
1718

1819
const MINIMAL_RENDER = Boolean(JSON.parse(process.env.MINIMAL_RENDER || 'false'))
1920

@@ -76,96 +77,98 @@ export const DefaultLayout = (props: Props) => {
7677

7778
return (
7879
<DomainNameEditProvider>
79-
<Head>
80-
{error === '404' ? (
81-
<title>{t('oops')}</title>
82-
) : (!isHomepageVersion && page.fullTitle) ||
83-
(currentPathWithoutLanguage.includes('enterprise-server') && page.fullTitle) ? (
84-
<title>{page.fullTitle}</title>
85-
) : null}
86-
87-
{/* For Google and Bots */}
88-
<meta name="description" content={metaDescription} />
89-
{page.hidden && <meta name="robots" content="noindex" />}
90-
{Object.values(languages)
91-
.filter((lang) => lang.code !== router.locale)
92-
.map((variant) => {
93-
return (
94-
<link
95-
key={variant.code}
96-
rel="alternate"
97-
hrefLang={variant.hreflang || variant.code}
98-
href={`https://docs.github.com/${variant.code}${
99-
router.asPath === '/' ? '' : router.asPath
100-
}`}
101-
/>
102-
)
103-
})}
104-
105-
{/* For local site search indexing */}
106-
{page.topics.length > 0 && <meta name="keywords" content={page.topics.join(',')} />}
107-
108-
{/* For analytics events */}
109-
{router.locale && <meta name="path-language" content={router.locale} />}
110-
{currentVersion && <meta name="path-version" content={currentVersion} />}
111-
{currentProduct && <meta name="path-product" content={currentProduct.id} />}
112-
{relativePath && (
113-
<meta
114-
name="path-article"
115-
content={relativePath.replace('/index.md', '').replace('.md', '')}
116-
/>
117-
)}
118-
{page.type && <meta name="page-type" content={page.type} />}
119-
{page.documentType && <meta name="page-document-type" content={page.documentType} />}
120-
{status && <meta name="status" content={status.toString()} />}
121-
122-
{/* OpenGraph data */}
123-
{page.fullTitle && (
124-
<>
125-
<meta property="og:site_name" content="GitHub Docs" />
126-
<meta property="og:title" content={page.fullTitle} />
127-
<meta property="og:type" content="article" />
128-
<meta property="og:url" content={fullUrl} />
129-
<meta property="og:image" content={getSocialCardImage()} />
130-
</>
131-
)}
132-
{/* Twitter Meta Tags */}
133-
<meta name="twitter:card" content="summary" />
134-
<meta property="twitter:domain" content={new URL(fullUrl).hostname} />
135-
<meta property="twitter:url" content={fullUrl} />
136-
<meta name="twitter:title" content={page.fullTitle} />
137-
{page.introPlainText && <meta name="twitter:description" content={page.introPlainText} />}
138-
<meta name="twitter:image" content={getSocialCardImage()} />
139-
</Head>
140-
<a
141-
href="#main-content"
142-
className="visually-hidden skip-button color-bg-accent-emphasis color-fg-on-emphasis"
143-
>
144-
Skip to main content
145-
</a>
146-
<Header />
147-
<ClientSideLanguageRedirect />
148-
<div className="d-lg-flex">
149-
{isHomepageVersion ? null : <SidebarNav />}
150-
{/* Need to set an explicit height for sticky elements since we also
151-
set overflow to auto */}
152-
<div className="flex-column flex-1 min-width-0">
153-
<main id="main-content" style={{ scrollMarginTop: '5rem' }}>
154-
<DeprecationBanner />
155-
<RestBanner />
156-
157-
{props.children}
158-
</main>
159-
<footer data-container="footer">
160-
<SupportSection />
161-
<LegalFooter />
162-
<ScrollButton
163-
className="position-fixed bottom-0 mb-4 right-0 mr-4 z-1"
164-
ariaLabel={t('scroll_to_top')}
80+
<SearchOverlayContextProvider>
81+
<Head>
82+
{error === '404' ? (
83+
<title>{t('oops')}</title>
84+
) : (!isHomepageVersion && page.fullTitle) ||
85+
(currentPathWithoutLanguage.includes('enterprise-server') && page.fullTitle) ? (
86+
<title>{page.fullTitle}</title>
87+
) : null}
88+
89+
{/* For Google and Bots */}
90+
<meta name="description" content={metaDescription} />
91+
{page.hidden && <meta name="robots" content="noindex" />}
92+
{Object.values(languages)
93+
.filter((lang) => lang.code !== router.locale)
94+
.map((variant) => {
95+
return (
96+
<link
97+
key={variant.code}
98+
rel="alternate"
99+
hrefLang={variant.hreflang || variant.code}
100+
href={`https://docs.github.com/${variant.code}${
101+
router.asPath === '/' ? '' : router.asPath
102+
}`}
103+
/>
104+
)
105+
})}
106+
107+
{/* For local site search indexing */}
108+
{page.topics.length > 0 && <meta name="keywords" content={page.topics.join(',')} />}
109+
110+
{/* For analytics events */}
111+
{router.locale && <meta name="path-language" content={router.locale} />}
112+
{currentVersion && <meta name="path-version" content={currentVersion} />}
113+
{currentProduct && <meta name="path-product" content={currentProduct.id} />}
114+
{relativePath && (
115+
<meta
116+
name="path-article"
117+
content={relativePath.replace('/index.md', '').replace('.md', '')}
165118
/>
166-
</footer>
119+
)}
120+
{page.type && <meta name="page-type" content={page.type} />}
121+
{page.documentType && <meta name="page-document-type" content={page.documentType} />}
122+
{status && <meta name="status" content={status.toString()} />}
123+
124+
{/* OpenGraph data */}
125+
{page.fullTitle && (
126+
<>
127+
<meta property="og:site_name" content="GitHub Docs" />
128+
<meta property="og:title" content={page.fullTitle} />
129+
<meta property="og:type" content="article" />
130+
<meta property="og:url" content={fullUrl} />
131+
<meta property="og:image" content={getSocialCardImage()} />
132+
</>
133+
)}
134+
{/* Twitter Meta Tags */}
135+
<meta name="twitter:card" content="summary" />
136+
<meta property="twitter:domain" content={new URL(fullUrl).hostname} />
137+
<meta property="twitter:url" content={fullUrl} />
138+
<meta name="twitter:title" content={page.fullTitle} />
139+
{page.introPlainText && <meta name="twitter:description" content={page.introPlainText} />}
140+
<meta name="twitter:image" content={getSocialCardImage()} />
141+
</Head>
142+
<a
143+
href="#main-content"
144+
className="visually-hidden skip-button color-bg-accent-emphasis color-fg-on-emphasis"
145+
>
146+
Skip to main content
147+
</a>
148+
<Header />
149+
<ClientSideLanguageRedirect />
150+
<div className="d-lg-flex">
151+
{isHomepageVersion ? null : <SidebarNav />}
152+
{/* Need to set an explicit height for sticky elements since we also
153+
set overflow to auto */}
154+
<div className="flex-column flex-1 min-width-0">
155+
<main id="main-content" style={{ scrollMarginTop: '5rem' }}>
156+
<DeprecationBanner />
157+
<RestBanner />
158+
159+
{props.children}
160+
</main>
161+
<footer data-container="footer">
162+
<SupportSection />
163+
<LegalFooter />
164+
<ScrollButton
165+
className="position-fixed bottom-0 mb-4 right-0 mr-4 z-1"
166+
ariaLabel={t('scroll_to_top')}
167+
/>
168+
</footer>
169+
</div>
167170
</div>
168-
</div>
171+
</SearchOverlayContextProvider>
169172
</DomainNameEditProvider>
170173
)
171174
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.supportGrid {
2+
display: grid;
3+
gap: var(--base-size-4);
4+
5+
// Mobile is 1 column
6+
grid-template-columns: 1fr;
7+
grid-column-gap: 1rem;
8+
9+
grid-row-gap: 2rem;
10+
}
11+
12+
// Medium is 2 columns
13+
@media (min-width: 768px) {
14+
.supportGrid {
15+
grid-template-columns: repeat(2, 1fr);
16+
grid-row-gap: 1rem;
17+
}
18+
}
19+
20+
// Large is 4 columns
21+
@media (min-width: 1280px) {
22+
.supportGrid {
23+
grid-template-columns: minmax(18rem, 1fr) repeat(3, 1fr);
24+
}
25+
}

src/frame/components/page-footer/SupportSection.tsx

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ import { useMainContext } from 'src/frame/components/context/MainContext'
77
import { useVersion } from 'src/versions/components/useVersion'
88
import { useRouter } from 'next/router'
99
import { useTranslation } from 'src/languages/components/useTranslation'
10+
import { AISearchCTAPopup } from '@/search/components/input/AISearchCTAPopup'
11+
import { useSearchOverlayContext } from '@/search/components/context/SearchOverlayContext'
12+
13+
import styles from './SupportSection.module.scss'
1014

1115
export const SupportSection = () => {
1216
const { currentVersion } = useVersion()
1317
const { relativePath, enterpriseServerReleases } = useMainContext()
1418
const router = useRouter()
1519
const { t } = useTranslation('footer')
20+
const { setIsSearchOpen } = useSearchOverlayContext()
1621

1722
const isDeprecated =
1823
enterpriseServerReleases.isOldestReleaseDeprecated &&
@@ -24,47 +29,25 @@ export const SupportSection = () => {
2429
const showSurvey = !isDeprecated && !isSitePolicyDocs
2530
const showContribution = !isDeprecated && !isEarlyAccess && isEnglish
2631
const showSupport = true
27-
const totalCols = Number(showSurvey) + Number(showContribution) + Number(showSupport)
32+
const showCopilotCTA = !isDeprecated && !isEarlyAccess && isEnglish
2833

2934
return (
3035
<section className="container-xl mt-lg-8 mt-6 px-3 px-md-6 no-print mx-auto">
3136
<h2 className="f3">{t('support_heading')}</h2>
32-
<div className="container-xl mx-auto py-6 py-lg-6 clearfix border-top border-color-secondary">
33-
{showSurvey && (
34-
<div
35-
className={cx(
36-
'float-left pr-4 mb-6 mb-xl-0 col-12',
37-
totalCols > 1 && 'col-lg-6',
38-
totalCols > 2 && 'col-xl-3',
39-
)}
40-
>
41-
<Survey />
42-
</div>
43-
)}
44-
{showContribution && (
45-
<div
46-
className={cx(
47-
'float-left pr-4 mb-6 mb-xl-0 col-12',
48-
totalCols > 1 && 'col-lg-6',
49-
totalCols > 2 && 'col-xl-4',
50-
totalCols > 2 && showSurvey && 'offset-xl-1',
51-
)}
52-
>
53-
<Contribution />
54-
</div>
37+
38+
{/* CSS Grid container */}
39+
<div
40+
className={cx(
41+
'border-top border-color-secondary pt-6',
42+
styles.supportGrid /* ← adds the grid rules */,
5543
)}
56-
{showSupport && (
57-
<div
58-
className={cx(
59-
'float-left pr-4 mb-6 mb-xl-0 col-12',
60-
totalCols > 1 && 'col-lg-6',
61-
totalCols > 2 && 'col-xl-3',
62-
totalCols > 2 && (showSurvey || showContribution) && 'offset-xl-1',
63-
)}
64-
>
65-
<Support />
66-
</div>
44+
>
45+
{showCopilotCTA && (
46+
<AISearchCTAPopup isOpen setIsSearchOpen={setIsSearchOpen} isDismissible={false} />
6747
)}
48+
{showSurvey && <Survey />}
49+
{showContribution && <Contribution />}
50+
{showSupport && <Support />}
6851
</div>
6952
</section>
7053
)

src/frame/components/page-header/Header.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import { HeaderSearchAndWidgets } from './HeaderSearchAndWidgets'
2020
import { useInnerWindowWidth } from './hooks/useInnerWindowWidth'
2121
import { EXPERIMENTS } from '@/events/components/experiments/experiments'
2222
import { useShouldShowExperiment } from '@/events/components/experiments/useShouldShowExperiment'
23-
import { useQueryParam } from '@/frame/components/hooks/useQueryParam'
2423
import { useMultiQueryParams } from '@/search/components/hooks/useMultiQueryParams'
2524
import { SearchOverlayContainer } from '@/search/components/input/SearchOverlayContainer'
2625
import { useCTAPopoverContext } from '@/frame/components/context/CTAContext'
26+
import { useSearchOverlayContext } from '@/search/components/context/SearchOverlayContext'
2727

2828
import styles from './Header.module.scss'
2929

@@ -34,10 +34,6 @@ export const Header = () => {
3434
const { currentVersion } = useVersion()
3535
const { t } = useTranslation(['header'])
3636
const isRestPage = currentProduct && currentProduct.id === 'rest'
37-
const { queryParam: isSearchOpen, setQueryParam: setIsSearchOpen } = useQueryParam(
38-
'search-overlay-open',
39-
true,
40-
)
4137
const { params, updateParams } = useMultiQueryParams()
4238
const [scroll, setScroll] = useState(false)
4339
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
@@ -52,6 +48,7 @@ export const Header = () => {
5248
const returnFocusRef = useRef(null)
5349
const searchButtonRef = useRef<HTMLButtonElement>(null)
5450
const { initializeCTA } = useCTAPopoverContext()
51+
const { isSearchOpen, setIsSearchOpen } = useSearchOverlayContext()
5552

5653
const { showExperiment: showNewSearch, experimentLoading: newSearchLoading } =
5754
useShouldShowExperiment(EXPERIMENTS.ai_search_experiment)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Context to manage the state of the SearchOverlay
2+
import { createContext, useContext, PropsWithChildren } from 'react'
3+
import { useQueryParam } from '@/frame/components/hooks/useQueryParam'
4+
5+
type SearchOverlayState = {
6+
isSearchOpen: boolean
7+
setIsSearchOpen: (open: boolean) => void
8+
}
9+
10+
const SearchOverlayContext = createContext<SearchOverlayState | undefined>(undefined)
11+
12+
export function SearchOverlayContextProvider({ children }: PropsWithChildren) {
13+
const { queryParam: isSearchOpen, setQueryParam: setIsSearchOpen } = useQueryParam(
14+
'search-overlay-open',
15+
true,
16+
)
17+
18+
return (
19+
<SearchOverlayContext.Provider value={{ isSearchOpen, setIsSearchOpen } as SearchOverlayState}>
20+
{children}
21+
</SearchOverlayContext.Provider>
22+
)
23+
}
24+
25+
export const useSearchOverlayContext = () => {
26+
const ctx = useContext(SearchOverlayContext)
27+
if (!ctx)
28+
throw new Error('useSearchOverlayContext must be used inside <SearchOverlayContextProvider>')
29+
return ctx
30+
}

0 commit comments

Comments
 (0)