diff --git a/.changeset/lemon-actors-sing.md b/.changeset/lemon-actors-sing.md new file mode 100644 index 000000000..d21294786 --- /dev/null +++ b/.changeset/lemon-actors-sing.md @@ -0,0 +1,6 @@ +--- +"@gitbook/react-openapi": minor +"gitbook": minor +--- + +Design tweaks to code blocks and OpenAPI pages diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.css b/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.css index 6a5680179..71d75354e 100644 --- a/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.css +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.css @@ -24,7 +24,7 @@ } .highlight-line-number { - @apply text-sm text-right pr-3.5 rounded-l pl-2 sticky left-[-3px] bg-gradient-to-r from-80% from-tint to-transparent; + @apply text-sm text-right pr-3.5 rounded-l pl-2 sticky left-[-3px] bg-gradient-to-r from-80% from-tint-subtle contrast-more:from-tint-base theme-muted:from-tint-base [html.theme-bold.sidebar-filled_&]:from-tint-base to-transparent; @apply before:text-tint before:content-[counter(line)]; .highlight-line.highlighted > & { diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.tsx index f1f8cb8f6..d8f35d90b 100644 --- a/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.tsx +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlockRenderer.tsx @@ -39,7 +39,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer( >
{title ? ( -
+
{title}
) : null} @@ -50,8 +50,8 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer( />
diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/theme.css b/packages/gitbook/src/components/DocumentView/CodeBlock/theme.css
index 22ce3b60e..21c0fd579 100644
--- a/packages/gitbook/src/components/DocumentView/CodeBlock/theme.css
+++ b/packages/gitbook/src/components/DocumentView/CodeBlock/theme.css
@@ -1,31 +1,67 @@
 :root {
     --shiki-color-text: theme("colors.tint.11");
-    --shiki-token-constant: #0a6355;
-    --shiki-token-string: #8b6d32;
-    --shiki-token-comment: theme("colors.teal.700/.64");
-    --shiki-token-keyword: theme("colors.pomegranate.600");
-    --shiki-token-parameter: #0a3069;
-    --shiki-token-function: #8250df;
-    --shiki-token-string-expression: #6a4906;
-    --shiki-token-punctuation: theme("colors.pomegranate.700/.92");
-    --shiki-token-link: theme("colors.tint.12");
-    --shiki-token-inserted: #22863a;
-    --shiki-token-deleted: #b31d28;
-    --shiki-token-changed: #8250df;
+    --shiki-token-punctuation: theme("colors.tint.11");
+    --shiki-token-comment: theme("colors.neutral.9/.7");
+    --shiki-token-link: theme("colors.primary.10");
+
+    --shiki-token-constant: theme("colors.warning.10");
+    --shiki-token-string: theme("colors.success.10");
+    --shiki-token-string-expression: theme("colors.success.10");
+    --shiki-token-keyword: theme("colors.danger.10");
+    --shiki-token-parameter: theme("colors.warning.10");
+    --shiki-token-function: theme("colors.primary.10");
+
+    --shiki-token-inserted: theme("colors.success.10");
+    --shiki-token-deleted: theme("colors.danger.10");
+    --shiki-token-changed: theme("colors.tint.12");
+}
+
+@media (prefers-contrast: more) {
+    :root {
+        --shiki-color-text: theme("colors.tint.12");
+        --shiki-token-punctuation: theme("colors.tint.12");
+        --shiki-token-comment: theme("colors.neutral.11");
+        --shiki-token-link: theme("colors.primary.11");
+
+        --shiki-token-constant: theme("colors.warning.11");
+        --shiki-token-string: theme("colors.success.11");
+        --shiki-token-string-expression: theme("colors.success.11");
+        --shiki-token-keyword: theme("colors.danger.11");
+        --shiki-token-parameter: theme("colors.warning.11");
+        --shiki-token-function: theme("colors.primary.11");
+
+        --shiki-token-inserted: theme("colors.success.11");
+        --shiki-token-deleted: theme("colors.danger.11");
+        --shiki-token-changed: theme("colors.tint.12");
+    }
 }
 
 html.dark {
-    --shiki-color-text: theme("colors.tint.11");
-    --shiki-token-constant: #d19a66;
-    --shiki-token-string: theme("colors.pomegranate.300");
-    --shiki-token-comment: theme("colors.teal.300/.64");
-    --shiki-token-keyword: theme("colors.pomegranate.400");
-    --shiki-token-parameter: theme("colors.yellow.500");
-    --shiki-token-function: #56b6c2;
-    --shiki-token-string-expression: theme("colors.tint.11");
-    --shiki-token-punctuation: #acc6ee;
-    --shiki-token-link: theme("colors.pomegranate.400");
-    --shiki-token-inserted: #85e89d;
-    --shiki-token-deleted: #fdaeb7;
-    --shiki-token-changed: #56b6c2;
+    /* Override select colors to have more contrast */
+    --shiki-token-comment: theme("colors.neutral.9");
+
+    --shiki-token-constant: theme("colors.warning.11");
+    --shiki-token-string: theme("colors.success.11");
+    --shiki-token-string-expression: theme("colors.success.11");
+    --shiki-token-keyword: theme("colors.danger.11");
+    --shiki-token-parameter: theme("colors.warning.11");
+    --shiki-token-function: theme("colors.primary.11");
+}
+
+.code-monochrome {
+    --shiki-token-constant: theme("colors.tint.11");
+    --shiki-token-string: theme("colors.tint.12");
+    --shiki-token-string-expression: theme("colors.tint.12");
+    --shiki-token-keyword: theme("colors.primary.10");
+    --shiki-token-parameter: theme("colors.tint.9");
+    --shiki-token-function: theme("colors.primary.9");
+}
+
+html.dark.code-monochrome {
+    --shiki-token-constant: theme("colors.tint.11");
+    --shiki-token-string: theme("colors.tint.12");
+    --shiki-token-string-expression: theme("colors.tint.12");
+    --shiki-token-keyword: theme("colors.primary.11");
+    --shiki-token-parameter: theme("colors.tint.10");
+    --shiki-token-function: theme("colors.primary.10");
 }
diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css
index 39cccc4b3..7d08d97f5 100644
--- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css
+++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css
@@ -169,41 +169,18 @@
     @apply flex flex-col;
 }
 
-.openapi-schema {
+.openapi-schema,
+.openapi-disclosure {
     @apply py-2.5 flex flex-col gap-2;
 }
 
-.openapi-section-body .openapi-schema-properties {
-    @apply divide-y divide-tint-subtle;
-}
-
-.openapi-disclosure-group-panel > .openapi-schema-properties > *:first-child > .openapi-schema {
-    @apply pt-0;
-}
-
-.openapi-responsebody > .openapi-schema-properties > .openapi-schema:last-child {
-    @apply pb-0;
-}
-
-.openapi-responsebody > .openapi-schema-properties > .openapi-schema:only-child {
-    @apply py-0;
-}
-
 .openapi-schema-properties .openapi-schema:last-child {
     @apply border-b-0;
 }
 
-.openapi-schema-properties .openapi-schema-opened {
-    @apply pb-3;
-}
-
-.openapi-schema > .openapi-schema-properties {
-    @apply mt-3;
-}
-
 /* Schema Presentation */
 .openapi-schema-presentation {
-    @apply flex flex-col gap-1.5 font-normal;
+    @apply flex flex-col gap-1 font-normal;
 }
 
 .openapi-schema-properties:last-child {
@@ -221,7 +198,7 @@
 }
 
 .openapi-schema-propertyname {
-    @apply select-all font-mono font-normal text-tint-strong;
+    @apply select-all font-mono font-semibold text-tint-strong;
 }
 
 .openapi-schema-propertyname[data-deprecated="true"] {
@@ -244,6 +221,10 @@
     @apply text-success dark:text-success-subtle/9 text-[0.813rem] lowercase;
 }
 
+.openapi-schema-types {
+    @apply flex items-baseline flex-wrap gap-1;
+}
+
 .openapi-schema-type {
     @apply text-tint select-text text-[0.813rem] font-mono [word-spacing:-0.25rem];
 }
@@ -321,7 +302,7 @@
 .openapi-schema-pattern code,
 .openapi-schema-enum-value code,
 .openapi-schema-default code {
-    @apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
+    @apply py-px px-1 min-w-[1.625rem] text-tint-strong font-normal w-fit justify-center items-center ring-1 ring-inset ring-tint-subtle bg-tint rounded text-xs leading-[calc(max(1.20em,1.25rem))] before:!content-none after:!content-none;
 }
 
 /* Authentication */
@@ -373,28 +354,25 @@
 }
 
 .openapi-response-tab-content {
-    @apply overflow-hidden max-w-full flex items-baseline;
-    @apply text-left text-pretty relative leading-[1.125rem] text-tint !font-normal truncate select-text;
+    @apply flex items-baseline truncate grow shrink max-w-max basis-[60%] mr-auto;
+    @apply text-left text-pretty relative leading-tight text-tint select-text;
 }
 
 .openapi-response-description.openapi-markdown {
-    @apply text-left prose-sm text-[0.813rem] text-pretty h-auto relative leading-[1.125rem] text-tint !font-normal truncate select-text prose-strong:font-semibold prose-strong:text-inherit;
+    @apply text-left truncate prose-sm text-sm leading-tight text-tint select-text prose-strong:font-semibold prose-strong:text-inherit;
 }
 
-.openapi-response-description.openapi-markdown::-webkit-scrollbar {
-    display: none;
+.openapi-disclosure-group-trigger[aria-expanded="true"] .openapi-response-tab-content {
+    @apply basis-full;
 }
 
-.openapi-response-description p {
-    @apply truncate max-w-full inline pr-1;
-}
-
-.openapi-response-content-type {
-    @apply text-xs text-tint-8 ml-auto shrink-0;
+.openapi-disclosure-group-trigger[aria-expanded="true"]
+    .openapi-response-description.openapi-markdown {
+    @apply whitespace-normal;
 }
 
 .openapi-response-body {
-    @apply flex flex-col gap-3;
+    @apply flex flex-col;
 }
 
 /* Response Body and Headers */
@@ -411,16 +389,7 @@
     @apply px-3 py-1;
 }
 
-.openapi-responsebody-header-content,
-.openapi-responseheaders-header-content {
-    /* unstyled */
-}
-
 /* Code Sample */
-.openapi-codesample {
-    @apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
-}
-
 .openapi-codesample-header {
     @apply flex flex-row items-center;
 }
@@ -429,6 +398,12 @@
     @apply flex flex-row items-center gap-2.5;
 }
 
+.openapi-panel-heading,
+.openapi-codesample-header,
+.openapi-response-examples-header {
+    @apply border-b border-tint-subtle;
+}
+
 .openapi-response-examples-header .openapi-select > button {
     @apply max-w-full overflow-hidden shrink pl-0.5 py-0.5;
 }
@@ -503,8 +478,16 @@
 }
 
 /* Panel */
-.openapi-panel {
-    @apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
+.openapi-panel,
+.openapi-codesample,
+.openapi-response-examples {
+    @apply border rounded-md straight-corners:rounded-none bg-tint-subtle border-tint-subtle shadow-sm;
+}
+
+.openapi-panel pre,
+.openapi-codesample pre,
+.openapi-response-examples pre {
+    @apply bg-transparent border-none rounded-none shadow-none;
 }
 
 .openapi-panel-heading {
@@ -513,12 +496,11 @@
 
 .openapi-panel-body {
     @apply relative;
-    @apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
 }
 
 .openapi-panel-footer,
 .openapi-codesample-footer {
-    @apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint;
+    @apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint empty:hidden;
 }
 
 .openapi-panel-footer .openapi-markdown {
@@ -526,10 +508,6 @@
 }
 
 /* Example */
-.openapi-response-examples {
-    @apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
-}
-
 .openapi-response-examples-header {
     @apply flex flex-row items-center p-2.5;
 }
@@ -551,7 +529,6 @@
 .openapi-response-examples-panel,
 .openapi-codesample-panel {
     @apply flex-1 text-sm relative focus-visible:outline-none;
-    @apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
 }
 
 .openapi-example-empty {
@@ -703,15 +680,19 @@ body:has(.openapi-select-popover) {
 
 /* Disclosure group */
 .openapi-disclosure-group {
-    @apply border-tint-subtle border-b border-x overflow-auto last:rounded-b-md straight-corners:last:rounded-none first:rounded-t-md straight-corners:first:rounded-none first:border-t relative;
+    @apply border-tint-subtle transition-all border-b border-x overflow-auto last:rounded-b-md straight-corners:last:rounded-none first:rounded-t-md straight-corners:first:rounded-none first:border-t relative;
 }
 
-.openapi-disclosure-group-header {
-    @apply flex flex-row items-baseline justify-between gap-3 relative;
+.openapi-disclosure-group:has(.openapi-disclosure-group-trigger:hover) {
+    @apply bg-tint-subtle;
 }
 
 .openapi-disclosure-group-trigger {
-    @apply flex items-baseline transition-all hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1;
+    @apply flex w-full items-baseline gap-3 transition-all relative flex-1 p-3 -outline-offset-1;
+}
+
+.openapi-disclosure-group-label {
+    @apply flex flex-wrap items-baseline gap-x-3 gap-y-1 flex-1 truncate;
 }
 
 .openapi-disclosure-group-trigger:disabled {
@@ -722,10 +703,6 @@ body:has(.openapi-select-popover) {
     @apply invisible;
 }
 
-.openapi-disclosure-group-trigger[aria-expanded="true"] .openapi-response-description {
-    @apply whitespace-normal;
-}
-
 .openapi-disclosure-group-icon > svg {
     @apply size-3 text-tint-subtle transition-all duration-300;
 }
@@ -735,29 +712,20 @@ body:has(.openapi-select-popover) {
 }
 
 .openapi-disclosure-group-panel {
-    @apply p-3 pt-1 transition-all;
+    @apply px-3 transition-all;
 }
 
 .openapi-disclosure-group-trigger[aria-expanded="true"] > .openapi-disclosure-group-icon > svg {
     @apply rotate-90;
 }
 
-.openapi-disclosure-group:hover .openapi-disclosure-group-mediatype,
-.openapi-disclosure-group-mediatype:has(> .openapi-select[data-open="true"]) {
-    @apply opacity-11 visible flex;
-}
-
 .openapi-disclosure-group-mediatype {
-    @apply opacity-0 invisible text-xs transition-opacity duration-300 shrink-0 absolute right-2.5 top-2.5;
-}
-
-.openapi-disclosure-group-mediatype > span {
-    @apply px-1 bg-tint-6 text-tint-12 rounded-full straight-corners:rounded-md;
+    @apply text-[0.625rem] font-mono shrink-0 grow-0 text-tint-subtle contrast-more:text-tint;
 }
 
 /* Disclosure */
 .openapi-schemas-disclosure > .openapi-disclosure-trigger {
-    @apply flex items-center font-mono !w-full transition-all text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1;
+    @apply flex items-center font-mono transition-all text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1;
 }
 
 .openapi-schemas-disclosure > .openapi-disclosure-trigger,
@@ -765,51 +733,92 @@ body:has(.openapi-select-popover) {
     @apply straight-corners:!rounded-none;
 }
 
+.openapi-disclosure-panel {
+    @apply ml-1.5 pl-3 border-l border-tint-subtle;
+}
+
+.openapi-schema .openapi-schema-properties .openapi-schema {
+    @apply animate-fadeIn [animation-fill-mode:both];
+}
+
 .openapi-schemas-disclosure > .openapi-disclosure-trigger[aria-expanded="true"] > svg {
     @apply rotate-90;
 }
 
 .openapi-disclosure-trigger {
-    @apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl straight-corners:rounded border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
+    @apply flex flex-row justify-between flex-wrap relative items-start gap-2 text-left -mx-3 px-3 -my-2.5 py-2.5 pr-10;
 }
 
-.openapi-disclosure-trigger span {
-    @apply truncate;
+.openapi-disclosure {
+    @apply -mx-3 px-3 py-2.5 transition-all flex flex-col ring-tint-subtle;
 }
 
-.openapi-disclosure svg {
-    @apply size-3 shrink-0 transition-transform duration-300;
+.openapi-disclosure:not(
+        .openapi-disclosure-group .openapi-disclosure,
+        .openapi-schema-alternatives .openapi-disclosure
+    ) {
+    @apply rounded-xl;
+}
+
+.openapi-disclosure:has(> .openapi-disclosure-trigger:hover) {
+    @apply bg-tint-subtle;
+}
+
+.openapi-disclosure:has(> .openapi-disclosure-trigger:hover),
+.openapi-disclosure[data-expanded="true"] {
+    @apply ring-1 shadow-sm;
+}
+
+.openapi-disclosure[data-expanded="true"]:not(:first-child) {
+    @apply mt-2;
+}
+.openapi-disclosure[data-expanded="true"]:not(:last-child) {
+    @apply mb-2;
+}
+
+.openapi-disclosure-trigger-label {
+    @apply absolute right-3 px-2 h-5 justify-end shrink-0 ring-tint-subtle truncate text-tint duration-300 transition-all rounded straight-corners:rounded-none flex flex-row gap-1 items-center text-xs;
+}
+
+.openapi-disclosure-trigger-label span {
+    @apply hidden;
+}
+
+.openapi-disclosure-trigger-label svg {
+    @apply size-3 shrink-0 transition-transform duration-300 text-tint-subtle;
+}
+
+.openapi-disclosure-trigger:hover > .openapi-disclosure-trigger-label,
+.openapi-disclosure-trigger[aria-expanded="true"] > .openapi-disclosure-trigger-label {
+    @apply shadow ring-1 bg-tint-base;
+}
+
+.openapi-disclosure-trigger:hover > .openapi-disclosure-trigger-label span,
+.openapi-disclosure-trigger[aria-expanded="true"] > .openapi-disclosure-trigger-label span {
+    @apply block animate-fadeIn;
+}
+
+@media (hover: none) {
+    /* Make button label always visible on non-hover devices like phones */
+    .openapi-disclosure-trigger-label {
+        @apply relative ring-1 bg-tint-base;
+    }
+    .openapi-disclosure-trigger-label span {
+        @apply block;
+    }
+    .openapi-disclosure-trigger {
+        @apply pr-3;
+    }
 }
 
 .openapi-disclosure-trigger[aria-expanded="true"] svg {
     @apply rotate-45;
 }
 
-.openapi-disclosure-trigger[aria-expanded="true"] {
-    @apply w-full rounded-lg border-b rounded-b-none straight-corners:rounded-b-none;
-}
-
 .openapi-disclosure-trigger[aria-expanded="false"] {
     @apply w-auto;
 }
 
-.openapi-disclosure-panel[aria-hidden="false"] {
-    @apply border-b border-x border-tint-subtle rounded-b-lg straight-corners:rounded-b;
-}
-
-.openapi-disclosure-panel .openapi-schema {
-    @apply p-2.5;
-}
-
-.openapi-disclosure .openapi-schema-properties .openapi-schema:only-child,
-.openapi-disclosure .openapi-schema-properties .openapi-schema:only-child .openapi-schema-name {
-    @apply !m-0;
-}
-
-.openapi-disclosure .openapi-schema-properties .openapi-schema-enum {
-    @apply pt-0 mt-0;
-}
-
 .openapi-section-body.openapi-schema.openapi-schema-root {
     @apply space-y-2.5;
 }
@@ -819,6 +828,16 @@ body:has(.openapi-select-popover) {
     @apply p-2.5;
 }
 
+.openapi-schema-alternatives {
+    @apply ml-1.5 pl-3 border-l border-tint-subtle;
+}
+.openapi-schema-alternative {
+    @apply relative;
+}
+.openapi-schema-alternative-separator {
+    @apply p-0.5 tracking-wide leading-none uppercase text-[0.625rem] text-tint-subtle whitespace-nowrap absolute -left-3 -bottom-2.5 -translate-x-1/2 z-10 bg-tint-base border-y border-tint-subtle -rotate-6;
+}
+
 .openapi-tooltip {
     @apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md straight-corners:rounded-none font-medium px-1.5 py-0.5 shadow-sm text-[13px];
 }
@@ -865,11 +884,11 @@ body:has(.openapi-select-popover) {
 }
 
 @keyframes popover-leave {
-    0% {
+    from {
         opacity: 1;
         transform: translateY(0) scale(1);
     }
-    100% {
+    to {
         opacity: 0;
         transform: translateY(4px) scale(0.95);
     }
diff --git a/packages/react-openapi/src/OpenAPIDisclosure.tsx b/packages/react-openapi/src/OpenAPIDisclosure.tsx
index d84b4523b..56e663b44 100644
--- a/packages/react-openapi/src/OpenAPIDisclosure.tsx
+++ b/packages/react-openapi/src/OpenAPIDisclosure.tsx
@@ -9,11 +9,12 @@ import { Button, Disclosure, DisclosurePanel } from 'react-aria-components';
  */
 export function OpenAPIDisclosure(props: {
     icon: React.ReactNode;
+    header: React.ReactNode;
     children: React.ReactNode;
     label: string | ((isExpanded: boolean) => string);
     className?: string;
 }): React.JSX.Element {
-    const { icon, children, label, className } = props;
+    const { icon, header, label, children, className } = props;
     const [isExpanded, setIsExpanded] = useState(false);
 
     return (
@@ -31,8 +32,11 @@ export function OpenAPIDisclosure(props: {
                         : 'none',
                 })}
             >
-                {icon}
-                {typeof label === 'function' ? label(isExpanded) : label}
+                {header}
+                
+ {typeof label === 'function' ? label(isExpanded) : label} + {icon} +
{isExpanded ? children : null} diff --git a/packages/react-openapi/src/OpenAPIDisclosureGroup.tsx b/packages/react-openapi/src/OpenAPIDisclosureGroup.tsx index 7f2a428d5..27584cfc6 100644 --- a/packages/react-openapi/src/OpenAPIDisclosureGroup.tsx +++ b/packages/react-openapi/src/OpenAPIDisclosureGroup.tsx @@ -96,56 +96,57 @@ function DisclosureItem(props: { return (
-
- - {group.tabs ? ( -
e.stopPropagation()} - > - {group.tabs?.length > 1 ? ( - { - state.expand(); - }} - items={group.tabs} - placement="bottom end" - > - {group.tabs.map((tab) => ( - - {tab.label} - - ))} - - ) : group.tabs[0]?.label ? ( - {group.tabs[0].label} - ) : null} -
- ) : null} -
+ + {group.tabs ? ( +
e.stopPropagation()} + > + {group.tabs?.length > 1 ? ( + { + state.expand(); + }} + items={group.tabs} + placement="bottom end" + > + {group.tabs.map((tab) => ( + + {tab.label} + + ))} + + ) : group.tabs[0]?.label ? ( + {group.tabs[0].label} + ) : null} +
+ ) : null} +
+ {state.isExpanded && selectedTab && (
diff --git a/packages/react-openapi/src/OpenAPIResponse.tsx b/packages/react-openapi/src/OpenAPIResponse.tsx index de744b758..3c005ec29 100644 --- a/packages/react-openapi/src/OpenAPIResponse.tsx +++ b/packages/react-openapi/src/OpenAPIResponse.tsx @@ -1,7 +1,9 @@ import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import { OpenAPIDisclosure } from './OpenAPIDisclosure'; +import { OpenAPISchemaPresentation } from './OpenAPISchema'; import { OpenAPISchemaProperties } from './OpenAPISchemaServer'; import type { OpenAPIClientContext } from './context'; +import { tString } from './translate'; import { parameterToProperty, resolveDescription } from './utils'; /** @@ -27,7 +29,31 @@ export function OpenAPIResponse(props: { return (
{headers.length > 0 ? ( - + + } + icon={context.icons.plus} + label={(isExpanded) => + tString( + context.translation, + isExpanded ? 'hide' : 'show', + tString( + context.translation, + headers.length === 1 ? 'header' : 'headers' + ) + ) + } + > parameterToProperty({ name, ...header }) @@ -40,7 +66,13 @@ export function OpenAPIResponse(props: {
diff --git a/packages/react-openapi/src/OpenAPISchema.tsx b/packages/react-openapi/src/OpenAPISchema.tsx index d268b17cf..8f4c0cd33 100644 --- a/packages/react-openapi/src/OpenAPISchema.tsx +++ b/packages/react-openapi/src/OpenAPISchema.tsx @@ -4,6 +4,7 @@ import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser'; import { useId } from 'react'; +import type { ComponentPropsWithoutRef } from 'react'; import clsx from 'clsx'; import { Markdown } from './Markdown'; @@ -19,73 +20,98 @@ import { checkIsReference, resolveDescription, resolveFirstExample } from './uti type CircularRefsIds = Map; export interface OpenAPISchemaPropertyEntry { - propertyName?: string | undefined; - required?: boolean | undefined; + propertyName?: string; + required?: boolean | null; schema: OpenAPIV3.SchemaObject; } /** * Render a property of an OpenAPI schema. */ -function OpenAPISchemaProperty(props: { - property: OpenAPISchemaPropertyEntry; - context: OpenAPIClientContext; - circularRefs: CircularRefsIds; - className?: string; -}) { - const { circularRefs: parentCircularRefs, context, className, property } = props; +function OpenAPISchemaProperty( + props: { + property: OpenAPISchemaPropertyEntry; + context: OpenAPIClientContext; + circularRefs: CircularRefsIds; + className?: string; + } & Omit, 'property' | 'context' | 'circularRefs' | 'className'> +) { + const { circularRefs: parentCircularRefs, context, className, property, ...rest } = props; const { schema } = property; const id = useId(); - return ( -
- - {(() => { - const circularRefId = parentCircularRefs.get(schema); - // Avoid recursing infinitely, and instead render a link to the parent schema - if (circularRefId) { - return ; - } + const circularRefId = parentCircularRefs.get(schema); + // Avoid recursing infinitely, and instead render a link to the parent schema + if (circularRefId) { + return ; + } - const circularRefs = new Map(parentCircularRefs); - circularRefs.set(schema, id); + const circularRefs = new Map(parentCircularRefs); + circularRefs.set(schema, id); - const properties = getSchemaProperties(schema); - if (properties?.length) { - return ( - - getDisclosureLabel({ schema, isExpanded, context }) - } - > - ; + const content = (() => { + if (properties?.length) { + return ( + + ); + } + + if (alternatives) { + return ( +
+ {alternatives.map((alternativeSchema, index) => ( +
+ - - ); - } + {index < alternatives.length - 1 ? ( + + {(schema.anyOf || schema.oneOf) && + tString(context.translation, 'or')} + {schema.allOf && tString(context.translation, 'and')} + + ) : null} +
+ ))} +
+ ); + } - const ancestors = new Set(circularRefs.keys()); - const alternatives = getSchemaAlternatives(schema, ancestors); + return null; + })(); - if (alternatives) { - return alternatives.map((schema, index) => ( - - )); - } + if (properties?.length) { + return ( + getDisclosureLabel({ schema, isExpanded, context })} + {...rest} + > + {content} + + ); + } - return null; - })()} + return ( +
+ {header} + {content}
); } @@ -115,6 +141,7 @@ function OpenAPISchemaProperties(props: { circularRefs={circularRefs} property={property} context={context} + style={{ animationDelay: `${index * 0.02}s` }} /> ); })} @@ -205,34 +232,26 @@ function OpenAPISchemaAlternative(props: { context: OpenAPIClientContext; }) { const { schema, circularRefs, context } = props; - - const description = resolveDescription(schema); const properties = getSchemaProperties(schema); - return ( - <> - {description ? ( - - ) : null} - getDisclosureLabel({ schema, isExpanded, context })} - > - {properties?.length ? ( - - ) : ( - - )} - - + return properties?.length ? ( + } + label={(isExpanded) => getDisclosureLabel({ schema, isExpanded, context })} + > + + + ) : ( + ); } @@ -293,7 +312,7 @@ function OpenAPISchemaEnum(props: { return ( - Available options:{' '} + {tString(context.translation, 'possible_values')}:{' '} {enumValues.map((item, index) => ( ) : null} - {required ? ( + {required === null ? null : required ? ( {t(context.translation, 'required')} diff --git a/packages/react-openapi/src/StaticSection.tsx b/packages/react-openapi/src/StaticSection.tsx index ffb3a2bba..b79b20e61 100644 --- a/packages/react-openapi/src/StaticSection.tsx +++ b/packages/react-openapi/src/StaticSection.tsx @@ -11,7 +11,7 @@ export function SectionHeader(props: ComponentPropsWithoutRef<'div'>) { {...props} className={clsx( 'openapi-section-header', - props.className && `${props.className}-header` + props.className ? `${props.className}-header` : undefined )} /> ); diff --git a/packages/react-openapi/src/schemas/OpenAPISchemas.tsx b/packages/react-openapi/src/schemas/OpenAPISchemas.tsx index 2283245fc..eddb893de 100644 --- a/packages/react-openapi/src/schemas/OpenAPISchemas.tsx +++ b/packages/react-openapi/src/schemas/OpenAPISchemas.tsx @@ -9,7 +9,7 @@ import { getOpenAPIClientContext, resolveOpenAPIContext, } from '../context'; -import { t } from '../translate'; +import { t, tString } from '../translate'; import { getExampleFromSchema } from '../util/example'; /** @@ -89,7 +89,10 @@ export function OpenAPISchemas(props: { className="openapi-schemas-disclosure" key={name} icon={context.icons.chevronRight} - label={name} + header={name} + label={(isExpanded) => + tString(context.translation, isExpanded ? 'hide' : 'show') + } >
diff --git a/packages/react-openapi/src/translations/de.ts b/packages/react-openapi/src/translations/de.ts index 8fc792e8e..8a5236c58 100644 --- a/packages/react-openapi/src/translations/de.ts +++ b/packages/react-openapi/src/translations/de.ts @@ -18,9 +18,11 @@ export const de = { nullable: 'Nullfähig', body: 'Rumpf', payload: 'Nutzlast', - headers: 'Kopfzeilen', + headers: 'Header', + header: 'Header', authorizations: 'Autorisierungen', responses: 'Antworten', + response: 'Antwort', path_parameters: 'Pfadparameter', query_parameters: 'Abfrageparameter', header_parameters: 'Header-Parameter', @@ -30,8 +32,11 @@ export const de = { success: 'Erfolg', redirect: 'Umleitung', error: 'Fehler', - show: 'Anzeigen', - hide: 'Verstecken', + show: 'Zeige ${1}', + hide: 'Verstecke ${1}', available_items: 'Verfügbare Elemente', - child_attributes: 'Unterattribute', + properties: 'Eigenschaften', + or: 'oder', + and: 'und', + possible_values: 'Mögliche Werte', }; diff --git a/packages/react-openapi/src/translations/en.ts b/packages/react-openapi/src/translations/en.ts index 61f45e9e3..4276595d5 100644 --- a/packages/react-openapi/src/translations/en.ts +++ b/packages/react-openapi/src/translations/en.ts @@ -19,8 +19,10 @@ export const en = { body: 'Body', payload: 'Payload', headers: 'Headers', + header: 'Header', authorizations: 'Authorizations', responses: 'Responses', + response: 'Response', path_parameters: 'Path parameters', query_parameters: 'Query parameters', header_parameters: 'Header parameters', @@ -30,8 +32,11 @@ export const en = { success: 'Success', redirect: 'Redirect', error: 'Error', - show: 'Show', - hide: 'Hide', + show: 'Show ${1}', + hide: 'Hide ${1}', available_items: 'Available items', - child_attributes: 'Child attributes', + possible_values: 'Possible values', + properties: 'Properties', + or: 'or', + and: 'and', }; diff --git a/packages/react-openapi/src/translations/es.ts b/packages/react-openapi/src/translations/es.ts index 5faa36c67..f5ac905f6 100644 --- a/packages/react-openapi/src/translations/es.ts +++ b/packages/react-openapi/src/translations/es.ts @@ -18,9 +18,11 @@ export const es = { nullable: 'Nulo', body: 'Cuerpo', payload: 'Caga útil', - headers: 'Encabezados', + headers: 'Headers', + header: 'Header', authorizations: 'Autorizaciones', responses: 'Respuestas', + response: 'Respuesta', path_parameters: 'Parámetros de ruta', query_parameters: 'Parámetros de consulta', header_parameters: 'Parámetros de encabezado', @@ -30,8 +32,11 @@ export const es = { success: 'Éxito', redirect: 'Redirección', error: 'Error', - show: 'Mostrar', - hide: 'Ocultar', + show: 'Mostrar ${1}', + hide: 'Ocultar ${1}', available_items: 'Elementos disponibles', - child_attributes: 'Atributos secundarios', + properties: 'Propiedades', + or: 'o', + and: 'y', + possible_values: 'Valores posibles', }; diff --git a/packages/react-openapi/src/translations/fr.ts b/packages/react-openapi/src/translations/fr.ts index ccaf05b3d..b2e563c81 100644 --- a/packages/react-openapi/src/translations/fr.ts +++ b/packages/react-openapi/src/translations/fr.ts @@ -18,20 +18,25 @@ export const fr = { nullable: 'Nullable', body: 'Corps', payload: 'Charge utile', - headers: 'En-têtes', + headers: 'Headers', + header: 'Header', authorizations: 'Autorisations', responses: 'Réponses', + response: 'Réponse', path_parameters: 'Paramètres de chemin', query_parameters: 'Paramètres de requête', - header_parameters: 'Paramètres d’en-tête', + header_parameters: "Paramètres d'en-tête", attributes: 'Attributs', test_it: 'Tester', information: 'Information', success: 'Succès', redirect: 'Redirection', error: 'Erreur', - show: 'Afficher', - hide: 'Masquer', + show: 'Afficher ${1}', + hide: 'Masquer ${1}', available_items: 'Éléments disponibles', - child_attributes: 'Attributs enfants', + properties: 'Propriétés', + or: 'ou', + and: 'et', + possible_values: 'Valeurs possibles', }; diff --git a/packages/react-openapi/src/translations/ja.ts b/packages/react-openapi/src/translations/ja.ts index 55b5b2a0a..e393dd6cb 100644 --- a/packages/react-openapi/src/translations/ja.ts +++ b/packages/react-openapi/src/translations/ja.ts @@ -19,8 +19,10 @@ export const ja = { body: '本文', payload: 'ペイロード', headers: 'ヘッダー', + header: 'ヘッダー', authorizations: '認可', responses: 'レスポンス', + response: 'レスポンス', path_parameters: 'パスパラメータ', query_parameters: 'クエリパラメータ', header_parameters: 'ヘッダーパラメータ', @@ -30,8 +32,11 @@ export const ja = { success: '成功', redirect: 'リダイレクト', error: 'エラー', - show: '表示', - hide: '非表示', + show: '${1}を表示', + hide: '${1}を非表示', available_items: '利用可能なアイテム', - child_attributes: '子属性', + properties: 'プロパティ', + or: 'または', + and: 'かつ', + possible_values: '可能な値', }; diff --git a/packages/react-openapi/src/translations/nl.ts b/packages/react-openapi/src/translations/nl.ts index 5186580c7..34867ccf1 100644 --- a/packages/react-openapi/src/translations/nl.ts +++ b/packages/react-openapi/src/translations/nl.ts @@ -19,8 +19,10 @@ export const nl = { body: 'Body', payload: 'Payload', headers: 'Headers', + header: 'Header', authorizations: 'Autorisaties', responses: 'Reacties', + response: 'Reactie', path_parameters: 'Padparameters', query_parameters: 'Queryparameters', header_parameters: 'Headerparameters', @@ -30,8 +32,11 @@ export const nl = { success: 'Succes', redirect: 'Omleiding', error: 'Fout', - show: 'Toon', - hide: 'Verbergen', + show: 'Toon ${1}', + hide: 'Verberg ${1}', available_items: 'Beschikbare items', - child_attributes: 'Kindattributen', + properties: 'Eigenschappen', + or: 'of', + and: 'en', + possible_values: 'Mogelijke waarden', }; diff --git a/packages/react-openapi/src/translations/no.ts b/packages/react-openapi/src/translations/no.ts index a4efc3cfe..270117793 100644 --- a/packages/react-openapi/src/translations/no.ts +++ b/packages/react-openapi/src/translations/no.ts @@ -18,9 +18,11 @@ export const no = { nullable: 'Kan være null', body: 'Brødtekst', payload: 'Nyttelast', - headers: 'Overskrifter', + headers: 'Headers', + header: 'Header', authorizations: 'Autorisasjoner', responses: 'Responser', + response: 'Respons', path_parameters: 'Sti-parametere', query_parameters: 'Forespørselsparametere', header_parameters: 'Header-parametere', @@ -30,8 +32,11 @@ export const no = { success: 'Suksess', redirect: 'Viderekobling', error: 'Feil', - show: 'Vis', - hide: 'Skjul', + show: 'Vis ${1}', + hide: 'Skjul ${1}', available_items: 'Tilgjengelige elementer', - child_attributes: 'Barneattributter', + properties: 'Egenskaper', + or: 'eller', + and: 'og', + possible_values: 'Mulige verdier', }; diff --git a/packages/react-openapi/src/translations/pt-br.ts b/packages/react-openapi/src/translations/pt-br.ts index 7c1f86a7c..00e8ab3c2 100644 --- a/packages/react-openapi/src/translations/pt-br.ts +++ b/packages/react-openapi/src/translations/pt-br.ts @@ -18,9 +18,11 @@ export const pt_br = { nullable: 'Nulo', body: 'Corpo', payload: 'Carga útil', - headers: 'Cabeçalhos', + headers: 'Headers', + header: 'Header', authorizations: 'Autorizações', responses: 'Respostas', + response: 'Resposta', path_parameters: 'Parâmetros de rota', query_parameters: 'Parâmetros de consulta', header_parameters: 'Parâmetros de cabeçalho', @@ -30,8 +32,11 @@ export const pt_br = { success: 'Sucesso', redirect: 'Redirecionamento', error: 'Erro', - show: 'Mostrar', - hide: 'Ocultar', + show: 'Mostrar ${1}', + hide: 'Ocultar ${1}', available_items: 'Itens disponíveis', - child_attributes: 'Atributos filhos', + properties: 'Propriedades', + or: 'ou', + and: 'e', + possible_values: 'Valores possíveis', }; diff --git a/packages/react-openapi/src/translations/zh.ts b/packages/react-openapi/src/translations/zh.ts index 414043fd3..f8299d199 100644 --- a/packages/react-openapi/src/translations/zh.ts +++ b/packages/react-openapi/src/translations/zh.ts @@ -18,9 +18,11 @@ export const zh = { nullable: '可为 null', body: '请求体', payload: '有效载荷', - headers: '头部信息', + headers: '头字段', + header: '头部', authorizations: '授权', responses: '响应', + response: '响应', path_parameters: '路径参数', query_parameters: '查询参数', header_parameters: '头参数', @@ -30,8 +32,11 @@ export const zh = { success: '成功', redirect: '重定向', error: '错误', - show: '显示', - hide: '隐藏', + show: '显示${1}', + hide: '隐藏${1}', available_items: '可用项', - child_attributes: '子属性', + properties: '属性', + or: '或', + and: '和', + possible_values: '可能的值', };