@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
@@ -0,0 +1,325 @@
1
+ import { useState } from "preact/hooks"
2
+ import { APIGroup } from "./api"
3
+ import { APIPlayground, PlayButton, type Server } from "./api-playground"
4
+ import { Fence } from "./fence"
5
+ import { Tag } from "./tag"
6
+ import { Col, Row } from "./text"
7
+ import { motion } from "motion/react"
8
+
9
+ export function Properties({ children }: { children: React.ReactNode }) {
10
+ return (
11
+ <div className="my-2">
12
+ <ul
13
+ role="list"
14
+ className="m-0 max-w-[calc(var(--container-lg)-(--spacing(8)))] list-none divide-y divide-zinc-900/5 p-0 dark:divide-white/10"
15
+ >
16
+ {children}
17
+ </ul>
18
+ </div>
19
+ )
20
+ }
21
+
22
+ export function Property({
23
+ name,
24
+ children,
25
+ type,
26
+ required = false,
27
+ }: {
28
+ name: string
29
+ children: React.ReactNode
30
+ type?: string
31
+ required?: boolean
32
+ }) {
33
+ return (
34
+ <div className="m-0 px-0 first:pt-0 last:pb-0">
35
+ <div className="my-4 flex flex-wrap items-center gap-x-3 gap-2">
36
+ <Tag className="px-4 py-1">{name}</Tag>
37
+ <p className="font-mono text-xs text-zinc-400 dark:text-zinc-500">
38
+ {type}
39
+ </p>
40
+ {required && <span className="text-rose-400 ml-1 text-xs">Required</span>}
41
+ <p className="w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0 text-sm">
42
+ {children}
43
+ </p>
44
+ </div>
45
+ </div>
46
+ )
47
+ }
48
+
49
+ interface ParamFieldProps {
50
+ body: string;
51
+ type: string;
52
+ default?: string | number;
53
+ required?: boolean;
54
+ children: React.ReactNode;
55
+ }
56
+
57
+ export function ParamField({ body, type, default: defaultValue, required, children }: ParamFieldProps) {
58
+ return (
59
+ <Property name={body} type={type} required={required}>
60
+ {children}
61
+ {defaultValue != null && (
62
+ <span className="text-gray-500 ml-1">(default: {defaultValue})</span>
63
+ )}
64
+ </Property>
65
+ );
66
+ }
67
+
68
+ interface ResponseFieldProps {
69
+ name: string;
70
+ type: string;
71
+ required?: boolean;
72
+ children: React.ReactNode;
73
+ }
74
+
75
+ export function ResponseField({ name, type, required, children }: ResponseFieldProps) {
76
+ return (
77
+ <Property name={name} type={type} required={required}>
78
+ {children}
79
+ </Property>
80
+ );
81
+ }
82
+
83
+ interface Schema {
84
+ $ref?: string;
85
+ type?: string;
86
+ items?: Schema;
87
+ properties?: Record<string, Schema>;
88
+ required?: boolean;
89
+ description?: string;
90
+ default?: string | number;
91
+ }
92
+
93
+ interface Components {
94
+ schemas?: Record<string, Schema>;
95
+ }
96
+
97
+ interface Parameter {
98
+ name: string;
99
+ schema?: Schema;
100
+ required?: boolean;
101
+ description?: string;
102
+ }
103
+
104
+ interface Content {
105
+ schema?: Schema;
106
+ }
107
+
108
+ interface RequestBody {
109
+ description?: string;
110
+ content?: {
111
+ 'application/json'?: Content;
112
+ };
113
+ }
114
+
115
+ interface Response {
116
+ content?: {
117
+ 'application/json'?: Content;
118
+ };
119
+ }
120
+
121
+ interface PathMethod {
122
+ description?: string;
123
+ parameters?: Parameter[];
124
+ requestBody?: RequestBody;
125
+ responses: Record<string, Response>;
126
+ }
127
+
128
+ export interface OpenAPIJson {
129
+ paths: Record<string, Record<string, PathMethod>>;
130
+ components: Components;
131
+ servers?: Server[];
132
+ }
133
+
134
+ function resolveSchema(schema: Schema | undefined, components: Components): Schema {
135
+ if (!schema) return {};
136
+
137
+ if (schema.$ref) {
138
+ const refPath = schema.$ref.replace(/^#\/components\/schemas\//, '');
139
+ return resolveSchema(components.schemas?.[refPath], components);
140
+ }
141
+
142
+ if (schema.type === 'array' && schema.items) {
143
+ return {
144
+ ...schema,
145
+ items: resolveSchema(schema.items, components)
146
+ };
147
+ }
148
+
149
+ if (schema.properties) {
150
+ const resolvedProps: Record<string, Schema> = {};
151
+ for (const [key, value] of Object.entries(schema.properties)) {
152
+ resolvedProps[key] = resolveSchema(value, components);
153
+ }
154
+ return {
155
+ ...schema,
156
+ properties: resolvedProps
157
+ };
158
+ }
159
+
160
+ return schema;
161
+ }
162
+
163
+ export function OpenAPI({ openAPIJson, method: selectedMethod, path: selectedPath }: { openAPIJson: OpenAPIJson, method: string, path: string }) {
164
+ const { paths, components } = openAPIJson;
165
+ const [showPlayground, setShowPlayground] = useState(false);
166
+
167
+ const handleShowPlayground = () => {
168
+ setShowPlayground(true);
169
+ }
170
+
171
+ return (
172
+ <div className="max-w-none w-full">
173
+ {
174
+ showPlayground &&
175
+ <motion.div
176
+ initial={{ x: "100%" }}
177
+ animate={{ x: 0 }}
178
+ exit={{ x: "100%" }}
179
+ transition={{ type: "spring", damping: 20 }}
180
+ className="fixed inset-y-0 right-0 w-full max-w-3xl bg-white dark:bg-[#1e1e1e] shadow-xl z-50 overflow-y-auto border-l border-zinc-200 dark:border-zinc-800"
181
+ >
182
+ <div className="p-6">
183
+ <div className="flex justify-between items-center mb-6">
184
+ <h2 className="text-xl font-semibold">API Playground</h2>
185
+ <button
186
+ onClick={() => setShowPlayground(false)}
187
+ className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
188
+ >
189
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
190
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
191
+ </svg>
192
+ </button>
193
+ </div>
194
+ <APIPlayground
195
+ method={selectedMethod}
196
+ url={selectedPath}
197
+ servers={openAPIJson.servers}
198
+ />
199
+ </div>
200
+ </motion.div>
201
+ }
202
+
203
+ {Object.entries(paths).map(([path, methods]) => {
204
+ if (path !== selectedPath) {
205
+ return null;
206
+ }
207
+
208
+ return (
209
+ <div key={path} className="my-8">
210
+ {Object.entries(methods).map(([method, details]) => {
211
+ const { description, parameters, requestBody, responses } = details;
212
+ const methodUpper = method.toUpperCase();
213
+
214
+ if (methodUpper !== selectedMethod) {
215
+ return null;
216
+ }
217
+
218
+ return (
219
+ <Row key={`${method}-${path}`} cols={2}>
220
+ <Col>
221
+ <h2 className="flex items-center mt-4">
222
+ <Tag variant="medium" className="mr-2 py-1 px-4 rounded-lg">{methodUpper}</Tag>
223
+ <span className="ml-2 font-mono">{path}</span>
224
+ </h2>
225
+
226
+ {description && <p>{description}</p>}
227
+
228
+ {parameters && parameters.length > 0 && (
229
+ <div className="mt-4">
230
+ <h3>Parameters</h3>
231
+ <Properties>
232
+ {parameters.map((param) => (
233
+ <ParamField
234
+ key={param.name}
235
+ body={param.name}
236
+ type={param.schema?.type ?? 'string'}
237
+ required={param.required}
238
+ default={param.schema?.default}
239
+ >
240
+ {param.description}
241
+ </ParamField>
242
+ ))}
243
+ </Properties>
244
+ </div>
245
+ )}
246
+ {requestBody && (
247
+ <div className="mt-4">
248
+ <h3>Request Body</h3>
249
+ <p>{requestBody.description}</p>
250
+ {requestBody.content?.['application/json']?.schema && (
251
+ (() => {
252
+ const resolvedSchema = resolveSchema(requestBody.content['application/json'].schema, components);
253
+ return (
254
+ <Properties>
255
+ {Object.entries(resolvedSchema.properties || {}).map(([propName, prop]) => (
256
+ <ParamField
257
+ key={propName}
258
+ body={propName}
259
+ type={prop.type ?? 'string'}
260
+ required={prop.required}
261
+ default={prop.default}
262
+ >
263
+ {prop.description}
264
+ </ParamField>
265
+ ))}
266
+ </Properties>
267
+ );
268
+ })()
269
+ )}
270
+ </div>
271
+ )}
272
+ {components?.schemas && (
273
+ <div>
274
+ <h3>Schemas</h3>
275
+ {Object.entries(components.schemas).filter(([, schema]) => schema.properties).map(([name, schema]) => (
276
+ <div key={name} className="mt-4">
277
+ <h4>{name}</h4>
278
+ <Properties>
279
+ {Object.entries(schema.properties || {}).map(([propName, prop]) => (
280
+ <ResponseField
281
+ key={propName}
282
+ name={propName}
283
+ type={prop.type ?? 'string'}
284
+ required={prop.required}
285
+ >
286
+ {prop.description}
287
+ </ResponseField>
288
+ ))}
289
+ </Properties>
290
+ </div>
291
+ ))}
292
+ </div>
293
+ )}
294
+ </Col>
295
+ <Col sticky>
296
+ <button
297
+ onClick={handleShowPlayground}
298
+ className='ml-auto mt-3 flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition bg-sky-600 text-white hover:bg-sky-700'>
299
+ Try
300
+ <PlayButton className="h-4 w-4" />
301
+ </button>
302
+ <APIGroup title="Request" tag={methodUpper} label={path}>
303
+ {Object.entries(responses).map(([code, response]) => (
304
+ <div key={code} title={code}>
305
+ {response.content?.['application/json']?.schema && (() => {
306
+ const resolvedSchema = resolveSchema(response.content['application/json'].schema, components);
307
+ return (
308
+ <Fence language="json">
309
+ {JSON.stringify(resolvedSchema, null, 2)}
310
+ </Fence>
311
+ );
312
+ })()}
313
+ </div>
314
+ ))}
315
+ </APIGroup>
316
+ </Col>
317
+ </Row>
318
+ )
319
+ })}
320
+ </div>
321
+ )
322
+ })}
323
+ </div>
324
+ )
325
+ }
@@ -0,0 +1,47 @@
1
+ type PathRange = {
2
+ path: string;
3
+ range: string;
4
+ };
5
+
6
+ type SourceProps = {
7
+ url: string;
8
+ branch?: string;
9
+ paths: PathRange[];
10
+ };
11
+
12
+ export const Source = ({ url, branch = "main", paths }: SourceProps) => {
13
+ const createSourceUrl = (path: string, range: string) => {
14
+ const [start, end] = range.split('-');
15
+
16
+ if (url.includes('github.com')) {
17
+ return `${url}/blob/${branch}/${path}#L${start}${end ? `-L${end}` : ''}`;
18
+ } else if (url.includes('bitbucket.org')) {
19
+ return `${url}/src/${branch}/${path}#lines-${start}${end ? `:${end}` : ''}`;
20
+ } else if (url.includes('gitlab.com')) {
21
+ return `${url}/-/blob/${branch}/${path}#L${range.replace('-', '-')}`;
22
+ }
23
+
24
+ return url;
25
+ };
26
+
27
+ return (
28
+ <div>
29
+ <p className="text-sm text-slate-900 dark:text-white">Sources:</p>
30
+ <ul className="list-none pl-0 flex flex-wrap gap-2 mt-2">
31
+ {paths.map(({ path, range }) => (
32
+ <li key={path + range}>
33
+ <a
34
+ href={createSourceUrl(path, range)}
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ className="rounded px-3 py-1 font-mono text-sm text-slate-700 dark:text-slate-400 hover:opacity-80 transition-colors border border-slate-200 dark:border-slate-700"
38
+ >
39
+ {path}
40
+ <span className="ml-2 opacity-70">{range}</span>
41
+ </a>
42
+ </li>
43
+ ))}
44
+ </ul>
45
+ </div>
46
+ );
47
+ };
@@ -0,0 +1,49 @@
1
+ import classNames from "classnames"
2
+ import { Children } from "preact/compat"
3
+
4
+ export function Steps({ children }: { children: preact.ComponentChildren }) {
5
+ const steps = Children.toArray(children) as preact.ComponentChildren[];
6
+ return (
7
+ <nav aria-label="Progress" className="py-16">
8
+ <div className="overflow-hidden">
9
+ {steps.map((step, stepIdx) => {
10
+ const stepElement = Children.toArray(step)[0] as preact.JSX.Element;
11
+ return (
12
+ <div key={stepIdx} className={classNames(
13
+ {
14
+ "pb-10": stepIdx !== steps.length - 1,
15
+ "pb-2": stepIdx === steps.length - 1,
16
+ },
17
+ 'relative'
18
+ )}>
19
+ <div aria-hidden="true" className="absolute top-10 left-4 h-full w-[0.5px] bg-gray-300/50 dark:bg-gray-600/50" style={{ top: '40px', height: 'calc(100% - 44px)' }} />
20
+ <div className="group relative flex items-start">
21
+ <span className="flex h-9 items-center">
22
+ <span className="relative z-10 flex size-8 items-center justify-center rounded-full bg-white dark:bg-gray-800 group-hover:border-gray-400">
23
+ <span className="text-sm font-medium text-gray-500 dark:text-white">
24
+ {stepIdx + 1}
25
+ </span>
26
+ </span>
27
+ </span>
28
+ <span className="ml-4 flex min-w-0 flex-col">
29
+ <p className="text-lg font-medium text-inherit">{stepElement.props.title}</p>
30
+ </span>
31
+ </div>
32
+ <div className="ml-12 not-prose">
33
+ {stepElement.props.children}
34
+ </div>
35
+ </div>
36
+ )
37
+ })}
38
+ </div>
39
+ </nav>
40
+ )
41
+ }
42
+
43
+ export function Step({
44
+ children
45
+ }: {
46
+ children: preact.ComponentChildren
47
+ }) {
48
+ return <p className='text-sm'>{children}</p>
49
+ }
@@ -0,0 +1,15 @@
1
+ import type { JSX } from 'preact/jsx-runtime';
2
+
3
+ export const Table = (props: JSX.IntrinsicElements['table']) => (
4
+ <div className="overflow-x-auto rounded-lg my-8">
5
+ <table className="border-collapse w-full border border-gray-300/10 rounded-lg" {...props} />
6
+ </div>
7
+ );
8
+
9
+ export const Th = (props: JSX.IntrinsicElements['th']) => (
10
+ <th className="border border-gray-300/10 p-2 bg-gray-100/25 text-left first:rounded-tl-lg last:rounded-tr-lg" {...props} />
11
+ );
12
+
13
+ export const Td = (props: JSX.IntrinsicElements['td']) => (
14
+ <td className="border border-gray-300/10 p-2 text-left last-row:first:rounded-bl-lg last-row:last:rounded-br-lg" {...props} />
15
+ );
@@ -0,0 +1,66 @@
1
+ import classNames from 'classnames'
2
+
3
+ const variantStyles = {
4
+ small: '',
5
+ medium: 'rounded-lg px-1.5 ring-1 ring-inset',
6
+ }
7
+
8
+ const colorStyles = {
9
+ emerald: {
10
+ small: 'text-emerald-500 dark:text-emerald-400',
11
+ medium:
12
+ 'ring-emerald-300 dark:ring-emerald-400/30 bg-emerald-400/10 text-emerald-500 dark:text-emerald-400',
13
+ },
14
+ sky: {
15
+ small: 'text-sky-500',
16
+ medium:
17
+ 'ring-sky-300 bg-sky-400/10 text-sky-500 dark:ring-sky-400/30 dark:bg-sky-400/10 dark:text-sky-400',
18
+ },
19
+ amber: {
20
+ small: 'text-amber-500',
21
+ medium:
22
+ 'ring-amber-300 bg-amber-400/10 text-amber-500 dark:ring-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400',
23
+ },
24
+ rose: {
25
+ small: 'text-red-500 dark:text-rose-500',
26
+ medium:
27
+ 'ring-rose-200 bg-rose-50 text-red-500 dark:ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-400',
28
+ },
29
+ zinc: {
30
+ small: 'text-zinc-400 dark:text-zinc-500',
31
+ medium:
32
+ 'ring-zinc-200 bg-zinc-50 text-zinc-500 dark:ring-zinc-500/20 dark:bg-zinc-400/10 dark:text-zinc-400',
33
+ },
34
+ }
35
+
36
+ const valueColorMap = {
37
+ GET: 'emerald',
38
+ POST: 'sky',
39
+ PUT: 'amber',
40
+ DELETE: 'rose',
41
+ } as Record<string, keyof typeof colorStyles>
42
+
43
+ export function Tag({
44
+ children,
45
+ variant = 'medium',
46
+ color = valueColorMap[children] ?? 'zinc',
47
+ className,
48
+ }: {
49
+ children: keyof typeof valueColorMap & (string)
50
+ variant?: keyof typeof variantStyles
51
+ color?: keyof typeof colorStyles
52
+ className?: string
53
+ }) {
54
+ return (
55
+ <span
56
+ className={classNames(
57
+ 'font-mono text-[0.625rem]/6 font-semibold',
58
+ variantStyles[variant],
59
+ colorStyles[color][variant],
60
+ className,
61
+ )}
62
+ >
63
+ {children}
64
+ </span>
65
+ )
66
+ }
@@ -0,0 +1,98 @@
1
+ import classNames from "classnames"
2
+ import { useState } from "preact/hooks"
3
+
4
+
5
+ export const h1 = function H1(props: preact.JSX.HTMLAttributes<HTMLHeadingElement>) {
6
+ return <h1 className="text-4xl font-bold mb-4 mt-12" {...props} />
7
+ }
8
+
9
+ export const h2 = function H2(props: preact.JSX.HTMLAttributes<HTMLHeadingElement>) {
10
+ return <h2 className="text-3xl font-bold mb-3 mt-10" {...props} />
11
+ }
12
+
13
+ export const h3 = function H3(props: preact.JSX.HTMLAttributes<HTMLHeadingElement>) {
14
+ return <h3 className="text-2xl font-bold mb-2 mt-8" {...props} />
15
+ }
16
+
17
+ export const h4 = function H4(props: preact.JSX.HTMLAttributes<HTMLHeadingElement>) {
18
+ return <h4 className="text-xl font-bold mb-2 mt-6" {...props} />
19
+ }
20
+
21
+ export const h5 = function H5(props: preact.JSX.HTMLAttributes<HTMLHeadingElement>) {
22
+ return <h5 className="text-lg font-bold mb-2" {...props} />
23
+ }
24
+
25
+ export const h6 = function H6(props: preact.JSX.HTMLAttributes<HTMLHeadingElement>) {
26
+ return <h6 className="text-base font-bold mb-2" {...props} />
27
+ }
28
+
29
+ export const p = function P(props: preact.JSX.HTMLAttributes<HTMLParagraphElement>) {
30
+ return <p className="mb-2 inline-block" {...props} />
31
+ }
32
+
33
+ export function Row({ children, cols = 1 }: { children: preact.ComponentChildren, cols?: number }) {
34
+ return (
35
+ <div className={`grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none`} style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}>
36
+ {children}
37
+ </div>
38
+ )
39
+ }
40
+
41
+ export function Col({
42
+ children,
43
+ sticky = false,
44
+ rows = 1,
45
+ gap = 4
46
+ }: {
47
+ children: preact.ComponentChildren
48
+ sticky?: boolean
49
+ rows?: number
50
+ gap?: number
51
+ }) {
52
+ return (
53
+ <div
54
+ className={classNames("[&>:first-child]:mt-0 [&>:last-child]:mb-0", {
55
+ 'xl:sticky xl:top-24': sticky,
56
+ })}
57
+ style={{
58
+ display: 'grid',
59
+ gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`,
60
+ gap: `${gap * 0.25}rem`
61
+ }}
62
+ >
63
+ {children}
64
+ </div>
65
+ )
66
+ }
67
+
68
+
69
+ export function Anchor({ children, href }: { children: preact.ComponentChildren, href: string }) {
70
+ return <a href={href} className="text-blue-500 hover:text-blue-600">{children}</a>
71
+ }
72
+
73
+ export function Image({ src, alt }: { src: string, alt: string }) {
74
+ const [isExpanded, setIsExpanded] = useState(false);
75
+
76
+ return (
77
+ <>
78
+ <img
79
+ src={src}
80
+ alt={alt}
81
+ className="w-full h-auto cursor-pointer"
82
+ onClick={() => setIsExpanded(true)}
83
+ />
84
+ {isExpanded && (
85
+ <div
86
+ className="fixed inset-0 bg-black bg-opacity-90 z-50 flex items-center justify-center"
87
+ onClick={() => setIsExpanded(false)}
88
+ >
89
+ <img
90
+ src={src}
91
+ alt={alt}
92
+ className="max-h-[90vh] max-w-[90vw] object-contain"
93
+ />
94
+ </div>
95
+ )}
96
+ </>
97
+ );
98
+ }
@@ -0,0 +1,15 @@
1
+ import { visit } from 'unist-util-visit';
2
+
3
+ export const remarkSafeVars = () => {
4
+ return (tree: any) => {
5
+ visit(tree, 'mdxTextExpression', (node: any, index: number | undefined, parent: any) => {
6
+ if (parent && typeof index === 'number') {
7
+ // Replace mdxTextExpression with inlineCode node for code formatting
8
+ parent.children[index] = {
9
+ type: 'inlineCode',
10
+ value: node.value
11
+ };
12
+ }
13
+ });
14
+ };
15
+ };