@xyd-js/atlas 0.1.0-build.173

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 (68) hide show
  1. package/.babelrc +6 -0
  2. package/.storybook/index.css +6 -0
  3. package/.storybook/main.ts +19 -0
  4. package/.storybook/preview.ts +17 -0
  5. package/.storybook/public/fonts/fustat-ext-500.woff2 +0 -0
  6. package/.storybook/public/fonts/fustat-ext-600.woff2 +0 -0
  7. package/.storybook/public/fonts/fustat-ext-700.woff2 +0 -0
  8. package/.storybook/public/fonts/fustat-regular.woff2 +0 -0
  9. package/CHANGELOG.md +1984 -0
  10. package/LICENSE +21 -0
  11. package/README.md +3 -0
  12. package/declarations.d.ts +4 -0
  13. package/dist/VideoGuide-C0K9fFar-Dk2lkn4r.js +4 -0
  14. package/dist/VideoGuide-C0K9fFar-Dk2lkn4r.js.map +1 -0
  15. package/dist/VideoGuide-CJYkuLst-Dk2lkn4r.js +4 -0
  16. package/dist/VideoGuide-CJYkuLst-Dk2lkn4r.js.map +1 -0
  17. package/dist/index.css +48 -0
  18. package/dist/index.d.ts +38 -0
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/styles.css +104 -0
  22. package/dist/tokens.css +60 -0
  23. package/dist/xydPlugin.d.ts +5 -0
  24. package/dist/xydPlugin.js +2 -0
  25. package/dist/xydPlugin.js.map +1 -0
  26. package/index.ts +2 -0
  27. package/package.json +68 -0
  28. package/packages/xyd-plugin/SidebarItem.tsx +27 -0
  29. package/packages/xyd-plugin/index.ts +20 -0
  30. package/postcss.config.cjs +5 -0
  31. package/rollup.config.js +120 -0
  32. package/src/components/ApiRef/ApiRefItem/ApiRefItem.styles.tsx +110 -0
  33. package/src/components/ApiRef/ApiRefItem/ApiRefItem.tsx +557 -0
  34. package/src/components/ApiRef/ApiRefItem/index.ts +7 -0
  35. package/src/components/ApiRef/ApiRefProperties/ApiRefProperties.styles.tsx +202 -0
  36. package/src/components/ApiRef/ApiRefProperties/ApiRefProperties.tsx +665 -0
  37. package/src/components/ApiRef/ApiRefProperties/index.ts +7 -0
  38. package/src/components/ApiRef/ApiRefSamples/ApiRefSamples.styles.tsx +28 -0
  39. package/src/components/ApiRef/ApiRefSamples/ApiRefSamples.tsx +69 -0
  40. package/src/components/ApiRef/ApiRefSamples/index.ts +7 -0
  41. package/src/components/ApiRef/index.ts +5 -0
  42. package/src/components/Atlas/Atlas.styles.tsx +5 -0
  43. package/src/components/Atlas/Atlas.tsx +43 -0
  44. package/src/components/Atlas/AtlasContext.tsx +47 -0
  45. package/src/components/Atlas/AtlasDecorator.styles.ts +22 -0
  46. package/src/components/Atlas/AtlasDecorator.tsx +15 -0
  47. package/src/components/Atlas/AtlasLazy/AtlasLazy.styles.tsx +9 -0
  48. package/src/components/Atlas/AtlasLazy/AtlasLazy.tsx +42 -0
  49. package/src/components/Atlas/AtlasLazy/hooks.ts +29 -0
  50. package/src/components/Atlas/AtlasLazy/index.ts +7 -0
  51. package/src/components/Atlas/AtlasPrimary.tsx +21 -0
  52. package/src/components/Atlas/AtlasSecondary.tsx +148 -0
  53. package/src/components/Atlas/index.ts +7 -0
  54. package/src/components/Atlas/types.ts +11 -0
  55. package/src/components/Code/CodeSampleButtons/CodeSampleButtons.styles.tsx +58 -0
  56. package/src/components/Code/CodeSampleButtons/CodeSampleButtons.tsx +97 -0
  57. package/src/components/Code/CodeSampleButtons/index.ts +7 -0
  58. package/src/components/Code/index.ts +2 -0
  59. package/src/components/Icon/index.tsx +386 -0
  60. package/src/docs/AtlasExample/AtlasExample.stories.tsx +47 -0
  61. package/src/docs/AtlasExample/todo-app.uniform.json +625 -0
  62. package/src/docs/AtlasExample/uniform-to-references.ts +101 -0
  63. package/src/styles/styles.css +104 -0
  64. package/src/styles/tokens.css +60 -0
  65. package/src/utils/mdx.ts +2 -0
  66. package/tsconfig.json +51 -0
  67. package/types.d.ts +22 -0
  68. package/vite.config.ts +25 -0
@@ -0,0 +1,557 @@
1
+ import React, {createContext, useContext, useState, useCallback, useEffect} from "react";
2
+
3
+ import {
4
+ Definition,
5
+ DefinitionVariant,
6
+ Meta,
7
+ OpenAPIReferenceContext,
8
+ Reference,
9
+ ReferenceCategory
10
+ } from "@xyd-js/uniform";
11
+ import {Heading, Code, Badge, Text} from "@xyd-js/components/writer";
12
+
13
+ import {
14
+ ApiRefProperties,
15
+ ApiRefSamples
16
+ } from "@/components/ApiRef";
17
+ import * as cn from "@/components/ApiRef/ApiRefItem/ApiRefItem.styles";
18
+ import {useVariantToggles, type VariantToggleConfig} from "@/components/Atlas/AtlasContext";
19
+
20
+ export interface ApiRefItemProps {
21
+ reference: Reference
22
+ kind?: "secondary"
23
+ }
24
+
25
+ // TODO: context with current referene?
26
+ export function ApiRefItem({
27
+ kind,
28
+ reference
29
+ }: ApiRefItemProps) {
30
+ const hasExamples = reference.examples?.groups?.length || false
31
+
32
+ let header: React.ReactNode | null = <$IntroHeader reference={reference}/>
33
+ let examples: React.ReactNode | null = <ApiRefSamples examples={reference.examples}/>
34
+
35
+ if (kind === "secondary") {
36
+ header = null
37
+ examples = null
38
+ }
39
+
40
+ return <atlas-apiref-item
41
+ data-has-examples={hasExamples ? "true" : undefined}
42
+ className={cn.ApiRefItemHost}
43
+ >
44
+ <atlas-apiref-item-showcase className={cn.ApiRefItemGrid}>
45
+ <div>
46
+ {header}
47
+ <$Definitions
48
+ kind={kind}
49
+ reference={reference}
50
+ />
51
+ </div>
52
+
53
+ {examples}
54
+ </atlas-apiref-item-showcase>
55
+ </atlas-apiref-item>
56
+ }
57
+
58
+ function $IntroHeader({reference}: ApiRefItemProps) {
59
+ let topNavbar;
60
+
61
+ switch (reference?.category) {
62
+ case ReferenceCategory.REST: {
63
+ const ctx = reference.context as OpenAPIReferenceContext
64
+
65
+ if (!ctx || !ctx.method || !ctx.fullPath) {
66
+ break;
67
+ }
68
+
69
+ // TODO: finish subitlte from ref
70
+ topNavbar = <$Navbar
71
+ label={ctx.method}
72
+ subtitle={`${ctx.fullPath}`}
73
+ matchSubtitle={ctx.path}
74
+ />
75
+ break;
76
+ }
77
+ }
78
+ return <>
79
+ <$Title title={reference.title}/>
80
+
81
+ {topNavbar}
82
+
83
+ {reference.description}
84
+ </>
85
+ }
86
+
87
+ function $Authorization({reference}: ApiRefItemProps) {
88
+ if (!reference.context) {
89
+ return null;
90
+ }
91
+
92
+ const context = reference.context as OpenAPIReferenceContext;
93
+
94
+ if (!context.scopes || !context.scopes.length) {
95
+ return null;
96
+ }
97
+
98
+ return <div>
99
+ <div className={cn.ApiRefItemDefinitionsItem}>
100
+ <div part="header">
101
+ <$Subtitle title="Scopes"/>
102
+ </div>
103
+
104
+ <$DefinitionBody definition={{
105
+ title: "",
106
+ properties: [],
107
+ description: <>
108
+ Required scopes: {context.scopes.map(s => <Code>{s}</Code>)}
109
+ </>
110
+ }}
111
+ />
112
+ </div>
113
+ </div>
114
+ }
115
+
116
+ const VariantContext = createContext<{
117
+ setVariant: (variant: DefinitionVariant) => void,
118
+ variant?: DefinitionVariant,
119
+ variantToggles: VariantToggleConfig[],
120
+ selectedValues: Record<string, string>,
121
+ setSelectedValue: (key: string, value: string) => void,
122
+ variants: DefinitionVariant[],
123
+ }>({
124
+ variant: undefined,
125
+ setVariant: () => {
126
+ },
127
+ variantToggles: [],
128
+ selectedValues: {},
129
+ setSelectedValue: () => {
130
+ },
131
+ variants: [],
132
+ });
133
+
134
+ function $Definitions({
135
+ kind,
136
+ reference
137
+ }: ApiRefItemProps) {
138
+ let argDefinition: Definition | undefined
139
+ let definitions = reference?.definitions || []
140
+
141
+ if (reference?.category === ReferenceCategory.GRAPHQL) {
142
+ const gqlDefinitions: Definition[] = []
143
+
144
+ // First find the arguments definition
145
+ reference?.definitions?.forEach(definition => {
146
+ const foundArgs = definition?.meta?.find(meta => {
147
+ return meta.name === "type" && meta.value === "arguments"
148
+ })
149
+
150
+ if (foundArgs) {
151
+ argDefinition = definition
152
+ } else {
153
+ gqlDefinitions.push(definition)
154
+ }
155
+ })
156
+
157
+ // Process each definition to merge argument properties
158
+ definitions = gqlDefinitions
159
+ .filter(definition => definition?.properties?.length)
160
+ .map(definition => {
161
+ if (!definition.properties?.length) return definition
162
+
163
+ // For each property in the definition
164
+ const updatedProperties = definition.properties.map(prop => {
165
+ // Find matching variant in argDefinition by symbolName
166
+ const matchingVariant = argDefinition?.variants?.find(variant => {
167
+ const symbolMeta = variant.meta?.find(m => m.name === 'symbolName')
168
+ return symbolMeta?.value === prop.name
169
+ })
170
+
171
+ if (matchingVariant) {
172
+ // Add meta flag to indicate this property has arguments, but only if it doesn't already have it
173
+ const meta = prop.meta || []
174
+ if (!meta.some(m => m.name === 'hasArguments')) {
175
+ meta.push({
176
+ name: 'hasArguments', // TODO: better solution in the future
177
+ value: 'true'
178
+ })
179
+ }
180
+
181
+ // Merge properties from the matching variant
182
+ return {
183
+ ...prop,
184
+ meta,
185
+ properties: matchingVariant.properties || []
186
+ }
187
+ }
188
+
189
+ return prop
190
+ })
191
+
192
+ return {
193
+ ...definition,
194
+ properties: updatedProperties
195
+ }
196
+ })
197
+ }
198
+
199
+ return <atlas-apiref-definitions className={cn.ApiRefItemDefinitionsHost}>
200
+ <$Authorization reference={reference}/>
201
+
202
+ {definitions?.map((definition, i) => {
203
+ if (kind === "secondary") {
204
+ return <$DefinitionBody key={i} definition={definition}/>
205
+ }
206
+
207
+ return <$VariantsProvider key={i} definition={definition}>
208
+ <div>
209
+ {
210
+ definition?.title ? <div key={i} className={cn.ApiRefItemDefinitionsItem}>
211
+ <div part="header">
212
+ <$Subtitle title={definition.title}/>
213
+ <div part="controls">
214
+ <$VariantSelects/>
215
+ </div>
216
+ </div>
217
+
218
+ <$DefinitionBody definition={definition}/>
219
+ </div> : null
220
+ }
221
+ </div>
222
+ </$VariantsProvider>
223
+ })}
224
+ </atlas-apiref-definitions>
225
+ }
226
+
227
+ function $VariantsProvider({definition, children}: {
228
+ definition: Definition,
229
+ children: React.ReactNode
230
+ }) {
231
+ const variants = definition.variants || [];
232
+ const variantMetas = variants.reduce((acc, variant) => {
233
+ const allMetaNames = variant.meta?.reduce((metaAcc, meta) => ({
234
+ ...metaAcc,
235
+ [meta.name]: 1,
236
+ }), {}) || {}
237
+
238
+
239
+ return {
240
+ ...acc,
241
+ ...allMetaNames,
242
+ }
243
+ }, {});
244
+ const variantToggles = (useVariantToggles() || []).filter(toggle => variantMetas[toggle.key])
245
+
246
+ const [selectedValues, setSelectedValues] = useState<Record<string, string>>(() => {
247
+ const initial: Record<string, string> = {};
248
+ variantToggles.forEach(toggle => {
249
+ initial[toggle.key] = toggle.defaultValue;
250
+ });
251
+ return initial;
252
+ });
253
+
254
+ const setSelectedValue = useCallback((key: string, value: string) => {
255
+ setSelectedValues(prev => ({...prev, [key]: value}));
256
+ }, []);
257
+
258
+
259
+ const [variant, setVariant] = useState<DefinitionVariant | undefined>(() => {
260
+ return findMatchingVariant(variants, selectedValues);
261
+ });
262
+
263
+ useEffect(() => {
264
+ const newVariant = findMatchingVariant(variants, selectedValues);
265
+ setVariant(newVariant);
266
+ }, [selectedValues, variants]);
267
+
268
+ return <VariantContext.Provider value={{
269
+ variant,
270
+ setVariant,
271
+ variantToggles,
272
+ selectedValues,
273
+ setSelectedValue,
274
+ variants,
275
+ }}>
276
+ {children}
277
+ </VariantContext.Provider>
278
+ }
279
+
280
+ function findMatchingVariant(variants: DefinitionVariant[], selectedValues: Record<string, string>): DefinitionVariant | undefined {
281
+ const matchingVariant = variants.find(variant => {
282
+ const matches = Object.entries(selectedValues).every(([key, value]) => {
283
+ if (!value) return true; // Skip empty values
284
+ const meta = variant.meta?.find(m => m.name === key);
285
+ return meta?.value === value;
286
+ });
287
+ return matches;
288
+ });
289
+
290
+ return matchingVariant || variants[0];
291
+ }
292
+
293
+ function $VariantSelects() {
294
+ const {variantToggles, selectedValues, setSelectedValue, variants} = useContext(VariantContext);
295
+
296
+ if (!variants?.length) return null;
297
+
298
+ // Create selects based on variantToggles
299
+ return (
300
+ <div className={""}>
301
+ {variantToggles.map((toggle, index) => {
302
+ // Get all unique values for this toggle
303
+ const availableValues = Array.from(new Set(
304
+ variants.map(v => {
305
+ const meta = v.meta?.find(m => m.name === toggle.key);
306
+ return meta?.value as string;
307
+ }).filter(Boolean)
308
+ )).sort();
309
+
310
+ // Get available values based on other selected values
311
+ const filteredValues = availableValues.filter(value => {
312
+ // For the first toggle, show all values
313
+ if (index === 0) return true;
314
+
315
+ // For other toggles, check if there's a variant with this value and all previous selected values
316
+ return variants.some(variant => {
317
+ // First check if this variant has the value we're checking
318
+ const hasValue = variant.meta?.some(m => m.name === toggle.key && m.value === value);
319
+ if (!hasValue) return false;
320
+
321
+ // Then check if it matches all previous selected values
322
+ return variantToggles.slice(0, index).every(prevToggle => {
323
+ const selectedValue = selectedValues[prevToggle.key];
324
+ if (!selectedValue) return true; // Skip empty values
325
+
326
+ const meta = variant.meta?.find(m => m.name === prevToggle.key);
327
+ return meta?.value === selectedValue;
328
+ });
329
+ });
330
+ });
331
+
332
+ // If no values available, use the current value
333
+ const displayValues = filteredValues.length > 0 ? filteredValues :
334
+ (selectedValues[toggle.key] ? [selectedValues[toggle.key]] : availableValues);
335
+
336
+ return (
337
+ <select
338
+ key={toggle.key}
339
+ value={selectedValues[toggle.key] || ''}
340
+ onChange={(e) => {
341
+ const newValue = e.target.value;
342
+ setSelectedValue(toggle.key, newValue);
343
+
344
+ // For the first toggle (status), check if current content type is still valid
345
+ if (index === 0 && variantToggles.length > 1) {
346
+ const contentTypeKey = variantToggles[1].key;
347
+ const currentContentType = selectedValues[contentTypeKey];
348
+
349
+ if (currentContentType) {
350
+ // Check if current content type is valid for new status
351
+ const isValid = variants.some(variant => {
352
+ const statusMeta = variant.meta?.find(m => m.name === toggle.key);
353
+ const contentTypeMeta = variant.meta?.find(m => m.name === contentTypeKey);
354
+ return statusMeta?.value === newValue && contentTypeMeta?.value === currentContentType;
355
+ });
356
+
357
+ // If not valid, reset content type
358
+ if (!isValid) {
359
+ setSelectedValue(contentTypeKey, '');
360
+ }
361
+ }
362
+ } else if (index < variantToggles.length - 1) {
363
+ // For other toggles, reset all toggles after this one
364
+ // Only if there are toggles after this one
365
+ variantToggles.slice(index + 1).forEach(nextToggle => {
366
+ setSelectedValue(nextToggle.key, '');
367
+ });
368
+ }
369
+ }}
370
+ >
371
+ {displayValues.map(value => (
372
+ <option key={value} value={value}>
373
+ {value}
374
+ </option>
375
+ ))}
376
+ </select>
377
+ );
378
+ })}
379
+ </div>
380
+ );
381
+ }
382
+
383
+ interface DefinitionBodyProps {
384
+ definition: Definition
385
+ }
386
+
387
+ function $DefinitionBody(props: DefinitionBodyProps) {
388
+ const {definition} = props;
389
+ const {variant} = useContext(VariantContext);
390
+
391
+ let apiRefProperties: React.ReactNode | null = null;
392
+
393
+ if (variant) {
394
+ if (variant.properties?.length) {
395
+ apiRefProperties = <ApiRefProperties properties={variant.properties}/>;
396
+ } else if (variant.rootProperty) {
397
+ apiRefProperties = <ApiRefProperties properties={[variant.rootProperty]}/>;
398
+ }
399
+ } else {
400
+ if (definition.properties?.length) {
401
+ apiRefProperties = <ApiRefProperties properties={definition.properties}/>;
402
+ } else if (definition.rootProperty) {
403
+ apiRefProperties = <ApiRefProperties properties={[definition.rootProperty]}/>;
404
+ }
405
+ }
406
+
407
+ const getMetaInfo = (meta: Meta[] | undefined) => {
408
+ if (!meta?.length) return null;
409
+
410
+ const minimum = meta.find(m => m.name === 'minimum')?.value;
411
+ const maximum = meta.find(m => m.name === 'maximum')?.value;
412
+ const example = meta.find(m => m.name === 'example')?.value;
413
+
414
+ const rangeInfo: string[] = [];
415
+ if (minimum !== undefined && maximum !== undefined) {
416
+ rangeInfo.push(`Required range: ${minimum} <= x <= ${maximum}`);
417
+ } else if (minimum !== undefined) {
418
+ rangeInfo.push(`Required range: x >= ${minimum}`);
419
+ } else if (maximum !== undefined) {
420
+ rangeInfo.push(`Required range: x <= ${maximum}`);
421
+ }
422
+
423
+ const exampleInfo = example ? [`Examples:`, `"${example}"`] : [];
424
+
425
+ return [...rangeInfo, ...exampleInfo].length > 0 ? (
426
+ <div style={{
427
+ marginTop: '1rem',
428
+ marginBottom: '1rem',
429
+ color: 'var(--color-text-secondary)',
430
+ fontSize: '0.875rem'
431
+ }}>
432
+ {rangeInfo.map((info, i) => (
433
+ <div key={`range-${i}`}>{info}</div>
434
+ ))}
435
+ {exampleInfo.length > 0 && (
436
+ <>
437
+ <div>{exampleInfo[0]}</div>
438
+ <div>{exampleInfo[1]}</div>
439
+ </>
440
+ )}
441
+ </div>
442
+ ) : null;
443
+ };
444
+
445
+ const metaInfo = variant ? getMetaInfo(variant.meta) : getMetaInfo(definition.meta);
446
+ const description = variant ? variant.description : definition.description;
447
+ const metaDescription = definitionMetaDescription(variant ? variant : definition);
448
+
449
+ return <div className={cn.DefinitionBody}>
450
+ {
451
+ description && <div>
452
+ {description}
453
+ </div>
454
+ }
455
+ {
456
+ metaDescription && <div>
457
+ {metaDescription}
458
+ </div>
459
+ }
460
+
461
+ {metaInfo}
462
+
463
+ {apiRefProperties}
464
+ </div>
465
+ }
466
+
467
+ interface NavbarProps {
468
+ label: string
469
+ subtitle: string
470
+ matchSubtitle?: string
471
+ }
472
+
473
+ function $Navbar({label, subtitle, matchSubtitle}: NavbarProps) {
474
+ const renderSubtitle = () => {
475
+ if (!matchSubtitle) {
476
+ return subtitle;
477
+ }
478
+
479
+ const index = subtitle.indexOf(matchSubtitle);
480
+ if (index === -1) {
481
+ return subtitle;
482
+ }
483
+
484
+ const before = subtitle.substring(0, index);
485
+ const match = subtitle.substring(index, index + matchSubtitle.length);
486
+ const after = subtitle.substring(index + matchSubtitle.length);
487
+
488
+ return (
489
+ <>
490
+ {before}
491
+ <Text as="span" weight="bold">
492
+ {match}
493
+ </Text>
494
+ {after}
495
+ </>
496
+ );
497
+ };
498
+
499
+ const handleClick = (e: React.MouseEvent) => {
500
+ const target = e.target as HTMLElement;
501
+ const range = document.createRange();
502
+ range.selectNodeContents(target);
503
+ const selection = window.getSelection();
504
+ if (selection) {
505
+ selection.removeAllRanges();
506
+ selection.addRange(range);
507
+ }
508
+ };
509
+
510
+ return <>
511
+ <div className={cn.ApiRefItemNavbarHost}>
512
+ <div className={cn.ApiRefItemNavbarContainer}>
513
+ <div className={cn.ApiRefItemNavbarLabel}>
514
+ {/* TODO: in the future not only for REST */}
515
+ <div data-active="true" data-atlas-oas-method={label.toUpperCase()}>
516
+ <Badge size="xs">
517
+ {label.toUpperCase()}
518
+ </Badge>
519
+ </div>
520
+ </div>
521
+ <div
522
+ className={cn.ApiRefItemNavbarSubtitle}
523
+ onClick={handleClick}
524
+ >
525
+ {renderSubtitle()}
526
+ </div>
527
+ </div>
528
+ </div>
529
+ </>
530
+ }
531
+
532
+ function $Title({title}: { title: string }) {
533
+ return <>
534
+ <Heading size={1}>
535
+ {title}
536
+ </Heading>
537
+ </>
538
+ }
539
+
540
+ function $Subtitle({title}: { title: string }) {
541
+ return <>
542
+ <Heading size={3}>
543
+ {title}
544
+ </Heading>
545
+ </>
546
+ }
547
+
548
+ function definitionMetaDescription(definition: Definition | DefinitionVariant): string {
549
+ if (definition.meta?.length) {
550
+ const descriptionMeta = definition.meta.find(meta => meta.name === 'definitionDescription');
551
+ if (descriptionMeta) {
552
+ return descriptionMeta.value as string || "";
553
+ }
554
+ }
555
+
556
+ return "";
557
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ ApiRefItem
3
+ } from "./ApiRefItem"
4
+
5
+ export type {
6
+ ApiRefItemProps
7
+ } from "./ApiRefItem"