@xyd-js/apidocs-demo 0.0.0-build

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.
@@ -0,0 +1,722 @@
1
+ import React, { useState, useRef, createContext, useContext, useEffect, useMemo } from 'react';
2
+ import {
3
+ Outlet,
4
+ useFetcher,
5
+ useLocation,
6
+ useNavigate,
7
+ useNavigation,
8
+ } from "react-router";
9
+ import { Box, Button, Dropdown, Flex, TextField, Text, Spinner, FixedZIndex } from 'gestalt';
10
+ import GitHubButton from 'react-github-btn'
11
+
12
+ import { Badge } from "@xyd-js/components/writer"
13
+ import { ReactContent } from "@xyd-js/components/content";
14
+ import { Atlas, AtlasContext, type VariantToggleConfig } from "@xyd-js/atlas";
15
+ import { Surfaces } from "@xyd-js/framework";
16
+ import { Framework, FwLink } from "@xyd-js/framework/react";
17
+ import ThemePoetry from "@xyd-js/theme-poetry";
18
+ import ThemeOpener from "@xyd-js/theme-opener";
19
+ import ThemeCosmo from "@xyd-js/theme-cosmo";
20
+ import ThemePicasso from "@xyd-js/theme-picasso";
21
+ import ThemeGusto from "@xyd-js/theme-gusto";
22
+ import ThemeSolar from "@xyd-js/theme-solar";
23
+
24
+ import poetryCss from '@xyd-js/theme-poetry/index.css?url';
25
+ import openerCss from '@xyd-js/theme-opener/index.css?url';
26
+ import cosmoCss from '@xyd-js/theme-cosmo/index.css?url';
27
+ import picassoCss from '@xyd-js/theme-picasso/index.css?url';
28
+ import gustoCss from '@xyd-js/theme-gusto/index.css?url';
29
+ import solarCss from '@xyd-js/theme-solar/index.css?url';
30
+
31
+ import { SETTINGS } from '../settings';
32
+ import { useGlobalState } from '../context';
33
+
34
+ import { DOCS_PREFIX } from '~/const';
35
+ import { UrlContext } from '~/context';
36
+ import { toUniform } from '~/utils/toUniform';
37
+
38
+ const surfaces = new Surfaces()
39
+
40
+ function SidebarItemRight(props: any) {
41
+ const openapi = props?.pageMeta?.openapi || ""
42
+ const [_, region = ""] = openapi.includes("#") ? openapi.split("#") : ["", openapi]
43
+ const [method = ""] = region.split(" ")
44
+
45
+ if (!method) {
46
+ return null
47
+ }
48
+ let methodText = method.toUpperCase()
49
+ if (method === "DELETE") {
50
+ methodText = "DEL"
51
+ }
52
+ []
53
+ return <div data-active={props?.active ? "true" : undefined} data-atlas-oas-method={method}>
54
+ <Badge size="xs">
55
+ {methodText}
56
+ </Badge>
57
+ </div>
58
+ }
59
+
60
+ surfaces.define("sidebar.item.right", SidebarItemRight);
61
+
62
+ const reactContent = new ReactContent(SETTINGS, {
63
+ Link: FwLink,
64
+ components: {
65
+ Atlas,
66
+ },
67
+ useLocation, // // TODO: !!!! BETTER API !!!!!
68
+ useNavigate,
69
+ useNavigation
70
+ })
71
+ // TODO: !!! for demo it cannot be globalThis cuz its globally for whole server !!!
72
+ globalThis.__xydThemeSettings = SETTINGS?.theme
73
+ globalThis.__xydReactContent = reactContent
74
+ globalThis.__xydSurfaces = surfaces
75
+
76
+ let theme: any | null = null
77
+
78
+ switch (SETTINGS?.theme?.name) {
79
+ case "poetry":
80
+ theme = new ThemePoetry()
81
+ break
82
+ case "opener":
83
+ theme = new ThemeOpener()
84
+ break
85
+ case "cosmo":
86
+ theme = new ThemeCosmo()
87
+ break
88
+ case "picasso":
89
+ theme = new ThemePicasso()
90
+ break
91
+ case "gusto":
92
+ theme = new ThemeGusto()
93
+ break
94
+ case "solar":
95
+ theme = new ThemeSolar()
96
+ break
97
+ default:
98
+ theme = null
99
+ }
100
+
101
+ interface DemoContextType {
102
+ example: any;
103
+ setExample: (example: any) => void;
104
+ settings: any;
105
+ fetcher: any;
106
+ isThemeSwitching: boolean;
107
+ setIsThemeSwitching: (switching: boolean) => void;
108
+ }
109
+
110
+ export const DemoContext = createContext<DemoContextType>({
111
+ example: null,
112
+ setExample: () => {
113
+ },
114
+ settings: {},
115
+ fetcher: null,
116
+ isThemeSwitching: false,
117
+ setIsThemeSwitching: () => {
118
+ }
119
+ });
120
+
121
+ export async function loader() {
122
+ return {
123
+ defaultExample: await toUniform(
124
+ "/docs/api",
125
+ "https://raw.githubusercontent.com/livesession/livesession-openapi/master/openapi.yaml",
126
+ "",
127
+ ""
128
+ ),
129
+ // defaultExample: {}
130
+ }
131
+ }
132
+
133
+ export default function Layout({ loaderData }: { loaderData: any }) {
134
+ const [example, setExample] = useState<any>(null);
135
+ const [isThemeSwitching, setIsThemeSwitching] = useState(false);
136
+ const { actionData: globalActionData, setActionData } = useGlobalState();
137
+ const fetcher = useFetcher();
138
+ const navigate = useNavigate()
139
+
140
+ let effectiveActionData = globalActionData || null;
141
+ if (!globalActionData?.references?.length && loaderData?.defaultExample?.references?.length) {
142
+ effectiveActionData = {
143
+ ...globalActionData,
144
+ ...loaderData.defaultExample
145
+ }
146
+ }
147
+
148
+ useEffect(() => {
149
+ if (!globalActionData?.references?.length && loaderData?.defaultExample?.references?.length) {
150
+ setActionData(loaderData?.defaultExample)
151
+
152
+ let canonical = loaderData?.defaultExample?.references?.[0]?.canonical
153
+ if (canonical) {
154
+ canonical = canonical.startsWith("/") ? canonical : `/${canonical}`
155
+ if (canonical.endsWith("/")) {
156
+ canonical = canonical.slice(0, -1)
157
+ }
158
+
159
+ navigate(`${DOCS_PREFIX}${canonical}`)
160
+ }
161
+ }
162
+ }, [])
163
+
164
+ const settings = effectiveActionData?.settings || SETTINGS;
165
+
166
+ let currentTheme = null;
167
+ switch (settings?.theme?.name) {
168
+ case "poetry":
169
+ currentTheme = new ThemePoetry();
170
+ break;
171
+ case "opener":
172
+ currentTheme = new ThemeOpener();
173
+ break;
174
+ case "cosmo":
175
+ currentTheme = new ThemeCosmo();
176
+ break;
177
+ case "picasso":
178
+ currentTheme = new ThemePicasso();
179
+ break;
180
+ case "gusto":
181
+ currentTheme = new ThemeGusto();
182
+ break;
183
+ case "solar":
184
+ currentTheme = new ThemeSolar();
185
+ break;
186
+ default:
187
+ currentTheme = null;
188
+ }
189
+
190
+ const {
191
+ Page: BaseThemePage,
192
+ Layout: BaseThemeLayout,
193
+ } = currentTheme || {};
194
+
195
+ return (
196
+ <DemoContext.Provider value={{ example, setExample, settings: effectiveActionData?.settings || SETTINGS, fetcher, isThemeSwitching, setIsThemeSwitching }}>
197
+ <Layout2 effectiveActionData={effectiveActionData} BaseThemePage={BaseThemePage}
198
+ BaseThemeLayout={BaseThemeLayout} />
199
+ </DemoContext.Provider>
200
+ );
201
+ }
202
+
203
+ const Layout2 = React.memo(function Layout2({
204
+ effectiveActionData,
205
+ BaseThemeLayout,
206
+ BaseThemePage
207
+ }: {
208
+ effectiveActionData: any;
209
+ BaseThemeLayout: any;
210
+ BaseThemePage: any;
211
+ }) {
212
+
213
+ let atlasVariantToggles: VariantToggleConfig[] = [];
214
+ if (effectiveActionData.exampleType === "openapi") {
215
+ atlasVariantToggles = [
216
+ { key: "status", defaultValue: "200" },
217
+ { key: "contentType", defaultValue: "application/json" }
218
+ ];
219
+ } else {
220
+ atlasVariantToggles = [
221
+ { key: "symbolName", defaultValue: "" }
222
+ ];
223
+ }
224
+
225
+ return <Framework
226
+ settings={effectiveActionData.settings || {}}
227
+ sidebarGroups={effectiveActionData.groups || []}
228
+ metadata={{
229
+ layout: "wide",
230
+ uniform: "1",
231
+ title: "OpenAPI Demo"
232
+ }}
233
+ surfaces={surfaces}
234
+ // BannerContent={MemoizedActionDropdownExample}
235
+ >
236
+ <AtlasContext
237
+ value={{
238
+ syntaxHighlight: effectiveActionData.settings?.theme?.coder?.syntaxHighlight || null,
239
+ baseMatch: "/docs/api",
240
+ variantToggles: atlasVariantToggles,
241
+ Link: FwLink,
242
+ }}
243
+ >
244
+ <MemoizedActionDropdownExample />
245
+ <BaseThemeLayout>
246
+ <UrlContext.Provider value={{ BaseThemePage }}>
247
+ <Outlet />
248
+ </UrlContext.Provider>
249
+ </BaseThemeLayout>
250
+ <Loader />
251
+ </AtlasContext>
252
+ </Framework>
253
+ }, (prevProps, nextProps) => {
254
+ return JSON.stringify(prevProps.effectiveActionData) === JSON.stringify(nextProps.effectiveActionData);
255
+ });
256
+
257
+ function Loader() {
258
+ const { fetcher, isThemeSwitching } = useContext(DemoContext)
259
+ const location = useLocation()
260
+ const [inProgress, setInProgress] = useState(false)
261
+
262
+ const previousPathname = useRef(location.pathname)
263
+
264
+ const submitting = fetcher.state === "submitting" || fetcher.state === "loading"
265
+ const loading =
266
+ submitting ||
267
+ isThemeSwitching ||
268
+ inProgress
269
+
270
+ useEffect(() => {
271
+ if (submitting) {
272
+ setInProgress(true)
273
+ }
274
+ }, [fetcher])
275
+
276
+ useEffect(() => {
277
+ if (previousPathname.current !== location.pathname) {
278
+ setInProgress(false)
279
+ }
280
+ previousPathname.current = location.pathname
281
+ }, [location.pathname])
282
+
283
+ if (!loading) {
284
+ return null
285
+ }
286
+
287
+ return <div style={{
288
+ position: "fixed", // or absolute depending on your needs
289
+ top: 0,
290
+ left: 0,
291
+ right: 0,
292
+ bottom: 0,
293
+ width: "100%",
294
+ height: "100%",
295
+ display: "flex",
296
+ justifyContent: "center",
297
+ alignItems: "center",
298
+ zIndex: 1000,
299
+ background: "var(--white)"
300
+ }}>
301
+ <Spinner
302
+ accessibilityLabel="Loading..."
303
+ label={isThemeSwitching ? "Switching theme..." : "Loading..."}
304
+ show={true}
305
+ />
306
+ </div>
307
+ }
308
+
309
+ function MemoizedActionDropdownExample() {
310
+ const { settings: globalSettings } = useContext(DemoContext)
311
+ return <ActionDropdownExample settings={globalSettings} />
312
+ }
313
+
314
+ function ActionDropdownExample({ settings }: { settings: any }) {
315
+ const { example } = useContext(DemoContext)
316
+ const { setActionData } = useGlobalState();
317
+ const { actionData: globalActionData } = useGlobalState();
318
+ const navigate = useNavigate()
319
+ const { fetcher } = useContext(DemoContext)
320
+ const formRef = useRef(null)
321
+
322
+ useEffect(() => {
323
+ if (fetcher.data && fetcher.state === "idle") {
324
+ // Preserve current theme settings when API returns data
325
+ const apiDataWithCurrentTheme = {
326
+ ...fetcher.data,
327
+ settings: {
328
+ ...fetcher.data.settings,
329
+ theme: settings.theme // Preserve current theme
330
+ }
331
+ }
332
+ setActionData(apiDataWithCurrentTheme)
333
+ let canonical = fetcher?.data?.references?.[0]?.canonical
334
+ if (canonical) {
335
+ canonical = canonical.startsWith("/") ? canonical : `/${canonical}`
336
+ if (canonical.endsWith("/")) {
337
+ canonical = canonical.slice(0, -1)
338
+ }
339
+
340
+ navigate(`${DOCS_PREFIX}${canonical}`)
341
+ }
342
+ } else if (fetcher.state === "loading") {
343
+ // setActionData(data => ({
344
+ // references: [],
345
+ // groups: [],
346
+ // exampleType: "",
347
+ // settings: {
348
+ // ...settings,
349
+ // }
350
+ // // ...data,
351
+ // }))
352
+ // setActionData({
353
+ // references: [],
354
+ // settings: settings, // Use current settings instead of default SETTINGS
355
+ // groups: [],
356
+ // exampleType: ""
357
+ // })
358
+ }
359
+ }, [fetcher])
360
+
361
+ const loading = fetcher.state === "submitting"
362
+ const disabled = loading || !example?.url
363
+
364
+ function onSelect(example: any) {
365
+ fetcher.submit(
366
+ {
367
+ type: example?.type,
368
+ value: example?.value,
369
+ example: example?.url
370
+ },
371
+ { action: "/api/try", method: "post" }
372
+ );
373
+ }
374
+
375
+ return (
376
+ <div className="banner-container">
377
+ <div className="banner-left">
378
+ <GithubStars settings={settings} />
379
+ </div>
380
+
381
+ <fetcher.Form method="POST" action="/api/try" ref={formRef}>
382
+ <input type="hidden" name="type" value={example?.type} />
383
+ <input type="hidden" name="value" value={example?.value} />
384
+ <input type="hidden" name="currentSettings" value={JSON.stringify(settings)} />
385
+ <Flex alignItems="center" gap={2}>
386
+ <UniformURLInput />
387
+
388
+ <Flex>
389
+ <Button type="submit" size="sm" text="Try!" disabled={disabled} />
390
+ </Flex>
391
+
392
+ <Flex width="100%">
393
+ <SelectPredefinedUniformURL onSelect={onSelect} />
394
+ </Flex>
395
+ {/* TODO: in the futures */}
396
+ <Flex width="100%">
397
+ <SelectTheme />
398
+ </Flex>
399
+ </Flex>
400
+ </fetcher.Form>
401
+ <div />
402
+ </div>
403
+ );
404
+ }
405
+
406
+ function GithubStars({ settings }: { settings: any }) {
407
+ return <>
408
+ {
409
+ settings?.integrations?.apps?.githubStar && <>
410
+ <Text weight="bold">
411
+ Star us on GitHub ⭐️
412
+ </Text>
413
+
414
+ <GitHubButton
415
+ href={settings?.integrations?.apps?.githubStar?.href}
416
+ data-icon={settings?.integrations?.apps?.githubStar?.dataIcon || "octicon-star"}
417
+ data-size={settings?.integrations?.apps?.githubStar?.dataSize || "large"}
418
+ data-show-count={settings?.integrations?.apps?.githubStar?.dataShowCount || true}
419
+ aria-label={settings?.integrations?.apps?.githubStar?.ariaLabel}
420
+ >
421
+ {settings?.integrations?.apps?.githubStar?.title}
422
+ </GitHubButton>
423
+ </>
424
+ }
425
+ </>
426
+ }
427
+
428
+ function SelectPredefinedUniformURL({
429
+ onSelect: onSelectCb
430
+ }: any) {
431
+ const { setExample, example } = useContext(DemoContext)
432
+
433
+ const exmaples = {
434
+ // openai: {
435
+ // value: "openai",
436
+ // // url: "https://raw.githubusercontent.com/openai/openai-openapi/refs/heads/master/openapi.yaml",
437
+ // // url: "https://app.stainless.com/api/spec/documented/openai/openapi.documented.yml",
438
+ // url: "https://raw.githubusercontent.com/openai/openai-openapi/refs/heads/manual_spec/openapi2.yaml",
439
+ // label: "OpenAI",
440
+ // type: "openapi"
441
+ // },
442
+ livesession: {
443
+ value: "livesession",
444
+ url: "https://raw.githubusercontent.com/livesession/livesession-openapi/master/openapi.yaml",
445
+ label: "Livesession",
446
+ type: "openapi"
447
+ },
448
+ vercel: {
449
+ value: "vercel",
450
+ url: "https://openapi.vercel.sh",
451
+ label: "Vercel",
452
+ type: "openapi"
453
+ },
454
+ intercom: {
455
+ value: "intercom",
456
+ url: "https://developers.intercom.com/_spec/docs/references/@2.11/rest-api/api.intercom.io.json",
457
+ label: "Intercom",
458
+ type: "openapi"
459
+ },
460
+ box: {
461
+ value: "box",
462
+ url: "https://raw.githubusercontent.com/box/box-openapi/main/openapi.json",
463
+ label: "Box",
464
+ type: "openapi"
465
+ },
466
+ // github: {
467
+ // value: "github",
468
+ // url: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/ghes-3.0/ghes-3.0.json",
469
+ // label: "GitHub",
470
+ // type: "openapi"
471
+ // },
472
+ // digitalocean: {
473
+ // value: "digitalocean",
474
+ // url: "https://raw.githubusercontent.com/digitalocean/openapi/main/specification/DigitalOcean-public.v2.yaml",
475
+ // label: "DigitalOcean",
476
+ // type: "openapi"
477
+ // },
478
+ monday: {
479
+ value: "monday",
480
+ url: "https://api.monday.com/v2/get_schema?format=sdl",
481
+ label: "Monday.com",
482
+ type: "graphql"
483
+ },
484
+ braintree: {
485
+ value: "braintree",
486
+ url: "https://raw.githubusercontent.com/braintree/graphql-api/master/schema.graphql",
487
+ label: "Braintree",
488
+ type: "graphql"
489
+ },
490
+ githubgraphql: {
491
+ value: "githubgraphql",
492
+ url: "https://docs.github.com/public/fpt/schema.docs.graphql",
493
+ label: "GitHub",
494
+ type: "graphql"
495
+ },
496
+ artsy: {
497
+ value: "artsy",
498
+ url: "https://raw.githubusercontent.com/artsy/metaphysics/main/_schemaV2.graphql",
499
+ label: "Artsy",
500
+ type: "graphql"
501
+ }
502
+ } as any
503
+
504
+ const [open, setOpen] = useState(false);
505
+ const anchorRef = useRef(null);
506
+ // const [selected, setSelected] = useState(null)
507
+
508
+ const selected = Object.values(exmaples).find((entry: any) => entry.url === example?.url) as any
509
+
510
+ const onSelect = ({ item }: any) => {
511
+ // setSelected(item)
512
+ setOpen(false)
513
+
514
+ const example = exmaples[item?.value]
515
+ if (!example) return
516
+
517
+ setExample(example)
518
+ onSelectCb?.(example)
519
+ };
520
+
521
+ return <>
522
+ <Button
523
+ ref={anchorRef}
524
+ accessibilityControls="choose-example"
525
+ accessibilityExpanded={open}
526
+ accessibilityHaspopup
527
+ iconEnd="arrow-down"
528
+ onClick={() => setOpen((prevVal) => !prevVal)}
529
+ selected={open}
530
+ size="sm"
531
+ text={'Pick Example'}
532
+ />
533
+ {open && (
534
+ <Dropdown
535
+ anchor={anchorRef.current}
536
+ id="choose-example"
537
+ onDismiss={() => setOpen(false)}
538
+ zIndex={new FixedZIndex(9999)}
539
+ >
540
+
541
+ {Object.values(exmaples).map((example: any) => (
542
+ <Dropdown.Item
543
+ onSelect={onSelect}
544
+ option={{ value: example.value, label: example.label }}
545
+ selected={{
546
+ value: selected?.value || "",
547
+ label: selected?.label || ""
548
+ }}
549
+ badge={{
550
+ text: example.type === "openapi" ? "OpenAPI" : "GraphQL",
551
+ type: example.type === "openapi" ? "success" : "recommendation"
552
+ }}
553
+ />
554
+ ))}
555
+ </Dropdown>
556
+ )}
557
+ </>
558
+ }
559
+
560
+ function SelectTheme() {
561
+ const themes = [
562
+ {
563
+ value: "poetry",
564
+ label: "Poetry",
565
+ },
566
+ {
567
+ value: "opener",
568
+ label: "Opener",
569
+ },
570
+ {
571
+ value: "cosmo",
572
+ label: "Cosmo",
573
+ },
574
+ {
575
+ value: "picasso",
576
+ label: "Picasso",
577
+ },
578
+ ]
579
+ const [open, setOpen] = useState(false);
580
+ const [selected, setSelected] = useState(null);
581
+ const anchorRef = useRef(null);
582
+ const { setActionData } = useGlobalState();
583
+ const { setIsThemeSwitching } = useContext(DemoContext);
584
+
585
+ const onSelect = async ({ item }: { item: any }) => {
586
+ setSelected(item);
587
+ setOpen(false);
588
+ setIsThemeSwitching(true);
589
+
590
+ try {
591
+ // Update theme in settings
592
+ const newSettings = JSON.parse(JSON.stringify(SETTINGS));
593
+ newSettings.theme = {
594
+ ...newSettings.theme,
595
+ name: item.value
596
+ };
597
+
598
+ // Update global theme settings
599
+ (globalThis as any).__xydThemeSettings = newSettings.theme;
600
+
601
+ // Dynamically import theme CSS
602
+ const themeName = item.value;
603
+
604
+ // Create new theme style link first (but don't append yet)
605
+ const themeStyles = document.createElement('link');
606
+ themeStyles.rel = 'stylesheet';
607
+ let themeCss = poetryCss
608
+ if (themeName === "picasso") {
609
+ themeCss = picassoCss
610
+ } else if (themeName === "cosmo") {
611
+ themeCss = cosmoCss
612
+ } else if (themeName === "opener") {
613
+ themeCss = openerCss
614
+ } else if (themeName === "gusto") {
615
+ themeCss = gustoCss
616
+ } else if (themeName === "solar") {
617
+ themeCss = solarCss
618
+ }
619
+ themeStyles.href = themeCss
620
+ themeStyles.setAttribute('data-theme-style', 'true');
621
+
622
+
623
+ // Now that new CSS is loaded, remove old theme styles
624
+ const oldThemeStyles = document.querySelectorAll('link[data-theme-style]:not([href*="' + themeName + '"])');
625
+ oldThemeStyles.forEach(style => style.remove());
626
+
627
+ // Wait for CSS to load BEFORE removing old styles
628
+ await new Promise<void>((resolve, reject) => {
629
+ themeStyles.onload = () => resolve();
630
+ themeStyles.onerror = () => reject(new Error('Failed to load theme CSS'));
631
+ document.head.appendChild(themeStyles);
632
+ });
633
+
634
+ // Remove default theme styles
635
+ const defaultThemeStyles = document.querySelectorAll('link[data-xyd-theme-default]');
636
+ defaultThemeStyles.forEach(style => style.remove());
637
+
638
+ // Small delay to ensure CSS is applied
639
+ await new Promise(resolve => setTimeout(resolve, 100));
640
+
641
+ // Update global state to trigger re-render with new theme
642
+ setActionData((prev: any) => ({
643
+ ...prev,
644
+ settings: newSettings
645
+ }));
646
+
647
+ } catch (error) {
648
+ console.error('Error switching theme:', error);
649
+ } finally {
650
+ setIsThemeSwitching(false);
651
+ }
652
+ };
653
+
654
+ return <>
655
+ <Button
656
+ ref={anchorRef}
657
+ accessibilityControls="choose-theme"
658
+ accessibilityExpanded={open}
659
+ accessibilityHaspopup
660
+ iconEnd="arrow-down"
661
+ onClick={() => setOpen((prevVal) => !prevVal)}
662
+ selected={open}
663
+ size="sm"
664
+ text={selected ? selected.label : 'Pick Theme'}
665
+ />
666
+ {
667
+ open && (
668
+ <Dropdown
669
+ anchor={anchorRef.current}
670
+ id="choose-theme"
671
+ onDismiss={() => setOpen(false)}
672
+ zIndex={new FixedZIndex(9999)}
673
+ >
674
+ {themes.map((theme) => (
675
+ <Dropdown.Item
676
+ key={theme.value}
677
+ onSelect={onSelect}
678
+ option={{ value: theme.value, label: theme.label }}
679
+ selected={selected}
680
+ />
681
+ ))}
682
+ </Dropdown>
683
+ )
684
+ }
685
+ </>
686
+ }
687
+
688
+ function UniformURLInput() {
689
+ const { example, setExample } = useContext(DemoContext)
690
+
691
+ function handleChange(value: string) {
692
+ setExample({
693
+ ...example,
694
+ url: value
695
+ })
696
+ }
697
+
698
+ return (
699
+ <Flex
700
+ alignItems="center"
701
+ gap={4}
702
+ height="100%"
703
+ width="100%"
704
+ >
705
+ <Box width={400}>
706
+ <TextField
707
+ id="header-example"
708
+ onChange={({ value }) => {
709
+ handleChange(value);
710
+ }}
711
+ placeholder="URL to OpenAPI / GraphQL / React"
712
+ type="text"
713
+ size="sm"
714
+ value={example?.url || ""}
715
+ name="example"
716
+ />
717
+ </Box>
718
+ </Flex>
719
+ );
720
+ }
721
+
722
+