skrypt-ai 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.
- package/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/autofix/index.d.ts +46 -0
- package/dist/autofix/index.js +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +40 -0
- package/dist/commands/autofix.d.ts +2 -0
- package/dist/commands/autofix.js +143 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +320 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/review-pr.d.ts +2 -0
- package/dist/commands/review-pr.js +117 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +142 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.js +82 -0
- package/dist/config/types.d.ts +24 -0
- package/dist/config/types.js +34 -0
- package/dist/generator/generator.d.ts +15 -0
- package/dist/generator/generator.js +144 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +4 -0
- package/dist/generator/organizer.d.ts +29 -0
- package/dist/generator/organizer.js +222 -0
- package/dist/generator/types.d.ts +83 -0
- package/dist/generator/types.js +1 -0
- package/dist/generator/writer.d.ts +28 -0
- package/dist/generator/writer.js +320 -0
- package/dist/github/pr-comments.d.ts +40 -0
- package/dist/github/pr-comments.js +308 -0
- package/dist/llm/anthropic-client.d.ts +16 -0
- package/dist/llm/anthropic-client.js +92 -0
- package/dist/llm/index.d.ts +53 -0
- package/dist/llm/index.js +400 -0
- package/dist/llm/llm.manual-test.d.ts +1 -0
- package/dist/llm/llm.manual-test.js +112 -0
- package/dist/llm/llm.mock-test.d.ts +4 -0
- package/dist/llm/llm.mock-test.js +79 -0
- package/dist/llm/openai-client.d.ts +17 -0
- package/dist/llm/openai-client.js +90 -0
- package/dist/llm/types.d.ts +60 -0
- package/dist/llm/types.js +20 -0
- package/dist/scanner/content-type.d.ts +39 -0
- package/dist/scanner/content-type.js +194 -0
- package/dist/scanner/content-type.test.d.ts +1 -0
- package/dist/scanner/content-type.test.js +231 -0
- package/dist/scanner/go.d.ts +20 -0
- package/dist/scanner/go.js +269 -0
- package/dist/scanner/index.d.ts +21 -0
- package/dist/scanner/index.js +137 -0
- package/dist/scanner/python.d.ts +6 -0
- package/dist/scanner/python.js +57 -0
- package/dist/scanner/python_parser.py +230 -0
- package/dist/scanner/rust.d.ts +23 -0
- package/dist/scanner/rust.js +304 -0
- package/dist/scanner/scanner.test.d.ts +1 -0
- package/dist/scanner/scanner.test.js +210 -0
- package/dist/scanner/types.d.ts +50 -0
- package/dist/scanner/types.js +1 -0
- package/dist/scanner/typescript.d.ts +34 -0
- package/dist/scanner/typescript.js +327 -0
- package/dist/scanner/typescript.manual-test.d.ts +1 -0
- package/dist/scanner/typescript.manual-test.js +112 -0
- package/dist/template/docs.json +32 -0
- package/dist/template/mdx-components.tsx +62 -0
- package/dist/template/next-env.d.ts +6 -0
- package/dist/template/next.config.mjs +17 -0
- package/dist/template/package.json +39 -0
- package/dist/template/postcss.config.mjs +5 -0
- package/dist/template/public/search-index.json +1 -0
- package/dist/template/scripts/build-search-index.mjs +120 -0
- package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
- package/dist/template/src/app/api/openapi/route.ts +48 -0
- package/dist/template/src/app/api/rate-limit/route.ts +84 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
- package/dist/template/src/app/docs/layout.tsx +9 -0
- package/dist/template/src/app/docs/page.mdx +67 -0
- package/dist/template/src/app/error.tsx +63 -0
- package/dist/template/src/app/layout.tsx +71 -0
- package/dist/template/src/app/page.tsx +18 -0
- package/dist/template/src/app/reference/route.ts +36 -0
- package/dist/template/src/app/robots.ts +14 -0
- package/dist/template/src/app/sitemap.ts +64 -0
- package/dist/template/src/components/breadcrumbs.tsx +41 -0
- package/dist/template/src/components/copy-button.tsx +29 -0
- package/dist/template/src/components/docs-layout.tsx +35 -0
- package/dist/template/src/components/edit-link.tsx +39 -0
- package/dist/template/src/components/feedback.tsx +52 -0
- package/dist/template/src/components/header.tsx +66 -0
- package/dist/template/src/components/mdx/accordion.tsx +48 -0
- package/dist/template/src/components/mdx/api-badge.tsx +57 -0
- package/dist/template/src/components/mdx/callout.tsx +111 -0
- package/dist/template/src/components/mdx/card.tsx +62 -0
- package/dist/template/src/components/mdx/changelog.tsx +57 -0
- package/dist/template/src/components/mdx/code-block.tsx +42 -0
- package/dist/template/src/components/mdx/code-group.tsx +125 -0
- package/dist/template/src/components/mdx/code-playground.tsx +322 -0
- package/dist/template/src/components/mdx/go-playground.tsx +235 -0
- package/dist/template/src/components/mdx/heading.tsx +37 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
- package/dist/template/src/components/mdx/index.tsx +15 -0
- package/dist/template/src/components/mdx/param-table.tsx +71 -0
- package/dist/template/src/components/mdx/python-playground.tsx +293 -0
- package/dist/template/src/components/mdx/steps.tsx +43 -0
- package/dist/template/src/components/mdx/tabs.tsx +81 -0
- package/dist/template/src/components/rate-limit-display.tsx +183 -0
- package/dist/template/src/components/search-dialog.tsx +178 -0
- package/dist/template/src/components/sidebar.tsx +129 -0
- package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
- package/dist/template/src/components/table-of-contents.tsx +84 -0
- package/dist/template/src/components/theme-toggle.tsx +46 -0
- package/dist/template/src/components/version-selector.tsx +61 -0
- package/dist/template/src/contexts/syntax-theme.tsx +52 -0
- package/dist/template/src/lib/highlight.ts +83 -0
- package/dist/template/src/lib/search-types.ts +37 -0
- package/dist/template/src/lib/search.ts +125 -0
- package/dist/template/src/lib/utils.ts +6 -0
- package/dist/template/src/styles/globals.css +152 -0
- package/dist/template/tsconfig.json +25 -0
- package/dist/template/tsconfig.tsbuildinfo +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { ThumbsUp, ThumbsDown, Check } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
export function Feedback() {
|
|
7
|
+
const [submitted, setSubmitted] = useState(false)
|
|
8
|
+
const [selection, setSelection] = useState<'yes' | 'no' | null>(null)
|
|
9
|
+
|
|
10
|
+
async function handleFeedback(helpful: boolean) {
|
|
11
|
+
setSelection(helpful ? 'yes' : 'no')
|
|
12
|
+
// TODO: Send to analytics
|
|
13
|
+
setTimeout(() => setSubmitted(true), 500)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (submitted) {
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex items-center gap-2 text-[13px] text-[var(--color-text-tertiary)]">
|
|
19
|
+
<Check size={16} className="text-emerald-500" />
|
|
20
|
+
Thanks for your feedback!
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex items-center gap-3">
|
|
27
|
+
<span className="text-[13px] text-[var(--color-text-tertiary)]">Was this helpful?</span>
|
|
28
|
+
<div className="flex items-center gap-1">
|
|
29
|
+
<button
|
|
30
|
+
onClick={() => handleFeedback(true)}
|
|
31
|
+
className={`p-1.5 rounded-md transition-colors ${
|
|
32
|
+
selection === 'yes'
|
|
33
|
+
? 'bg-emerald-100 text-emerald-600'
|
|
34
|
+
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]'
|
|
35
|
+
}`}
|
|
36
|
+
>
|
|
37
|
+
<ThumbsUp size={16} />
|
|
38
|
+
</button>
|
|
39
|
+
<button
|
|
40
|
+
onClick={() => handleFeedback(false)}
|
|
41
|
+
className={`p-1.5 rounded-md transition-colors ${
|
|
42
|
+
selection === 'no'
|
|
43
|
+
? 'bg-red-100 text-red-600'
|
|
44
|
+
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]'
|
|
45
|
+
}`}
|
|
46
|
+
>
|
|
47
|
+
<ThumbsDown size={16} />
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { Search, Menu, X } from 'lucide-react'
|
|
5
|
+
import { useState } from 'react'
|
|
6
|
+
import { SearchDialog } from './search-dialog'
|
|
7
|
+
import { ThemeToggle } from './theme-toggle'
|
|
8
|
+
import { SyntaxThemeSelector } from './syntax-theme-selector'
|
|
9
|
+
|
|
10
|
+
interface HeaderProps {
|
|
11
|
+
onMenuToggle?: () => void
|
|
12
|
+
menuOpen?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function Header({ onMenuToggle, menuOpen }: HeaderProps) {
|
|
16
|
+
const [searchOpen, setSearchOpen] = useState(false)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<header className="sticky top-0 z-50 border-b border-[var(--color-border)] bg-[var(--color-bg)]/95 backdrop-blur">
|
|
21
|
+
<div className="flex items-center justify-between h-16 px-4 md:px-6">
|
|
22
|
+
<div className="flex items-center gap-4">
|
|
23
|
+
{/* Mobile menu button */}
|
|
24
|
+
<button
|
|
25
|
+
onClick={onMenuToggle}
|
|
26
|
+
className="md:hidden p-2 -ml-2 text-[var(--color-text-secondary)] hover:text-[var(--color-text)]"
|
|
27
|
+
aria-label={menuOpen ? 'Close menu' : 'Open menu'}
|
|
28
|
+
aria-expanded={menuOpen}
|
|
29
|
+
>
|
|
30
|
+
{menuOpen ? <X size={20} /> : <Menu size={20} />}
|
|
31
|
+
</button>
|
|
32
|
+
|
|
33
|
+
<Link href="/" className="font-semibold text-lg">
|
|
34
|
+
Docs
|
|
35
|
+
</Link>
|
|
36
|
+
<nav className="hidden md:flex items-center gap-4">
|
|
37
|
+
<Link href="/docs" className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)]">
|
|
38
|
+
Documentation
|
|
39
|
+
</Link>
|
|
40
|
+
<Link href="/docs/api" className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)]">
|
|
41
|
+
API Reference
|
|
42
|
+
</Link>
|
|
43
|
+
</nav>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className="flex items-center gap-2">
|
|
47
|
+
<button
|
|
48
|
+
onClick={() => setSearchOpen(true)}
|
|
49
|
+
className="flex items-center gap-2 px-3 py-1.5 text-sm text-[var(--color-text-tertiary)] bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded-lg hover:border-[var(--color-border-strong)] transition-colors"
|
|
50
|
+
>
|
|
51
|
+
<Search size={16} />
|
|
52
|
+
<span className="hidden sm:inline">Search docs...</span>
|
|
53
|
+
<kbd className="hidden sm:inline px-1.5 py-0.5 text-xs bg-[var(--color-bg-tertiary)] rounded">
|
|
54
|
+
⌘K
|
|
55
|
+
</kbd>
|
|
56
|
+
</button>
|
|
57
|
+
<SyntaxThemeSelector />
|
|
58
|
+
<ThemeToggle />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</header>
|
|
62
|
+
|
|
63
|
+
<SearchDialog open={searchOpen} onClose={() => setSearchOpen(false)} />
|
|
64
|
+
</>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, ReactNode } from 'react'
|
|
4
|
+
import { ChevronDown } from 'lucide-react'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
|
|
7
|
+
interface AccordionProps {
|
|
8
|
+
title: string
|
|
9
|
+
defaultOpen?: boolean
|
|
10
|
+
children: ReactNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Accordion({ title, defaultOpen = false, children }: AccordionProps) {
|
|
14
|
+
const [open, setOpen] = useState(defaultOpen)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="border border-[var(--color-border)] rounded-lg my-4">
|
|
18
|
+
<button
|
|
19
|
+
onClick={() => setOpen(!open)}
|
|
20
|
+
className="flex items-center justify-between w-full px-4 py-3 text-left font-medium hover:bg-[var(--color-bg-secondary)] transition-colors"
|
|
21
|
+
>
|
|
22
|
+
{title}
|
|
23
|
+
<ChevronDown
|
|
24
|
+
size={20}
|
|
25
|
+
className={cn(
|
|
26
|
+
'text-[var(--color-text-tertiary)] transition-transform',
|
|
27
|
+
open && 'rotate-180'
|
|
28
|
+
)}
|
|
29
|
+
/>
|
|
30
|
+
</button>
|
|
31
|
+
{open && (
|
|
32
|
+
<div className="px-4 pb-4 pt-0 border-t border-[var(--color-border)]">
|
|
33
|
+
<div className="pt-3 text-[var(--color-text-secondary)]">
|
|
34
|
+
{children}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
)}
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface AccordionGroupProps {
|
|
43
|
+
children: ReactNode
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function AccordionGroup({ children }: AccordionGroupProps) {
|
|
47
|
+
return <div className="my-6 space-y-2">{children}</div>
|
|
48
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
2
|
+
type Status = 'stable' | 'beta' | 'deprecated' | 'experimental'
|
|
3
|
+
|
|
4
|
+
const methodColors: Record<Method, string> = {
|
|
5
|
+
GET: 'bg-emerald-100 text-emerald-700 border-emerald-200',
|
|
6
|
+
POST: 'bg-blue-100 text-blue-700 border-blue-200',
|
|
7
|
+
PUT: 'bg-amber-100 text-amber-700 border-amber-200',
|
|
8
|
+
PATCH: 'bg-purple-100 text-purple-700 border-purple-200',
|
|
9
|
+
DELETE: 'bg-red-100 text-red-700 border-red-200',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const statusColors: Record<Status, string> = {
|
|
13
|
+
stable: 'bg-emerald-100 text-emerald-700',
|
|
14
|
+
beta: 'bg-blue-100 text-blue-700',
|
|
15
|
+
deprecated: 'bg-red-100 text-red-700',
|
|
16
|
+
experimental: 'bg-purple-100 text-purple-700',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface MethodBadgeProps {
|
|
20
|
+
method: Method
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function MethodBadge({ method }: MethodBadgeProps) {
|
|
24
|
+
return (
|
|
25
|
+
<span className={`inline-block px-2 py-0.5 text-[11px] font-bold uppercase rounded border ${methodColors[method]}`}>
|
|
26
|
+
{method}
|
|
27
|
+
</span>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface StatusBadgeProps {
|
|
32
|
+
status: Status
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function StatusBadge({ status }: StatusBadgeProps) {
|
|
36
|
+
return (
|
|
37
|
+
<span className={`inline-block px-2 py-0.5 text-[11px] font-medium uppercase rounded ${statusColors[status]}`}>
|
|
38
|
+
{status}
|
|
39
|
+
</span>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface EndpointProps {
|
|
44
|
+
method: Method
|
|
45
|
+
path: string
|
|
46
|
+
status?: Status
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function Endpoint({ method, path, status }: EndpointProps) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex items-center gap-3 my-4 p-3 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded-lg">
|
|
52
|
+
<MethodBadge method={method} />
|
|
53
|
+
<code className="text-[14px] font-semibold">{path}</code>
|
|
54
|
+
{status && <StatusBadge status={status} />}
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
import {
|
|
3
|
+
Info as InfoIcon,
|
|
4
|
+
AlertTriangle,
|
|
5
|
+
CheckCircle,
|
|
6
|
+
XCircle,
|
|
7
|
+
Lightbulb
|
|
8
|
+
} from 'lucide-react'
|
|
9
|
+
import { ReactNode } from 'react'
|
|
10
|
+
|
|
11
|
+
type CalloutType = 'info' | 'warning' | 'success' | 'error' | 'tip' | 'note'
|
|
12
|
+
|
|
13
|
+
interface CalloutProps {
|
|
14
|
+
type?: CalloutType
|
|
15
|
+
title?: string
|
|
16
|
+
children: ReactNode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const config: Record<CalloutType, {
|
|
20
|
+
icon: typeof InfoIcon
|
|
21
|
+
bg: string
|
|
22
|
+
border: string
|
|
23
|
+
title: string
|
|
24
|
+
text: string
|
|
25
|
+
}> = {
|
|
26
|
+
info: {
|
|
27
|
+
icon: InfoIcon,
|
|
28
|
+
bg: 'bg-blue-50 dark:bg-blue-950/30',
|
|
29
|
+
border: 'border-blue-200 dark:border-blue-800',
|
|
30
|
+
title: 'text-blue-800 dark:text-blue-300',
|
|
31
|
+
text: 'text-blue-700 dark:text-blue-400',
|
|
32
|
+
},
|
|
33
|
+
warning: {
|
|
34
|
+
icon: AlertTriangle,
|
|
35
|
+
bg: 'bg-amber-50 dark:bg-amber-950/30',
|
|
36
|
+
border: 'border-amber-200 dark:border-amber-800',
|
|
37
|
+
title: 'text-amber-800 dark:text-amber-300',
|
|
38
|
+
text: 'text-amber-700 dark:text-amber-400',
|
|
39
|
+
},
|
|
40
|
+
success: {
|
|
41
|
+
icon: CheckCircle,
|
|
42
|
+
bg: 'bg-green-50 dark:bg-green-950/30',
|
|
43
|
+
border: 'border-green-200 dark:border-green-800',
|
|
44
|
+
title: 'text-green-800 dark:text-green-300',
|
|
45
|
+
text: 'text-green-700 dark:text-green-400',
|
|
46
|
+
},
|
|
47
|
+
error: {
|
|
48
|
+
icon: XCircle,
|
|
49
|
+
bg: 'bg-red-50 dark:bg-red-950/30',
|
|
50
|
+
border: 'border-red-200 dark:border-red-800',
|
|
51
|
+
title: 'text-red-800 dark:text-red-300',
|
|
52
|
+
text: 'text-red-700 dark:text-red-400',
|
|
53
|
+
},
|
|
54
|
+
tip: {
|
|
55
|
+
icon: Lightbulb,
|
|
56
|
+
bg: 'bg-purple-50 dark:bg-purple-950/30',
|
|
57
|
+
border: 'border-purple-200 dark:border-purple-800',
|
|
58
|
+
title: 'text-purple-800 dark:text-purple-300',
|
|
59
|
+
text: 'text-purple-700 dark:text-purple-400',
|
|
60
|
+
},
|
|
61
|
+
note: {
|
|
62
|
+
icon: InfoIcon,
|
|
63
|
+
bg: 'bg-gray-50 dark:bg-gray-900/50',
|
|
64
|
+
border: 'border-gray-200 dark:border-gray-700',
|
|
65
|
+
title: 'text-gray-800 dark:text-gray-200',
|
|
66
|
+
text: 'text-gray-700 dark:text-gray-300',
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function Callout({ type = 'info', title, children }: CalloutProps) {
|
|
71
|
+
const { icon: Icon, bg, border, title: titleColor, text } = config[type]
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className={cn('my-6 p-4 border rounded-lg', bg, border)}>
|
|
75
|
+
<div className="flex gap-3">
|
|
76
|
+
<Icon className={cn('w-5 h-5 mt-0.5 flex-shrink-0', titleColor)} />
|
|
77
|
+
<div>
|
|
78
|
+
{title && (
|
|
79
|
+
<div className={cn('font-medium mb-1', titleColor)}>{title}</div>
|
|
80
|
+
)}
|
|
81
|
+
<div className={cn('text-sm', text)}>{children}</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Convenience components
|
|
89
|
+
export function Info(props: Omit<CalloutProps, 'type'>) {
|
|
90
|
+
return <Callout type="info" {...props} />
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function Warning(props: Omit<CalloutProps, 'type'>) {
|
|
94
|
+
return <Callout type="warning" {...props} />
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function Success(props: Omit<CalloutProps, 'type'>) {
|
|
98
|
+
return <Callout type="success" {...props} />
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function Error(props: Omit<CalloutProps, 'type'>) {
|
|
102
|
+
return <Callout type="error" {...props} />
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function Tip(props: Omit<CalloutProps, 'type'>) {
|
|
106
|
+
return <Callout type="tip" {...props} />
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function Note(props: Omit<CalloutProps, 'type'>) {
|
|
110
|
+
return <Callout type="note" {...props} />
|
|
111
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
import { cn } from '@/lib/utils'
|
|
3
|
+
import * as Icons from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
interface CardProps {
|
|
6
|
+
title: string
|
|
7
|
+
icon?: keyof typeof Icons
|
|
8
|
+
href?: string
|
|
9
|
+
children?: React.ReactNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Card({ title, icon, href, children }: CardProps) {
|
|
13
|
+
const Icon = icon ? Icons[icon] as React.ComponentType<{ size?: number; className?: string }> : null
|
|
14
|
+
|
|
15
|
+
const content = (
|
|
16
|
+
<div className={cn(
|
|
17
|
+
'p-4 border border-[var(--color-border)] rounded-lg transition-colors',
|
|
18
|
+
href && 'hover:border-[var(--color-primary)] hover:bg-[var(--color-bg-secondary)] cursor-pointer'
|
|
19
|
+
)}>
|
|
20
|
+
<div className="flex items-start gap-3">
|
|
21
|
+
{Icon && (
|
|
22
|
+
<div className="p-2 bg-[var(--color-primary)]/10 text-[var(--color-primary)] rounded-lg">
|
|
23
|
+
<Icon size={20} />
|
|
24
|
+
</div>
|
|
25
|
+
)}
|
|
26
|
+
<div>
|
|
27
|
+
<h3 className="font-medium text-[var(--color-text)]">{title}</h3>
|
|
28
|
+
{children && (
|
|
29
|
+
<p className="mt-1 text-sm text-[var(--color-text-secondary)]">
|
|
30
|
+
{children}
|
|
31
|
+
</p>
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if (href) {
|
|
39
|
+
return <Link href={href}>{content}</Link>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return content
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface CardGroupProps {
|
|
46
|
+
cols?: 1 | 2 | 3
|
|
47
|
+
children: React.ReactNode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function CardGroup({ cols = 2, children }: CardGroupProps) {
|
|
51
|
+
const gridCols = {
|
|
52
|
+
1: 'grid-cols-1',
|
|
53
|
+
2: 'grid-cols-1 md:grid-cols-2',
|
|
54
|
+
3: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className={cn('grid gap-4 my-6', gridCols[cols])}>
|
|
59
|
+
{children}
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
interface ChangelogEntryProps {
|
|
4
|
+
version: string
|
|
5
|
+
date: string
|
|
6
|
+
children: ReactNode
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ChangelogEntry({ version, date, children }: ChangelogEntryProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="relative pl-6 pb-8 border-l-2 border-[var(--color-border)] last:pb-0">
|
|
12
|
+
<div className="absolute -left-[9px] top-0 w-4 h-4 rounded-full bg-[var(--color-primary)] border-2 border-[var(--color-bg)]" />
|
|
13
|
+
<div className="flex items-center gap-3 mb-2">
|
|
14
|
+
<span className="font-semibold text-[15px]">v{version}</span>
|
|
15
|
+
<span className="text-[13px] text-[var(--color-text-tertiary)]">{date}</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div className="text-[14px] text-[var(--color-text-secondary)]">
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ChangelogProps {
|
|
25
|
+
children: ReactNode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Changelog({ children }: ChangelogProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div className="my-8">
|
|
31
|
+
{children}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Change type badges
|
|
37
|
+
type ChangeType = 'added' | 'changed' | 'deprecated' | 'removed' | 'fixed' | 'security'
|
|
38
|
+
|
|
39
|
+
const typeColors: Record<ChangeType, string> = {
|
|
40
|
+
added: 'bg-emerald-100 text-emerald-700',
|
|
41
|
+
changed: 'bg-blue-100 text-blue-700',
|
|
42
|
+
deprecated: 'bg-yellow-100 text-yellow-700',
|
|
43
|
+
removed: 'bg-red-100 text-red-700',
|
|
44
|
+
fixed: 'bg-purple-100 text-purple-700',
|
|
45
|
+
security: 'bg-orange-100 text-orange-700',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function Change({ type, children }: { type: ChangeType; children: ReactNode }) {
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex items-start gap-2 my-1">
|
|
51
|
+
<span className={`px-1.5 py-0.5 text-[11px] font-medium uppercase rounded ${typeColors[type]}`}>
|
|
52
|
+
{type}
|
|
53
|
+
</span>
|
|
54
|
+
<span>{children}</span>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, ReactNode, isValidElement, Children } from 'react'
|
|
4
|
+
import { Copy, Check } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
interface CodeBlockProps {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
className?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function CodeBlock({ children, className }: CodeBlockProps) {
|
|
12
|
+
const [copied, setCopied] = useState(false)
|
|
13
|
+
|
|
14
|
+
// Extract code text from children
|
|
15
|
+
let codeText = ''
|
|
16
|
+
Children.forEach(children, (child) => {
|
|
17
|
+
if (isValidElement(child) && child.type === 'code') {
|
|
18
|
+
codeText = (child.props as { children?: string }).children || ''
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
async function handleCopy() {
|
|
23
|
+
await navigator.clipboard.writeText(codeText)
|
|
24
|
+
setCopied(true)
|
|
25
|
+
setTimeout(() => setCopied(false), 2000)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="relative group my-4">
|
|
30
|
+
<pre className={className}>
|
|
31
|
+
{children}
|
|
32
|
+
</pre>
|
|
33
|
+
<button
|
|
34
|
+
onClick={handleCopy}
|
|
35
|
+
className="absolute top-2 right-2 p-1.5 rounded bg-[var(--color-bg-tertiary)]/80 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] opacity-0 group-hover:opacity-100 transition-opacity"
|
|
36
|
+
title={copied ? 'Copied!' : 'Copy code'}
|
|
37
|
+
>
|
|
38
|
+
{copied ? <Check size={14} /> : <Copy size={14} />}
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, Children, isValidElement, ReactNode, ReactElement } from 'react'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { Copy, Check } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
interface CodeGroupProps {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface CodeBlockInfo {
|
|
12
|
+
language: string
|
|
13
|
+
filename?: string
|
|
14
|
+
code: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface PreProps {
|
|
18
|
+
children?: ReactElement<CodeProps>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface CodeProps {
|
|
22
|
+
className?: string
|
|
23
|
+
children?: string
|
|
24
|
+
'data-filename'?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractCodeBlocks(children: ReactNode): CodeBlockInfo[] {
|
|
28
|
+
const blocks: CodeBlockInfo[] = []
|
|
29
|
+
|
|
30
|
+
Children.forEach(children, (child) => {
|
|
31
|
+
if (isValidElement<PreProps>(child) && child.type === 'pre') {
|
|
32
|
+
const codeElement = child.props.children
|
|
33
|
+
if (isValidElement<CodeProps>(codeElement) && codeElement.type === 'code') {
|
|
34
|
+
const className = codeElement.props.className || ''
|
|
35
|
+
const match = className.match(/language-(\w+)/)
|
|
36
|
+
const language = match ? match[1] : 'text'
|
|
37
|
+
|
|
38
|
+
const filename = codeElement.props['data-filename'] ||
|
|
39
|
+
codeElement.props.children?.toString().match(/^\/\/ (.+)\n/)?.[1]
|
|
40
|
+
|
|
41
|
+
blocks.push({
|
|
42
|
+
language,
|
|
43
|
+
filename,
|
|
44
|
+
code: codeElement.props.children?.toString() || '',
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return blocks
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function CodeGroup({ children }: CodeGroupProps) {
|
|
54
|
+
const blocks = extractCodeBlocks(children)
|
|
55
|
+
const [activeIndex, setActiveIndex] = useState(0)
|
|
56
|
+
const [copied, setCopied] = useState(false)
|
|
57
|
+
|
|
58
|
+
if (blocks.length === 0) {
|
|
59
|
+
return <div>{children}</div>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const activeBlock = blocks[activeIndex]
|
|
63
|
+
|
|
64
|
+
const copyToClipboard = async () => {
|
|
65
|
+
await navigator.clipboard.writeText(activeBlock.code)
|
|
66
|
+
setCopied(true)
|
|
67
|
+
setTimeout(() => setCopied(false), 2000)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const getLanguageLabel = (lang: string, filename?: string) => {
|
|
71
|
+
if (filename) return filename
|
|
72
|
+
const labels: Record<string, string> = {
|
|
73
|
+
typescript: 'TypeScript',
|
|
74
|
+
javascript: 'JavaScript',
|
|
75
|
+
python: 'Python',
|
|
76
|
+
bash: 'Bash',
|
|
77
|
+
shell: 'Shell',
|
|
78
|
+
json: 'JSON',
|
|
79
|
+
yaml: 'YAML',
|
|
80
|
+
go: 'Go',
|
|
81
|
+
rust: 'Rust',
|
|
82
|
+
}
|
|
83
|
+
return labels[lang] || lang
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="my-6 border border-[var(--color-border)] rounded-lg overflow-hidden">
|
|
88
|
+
{/* Tab bar */}
|
|
89
|
+
<div className="flex items-center justify-between bg-[var(--color-bg-secondary)] border-b border-[var(--color-border)]">
|
|
90
|
+
<div className="flex">
|
|
91
|
+
{blocks.map((block, index) => (
|
|
92
|
+
<button
|
|
93
|
+
key={`${block.language}-${block.filename || index}`}
|
|
94
|
+
onClick={() => setActiveIndex(index)}
|
|
95
|
+
className={cn(
|
|
96
|
+
'px-4 py-2 text-sm font-medium transition-colors',
|
|
97
|
+
index === activeIndex
|
|
98
|
+
? 'bg-[var(--color-code-bg)] text-[var(--color-code-text)]'
|
|
99
|
+
: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text)]'
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{getLanguageLabel(block.language, block.filename)}
|
|
103
|
+
</button>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
<button
|
|
107
|
+
onClick={copyToClipboard}
|
|
108
|
+
className="p-2 mr-2 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] transition-colors"
|
|
109
|
+
title="Copy code"
|
|
110
|
+
>
|
|
111
|
+
{copied ? <Check size={16} /> : <Copy size={16} />}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Code content */}
|
|
116
|
+
<div className="bg-[var(--color-code-bg)]">
|
|
117
|
+
<pre className="!m-0 !rounded-none">
|
|
118
|
+
<code className={`language-${activeBlock.language}`}>
|
|
119
|
+
{activeBlock.code}
|
|
120
|
+
</code>
|
|
121
|
+
</pre>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|