1
0
mirror of synced 2025-12-22 19:34:15 -05:00
Files
docs/components/article/InArticlePicker.tsx
2022-11-04 17:12:08 +00:00

135 lines
3.8 KiB
TypeScript

import { useEffect, useState } from 'react'
import Cookies from 'js-cookie'
import { UnderlineNav } from '@primer/react'
import { sendEvent, EventType } from 'components/lib/events'
import { useRouter } from 'next/router'
type Option = {
value: string
label: string
}
type Props = {
// Use this if not specified on the query string
defaultValue?: string
// Use this if not specified on the query string or no cookie
fallbackValue: string
cookieKey: string
queryStringKey: string
onValue: (value: string) => void
preferenceName: string
options: Option[]
ariaLabel: string
}
export const InArticlePicker = ({
defaultValue,
fallbackValue,
cookieKey,
queryStringKey,
onValue,
preferenceName,
options,
ariaLabel,
}: Props) => {
const router = useRouter()
const { query, locale } = router
const [currentValue, setCurrentValue] = useState('')
// Run on mount for client-side only features
useEffect(() => {
const raw = query[queryStringKey]
let value = ''
if (raw) {
if (Array.isArray(raw)) value = raw[0]
else value = raw
}
// Only pick it up from the possible query string if its value
// is a valid option.
const possibleValues = options.map((option) => option.value)
if (!value || !possibleValues.includes(value)) {
const cookieValue = Cookies.get(cookieKey)
if (defaultValue) {
value = defaultValue
} else if (cookieValue && possibleValues.includes(cookieValue)) {
value = cookieValue
} else {
value = fallbackValue
}
}
setCurrentValue(value)
}, [query, fallbackValue, defaultValue, options])
const [asPathRoot, asPathQuery = ''] = router.asPath.split('#')[0].split('?')
useEffect(() => {
// This will make the hook run this callback on mount and on change.
// That's important because even though the user hasn't interacted
// and made an overriding choice, we still want to run this callback
// because the page might need to be corrected based on *a* choice
// independent of whether it's a change.
if (currentValue) {
onValue(currentValue)
}
}, [
currentValue,
// This is important because we can't otherwise rely on the firing
// of this effect on initial mount. It also needs to fire when the
// URL (i.e. route) changes.
// Don't use `router.asPath` because that contains the query string
// which we handle in the other useEffect above.
asPathRoot,
])
function onClickChoice(value: string) {
const params = new URLSearchParams(asPathQuery)
params.set(queryStringKey, value)
const newPath = `/${locale}${asPathRoot}?${params}`
router.push(newPath, undefined, { shallow: true, locale })
sendEvent({
type: EventType.preference,
preference_name: preferenceName,
preference_value: value,
})
Cookies.set(cookieKey, value, {
sameSite: 'strict',
secure: document.location.protocol !== 'http:',
expires: 365,
})
}
const sharedContainerProps = {
'data-testid': `${queryStringKey}-picker`,
'aria-label': ariaLabel,
[`data-default-${queryStringKey}`]: defaultValue || '',
className: 'mb-4',
}
const params = new URLSearchParams(asPathQuery)
return (
<UnderlineNav {...sharedContainerProps}>
{options.map((option) => {
params.set(queryStringKey, option.value)
const linkProps = {
[`data-${queryStringKey}`]: option.value,
}
return (
<UnderlineNav.Link
href={`?${params}`}
key={option.value}
selected={option.value === currentValue}
onClick={(event) => {
event.preventDefault()
onClickChoice(option.value)
}}
{...linkProps}
>
{option.label}
</UnderlineNav.Link>
)
})}
</UnderlineNav>
)
}