polen 0.11.0-next.11 → 0.11.0-next.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/iso/schema/$$.d.ts +1 -0
- package/build/api/iso/schema/$$.d.ts.map +1 -1
- package/build/api/iso/schema/$$.js +1 -0
- package/build/api/iso/schema/$$.js.map +1 -1
- package/build/api/iso/schema/routing.d.ts +34 -0
- package/build/api/iso/schema/routing.d.ts.map +1 -1
- package/build/api/iso/schema/routing.js +58 -0
- package/build/api/iso/schema/routing.js.map +1 -1
- package/build/api/iso/schema/validation.d.ts +32 -0
- package/build/api/iso/schema/validation.d.ts.map +1 -0
- package/build/api/iso/schema/validation.js +101 -0
- package/build/api/iso/schema/validation.js.map +1 -0
- package/build/template/components/ArgumentList.d.ts +3 -4
- package/build/template/components/ArgumentList.d.ts.map +1 -1
- package/build/template/components/ArgumentList.js.map +1 -1
- package/build/template/components/Changelog.js +2 -2
- package/build/template/components/Changelog.js.map +1 -1
- package/build/template/components/DeprecationReason.d.ts +2 -2
- package/build/template/components/DeprecationReason.d.ts.map +1 -1
- package/build/template/components/DeprecationReason.js.map +1 -1
- package/build/template/components/Description.d.ts +2 -2
- package/build/template/components/Description.d.ts.map +1 -1
- package/build/template/components/Description.js.map +1 -1
- package/build/template/components/Field.d.ts +3 -4
- package/build/template/components/Field.d.ts.map +1 -1
- package/build/template/components/Field.js.map +1 -1
- package/build/template/components/FieldListSection.d.ts +3 -4
- package/build/template/components/FieldListSection.d.ts.map +1 -1
- package/build/template/components/FieldListSection.js.map +1 -1
- package/build/template/components/MissingSchema.d.ts +2 -1
- package/build/template/components/MissingSchema.d.ts.map +1 -1
- package/build/template/components/MissingSchema.js.map +1 -1
- package/build/template/components/ReferenceLink.d.ts +9 -11
- package/build/template/components/ReferenceLink.d.ts.map +1 -1
- package/build/template/components/ReferenceLink.js.map +1 -1
- package/build/template/components/ToastContainer.d.ts +3 -0
- package/build/template/components/ToastContainer.d.ts.map +1 -0
- package/build/template/components/ToastContainer.js +44 -0
- package/build/template/components/ToastContainer.js.map +1 -0
- package/build/template/components/ToastItem.d.ts +7 -0
- package/build/template/components/ToastItem.d.ts.map +1 -0
- package/build/template/components/ToastItem.js +48 -0
- package/build/template/components/ToastItem.js.map +1 -0
- package/build/template/components/TypeAnnotation.d.ts +4 -5
- package/build/template/components/TypeAnnotation.d.ts.map +1 -1
- package/build/template/components/TypeAnnotation.js.map +1 -1
- package/build/template/components/VersionPicker.d.ts +8 -0
- package/build/template/components/VersionPicker.d.ts.map +1 -0
- package/build/template/components/VersionPicker.js +66 -0
- package/build/template/components/VersionPicker.js.map +1 -0
- package/build/template/hooks/useReferencePath.d.ts +9 -0
- package/build/template/hooks/useReferencePath.d.ts.map +1 -0
- package/build/template/hooks/useReferencePath.js +18 -0
- package/build/template/hooks/useReferencePath.js.map +1 -0
- package/build/template/routes/reference.d.ts +24 -8
- package/build/template/routes/reference.d.ts.map +1 -1
- package/build/template/routes/reference.js +47 -53
- package/build/template/routes/reference.js.map +1 -1
- package/build/template/routes/root.d.ts +1 -0
- package/build/template/routes/root.d.ts.map +1 -1
- package/build/template/routes/root.js +16 -5
- package/build/template/routes/root.js.map +1 -1
- package/build/template/stores/$$.d.ts +13 -0
- package/build/template/stores/$$.d.ts.map +1 -0
- package/build/template/stores/$$.js +5 -0
- package/build/template/stores/$$.js.map +1 -0
- package/build/template/stores/$.d.ts +2 -0
- package/build/template/stores/$.d.ts.map +1 -0
- package/build/template/stores/$.js +2 -0
- package/build/template/stores/$.js.map +1 -0
- package/build/template/stores/schema.d.ts +40 -0
- package/build/template/stores/schema.d.ts.map +1 -0
- package/build/template/stores/schema.js +36 -0
- package/build/template/stores/schema.js.map +1 -0
- package/build/template/stores/toast.d.ts +103 -0
- package/build/template/stores/toast.d.ts.map +1 -0
- package/build/template/stores/toast.js +105 -0
- package/build/template/stores/toast.js.map +1 -0
- package/build/template/utils/try-with-toast.d.ts +9 -0
- package/build/template/utils/try-with-toast.d.ts.map +1 -0
- package/build/template/utils/try-with-toast.js +37 -0
- package/build/template/utils/try-with-toast.js.map +1 -0
- package/package.json +2 -1
- package/src/api/iso/schema/$$.ts +1 -0
- package/src/api/iso/schema/routing.ts +89 -0
- package/src/api/iso/schema/validation.ts +136 -0
- package/src/template/components/ArgumentList.tsx +2 -6
- package/src/template/components/Changelog.tsx +2 -2
- package/src/template/components/DeprecationReason.tsx +2 -2
- package/src/template/components/Description.tsx +2 -2
- package/src/template/components/Field.tsx +2 -6
- package/src/template/components/FieldListSection.tsx +2 -6
- package/src/template/components/MissingSchema.tsx +3 -1
- package/src/template/components/ReferenceLink.tsx +9 -11
- package/src/template/components/ToastContainer.tsx +67 -0
- package/src/template/components/ToastItem.tsx +119 -0
- package/src/template/components/TypeAnnotation.tsx +2 -6
- package/src/template/components/VersionPicker.tsx +94 -0
- package/src/template/hooks/useReferencePath.ts +20 -0
- package/src/template/routes/reference.tsx +49 -63
- package/src/template/routes/root.tsx +29 -6
- package/src/template/stores/$$.ts +15 -0
- package/src/template/stores/$.ts +1 -0
- package/src/template/stores/schema.ts +52 -0
- package/src/template/stores/toast.ts +153 -0
- package/src/template/utils/try-with-toast.ts +41 -0
- package/build/template/components/VersionSelector.d.ts +0 -8
- package/build/template/components/VersionSelector.d.ts.map +0 -1
- package/build/template/components/VersionSelector.js +0 -22
- package/build/template/components/VersionSelector.js.map +0 -1
- package/src/template/components/VersionSelector.tsx +0 -42
@@ -1,14 +1,10 @@
|
|
1
|
+
import type { React } from '#dep/react/index'
|
1
2
|
import { Grafaid } from '#lib/grafaid/index'
|
2
3
|
import { Box, Heading } from '@radix-ui/themes'
|
3
4
|
import type { GraphQLNamedType } from 'graphql'
|
4
|
-
import type { FC } from 'react'
|
5
5
|
import { FieldList } from './FieldList.js'
|
6
6
|
|
7
|
-
export
|
8
|
-
data: GraphQLNamedType
|
9
|
-
}
|
10
|
-
|
11
|
-
export const FieldListSection: FC<Props> = ({ data }) => {
|
7
|
+
export const FieldListSection: React.FC<{ data: GraphQLNamedType }> = ({ data }) => {
|
12
8
|
if (!Grafaid.Schema.TypesLike.isFielded(data)) return null
|
13
9
|
|
14
10
|
const fields = Grafaid.Schema.NodesLike.getFields(data)
|
@@ -1,17 +1,8 @@
|
|
1
1
|
import { Api } from '#api/iso'
|
2
|
-
import type {
|
2
|
+
import type { React } from '#dep/react/index'
|
3
3
|
import { useVersionPath } from '../hooks/useVersionPath.js'
|
4
4
|
import { Link } from './Link.js'
|
5
5
|
|
6
|
-
interface Props {
|
7
|
-
/** The GraphQL type name */
|
8
|
-
type: string
|
9
|
-
/** Optional field name for field-specific links */
|
10
|
-
field?: string
|
11
|
-
/** Link content */
|
12
|
-
children: ReactNode
|
13
|
-
}
|
14
|
-
|
15
6
|
/**
|
16
7
|
* Link component for GraphQL schema references that preserves version context
|
17
8
|
*
|
@@ -19,7 +10,14 @@ interface Props {
|
|
19
10
|
* <ReferenceLink type="User">User</ReferenceLink>
|
20
11
|
* <ReferenceLink type="User" field="name">User.name</ReferenceLink>
|
21
12
|
*/
|
22
|
-
export const ReferenceLink
|
13
|
+
export const ReferenceLink: React.FC<{
|
14
|
+
/** The GraphQL type name */
|
15
|
+
type: string
|
16
|
+
/** Optional field name for field-specific links */
|
17
|
+
field?: string
|
18
|
+
/** Link content */
|
19
|
+
children: React.ReactNode
|
20
|
+
}> = ({ type, field, children }) => {
|
23
21
|
const versionPath = useVersionPath()
|
24
22
|
|
25
23
|
const path = Api.Schema.Routing.joinSegmentsAndPaths(
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import type { React } from '#dep/react/index'
|
2
|
+
import { Box, Flex } from '@radix-ui/themes'
|
3
|
+
import { useSnapshot } from 'valtio'
|
4
|
+
import { Stores } from '../stores/$.js'
|
5
|
+
import { ToastItem } from './ToastItem.js'
|
6
|
+
|
7
|
+
export const ToastContainer: React.FC = () => {
|
8
|
+
const snap = useSnapshot(Stores.Toast.store)
|
9
|
+
|
10
|
+
return (
|
11
|
+
<>
|
12
|
+
<style>
|
13
|
+
{`
|
14
|
+
@keyframes slideIn {
|
15
|
+
from {
|
16
|
+
transform: translateX(100%);
|
17
|
+
opacity: 0;
|
18
|
+
}
|
19
|
+
to {
|
20
|
+
transform: translateX(0);
|
21
|
+
opacity: 1;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
@keyframes slideOut {
|
26
|
+
from {
|
27
|
+
transform: translateX(0);
|
28
|
+
opacity: 1;
|
29
|
+
}
|
30
|
+
to {
|
31
|
+
transform: translateX(100%);
|
32
|
+
opacity: 0;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
@keyframes timerCountdown {
|
37
|
+
from {
|
38
|
+
width: 100%;
|
39
|
+
}
|
40
|
+
to {
|
41
|
+
width: 0%;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
`}
|
45
|
+
</style>
|
46
|
+
<Box
|
47
|
+
position='fixed'
|
48
|
+
bottom='0'
|
49
|
+
right='0'
|
50
|
+
p='4'
|
51
|
+
style={{
|
52
|
+
zIndex: 9999,
|
53
|
+
pointerEvents: 'none',
|
54
|
+
}}
|
55
|
+
>
|
56
|
+
<Flex
|
57
|
+
direction='column'
|
58
|
+
gap='2'
|
59
|
+
align='end'
|
60
|
+
style={{ pointerEvents: 'auto' }}
|
61
|
+
>
|
62
|
+
{snap.toasts.map(toast => <ToastItem key={toast.id} toast={toast} />)}
|
63
|
+
</Flex>
|
64
|
+
</Box>
|
65
|
+
</>
|
66
|
+
)
|
67
|
+
}
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import type { React } from '#dep/react/index'
|
2
|
+
import {
|
3
|
+
CheckCircledIcon,
|
4
|
+
Cross2Icon,
|
5
|
+
CrossCircledIcon,
|
6
|
+
ExclamationTriangleIcon,
|
7
|
+
InfoCircledIcon,
|
8
|
+
} from '@radix-ui/react-icons'
|
9
|
+
import { Box, Button, Card, Flex, IconButton, Text } from '@radix-ui/themes'
|
10
|
+
import type { TextProps } from '@radix-ui/themes'
|
11
|
+
import type { ReadonlyDeep } from 'type-fest'
|
12
|
+
import { Stores } from '../stores/$.js'
|
13
|
+
|
14
|
+
const toastVariants = {
|
15
|
+
info: {
|
16
|
+
icon: <InfoCircledIcon />,
|
17
|
+
color: 'blue',
|
18
|
+
},
|
19
|
+
success: {
|
20
|
+
icon: <CheckCircledIcon />,
|
21
|
+
color: 'green',
|
22
|
+
},
|
23
|
+
warning: {
|
24
|
+
icon: <ExclamationTriangleIcon />,
|
25
|
+
color: 'amber',
|
26
|
+
},
|
27
|
+
error: {
|
28
|
+
icon: <CrossCircledIcon />,
|
29
|
+
color: 'red',
|
30
|
+
},
|
31
|
+
} satisfies Record<Stores.Toast.Type, {
|
32
|
+
icon: React.ReactElement
|
33
|
+
color: NonNullable<TextProps['color']>
|
34
|
+
}>
|
35
|
+
|
36
|
+
export const ToastItem: React.FC<{ toast: ReadonlyDeep<Stores.Toast.Toast> }> = ({ toast }) => {
|
37
|
+
const handleClose = () => Stores.toast.remove(toast.id)
|
38
|
+
const duration = toast.duration ?? 5000
|
39
|
+
const showTimer = duration > 0
|
40
|
+
const type: Stores.Toast.Type = toast.type || 'info'
|
41
|
+
|
42
|
+
return (
|
43
|
+
<Card
|
44
|
+
size={'2'}
|
45
|
+
style={{
|
46
|
+
animation: 'slideIn 0.2s ease-out',
|
47
|
+
position: 'relative',
|
48
|
+
}}
|
49
|
+
>
|
50
|
+
<Flex gap='3' align='start' maxWidth={'400px'}>
|
51
|
+
<Text
|
52
|
+
as='div'
|
53
|
+
size='4'
|
54
|
+
color={toastVariants[type].color}
|
55
|
+
>
|
56
|
+
{toastVariants[type].icon}
|
57
|
+
</Text>
|
58
|
+
<Box style={{ flex: 1 }}>
|
59
|
+
<Flex align='baseline' gap='2' wrap='wrap'>
|
60
|
+
<Text weight='medium' size='2'>
|
61
|
+
{toast.message}
|
62
|
+
</Text>
|
63
|
+
{toast.actions.map((action) => (
|
64
|
+
<Button
|
65
|
+
id={action.label}
|
66
|
+
size='1'
|
67
|
+
variant='soft'
|
68
|
+
onClick={() => {
|
69
|
+
action.onClick()
|
70
|
+
handleClose()
|
71
|
+
}}
|
72
|
+
>
|
73
|
+
{action.label}
|
74
|
+
</Button>
|
75
|
+
))}
|
76
|
+
</Flex>
|
77
|
+
|
78
|
+
{toast.description && (
|
79
|
+
<Text size='1' color='gray' mt='1' style={{ display: 'block' }}>
|
80
|
+
{toast.description}
|
81
|
+
</Text>
|
82
|
+
)}
|
83
|
+
</Box>
|
84
|
+
|
85
|
+
<IconButton
|
86
|
+
size='1'
|
87
|
+
variant='ghost'
|
88
|
+
color='gray'
|
89
|
+
onClick={handleClose}
|
90
|
+
style={{ flexShrink: 0 }}
|
91
|
+
>
|
92
|
+
<Cross2Icon />
|
93
|
+
</IconButton>
|
94
|
+
</Flex>
|
95
|
+
|
96
|
+
{showTimer && (
|
97
|
+
<Box
|
98
|
+
style={{
|
99
|
+
position: 'absolute',
|
100
|
+
bottom: 0,
|
101
|
+
left: 0,
|
102
|
+
right: 0,
|
103
|
+
height: '2px',
|
104
|
+
backgroundColor: `var(--${toastVariants[toast.type || 'info'].color}-a3)`,
|
105
|
+
overflow: 'hidden',
|
106
|
+
}}
|
107
|
+
>
|
108
|
+
<Box
|
109
|
+
style={{
|
110
|
+
height: '100%',
|
111
|
+
backgroundColor: `var(--${toastVariants[toast.type || 'info'].color}-a6)`,
|
112
|
+
animation: `timerCountdown ${duration}ms linear forwards`,
|
113
|
+
}}
|
114
|
+
/>
|
115
|
+
</Box>
|
116
|
+
)}
|
117
|
+
</Card>
|
118
|
+
)
|
119
|
+
}
|
@@ -1,17 +1,13 @@
|
|
1
|
+
import type { React } from '#dep/react/index'
|
1
2
|
import { Text } from '@radix-ui/themes'
|
2
3
|
import type { GraphQLType } from 'graphql'
|
3
4
|
import { isInputObjectType, isListType, isNamedType, isNonNullType, isScalarType } from 'graphql'
|
4
|
-
import type { FC } from 'react'
|
5
5
|
import { ReferenceLink } from './ReferenceLink.js'
|
6
6
|
|
7
|
-
export interface Props {
|
8
|
-
type: GraphQLType // Can be either GraphQLInputType or GraphQLOutputType
|
9
|
-
}
|
10
|
-
|
11
7
|
/**
|
12
8
|
* Renders a GraphQL type recursively, with links for named types
|
13
9
|
*/
|
14
|
-
export const TypeAnnotation: FC<
|
10
|
+
export const TypeAnnotation: React.FC<{ type: GraphQLType }> = ({ type }) => {
|
15
11
|
// Handle NonNull type wrapper
|
16
12
|
if (isNonNullType(type)) {
|
17
13
|
return (
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import { Api } from '#api/iso'
|
2
|
+
import type { React } from '#dep/react/index'
|
3
|
+
import { Select } from '@radix-ui/themes'
|
4
|
+
import { useNavigate } from 'react-router'
|
5
|
+
import { useReferencePath } from '../hooks/useReferencePath.js'
|
6
|
+
import { schemaSource } from '../sources/schema-source.js'
|
7
|
+
import { Stores } from '../stores/$.js'
|
8
|
+
import { tryWithToast } from '../utils/try-with-toast.js'
|
9
|
+
|
10
|
+
interface Props {
|
11
|
+
all: string[]
|
12
|
+
current: string
|
13
|
+
}
|
14
|
+
|
15
|
+
export const VersionPicker: React.FC<Props> = ({ all, current }) => {
|
16
|
+
const navigate = useNavigate()
|
17
|
+
const currentPath = useReferencePath()
|
18
|
+
|
19
|
+
// Don't show selector if only one version
|
20
|
+
if (all.length <= 1) {
|
21
|
+
return null
|
22
|
+
}
|
23
|
+
|
24
|
+
const handleVersionChange = async (newVersion: string) => {
|
25
|
+
const error = await tryWithToast(async () => {
|
26
|
+
// Check if current path exists in target version
|
27
|
+
const targetSchema = await schemaSource.get(newVersion)
|
28
|
+
// Find fallback path if needed
|
29
|
+
const fallbackPath = Api.Schema.Validation.findFallbackPath(targetSchema, currentPath)
|
30
|
+
// Get redirect description if path changed
|
31
|
+
const redirectDescription = Api.Schema.Validation.getRedirectDescription(
|
32
|
+
targetSchema,
|
33
|
+
currentPath,
|
34
|
+
fallbackPath,
|
35
|
+
newVersion,
|
36
|
+
)
|
37
|
+
// Create the new path
|
38
|
+
const newPath = Api.Schema.Routing.createReferencePath({
|
39
|
+
version: newVersion,
|
40
|
+
type: fallbackPath.type,
|
41
|
+
field: fallbackPath.field,
|
42
|
+
})
|
43
|
+
// Show toast notification if schema location redirect will occur
|
44
|
+
if (redirectDescription) {
|
45
|
+
Stores.Toast.store.info(redirectDescription, {
|
46
|
+
duration: 160_000,
|
47
|
+
actions: [
|
48
|
+
{
|
49
|
+
label: 'Go back',
|
50
|
+
onClick() {
|
51
|
+
navigate(-1)
|
52
|
+
},
|
53
|
+
},
|
54
|
+
{
|
55
|
+
label: 'View changelog',
|
56
|
+
onClick() {
|
57
|
+
// Navigate to changelog page
|
58
|
+
navigate('/changelog')
|
59
|
+
},
|
60
|
+
},
|
61
|
+
],
|
62
|
+
})
|
63
|
+
}
|
64
|
+
|
65
|
+
navigate(newPath)
|
66
|
+
}, 'Failed to switch version')
|
67
|
+
|
68
|
+
// Fallback logic if error occurred
|
69
|
+
if (error) {
|
70
|
+
// Fallback to simple navigation if schema loading fails
|
71
|
+
const newPath = Api.Schema.Routing.createReferencePath({
|
72
|
+
version: newVersion,
|
73
|
+
type: currentPath.type,
|
74
|
+
field: currentPath.field,
|
75
|
+
})
|
76
|
+
navigate(newPath)
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
return (
|
81
|
+
<Select.Root value={current} onValueChange={handleVersionChange}>
|
82
|
+
<Select.Trigger>
|
83
|
+
{current === Api.Schema.VERSION_LATEST ? `Latest` : current}
|
84
|
+
</Select.Trigger>
|
85
|
+
<Select.Content>
|
86
|
+
{all.map(version => (
|
87
|
+
<Select.Item key={version} value={version}>
|
88
|
+
{version === Api.Schema.VERSION_LATEST ? `Latest` : version}
|
89
|
+
</Select.Item>
|
90
|
+
))}
|
91
|
+
</Select.Content>
|
92
|
+
</Select.Root>
|
93
|
+
)
|
94
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { Api } from '#api/iso'
|
2
|
+
import { useLocation, useParams } from 'react-router'
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Hook that returns parsed reference path parameters
|
6
|
+
*
|
7
|
+
* @throws {Error} If not currently on a reference route
|
8
|
+
* @returns Parsed reference path object
|
9
|
+
*/
|
10
|
+
export const useReferencePath = (): Api.Schema.Validation.PathValidation => {
|
11
|
+
const params = useParams()
|
12
|
+
const location = useLocation()
|
13
|
+
|
14
|
+
Api.Schema.Routing.assertReferenceRoute({
|
15
|
+
pathname: location.pathname,
|
16
|
+
params,
|
17
|
+
})
|
18
|
+
|
19
|
+
return params
|
20
|
+
}
|
@@ -1,15 +1,16 @@
|
|
1
1
|
import type { Content } from '#api/content/$'
|
2
2
|
import { Api } from '#api/iso'
|
3
|
+
import type { React } from '#dep/react/index'
|
3
4
|
import { GrafaidOld } from '#lib/grafaid-old/index'
|
4
|
-
import { Grafaid } from '#lib/grafaid/index'
|
5
5
|
import { route, routeIndex } from '#lib/react-router-aid/react-router-aid'
|
6
6
|
import { createLoader, useLoaderData } from '#lib/react-router-loader/react-router-loader'
|
7
7
|
import { Box } from '@radix-ui/themes'
|
8
|
-
import {
|
8
|
+
import { neverCase } from '@wollybeard/kit/language'
|
9
|
+
import { useParams } from 'react-router'
|
9
10
|
import { Field } from '../components/Field.js'
|
10
11
|
import { MissingSchema } from '../components/MissingSchema.js'
|
11
12
|
import { NamedType } from '../components/NamedType.js'
|
12
|
-
import {
|
13
|
+
import { VersionPicker } from '../components/VersionPicker.js'
|
13
14
|
import { SidebarLayout } from '../layouts/index.js'
|
14
15
|
import { schemaSource } from '../sources/schema-source.js'
|
15
16
|
|
@@ -29,7 +30,9 @@ export const loader = createLoader(async ({ params }) => {
|
|
29
30
|
}
|
30
31
|
})
|
31
32
|
|
32
|
-
|
33
|
+
// Single component that handles all reference route variations
|
34
|
+
const ReferenceView = () => {
|
35
|
+
const params = useParams() as { version?: string; type?: string; field?: string }
|
33
36
|
const data = useLoaderData<typeof loader>()
|
34
37
|
|
35
38
|
if (!data.schema) {
|
@@ -59,91 +62,74 @@ const RouteReferenceComponent = () => {
|
|
59
62
|
// Calculate basePath based on current version
|
60
63
|
const basePath = Api.Schema.Routing.createReferenceBasePath(data.currentVersion)
|
61
64
|
|
65
|
+
// Determine view type and render appropriate content
|
66
|
+
const viewType = Api.Schema.Routing.getReferenceViewType({
|
67
|
+
schema: data.schema,
|
68
|
+
type: params.type,
|
69
|
+
field: params.field,
|
70
|
+
})
|
71
|
+
|
72
|
+
const content: React.ReactNode = (() => {
|
73
|
+
if (viewType === 'index') {
|
74
|
+
return <div>Select a type from the sidebar to view its documentation.</div>
|
75
|
+
} else if (viewType === 'type-missing' || viewType === 'field-missing') {
|
76
|
+
return <MissingSchema />
|
77
|
+
} else if (viewType === 'type') {
|
78
|
+
const type = data.schema.getType(params.type!)!
|
79
|
+
return <NamedType data={type} />
|
80
|
+
} else if (viewType === 'field') {
|
81
|
+
const type = data.schema.getType(params.type!)!
|
82
|
+
const fields = (type as any).getFields()
|
83
|
+
const field = fields[params.field!]
|
84
|
+
return <Field data={field} />
|
85
|
+
} else {
|
86
|
+
neverCase(viewType)
|
87
|
+
}
|
88
|
+
})()
|
89
|
+
|
62
90
|
return (
|
63
91
|
<SidebarLayout sidebar={sidebarItems} basePath={basePath}>
|
64
92
|
<Box mb={`4`}>
|
65
|
-
<
|
66
|
-
|
67
|
-
|
93
|
+
<VersionPicker
|
94
|
+
all={[...data.availableVersions]} // Convert readonly to mutable
|
95
|
+
current={data.currentVersion}
|
68
96
|
/>
|
69
97
|
</Box>
|
70
|
-
|
98
|
+
{content}
|
71
99
|
</SidebarLayout>
|
72
100
|
)
|
73
101
|
}
|
74
102
|
|
75
|
-
//
|
76
|
-
const useReferenceSchema = () => {
|
77
|
-
const data = useLoaderData<typeof loader>('reference')
|
78
|
-
if (!data?.schema) {
|
79
|
-
throw new Error('Schema not found')
|
80
|
-
}
|
81
|
-
return data
|
82
|
-
}
|
83
|
-
|
84
|
-
const useSchemaType = (typeName: string) => {
|
85
|
-
const { schema } = useReferenceSchema()
|
86
|
-
const type = schema.getType(typeName)
|
87
|
-
if (!type) {
|
88
|
-
throw new Error(`Could not find type ${typeName}`)
|
89
|
-
}
|
90
|
-
return type
|
91
|
-
}
|
92
|
-
|
93
|
-
const useSchemaField = (typeName: string, fieldName: string) => {
|
94
|
-
const type = useSchemaType(typeName)
|
95
|
-
if (!Grafaid.Schema.TypesLike.isFielded(type)) {
|
96
|
-
throw new Error(`Type ${typeName} does not have fields`)
|
97
|
-
}
|
98
|
-
|
99
|
-
const fields = type.getFields()
|
100
|
-
const field = fields[fieldName]
|
101
|
-
if (!field) {
|
102
|
-
throw new Error(`Could not find field ${fieldName} on type ${typeName}`)
|
103
|
-
}
|
104
|
-
return field
|
105
|
-
}
|
106
|
-
|
107
|
-
const RouteComponentIndex = () => {
|
108
|
-
return <div>Select a type from the sidebar to view its documentation.</div>
|
109
|
-
}
|
110
|
-
|
111
|
-
const RouteComponentType = () => {
|
112
|
-
const params = useParams() as { type: string }
|
113
|
-
const type = useSchemaType(params.type)
|
114
|
-
return <NamedType data={type} />
|
115
|
-
}
|
116
|
-
|
117
|
-
const RouteComponentTypeField = () => {
|
118
|
-
const params = useParams() as { type: string; field: string }
|
119
|
-
const field = useSchemaField(params.type, params.field)
|
120
|
-
return <Field data={field} />
|
121
|
-
}
|
122
|
-
|
103
|
+
// Define routes that handle type and field params
|
123
104
|
const typeAndFieldRoutes = [
|
124
|
-
routeIndex(
|
105
|
+
routeIndex({
|
106
|
+
Component: ReferenceView,
|
107
|
+
loader,
|
108
|
+
}),
|
125
109
|
route({
|
126
110
|
path: `:type`,
|
127
|
-
Component:
|
111
|
+
Component: ReferenceView,
|
128
112
|
errorElement: <MissingSchema />,
|
113
|
+
loader,
|
129
114
|
children: [
|
130
115
|
route({
|
131
116
|
path: `:field`,
|
132
|
-
Component:
|
117
|
+
Component: ReferenceView,
|
133
118
|
errorElement: <MissingSchema />,
|
119
|
+
loader,
|
134
120
|
}),
|
135
121
|
],
|
136
122
|
}),
|
137
123
|
]
|
138
124
|
|
139
125
|
/**
|
140
|
-
* Reference documentation
|
126
|
+
* Reference documentation routes using proper React Router patterns
|
127
|
+
* - Parent routes have no components (automatically render Outlet)
|
128
|
+
* - Leaf routes have components and loaders that always run fresh
|
129
|
+
* - Single ReferenceView component handles all variations
|
141
130
|
*/
|
142
131
|
export const reference = route({
|
143
|
-
id: 'reference',
|
144
132
|
path: `reference`,
|
145
|
-
loader,
|
146
|
-
Component: RouteReferenceComponent,
|
147
133
|
children: [
|
148
134
|
...typeAndFieldRoutes,
|
149
135
|
route({
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import type { ReactRouter } from '#dep/react-router/index'
|
2
2
|
import { route } from '#lib/react-router-aid/react-router-aid'
|
3
|
+
import { createLoader } from '#lib/react-router-loader/react-router-loader'
|
4
|
+
import type { Stores } from '#template/stores/$'
|
3
5
|
import { Box, Flex, Theme } from '@radix-ui/themes'
|
4
6
|
import { Link as LinkReactRouter } from 'react-router'
|
5
7
|
import { Outlet, ScrollRestoration } from 'react-router'
|
@@ -11,6 +13,7 @@ import { Link as PolenLink } from '../components/Link.js'
|
|
11
13
|
import { Logo } from '../components/Logo.js'
|
12
14
|
import { NotFound } from '../components/NotFound.js'
|
13
15
|
import { ThemeToggle } from '../components/ThemeToggle.js'
|
16
|
+
import { ToastContainer } from '../components/ToastContainer.js'
|
14
17
|
import { ThemeProvider, useTheme } from '../contexts/ThemeContext.js'
|
15
18
|
import { changelog } from './changelog.js'
|
16
19
|
import { index } from './index.js'
|
@@ -46,7 +49,12 @@ const Layout = () => {
|
|
46
49
|
style={{ color: `inherit`, textDecoration: `none` }}
|
47
50
|
>
|
48
51
|
<Box display={{ initial: `block`, md: `block` }}>
|
49
|
-
<Logo
|
52
|
+
<Logo
|
53
|
+
src={logoSrc}
|
54
|
+
title={templateVariables.title}
|
55
|
+
height={30}
|
56
|
+
showTitle={true}
|
57
|
+
/>
|
50
58
|
</Box>
|
51
59
|
</LinkReactRouter>
|
52
60
|
<Flex direction='row' gap='4' style={{ flex: 1 }}>
|
@@ -61,7 +69,7 @@ const Layout = () => {
|
|
61
69
|
)
|
62
70
|
|
63
71
|
return (
|
64
|
-
<Theme asChild appearance={appearance}>
|
72
|
+
<Theme asChild appearance={appearance} radius='none'>
|
65
73
|
<Box
|
66
74
|
width={{ initial: `100%`, sm: `100%`, md: `var(--container-4)` }}
|
67
75
|
maxWidth='100vw'
|
@@ -72,15 +80,13 @@ const Layout = () => {
|
|
72
80
|
>
|
73
81
|
{header}
|
74
82
|
<Outlet />
|
83
|
+
<ToastContainer />
|
75
84
|
</Box>
|
76
85
|
</Theme>
|
77
86
|
)
|
78
87
|
}
|
79
88
|
|
80
|
-
const children: ReactRouter.RouteObject[] = [
|
81
|
-
index,
|
82
|
-
pages,
|
83
|
-
]
|
89
|
+
const children: ReactRouter.RouteObject[] = [index, pages]
|
84
90
|
|
85
91
|
//
|
86
92
|
//
|
@@ -122,8 +128,25 @@ children.push(notFoundRoute)
|
|
122
128
|
//
|
123
129
|
//
|
124
130
|
|
131
|
+
const storeModules = import.meta.glob('../stores/!($.*)*.ts', { eager: true }) as Record<
|
132
|
+
string,
|
133
|
+
Stores.StoreModule
|
134
|
+
>
|
135
|
+
|
125
136
|
export const root = route({
|
126
137
|
path: `/`,
|
127
138
|
Component,
|
139
|
+
loader: createLoader(async () => {
|
140
|
+
// Reset all stores on SSR to prevent cross-request pollution
|
141
|
+
if (import.meta.env.SSR) {
|
142
|
+
for (const module of Object.values(storeModules)) {
|
143
|
+
if (module.store?.reset) {
|
144
|
+
module.store.reset()
|
145
|
+
}
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
return {}
|
150
|
+
}),
|
128
151
|
children,
|
129
152
|
})
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export * as Schema from './schema.js'
|
2
|
+
export { store as schema } from './schema.js'
|
3
|
+
|
4
|
+
export * as Toast from './toast.js'
|
5
|
+
export { store as toast } from './toast.js'
|
6
|
+
|
7
|
+
export interface Store {
|
8
|
+
reset: () => void
|
9
|
+
set: (data?: unknown) => void
|
10
|
+
}
|
11
|
+
|
12
|
+
export interface StoreModule {
|
13
|
+
store: Store
|
14
|
+
initialState: unknown
|
15
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export * as Stores from './$$.js'
|