SecondLevelNav.Layout.tsx188 lines · main
1import { ChevronLeft, Code } from 'lucide-react'
2import { useMemo, useState, type PropsWithChildren, type ReactNode } from 'react'
3import {
4 Alert,
5 AlertDescription,
6 AlertTitle,
7 Button,
8 cn,
9 Popover,
10 PopoverContent,
11 PopoverTrigger,
12} from 'ui'
13
14import { navigateToSection } from './Content/Content.utils'
15import { DOCS_RESOURCE_CONTENT } from './ProjectAPIDocs.constants'
16import { DocsButton } from '@/components/ui/DocsButton'
17import { useAppStateSnapshot } from '@/state/app-state'
18
19type DocsResourceContentItem = (typeof DOCS_RESOURCE_CONTENT)[keyof typeof DOCS_RESOURCE_CONTENT]
20
21export type MenuItemFilter = (item: DocsResourceContentItem) => boolean
22export type ResourcePickerRenderProps = {
23 selectedResource?: string
24 onSelect: (value: string) => void
25 closePopover: () => void
26}
27
28type SecondLevelNavLayoutProps = {
29 category: string
30 title: string
31 docsUrl: string
32 menuItemFilter?: MenuItemFilter
33 renderResourceList: (props: ResourcePickerRenderProps) => ReactNode
34}
35
36export const SecondLevelNavLayout = ({
37 category,
38 title,
39 docsUrl,
40 menuItemFilter,
41 renderResourceList,
42}: SecondLevelNavLayoutProps) => {
43 const snap = useAppStateSnapshot()
44 const [, resource] = snap.activeDocsSection
45
46 return (
47 <SecondLevelNavOuterContainer>
48 <SecondLevelNavInnerContainer>
49 <NavTitle title={title} category={category} />
50 <ResourcePicker
51 category={category}
52 resource={resource}
53 renderResourceList={renderResourceList}
54 />
55 <MenuItems category={category} menuItemFilter={menuItemFilter} />
56 </SecondLevelNavInnerContainer>
57
58 <SecondLevelNavInnerContainer className="py-4 border-t">
59 <MoreInformation docsUrl={docsUrl} />
60 </SecondLevelNavInnerContainer>
61 </SecondLevelNavOuterContainer>
62 )
63}
64
65const SecondLevelNavOuterContainer = ({ children }: PropsWithChildren) => {
66 return <div className="py-2">{children}</div>
67}
68
69type SecondLevelLevelNavInnerContainerProps = PropsWithChildren<{
70 className?: string
71}>
72
73const SecondLevelNavInnerContainer = ({
74 children,
75 className,
76}: SecondLevelLevelNavInnerContainerProps) => {
77 return <div className={cn('px-4', className)}>{children}</div>
78}
79
80type ResourcePickerProps = {
81 category: string
82 resource?: string
83 renderResourceList: (props: ResourcePickerRenderProps) => ReactNode
84}
85
86type NavTitleProps = {
87 title: string
88 category: string
89}
90
91const NavTitle = ({ title, category }: NavTitleProps) => {
92 const snap = useAppStateSnapshot()
93 const handleBack = () => {
94 snap.setActiveDocsSection([category])
95 }
96
97 return (
98 <div className="flex items-center space-x-2 mb-2">
99 <Button type="text" icon={<ChevronLeft />} className="px-1" onClick={handleBack} />
100 <p className="text-sm text-foreground-light capitalize">{title}</p>
101 </div>
102 )
103}
104
105const ResourcePicker = ({ category, resource, renderResourceList }: ResourcePickerProps) => {
106 const snap = useAppStateSnapshot()
107
108 const [open, setOpen] = useState(false)
109
110 const handleSelect = (value: string) => {
111 snap.setActiveDocsSection([category, value])
112 setOpen(false)
113 }
114
115 return (
116 <Popover open={open} onOpenChange={setOpen} modal={false}>
117 <PopoverTrigger asChild>
118 <Button
119 type="default"
120 size="small"
121 className="w-full justify-between gap-2"
122 iconRight={<Code className="rotate-90" />}
123 >
124 <span className="truncate">{resource ?? 'Select a resource'}</span>
125 </Button>
126 </PopoverTrigger>
127 <PopoverContent className="p-0 w-64" side="bottom" align="center">
128 {renderResourceList({
129 selectedResource: resource,
130 onSelect: handleSelect,
131 closePopover: () => setOpen(false),
132 })}
133 </PopoverContent>
134 </Popover>
135 )
136}
137
138type MenuItemsProps = {
139 category: string
140 menuItemFilter?: MenuItemFilter
141}
142
143const MenuItems = ({ category, menuItemFilter }: MenuItemsProps) => {
144 const menuItems = useMemo(() => {
145 const items = Object.values(DOCS_RESOURCE_CONTENT).filter(
146 (content) => content.category === category
147 )
148 return menuItemFilter ? items.filter(menuItemFilter) : items
149 }, [category, menuItemFilter])
150
151 return (
152 <div className="py-4 space-y-2">
153 {menuItems.map((item) => (
154 <button
155 key={item.key}
156 className="w-full text-left text-sm text-foreground-light px-4 hover:text-foreground"
157 onClick={() => navigateToSection(item.key)}
158 >
159 {item.title}
160 </button>
161 ))}
162 </div>
163 )
164}
165
166type MoreInformationProps = {
167 docsUrl: string
168}
169
170const MoreInformation = ({ docsUrl }: MoreInformationProps) => {
171 return (
172 <Alert className="p-3">
173 <AlertTitle>
174 <p className="text-xs">Unable to find what you're looking for?</p>
175 </AlertTitle>
176 <AlertDescription className="space-y-1">
177 <p className="text-xs leading-normal!">
178 The API methods shown here are only the commonly used ones to get you started building
179 quickly.
180 </p>
181 <p className="text-xs leading-normal!">
182 Head over to our docs site for the full API documentation.
183 </p>
184 <DocsButton className="mt-2!" href={docsUrl} />
185 </AlertDescription>
186 </Alert>
187 )
188}