@gitbook/react-openapi 1.1.4 → 1.1.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @gitbook/react-openapi
2
2
 
3
+ ## 1.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 886e204: Update OpenAPI operation path design
8
+
3
9
  ## 1.1.4
4
10
 
5
11
  ### Patch Changes
@@ -10,6 +10,7 @@ var __assign = (this && this.__assign) || function () {
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
12
  import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
13
+ import { ScalarApiButton } from './ScalarApiButton';
13
14
  import { StaticSection } from './StaticSection';
14
15
  import { codeSampleGenerators } from './code-samples';
15
16
  import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
@@ -73,6 +74,7 @@ export function OpenAPICodeSample(props) {
73
74
  code: generator.generate(input),
74
75
  syntax: generator.syntax,
75
76
  }),
77
+ footer: <OpenAPICodeSampleFooter data={data} context={context}/>,
76
78
  }); });
77
79
  // Use custom samples if defined
78
80
  var customCodeSamples = null;
@@ -92,6 +94,7 @@ export function OpenAPICodeSample(props) {
92
94
  code: sample.source,
93
95
  syntax: sample.lang,
94
96
  }),
97
+ footer: <OpenAPICodeSampleFooter data={data} context={context}/>,
95
98
  }); });
96
99
  }
97
100
  });
@@ -108,6 +111,21 @@ export function OpenAPICodeSample(props) {
108
111
  </StaticSection>
109
112
  </OpenAPITabs>);
110
113
  }
114
+ function OpenAPICodeSampleFooter(props) {
115
+ var data = props.data, context = props.context;
116
+ var method = data.method, path = data.path;
117
+ var specUrl = context.specUrl;
118
+ var hideTryItPanel = data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'];
119
+ if (hideTryItPanel) {
120
+ return null;
121
+ }
122
+ if (!validateHttpMethod(method)) {
123
+ return null;
124
+ }
125
+ return (<div className="openapi-codesample-footer">
126
+ <ScalarApiButton method={method} path={path} specUrl={specUrl}/>
127
+ </div>);
128
+ }
111
129
  function getSecurityHeaders(securities) {
112
130
  var _a;
113
131
  var _b, _c;
@@ -146,3 +164,6 @@ function getSecurityHeaders(securities) {
146
164
  }
147
165
  }
148
166
  }
167
+ function validateHttpMethod(method) {
168
+ return ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'].includes(method);
169
+ }
@@ -0,0 +1,4 @@
1
+ import { type ButtonProps } from 'react-aria-components';
2
+ export declare function OpenAPICopyButton(props: ButtonProps & {
3
+ value: string;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ import { useState } from 'react';
3
+ import { Button, Tooltip, TooltipTrigger } from 'react-aria-components';
4
+ export function OpenAPICopyButton(props) {
5
+ var value = props.value;
6
+ var children = props.children, onPress = props.onPress, className = props.className;
7
+ var _a = useState(false), copied = _a[0], setCopied = _a[1];
8
+ var _b = useState(false), isOpen = _b[0], setIsOpen = _b[1];
9
+ var handleCopy = function () {
10
+ if (!value)
11
+ return;
12
+ navigator.clipboard.writeText(value).then(function () {
13
+ setIsOpen(true);
14
+ setCopied(true);
15
+ setTimeout(function () {
16
+ setCopied(false);
17
+ }, 2000);
18
+ });
19
+ };
20
+ return (<TooltipTrigger isOpen={isOpen} onOpenChange={setIsOpen} closeDelay={200} delay={200}>
21
+ <Button type="button" preventFocusOnPress onPress={function (e) {
22
+ handleCopy();
23
+ onPress === null || onPress === void 0 ? void 0 : onPress(e);
24
+ }} className={"openapi-copy-button ".concat(className)} {...props}>
25
+ {children}
26
+ </Button>
27
+
28
+ <Tooltip isOpen={isOpen} onOpenChange={setIsOpen} placement="top" offset={4} className="openapi-tooltip">
29
+ {copied ? 'Copied' : 'Copy to clipboard'}{' '}
30
+ </Tooltip>
31
+ </TooltipTrigger>);
32
+ }
@@ -25,6 +25,7 @@ export function OpenAPIOperation(props) {
25
25
  title: operation.summary,
26
26
  })
27
27
  : null}
28
+ <OpenAPIPath data={data} context={context}/>
28
29
  {operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
29
30
  </div>
30
31
  <div className="openapi-columns">
@@ -37,7 +38,6 @@ export function OpenAPIOperation(props) {
37
38
  {'.'}
38
39
  </div>) : null}
39
40
  <OpenAPIOperationDescription operation={operation} context={context}/>
40
- <OpenAPIPath data={data} context={context}/>
41
41
  <OpenAPISpec data={data} context={clientContext}/>
42
42
  </div>
43
43
  <div className="openapi-column-preview">
@@ -1,4 +1,3 @@
1
- import type React from 'react';
2
1
  import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
3
2
  /**
4
3
  * Display the path of an operation.
@@ -6,4 +5,4 @@ import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
6
5
  export declare function OpenAPIPath(props: {
7
6
  data: OpenAPIOperationData;
8
7
  context: OpenAPIContextProps;
9
- }): React.JSX.Element;
8
+ }): import("react").JSX.Element;
@@ -1,47 +1,49 @@
1
- import { ScalarApiButton } from './ScalarApiButton';
1
+ import { OpenAPICopyButton } from './OpenAPICopyButton';
2
+ import { getDefaultServerURL } from './util/server';
2
3
  /**
3
4
  * Display the path of an operation.
4
5
  */
5
6
  export function OpenAPIPath(props) {
6
- var data = props.data, context = props.context;
7
- var method = data.method, path = data.path;
8
- var specUrl = context.specUrl;
9
- var hideTryItPanel = data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'];
7
+ var data = props.data;
8
+ var method = data.method, path = data.path, operation = data.operation;
9
+ var server = getDefaultServerURL(data.servers);
10
+ var formattedPath = formatPath(path);
10
11
  return (<div className="openapi-path">
11
12
  <div className={"openapi-method openapi-method-".concat(method)}>{method}</div>
12
- <div className="openapi-path-title" data-deprecated={data.operation.deprecated}>
13
- <p>{formatPath(path)}</p>
14
- </div>
15
- {!hideTryItPanel && validateHttpMethod(method) && (<ScalarApiButton method={method} path={path} specUrl={specUrl}/>)}
13
+
14
+ <OpenAPICopyButton value={server + path} className="openapi-path-title" data-deprecated={operation.deprecated}>
15
+ <span className="openapi-path-server">{server}</span>
16
+ {formattedPath}
17
+ </OpenAPICopyButton>
16
18
  </div>);
17
19
  }
18
- function validateHttpMethod(method) {
19
- return ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'].includes(method);
20
- }
21
- // Format the path to highlight placeholders
20
+ /**
21
+ * Format the path by wrapping placeholders in <span> tags.
22
+ */
22
23
  function formatPath(path) {
23
24
  // Matches placeholders like {id}, {userId}, etc.
24
- var regex = /\{(\w+)\}/g;
25
+ var regex = /\{\s*(\w+)\s*\}|:\w+/g;
25
26
  var parts = [];
26
27
  var lastIndex = 0;
27
- // Replace placeholders with <em> tags
28
- path.replace(regex, function (match, key, offset) {
29
- parts.push(path.slice(lastIndex, offset));
30
- parts.push(<em key={key}>{"{".concat(key, "}")}</em>);
28
+ //Wrap the variables in <span> tags and maintain either {variable} or :variable
29
+ path.replace(regex, function (match, _, offset) {
30
+ if (offset > lastIndex) {
31
+ parts.push(path.slice(lastIndex, offset));
32
+ }
33
+ parts.push(<span key={offset} className="openapi-path-variable">
34
+ {match}
35
+ </span>);
31
36
  lastIndex = offset + match.length;
32
37
  return match;
33
38
  });
34
- // Push remaining text after the last placeholder
35
- parts.push(path.slice(lastIndex));
36
- // Join parts with separators wrapped in <span>
37
- var formattedPath = parts.reduce(function (acc, part, index) {
38
- if (typeof part === 'string' && index > 0 && part === '/') {
39
- acc.push(<span className="openapi-path-separator" key={"sep-".concat(index)}>
40
- /
41
- </span>);
39
+ if (lastIndex < path.length) {
40
+ parts.push(path.slice(lastIndex));
41
+ }
42
+ var formattedPath = parts.map(function (part, index) {
43
+ if (typeof part === 'string') {
44
+ return <span key={index}>{part}</span>;
42
45
  }
43
- acc.push(part);
44
- return acc;
45
- }, []);
46
- return <span>{formattedPath}</span>;
46
+ return part;
47
+ });
48
+ return formattedPath;
47
49
  }
@@ -1,3 +1,4 @@
1
+ import { Markdown } from './Markdown';
1
2
  import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
2
3
  import { StaticSection } from './StaticSection';
3
4
  import { generateSchemaExample } from './generateSchemaExample';
@@ -32,35 +33,31 @@ export function OpenAPIResponseExample(props) {
32
33
  }
33
34
  return Number(a) - Number(b);
34
35
  });
35
- var tabs = responses
36
- .map(function (_a) {
36
+ var tabs = responses.map(function (_a) {
37
37
  var key = _a[0], responseObject = _a[1];
38
38
  var description = resolveDescription(responseObject);
39
39
  if (checkIsReference(responseObject)) {
40
40
  return {
41
41
  key: key,
42
42
  label: key,
43
- description: description,
44
43
  body: (<OpenAPIExample example={getExampleFromReference(responseObject)} context={context} syntax="json"/>),
44
+ footer: description ? <Markdown source={description}/> : undefined,
45
45
  };
46
46
  }
47
47
  if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
48
48
  return {
49
49
  key: key,
50
50
  label: key,
51
- description: description,
52
51
  body: <OpenAPIEmptyResponseExample />,
52
+ footer: description ? <Markdown source={description}/> : undefined,
53
53
  };
54
54
  }
55
55
  return {
56
56
  key: key,
57
57
  label: key,
58
- description: resolveDescription(responseObject),
59
58
  body: <OpenAPIResponse context={context} content={responseObject.content}/>,
59
+ footer: description ? <Markdown source={description}/> : undefined,
60
60
  };
61
- })
62
- .filter(function (val) {
63
- return Boolean(val);
64
61
  });
65
62
  if (tabs.length === 0) {
66
63
  return null;
@@ -3,7 +3,7 @@ export type TabItem = {
3
3
  key: Key;
4
4
  label: string;
5
5
  body: React.ReactNode;
6
- description?: string;
6
+ footer?: React.ReactNode;
7
7
  };
8
8
  /**
9
9
  * The OpenAPI Tabs wrapper component.
@@ -2,7 +2,6 @@
2
2
  import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
4
4
  import { useEventCallback } from 'usehooks-ts';
5
- import { Markdown } from './Markdown';
6
5
  import { getOrCreateTabStoreByKey } from './useSyncedTabsGlobalState';
7
6
  var OpenAPITabsContext = createContext(null);
8
7
  function useOpenAPITabsContext() {
@@ -105,6 +104,14 @@ export function OpenAPITabsPanels() {
105
104
  var key = selectedTab.key.toString();
106
105
  return (<TabPanel key={key} id={key} className="openapi-tabs-panel">
107
106
  {selectedTab.body}
108
- {selectedTab.description ? (<Markdown source={selectedTab.description} className="openapi-tabs-footer"/>) : null}
107
+ {selectedTab.footer ? (<OpenAPITabsPanelFooter>{selectedTab.footer}</OpenAPITabsPanelFooter>) : null}
109
108
  </TabPanel>);
110
109
  }
110
+ /**
111
+ * The OpenAPI Tabs panel footer component.
112
+ * This component should be used as a child of the OpenAPITabs component.
113
+ */
114
+ function OpenAPITabsPanelFooter(props) {
115
+ var children = props.children;
116
+ return <div className="openapi-tabs-footer">{children}</div>;
117
+ }
@@ -16,10 +16,10 @@ export function ScalarApiButton(props) {
16
16
  (_b = (_a = controllerRef.current) === null || _a === void 0 ? void 0 : _a.openClient) === null || _b === void 0 ? void 0 : _b.call(_a);
17
17
  setIsOpen(true);
18
18
  }}>
19
- <svg xmlns="http://www.w3.org/2000/svg" width="10" height="12" fill="none">
19
+ Test it
20
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 12" fill="currentColor">
20
21
  <path stroke="currentColor" strokeWidth="1.5" d="M1 10.05V1.43c0-.2.2-.31.37-.22l7.26 4.08c.17.1.17.33.01.43l-7.26 4.54a.25.25 0 0 1-.38-.21Z"/>
21
22
  </svg>
22
- Test it
23
23
  </button>
24
24
 
25
25
  {isOpen &&