Skip to content

Commit 76567fc

Browse files
refactor: Clean up old layout logic (#158)
* Update redirect banner, remove unnecessary redirects, stackblitz iframe * Remove unnecessary redirect * Use LoaderFunctionArgs * Remove unused import * Mark HTMLProps as type import * Move useDocsConfig into DocsLayout * Add back old version redirects * Refactor useDocsConfig * Use useCurrentFramework once * Refactor localMenu * Change extension to .ts * Commit suggestion
1 parent ca98c58 commit 76567fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+655
-1023
lines changed

Diff for: app/components/DocsLayout.tsx

+188-18
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { DocSearch } from '@docsearch/react'
22
import * as React from 'react'
33
import { CgClose, CgMenuLeft } from 'react-icons/cg'
4-
import { FaArrowLeft, FaArrowRight, FaTimes } from 'react-icons/fa'
5-
import { NavLink, useMatches } from '@remix-run/react'
6-
import { last } from '~/utils/utils'
4+
import {
5+
FaArrowLeft,
6+
FaArrowRight,
7+
FaDiscord,
8+
FaGithub,
9+
FaTimes,
10+
} from 'react-icons/fa'
11+
import { NavLink, useMatches, useNavigate, useParams } from '@remix-run/react'
712
import { Carbon } from '~/components/Carbon'
813
import { LinkOrA } from '~/components/LinkOrA'
914
import { Search } from '~/components/Search'
@@ -12,27 +17,192 @@ import { useLocalStorage } from '~/utils/useLocalStorage'
1217
import { DocsCalloutQueryGG } from '~/components/DocsCalloutQueryGG'
1318
import { DocsCalloutBytes } from '~/components/DocsCalloutBytes'
1419
import { DocsLogo } from '~/components/DocsLogo'
15-
import type { DocsConfig } from '~/utils/config'
20+
import { generatePath, last } from '~/utils/utils'
21+
import type { AvailableOptions } from '~/components/Select'
22+
import type { ConfigSchema, MenuItem } from '~/utils/config'
1623

17-
export function DocsLayout({
18-
name,
19-
version,
20-
colorFrom,
21-
colorTo,
22-
textColor,
24+
/**
25+
* Use framework in URL path
26+
* Otherwise use framework in localStorage if it exists for this project
27+
* Otherwise fallback to react
28+
*/
29+
function useCurrentFramework(frameworks: AvailableOptions) {
30+
const { framework: paramsFramework } = useParams()
31+
const localStorageFramework = localStorage.getItem('framework')
32+
33+
return (
34+
paramsFramework ||
35+
(localStorageFramework && localStorageFramework in frameworks
36+
? localStorageFramework
37+
: 'react')
38+
)
39+
}
40+
41+
const useMenuConfig = ({
2342
config,
24-
children,
43+
framework,
44+
repo,
2545
}: {
46+
config: ConfigSchema
47+
framework: string
48+
repo: string
49+
}) => {
50+
const frameworkMenuItems =
51+
config.frameworkMenus.find((d) => d.framework === framework)?.menuItems ??
52+
[]
53+
54+
const localMenu: MenuItem = {
55+
label: 'Menu',
56+
children: [
57+
{
58+
label: 'Home',
59+
to: '..',
60+
},
61+
{
62+
label: (
63+
<div className="flex items-center gap-2">
64+
GitHub <FaGithub className="text-lg opacity-20" />
65+
</div>
66+
),
67+
to: `https://github.com/${repo}`,
68+
},
69+
{
70+
label: (
71+
<div className="flex items-center gap-2">
72+
Discord <FaDiscord className="text-lg opacity-20" />
73+
</div>
74+
),
75+
to: 'https://tlinz.com/discord',
76+
},
77+
],
78+
}
79+
80+
return [
81+
localMenu,
82+
// Merge the two menus together based on their group labels
83+
...config.menu.map((d) => {
84+
const match = frameworkMenuItems.find((d2) => d2.label === d.label)
85+
return {
86+
label: d.label,
87+
children: [
88+
...d.children.map((d) => ({ ...d, badge: 'core' })),
89+
...(match?.children ?? []).map((d) => ({ ...d, badge: framework })),
90+
],
91+
}
92+
}),
93+
...frameworkMenuItems.filter(
94+
(d) => !config.menu.find((dd) => dd.label === d.label)
95+
),
96+
].filter(Boolean)
97+
}
98+
99+
const useFrameworkConfig = ({
100+
framework,
101+
frameworks,
102+
}: {
103+
framework: string
104+
frameworks: AvailableOptions
105+
}) => {
106+
const matches = useMatches()
107+
const match = matches[matches.length - 1]
108+
const navigate = useNavigate()
109+
110+
const frameworkConfig = React.useMemo(() => {
111+
return {
112+
label: 'Framework',
113+
selected: frameworks[framework] ? framework : 'react',
114+
available: frameworks,
115+
onSelect: (option: { label: string; value: string }) => {
116+
const url = generatePath(match.id, {
117+
...match.params,
118+
framework: option.value,
119+
})
120+
121+
localStorage.setItem('framework', option.value)
122+
123+
navigate(url)
124+
},
125+
}
126+
}, [frameworks, framework, match, navigate])
127+
128+
return frameworkConfig
129+
}
130+
131+
const useVersionConfig = ({
132+
availableVersions,
133+
}: {
134+
availableVersions: string[]
135+
}) => {
136+
const matches = useMatches()
137+
const match = matches[matches.length - 1]
138+
const params = useParams()
139+
const version = params.version!
140+
const navigate = useNavigate()
141+
142+
const versionConfig = React.useMemo(() => {
143+
const available = availableVersions.reduce(
144+
(acc: AvailableOptions, version) => {
145+
acc[version] = {
146+
label: version,
147+
value: version,
148+
}
149+
return acc
150+
},
151+
{
152+
latest: {
153+
label: 'Latest',
154+
value: 'latest',
155+
},
156+
}
157+
)
158+
159+
return {
160+
label: 'Version',
161+
selected: version,
162+
available,
163+
onSelect: (option: { label: string; value: string }) => {
164+
const url = generatePath(match.id, {
165+
...match.params,
166+
version: option.value,
167+
})
168+
navigate(url)
169+
},
170+
}
171+
}, [version, match, navigate, availableVersions])
172+
173+
return versionConfig
174+
}
175+
176+
type DocsLayoutProps = {
26177
name: string
27178
version: string
28179
colorFrom: string
29180
colorTo: string
30181
textColor: string
31-
config: DocsConfig
182+
config: ConfigSchema
183+
frameworks: AvailableOptions
184+
availableVersions: string[]
185+
repo: string
32186
children: React.ReactNode
33-
}) {
34-
const frameworkConfig = config.frameworkConfig
35-
const versionConfig = config.versionConfig
187+
}
188+
189+
export function DocsLayout({
190+
name,
191+
version,
192+
colorFrom,
193+
colorTo,
194+
textColor,
195+
config,
196+
frameworks,
197+
availableVersions,
198+
repo,
199+
children,
200+
}: DocsLayoutProps) {
201+
const framework = useCurrentFramework(frameworks)
202+
const frameworkConfig = useFrameworkConfig({ framework, frameworks })
203+
const versionConfig = useVersionConfig({ availableVersions })
204+
const menuConfig = useMenuConfig({ config, framework, repo })
205+
36206
const matches = useMatches()
37207
const lastMatch = last(matches)
38208

@@ -41,8 +211,8 @@ export function DocsLayout({
41211
const detailsRef = React.useRef<HTMLElement>(null!)
42212

43213
const flatMenu = React.useMemo(
44-
() => config.menu.flatMap((d) => d.children),
45-
[config.menu]
214+
() => menuConfig.flatMap((d) => d.children),
215+
[menuConfig]
46216
)
47217

48218
const docsMatch = matches.find((d) => d.pathname.includes('/docs'))
@@ -58,7 +228,7 @@ export function DocsLayout({
58228

59229
const [showBytes, setShowBytes] = useLocalStorage('showBytes', true)
60230

61-
const menuItems = config.menu.map((group, i) => {
231+
const menuItems = menuConfig.map((group, i) => {
62232
return (
63233
<div key={i}>
64234
<div className="text-[.9em] uppercase font-black">{group.label}</div>

Diff for: app/components/MarkdownLink.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { HTMLProps } from 'react'
21
import { Link } from '@remix-run/react'
2+
import type { HTMLProps } from 'react'
33

44
export function MarkdownLink(props: HTMLProps<HTMLAnchorElement>) {
55
if ((props as { href: string }).href?.startsWith('http')) {

Diff for: app/components/RedirectVersionBanner.tsx

+29-38
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { Link, useLocation } from '@remix-run/react'
1+
import { Link } from '@remix-run/react'
22
import { useLocalStorage } from '~/utils/useLocalStorage'
33
import { useClientOnlyRender } from '~/utils/useClientOnlyRender'
44

55
export function RedirectVersionBanner(props: {
6-
currentVersion: string
6+
version: string
77
latestVersion: string
8+
redirectUrl: string
89
}) {
9-
const location = useLocation()
10+
const { version, latestVersion, redirectUrl } = props
1011

1112
// After user clicks hide, do not show modal for a month, and then remind users that there is a new version!
1213
const [showModal, setShowModal] = useLocalStorage(
@@ -15,44 +16,34 @@ export function RedirectVersionBanner(props: {
1516
1000 * 60 * 24 * 30
1617
)
1718

18-
const isLowerVersion =
19-
Number(props.currentVersion) < Number(props.latestVersion[1])
20-
const redirectTarget = location.pathname.replace(
21-
`v${props.currentVersion}`,
22-
'latest'
23-
)
24-
2519
if (!useClientOnlyRender()) {
2620
return null
2721
}
2822

29-
return (
30-
<>
31-
{isLowerVersion && showModal ? (
32-
<div className="p-4 bg-blue-500 text-white flex items-center justify-center gap-4">
33-
<div>
34-
You are currently reading <strong>v{props.currentVersion}</strong>{' '}
35-
docs. Redirect to{' '}
36-
<a href={redirectTarget} className="font-bold underline">
37-
latest
38-
</a>{' '}
39-
version?
40-
</div>
41-
<Link
42-
to={redirectTarget}
43-
replace
44-
className="bg-white text-black py-1 px-2 rounded-md uppercase font-black text-xs"
45-
>
46-
Latest
47-
</Link>
48-
<button
49-
onClick={() => setShowModal(false)}
50-
className="bg-white text-black py-1 px-2 rounded-md uppercase font-black text-xs"
51-
>
52-
Hide
53-
</button>
23+
if (![latestVersion, 'latest'].includes(version) && showModal) {
24+
return (
25+
<div className="p-4 bg-blue-500 text-white flex items-center justify-center gap-4">
26+
<div>
27+
You are currently reading <strong>{version}</strong> docs. Redirect to{' '}
28+
<a href={redirectUrl} className="font-bold underline">
29+
latest
30+
</a>{' '}
31+
version?
5432
</div>
55-
) : null}
56-
</>
57-
)
33+
<Link
34+
to={redirectUrl}
35+
replace
36+
className="bg-white text-black py-1 px-2 rounded-md uppercase font-black text-xs"
37+
>
38+
Latest
39+
</Link>
40+
<button
41+
onClick={() => setShowModal(false)}
42+
className="bg-white text-black py-1 px-2 rounded-md uppercase font-black text-xs"
43+
>
44+
Hide
45+
</button>
46+
</div>
47+
)
48+
}
5849
}

Diff for: app/projects/form.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import reactLogo from '~/images/react-logo.svg'
2+
import solidLogo from '~/images/solid-logo.svg'
3+
import vueLogo from '~/images/vue-logo.svg'
4+
5+
export const repo = 'tanstack/form'
6+
7+
export const latestBranch = 'main'
8+
export const latestVersion = 'v0'
9+
export const availableVersions = ['v0']
10+
11+
export const colorFrom = 'from-yellow-500'
12+
export const colorTo = 'to-yellow-600'
13+
export const textColor = 'text-yellow-600'
14+
15+
export const frameworks = {
16+
react: { label: 'React', logo: reactLogo, value: 'react' },
17+
solid: { label: 'Solid', logo: solidLogo, value: 'solid' },
18+
vue: { label: 'Vue', logo: vueLogo, value: 'vue' },
19+
} as const
20+
21+
export type Framework = keyof typeof frameworks
22+
23+
export function getBranch(argVersion?: string) {
24+
const version = argVersion || latestVersion
25+
26+
return ['latest', latestVersion].includes(version) ? latestBranch : version
27+
}

0 commit comments

Comments
 (0)