1 import React from 'react'
2 import {ActionList, ActionMenu} from '@primer/react'
3 import * as getNav from '../util/get-nav'
4 import usePage from '../hooks/use-page'
5 import {LinkNoUnderline} from './link'
6 import useLocationChange from '../hooks/use-location-change'
7 import styled from 'styled-components'
8
9 import * as styles from './variant-select.module.css'
10
11 const StyledOverlay = styled(ActionMenu.Overlay)`
12 background-color: var(--bgColor-default, #ffffff) !important;
13 border-color: var(--borderColor-default, #d0d7de);
14 border-width: 1px;
15 border-style: solid;
16 box-shadow: var(--shadow-resting-medium, 0 3px 6px rgba(140, 149, 159, 0.15));
17 `
18
19 const VariantItem = ({title, shortName, url, active}) => {
20 return (
21 <ActionList.Item state={{scrollUpdate: false}} id={shortName} active={active} tabIndex={null}>
22 <LinkNoUnderline to={url}>{title}</LinkNoUnderline>
23 </ActionList.Item>
24 )
25 }
26
27 const useVariantFocus = () => {
28 const locationChange = useLocationChange()
29 const anchorRef = React.useRef(null)
30
31 React.useEffect(() => {
32 if (locationChange.change && getNav.didVariantChange(locationChange.previous, locationChange.current)) {
33 anchorRef.current.focus()
34 }
35 }, [locationChange])
36
37 return anchorRef
38 }
39
40 const VariantMenu = ({title, latest, current, prerelease, legacy}) => {
41 const [open, setOpen] = React.useState(false)
42 const anchorRef = useVariantFocus()
43 const labelId = 'label-versions-list-item'
44 const locationChange = useLocationChange()
45
46 React.useEffect(() => {
47 if (locationChange.change && getNav.didVariantChange(locationChange.previous, locationChange.current)) {
48 setOpen(false)
49 }
50 // eslint-disable-next-line react-hooks/exhaustive-deps -- granular deps are intentional; locationChange.current is a URL string, not a ref
51 }, [locationChange.change, locationChange.current, locationChange.previous])
52
53 return (
54 <>
55 <p id={labelId} className={styles.Box}>
56 Select CLI Version:
57 </p>
58 <ActionMenu anchorRef={anchorRef} open={open} onOpenChange={setOpen}>
59 <ActionMenu.Button aria-describedby={labelId} className={styles.menuButton}>
60 {title}
61 </ActionMenu.Button>
62 <StyledOverlay width="auto" onEscape={() => setOpen(false)}>
63 <ActionList aria-labelledby={labelId}>
64 <ActionList.Group>
65 <ActionList.GroupHeading>Current</ActionList.GroupHeading>
66 <VariantItem {...latest} />
67 {current && <VariantItem {...current} />}
68 {prerelease && <VariantItem {...prerelease} />}
69 </ActionList.Group>
70 <ActionList.Group>
71 <ActionList.GroupHeading>Legacy</ActionList.GroupHeading>
72 {legacy.map(item => (
73 <VariantItem key={item.title} {...item} />
74 ))}
75 </ActionList.Group>
76 </ActionList>
77 </StyledOverlay>
78 </ActionMenu>
79 </>
80 )
81 }
82
83 const useVariants = () => {
84 const {pathname: path} = usePage().location
85
86 return React.useMemo(() => {
87 const variantPages = getNav.getVariantsForPath(path)
88
89 if (!variantPages.length) {
90 return null
91 }
92
93 const result = {latest: null, current: null, prerelease: null, legacy: []}
94
95 for (const {variant, page} of variantPages) {
96 const item = {...variant, url: page.url, active: page.url === path}
97 let typeDesc = ''
98 switch (variant.type) {
99 case 'latest':
100 result.latest = item
101 typeDesc = ' (Latest)'
102 break
103 case 'current':
104 result.current = item
105 typeDesc = ' (Current)'
106 break
107 case 'prerelease':
108 result.prerelease = item
109 typeDesc = ' (Prerelease)'
110 break
111 default:
112 result.legacy.push(item)
113 typeDesc = ' (Legacy)'
114 }
115 if (item.active) {
116 result.title = `${item.title}${typeDesc}`
117 }
118 }
119
120 result.legacy.sort((a, b) => parseInt(b.shortName.slice(1)) - parseInt(a.shortName.slice(1)))
121
122 return result
123 }, [path])
124 }
125
126 const VariantSelect = () => {
127 const variants = useVariants()
128 return variants ? (
129 <div className={styles.Box_1}>
130 <VariantMenu {...variants} />
131 </div>
132 ) : null
133 }
134
135 export default VariantSelect