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} tabIndex={null}>
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
57 aria-describedby={labelId}
58 sx={{
59 width: ['100%', null, 'auto'],
60 '&:focus-visible': {
61 outline: '2px solid',
62 outlineColor: '-webkit-focus-ring-color',
63 outlineOffset: '1px',
64 },
65 }}
66 >
67 {title}
68 </ActionMenu.Button>
69 <StyledOverlay width="auto" onEscape={() => setOpen(false)}>
70 <ActionList aria-labelledby={labelId}>
71 <ActionList.Group>
72 <ActionList.GroupHeading>Current</ActionList.GroupHeading>
73 <VariantItem {...latest} />
74 {current && <VariantItem {...current} />}
75 {prerelease && <VariantItem {...prerelease} />}
76 </ActionList.Group>
77 <ActionList.Group>
78 <ActionList.GroupHeading>Legacy</ActionList.GroupHeading>
79 {legacy.map(item => (
80 <VariantItem key={item.title} {...item} />
81 ))}
82 </ActionList.Group>
83 </ActionList>
84 </StyledOverlay>
85 </ActionMenu>
86 </>
87 )
88 }
89
90 const useVariants = () => {
91 const {pathname: path} = usePage().location
92
93 return React.useMemo(() => {
94 const variantPages = getNav.getVariantsForPath(path)
95
96 if (!variantPages.length) {
97 return null
98 }
99
100 const result = {latest: null, current: null, prerelease: null, legacy: []}
101
102 for (const {variant, page} of variantPages) {
103 const item = {...variant, url: page.url, active: page.url === path}
104 let typeDesc = ''
105 switch (variant.type) {
106 case 'latest':
107 result.latest = item
108 typeDesc = ' (Latest)'
109 break
110 case 'current':
111 result.current = item
112 typeDesc = ' (Current)'
113 break
114 case 'prerelease':
115 result.prerelease = item
116 typeDesc = ' (Prerelease)'
117 break
118 default:
119 result.legacy.push(item)
120 typeDesc = ' (Legacy)'
121 }
122 if (item.active) {
123 result.title = `${item.title}${typeDesc}`
124 }
125 }
126
127 result.legacy.sort((a, b) => parseInt(b.shortName.slice(1)) - parseInt(a.shortName.slice(1)))
128
129 return result
130 }, [path])
131 }
132
133 const VariantSelect = () => {
134 const variants = useVariants()
135 return variants ? (
136 <Box sx={{mt: 2, mb: 3}}>
137 <VariantMenu {...variants} />
138 </Box>
139 ) : null
140 }
141
142 export default VariantSelect