1- import { useSuspenseQuery } from "@tanstack/react-query" ;
2- import { CheckIcon , CopyIcon } from "lucide-react" ;
3- import { useState , useTransition } from "react" ;
4- import { InlineCode } from "../../components/InlineCode.js" ;
1+ import { CheckIcon , CopyIcon , EyeIcon } from "lucide-react" ;
2+ import { useState } from "react" ;
3+ import {
4+ HoverCard ,
5+ HoverCardContent ,
6+ HoverCardTrigger ,
7+ } from "zudoku/ui/HoverCard.js" ;
8+ import { Input } from "zudoku/ui/Input.js" ;
9+ import { NativeSelect , NativeSelectOption } from "zudoku/ui/NativeSelect.js" ;
510import { Button } from "../../ui/Button.js" ;
6- import { useCreateQuery } from "./client/useCreateQuery.js" ;
7- import { useOasConfig } from "./context.js" ;
8- import { graphql } from "./graphql/index.js" ;
9- import { SimpleSelect } from "./SimpleSelect.js" ;
11+ import { cn } from "../../util/cn.js" ;
12+ import type { Server } from "./graphql/graphql.js" ;
1013import { useSelectedServer } from "./state.js" ;
1114
12- const ServersQuery = graphql ( /* GraphQL */ `
13- query ServersQuery($input: JSON!, $type: SchemaType!) {
14- schema(input: $input, type: $type) {
15- url
16- servers {
17- url
18- }
19- }
20- }
21- ` ) ;
22-
2315const CopyButton = ( { url } : { url : string } ) => {
2416 const [ isCopied , setIsCopied ] = useState ( false ) ;
2517
@@ -43,43 +35,122 @@ const CopyButton = ({ url }: { url: string }) => {
4335 ) ;
4436} ;
4537
46- export const Endpoint = ( ) => {
47- const { input, type } = useOasConfig ( ) ;
48- const query = useCreateQuery ( ServersQuery , { input, type } ) ;
49- const result = useSuspenseQuery ( query ) ;
50- const [ , startTransition ] = useTransition ( ) ;
51- const { selectedServer, setSelectedServer } = useSelectedServer (
52- result . data . schema . servers ,
53- ) ;
38+ const ResolvedUrlPreview = ( { url } : { url : string } ) => (
39+ < HoverCard openDelay = { 0 } >
40+ < HoverCardTrigger asChild >
41+ < Button variant = "ghost" size = "icon-xs" >
42+ < EyeIcon size = { 14 } />
43+ </ Button >
44+ </ HoverCardTrigger >
45+ < HoverCardContent className = "w-fit max-w-xl flex items-center justify-between gap-2" >
46+ < pre className = "text-xs font-mono whitespace-pre-wrap break-all" >
47+ { url }
48+ </ pre >
49+ < CopyButton url = { url } />
50+ </ HoverCardContent >
51+ </ HoverCard >
52+ ) ;
5453
55- const { servers } = result . data . schema ;
56- const firstServer = servers . at ( 0 ) ;
54+ export const Endpoint = ( { servers } : { servers : Server [ ] } ) => {
55+ const {
56+ selectedServer,
57+ selectedServerTemplate,
58+ selectedServerVariables,
59+ templateSegments,
60+ setSelectedServer,
61+ setSelectedServerVariable,
62+ } = useSelectedServer ( servers ) ;
5763
58- if ( ! firstServer ) return null ;
64+ if ( servers . length === 0 ) return null ;
65+
66+ const selectedServerDefinition = servers . find (
67+ ( server ) => server . url === selectedServerTemplate ,
68+ ) ;
69+ const templateVariables = selectedServerDefinition ?. variables ?? [ ] ;
5970
6071 return (
61- < div className = "flex items-center gap-1.5 flex-nowrap" >
62- < span className = "font-medium text-sm" > Endpoint</ span >
63- { servers . length > 1 ? (
64- < SimpleSelect
65- className = "font-mono text-xs border-input bg-transparent dark:bg-input/30 dark:hover:bg-input/50 py-1.5 max-w-[450px] truncate"
66- onChange = { ( e ) =>
67- startTransition ( ( ) => setSelectedServer ( e . target . value ) )
68- }
69- value = { selectedServer }
70- showChevrons = { servers . length > 1 }
71- options = { servers . map ( ( server ) => ( {
72- value : server . url ,
73- label : server . url ,
74- } ) ) }
75- />
76- ) : (
77- < InlineCode className = "text-xs px-2 py-1.5" selectOnClick >
78- { firstServer . url }
79- </ InlineCode >
80- ) }
72+ < div className = "border rounded-lg p-3" >
73+ < div className = "flex flex-col gap-1.5" >
74+ < div className = "flex flex-wrap items-center gap-2" >
75+ < span className = "font-medium text-sm" > Endpoint</ span >
76+ { servers . length > 1 && (
77+ < NativeSelect
78+ className = "field-sizing-content truncate py-1 w-fit h-fit text-sm"
79+ value = { selectedServerTemplate }
80+ onChange = { ( e ) => setSelectedServer ( e . target . value ) }
81+ >
82+ { servers . map ( ( server , index ) => (
83+ < NativeSelectOption key = { server . url } value = { server . url } >
84+ { server . description ?? `Server ${ index + 1 } ` }
85+ </ NativeSelectOption >
86+ ) ) }
87+ </ NativeSelect >
88+ ) }
89+ </ div >
90+ { selectedServerDefinition ?. description && (
91+ < div className = "text-xs text-muted-foreground" >
92+ { selectedServerDefinition . description }
93+ </ div >
94+ ) }
95+
96+ { templateVariables . length > 0 ? (
97+ < div className = "font-mono text-xs flex flex-wrap items-center gap-0.5" >
98+ { templateSegments . map ( ( segment , index ) => {
99+ if ( segment . type === "text" ) {
100+ return (
101+ // biome-ignore lint/suspicious/noArrayIndexKey: index should be stable
102+ < span key = { `text-${ index } ` } className = "whitespace-pre-wrap" >
103+ { segment . value }
104+ </ span >
105+ ) ;
106+ }
107+
108+ const variable = templateVariables . find (
109+ ( item ) => item . name === segment . name ,
110+ ) ;
111+ const variableValue = selectedServerVariables [ segment . name ] ?? "" ;
112+
113+ if ( variable ?. enumValues && variable . enumValues . length > 0 ) {
114+ return (
115+ < select
116+ key = { `var-${ segment . name } -${ index } ` }
117+ className = { cn (
118+ "field-sizing-content max-w-42 border-x-0 border-t-0 border-b rounded-none font-mono text-xs!" ,
119+ "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]" ,
120+ ) }
121+ value = { variableValue }
122+ onChange = { ( e ) => {
123+ setSelectedServerVariable ( segment . name , e . target . value ) ;
124+ } }
125+ >
126+ { variable . enumValues . map ( ( enumValue ) => (
127+ < option key = { enumValue } value = { enumValue } >
128+ { enumValue }
129+ </ option >
130+ ) ) }
131+ </ select >
132+ ) ;
133+ }
81134
82- < CopyButton url = { servers . length > 1 ? selectedServer : firstServer . url } />
135+ return (
136+ < Input
137+ key = { `var-${ segment . name } -${ index } ` }
138+ className = "field-sizing-content w-fit max-w-36 h-fit px-1 py-0.5 border-x-0 border-t-0 border-b rounded-none font-mono text-xs!"
139+ value = { variableValue }
140+ onChange = { ( e ) => {
141+ setSelectedServerVariable ( segment . name , e . target . value ) ;
142+ } }
143+ />
144+ ) ;
145+ } ) }
146+ < ResolvedUrlPreview url = { selectedServer } />
147+ </ div >
148+ ) : (
149+ < div className = "font-mono text-xs flex flex-wrap items-center py-1" >
150+ { selectedServer }
151+ </ div >
152+ ) }
153+ </ div >
83154 </ div >
84155 ) ;
85156} ;
0 commit comments