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