chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface PageNeighbour {
|
||||
url: string
|
||||
name: ReactNode
|
||||
}
|
||||
|
||||
interface PageFooterProps {
|
||||
previous?: PageNeighbour
|
||||
next?: PageNeighbour
|
||||
}
|
||||
|
||||
const SOCIAL_LINKS = [
|
||||
{
|
||||
href: 'https://x.com/simdotai',
|
||||
label: 'X (Twitter)',
|
||||
icon: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/simstudioai/sim',
|
||||
label: 'GitHub',
|
||||
icon: 'M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z',
|
||||
},
|
||||
{
|
||||
href: 'https://discord.gg/Hr4UWYEcTT',
|
||||
label: 'Discord',
|
||||
icon: 'M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z',
|
||||
},
|
||||
] as const
|
||||
|
||||
export function PageFooter({ previous, next }: PageFooterProps) {
|
||||
return (
|
||||
<div className='mt-12'>
|
||||
<div className='h-px w-full bg-[var(--border)]' />
|
||||
|
||||
{(previous || next) && (
|
||||
<div className='flex gap-2 py-3'>
|
||||
{previous ? (
|
||||
<Link
|
||||
href={previous.url}
|
||||
className='group flex flex-1 flex-col gap-1 rounded-lg px-3 py-3 transition-colors hover:bg-[var(--surface-3)]'
|
||||
>
|
||||
<span className='text-[var(--text-muted)] text-xs'>Previous</span>
|
||||
<span className='flex items-center gap-1.5 text-[var(--text-body)] text-sm transition-colors group-hover:text-[var(--text-primary)]'>
|
||||
<ChevronLeft className='size-3.5 shrink-0' />
|
||||
{previous.name}
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div className='flex-1' />
|
||||
)}
|
||||
|
||||
{next ? (
|
||||
<Link
|
||||
href={next.url}
|
||||
className='group flex flex-1 flex-col items-end gap-1 rounded-lg px-3 py-3 transition-colors hover:bg-[var(--surface-3)]'
|
||||
>
|
||||
<span className='text-[var(--text-muted)] text-xs'>Next</span>
|
||||
<span className='flex items-center gap-1.5 text-[var(--text-body)] text-sm transition-colors group-hover:text-[var(--text-primary)]'>
|
||||
{next.name}
|
||||
<ChevronRight className='size-3.5 shrink-0' />
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div className='flex-1' />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='flex items-center gap-4 pt-4 pb-6'>
|
||||
{SOCIAL_LINKS.map((link) => (
|
||||
<Link
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
aria-label={link.label}
|
||||
>
|
||||
<svg
|
||||
viewBox='0 0 24 24'
|
||||
className='size-[16px] fill-[var(--text-muted)] transition-colors hover:fill-[var(--text-icon)]'
|
||||
>
|
||||
<path d={link.icon} />
|
||||
</svg>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface PageNavigationArrowsProps {
|
||||
previous?: {
|
||||
url: string
|
||||
}
|
||||
next?: {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
const ARROW_LINK_CLASS =
|
||||
'flex size-[30px] items-center justify-center rounded-lg text-[var(--text-icon)] transition-colors hover:bg-[var(--surface-active)]'
|
||||
|
||||
export function PageNavigationArrows({ previous, next }: PageNavigationArrowsProps) {
|
||||
if (!previous && !next) return null
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-2'>
|
||||
{previous && (
|
||||
<Link
|
||||
href={previous.url}
|
||||
className={ARROW_LINK_CLASS}
|
||||
aria-label='Previous page'
|
||||
title='Previous page'
|
||||
>
|
||||
<ChevronLeft className='size-[16px]' />
|
||||
</Link>
|
||||
)}
|
||||
{next && (
|
||||
<Link href={next.url} className={ARROW_LINK_CLASS} aria-label='Next page' title='Next page'>
|
||||
<ChevronRight className='size-[16px]' />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
'use client'
|
||||
|
||||
import { type ReactNode, useState } from 'react'
|
||||
import type { Folder, Item, Separator } from 'fumadocs-core/page-tree'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { i18n } from '@/lib/i18n'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function SidebarChevron({ open, className }: { open: boolean; className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width='5'
|
||||
height='8'
|
||||
viewBox='0 0 6 10'
|
||||
fill='none'
|
||||
className={cn(
|
||||
'flex-shrink-0 transition-transform duration-200',
|
||||
open && 'rotate-90',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<path
|
||||
d='M1 1L5 5L1 9'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.33'
|
||||
strokeLinecap='square'
|
||||
strokeLinejoin='miter'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const LANG_PREFIXES = i18n.languages.map((l) => `/${l}`)
|
||||
|
||||
function stripLangPrefix(path: string): string {
|
||||
for (const prefix of LANG_PREFIXES) {
|
||||
if (path === prefix) return '/'
|
||||
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
function isActive(url: string, pathname: string, nested = true): boolean {
|
||||
const normalizedPathname = stripLangPrefix(pathname)
|
||||
const normalizedUrl = stripLangPrefix(url)
|
||||
return (
|
||||
normalizedUrl === normalizedPathname ||
|
||||
(nested && normalizedPathname.startsWith(`${normalizedUrl}/`))
|
||||
)
|
||||
}
|
||||
|
||||
const ITEM_BASE =
|
||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-[var(--text-muted)] text-sm transition-colors hover:bg-[var(--surface-active)] hover:text-[var(--text-body)]'
|
||||
const ITEM_ACTIVE_MOBILE = 'bg-[var(--surface-active)] font-medium text-[var(--text-primary)]'
|
||||
|
||||
const ITEM_DESKTOP =
|
||||
'lg:mb-[0.0625rem] lg:block lg:rounded-lg lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight'
|
||||
const ITEM_TEXT = 'lg:text-[var(--text-body)]'
|
||||
const ITEM_HOVER = 'lg:hover:bg-[var(--surface-3)]'
|
||||
const ITEM_ACTIVE = 'lg:bg-[var(--surface-active)] lg:font-normal lg:text-[var(--text-body)]'
|
||||
|
||||
const FOLDER_TEXT = 'lg:text-[var(--text-body)] lg:font-medium'
|
||||
const FOLDER_HOVER = 'lg:hover:bg-[var(--surface-3)]'
|
||||
const FOLDER_ACTIVE = 'lg:bg-[var(--surface-active)] lg:text-[var(--text-body)]'
|
||||
|
||||
export function SidebarItem({ item }: { item: Item }) {
|
||||
const pathname = usePathname()
|
||||
const active = isActive(item.url, pathname, false)
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={item.url}
|
||||
data-active={active}
|
||||
className={cn(
|
||||
ITEM_BASE,
|
||||
active && ITEM_ACTIVE_MOBILE,
|
||||
ITEM_DESKTOP,
|
||||
ITEM_TEXT,
|
||||
!active && ITEM_HOVER,
|
||||
active && ITEM_ACTIVE
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function isApiReferenceFolder(node: Folder): boolean {
|
||||
if (node.index?.url.includes('/api-reference/')) return true
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'page' && child.url.includes('/api-reference/')) return true
|
||||
if (child.type === 'folder' && isApiReferenceFolder(child)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function SidebarFolder({ item, children }: { item: Folder; children: ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const hasActiveChild = checkHasActiveChild(item, pathname)
|
||||
const isApiRef = isApiReferenceFolder(item)
|
||||
const isOnApiRefPage = stripLangPrefix(pathname).startsWith('/api-reference')
|
||||
const hasChildren = item.children.length > 0
|
||||
const defaultOpen = hasActiveChild || (isApiRef && isOnApiRefPage)
|
||||
const [manualOpen, setManualOpen] = useState<{ pathname: string; open: boolean } | null>(null)
|
||||
const open = manualOpen?.pathname === pathname ? manualOpen.open : defaultOpen
|
||||
const toggleOpen = () => setManualOpen({ pathname, open: !open })
|
||||
const active = item.index ? isActive(item.index.url, pathname, false) : false
|
||||
|
||||
if (item.index && !hasChildren) {
|
||||
return (
|
||||
<Link
|
||||
href={item.index.url}
|
||||
data-active={active}
|
||||
className={cn(
|
||||
ITEM_BASE,
|
||||
active && ITEM_ACTIVE_MOBILE,
|
||||
ITEM_DESKTOP,
|
||||
ITEM_TEXT,
|
||||
!active && ITEM_HOVER,
|
||||
active && ITEM_ACTIVE
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col lg:mb-[0.0625rem]'>
|
||||
<div className='flex w-full items-center lg:gap-0.5'>
|
||||
{item.index ? (
|
||||
<>
|
||||
<Link
|
||||
href={item.index.url}
|
||||
data-active={active}
|
||||
className={cn(
|
||||
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-[var(--text-muted)] hover:bg-[var(--surface-active)] hover:text-[var(--text-body)]',
|
||||
active && ITEM_ACTIVE_MOBILE,
|
||||
'lg:block lg:flex-1 lg:rounded-lg lg:px-2.5 lg:py-1.5 lg:text-[13px] lg:leading-tight',
|
||||
FOLDER_TEXT,
|
||||
!active && FOLDER_HOVER,
|
||||
active && FOLDER_ACTIVE
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
{hasChildren && (
|
||||
<button
|
||||
onClick={toggleOpen}
|
||||
className={cn(
|
||||
'rounded-md p-1 hover:bg-[var(--surface-active)]',
|
||||
'lg:cursor-pointer lg:rounded-md lg:p-1 lg:transition-colors lg:hover:bg-[var(--surface-3)]'
|
||||
)}
|
||||
aria-label={open ? 'Collapse' : 'Expand'}
|
||||
>
|
||||
<SidebarChevron open={open} className='text-[var(--text-icon)]' />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
onClick={toggleOpen}
|
||||
className={cn(
|
||||
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-[var(--text-muted)] hover:bg-[var(--surface-active)]',
|
||||
'lg:flex lg:w-full lg:cursor-pointer lg:items-center lg:justify-between lg:rounded-lg lg:px-2.5 lg:py-1.5 lg:text-left lg:text-[13px] lg:leading-tight',
|
||||
FOLDER_TEXT,
|
||||
FOLDER_HOVER
|
||||
)}
|
||||
>
|
||||
<span>{item.name}</span>
|
||||
<SidebarChevron open={open} className='ml-auto text-[var(--text-icon)]' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{hasChildren && (
|
||||
<div
|
||||
className={cn(
|
||||
'grid transition-[grid-template-rows,opacity] duration-200 ease-in-out',
|
||||
open ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'
|
||||
)}
|
||||
>
|
||||
<div className='overflow-hidden'>
|
||||
<div className='ml-4 flex flex-col gap-0.5 lg:hidden'>{children}</div>
|
||||
<ul className='mt-0.5 ml-2 hidden space-y-[0.0625rem] border-[var(--surface-active)] border-l pl-2.5 lg:block'>
|
||||
{children}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarSeparator({ item }: { item: Separator }) {
|
||||
return (
|
||||
<div
|
||||
data-separator
|
||||
className={cn('mt-5 mb-1.5 px-2', 'lg:relative lg:mt-0 lg:mb-1.5 lg:px-[13px] lg:pt-0')}
|
||||
>
|
||||
<div className='separator-divider hidden'>
|
||||
<div className='h-[20px]' />
|
||||
<div className='h-px bg-[var(--surface-active)]' />
|
||||
<div className='h-[20px]' />
|
||||
</div>
|
||||
<p
|
||||
className={cn(
|
||||
'font-medium text-[var(--text-muted)] text-xs',
|
||||
'lg:font-semibold lg:text-[10px] lg:text-[var(--text-muted)] lg:uppercase lg:tracking-[0.06em]'
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function checkHasActiveChild(node: Folder, pathname: string): boolean {
|
||||
if (node.index && isActive(node.index.url, pathname)) {
|
||||
return true
|
||||
}
|
||||
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'page' && isActive(child.url, pathname)) {
|
||||
return true
|
||||
}
|
||||
if (child.type === 'folder' && checkHasActiveChild(child, pathname)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user