zudoku 0.3.1-dev.2 → 0.3.1-dev.20

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 (125) hide show
  1. package/LICENSE.md +21 -0
  2. package/dist/lib/authentication/authentication.d.ts +1 -0
  3. package/dist/lib/authentication/components/CallbackHandler.d.ts +3 -0
  4. package/dist/lib/authentication/components/CallbackHandler.js +34 -0
  5. package/dist/lib/authentication/components/CallbackHandler.js.map +1 -0
  6. package/dist/lib/authentication/providers/auth0.js +20 -1
  7. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  8. package/dist/lib/authentication/providers/openid.d.ts +3 -10
  9. package/dist/lib/authentication/providers/openid.js +46 -31
  10. package/dist/lib/authentication/providers/openid.js.map +1 -1
  11. package/dist/lib/components/Header.js +2 -4
  12. package/dist/lib/components/Header.js.map +1 -1
  13. package/dist/lib/components/Layout.js +5 -1
  14. package/dist/lib/components/Layout.js.map +1 -1
  15. package/dist/lib/components/context/ZudokuProvider.js +1 -3
  16. package/dist/lib/components/context/ZudokuProvider.js.map +1 -1
  17. package/dist/lib/core/DevPortalContext.d.ts +1 -4
  18. package/dist/lib/core/DevPortalContext.js +2 -2
  19. package/dist/lib/core/DevPortalContext.js.map +1 -1
  20. package/dist/lib/core/plugins.d.ts +2 -4
  21. package/dist/lib/core/plugins.js.map +1 -1
  22. package/dist/lib/plugins/openapi/OperationList.js +3 -4
  23. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  24. package/dist/lib/plugins/openapi/OperationListItem.js +2 -3
  25. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  26. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  27. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  28. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +2 -2
  29. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js.map +1 -1
  30. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.d.ts +9 -0
  31. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.js +14 -0
  32. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.js.map +1 -0
  33. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.d.ts +6 -0
  34. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.js +17 -0
  35. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.js.map +1 -0
  36. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.d.ts +7 -0
  37. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js +10 -0
  38. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js.map +1 -0
  39. package/dist/lib/plugins/openapi/schema/SchemaComponents.d.ts +3 -3
  40. package/dist/lib/plugins/openapi/schema/SchemaComponents.js +15 -15
  41. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
  42. package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
  43. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  44. package/dist/lib/plugins/openapi/schema/utils.d.ts +6 -0
  45. package/dist/lib/plugins/openapi/schema/utils.js +5 -0
  46. package/dist/lib/plugins/openapi/schema/utils.js.map +1 -1
  47. package/lib/{AuthenticationPlugin-CH5NSVOu.js → AuthenticationPlugin-owbEUimP.js} +3 -3
  48. package/lib/{AuthenticationPlugin-CH5NSVOu.js.map → AuthenticationPlugin-owbEUimP.js.map} +1 -1
  49. package/lib/{CategoryHeading-z15xh7Jb.js → CategoryHeading-DnPprxtD.js} +2 -2
  50. package/lib/{CategoryHeading-z15xh7Jb.js.map → CategoryHeading-DnPprxtD.js.map} +1 -1
  51. package/lib/{Combination-DTfV-c98.js → Combination-DruV0zX_.js} +3 -3
  52. package/lib/{Combination-DTfV-c98.js.map → Combination-DruV0zX_.js.map} +1 -1
  53. package/lib/ErrorPage-PUg985n_.js +18 -0
  54. package/lib/ErrorPage-PUg985n_.js.map +1 -0
  55. package/lib/{Input-DB9VROFR.js → Input-CBfi9Yjc.js} +5 -4
  56. package/lib/{Input-DB9VROFR.js.map → Input-CBfi9Yjc.js.map} +1 -1
  57. package/lib/{Markdown-CEccPMI_.js → Markdown-Chb9VIBv.js} +2 -2
  58. package/lib/{Markdown-CEccPMI_.js.map → Markdown-Chb9VIBv.js.map} +1 -1
  59. package/lib/{MdxPage-CnqOoqvp.js → MdxPage-CIBHMwTd.js} +5 -5
  60. package/lib/{MdxPage-CnqOoqvp.js.map → MdxPage-CIBHMwTd.js.map} +1 -1
  61. package/lib/OperationList-CZiSz5JH.js +590 -0
  62. package/lib/OperationList-CZiSz5JH.js.map +1 -0
  63. package/lib/{Route-DfAFiR7v.js → Route-Cle-r-bq.js} +4 -4
  64. package/lib/{Route-DfAFiR7v.js.map → Route-Cle-r-bq.js.map} +1 -1
  65. package/lib/{Spinner-BT_AYFrA.js → SidebarBadge-Ba0PhibA.js} +66 -76
  66. package/lib/SidebarBadge-Ba0PhibA.js.map +1 -0
  67. package/lib/{SlotletProvider-ByLSCZQa.js → SlotletProvider-Dq80og6-.js} +4 -4
  68. package/lib/{SlotletProvider-ByLSCZQa.js.map → SlotletProvider-Dq80og6-.js.map} +1 -1
  69. package/lib/Spinner-CvXZ7QK4.js +15 -0
  70. package/lib/Spinner-CvXZ7QK4.js.map +1 -0
  71. package/lib/{ZudokuContext-BIZ8zHbZ.js → ZudokuContext-BQ45UjcB.js} +2 -2
  72. package/lib/{ZudokuContext-BIZ8zHbZ.js.map → ZudokuContext-BQ45UjcB.js.map} +1 -1
  73. package/lib/{index-D-9zqIOh.js → index-Br1MQPxy.js} +595 -587
  74. package/lib/index-Br1MQPxy.js.map +1 -0
  75. package/lib/{index-Dz4LyXZI.js → index-DCJ9wEIV.js} +3 -3
  76. package/lib/{index-Dz4LyXZI.js.map → index-DCJ9wEIV.js.map} +1 -1
  77. package/lib/{index-7kcHaXD6.js → index-Yjb2PyPF.js} +4 -4
  78. package/lib/{index-7kcHaXD6.js.map → index-Yjb2PyPF.js.map} +1 -1
  79. package/lib/{urql-DrBfkb92.js → urql-YhcsXYy8.js} +2 -2
  80. package/lib/urql-YhcsXYy8.js.map +1 -0
  81. package/lib/{utils-Bh4upQ0e.js → utils-pDHePxa0.js} +3 -3
  82. package/lib/{utils-Bh4upQ0e.js.map → utils-pDHePxa0.js.map} +1 -1
  83. package/lib/zudoku.auth-auth0.js +31 -17
  84. package/lib/zudoku.auth-auth0.js.map +1 -1
  85. package/lib/zudoku.auth-clerk.js +1 -1
  86. package/lib/zudoku.auth-openid.js +491 -421
  87. package/lib/zudoku.auth-openid.js.map +1 -1
  88. package/lib/zudoku.components.js +434 -443
  89. package/lib/zudoku.components.js.map +1 -1
  90. package/lib/zudoku.openapi-worker.js +1 -1
  91. package/lib/zudoku.plugin-api-keys.js +6 -6
  92. package/lib/zudoku.plugin-custom-page.js +1 -1
  93. package/lib/zudoku.plugin-markdown.js +1 -1
  94. package/lib/zudoku.plugin-openapi.js +8 -7
  95. package/lib/zudoku.plugin-openapi.js.map +1 -1
  96. package/package.json +80 -100
  97. package/src/app/main.css +26 -1
  98. package/src/lib/authentication/authentication.ts +1 -0
  99. package/src/lib/authentication/components/CallbackHandler.tsx +59 -0
  100. package/src/lib/authentication/providers/auth0.tsx +28 -2
  101. package/src/lib/authentication/providers/openid.tsx +50 -37
  102. package/src/lib/components/Header.tsx +3 -10
  103. package/src/lib/components/Layout.tsx +6 -1
  104. package/src/lib/components/context/ZudokuProvider.tsx +1 -4
  105. package/src/lib/core/DevPortalContext.ts +2 -7
  106. package/src/lib/core/plugins.ts +1 -2
  107. package/src/lib/plugins/openapi/OperationList.tsx +3 -7
  108. package/src/lib/plugins/openapi/OperationListItem.tsx +3 -4
  109. package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -1
  110. package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +27 -5
  111. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.tsx +47 -0
  112. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.tsx +54 -0
  113. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.tsx +30 -0
  114. package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +41 -41
  115. package/src/lib/plugins/openapi/schema/SchemaView.tsx +4 -4
  116. package/src/lib/plugins/openapi/schema/utils.ts +8 -0
  117. package/dist/lib/plugins/openapi/util/prose.d.ts +0 -1
  118. package/dist/lib/plugins/openapi/util/prose.js +0 -4
  119. package/dist/lib/plugins/openapi/util/prose.js.map +0 -1
  120. package/lib/OperationList-Cxiw2Z-v.js +0 -457
  121. package/lib/OperationList-Cxiw2Z-v.js.map +0 -1
  122. package/lib/Spinner-BT_AYFrA.js.map +0 -1
  123. package/lib/index-D-9zqIOh.js.map +0 -1
  124. package/lib/urql-DrBfkb92.js.map +0 -1
  125. package/src/lib/plugins/openapi/util/prose.ts +0 -7
@@ -1,6 +1,5 @@
1
1
  import { MoonStarIcon, SunIcon } from "lucide-react";
2
2
  import { memo } from "react";
3
-
4
3
  import { Link, useLocation } from "react-router-dom";
5
4
  import { useAuth } from "../authentication/hook.js";
6
5
  import { isProfileMenuPlugin, ProfileNavigationItem } from "../core/plugins.js";
@@ -45,6 +44,7 @@ const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
45
44
  };
46
45
 
47
46
  export const Header = memo(function HeaderInner() {
47
+ const auth = useAuth();
48
48
  const [isDark, toggleTheme] = useTheme();
49
49
  const { isAuthenticated, profile, isAuthEnabled } = useAuth();
50
50
  const { pathname } = useLocation();
@@ -97,15 +97,8 @@ export const Header = memo(function HeaderInner() {
97
97
  <div className="items-center justify-self-end text-sm hidden lg:flex gap-2">
98
98
  <Slotlet name="head-navigation-start" />
99
99
  {isAuthEnabled && !isAuthenticated ? (
100
- <Button variant="ghost" asChild>
101
- <Link
102
- to={{
103
- pathname: "/signin",
104
- search: `?redirect=${encodeURIComponent(pathname)}`,
105
- }}
106
- >
107
- Login
108
- </Link>
100
+ <Button variant="ghost" onClick={() => auth.login()}>
101
+ Login
109
102
  </Button>
110
103
  ) : (
111
104
  accountItems.length > 0 && (
@@ -14,13 +14,18 @@ import { Spinner } from "./Spinner.js";
14
14
  export const Layout = ({ children }: { children?: ReactNode }) => {
15
15
  const location = useLocation();
16
16
  const { setActiveAnchor } = useViewportAnchor();
17
- const { meta } = useZudoku();
17
+ const { meta, authentication } = useZudoku();
18
18
 
19
19
  useScrollToAnchor();
20
20
  useScrollToTop();
21
21
 
22
22
  const previousLocationPath = useRef(location.pathname);
23
23
 
24
+ useEffect(() => {
25
+ // Initialize the authentication plugin
26
+ authentication?.pageLoad ? authentication.pageLoad() : null;
27
+ }, [authentication]);
28
+
24
29
  useEffect(() => {
25
30
  // always reset on location change
26
31
  if (location.pathname !== previousLocationPath.current) {
@@ -1,6 +1,5 @@
1
1
  import { useSuspenseQuery } from "@tanstack/react-query";
2
2
  import type { PropsWithChildren } from "react";
3
- import { useNavigate } from "react-router-dom";
4
3
  import { DevPortalContext } from "../../core/DevPortalContext.js";
5
4
  import { ZudokuReactContext } from "./ZudokuContext.js";
6
5
 
@@ -8,11 +7,9 @@ export const ZudokuProvider = ({
8
7
  children,
9
8
  context,
10
9
  }: PropsWithChildren<{ context: DevPortalContext }>) => {
11
- const navigate = useNavigate();
12
-
13
10
  useSuspenseQuery({
14
11
  queryFn: async () => {
15
- await context.initialize({ navigate });
12
+ await context.initialize();
16
13
  return true;
17
14
  },
18
15
  queryKey: ["zudoku-initialize"],
@@ -1,5 +1,4 @@
1
1
  import { QueryClient } from "@tanstack/react-query";
2
- import { NavigateFunction } from "react-router-dom";
3
2
  import type { SidebarConfig } from "../../config/validators/SidebarSchema.js";
4
3
  import { type AuthenticationProvider } from "../authentication/authentication.js";
5
4
  import type { ComponentsContextType } from "../components/context/ComponentsContext.js";
@@ -84,15 +83,11 @@ export class DevPortalContext {
84
83
  this.page = config.page;
85
84
  }
86
85
 
87
- initialize = async ({
88
- navigate,
89
- }: {
90
- navigate: NavigateFunction;
91
- }): Promise<void> => {
86
+ initialize = async (): Promise<void> => {
92
87
  await Promise.all(
93
88
  this.plugins
94
89
  .filter(needsInitialization)
95
- .map((plugin) => plugin.initialize?.(this, { navigate })),
90
+ .map((plugin) => plugin.initialize?.(this)),
96
91
  );
97
92
  };
98
93
 
@@ -1,5 +1,5 @@
1
1
  import { type ReactElement } from "react";
2
- import { NavigateFunction, type RouteObject } from "react-router-dom";
2
+ import { type RouteObject } from "react-router-dom";
3
3
  import type { Sidebar } from "../../config/validators/SidebarSchema.js";
4
4
  import { MdxComponentsType } from "../util/MdxComponents.js";
5
5
  import { DevPortalContext, type ApiIdentity } from "./DevPortalContext.js";
@@ -40,7 +40,6 @@ export type ProfileNavigationItem = {
40
40
  export interface CommonPlugin {
41
41
  initialize?: (
42
42
  context: DevPortalContext,
43
- options: { navigate: NavigateFunction },
44
43
  ) => Promise<void | boolean> | void | boolean;
45
44
  getHead?: () => ReactElement | undefined;
46
45
  getMdxComponents?: () => MdxComponentsType;
@@ -4,14 +4,13 @@ import { DeveloperHint } from "../../components/DeveloperHint.js";
4
4
  import { ErrorPage } from "../../components/ErrorPage.js";
5
5
  import { Heading } from "../../components/Heading.js";
6
6
  import { InlineCode } from "../../components/InlineCode.js";
7
- import { Markdown } from "../../components/Markdown.js";
7
+ import { Markdown, ProseClasses } from "../../components/Markdown.js";
8
8
  import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
9
9
  import { cn } from "../../util/cn.js";
10
10
  import { OperationListItem } from "./OperationListItem.js";
11
11
  import StaggeredRender from "./StaggeredRender.js";
12
12
  import { useOasConfig } from "./context.js";
13
13
  import { graphql } from "./graphql/index.js";
14
- import { SchemaProseClasses } from "./util/prose.js";
15
14
  import { useQuery } from "./util/urql.js";
16
15
 
17
16
  export const OperationsFragment = graphql(/* GraphQL */ `
@@ -124,10 +123,7 @@ export const OperationList = () => {
124
123
  return (
125
124
  <div className="pt-[--padding-content-top]">
126
125
  <div
127
- className={cn(
128
- SchemaProseClasses,
129
- "mb-16 max-w-full prose-img:max-w-prose",
130
- )}
126
+ className={cn(ProseClasses, "mb-16 max-w-full prose-img:max-w-prose")}
131
127
  >
132
128
  <CategoryHeading>Overview</CategoryHeading>
133
129
  <Heading level={1} id="description" registerSidebarAnchor>
@@ -142,7 +138,7 @@ export const OperationList = () => {
142
138
  {tag.name && <CategoryHeading>{tag.name}</CategoryHeading>}
143
139
  {tag.description && (
144
140
  <Markdown
145
- className={`${SchemaProseClasses} mt-2 mb-12`}
141
+ className={`${ProseClasses} max-w-full prose-img:max-w-prose w-full mt-2 mb-12`}
146
142
  content={tag.description}
147
143
  />
148
144
  )}
@@ -1,6 +1,6 @@
1
1
  import { useState } from "react";
2
2
  import { Heading } from "../../components/Heading.js";
3
- import { Markdown } from "../../components/Markdown.js";
3
+ import { Markdown, ProseClasses } from "../../components/Markdown.js";
4
4
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/Tabs.js";
5
5
  import { groupBy } from "../../util/groupBy.js";
6
6
  import { renderIf } from "../../util/renderIf.js";
@@ -9,7 +9,6 @@ import { ParameterList } from "./ParameterList.js";
9
9
  import { Sidecar } from "./Sidecar.js";
10
10
  import { FragmentType, useFragment } from "./graphql/index.js";
11
11
  import { SchemaView } from "./schema/SchemaView.js";
12
- import { SchemaProseClasses } from "./util/prose.js";
13
12
 
14
13
  export const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
15
14
  export type ParameterGroup = (typeof PARAM_GROUPS)[number];
@@ -39,7 +38,7 @@ export const OperationListItem = ({
39
38
  </Heading>
40
39
  {operation.description && (
41
40
  <Markdown
42
- className={SchemaProseClasses}
41
+ className={`${ProseClasses} max-w-full prose-img:max-w-prose`}
43
42
  content={operation.description}
44
43
  />
45
44
  )}
@@ -89,7 +88,7 @@ export const OperationListItem = ({
89
88
  ))}
90
89
  </TabsList>
91
90
  )}
92
- <ul className="list-none m-0 px-0 overflow-hidden">
91
+ <ul className="list-none m-0 px-0">
93
92
  {operation.responses.map((response) => (
94
93
  <TabsContent
95
94
  value={response.statusCode}
@@ -55,7 +55,7 @@ export const ParameterListItem = ({
55
55
  {parameter.description && (
56
56
  <Markdown
57
57
  content={parameter.description}
58
- className="text-sm prose-p:my-1"
58
+ className="text-sm prose-p:my-1 prose-code:whitespace-pre-line"
59
59
  />
60
60
  )}
61
61
  </li>
@@ -1,5 +1,4 @@
1
1
  import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
2
- import { CirclePlayIcon } from "lucide-react";
3
2
  import { type PropsWithChildren, useState } from "react";
4
3
  import {
5
4
  Dialog,
@@ -11,16 +10,39 @@ import { Playground, type PlaygroundContentProps } from "./Playground.js";
11
10
 
12
11
  export type PlaygroundDialogProps = PropsWithChildren<PlaygroundContentProps>;
13
12
 
13
+ const HeroPlayIcon = ({
14
+ className,
15
+ size = 16,
16
+ }: {
17
+ className?: string;
18
+ size?: number;
19
+ }) => (
20
+ <svg
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ viewBox="0 0 24 24"
23
+ fill="currentColor"
24
+ className={className}
25
+ width={size}
26
+ height={size}
27
+ >
28
+ <path
29
+ fillRule="evenodd"
30
+ d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm14.024-.983a1.125 1.125 0 0 1 0 1.966l-5.603 3.113A1.125 1.125 0 0 1 9 15.113V8.887c0-.857.921-1.4 1.671-.983l5.603 3.113Z"
31
+ clipRule="evenodd"
32
+ />
33
+ </svg>
34
+ );
35
+
14
36
  const PlaygroundDialog = (props: PlaygroundDialogProps) => {
15
37
  const [open, setOpen] = useState(false);
16
38
  return (
17
39
  <Dialog onOpenChange={(open) => setOpen(open)}>
18
40
  <DialogTrigger asChild>
19
41
  {props.children ?? (
20
- <CirclePlayIcon
21
- className="cursor-pointer text-primary hover:text-primary/80"
22
- size={16}
23
- />
42
+ <button className="flex gap-1 items-center px-2 py-1 rounded-md bg-primary/80 hover:bg-primary transition text-primary-foreground text-xs">
43
+ Test
44
+ <HeroPlayIcon className="" size={14} />
45
+ </button>
24
46
  )}
25
47
  </DialogTrigger>
26
48
 
@@ -0,0 +1,47 @@
1
+ import * as Collapsible from "@radix-ui/react-collapsible";
2
+ import { SquareMinusIcon, SquarePlusIcon } from "lucide-react";
3
+ import type { SchemaObject } from "../../../../oas/parser/index.js";
4
+ import { Card } from "../../../../ui/Card.js";
5
+ import type { LogicalGroupType } from "../utils.js";
6
+ import { LogicalGroupItem } from "./LogicalGroupItem.js";
7
+
8
+ const typeLabel = {
9
+ AND: "All of",
10
+ OR: "Any of",
11
+ ONE: "One of",
12
+ };
13
+
14
+ export const LogicalGroup = ({
15
+ schemas,
16
+ type,
17
+ isOpen,
18
+ level,
19
+ toggleOpen,
20
+ }: {
21
+ schemas: SchemaObject[];
22
+ type: LogicalGroupType;
23
+ isOpen: boolean;
24
+ toggleOpen: () => void;
25
+ level: number;
26
+ }) => (
27
+ <Collapsible.Root open={isOpen} onOpenChange={toggleOpen} asChild>
28
+ <Card className="px-6">
29
+ <Collapsible.Trigger className="flex gap-2 items-center py-2 w-full text-sm text-muted-foreground -translate-x-1.5">
30
+ {isOpen ? <SquareMinusIcon size={14} /> : <SquarePlusIcon size={14} />}
31
+ <span>{typeLabel[type]}</span>
32
+ </Collapsible.Trigger>
33
+
34
+ <Collapsible.Content className="pb-4">
35
+ {schemas.map((subSchema, index) => (
36
+ // eslint-disable-next-line react/no-array-index-key
37
+ <LogicalGroupItem
38
+ key={index}
39
+ type={type}
40
+ schema={subSchema}
41
+ level={level}
42
+ />
43
+ ))}
44
+ </Collapsible.Content>
45
+ </Card>
46
+ </Collapsible.Root>
47
+ );
@@ -0,0 +1,54 @@
1
+ import {
2
+ ChevronDownIcon,
3
+ CircleDotIcon,
4
+ CircleFadingPlusIcon,
5
+ CircleIcon,
6
+ } from "lucide-react";
7
+ import { cn } from "../../../../util/cn.js";
8
+
9
+ import type { LogicalGroupType } from "../utils.js";
10
+
11
+ const iconMap = {
12
+ AND: <CircleFadingPlusIcon size={16} className="fill-card" />,
13
+ OR: <CircleDotIcon size={16} className="fill-card" />,
14
+ ONE: <CircleIcon size={14} className="fill-card" />,
15
+ } as const;
16
+
17
+ const colorClass = {
18
+ AND: "text-green-500 dark:text-green-300/60",
19
+ OR: "text-blue-400 dark:text-blue-500",
20
+ ONE: "text-purple-500 dark:text-purple-300/60",
21
+ } as const;
22
+
23
+ export const LogicalGroupConnector = ({
24
+ type,
25
+ isOpen,
26
+ className,
27
+ }: {
28
+ type: LogicalGroupType;
29
+ isOpen: boolean;
30
+ className?: string;
31
+ }) => {
32
+ return (
33
+ <div
34
+ className={cn(
35
+ colorClass[type],
36
+ "relative text-sm flex py-2",
37
+ "before:border-l before:absolute before:-top-2 before:-bottom-2 before:border-border before:border-dashed before:content-['']",
38
+ className,
39
+ )}
40
+ >
41
+ <div className="-translate-x-[7px] flex gap-1 items-center">
42
+ {iconMap[type]}
43
+ <div
44
+ className={cn(
45
+ "translate-y-px mx-px opacity-0 group-hover:opacity-100 transition",
46
+ !isOpen && "-rotate-90",
47
+ )}
48
+ >
49
+ <ChevronDownIcon size={16} />
50
+ </div>
51
+ </div>
52
+ </div>
53
+ );
54
+ };
@@ -0,0 +1,30 @@
1
+ import * as Collapsible from "@radix-ui/react-collapsible";
2
+ import { useState } from "react";
3
+ import type { SchemaObject } from "../../../../oas/parser/index.js";
4
+ import { SchemaView } from "../SchemaView.js";
5
+ import type { LogicalGroupType } from "../utils.js";
6
+ import { LogicalGroupConnector } from "./LogicalGroupConnector.js";
7
+
8
+ export const LogicalGroupItem = (props: {
9
+ type: LogicalGroupType;
10
+ schema: SchemaObject;
11
+ level: number;
12
+ }) => {
13
+ const [isOpen, setIsOpen] = useState(true);
14
+
15
+ return (
16
+ <Collapsible.Root
17
+ open={isOpen}
18
+ onOpenChange={() => setIsOpen((prev) => !prev)}
19
+ className="group"
20
+ >
21
+ <Collapsible.Trigger>
22
+ <LogicalGroupConnector type={props.type} isOpen={isOpen} />
23
+ </Collapsible.Trigger>
24
+ {!isOpen && <div className="wavy-line bg-border translate-y-1" />}
25
+ <Collapsible.Content>
26
+ <SchemaView schema={props.schema} level={props.level + 1} />
27
+ </Collapsible.Content>
28
+ </Collapsible.Root>
29
+ );
30
+ };
@@ -1,12 +1,18 @@
1
1
  import * as Collapsible from "@radix-ui/react-collapsible";
2
2
  import { ListPlusIcon } from "lucide-react";
3
- import { useState } from "react";
3
+ import { useCallback, useState } from "react";
4
4
  import { Markdown, ProseClasses } from "../../../components/Markdown.js";
5
5
  import type { SchemaObject } from "../../../oas/parser/index.js";
6
6
  import { Button } from "../../../ui/Button.js";
7
7
  import { cn } from "../../../util/cn.js";
8
+ import { objectEntries } from "../../../util/objectEntries.js";
9
+ import { LogicalGroup } from "./LogicalGroup/LogicalGroup.js";
8
10
  import { SchemaView } from "./SchemaView.js";
9
- import { hasLogicalGroupings, isComplexType } from "./utils.js";
11
+ import {
12
+ hasLogicalGroupings,
13
+ isComplexType,
14
+ LogicalSchemaTypeMap,
15
+ } from "./utils.js";
10
16
 
11
17
  export const SchemaLogicalGroup = ({
12
18
  schema,
@@ -15,41 +21,34 @@ export const SchemaLogicalGroup = ({
15
21
  schema: SchemaObject;
16
22
  level: number;
17
23
  }) => {
18
- const renderLogicalGroup = (
19
- group: SchemaObject[],
20
- groupName: string,
21
- separator: string,
22
- ) => {
23
- return group.map((subSchema, index) => (
24
- <div key={index} className="my-2">
25
- <strong>{groupName}</strong>
26
- <div className="mt-2">
27
- <SchemaView schema={subSchema} level={level + 1} />
28
- {index < group.length - 1 && (
29
- <div className="text-center my-2">{separator}</div>
30
- )}
31
- </div>
32
- </div>
33
- ));
34
- };
24
+ const [isOpen, setIsOpen] = useState(true);
25
+ const toggleOpen = useCallback(() => setIsOpen((prev) => !prev), []);
35
26
 
36
- if (schema.oneOf) return renderLogicalGroup(schema.oneOf, "One of", "OR");
37
- if (schema.allOf) return renderLogicalGroup(schema.allOf, "All of", "AND");
38
- if (schema.anyOf) return renderLogicalGroup(schema.anyOf, "Any of", "OR");
27
+ for (const [key, type] of objectEntries(LogicalSchemaTypeMap)) {
28
+ if (!schema[key]) continue;
39
29
 
40
- return null;
30
+ return (
31
+ <LogicalGroup
32
+ schemas={schema[key]}
33
+ type={type}
34
+ isOpen={isOpen}
35
+ toggleOpen={toggleOpen}
36
+ level={level}
37
+ />
38
+ );
39
+ }
41
40
  };
42
41
 
43
42
  export const SchemaPropertyItem = ({
44
43
  name,
45
- value,
44
+ schema,
46
45
  group,
47
46
  level,
48
47
  defaultOpen = false,
49
48
  showCollapseButton = true,
50
49
  }: {
51
50
  name: string;
52
- value: SchemaObject;
51
+ schema: SchemaObject;
53
52
  group: "required" | "optional" | "deprecated";
54
53
  level: number;
55
54
  defaultOpen?: boolean;
@@ -63,12 +62,12 @@ export const SchemaPropertyItem = ({
63
62
  <div className="flex gap-2 items-center">
64
63
  <code>{name}</code>
65
64
  <span className="text-muted-foreground">
66
- {value.type === "array" && value.items?.type ? (
67
- <span>{value.items.type}[]</span>
68
- ) : Array.isArray(value.type) ? (
69
- <span>{value.type.join(" | ")}</span>
65
+ {schema.type === "array" && schema.items?.type ? (
66
+ <span>{schema.items.type}[]</span>
67
+ ) : Array.isArray(schema.type) ? (
68
+ <span>{schema.type.join(" | ")}</span>
70
69
  ) : (
71
- <span>{value.type}</span>
70
+ <span>{schema.type}</span>
72
71
  )}
73
72
  </span>
74
73
  {group === "optional" && (
@@ -78,14 +77,14 @@ export const SchemaPropertyItem = ({
78
77
  )}
79
78
  </div>
80
79
 
81
- {value.description && (
80
+ {schema.description && (
82
81
  <Markdown
83
82
  className={cn(ProseClasses, "text-sm leading-normal line-clamp-4")}
84
- content={value.description}
83
+ content={schema.description}
85
84
  />
86
85
  )}
87
86
 
88
- {(hasLogicalGroupings(value) || isComplexType(value)) && (
87
+ {(hasLogicalGroupings(schema) || isComplexType(schema)) && (
89
88
  <Collapsible.Root
90
89
  defaultOpen={defaultOpen}
91
90
  open={isOpen}
@@ -107,14 +106,15 @@ export const SchemaPropertyItem = ({
107
106
  )}
108
107
  <Collapsible.Content>
109
108
  <div className="mt-2">
110
- {hasLogicalGroupings(value) && (
111
- <SchemaLogicalGroup schema={value} level={level + 1} />
112
- )}
113
- {value.type === "object" && (
114
- <SchemaView schema={value} level={level + 1} />
115
- )}
116
- {value.type === "array" && typeof value.items === "object" && (
117
- <SchemaView schema={value.items} level={level + 1} />
109
+ {hasLogicalGroupings(schema) ? (
110
+ <SchemaLogicalGroup schema={schema} level={level + 1} />
111
+ ) : schema.type === "object" ? (
112
+ <SchemaView schema={schema} level={level + 1} />
113
+ ) : (
114
+ schema.type === "array" &&
115
+ typeof schema.items === "object" && (
116
+ <SchemaView schema={schema.items} level={level + 1} />
117
+ )
118
118
  )}
119
119
  </div>
120
120
  </Collapsible.Content>
@@ -111,11 +111,11 @@ export const SchemaView = ({
111
111
  (group) =>
112
112
  groupedProperties[group] && (
113
113
  <ul key={group} className="divide-y">
114
- {groupedProperties[group].map(([key, value]) => (
114
+ {groupedProperties[group].map(([name, schema]) => (
115
115
  <SchemaPropertyItem
116
- key={key}
117
- name={key}
118
- value={value}
116
+ key={name}
117
+ name={name}
118
+ schema={schema}
119
119
  group={group}
120
120
  level={level}
121
121
  defaultOpen={isTopLevelSingleItem || defaultOpen}
@@ -8,3 +8,11 @@ export const isComplexType = (value: SchemaObject) =>
8
8
 
9
9
  export const hasLogicalGroupings = (value: SchemaObject) =>
10
10
  Boolean(value.oneOf ?? value.allOf ?? value.anyOf);
11
+
12
+ export const LogicalSchemaTypeMap = {
13
+ allOf: "AND",
14
+ anyOf: "OR",
15
+ oneOf: "ONE",
16
+ } as const;
17
+
18
+ export type LogicalGroupType = "AND" | "OR" | "ONE";
@@ -1 +0,0 @@
1
- export declare const SchemaProseClasses: string;
@@ -1,4 +0,0 @@
1
- import { ProseClasses } from "../../../components/Markdown.js";
2
- import { cn } from "../../../util/cn.js";
3
- export const SchemaProseClasses = cn(ProseClasses, "max-w-full prose-img:max-w-prose");
4
- //# sourceMappingURL=prose.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prose.js","sourceRoot":"","sources":["../../../../../src/lib/plugins/openapi/util/prose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,OAAO,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAEzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAClC,YAAY,EACZ,kCAAkC,CACnC,CAAC"}