Persist tab state in query params for linking (#31499)
This commit is contained in:
@@ -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,11 +85,13 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => {
|
||||
}
|
||||
}, [currentPlatform, detectedPlatforms.join(',')])
|
||||
|
||||
const onClickPlatform = (platform: string) => {
|
||||
setCurrentPlatform(platform)
|
||||
|
||||
// imperatively modify the article content
|
||||
showPlatformSpecificContent(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 })
|
||||
|
||||
sendEvent({
|
||||
type: EventType.preference,
|
||||
@@ -89,9 +101,12 @@ export const PlatformPicker = ({ variant = 'subnav' }: Props) => {
|
||||
|
||||
Cookies.set('osPreferred', platform, {
|
||||
sameSite: 'strict',
|
||||
secure: true,
|
||||
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)
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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)
|
||||
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: true })
|
||||
}
|
||||
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) => (
|
||||
{detectedTools.map((tool) => {
|
||||
params.set(toolQueryKey, tool)
|
||||
return (
|
||||
<UnderlineNav.Link
|
||||
href={`?${params.toString()}`}
|
||||
key={tool}
|
||||
data-tool={tool}
|
||||
selected={tool === currentTool}
|
||||
onClick={() => {
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
onClickTool(tool)
|
||||
}}
|
||||
>
|
||||
{allTools[tool]}
|
||||
</UnderlineNav.Link>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</UnderlineNav>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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:',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user