Files
upstream-tldraw/apps/docs/components/search/SearchButton.tsx
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

108 lines
3.0 KiB
TypeScript

'use client'
import { MagnifyingGlassIcon as MagnifyingGlassIconSmall } from '@heroicons/react/20/solid'
import {
MagnifyingGlassIcon as MagnifyingGlassIconLarge,
XMarkIcon,
} from '@heroicons/react/24/solid'
import { usePathname } from 'next/navigation'
import { useEffect, useState } from 'react'
import { twJoin } from 'tailwind-merge'
import { cn } from '@/utils/cn'
import { Search } from '.'
function isEditableElement(target: EventTarget | null) {
if (!(target instanceof HTMLElement)) return false
return (
target.isContentEditable ||
target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.tagName === 'SELECT'
)
}
export function SearchButton({
type,
layout,
className,
}: {
type: 'docs' | 'blog'
layout: 'mobile' | 'desktop' | 'keyboard-shortcut-only'
className?: string
}) {
const [open, setOpen] = useState(false)
const pathname = usePathname()
useEffect(() => {
if (layout !== 'desktop' && layout !== 'keyboard-shortcut-only') return
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === '/') {
// A bare "/" is only a shortcut outside text fields, otherwise typing a path into
// the feedback textarea opens the dialog and steals focus mid-word.
if (isEditableElement(e.target)) return
e.preventDefault()
setOpen(true)
} else if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
setOpen((open) => !open)
} else if (e.key === 'Escape') {
setOpen(false)
}
}
document.addEventListener('keydown', onKeyDown)
return () => document.removeEventListener('keydown', onKeyDown)
}, [layout])
useEffect(() => {
setOpen(false)
}, [pathname])
if (layout === 'keyboard-shortcut-only') {
return open ? <Search type={type} onClose={() => setOpen(false)} /> : null
}
return (
<div className={cn('group', className)}>
<button
className={cn(
'flex items-center text-black dark:text-white focus:outline-none',
layout === 'desktop' &&
twJoin(
'w-full h-10 justify-between bg-zinc-50 dark:bg-zinc-900 px-4 cursor-text',
'hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg focus:ring-2',
'focus:ring-blue-500'
),
layout === 'mobile' &&
'w-8 h-8 justify-center rounded focus:bg-zinc-100 dark:focus:bg-zinc-800'
)}
onClick={() => setOpen((open) => !open)}
>
<div className="flex items-center gap-3">
{open ? (
<XMarkIcon className="h-6 md:hidden" />
) : (
<>
<MagnifyingGlassIconSmall className={cn('h-4', layout === 'mobile' && 'hidden')} />
<MagnifyingGlassIconLarge className={cn('h-6', layout === 'desktop' && 'hidden')} />
</>
)}
{!open && (
<span
className={cn(
'capitalize text-sm text-zinc-400 dark:text-zinc-600',
layout === 'mobile' && 'hidden'
)}
>
Search {type}…
</span>
)}
</div>
<span className={cn('text-xs', layout === 'mobile' && 'hidden')}>⌘K</span>
</button>
{open && <Search type={type} onClose={() => setOpen(false)} />}
</div>
)
}