1
0
mirror of synced 2025-12-22 11:26:57 -05:00

Persist tab state in query params for linking (#31499)

This commit is contained in:
Evan Bonsignori
2022-10-12 04:43:31 -07:00
committed by GitHub
parent db77694065
commit d5cf8700ab
4 changed files with 95 additions and 45 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import Cookies from 'js-cookie'
import { SubNav, TabNav, UnderlineNav } from '@primer/react'
import { sendEvent, EventType } from 'components/lib/events'
@@ -7,6 +7,7 @@ import { useRouter } from 'next/router'
import { useArticleContext } from 'components/context/ArticleContext'
import { parseUserAgent } from 'components/lib/user-agent'
const platformQueryKey = 'platform'
const platforms = [
{ id: 'mac', label: 'Mac' },
{ id: 'windows', label: 'Windows' },
@@ -49,9 +50,10 @@ type Props = {
variant?: 'subnav' | 'tabnav' | 'underlinenav'
}
export const PlatformPicker = ({ variant = 'subnav' }: Props) => {
const router = useRouter()
const { query, asPath } = router
const { defaultPlatform, detectedPlatforms } = useArticleContext()
const [currentPlatform, setCurrentPlatform] = useState(defaultPlatform || '')
const { asPath } = useRouter()
// Run on mount for client-side only features
useEffect(() => {
@@ -60,7 +62,15 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => {
userAgent = 'mac'
}
const platform = defaultPlatform || Cookies.get('osPreferred') || userAgent || 'linux'
// If it's a valid platform option, set platform from query param
let platform =
query[platformQueryKey] && Array.isArray(query[platformQueryKey])
? query[platformQueryKey][0]
: query[platformQueryKey] || ''
if (!platform || !platforms.some((platform) => platform.id === query.platform)) {
platform = defaultPlatform || Cookies.get('osPreferred') || userAgent || 'linux'
}
setCurrentPlatform(platform)
// always trigger this on initial render. if the default doesn't change the other useEffect won't fire
@@ -75,23 +85,28 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => {
}
}, [currentPlatform, detectedPlatforms.join(',')])
const onClickPlatform = (platform: string) => {
setCurrentPlatform(platform)
const onClickPlatform = useCallback(
(platform: string) => {
// Set platform in query param without altering other query params
const [pathRoot, pathQuery = ''] = asPath.split('?')
const params = new URLSearchParams(pathQuery)
params.set(platformQueryKey, platform)
router.push({ pathname: pathRoot, query: params.toString() }, undefined, { shallow: true })
// imperatively modify the article content
showPlatformSpecificContent(platform)
sendEvent({
type: EventType.preference,
preference_name: 'os',
preference_value: platform,
})
sendEvent({
type: EventType.preference,
preference_name: 'os',
preference_value: platform,
})
Cookies.set('osPreferred', platform, {
sameSite: 'strict',
secure: true,
})
}
Cookies.set('osPreferred', platform, {
sameSite: 'strict',
secure: document.location.protocol !== 'http:',
expires: 365,
})
},
[asPath]
)
// only show platforms that are in the current article
const platformOptions = platforms.filter((platform) => detectedPlatforms.includes(platform.id))
@@ -128,15 +143,20 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => {
}
if (variant === 'underlinenav') {
const [, pathQuery = ''] = asPath.split('?')
const params = new URLSearchParams(pathQuery)
return (
<UnderlineNav {...sharedContainerProps}>
{platformOptions.map((option) => {
params.set(platformQueryKey, option.id)
return (
<UnderlineNav.Link
href={`?${params.toString()}`}
key={option.id}
data-platform={option.id}
selected={option.id === currentPlatform}
onClick={() => {
onClick={(event) => {
event.preventDefault()
onClickPlatform(option.id)
}}
>

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import Cookies from 'js-cookie'
import { UnderlineNav } from '@primer/react'
@@ -47,11 +47,13 @@ function getDefaultTool(defaultTool: string | undefined, detectedTools: Array<st
return detectedTools[0]
}
const toolQueryKey = 'tool'
type Props = {
variant?: 'subnav' | 'tabnav' | 'underlinenav'
}
export const ToolPicker = ({ variant = 'subnav' }: Props) => {
const { asPath } = useRouter()
const router = useRouter()
const { asPath, query } = router
// allTools comes from the ArticleContext which contains the list of tools available
const { defaultTool, detectedTools, allTools } = useArticleContext()
const [currentTool, setCurrentTool] = useState(getDefaultTool(defaultTool, detectedTools))
@@ -73,38 +75,66 @@ export const ToolPicker = ({ variant = 'subnav' }: Props) => {
}
}, [])
// Whenever the currentTool is changed, update the article content
// Whenever the currentTool is changed, update the article content or selected tool from query param
useEffect(() => {
preserveAnchorNodePosition(document, () => {
showToolSpecificContent(currentTool, Object.keys(allTools))
})
// If tool from query is a valid option, use it
const tool =
query[toolQueryKey] && Array.isArray(query[toolQueryKey])
? query[toolQueryKey][0]
: query[toolQueryKey] || ''
if (tool && detectedTools.includes(tool)) {
setCurrentTool(tool)
}
}, [currentTool, asPath])
function onClickTool(tool: string) {
setCurrentTool(tool)
sendEvent({
type: EventType.preference,
preference_name: 'application',
preference_value: tool,
})
Cookies.set('toolPreferred', tool, { sameSite: 'strict', secure: true })
}
const onClickTool = useCallback(
(tool: string) => {
// Set tool in query param without altering other query params
const [pathRoot, pathQuery = ''] = asPath.split('?')
const params = new URLSearchParams(pathQuery)
params.set(toolQueryKey, tool)
router.push({ pathname: pathRoot, query: params.toString() }, undefined, { shallow: true })
sendEvent({
type: EventType.preference,
preference_name: 'application',
preference_value: tool,
})
Cookies.set('toolPreferred', tool, {
sameSite: 'strict',
secure: document.location.protocol !== 'http:',
expires: 365,
})
},
[asPath]
)
if (variant === 'underlinenav') {
const [, pathQuery = ''] = asPath.split('?')
const params = new URLSearchParams(pathQuery)
return (
<UnderlineNav {...sharedContainerProps}>
{detectedTools.map((tool) => (
<UnderlineNav.Link
key={tool}
data-tool={tool}
selected={tool === currentTool}
onClick={() => {
onClickTool(tool)
}}
>
{allTools[tool]}
</UnderlineNav.Link>
))}
{detectedTools.map((tool) => {
params.set(toolQueryKey, tool)
return (
<UnderlineNav.Link
href={`?${params.toString()}`}
key={tool}
data-tool={tool}
selected={tool === currentTool}
onClick={(event) => {
event.preventDefault()
onClickTool(tool)
}}
>
{allTools[tool]}
</UnderlineNav.Link>
)
})}
</UnderlineNav>
)
}

View File

@@ -26,7 +26,7 @@ export function getUserEventsId() {
if (cookieValue) return cookieValue
cookieValue = uuidv4()
Cookies.set(COOKIE_NAME, cookieValue, {
secure: true,
secure: document.location.protocol !== 'http:',
sameSite: 'strict',
expires: 365,
})

View File

@@ -102,7 +102,7 @@ export function RestCodeSamples({ operation, slug }: Props) {
setSelectedLanguage(languageKey)
Cookies.set('codeSampleLanguagePreferred', languageKey, {
sameSite: 'strict',
secure: true,
secure: document.location.protocol !== 'http:',
})
}