mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement(docs): align docs UI with the platform emcn design system (#4962)
* improvement(docs): align docs UI with the platform emcn design system * improvement(docs): build language dropdown options in render scope
This commit is contained in:
@@ -33,19 +33,17 @@ const SOCIAL_LINKS = [
|
||||
export function PageFooter({ previous, next }: PageFooterProps) {
|
||||
return (
|
||||
<div className='mt-12'>
|
||||
<div className='h-px w-full bg-[rgba(0,0,0,0.08)] dark:bg-[rgba(255,255,255,0.08)]' />
|
||||
<div className='h-px w-full bg-[var(--border)]' />
|
||||
|
||||
{(previous || next) && (
|
||||
<div className='flex'>
|
||||
<div className='flex gap-2 py-3'>
|
||||
{previous ? (
|
||||
<Link
|
||||
href={previous.url}
|
||||
className='group flex flex-1 flex-col gap-1 px-6 py-5 transition-colors hover:bg-[rgba(0,0,0,0.02)] dark:hover:bg-[rgba(255,255,255,0.03)]'
|
||||
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-[rgba(0,0,0,0.4)] text-xs dark:text-[rgba(255,255,255,0.35)]'>
|
||||
Previous
|
||||
</span>
|
||||
<span className='flex items-center gap-1.5 font-[470] text-[rgba(0,0,0,0.7)] text-sm transition-colors group-hover:text-[rgba(0,0,0,0.88)] dark:text-[rgba(255,255,255,0.7)] dark:group-hover:text-[rgba(255,255,255,0.92)]'>
|
||||
<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>
|
||||
@@ -54,19 +52,13 @@ export function PageFooter({ previous, next }: PageFooterProps) {
|
||||
<div className='flex-1' />
|
||||
)}
|
||||
|
||||
{previous && next && (
|
||||
<div className='w-px bg-[rgba(0,0,0,0.08)] dark:bg-[rgba(255,255,255,0.08)]' />
|
||||
)}
|
||||
|
||||
{next ? (
|
||||
<Link
|
||||
href={next.url}
|
||||
className='group flex flex-1 flex-col items-end gap-1 px-6 py-5 transition-colors hover:bg-[rgba(0,0,0,0.02)] dark:hover:bg-[rgba(255,255,255,0.03)]'
|
||||
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-[rgba(0,0,0,0.4)] text-xs dark:text-[rgba(255,255,255,0.35)]'>
|
||||
Next
|
||||
</span>
|
||||
<span className='flex items-center gap-1.5 font-[470] text-[rgba(0,0,0,0.7)] text-sm transition-colors group-hover:text-[rgba(0,0,0,0.88)] dark:text-[rgba(255,255,255,0.7)] dark:group-hover:text-[rgba(255,255,255,0.92)]'>
|
||||
<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>
|
||||
@@ -77,9 +69,7 @@ export function PageFooter({ previous, next }: PageFooterProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='h-px w-full bg-[rgba(0,0,0,0.08)] dark:bg-[rgba(255,255,255,0.08)]' />
|
||||
|
||||
<div className='flex items-center gap-4 pt-10 pb-6'>
|
||||
<div className='flex items-center gap-4 pt-4 pb-6'>
|
||||
{SOCIAL_LINKS.map((link) => (
|
||||
<Link
|
||||
key={link.label}
|
||||
@@ -90,7 +80,7 @@ export function PageFooter({ previous, next }: PageFooterProps) {
|
||||
>
|
||||
<svg
|
||||
viewBox='0 0 24 24'
|
||||
className='size-5 fill-neutral-400 transition-colors hover:fill-neutral-500 dark:fill-neutral-500 dark:hover:fill-neutral-400'
|
||||
className='size-[16px] fill-[var(--text-muted)] transition-colors hover:fill-[var(--text-icon)]'
|
||||
>
|
||||
<path d={link.icon} />
|
||||
</svg>
|
||||
|
||||
@@ -12,6 +12,9 @@ interface PageNavigationArrowsProps {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -20,21 +23,16 @@ export function PageNavigationArrows({ previous, next }: PageNavigationArrowsPro
|
||||
{previous && (
|
||||
<Link
|
||||
href={previous.url}
|
||||
className='inline-flex items-center justify-center rounded-[5px] border border-transparent px-2.5 py-2 text-foreground/40 transition-colors duration-200 hover:border-border/40 hover:bg-neutral-100 hover:text-foreground/70 dark:hover:bg-neutral-800'
|
||||
className={ARROW_LINK_CLASS}
|
||||
aria-label='Previous page'
|
||||
title='Previous page'
|
||||
>
|
||||
<ChevronLeft className='size-4' />
|
||||
<ChevronLeft className='size-[16px]' />
|
||||
</Link>
|
||||
)}
|
||||
{next && (
|
||||
<Link
|
||||
href={next.url}
|
||||
className='inline-flex items-center justify-center rounded-[5px] border border-transparent px-2.5 py-2 text-foreground/40 transition-colors duration-200 hover:border-border/40 hover:bg-neutral-100 hover:text-foreground/70 dark:hover:bg-neutral-800'
|
||||
aria-label='Next page'
|
||||
title='Next page'
|
||||
>
|
||||
<ChevronRight className='size-4' />
|
||||
<Link href={next.url} className={ARROW_LINK_CLASS} aria-label='Next page' title='Next page'>
|
||||
<ChevronRight className='size-[16px]' />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -51,20 +51,18 @@ function isActive(url: string, pathname: string, nested = true): boolean {
|
||||
}
|
||||
|
||||
const ITEM_BASE =
|
||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground'
|
||||
const ITEM_ACTIVE_MOBILE = 'bg-fd-primary/10 font-medium text-fd-primary'
|
||||
'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-[#3b3b3b] lg:dark:text-[#cdcdcd]'
|
||||
const ITEM_HOVER = 'lg:hover:bg-[#f2f2f2] lg:dark:hover:bg-[#262626]'
|
||||
const ITEM_ACTIVE =
|
||||
'lg:bg-[#ececec] lg:font-normal lg:text-[#3b3b3b] lg:dark:bg-[#2c2c2c] lg:dark:text-[#cdcdcd]'
|
||||
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-[#3b3b3b] lg:font-medium lg:dark:text-[#cdcdcd]'
|
||||
const FOLDER_HOVER = 'lg:hover:bg-[#f2f2f2] lg:dark:hover:bg-[#262626]'
|
||||
const FOLDER_ACTIVE =
|
||||
'lg:bg-[#ececec] lg:text-[#3b3b3b] lg:dark:bg-[#2c2c2c] lg:dark:text-[#cdcdcd]'
|
||||
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()
|
||||
@@ -138,7 +136,7 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
|
||||
data-active={active}
|
||||
className={cn(
|
||||
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
|
||||
'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,
|
||||
@@ -152,12 +150,12 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
|
||||
<button
|
||||
onClick={toggleOpen}
|
||||
className={cn(
|
||||
'rounded p-1 hover:bg-fd-accent/50',
|
||||
'lg:cursor-pointer lg:rounded lg:p-1 lg:transition-colors lg:hover:bg-[#f2f2f2] lg:dark:hover:bg-[#262626]'
|
||||
'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-[#5e5e5e] dark:text-[#939393]' />
|
||||
<SidebarChevron open={open} className='text-[var(--text-icon)]' />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
@@ -166,14 +164,14 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
|
||||
onClick={toggleOpen}
|
||||
className={cn(
|
||||
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-fd-muted-foreground hover:bg-fd-accent/50',
|
||||
'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-[#5e5e5e] dark:text-[#939393]' />
|
||||
<SidebarChevron open={open} className='ml-auto text-[var(--text-icon)]' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -186,7 +184,7 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac
|
||||
>
|
||||
<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-[#ececec] border-l pl-2.5 lg:block dark:border-[#2c2c2c]'>
|
||||
<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>
|
||||
@@ -204,13 +202,13 @@ export function SidebarSeparator({ item }: { item: Separator }) {
|
||||
>
|
||||
<div className='separator-divider hidden'>
|
||||
<div className='h-[20px]' />
|
||||
<div className='h-px bg-[#ececec] dark:bg-[#2c2c2c]' />
|
||||
<div className='h-px bg-[var(--surface-active)]' />
|
||||
<div className='h-[20px]' />
|
||||
</div>
|
||||
<p
|
||||
className={cn(
|
||||
'font-medium text-fd-muted-foreground text-xs',
|
||||
'lg:font-semibold lg:text-[#5e5e5e] lg:text-[10px] lg:uppercase lg:tracking-[0.06em] lg:dark:text-[#939393]'
|
||||
'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}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { ChipLink } from '@/components/ui/chip'
|
||||
import { LanguageDropdown } from '@/components/ui/language-dropdown'
|
||||
import { SearchTrigger } from '@/components/ui/search-trigger'
|
||||
import { SimLogoFull } from '@/components/ui/sim-logo'
|
||||
@@ -27,7 +28,7 @@ export function Navbar() {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<nav className='sticky top-0 z-50 bg-background/80 backdrop-blur-md backdrop-saturate-150'>
|
||||
<nav className='sticky top-0 z-50 bg-[var(--bg)]/80 backdrop-blur-md backdrop-saturate-150'>
|
||||
<div className='hidden w-full flex-col lg:flex'>
|
||||
{/* Top row: logo, search, controls */}
|
||||
<div
|
||||
@@ -48,18 +49,15 @@ export function Navbar() {
|
||||
<div className='flex items-center gap-1'>
|
||||
<LanguageDropdown />
|
||||
<ThemeToggle />
|
||||
<Link
|
||||
href='https://sim.ai'
|
||||
className='ml-1 flex items-center rounded-[8px] bg-[#33c482] px-2.5 py-1.5 text-[13px] text-white transition-colors duration-200 hover:bg-[#2DAC72]'
|
||||
>
|
||||
<ChipLink href='https://sim.ai' variant='brand' className='ml-1'>
|
||||
Get started
|
||||
</Link>
|
||||
</ChipLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom row: navigation tabs — border on row, tabs overlap it */}
|
||||
<div
|
||||
className='flex h-[40px] items-stretch gap-6 border-border/20 border-b'
|
||||
className='flex h-[40px] items-stretch gap-6 border-[var(--border)]/20 border-b'
|
||||
style={{
|
||||
paddingLeft: 'calc(var(--sidebar-offset) + var(--nav-inset))',
|
||||
}}
|
||||
@@ -74,8 +72,8 @@ export function Navbar() {
|
||||
className={cn(
|
||||
'-mb-px relative flex items-center border-b text-[14px] tracking-[-0.01em] transition-colors',
|
||||
isActive
|
||||
? 'border-neutral-400 font-[480] text-neutral-800 dark:border-neutral-500 dark:text-neutral-200'
|
||||
: 'border-transparent font-[430] text-neutral-500 hover:border-neutral-300 hover:text-neutral-600 dark:text-neutral-400 dark:hover:border-neutral-600 dark:hover:text-neutral-300'
|
||||
? 'border-[var(--text-muted)] font-[480] text-[var(--text-primary)]'
|
||||
: 'border-transparent font-[430] text-[var(--text-muted)] hover:border-[var(--border-1)] hover:text-[var(--text-secondary)]'
|
||||
)}
|
||||
>
|
||||
{/* Invisible bold text reserves width to prevent layout shift */}
|
||||
|
||||
@@ -2,27 +2,18 @@
|
||||
|
||||
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import { Chip } from '@/components/ui/chip'
|
||||
|
||||
export function LLMCopyButton({ content }: { content: string }) {
|
||||
const [checked, onClick] = useCopyButton(() => navigator.clipboard.writeText(content))
|
||||
|
||||
return (
|
||||
<button
|
||||
<Chip
|
||||
onClick={onClick}
|
||||
className='flex cursor-pointer items-center gap-1.5 rounded-[5px] border border-transparent px-2.5 py-2 text-[13px] text-foreground/40 leading-none transition-colors duration-200 hover:border-border/40 hover:bg-neutral-100 hover:text-foreground/70 dark:hover:bg-neutral-800'
|
||||
leftIcon={checked ? Check : Copy}
|
||||
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
|
||||
>
|
||||
{checked ? (
|
||||
<>
|
||||
<Check className='size-3.5' />
|
||||
<span>Copied</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className='size-3.5' />
|
||||
<span>Copy page</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{checked ? 'Copied' : 'Copy page'}
|
||||
</Chip>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import type { DocsPageType } from '@/lib/source'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* Status-color mapping mirrored from the emcn `Badge` variants
|
||||
* (`apps/sim/components/emcn/components/badge/badge.tsx`) — `green`, `blue`,
|
||||
* `purple`, and `amber` over the shared `--badge-*` tokens.
|
||||
*/
|
||||
const CONFIG = {
|
||||
tutorial: { label: 'Tutorial', className: 'text-[#33c482] border-[#33c482]/30 bg-[#33c482]/10' },
|
||||
tutorial: {
|
||||
label: 'Tutorial',
|
||||
className: 'bg-[var(--badge-success-bg)] text-[var(--badge-success-text)]',
|
||||
},
|
||||
guide: {
|
||||
label: 'Guide',
|
||||
className: 'text-blue-600 border-blue-500/30 bg-blue-500/10 dark:text-blue-400',
|
||||
className: 'bg-[var(--badge-blue-bg)] text-[var(--badge-blue-text)]',
|
||||
},
|
||||
reference: {
|
||||
label: 'Reference',
|
||||
className: 'text-violet-600 border-violet-500/30 bg-violet-500/10 dark:text-violet-400',
|
||||
className: 'bg-[var(--badge-purple-bg)] text-[var(--badge-purple-text)]',
|
||||
},
|
||||
concept: {
|
||||
label: 'Concept',
|
||||
className: 'text-amber-600 border-amber-500/30 bg-amber-500/10 dark:text-amber-400',
|
||||
className: 'bg-[var(--badge-amber-bg)] text-[var(--badge-amber-text)]',
|
||||
},
|
||||
} as const satisfies Record<DocsPageType, { label: string; className: string }>
|
||||
|
||||
@@ -25,6 +33,7 @@ interface PageTypeBadgeProps {
|
||||
/**
|
||||
* Small label that tells the reader which Diátaxis mode a page is — learning,
|
||||
* task, lookup, or understanding. Rendered only when a page declares `type`.
|
||||
* Chrome matches the emcn `Badge` status variants (md size).
|
||||
*/
|
||||
export function PageTypeBadge({ type, className }: PageTypeBadgeProps) {
|
||||
const config = CONFIG[type]
|
||||
@@ -33,7 +42,7 @@ export function PageTypeBadge({ type, className }: PageTypeBadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center rounded-full border px-2 py-0.5 font-medium text-[11px] uppercase tracking-[0.04em]',
|
||||
'inline-flex items-center gap-1.5 rounded-md px-[9px] py-0.5 font-medium font-season text-[12px] transition-colors',
|
||||
config.className,
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function ActionImage({ src, alt, enableLightbox = true }: ActionImageProp
|
||||
src={src}
|
||||
alt={alt}
|
||||
className={cn(
|
||||
'inline-block w-full max-w-[200px] rounded border border-neutral-200 dark:border-neutral-700',
|
||||
'inline-block w-full max-w-[200px] rounded-lg border border-[var(--border-1)]',
|
||||
enableLightbox && 'transition-opacity group-hover:opacity-90'
|
||||
)}
|
||||
/>
|
||||
@@ -79,7 +79,7 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
|
||||
muted
|
||||
playsInline
|
||||
className={cn(
|
||||
'inline-block w-full max-w-[200px] rounded border border-neutral-200 dark:border-neutral-700',
|
||||
'inline-block w-full max-w-[200px] rounded-lg border border-[var(--border-1)]',
|
||||
enableLightbox && 'transition-opacity group-hover:opacity-90'
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
'use client'
|
||||
|
||||
import { type ComponentType, forwardRef, type ReactNode } from 'react'
|
||||
import { Check } from 'lucide-react'
|
||||
import { ChipChevronDown, chipVariants, TRIGGER_BORDER_CLASS } from '@/components/ui/chip'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type ChipIcon = ComponentType<{ className?: string }>
|
||||
|
||||
/**
|
||||
* Single option rendered inside a {@link ChipDropdown}.
|
||||
*/
|
||||
interface ChipDropdownOption {
|
||||
/** Stable identifier returned via `onChange`. */
|
||||
value: string
|
||||
/** Visual label rendered both inside the trigger (when selected) and in the menu. */
|
||||
label: ReactNode
|
||||
/** Optional leading icon rendered inside the menu item. */
|
||||
icon?: ChipIcon
|
||||
/** Pre-rendered leading element (e.g. a flag) — takes precedence over `icon`. */
|
||||
iconElement?: ReactNode
|
||||
}
|
||||
|
||||
interface ChipDropdownProps {
|
||||
/** Options to render in the menu. */
|
||||
options: ReadonlyArray<ChipDropdownOption>
|
||||
/** Currently selected value. */
|
||||
value?: string
|
||||
/** Called when the user picks a different option from the menu. */
|
||||
onChange?: (value: string) => void
|
||||
/** Shown in the trigger when nothing is selected. */
|
||||
placeholder?: string
|
||||
/** Aligns the dropdown popover relative to the trigger. */
|
||||
align?: 'start' | 'center' | 'end'
|
||||
/**
|
||||
* Whether the menu width should match the trigger's. When `true` (default),
|
||||
* the menu pins to `--radix-dropdown-menu-trigger-width`. Set `false` to let
|
||||
* the menu size to its widest item.
|
||||
*/
|
||||
matchTriggerWidth?: boolean
|
||||
/** Forwarded to the inner `DropdownMenuContent`. */
|
||||
contentClassName?: string
|
||||
/** Disables the trigger. */
|
||||
disabled?: boolean
|
||||
/** Optional icon rendered before the label. */
|
||||
leftIcon?: ChipIcon
|
||||
/** Forwarded class for the trigger button — layout/sizing only. */
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Docs-local mirror of the emcn `ChipDropdown`
|
||||
* (`apps/sim/components/emcn/components/chip-dropdown/chip-dropdown.tsx`) — a
|
||||
* 30px filled chip pill that opens a menu of options. The trigger reuses
|
||||
* `chipVariants` for visual parity with `Chip`; the chevron is owned by the
|
||||
* component and rendered in a 16px slot so the label's trailing gap matches a
|
||||
* leading icon's.
|
||||
*
|
||||
* @example
|
||||
* <ChipDropdown
|
||||
* value={currentLang}
|
||||
* onChange={setLanguage}
|
||||
* options={LANGUAGE_OPTIONS}
|
||||
* align='end'
|
||||
* matchTriggerWidth={false}
|
||||
* />
|
||||
*/
|
||||
const ChipDropdown = forwardRef<HTMLButtonElement, ChipDropdownProps>(function ChipDropdown(
|
||||
{
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
align = 'end',
|
||||
matchTriggerWidth = true,
|
||||
contentClassName,
|
||||
disabled,
|
||||
leftIcon: LeftIcon,
|
||||
className,
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const selected = options.find((option) => option.value === value)
|
||||
const displayLabel = selected?.label ?? placeholder ?? 'Select...'
|
||||
const isPlaceholder = selected == null
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild disabled={disabled}>
|
||||
<button
|
||||
ref={ref}
|
||||
type='button'
|
||||
disabled={disabled}
|
||||
className={cn(chipVariants({ variant: 'filled' }), TRIGGER_BORDER_CLASS, className)}
|
||||
>
|
||||
{LeftIcon ? (
|
||||
<LeftIcon className='size-[16px] flex-shrink-0 text-[var(--text-icon)]' />
|
||||
) : null}
|
||||
<span
|
||||
className={cn(
|
||||
'min-w-0 flex-1 truncate text-sm',
|
||||
isPlaceholder ? 'text-[var(--text-muted)]' : 'text-[var(--text-body)]'
|
||||
)}
|
||||
>
|
||||
{displayLabel}
|
||||
</span>
|
||||
<ChipChevronDown />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align={align}
|
||||
className={cn(
|
||||
matchTriggerWidth && 'w-[var(--radix-dropdown-menu-trigger-width)]',
|
||||
contentClassName
|
||||
)}
|
||||
>
|
||||
{options.map((option) => {
|
||||
const isSelected = option.value === value
|
||||
const OptionIcon = option.icon
|
||||
return (
|
||||
<DropdownMenuItem key={option.value} onSelect={() => onChange?.(option.value)}>
|
||||
{option.iconElement ?? (OptionIcon ? <OptionIcon /> : null)}
|
||||
<span>{option.label}</span>
|
||||
{isSelected ? <Check className='!ml-auto !size-[16px]' /> : null}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
})
|
||||
|
||||
export { ChipDropdown }
|
||||
export type { ChipDropdownOption, ChipDropdownProps }
|
||||
@@ -0,0 +1,172 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
type AnchorHTMLAttributes,
|
||||
type ButtonHTMLAttributes,
|
||||
type ComponentType,
|
||||
forwardRef,
|
||||
type ReactNode,
|
||||
} from 'react'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import Link, { type LinkProps } from 'next/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* Docs-local mirror of the platform chip chrome
|
||||
* (`apps/sim/components/emcn/components/chip/chip-chrome.ts`). The docs app
|
||||
* cannot import from `apps/sim`, so the canonical tokens are replicated here —
|
||||
* keep them in sync with the emcn source.
|
||||
*/
|
||||
export const chipContentGap = 'gap-1.5'
|
||||
/** Chip pill geometry — height, centering, gap, radius, padding, text size. */
|
||||
export const chipGeometryClass = `h-[30px] items-center ${chipContentGap} rounded-lg px-2 text-left text-sm`
|
||||
/** Chip-content icon (non-inverse): 16px, non-shrinking, `--text-icon`. */
|
||||
export const chipContentIconClass = 'size-[16px] flex-shrink-0 text-[var(--text-icon)]'
|
||||
/** Chip-content label (non-inverse): truncating `--text-body` at `text-sm`. */
|
||||
export const chipContentLabelClass = 'min-w-0 truncate text-[var(--text-body)] text-sm'
|
||||
/** The filled FILL (surface only, no border) — `--surface-5` light / `--surface-4` dark. */
|
||||
export const chipFilledFillTokens = 'bg-[var(--surface-5)] dark:bg-[var(--surface-4)]'
|
||||
/** 1px `--border-1` border applied to chip triggers so they read as controls. */
|
||||
export const TRIGGER_BORDER_CLASS = 'border border-[var(--border-1)]'
|
||||
|
||||
/**
|
||||
* 30px pill — the platform's most common chrome pattern, mirrored for the docs.
|
||||
*
|
||||
* @remarks
|
||||
* The implicit default variant is the bare pill — transparent, `--surface-active`
|
||||
* on hover. `filled` adds the filled surface; `brand` is the green CTA surface
|
||||
* (`--brand-accent`) used for the "Get started" link.
|
||||
*/
|
||||
const chipVariants = cva(
|
||||
`group cursor-pointer font-season ${chipGeometryClass} transition-colors disabled:cursor-not-allowed disabled:opacity-60`,
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'hover:bg-[var(--surface-active)]',
|
||||
filled: `${chipFilledFillTokens} hover:bg-[var(--surface-active)]`,
|
||||
brand:
|
||||
'bg-[var(--brand-accent)] text-white hover:bg-[var(--brand-accent-hover)] hover:text-white',
|
||||
},
|
||||
fullWidth: { true: 'flex', false: 'inline-flex' },
|
||||
},
|
||||
defaultVariants: { variant: 'default', fullWidth: false },
|
||||
}
|
||||
)
|
||||
|
||||
type ChipIcon = ComponentType<{ className?: string }>
|
||||
|
||||
type ChipVariant = VariantProps<typeof chipVariants>['variant']
|
||||
|
||||
interface ChipBaseProps extends VariantProps<typeof chipVariants> {
|
||||
/** Icon component rendered before the label. */
|
||||
leftIcon?: ChipIcon
|
||||
/** Icon component rendered after the label. */
|
||||
rightIcon?: ChipIcon
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* `brand` sets text color on the chip itself — its icon and label inherit via
|
||||
* `currentColor`. The default and `filled` chips use explicit icon
|
||||
* (`--text-icon`) and label (`--text-body`) colors.
|
||||
*/
|
||||
function ChipContent({
|
||||
variant,
|
||||
leftIcon: LeftIcon,
|
||||
rightIcon: RightIcon,
|
||||
children,
|
||||
}: ChipBaseProps) {
|
||||
const isInverse = variant === 'brand'
|
||||
const iconClass = cn(chipContentIconClass, isInverse && 'text-current')
|
||||
const labelClass = cn(chipContentLabelClass, 'flex-1', isInverse && 'text-current')
|
||||
return (
|
||||
<>
|
||||
{LeftIcon ? <LeftIcon className={iconClass} /> : null}
|
||||
{children != null && children !== false ? (
|
||||
<span className={labelClass}>{children}</span>
|
||||
) : null}
|
||||
{RightIcon ? <RightIcon className={iconClass} /> : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface ChipProps
|
||||
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'>,
|
||||
ChipBaseProps {}
|
||||
|
||||
/**
|
||||
* @example <Chip leftIcon={Copy} onClick={copyPage}>Copy page</Chip>
|
||||
*/
|
||||
const Chip = forwardRef<HTMLButtonElement, ChipProps>(function Chip(
|
||||
{ className, variant, fullWidth, leftIcon, rightIcon, children, type, ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type={type ?? 'button'}
|
||||
className={cn(chipVariants({ variant, fullWidth }), className)}
|
||||
{...props}
|
||||
>
|
||||
<ChipContent variant={variant} leftIcon={leftIcon} rightIcon={rightIcon}>
|
||||
{children}
|
||||
</ChipContent>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
interface ChipLinkProps
|
||||
extends Omit<LinkProps, 'children'>,
|
||||
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps | 'children'>,
|
||||
ChipBaseProps {}
|
||||
|
||||
/**
|
||||
* @example <ChipLink href='https://sim.ai' variant='brand'>Get started</ChipLink>
|
||||
*/
|
||||
const ChipLink = forwardRef<HTMLAnchorElement, ChipLinkProps>(function ChipLink(
|
||||
{ className, variant, fullWidth, leftIcon, rightIcon, children, ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<Link ref={ref} className={cn(chipVariants({ variant, fullWidth }), className)} {...props}>
|
||||
<ChipContent variant={variant} leftIcon={leftIcon} rightIcon={rightIcon}>
|
||||
{children}
|
||||
</ChipContent>
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
|
||||
interface ChipChevronDownProps {
|
||||
/** Layout-only extras (e.g. `ml-auto` to push the chevron flush right). Never chrome. */
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonical trailing chevron adornment for chip-style dropdown triggers — a
|
||||
* 16px hidden-from-AT slot centering the 10×6 chevron glyph in `--text-icon`,
|
||||
* matching the chevron `ChipDropdown` owns in the main app.
|
||||
*/
|
||||
export function ChipChevronDown({ className }: ChipChevronDownProps) {
|
||||
return (
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'inline-flex size-[16px] flex-shrink-0 items-center justify-center text-[var(--text-icon)]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<svg width='10' height='6' viewBox='0 0 10 6' fill='none'>
|
||||
<path
|
||||
d='M1 1L5 5L9 1'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.33'
|
||||
strokeLinecap='square'
|
||||
strokeLinejoin='miter'
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export { Chip, ChipLink, chipVariants }
|
||||
export type { ChipLinkProps, ChipProps, ChipVariant }
|
||||
@@ -17,7 +17,7 @@ export function CodeBlock(props: React.ComponentProps<typeof FumadocsCodeBlock>)
|
||||
return (
|
||||
<FumadocsCodeBlock
|
||||
{...props}
|
||||
className={cn('!border !border-fd-border !shadow-none', props.className)}
|
||||
className={cn('!border !border-[var(--border)] !shadow-none', props.className)}
|
||||
Actions={({ className }) => (
|
||||
<div className={cn('empty:hidden', className)}>
|
||||
<button
|
||||
@@ -27,13 +27,13 @@ export function CodeBlock(props: React.ComponentProps<typeof FumadocsCodeBlock>)
|
||||
const pre = (e.currentTarget as HTMLElement).closest('figure')?.querySelector('pre')
|
||||
if (pre) handleCopy(pre.textContent || '')
|
||||
}}
|
||||
className='cursor-pointer rounded-md p-2 text-muted-foreground transition-colors hover:text-foreground'
|
||||
className='cursor-pointer rounded-md p-2 text-[var(--text-muted)] transition-colors hover:text-[var(--text-icon)]'
|
||||
>
|
||||
<span className='flex items-center justify-center'>
|
||||
{copied ? (
|
||||
<Check size={16} className='text-green-600 dark:text-green-400' />
|
||||
<Check size={16} className='text-[var(--brand-accent)]' />
|
||||
) : (
|
||||
<Copy size={16} className='text-muted-foreground' />
|
||||
<Copy size={16} className='text-[var(--text-muted)]' />
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,14 @@ import { cn } from '@/lib/utils'
|
||||
const ANIMATION_CLASSES =
|
||||
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=open]:animate-in motion-reduce:animate-none'
|
||||
|
||||
/**
|
||||
* Menu surface mirrored from the emcn `DropdownMenu`
|
||||
* (`apps/sim/components/emcn/components/dropdown-menu/dropdown-menu.tsx`) —
|
||||
* `--bg` surface, `--border` outline, `rounded-xl`, `p-1.5`.
|
||||
*/
|
||||
const CONTENT_BASE_CLASSES =
|
||||
'z-50 max-h-[240px] min-w-[8rem] origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-y-auto overflow-x-hidden overscroll-none border border-[var(--border)] bg-[var(--bg)] p-1.5 font-season text-[var(--text-body)] shadow-sm'
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||
|
||||
@@ -23,7 +31,8 @@ function DropdownMenuContent({
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
ANIMATION_CLASSES,
|
||||
'z-50 max-h-[240px] min-w-[8rem] max-w-[220px] origin-[--radix-dropdown-menu-content-transform-origin] overflow-y-auto overflow-x-hidden rounded-lg border border-neutral-200 bg-white p-1.5 shadow-sm dark:border-neutral-800 dark:bg-neutral-900',
|
||||
CONTENT_BASE_CLASSES,
|
||||
'max-w-[220px] rounded-xl',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -41,7 +50,7 @@ function DropdownMenuItem({
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex min-w-0 cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-1.5 font-medium text-[13px] text-neutral-700 outline-none transition-colors focus:bg-neutral-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:text-neutral-300 dark:focus:bg-neutral-800',
|
||||
'relative flex h-[30px] min-w-0 cursor-pointer select-none items-center gap-2 rounded-lg px-2 text-[13px] text-[var(--text-body)] outline-none transition-colors focus:bg-[var(--surface-active)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>span]:min-w-0 [&>span]:truncate [&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:text-[var(--text-icon)]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -30,11 +30,11 @@ function FAQItemRow({
|
||||
type='button'
|
||||
onClick={onToggle}
|
||||
aria-expanded={isOpen}
|
||||
className='flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-left font-[470] text-[0.875rem] text-[rgba(0,0,0,0.8)] transition-colors hover:bg-[rgba(0,0,0,0.02)] dark:text-[rgba(255,255,255,0.85)] dark:hover:bg-[rgba(255,255,255,0.03)]'
|
||||
className='flex w-full cursor-pointer items-center gap-3 px-4 py-2.5 text-left font-[470] text-[0.875rem] text-[var(--text-body)] transition-colors hover:bg-[var(--surface-3)]'
|
||||
>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
'h-3.5 w-3.5 shrink-0 text-[rgba(0,0,0,0.3)] transition-transform duration-200 dark:text-[rgba(255,255,255,0.3)]',
|
||||
'size-[14px] shrink-0 text-[var(--text-icon)] transition-transform duration-200',
|
||||
isOpen && 'rotate-90'
|
||||
)}
|
||||
/>
|
||||
@@ -48,7 +48,7 @@ function FAQItemRow({
|
||||
}}
|
||||
>
|
||||
<div className='overflow-hidden'>
|
||||
<div className='px-4 pt-2 pb-2.5 pl-11 text-[0.875rem] text-[rgba(0,0,0,0.7)] leading-relaxed dark:text-[rgba(255,255,255,0.7)]'>
|
||||
<div className='px-4 pt-2 pb-2.5 pl-11 text-[0.875rem] text-[var(--text-secondary)] leading-relaxed'>
|
||||
{item.answer}
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,14 +80,11 @@ export function FAQ({ items, title = 'Common Questions' }: FAQProps) {
|
||||
dangerouslySetInnerHTML={{ __html: serializeJsonLd(faqSchema) }}
|
||||
/>
|
||||
<h2 className='mb-4 font-[500] text-xl'>{title}</h2>
|
||||
<div className='border-[rgba(0,0,0,0.08)] border-t border-b dark:border-[rgba(255,255,255,0.08)]'>
|
||||
<div className='border-[var(--border)] border-t border-b'>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={item.question}
|
||||
className={cn(
|
||||
index !== items.length - 1 &&
|
||||
'border-[rgba(0,0,0,0.08)] border-b dark:border-[rgba(255,255,255,0.08)]'
|
||||
)}
|
||||
className={cn(index !== items.length - 1 && 'border-[var(--border)] border-b')}
|
||||
>
|
||||
<FAQItemRow
|
||||
item={item}
|
||||
|
||||
@@ -45,12 +45,12 @@ export function Heading({ as, className, ...props }: HeadingProps) {
|
||||
{copied ? (
|
||||
<Check
|
||||
aria-hidden
|
||||
className='size-3.5 shrink-0 text-green-500 opacity-100 transition-opacity'
|
||||
className='size-[14px] shrink-0 text-[var(--brand-accent)] opacity-100 transition-opacity'
|
||||
/>
|
||||
) : (
|
||||
<Link
|
||||
aria-hidden
|
||||
className='size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 peer-hover:opacity-100'
|
||||
className='size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-opacity group-hover:opacity-100 peer-hover:opacity-100'
|
||||
/>
|
||||
)}
|
||||
</As>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Image({
|
||||
const image = (
|
||||
<NextImage
|
||||
className={cn(
|
||||
'overflow-hidden rounded-xl border border-border object-cover',
|
||||
'overflow-hidden rounded-xl border border-[var(--border)] object-cover',
|
||||
enableLightbox && 'cursor-pointer transition-opacity group-hover:opacity-95',
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { Check } from 'lucide-react'
|
||||
import { useParams, usePathname, useRouter } from 'next/navigation'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ChipDropdown } from '@/components/ui/chip-dropdown'
|
||||
|
||||
const languages = {
|
||||
en: { name: 'English', flag: '🇺🇸' },
|
||||
@@ -24,6 +17,12 @@ export function LanguageDropdown() {
|
||||
const params = useParams()
|
||||
const { push } = useRouter()
|
||||
|
||||
const languageOptions = Object.entries(languages).map(([code, lang]) => ({
|
||||
value: code,
|
||||
label: lang.name,
|
||||
iconElement: <span className='text-[13px]'>{lang.flag}</span>,
|
||||
}))
|
||||
|
||||
const langFromParams = params?.lang as string
|
||||
const currentLang =
|
||||
langFromParams && Object.keys(languages).includes(langFromParams) ? langFromParams : 'en'
|
||||
@@ -47,44 +46,14 @@ export function LanguageDropdown() {
|
||||
push(newPath)
|
||||
}
|
||||
|
||||
const languageEntries = Object.entries(languages)
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className='flex cursor-pointer items-center gap-1.5 rounded-[8px] px-2.5 py-1.5 text-[13px] text-foreground/50 transition-colors duration-200 hover:bg-neutral-100 hover:text-foreground/70 focus:outline-none dark:hover:bg-neutral-800 dark:hover:text-foreground/70'>
|
||||
<span>{languages[currentLang as keyof typeof languages]?.name}</span>
|
||||
<svg width='8' height='5' viewBox='0 0 10 6' fill='none' className='flex-shrink-0'>
|
||||
<path
|
||||
d='M1 1L5 5L9 1'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.33'
|
||||
strokeLinecap='square'
|
||||
strokeLinejoin='miter'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end' sideOffset={6} className='min-w-[160px]'>
|
||||
{languageEntries.map(([code, lang]) => {
|
||||
const isSelected = currentLang === code
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={code}
|
||||
onClick={() => handleLanguageChange(code)}
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center gap-2 text-[13px]',
|
||||
isSelected && 'font-medium'
|
||||
)}
|
||||
>
|
||||
<span className='text-[13px]'>{lang.flag}</span>
|
||||
<span className='flex-1'>{lang.name}</span>
|
||||
{isSelected && <Check className='ml-auto size-3.5' />}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<ChipDropdown
|
||||
value={currentLang}
|
||||
onChange={handleLanguageChange}
|
||||
options={languageOptions}
|
||||
align='end'
|
||||
matchTriggerWidth={false}
|
||||
contentClassName='min-w-[160px]'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ export function SearchTrigger() {
|
||||
<button
|
||||
type='button'
|
||||
data-search-trigger
|
||||
className='flex h-8 w-[360px] cursor-pointer items-center gap-2 rounded-lg border border-border/50 bg-fd-muted/50 px-3 text-[13px] text-fd-muted-foreground transition-colors hover:bg-fd-muted'
|
||||
className='flex h-[30px] w-[360px] cursor-pointer items-center gap-1.5 rounded-lg border border-[var(--border-1)] bg-[var(--surface-5)] px-2 font-season text-[var(--text-muted)] text-sm transition-colors hover:bg-[var(--surface-active)] dark:bg-[var(--surface-4)]'
|
||||
onClick={openSearchDialog}
|
||||
>
|
||||
<Search className='size-3.5' />
|
||||
<Search className='size-[14px] text-[var(--text-icon)]' />
|
||||
<span>Search…</span>
|
||||
<kbd className='ml-auto flex items-center font-medium'>
|
||||
<kbd className='ml-auto flex items-center'>
|
||||
<span className='text-[15px]'>⌘</span>
|
||||
<span className='text-[12px]'>K</span>
|
||||
</kbd>
|
||||
|
||||
@@ -52,7 +52,7 @@ export function SimLogoFull({ className }: SimLogoProps) {
|
||||
/>
|
||||
</g>
|
||||
{/* "Sim" text — adapts to light/dark mode */}
|
||||
<g className='fill-neutral-900 dark:fill-white'>
|
||||
<g className='fill-[var(--text-primary)]'>
|
||||
<path d='M31.57 15.85h2.59c0 .71.26 1.28.78 1.71.52.41 1.22.61 2.1.61.96 0 1.7-.18 2.21-.55.52-.39.78-.9.78-1.53 0-.46-.14-.85-.43-1.16-.27-.31-.77-.56-1.49-.75l-2.47-.58c-1.25-.31-2.17-.78-2.79-1.42-.59-.64-.89-1.48-.89-2.52 0-.87.22-1.62.66-2.26.46-.64 1.08-1.13 1.87-1.48.8-.35 1.72-.52 2.76-.52s1.93.18 2.67.55c.77.37 1.36.88 1.78 1.53.44.66.67 1.44.69 2.35h-2.59c-.02-.73-.26-1.3-.72-1.71-.46-.41-1.1-.61-1.93-.61-.84 0-1.49.18-1.95.55-.46.37-.69.87-.69 1.51 0 .95.69 1.59 2.07 1.94l2.47.61c1.19.27 2.08.71 2.67 1.33.59.6.89 1.42.89 2.46 0 .89-.24 1.67-.72 2.35-.48.66-1.14 1.17-1.98 1.53-.82.35-1.8.52-2.93.52-1.65 0-2.96-.41-3.94-1.22-.98-.81-1.47-1.89-1.47-3.24z' />
|
||||
<path d='M44.51 19.96v-14.16c1.08.39 1.55.39 2.7 0v14.16zm1.32-15.09c-.48 0-.9-.17-1.26-.52-.34-.37-.52-.79-.52-1.27 0-.5.17-.93.52-1.27.36-.35.79-.52 1.26-.52.5 0 .92.17 1.26.52s.52.77.52 1.27c0 .48-.17.91-.52 1.27-.34.35-.77.52-1.26.52z' />
|
||||
<path d='M51.98 19.96h-2.7v-14.16h2.41v2.39c.29-.79.84-1.46 1.61-1.98.79-.54 1.73-.81 2.85-.81 1.25 0 2.28.34 3.1 1.01.82.68 1.36 1.57 1.61 2.69h-.49c.19-1.12.72-2.02 1.58-2.69.86-.68 1.93-1.01 3.19-1.01 1.61 0 2.87.47 3.79 1.42.92.95 1.38 2.24 1.38 3.88v9.26h-2.64v-8.6c0-1.12-.29-1.98-.86-2.58-.56-.62-1.31-.93-2.27-.93-.67 0-1.26.15-1.78.46-.5.29-.89.71-1.18 1.27-.29.56-.43 1.22-.43 1.97v8.4h-2.67v-8.63c0-1.12-.28-1.97-.83-2.55-.56-.6-1.31-.9-2.27-.9-.67 0-1.26.15-1.78.46-.5.29-.89.71-1.18 1.27-.29.54-.43 1.19-.43 1.94z' />
|
||||
|
||||
@@ -56,11 +56,11 @@ export function ThemeToggle() {
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
|
||||
className='flex size-[30px] cursor-pointer items-center justify-center rounded-full text-foreground/40 transition-colors duration-200 hover:bg-neutral-100 hover:text-foreground/70 dark:hover:bg-neutral-800 dark:hover:text-foreground/70'
|
||||
className='flex size-[30px] cursor-pointer items-center justify-center rounded-lg text-[var(--text-icon)] transition-colors hover:bg-[var(--surface-active)]'
|
||||
aria-label='Toggle theme'
|
||||
>
|
||||
<SunIcon className='block dark:hidden' />
|
||||
<MoonIcon className='hidden dark:block' />
|
||||
<SunIcon className='block size-[14px] dark:hidden' />
|
||||
<MoonIcon className='hidden size-[14px] dark:block' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface VideoProps {
|
||||
|
||||
export function Video({
|
||||
src,
|
||||
className = 'w-full rounded-xl border border-border overflow-hidden outline-none focus:outline-none',
|
||||
className = 'w-full rounded-xl border border-[var(--border)] overflow-hidden outline-none focus:outline-none',
|
||||
autoPlay = true,
|
||||
loop = true,
|
||||
muted = true,
|
||||
|
||||
Reference in New Issue
Block a user