specra 0.1.0

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 (142) hide show
  1. package/LICENSE.MD +21 -0
  2. package/README.md +157 -0
  3. package/dist/app/api/mdx-watch/route.d.mts +12 -0
  4. package/dist/app/api/mdx-watch/route.d.ts +12 -0
  5. package/dist/app/api/mdx-watch/route.js +98 -0
  6. package/dist/app/api/mdx-watch/route.js.map +1 -0
  7. package/dist/app/api/mdx-watch/route.mjs +71 -0
  8. package/dist/app/api/mdx-watch/route.mjs.map +1 -0
  9. package/dist/app/docs-page.d.mts +32 -0
  10. package/dist/app/docs-page.d.ts +32 -0
  11. package/dist/app/docs-page.js +4072 -0
  12. package/dist/app/docs-page.js.map +1 -0
  13. package/dist/app/docs-page.mjs +14 -0
  14. package/dist/app/docs-page.mjs.map +1 -0
  15. package/dist/app/layout.css +297 -0
  16. package/dist/app/layout.css.map +1 -0
  17. package/dist/app/layout.d.mts +19 -0
  18. package/dist/app/layout.d.ts +19 -0
  19. package/dist/app/layout.js +112 -0
  20. package/dist/app/layout.js.map +1 -0
  21. package/dist/app/layout.mjs +13 -0
  22. package/dist/app/layout.mjs.map +1 -0
  23. package/dist/chunk-DR4EPLMT.mjs +1013 -0
  24. package/dist/chunk-DR4EPLMT.mjs.map +1 -0
  25. package/dist/chunk-INL2EC72.mjs +170 -0
  26. package/dist/chunk-INL2EC72.mjs.map +1 -0
  27. package/dist/chunk-IZFGEAD6.mjs +61 -0
  28. package/dist/chunk-IZFGEAD6.mjs.map +1 -0
  29. package/dist/chunk-KTRWWAGL.mjs +50 -0
  30. package/dist/chunk-KTRWWAGL.mjs.map +1 -0
  31. package/dist/chunk-MZJHJ6BV.mjs +21 -0
  32. package/dist/chunk-MZJHJ6BV.mjs.map +1 -0
  33. package/dist/chunk-NXRIAL7T.mjs +3119 -0
  34. package/dist/chunk-NXRIAL7T.mjs.map +1 -0
  35. package/dist/components/index.d.mts +822 -0
  36. package/dist/components/index.d.ts +822 -0
  37. package/dist/components/index.js +3738 -0
  38. package/dist/components/index.js.map +1 -0
  39. package/dist/components/index.mjs +3627 -0
  40. package/dist/components/index.mjs.map +1 -0
  41. package/dist/index.css +297 -0
  42. package/dist/index.css.map +1 -0
  43. package/dist/index.d.mts +545 -0
  44. package/dist/index.d.ts +545 -0
  45. package/dist/index.js +4648 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/index.mjs +347 -0
  48. package/dist/index.mjs.map +1 -0
  49. package/dist/lib/index.d.mts +798 -0
  50. package/dist/lib/index.d.ts +798 -0
  51. package/dist/lib/index.js +1301 -0
  52. package/dist/lib/index.js.map +1 -0
  53. package/dist/lib/index.mjs +89 -0
  54. package/dist/lib/index.mjs.map +1 -0
  55. package/package.json +119 -0
  56. package/src/app/api/mdx-watch/route.ts +86 -0
  57. package/src/app/docs-page.tsx +212 -0
  58. package/src/app/layout.tsx +74 -0
  59. package/src/components/docs/accordion.tsx +53 -0
  60. package/src/components/docs/api/api-endpoint.tsx +59 -0
  61. package/src/components/docs/api/api-params.tsx +43 -0
  62. package/src/components/docs/api/api-playground.tsx +233 -0
  63. package/src/components/docs/api/api-reference.tsx +291 -0
  64. package/src/components/docs/api/api-response.tsx +48 -0
  65. package/src/components/docs/api/index.ts +5 -0
  66. package/src/components/docs/badge.tsx +22 -0
  67. package/src/components/docs/breadcrumb.tsx +51 -0
  68. package/src/components/docs/callout.tsx +109 -0
  69. package/src/components/docs/card.tsx +84 -0
  70. package/src/components/docs/category-index.tsx +112 -0
  71. package/src/components/docs/code-block.tsx +129 -0
  72. package/src/components/docs/columns.tsx +45 -0
  73. package/src/components/docs/componentTextProps.ts +85 -0
  74. package/src/components/docs/dev-mode-badge.tsx +35 -0
  75. package/src/components/docs/doc-layout-wrapper.tsx +54 -0
  76. package/src/components/docs/doc-layout.tsx +111 -0
  77. package/src/components/docs/doc-loading.tsx +15 -0
  78. package/src/components/docs/doc-metadata.tsx +55 -0
  79. package/src/components/docs/doc-navigation.tsx +62 -0
  80. package/src/components/docs/doc-tags.tsx +25 -0
  81. package/src/components/docs/draft-badge.tsx +10 -0
  82. package/src/components/docs/footer.tsx +47 -0
  83. package/src/components/docs/frame.tsx +22 -0
  84. package/src/components/docs/header.tsx +122 -0
  85. package/src/components/docs/hot-reload-indicator.tsx +77 -0
  86. package/src/components/docs/icon.tsx +70 -0
  87. package/src/components/docs/image-card.tsx +95 -0
  88. package/src/components/docs/image.tsx +73 -0
  89. package/src/components/docs/index.ts +48 -0
  90. package/src/components/docs/math.tsx +46 -0
  91. package/src/components/docs/mdx-components.tsx +166 -0
  92. package/src/components/docs/mdx-hot-reload.tsx +37 -0
  93. package/src/components/docs/mermaid.tsx +77 -0
  94. package/src/components/docs/mobile-doc-layout.tsx +115 -0
  95. package/src/components/docs/not-found-content.tsx +55 -0
  96. package/src/components/docs/search-highlight.tsx +127 -0
  97. package/src/components/docs/search-modal.tsx +223 -0
  98. package/src/components/docs/sidebar-skeleton.tsx +39 -0
  99. package/src/components/docs/sidebar.tsx +323 -0
  100. package/src/components/docs/site-banner.tsx +92 -0
  101. package/src/components/docs/steps.tsx +29 -0
  102. package/src/components/docs/tab-context.tsx +28 -0
  103. package/src/components/docs/tab-groups.tsx +50 -0
  104. package/src/components/docs/table-of-contents.tsx +104 -0
  105. package/src/components/docs/tabs.tsx +63 -0
  106. package/src/components/docs/theme-toggle.tsx +39 -0
  107. package/src/components/docs/tooltip.tsx +37 -0
  108. package/src/components/docs/version-switcher.tsx +52 -0
  109. package/src/components/docs/video.tsx +80 -0
  110. package/src/components/global/index.ts +3 -0
  111. package/src/components/global/version-not-found.tsx +26 -0
  112. package/src/components/index.ts +8 -0
  113. package/src/components/theme-provider.tsx +11 -0
  114. package/src/components/ui/badge.tsx +46 -0
  115. package/src/components/ui/button.tsx +60 -0
  116. package/src/components/ui/dialog.tsx +143 -0
  117. package/src/components/ui/index.ts +6 -0
  118. package/src/components/ui/input.tsx +21 -0
  119. package/src/components/ui/textarea.tsx +18 -0
  120. package/src/index.ts +41 -0
  121. package/src/lib/api-parser.types.ts +78 -0
  122. package/src/lib/api.types.ts +202 -0
  123. package/src/lib/category.ts +71 -0
  124. package/src/lib/config.server.ts +170 -0
  125. package/src/lib/config.ts +20 -0
  126. package/src/lib/config.types.ts +295 -0
  127. package/src/lib/dev-utils.ts +75 -0
  128. package/src/lib/index.ts +27 -0
  129. package/src/lib/mdx-cache.ts +200 -0
  130. package/src/lib/mdx.ts +402 -0
  131. package/src/lib/parsers/base-parser.ts +16 -0
  132. package/src/lib/parsers/index.ts +69 -0
  133. package/src/lib/parsers/openapi-parser.ts +251 -0
  134. package/src/lib/parsers/postman-parser.ts +301 -0
  135. package/src/lib/parsers/specra-parser.ts +24 -0
  136. package/src/lib/redirects.ts +40 -0
  137. package/src/lib/remark-code-meta.ts +23 -0
  138. package/src/lib/sidebar-utils.ts +188 -0
  139. package/src/lib/toc.ts +24 -0
  140. package/src/lib/utils.ts +36 -0
  141. package/src/specra.config.json +124 -0
  142. package/src/styles/globals.css +427 -0
@@ -0,0 +1,29 @@
1
+ interface StepsProps {
2
+ children: React.ReactNode
3
+ }
4
+
5
+ interface StepProps {
6
+ title: string
7
+ children: React.ReactNode
8
+ }
9
+
10
+ export function Steps({ children }: StepsProps) {
11
+ return (
12
+ <div className="my-6 ml-4 space-y-6 [counter-reset:step]">
13
+ {children}
14
+ </div>
15
+ )
16
+ }
17
+
18
+ export function Step({ title, children }: StepProps) {
19
+ return (
20
+ <div className="relative pl-8 pb-6 border-l-2 border-border last:border-l-0 last:pb-0 [counter-increment:step] before:content-[counter(step)] before:absolute before:left-0 before:-translate-x-1/2 before:w-8 before:h-8 before:rounded-full before:bg-primary before:text-primary-foreground before:flex before:items-center before:justify-center before:text-sm before:font-semibold before:z-10">
21
+ <div className="mb-2">
22
+ <h3 className="text-lg font-semibold text-foreground">{title}</h3>
23
+ </div>
24
+ <div className="prose prose-sm dark:prose-invert max-w-none [&>*:last-child]:mb-0">
25
+ {children}
26
+ </div>
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,28 @@
1
+ "use client"
2
+
3
+ import { createContext, useContext, useState, ReactNode } from "react"
4
+
5
+ interface TabContextType {
6
+ activeTabGroup: string
7
+ setActiveTabGroup: (tabId: string) => void
8
+ }
9
+
10
+ const TabContext = createContext<TabContextType | undefined>(undefined)
11
+
12
+ export function TabProvider({ children, defaultTab }: { children: ReactNode; defaultTab: string }) {
13
+ const [activeTabGroup, setActiveTabGroup] = useState(defaultTab)
14
+
15
+ return (
16
+ <TabContext.Provider value={{ activeTabGroup, setActiveTabGroup }}>
17
+ {children}
18
+ </TabContext.Provider>
19
+ )
20
+ }
21
+
22
+ export function useTabContext() {
23
+ const context = useContext(TabContext)
24
+ if (!context) {
25
+ throw new Error("useTabContext must be used within TabProvider")
26
+ }
27
+ return context
28
+ }
@@ -0,0 +1,50 @@
1
+ "use client"
2
+
3
+ import { Icon } from "./icon"
4
+ import type { TabGroup } from "@/lib/config.types"
5
+
6
+ interface TabGroupsProps {
7
+ tabGroups: TabGroup[]
8
+ activeTabId?: string
9
+ onTabChange?: (tabId: string) => void
10
+ }
11
+
12
+ export function TabGroups({ tabGroups, activeTabId, onTabChange }: TabGroupsProps) {
13
+ const activeTab = activeTabId || tabGroups[0]?.id || ""
14
+
15
+ const handleTabChange = (tabId: string) => {
16
+ onTabChange?.(tabId)
17
+ }
18
+
19
+ if (!tabGroups || tabGroups.length === 0) {
20
+ return null
21
+ }
22
+
23
+ return (
24
+ <div className="sticky top-16 z-30 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
25
+ <div className="container mx-auto px-6">
26
+ <nav className="flex gap-1 overflow-x-auto no-scrollbar" aria-label="Documentation tabs">
27
+ {tabGroups.map((tab) => {
28
+ const isActive = tab.id === activeTab
29
+
30
+ return (
31
+ <button
32
+ key={tab.id}
33
+ onClick={() => handleTabChange(tab.id)}
34
+ className={`flex items-center gap-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-all border-b-2 ${
35
+ isActive
36
+ ? "border-primary text-primary"
37
+ : "border-transparent text-muted-foreground hover:text-foreground hover:border-border"
38
+ }`}
39
+ aria-current={isActive ? "page" : undefined}
40
+ >
41
+ {tab.icon && <Icon icon={tab.icon} size={16} className="shrink-0" />}
42
+ {tab.label}
43
+ </button>
44
+ )
45
+ })}
46
+ </nav>
47
+ </div>
48
+ </div>
49
+ )
50
+ }
@@ -0,0 +1,104 @@
1
+ "use client"
2
+
3
+ import { useEffect, useState } from "react"
4
+ import type { SpecraConfig } from "@/lib/config"
5
+
6
+ interface TOCItem {
7
+ id: string
8
+ title: string
9
+ level: number
10
+ }
11
+
12
+ interface TableOfContentsProps {
13
+ items: TOCItem[]
14
+ config: SpecraConfig
15
+ }
16
+
17
+ export function TableOfContents({ items, config }: TableOfContentsProps) {
18
+ const [activeId, setActiveId] = useState<string>("")
19
+
20
+ // Check if TOC should be shown
21
+ if (!config.navigation?.showTableOfContents) {
22
+ return null
23
+ }
24
+
25
+ // Filter items by max depth
26
+ const maxDepth = config.navigation?.tocMaxDepth || 3
27
+ const filteredItems = items.filter(item => item.level <= maxDepth)
28
+
29
+ // Check if tab groups are configured
30
+ const hasTabGroups = config.navigation?.tabGroups && config.navigation.tabGroups.length > 0
31
+
32
+ useEffect(() => {
33
+ const observer = new IntersectionObserver(
34
+ (entries) => {
35
+ entries.forEach((entry) => {
36
+ if (entry.isIntersecting) {
37
+ setActiveId(entry.target.id)
38
+ }
39
+ })
40
+ },
41
+ { rootMargin: "-80px 0px -80% 0px" },
42
+ )
43
+
44
+ filteredItems.forEach((item) => {
45
+ const element = document.getElementById(item.id)
46
+ if (element) {
47
+ observer.observe(element)
48
+ }
49
+ })
50
+
51
+ return () => observer.disconnect()
52
+ }, [filteredItems])
53
+
54
+ const handleClick = (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
55
+ e.preventDefault()
56
+ const element = document.getElementById(id)
57
+ if (element) {
58
+ const offset = 100 // Offset for fixed header
59
+ const elementPosition = element.getBoundingClientRect().top
60
+ const offsetPosition = elementPosition + window.scrollY - offset
61
+
62
+ window.scrollTo({
63
+ top: offsetPosition,
64
+ behavior: "smooth",
65
+ })
66
+
67
+ // Update URL without jumping
68
+ window.history.replaceState(null, "", `#${id}`)
69
+
70
+ // Manually set active ID after scroll
71
+ setActiveId(id)
72
+ }
73
+ }
74
+
75
+ // Adjust top position based on whether tabs are present
76
+ const stickyTop = hasTabGroups ? "top-[7.5rem]" : "top-24"
77
+ const maxHeight = hasTabGroups ? "max-h-[calc(100vh-10rem)]" : "max-h-[calc(100vh-7rem)]"
78
+
79
+ return (
80
+ <aside className={`w-64 hidden xl:block shrink-0 sticky ${stickyTop} self-start`}>
81
+ {filteredItems.length > 0 && (
82
+ <div className={`${maxHeight} overflow-y-auto bg-muted/30 dark:bg-muted/10 rounded-2xl p-4 border border-border/50`}>
83
+ <h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">On this page</h3>
84
+ <nav className="space-y-1">
85
+ {filteredItems.map((item) => (
86
+ <a
87
+ key={item.id}
88
+ href={`#${item.id}`}
89
+ onClick={(e) => handleClick(e, item.id)}
90
+ className={`block text-sm transition-all cursor-pointer rounded-xl px-3 py-2 ${item.level === 3 ? "ml-3" : ""} ${
91
+ activeId === item.id
92
+ ? "text-primary font-medium"
93
+ : "text-foreground hover:bg-accent/50"
94
+ }`}
95
+ >
96
+ {item.title}
97
+ </a>
98
+ ))}
99
+ </nav>
100
+ </div>
101
+ )}
102
+ </aside>
103
+ )
104
+ }
@@ -0,0 +1,63 @@
1
+ "use client"
2
+
3
+ import React, { useState, Children, isValidElement } from "react"
4
+
5
+ interface TabProps {
6
+ label: string
7
+ children: React.ReactNode
8
+ }
9
+
10
+ interface TabsProps {
11
+ children: React.ReactElement<TabProps> | React.ReactElement<TabProps>[]
12
+ defaultValue?: string
13
+ }
14
+
15
+ export function Tab({ children }: TabProps) {
16
+ return <>{children}</>
17
+ }
18
+
19
+ export function Tabs({ children, defaultValue }: TabsProps) {
20
+ const tabs = Children.toArray(children).filter(isValidElement) as React.ReactElement<TabProps>[]
21
+
22
+ // Use defaultValue or first tab label as initial active tab
23
+ const firstTabLabel = tabs[0]?.props.label || ""
24
+ const [activeTab, setActiveTab] = useState(defaultValue || firstTabLabel)
25
+
26
+ return (
27
+ <div className="my-6">
28
+ {/* Tab buttons */}
29
+ <div className="flex items-center gap-1 border-b border-border mb-4">
30
+ {tabs.map((tab) => {
31
+ const label = tab.props.label
32
+ const isActive = activeTab === label
33
+
34
+ return (
35
+ <button
36
+ key={label}
37
+ onClick={() => setActiveTab(label)}
38
+ className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
39
+ isActive
40
+ ? "border-primary text-primary"
41
+ : "border-transparent text-muted-foreground hover:text-foreground hover:border-border"
42
+ }`}
43
+ >
44
+ {label}
45
+ </button>
46
+ )
47
+ })}
48
+ </div>
49
+
50
+ {/* Tab content */}
51
+ {tabs.map((tab) => {
52
+ const label = tab.props.label
53
+ if (activeTab !== label) return null
54
+
55
+ return (
56
+ <div key={label} className="prose prose-slate dark:prose-invert max-w-none [&>*:first-child]:mt-0">
57
+ {tab.props.children}
58
+ </div>
59
+ )
60
+ })}
61
+ </div>
62
+ )
63
+ }
@@ -0,0 +1,39 @@
1
+ "use client"
2
+
3
+ import { Moon, Sun } from "lucide-react"
4
+ import { useEffect, useState } from "react"
5
+
6
+ export function ThemeToggle() {
7
+ const [theme, setTheme] = useState<"light" | "dark">("dark")
8
+
9
+ useEffect(() => {
10
+ // Check for saved theme preference or default to dark
11
+ const savedTheme = localStorage.getItem("theme") as "light" | "dark" | null
12
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches
13
+ const initialTheme = savedTheme || (prefersDark ? "dark" : "light")
14
+
15
+ setTheme(initialTheme)
16
+ document.documentElement.classList.toggle("dark", initialTheme === "dark")
17
+ }, [])
18
+
19
+ const toggleTheme = () => {
20
+ const newTheme = theme === "dark" ? "light" : "dark"
21
+ setTheme(newTheme)
22
+ localStorage.setItem("theme", newTheme)
23
+ document.documentElement.classList.toggle("dark", newTheme === "dark")
24
+ }
25
+
26
+ return (
27
+ <button
28
+ onClick={toggleTheme}
29
+ className="flex items-center justify-center w-9 h-9 rounded-md border border-border bg-background hover:bg-accent transition-colors"
30
+ aria-label="Toggle theme"
31
+ >
32
+ {theme === "dark" ? (
33
+ <Sun className="h-4 w-4 text-foreground" />
34
+ ) : (
35
+ <Moon className="h-4 w-4 text-foreground" />
36
+ )}
37
+ </button>
38
+ )
39
+ }
@@ -0,0 +1,37 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+
5
+ interface TooltipProps {
6
+ children: React.ReactNode
7
+ content: string
8
+ position?: "top" | "bottom" | "left" | "right"
9
+ }
10
+
11
+ export function Tooltip({ children, content, position = "top" }: TooltipProps) {
12
+ const [isVisible, setIsVisible] = useState(false)
13
+
14
+ const positions = {
15
+ top: "bottom-full left-1/2 -translate-x-1/2 mb-2",
16
+ bottom: "top-full left-1/2 -translate-x-1/2 mt-2",
17
+ left: "right-full top-1/2 -translate-y-1/2 mr-2",
18
+ right: "left-full top-1/2 -translate-y-1/2 ml-2",
19
+ }
20
+
21
+ return (
22
+ <span
23
+ className="relative inline-flex underline decoration-dotted cursor-help"
24
+ onMouseEnter={() => setIsVisible(true)}
25
+ onMouseLeave={() => setIsVisible(false)}
26
+ >
27
+ {children}
28
+ {isVisible && (
29
+ <span
30
+ className={`absolute ${positions[position]} z-50 px-2 py-1 text-xs text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap pointer-events-none`}
31
+ >
32
+ {content}
33
+ </span>
34
+ )}
35
+ </span>
36
+ )
37
+ }
@@ -0,0 +1,52 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import { Check, ChevronDown } from "lucide-react"
5
+ import { useRouter } from "next/navigation"
6
+
7
+ interface VersionSwitcherProps {
8
+ currentVersion: string
9
+ versions: string[]
10
+ }
11
+
12
+ export function VersionSwitcher({ currentVersion, versions }: VersionSwitcherProps) {
13
+ const [open, setOpen] = useState(false)
14
+ const router = useRouter()
15
+
16
+ const handleVersionChange = (version: string) => {
17
+ router.push(`/docs/${version}`)
18
+ setOpen(false)
19
+ }
20
+
21
+ return (
22
+ <div className="relative">
23
+ <button
24
+ onClick={() => setOpen(!open)}
25
+ className="flex items-center gap-2 px-3 py-2 text-sm text-foreground bg-muted rounded-md hover:bg-muted/80 transition-colors"
26
+ >
27
+ <span className="font-medium">{currentVersion}</span>
28
+ <ChevronDown className="h-4 w-4" />
29
+ </button>
30
+
31
+ {open && (
32
+ <>
33
+ <div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
34
+ <div className="absolute right-0 mt-2 w-48 bg-background border border-border rounded-md shadow-lg z-50">
35
+ <div className="p-2">
36
+ {versions.map((version) => (
37
+ <button
38
+ key={version}
39
+ onClick={() => handleVersionChange(version)}
40
+ className="flex items-center justify-between w-full px-3 py-2 text-sm text-foreground hover:bg-muted rounded-md transition-colors"
41
+ >
42
+ <span>{version}</span>
43
+ {version === currentVersion && <Check className="h-4 w-4 text-primary" />}
44
+ </button>
45
+ ))}
46
+ </div>
47
+ </div>
48
+ </>
49
+ )}
50
+ </div>
51
+ )
52
+ }
@@ -0,0 +1,80 @@
1
+ "use client"
2
+
3
+ interface VideoProps {
4
+ src: string
5
+ caption?: string
6
+ autoplay?: boolean
7
+ loop?: boolean
8
+ muted?: boolean
9
+ controls?: boolean
10
+ poster?: string
11
+ }
12
+
13
+ export function Video({
14
+ src,
15
+ caption,
16
+ autoplay = false,
17
+ loop = false,
18
+ muted = false,
19
+ controls = true,
20
+ poster,
21
+ }: VideoProps) {
22
+ // Check if it's a YouTube or Vimeo URL
23
+ const isYouTube = src.includes("youtube.com") || src.includes("youtu.be")
24
+ const isVimeo = src.includes("vimeo.com")
25
+
26
+ const getYouTubeId = (url: string) => {
27
+ const match = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/)
28
+ return match ? match[1] : null
29
+ }
30
+
31
+ const getVimeoId = (url: string) => {
32
+ const match = url.match(/vimeo\.com\/(\d+)/)
33
+ return match ? match[1] : null
34
+ }
35
+
36
+ return (
37
+ <figure className="my-6">
38
+ <div className="relative rounded-xl border border-border overflow-hidden bg-muted/30">
39
+ {isYouTube ? (
40
+ <div className="relative w-full" style={{ paddingBottom: "56.25%" }}>
41
+ <iframe
42
+ className="absolute top-0 left-0 w-full h-full"
43
+ src={`https://www.youtube.com/embed/${getYouTubeId(src)}${autoplay ? "?autoplay=1" : ""}`}
44
+ title="YouTube video"
45
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
46
+ allowFullScreen
47
+ />
48
+ </div>
49
+ ) : isVimeo ? (
50
+ <div className="relative w-full" style={{ paddingBottom: "56.25%" }}>
51
+ <iframe
52
+ className="absolute top-0 left-0 w-full h-full"
53
+ src={`https://player.vimeo.com/video/${getVimeoId(src)}${autoplay ? "?autoplay=1" : ""}`}
54
+ title="Vimeo video"
55
+ allow="autoplay; fullscreen; picture-in-picture"
56
+ allowFullScreen
57
+ />
58
+ </div>
59
+ ) : (
60
+ <video
61
+ src={src}
62
+ controls={controls}
63
+ autoPlay={autoplay}
64
+ loop={loop}
65
+ muted={muted}
66
+ poster={poster}
67
+ className="w-full h-auto"
68
+ >
69
+ Your browser does not support the video tag.
70
+ </video>
71
+ )}
72
+ </div>
73
+ {caption && (
74
+ <figcaption className="mt-2 text-center text-sm text-muted-foreground italic">
75
+ {caption}
76
+ </figcaption>
77
+ )}
78
+ </figure>
79
+ )
80
+ }
@@ -0,0 +1,3 @@
1
+
2
+
3
+ export * from './version-not-found'
@@ -0,0 +1,26 @@
1
+ import { AlertTriangle } from "lucide-react";
2
+ import Link from "next/link";
3
+
4
+ export function VersionNotFound() {
5
+ return (
6
+ <>
7
+ <div className="flex min-h-screen items-center justify-center px-4">
8
+ <div className="text-center">
9
+ <div className="mb-4 flex justify-center">
10
+ <AlertTriangle className="h-16 w-16 text-yellow-500" />
11
+ </div>
12
+ <h1 className="mb-2 text-4xl font-bold">Version Not Found</h1>
13
+ <p className="mb-6 text-muted-foreground">
14
+ The documentation version you're looking for doesn't exist.
15
+ </p>
16
+ <Link
17
+ href="/docs/v1.0.0"
18
+ className="inline-flex items-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
19
+ >
20
+ Go to Latest Version
21
+ </Link>
22
+ </div>
23
+ </div>
24
+ </>
25
+ )
26
+ }
@@ -0,0 +1,8 @@
1
+ // Documentation Components
2
+ export * from './docs'
3
+
4
+ // UI Components
5
+ export * from './ui'
6
+
7
+ // Global Components
8
+ export * from './global'
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ ThemeProvider as NextThemesProvider,
6
+ type ThemeProviderProps,
7
+ } from 'next-themes'
8
+
9
+ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
11
+ }
@@ -0,0 +1,46 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
14
+ secondary:
15
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
16
+ destructive:
17
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
18
+ outline:
19
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ variant: "default",
24
+ },
25
+ }
26
+ )
27
+
28
+ function Badge({
29
+ className,
30
+ variant,
31
+ asChild = false,
32
+ ...props
33
+ }: React.ComponentProps<"span"> &
34
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
35
+ const Comp = asChild ? Slot : "span"
36
+
37
+ return (
38
+ <Comp
39
+ data-slot="badge"
40
+ className={cn(badgeVariants({ variant }), className)}
41
+ {...props}
42
+ />
43
+ )
44
+ }
45
+
46
+ export { Badge, badgeVariants }
@@ -0,0 +1,60 @@
1
+ import * as React from 'react'
2
+ import { Slot } from '@radix-ui/react-slot'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13
+ destructive:
14
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
15
+ outline:
16
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
17
+ secondary:
18
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19
+ ghost:
20
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
21
+ link: 'text-primary underline-offset-4 hover:underline',
22
+ },
23
+ size: {
24
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
25
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
26
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
27
+ icon: 'size-9',
28
+ 'icon-sm': 'size-8',
29
+ 'icon-lg': 'size-10',
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: 'default',
34
+ size: 'default',
35
+ },
36
+ },
37
+ )
38
+
39
+ function Button({
40
+ className,
41
+ variant,
42
+ size,
43
+ asChild = false,
44
+ ...props
45
+ }: React.ComponentProps<'button'> &
46
+ VariantProps<typeof buttonVariants> & {
47
+ asChild?: boolean
48
+ }) {
49
+ const Comp = asChild ? Slot : 'button'
50
+
51
+ return (
52
+ <Comp
53
+ data-slot="button"
54
+ className={cn(buttonVariants({ variant, size, className }))}
55
+ {...props}
56
+ />
57
+ )
58
+ }
59
+
60
+ export { Button, buttonVariants }