@clidey/dory 0.1.0

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.
Files changed (104) hide show
  1. package/.dockerignore +4 -0
  2. package/.github/ISSUE_TEMPLATE/anything-else.md +10 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. package/.github/dependabot.yml +11 -0
  6. package/.github/workflows/claude.yml +37 -0
  7. package/CODE_OF_CONDUCT.md +128 -0
  8. package/CONTRIBUTING.md +91 -0
  9. package/LICENSE +201 -0
  10. package/README.md +185 -0
  11. package/SECURITY.md +11 -0
  12. package/assets/dory_logo.png +0 -0
  13. package/bin/dory.js +131 -0
  14. package/docs/README.md +2 -0
  15. package/docs/configuration/app-settings/feature-flags.mdx +157 -0
  16. package/docs/configuration/app-settings/user-settings.mdx +201 -0
  17. package/docs/configuration/installation-setup/env-vars.mdx +198 -0
  18. package/docs/configuration/installation-setup/installation.mdx +127 -0
  19. package/docs/dory.json +162 -0
  20. package/docs/integrations/analytics-monitoring/error-tracking.mdx +199 -0
  21. package/docs/integrations/analytics-monitoring/notifications.mdx +189 -0
  22. package/docs/integrations/analytics-monitoring/user-analytics.mdx +182 -0
  23. package/docs/integrations/shopify-integration/shopify-setup.mdx +226 -0
  24. package/docs/integrations/shopify-integration/shopify-webhooks.mdx +207 -0
  25. package/docs/overview/developer-overview/dev-architecture.mdx +292 -0
  26. package/docs/overview/product-intro/app-overview.mdx +167 -0
  27. package/docs/overview/product-intro/architecture.mdx +203 -0
  28. package/docs/overview/product-intro/core-concepts.mdx +267 -0
  29. package/docs/platform-admin/billing-subscriptions/billing.mdx +176 -0
  30. package/docs/platform-admin/system-maintenance/db-migrations.mdx +184 -0
  31. package/docs/platform-admin/system-maintenance/health-monitoring.mdx +222 -0
  32. package/docs/platform-admin/system-maintenance/queue-management.mdx +234 -0
  33. package/docs/platform-admin/user-team-management/teams.mdx +170 -0
  34. package/docs/platform-admin/user-team-management/users.mdx +185 -0
  35. package/docs/support/legal/dependency-analysis.mdx +132 -0
  36. package/docs/support/legal/privacy-terms.mdx +73 -0
  37. package/docs/support/troubleshooting/common-issues.mdx +159 -0
  38. package/docs/usage/api-usage/api-inventory.mdx +213 -0
  39. package/docs/usage/api-usage/api-products.mdx +249 -0
  40. package/docs/usage/api-usage/api-purchase-orders.mdx +289 -0
  41. package/docs/usage/api-usage/api-reports.mdx +227 -0
  42. package/docs/usage/api-usage/api-suppliers.mdx +171 -0
  43. package/docs/usage/api-usage/api-user-management.mdx +171 -0
  44. package/docs/usage/ui-workflows/bundles.mdx +157 -0
  45. package/docs/usage/ui-workflows/demand-planning.mdx +210 -0
  46. package/docs/usage/ui-workflows/favorites.mdx +167 -0
  47. package/docs/usage/ui-workflows/inventory-management.mdx +179 -0
  48. package/docs/usage/ui-workflows/onboarding.mdx +215 -0
  49. package/docs/usage/ui-workflows/purchase-orders.mdx +200 -0
  50. package/docs/usage/ui-workflows/reports.mdx +257 -0
  51. package/index.html +54 -0
  52. package/k8s/Dockerfile +15 -0
  53. package/k8s/config/nginx-default.conf +22 -0
  54. package/k8s/dev.Dockerfile +16 -0
  55. package/package.json +71 -0
  56. package/src/app.css +3 -0
  57. package/src/app.tsx +13 -0
  58. package/src/components/dark-mode-toggle.tsx +25 -0
  59. package/src/components/dropdown.tsx +49 -0
  60. package/src/components/hooks.tsx +28 -0
  61. package/src/components/loading.tsx +23 -0
  62. package/src/components/mdx-provider.tsx +13 -0
  63. package/src/components/navigation.tsx +53 -0
  64. package/src/components/notification.tsx +97 -0
  65. package/src/components/routes.tsx +89 -0
  66. package/src/components/search-index.ts +43 -0
  67. package/src/components/search.tsx +508 -0
  68. package/src/components/store.ts +144 -0
  69. package/src/index.css +24 -0
  70. package/src/main.tsx +18 -0
  71. package/src/mdx/accordion.tsx +48 -0
  72. package/src/mdx/api-playground.tsx +674 -0
  73. package/src/mdx/api.tsx +354 -0
  74. package/src/mdx/callout.tsx +80 -0
  75. package/src/mdx/card.tsx +48 -0
  76. package/src/mdx/code.tsx +289 -0
  77. package/src/mdx/expandable.tsx +40 -0
  78. package/src/mdx/fence.tsx +73 -0
  79. package/src/mdx/icon.tsx +73 -0
  80. package/src/mdx/latex.tsx +26 -0
  81. package/src/mdx/list.tsx +7 -0
  82. package/src/mdx/mdx.tsx +43 -0
  83. package/src/mdx/mermaid.tsx +102 -0
  84. package/src/mdx/open-api.tsx +325 -0
  85. package/src/mdx/source.tsx +47 -0
  86. package/src/mdx/step.tsx +49 -0
  87. package/src/mdx/table.tsx +15 -0
  88. package/src/mdx/tag.tsx +66 -0
  89. package/src/mdx/text.tsx +98 -0
  90. package/src/plugins/sanitize.ts +15 -0
  91. package/src/theme.scss +124 -0
  92. package/src/ui/header.tsx +88 -0
  93. package/src/ui/layout.tsx +167 -0
  94. package/src/ui/mobile-navigation.tsx +91 -0
  95. package/src/ui/prev-next-link.tsx +74 -0
  96. package/src/ui/table-of-content.tsx +166 -0
  97. package/src/utils/fonts.ts +20 -0
  98. package/src/utils/functions.ts +12 -0
  99. package/src/utils/hooks.ts +46 -0
  100. package/src/vite-env.d.ts +1 -0
  101. package/tsconfig.app.json +32 -0
  102. package/tsconfig.json +7 -0
  103. package/tsconfig.node.json +25 -0
  104. package/vite.config.ts +27 -0
package/src/theme.scss ADDED
@@ -0,0 +1,124 @@
1
+ html {
2
+ background-color: #ffffff !important;
3
+ }
4
+
5
+ h1, h2, h3, h4, h5, h6, p, li {
6
+ color: #000000 !important;
7
+ }
8
+
9
+ .mermaid {
10
+ background-color: #ffffff !important;
11
+ color: #000000 !important;
12
+ font-size: 12px !important;
13
+ }
14
+
15
+ .mermaid rect {
16
+ fill: #f8f8f8 !important;
17
+ stroke: #333333 !important;
18
+ }
19
+
20
+ .mermaid p {
21
+ color: #000000 !important;
22
+ }
23
+
24
+ .mermaid path {
25
+ stroke: #555555 !important;
26
+ }
27
+
28
+ .mermaid p {
29
+ background-color: #f8f8f8 !important;
30
+ color: #000000 !important;
31
+ }
32
+
33
+ .mermaid p, .mermaid text, .mermaid text > tspan {
34
+ color: #000000 !important;
35
+ fill: #000000 !important;
36
+ }
37
+
38
+ .mermaid line {
39
+ stroke: #333333 !important;
40
+ }
41
+
42
+ .mermaid .note {
43
+ fill: #ffffcc !important;
44
+ stroke: #999999 !important;
45
+ color: #000000 !important;
46
+ }
47
+
48
+ .docucod-mdx-code {
49
+ background-color: #f8f8f8 !important;
50
+ }
51
+
52
+ body.dark {
53
+ background-color: #1e1e1e !important;
54
+
55
+ #container {
56
+ background-color: #1e1e1e !important;
57
+ }
58
+
59
+ h1, h2, h3, h4, h5, h6, p, li, th, td {
60
+ color: #ffffff !important;
61
+ }
62
+
63
+ .mermaid {
64
+ background-color: #1e1e1e !important;
65
+ color: #ffffff !important;
66
+ }
67
+
68
+ .mermaid rect {
69
+ fill: #2a2a2a !important;
70
+ stroke: #aaaaaa !important;
71
+ }
72
+
73
+ .mermaid p {
74
+ color: #ffffff !important;
75
+ }
76
+
77
+ .mermaid path {
78
+ stroke: #cccccc !important;
79
+ }
80
+
81
+ .mermaid p {
82
+ background-color: #2a2a2a !important;
83
+ color: #ffffff !important;
84
+ }
85
+
86
+ .mermaid rect,
87
+ .mermaid polygon,
88
+ .mermaid circle,
89
+ .mermaid ellipse {
90
+ fill: #2a2a2a !important;
91
+ stroke: #444444 !important;
92
+ }
93
+
94
+ .mermaid p, .mermaid text, .mermaid text > tspan {
95
+ color: #ffffff !important;
96
+ fill: #ffffff !important;
97
+ }
98
+
99
+ .mermaid line {
100
+ stroke: #444444 !important;
101
+ }
102
+
103
+ .mermaid .note {
104
+ fill: #2a2a2a !important;
105
+ stroke: #444444 !important;
106
+ color: #ffffff !important;
107
+ }
108
+
109
+ .docucod-mdx-code {
110
+ background-color: #2a2a2a !important;
111
+ }
112
+
113
+ .docucod-mdx-code-group {
114
+ background-color: #2a2a2a !important;
115
+ }
116
+
117
+ .docucod-mdx-code-group-header {
118
+ background-color: #2a2a2a !important;
119
+ }
120
+
121
+ .prism-code {
122
+ background-color: transparent !important;
123
+ }
124
+ }
@@ -0,0 +1,88 @@
1
+ import classNames from 'classnames';
2
+ import { useEffect, useMemo, useState } from 'preact/hooks';
3
+ import { Link } from 'wouter-preact';
4
+ import { usePathname } from 'wouter-preact/use-browser-location';
5
+ import docsConfig from '../../docs/dory.json';
6
+ import { DarkModeToggle } from '../components/dark-mode-toggle';
7
+ import { MobileSearch, Search } from '../components/search';
8
+ import { ALL_NAVIGATION } from '../components/store';
9
+ import { MobileNavigation } from './mobile-navigation';
10
+
11
+ function TopLevelNavItem({ href, children, isActive }: { href: string; children: string; isActive?: boolean }) {
12
+ return (
13
+ <li>
14
+ <Link
15
+ to={href}
16
+ className={classNames(
17
+ "text-sm/5 transition",
18
+ isActive
19
+ ? "text-sky-500"
20
+ : "text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white"
21
+ )}
22
+ >
23
+ {children}
24
+ </Link>
25
+ </li>
26
+ );
27
+ }
28
+
29
+ export function Header({ className }: { className?: string }) {
30
+ const [isScrolled, setIsScrolled] = useState(false);
31
+ const pathname = usePathname();
32
+
33
+ const { light, dark } = useMemo(() => {
34
+ return { light: docsConfig.logo.light, dark: docsConfig.logo.dark };
35
+ }, []);
36
+
37
+ useEffect(() => {
38
+ function onScroll() {
39
+ setIsScrolled(window.scrollY > 0);
40
+ }
41
+ onScroll();
42
+ window.addEventListener('scroll', onScroll, { passive: true });
43
+ return () => {
44
+ window.removeEventListener('scroll', onScroll);
45
+ }
46
+ }, []);
47
+
48
+ return (
49
+ <header
50
+ className={classNames(
51
+ 'sticky top-0 z-50 flex flex-none flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-gray-900/5 sm:px-6 lg:px-8 dark:shadow-none',
52
+ className,
53
+ {
54
+ 'dark:bg-gray-900/95 dark:backdrop-blur-sm dark:[@supports(backdrop-filter:blur(0))]:bg-neutral-900/75': isScrolled,
55
+ 'dark:bg-transparent': !isScrolled,
56
+ },
57
+ )}
58
+ >
59
+ <div className="relative flex justify-between gap-6 sm:gap-8 md:grow items-center w-full">
60
+ <div className="flex items-center grow gap-4 lg:gap-6">
61
+ <img src={`/${light}`} alt="logo" className="w-10 h-10 hidden dark:block rounded-full" />
62
+ <img src={`/${dark}`} alt="logo" className="w-10 h-10 block dark:hidden rounded-full" />
63
+ <h1 className="text-base lg:text-2xl font-bold">{docsConfig.name}</h1>
64
+ </div>
65
+ <nav className="hidden md:block">
66
+ <ul role="list" className="flex items-center gap-8">
67
+ {ALL_NAVIGATION.map((tab) => (
68
+ <TopLevelNavItem
69
+ key={tab.title}
70
+ href={tab.groups[0].pages[0].href}
71
+ isActive={pathname.startsWith(`/${tab.groups[0].pages[0].href.split('/')[1]}`)}
72
+ >
73
+ {tab.title}
74
+ </TopLevelNavItem>
75
+ ))}
76
+ </ul>
77
+ </nav>
78
+ <div className="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:dark:bg-white/15" />
79
+ <div className="flex items-center gap-3">
80
+ <MobileSearch />
81
+ <Search />
82
+ <DarkModeToggle />
83
+ <MobileNavigation />
84
+ </div>
85
+ </div>
86
+ </header>
87
+ );
88
+ }
@@ -0,0 +1,167 @@
1
+ import type { ComponentChildren } from 'preact';
2
+ import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
3
+ import { useLocation } from 'wouter-preact';
4
+ import { Navigation } from '../components/navigation';
5
+ import { ALL_NAVIGATION, ALL_OPENAPI, completeFrontMatter, loadMDXFrontMatterForPath, loadAllMDXFrontMatter, ALL_PAGES, pathFromFilename } from '../components/store';
6
+ import { OpenAPI } from '../mdx/open-api';
7
+ import { Header } from './header';
8
+ import { PrevNextLinks } from './prev-next-link';
9
+ import { TableOfContents } from './table-of-content';
10
+ import { Loading } from '../components/loading';
11
+ import Dropdown from '../components/dropdown';
12
+ import { useNotification } from '../components/notification';
13
+
14
+ interface LayoutProps {
15
+ children: ComponentChildren;
16
+ }
17
+
18
+ const prompt = (pathname: string) => `Please read and analyze the content from this documentation page: ${window.location.origin}${pathname}.mdx\n\n` +
19
+ `Then, help me understand and answer any questions I have about it. ` +
20
+ `Please provide clear, detailed explanations and examples where relevant.`;
21
+
22
+ export default function Layout({ children }: LayoutProps) {
23
+ const [loading, setLoading] = useState(true);
24
+ const [pathname] = useLocation();
25
+ const { showNotification } = useNotification();
26
+
27
+ const handleOpenMDX = useCallback(() => {
28
+ window.open(`${window.location.origin}${pathname}.mdx`, '_blank');
29
+ }, [pathname]);
30
+
31
+ const handleCopyMDX = useCallback(() => {
32
+ ALL_PAGES[pathFromFilename(pathname)]().then((mdx) => {
33
+ navigator.clipboard.writeText(mdx.default.toString());
34
+ });
35
+ showNotification('Copied to clipboard');
36
+ }, []);
37
+ const handleOpenChatGPT = useCallback(() => {
38
+ const url = `https://chat.openai.com/?q=${encodeURIComponent(prompt(pathname))}`;
39
+ window.open(url, '_blank');
40
+ }, [pathname]);
41
+ const handleOpenAnthropic = useCallback(() => {
42
+ const url = `https://claude.ai/new?q=${encodeURIComponent(prompt(pathname))}`;
43
+ window.open(url, '_blank');
44
+ }, [pathname]);
45
+
46
+ const dropdownItems = useMemo(() => [
47
+ {
48
+ label: 'Open MDX',
49
+ onClick: handleOpenMDX,
50
+ },
51
+ {
52
+ label: 'Copy MDX',
53
+ onClick: handleCopyMDX
54
+ },
55
+ {
56
+ label: 'Open in ChatGPT',
57
+ onClick: handleOpenChatGPT
58
+ },
59
+ {
60
+ label: 'Open in Anthropic',
61
+ onClick: handleOpenAnthropic
62
+ }
63
+ ], [handleOpenMDX, handleCopyMDX, handleOpenChatGPT, handleOpenAnthropic]);
64
+
65
+ useEffect(() => {
66
+ loadMDXFrontMatterForPath(pathname).then(() => {
67
+ loadAllMDXFrontMatter(pathname).then(() => {
68
+ setLoading(false);
69
+ }).catch(() => {
70
+ setLoading(false);
71
+ });
72
+ }).catch(() => {
73
+ setLoading(false);
74
+ });
75
+ }, [pathname]);
76
+
77
+ const { title: group, group: title } = useMemo(() => {
78
+ const group = ALL_NAVIGATION.flatMap((tab) =>
79
+ tab.groups.find((group) =>
80
+ group.pages.some((page) => page.href === pathname)
81
+ )
82
+ );
83
+ const page = completeFrontMatter.find((page) => page.path === pathname);
84
+
85
+ useEffect(() => {
86
+ if (page?.title) {
87
+ document.title = page.title;
88
+ }
89
+ if (page?.description) {
90
+ const metaDescription = document.querySelector('meta[name="description"]');
91
+ if (metaDescription) {
92
+ metaDescription.setAttribute('content', page.description);
93
+ }
94
+ }
95
+ }, [page]);
96
+
97
+ return { group: group[0]?.title, title: page?.title, };
98
+ }, [pathname, loading]);
99
+
100
+ const tab = useMemo(() => {
101
+ return completeFrontMatter.find((page) => page.path === pathname);
102
+ }, [pathname, loading]);
103
+
104
+ const openAPIJSON = useMemo(() => {
105
+ if (tab == null || tab.openapi == null) {
106
+ return null;
107
+ }
108
+ const firstDirectory = `/${tab.path.split('/')[1]}`;
109
+ return ALL_OPENAPI[`${firstDirectory}/openapi.json`].default;
110
+ }, [tab, loading]);
111
+
112
+ const { method, path } = useMemo(() => {
113
+ if (tab == null || tab.openapi == null) {
114
+ return { method: '', path: '' };
115
+ }
116
+ return { method: tab.openapi.split(' ')[0], path: tab.openapi.split(' ')[1] };
117
+ }, [tab, loading]);
118
+
119
+ if (loading) return <div className="h-[25vh] flex grow"><Loading /></div>;
120
+
121
+ return (
122
+ <div className="flex w-full flex-col">
123
+ <Header />
124
+ <div id="container" className="relative mx-auto flex w-full max-w-8xl flex-auto justify-center sm:px-2 lg:px-8 xl:px-32">
125
+ <div className="hidden lg:relative lg:block lg:flex-none">
126
+ <div className="sticky top-[4.75rem] -ml-0.5 h-[calc(100vh-4.75rem)] w-64 overflow-x-hidden overflow-y-auto py-16 pr-8 pl-0.5 xl:w-72 xl:pr-16">
127
+ <Navigation />
128
+ </div>
129
+ </div>
130
+ <main className="flex px-4 py-16 sm:px-6 lg:px-8 grow">
131
+ <div className="max-w-[calc(100vw-16px)] xl:max-w-[70vw] min-w-0 px-4 lg:pr-0 lg:pl-8 xl:px-16 w-full">
132
+ <article className="h-full flex flex-1 flex-col">
133
+ <div className="flex justify-between items-center">
134
+ <header className="mb-8">
135
+ {title && (
136
+ <h1 className="font-display text-lg tracking-tight">
137
+ {title}
138
+ </h1>
139
+ )}
140
+ {group && (
141
+ <p className="text-4xl tracking-tight">
142
+ {group}
143
+ </p>
144
+ )}
145
+ </header>
146
+ {
147
+ openAPIJSON == null &&
148
+ <Dropdown className='min-w-[120px]' buttonLabel="AI Actions" items={dropdownItems} />
149
+ }
150
+ </div>
151
+ <div className="flex-1 min-h-[calc(100vh-2rem)]">
152
+ {openAPIJSON && tab && "openapi" in tab && method && path ?
153
+ <OpenAPI openAPIJson={JSON.parse(openAPIJSON)} method={method} path={path} />
154
+ : children}
155
+ </div>
156
+ <PrevNextLinks />
157
+ </article>
158
+ </div>
159
+ {
160
+ openAPIJSON == null &&
161
+ <TableOfContents />
162
+ }
163
+ </main>
164
+ </div>
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,91 @@
1
+ 'use client'
2
+
3
+ import {
4
+ Dialog,
5
+ DialogBackdrop,
6
+ DialogPanel,
7
+ TransitionChild,
8
+ } from '@headlessui/react'
9
+ import { motion } from 'motion/react'
10
+ import { createContext, Suspense, useContext, useState } from 'react'
11
+
12
+ import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
13
+ import { Navigation } from '../components/navigation'
14
+
15
+ const IsInsideMobileNavigationContext = createContext(false)
16
+
17
+ function MobileNavigationDialog({
18
+ isOpen,
19
+ close,
20
+ }: {
21
+ isOpen: boolean
22
+ close: () => void
23
+ }) {
24
+ return (
25
+ <Dialog
26
+ transition
27
+ open={isOpen}
28
+ onClose={close}
29
+ className="fixed inset-0 z-50 lg:hidden"
30
+ >
31
+ <DialogBackdrop
32
+ transition
33
+ className="fixed inset-0 top-0 pt-8 bg-zinc-400/20 backdrop-blur-xs data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in dark:bg-black/40"
34
+ />
35
+
36
+ <DialogPanel>
37
+ <TransitionChild>
38
+ <motion.div
39
+ layoutScroll
40
+ className="fixed top-0 bottom-0 left-0 w-full overflow-y-auto bg-white px-4 pt-8 pb-4 shadow-lg ring-1 shadow-zinc-900/10 ring-zinc-900/7.5 duration-500 ease-in-out data-closed:-translate-x-full min-[416px]:max-w-sm sm:px-6 sm:pb-10 dark:bg-zinc-900 dark:ring-zinc-800 flex flex-col justify-between"
41
+ >
42
+ <Navigation />
43
+ <div class="flex flex-col justify-end items-end mb-4 mr-2" onClick={close}>
44
+ <XMarkIcon className="w-8 stroke-zinc-900 dark:stroke-white" />
45
+ </div>
46
+ </motion.div>
47
+ </TransitionChild>
48
+ </DialogPanel>
49
+ </Dialog>
50
+ )
51
+ }
52
+
53
+ export function useIsInsideMobileNavigation() {
54
+ return useContext(IsInsideMobileNavigationContext)
55
+ }
56
+
57
+ export function useMobileNavigation() {
58
+ const [isOpen, setIsOpen] = useState(false)
59
+
60
+ return {
61
+ isOpen,
62
+ open: () => setIsOpen(true),
63
+ close: () => setIsOpen(false),
64
+ toggle: () => setIsOpen(prev => !prev)
65
+ }
66
+ }
67
+
68
+ export function MobileNavigation() {
69
+ const isInsideMobileNavigation = useIsInsideMobileNavigation()
70
+ const { isOpen, toggle, close } = useMobileNavigation()
71
+ const ToggleIcon = isOpen ? XMarkIcon : Bars3Icon;
72
+
73
+ return (
74
+ <IsInsideMobileNavigationContext.Provider value={true}>
75
+ <button
76
+ type="button"
77
+ className="relative flex size-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5 lg:hidden"
78
+ aria-label="Toggle navigation"
79
+ onClick={toggle}
80
+ >
81
+ <span className="absolute size-12 [@media(pointer:fine)]:hidden" />
82
+ <ToggleIcon className="w-8 stroke-zinc-900 dark:stroke-white" />
83
+ </button>
84
+ {!isInsideMobileNavigation && (
85
+ <Suspense fallback={null}>
86
+ <MobileNavigationDialog isOpen={isOpen} close={close} />
87
+ </Suspense>
88
+ )}
89
+ </IsInsideMobileNavigationContext.Provider>
90
+ )
91
+ }
@@ -0,0 +1,74 @@
1
+ import { ArrowRightIcon } from "@heroicons/react/24/outline";
2
+ import classNames from "classnames";
3
+ import { ALL_NAVIGATION } from "../components/store";
4
+ import { usePathname } from "../components/hooks";
5
+
6
+ function PageLink({
7
+ title,
8
+ href,
9
+ dir = 'next',
10
+ ...props
11
+ }: Omit<React.ComponentPropsWithoutRef<'div'>, 'dir' | 'title'> & {
12
+ title: string
13
+ href: string
14
+ dir?: 'previous' | 'next'
15
+ }) {
16
+ return (
17
+ <div {...props}>
18
+ <p className="font-display text-sm font-medium">
19
+ {dir === 'next' ? 'Next' : 'Previous'}
20
+ </p>
21
+ <div className="mt-1">
22
+ <a href={href} className={classNames('flex items-center gap-x-1 text-base font-semibold hover:opacity-80', {
23
+ 'flex-row-reverse': dir === 'previous',
24
+ },
25
+ )}>
26
+ <p className={classNames("flex items-center gap-x-1 text-base font-semibold hover:opacity-80", {
27
+ "flex-row-reverse": dir === "previous"
28
+ })}>
29
+ {title}
30
+ <ArrowRightIcon
31
+ className={classNames(
32
+ 'h-4 w-4 flex-none fill-current',
33
+ {
34
+ 'rotate-180': dir === 'previous',
35
+ },
36
+ )}
37
+ />
38
+ </p>
39
+ </a>
40
+ </div>
41
+ </div>
42
+ )
43
+ }
44
+ export function PrevNextLinks() {
45
+ const pathname = usePathname();
46
+ const allLinks = ALL_NAVIGATION.flatMap(tab =>
47
+ tab.groups.flatMap(group =>
48
+ group.pages.map(page => page.href)
49
+ )
50
+ );
51
+ const linkIndex = allLinks.findIndex(link => link === pathname);
52
+ const previousPage = linkIndex > -1 ? allLinks[linkIndex - 1] : null;
53
+ const nextPage = linkIndex > -1 ? allLinks[linkIndex + 1] : null;
54
+
55
+ const previousPageTitle = previousPage ? ALL_NAVIGATION
56
+ .flatMap(tab => tab.groups)
57
+ .flatMap(group => group.pages)
58
+ .find(page => page.href === previousPage)?.title : null;
59
+ const nextPageTitle = nextPage ? ALL_NAVIGATION
60
+ .flatMap(tab => tab.groups)
61
+ .flatMap(group => group.pages)
62
+ .find(page => page.href === nextPage)?.title : null;
63
+
64
+ if (!nextPage && !previousPage) {
65
+ return null;
66
+ }
67
+
68
+ return (
69
+ <div className="mt-12 pt-6 flex border-t border-black/20 dark:border-white/20">
70
+ {previousPage && <PageLink dir="previous" title={previousPageTitle ?? ''} href={previousPage} />}
71
+ {nextPage && <PageLink className="ml-auto text-right" title={nextPageTitle ?? ''} href={nextPage} />}
72
+ </div>
73
+ )
74
+ }