diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css index a0c1dc4..0411d13 100644 --- a/apps/web/src/styles/globals.css +++ b/apps/web/src/styles/globals.css @@ -1,6 +1,7 @@ @import "@workspace/ui/globals.css"; @import "@workspace/blocks/globals.css"; @import "@workspace/lexical/globals.css"; +@import "@workspace/search/globals.css"; :root { --ui-content-top-gap: --spacing(2); diff --git a/packages/blocks/src/blocks/search/handle.ts b/packages/blocks/src/blocks/search/handle.ts deleted file mode 100644 index 922f966..0000000 --- a/packages/blocks/src/blocks/search/handle.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Dialog } from "@base-ui/react/dialog" - -/** A shared handle for the optional global-search trigger. */ -export const searchDialogHandle = Dialog.createHandle() diff --git a/packages/blocks/src/blocks/search/locales/catalogs.test.ts b/packages/blocks/src/blocks/search/locales/catalogs.test.ts deleted file mode 100644 index c071ff6..0000000 --- a/packages/blocks/src/blocks/search/locales/catalogs.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, it } from "vitest" - -import { expectCompleteCatalogs } from "../../../i18n/test-catalogs" -import { searchMessages } from "../messages" -import { searchCatalogLocales } from "./catalogs" -import { messages as de } from "./de" -import { messages as en } from "./en" -import { messages as es } from "./es" -import { messages as fr } from "./fr" -import { messages as ja } from "./ja" -import { messages as ko } from "./ko" -import { messages as zhHans } from "./zh-Hans" -import { messages as zhHant } from "./zh-Hant" - -describe("search locale catalogs", () => { - it("ship every search message in every built-in locale", () => { - expectCompleteCatalogs({ - catalogs: { - de, - en, - es, - fr, - ja, - ko, - "zh-Hans": zhHans, - "zh-Hant": zhHant, - }, - locales: searchCatalogLocales, - messageIds: Object.values(searchMessages).map( - (descriptor) => descriptor.id - ), - }) - }) -}) diff --git a/packages/blocks/src/blocks/search/locales/catalogs.ts b/packages/blocks/src/blocks/search/locales/catalogs.ts deleted file mode 100644 index 9880353..0000000 --- a/packages/blocks/src/blocks/search/locales/catalogs.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { searchMessages } from "../messages" -import { - blockCatalogLocales, - type BlockCatalogLocale, - type BlockMessageCatalog, -} from "../../../i18n/catalogs" - -export { blockCatalogLocales as searchCatalogLocales } -export type SearchCatalogLocale = BlockCatalogLocale -export type SearchMessageCatalog = BlockMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/de.ts b/packages/blocks/src/blocks/search/locales/de.ts deleted file mode 100644 index b9a90a6..0000000 --- a/packages/blocks/src/blocks/search/locales/de.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "de" -export const languageTag = "de-DE" -export const messages = { - "blocks.search.actions.clear": "Suche löschen", - "blocks.search.actions.clearHistory": "Verlauf löschen", - "blocks.search.actions.close": "Suche schließen", - "blocks.search.actions.loadMore": "Mehr laden", - "blocks.search.actions.loadingMore": "Wird geladen…", - "blocks.search.actions.retry": "Erneut versuchen", - "blocks.search.actions.selectResult": "{title} öffnen", - "blocks.search.command.description": "Im gesamten Arbeitsbereich suchen", - "blocks.search.description": "Inhalte im gesamten Arbeitsbereich finden", - "blocks.search.empty.description": "Keine Ergebnisse für „{query}“", - "blocks.search.empty.title": "Keine Ergebnisse gefunden", - "blocks.search.error.description": - "Prüfe deine Verbindung und versuche es erneut.", - "blocks.search.error.title": "Suche nach „{query}“ fehlgeschlagen", - "blocks.search.history.remove": "„{query}“ aus den letzten Suchen entfernen", - "blocks.search.history.title": "Letzte Suchen", - "blocks.search.initial.description": - "Gib ein Stichwort ein, um den Arbeitsbereich zu durchsuchen.", - "blocks.search.initial.title": "Suche starten", - "blocks.search.loading": "Suche läuft…", - "blocks.search.placeholder": "Arbeitsbereich durchsuchen…", - "blocks.search.results.count": "{count} Ergebnisse", - "blocks.search.title": "Suche", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/en.ts b/packages/blocks/src/blocks/search/locales/en.ts deleted file mode 100644 index a6ed423..0000000 --- a/packages/blocks/src/blocks/search/locales/en.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "en" -export const languageTag = "en-US" -export const messages = { - "blocks.search.actions.clear": "Clear query", - "blocks.search.actions.clearHistory": "Clear history", - "blocks.search.actions.close": "Close search", - "blocks.search.actions.loadMore": "Load more", - "blocks.search.actions.loadingMore": "Loading…", - "blocks.search.actions.retry": "Try again", - "blocks.search.actions.selectResult": "Open {title}", - "blocks.search.command.description": "Search across your workspace", - "blocks.search.description": "Find content across your workspace", - "blocks.search.empty.description": "No results for “{query}”", - "blocks.search.empty.title": "No results found", - "blocks.search.error.description": "Check your connection and try again.", - "blocks.search.error.title": "Could not search for “{query}”", - "blocks.search.history.remove": "Remove “{query}” from recent searches", - "blocks.search.history.title": "Recent searches", - "blocks.search.initial.description": - "Enter a keyword to search your workspace.", - "blocks.search.initial.title": "Start searching", - "blocks.search.loading": "Searching…", - "blocks.search.placeholder": "Search your workspace…", - "blocks.search.results.count": "{count} results", - "blocks.search.title": "Search", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/es.ts b/packages/blocks/src/blocks/search/locales/es.ts deleted file mode 100644 index c6bc02f..0000000 --- a/packages/blocks/src/blocks/search/locales/es.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "es" -export const languageTag = "es-ES" -export const messages = { - "blocks.search.actions.clear": "Borrar búsqueda", - "blocks.search.actions.clearHistory": "Borrar historial", - "blocks.search.actions.close": "Cerrar búsqueda", - "blocks.search.actions.loadMore": "Cargar más", - "blocks.search.actions.loadingMore": "Cargando…", - "blocks.search.actions.retry": "Intentar de nuevo", - "blocks.search.actions.selectResult": "Abrir {title}", - "blocks.search.command.description": "Buscar en todo tu espacio de trabajo", - "blocks.search.description": - "Encuentra contenido en todo tu espacio de trabajo", - "blocks.search.empty.description": "No hay resultados para «{query}»", - "blocks.search.empty.title": "No se encontraron resultados", - "blocks.search.error.description": - "Comprueba tu conexión e inténtalo de nuevo.", - "blocks.search.error.title": "No se pudo buscar «{query}»", - "blocks.search.history.remove": "Quitar «{query}» de las búsquedas recientes", - "blocks.search.history.title": "Búsquedas recientes", - "blocks.search.initial.description": - "Introduce una palabra clave para buscar en tu espacio de trabajo.", - "blocks.search.initial.title": "Empezar a buscar", - "blocks.search.loading": "Buscando…", - "blocks.search.placeholder": "Buscar en tu espacio de trabajo…", - "blocks.search.results.count": "{count} resultados", - "blocks.search.title": "Buscar", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/fr.ts b/packages/blocks/src/blocks/search/locales/fr.ts deleted file mode 100644 index 7b51207..0000000 --- a/packages/blocks/src/blocks/search/locales/fr.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "fr" -export const languageTag = "fr-FR" -export const messages = { - "blocks.search.actions.clear": "Effacer la recherche", - "blocks.search.actions.clearHistory": "Effacer l’historique", - "blocks.search.actions.close": "Fermer la recherche", - "blocks.search.actions.loadMore": "Charger plus", - "blocks.search.actions.loadingMore": "Chargement…", - "blocks.search.actions.retry": "Réessayer", - "blocks.search.actions.selectResult": "Ouvrir {title}", - "blocks.search.command.description": - "Rechercher dans tout l’espace de travail", - "blocks.search.description": - "Trouver du contenu dans votre espace de travail", - "blocks.search.empty.description": "Aucun résultat pour « {query} »", - "blocks.search.empty.title": "Aucun résultat trouvé", - "blocks.search.error.description": "Vérifiez votre connexion et réessayez.", - "blocks.search.error.title": "Impossible de rechercher « {query} »", - "blocks.search.history.remove": "Retirer « {query} » des recherches récentes", - "blocks.search.history.title": "Recherches récentes", - "blocks.search.initial.description": - "Saisissez un mot-clé pour rechercher dans votre espace de travail.", - "blocks.search.initial.title": "Commencer la recherche", - "blocks.search.loading": "Recherche…", - "blocks.search.placeholder": "Rechercher dans l’espace de travail…", - "blocks.search.results.count": "{count} résultats", - "blocks.search.title": "Rechercher", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/ja.ts b/packages/blocks/src/blocks/search/locales/ja.ts deleted file mode 100644 index 1654766..0000000 --- a/packages/blocks/src/blocks/search/locales/ja.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "ja" -export const languageTag = "ja-JP" -export const messages = { - "blocks.search.actions.clear": "検索をクリア", - "blocks.search.actions.clearHistory": "履歴を削除", - "blocks.search.actions.close": "検索を閉じる", - "blocks.search.actions.loadMore": "さらに読み込む", - "blocks.search.actions.loadingMore": "読み込み中…", - "blocks.search.actions.retry": "再試行", - "blocks.search.actions.selectResult": "{title} を開く", - "blocks.search.command.description": "ワークスペース全体を検索", - "blocks.search.description": "ワークスペース全体からコンテンツを探す", - "blocks.search.empty.description": "「{query}」の結果はありません", - "blocks.search.empty.title": "結果が見つかりません", - "blocks.search.error.description": "接続を確認して、もう一度お試しください。", - "blocks.search.error.title": "「{query}」を検索できませんでした", - "blocks.search.history.remove": "最近の検索から「{query}」を削除", - "blocks.search.history.title": "最近の検索", - "blocks.search.initial.description": - "キーワードを入力してワークスペースを検索します。", - "blocks.search.initial.title": "検索を始める", - "blocks.search.loading": "検索中…", - "blocks.search.placeholder": "ワークスペースを検索…", - "blocks.search.results.count": "{count} 件の結果", - "blocks.search.title": "検索", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/ko.ts b/packages/blocks/src/blocks/search/locales/ko.ts deleted file mode 100644 index 3a19fd9..0000000 --- a/packages/blocks/src/blocks/search/locales/ko.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "ko" -export const languageTag = "ko-KR" -export const messages = { - "blocks.search.actions.clear": "검색어 지우기", - "blocks.search.actions.clearHistory": "기록 지우기", - "blocks.search.actions.close": "검색 닫기", - "blocks.search.actions.loadMore": "더 불러오기", - "blocks.search.actions.loadingMore": "불러오는 중…", - "blocks.search.actions.retry": "다시 시도", - "blocks.search.actions.selectResult": "{title} 열기", - "blocks.search.command.description": "작업 공간 전체 검색", - "blocks.search.description": "작업 공간 전체에서 콘텐츠 찾기", - "blocks.search.empty.description": "“{query}”에 대한 결과가 없습니다", - "blocks.search.empty.title": "결과를 찾을 수 없습니다", - "blocks.search.error.description": "연결을 확인한 후 다시 시도하세요.", - "blocks.search.error.title": "“{query}”을(를) 검색할 수 없습니다", - "blocks.search.history.remove": "최근 검색에서 “{query}” 제거", - "blocks.search.history.title": "최근 검색", - "blocks.search.initial.description": - "키워드를 입력하여 작업 공간을 검색하세요.", - "blocks.search.initial.title": "검색 시작", - "blocks.search.loading": "검색 중…", - "blocks.search.placeholder": "작업 공간 검색…", - "blocks.search.results.count": "결과 {count}개", - "blocks.search.title": "검색", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/zh-Hans.ts b/packages/blocks/src/blocks/search/locales/zh-Hans.ts deleted file mode 100644 index b710c02..0000000 --- a/packages/blocks/src/blocks/search/locales/zh-Hans.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "zh-Hans" -export const languageTag = "zh-CN" -export const messages = { - "blocks.search.actions.clear": "清除查询", - "blocks.search.actions.clearHistory": "清空历史", - "blocks.search.actions.close": "关闭搜索", - "blocks.search.actions.loadMore": "加载更多", - "blocks.search.actions.loadingMore": "正在加载…", - "blocks.search.actions.retry": "重试", - "blocks.search.actions.selectResult": "打开 {title}", - "blocks.search.command.description": "搜索整个工作区", - "blocks.search.description": "在工作区中查找内容", - "blocks.search.empty.description": "没有找到与“{query}”相关的内容", - "blocks.search.empty.title": "未找到结果", - "blocks.search.error.description": "请检查网络连接后重试。", - "blocks.search.error.title": "无法搜索“{query}”", - "blocks.search.history.remove": "从搜索历史中删除“{query}”", - "blocks.search.history.title": "搜索历史", - "blocks.search.initial.description": "输入关键词以搜索工作区。", - "blocks.search.initial.title": "开始搜索", - "blocks.search.loading": "正在搜索…", - "blocks.search.placeholder": "搜索工作区…", - "blocks.search.results.count": "{count} 条结果", - "blocks.search.title": "搜索", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/locales/zh-Hant.ts b/packages/blocks/src/blocks/search/locales/zh-Hant.ts deleted file mode 100644 index 089535d..0000000 --- a/packages/blocks/src/blocks/search/locales/zh-Hant.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { SearchMessageCatalog } from "./catalogs" - -export const locale = "zh-Hant" -export const languageTag = "zh-TW" -export const messages = { - "blocks.search.actions.clear": "清除搜尋", - "blocks.search.actions.clearHistory": "清除記錄", - "blocks.search.actions.close": "關閉搜尋", - "blocks.search.actions.loadMore": "載入更多", - "blocks.search.actions.loadingMore": "載入中…", - "blocks.search.actions.retry": "再試一次", - "blocks.search.actions.selectResult": "開啟 {title}", - "blocks.search.command.description": "搜尋整個工作區", - "blocks.search.description": "在工作區中尋找內容", - "blocks.search.empty.description": "沒有「{query}」的結果", - "blocks.search.empty.title": "找不到結果", - "blocks.search.error.description": "請檢查網絡連線後再試一次。", - "blocks.search.error.title": "無法搜尋「{query}」", - "blocks.search.history.remove": "從最近搜尋中移除「{query}」", - "blocks.search.history.title": "最近搜尋", - "blocks.search.initial.description": "輸入關鍵字以搜尋工作區。", - "blocks.search.initial.title": "開始搜尋", - "blocks.search.loading": "搜尋中…", - "blocks.search.placeholder": "搜尋工作區…", - "blocks.search.results.count": "{count} 項結果", - "blocks.search.title": "搜尋", -} as const satisfies SearchMessageCatalog diff --git a/packages/blocks/src/blocks/search/messages.ts b/packages/blocks/src/blocks/search/messages.ts deleted file mode 100644 index 73e7cbf..0000000 --- a/packages/blocks/src/blocks/search/messages.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { MessageDescriptor } from "@workspace/i18n" - -export const searchMessages = { - clear: { id: "blocks.search.actions.clear", message: "Clear query" }, - clearHistory: { - id: "blocks.search.actions.clearHistory", - message: "Clear history", - }, - close: { id: "blocks.search.actions.close", message: "Close search" }, - loadMore: { id: "blocks.search.actions.loadMore", message: "Load more" }, - loadingMore: { - id: "blocks.search.actions.loadingMore", - message: "Loading…", - }, - retry: { id: "blocks.search.actions.retry", message: "Try again" }, - selectResult: { - id: "blocks.search.actions.selectResult", - message: "Open {title}", - }, - commandDescription: { - id: "blocks.search.command.description", - message: "Search across your workspace", - }, - description: { - id: "blocks.search.description", - message: "Find content across your workspace", - }, - emptyDescription: { - id: "blocks.search.empty.description", - message: "No results for “{query}”", - }, - emptyTitle: { id: "blocks.search.empty.title", message: "No results found" }, - errorDescription: { - id: "blocks.search.error.description", - message: "Check your connection and try again.", - }, - errorTitle: { - id: "blocks.search.error.title", - message: "Could not search for “{query}”", - }, - history: { id: "blocks.search.history.title", message: "Recent searches" }, - initialDescription: { - id: "blocks.search.initial.description", - message: "Enter a keyword to search your workspace.", - }, - initialTitle: { - id: "blocks.search.initial.title", - message: "Start searching", - }, - loading: { id: "blocks.search.loading", message: "Searching…" }, - placeholder: { - id: "blocks.search.placeholder", - message: "Search your workspace…", - }, - removeHistory: { - id: "blocks.search.history.remove", - message: "Remove “{query}” from recent searches", - }, - resultCount: { - id: "blocks.search.results.count", - message: "{count} results", - }, - title: { id: "blocks.search.title", message: "Search" }, -} as const satisfies Record diff --git a/packages/search/package.json b/packages/search/package.json new file mode 100644 index 0000000..34f839d --- /dev/null +++ b/packages/search/package.json @@ -0,0 +1,32 @@ +{ + "name": "@workspace/search", + "version": "0.0.0", + "type": "module", + "private": true, + "scripts": { + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@tanstack/react-hotkeys": "^0.10.0", + "@workspace/i18n": "workspace:*", + "@workspace/ui": "workspace:*", + "cmdk": "^1.1.1", + "react": "^19.2.6" + }, + "devDependencies": { + "@testing-library/react": "^16.3.2", + "@types/react": "^19", + "@types/react-dom": "^19", + "jsdom": "^30.0.1", + "react-dom": "^19.2.6", + "typescript": "~6", + "vitest": "^4.1.10" + }, + "exports": { + ".": "./src/index.ts", + "./globals.css": "./src/styles/globals.css", + "./locales": "./src/locales/catalogs.ts", + "./locales/*": "./src/locales/*.ts" + } +} \ No newline at end of file diff --git a/packages/blocks/src/blocks/search/context.tsx b/packages/search/src/context.tsx similarity index 92% rename from packages/blocks/src/blocks/search/context.tsx rename to packages/search/src/context.tsx index f95b75a..56f93be 100644 --- a/packages/blocks/src/blocks/search/context.tsx +++ b/packages/search/src/context.tsx @@ -21,7 +21,7 @@ export interface SearchProviderProps { onSelect?: SearchSelectionHandler } -/** Supplies the host application's search implementation to the search blocks. */ +/** Supplies the host application's search implementation to the search package. */ export function SearchProvider({ adapter, children, @@ -48,7 +48,9 @@ export function SearchProvider({ export function useSearchContext(): SearchContextValue { const value = React.useContext(SearchContext) if (!value) { - throw new Error("Search blocks must be rendered inside a SearchProvider.") + throw new Error( + "Search components must be rendered inside a SearchProvider." + ) } return value } diff --git a/packages/blocks/src/blocks/search/dialog.tsx b/packages/search/src/dialog.tsx similarity index 98% rename from packages/blocks/src/blocks/search/dialog.tsx rename to packages/search/src/dialog.tsx index 603991a..8fc9c7e 100644 --- a/packages/blocks/src/blocks/search/dialog.tsx +++ b/packages/search/src/dialog.tsx @@ -11,7 +11,11 @@ import { DialogTitle, } from "@workspace/ui/components/dialog" import { cn } from "@workspace/ui/lib/utils" -import { LoaderCircleIcon, SearchIcon, XIcon } from "lucide-react" +import { + LoaderCircleIcon, + SearchIcon, + XIcon, +} from "@workspace/ui/components/icon" import { useSearchContext } from "./context" import { searchDialogHandle } from "./handle" diff --git a/packages/search/src/handle.ts b/packages/search/src/handle.ts new file mode 100644 index 0000000..00a4f02 --- /dev/null +++ b/packages/search/src/handle.ts @@ -0,0 +1,4 @@ +import { createDialogHandle } from "@workspace/ui/components/dialog" + +/** A shared handle for the optional global-search trigger. */ +export const searchDialogHandle = createDialogHandle() diff --git a/packages/blocks/src/blocks/search/highlight.ts b/packages/search/src/highlight.ts similarity index 100% rename from packages/blocks/src/blocks/search/highlight.ts rename to packages/search/src/highlight.ts diff --git a/packages/blocks/src/blocks/search/history.tsx b/packages/search/src/history.tsx similarity index 97% rename from packages/blocks/src/blocks/search/history.tsx rename to packages/search/src/history.tsx index 26ed5e5..bf52352 100644 --- a/packages/blocks/src/blocks/search/history.tsx +++ b/packages/search/src/history.tsx @@ -3,7 +3,7 @@ import { useTranslate } from "@workspace/i18n" import { Button } from "@workspace/ui/components/button" import { ScrollArea } from "@workspace/ui/components/scroll-area" import { cn } from "@workspace/ui/lib/utils" -import { HistoryIcon, Trash2Icon, XIcon } from "lucide-react" +import { HistoryIcon, Trash2Icon, XIcon } from "@workspace/ui/components/icon" import { searchMessages } from "./messages" diff --git a/packages/blocks/src/blocks/search/index.ts b/packages/search/src/index.ts similarity index 100% rename from packages/blocks/src/blocks/search/index.ts rename to packages/search/src/index.ts diff --git a/packages/search/src/locales/catalogs.test.ts b/packages/search/src/locales/catalogs.test.ts new file mode 100644 index 0000000..1896e21 --- /dev/null +++ b/packages/search/src/locales/catalogs.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest" + +import { searchMessages } from "../messages" +import { searchCatalogLocales } from "./catalogs" +import { messages as enUS } from "./en-US" +import { messages as zhHans } from "./zh-Hans" + +describe("search locale catalogs", () => { + it("ship every search message in every built-in locale", () => { + expectCompleteCatalogs({ + catalogs: { + "en-US": enUS, + "zh-Hans": zhHans, + }, + locales: searchCatalogLocales, + messageIds: Object.values(searchMessages).map( + (descriptor) => descriptor.id + ), + }) + }) +}) + +function expectCompleteCatalogs({ + catalogs, + locales, + messageIds, +}: { + catalogs: Readonly>>> + locales: readonly string[] + messageIds: readonly string[] +}) { + expect(Object.keys(catalogs)).toEqual([...locales]) + + const expectedIds = new Set(messageIds) + for (const messages of Object.values(catalogs)) { + expect(new Set(Object.keys(messages))).toEqual(expectedIds) + } +} diff --git a/packages/search/src/locales/catalogs.ts b/packages/search/src/locales/catalogs.ts new file mode 100644 index 0000000..c0d40cf --- /dev/null +++ b/packages/search/src/locales/catalogs.ts @@ -0,0 +1,16 @@ +import type { searchMessages } from "../messages" + +export const searchCatalogLocales = ["en-US", "zh-Hans"] as const + +interface MessageDescriptorMap { + readonly [key: string]: { + readonly id: string + } +} + +type MessageId = T[keyof T]["id"] + +export type SearchCatalogLocale = (typeof searchCatalogLocales)[number] +export type SearchMessageCatalog = Readonly< + Record, string> +> diff --git a/packages/search/src/locales/en-US.ts b/packages/search/src/locales/en-US.ts new file mode 100644 index 0000000..0d23b47 --- /dev/null +++ b/packages/search/src/locales/en-US.ts @@ -0,0 +1,27 @@ +import type { SearchMessageCatalog } from "./catalogs" + +export const locale = "en-US" +export const languageTag = "en-US" +export const messages = { + "search.actions.clear": "Clear query", + "search.actions.clearHistory": "Clear history", + "search.actions.close": "Close search", + "search.actions.loadMore": "Load more", + "search.actions.loadingMore": "Loading…", + "search.actions.retry": "Try again", + "search.actions.selectResult": "Open {title}", + "search.command.description": "Search across your workspace", + "search.description": "Find content across your workspace", + "search.empty.description": "No results for “{query}”", + "search.empty.title": "No results found", + "search.error.description": "Check your connection and try again.", + "search.error.title": "Could not search for “{query}”", + "search.history.remove": "Remove “{query}” from recent searches", + "search.history.title": "Recent searches", + "search.initial.description": "Enter a keyword to search your workspace.", + "search.initial.title": "Start searching", + "search.loading": "Searching…", + "search.placeholder": "Search your workspace…", + "search.results.count": "{count} results", + "search.title": "Search", +} as const satisfies SearchMessageCatalog diff --git a/packages/search/src/locales/zh-Hans.ts b/packages/search/src/locales/zh-Hans.ts new file mode 100644 index 0000000..a6b0bcd --- /dev/null +++ b/packages/search/src/locales/zh-Hans.ts @@ -0,0 +1,27 @@ +import type { SearchMessageCatalog } from "./catalogs" + +export const locale = "zh-Hans" +export const languageTag = "zh-CN" +export const messages = { + "search.actions.clear": "清除查询", + "search.actions.clearHistory": "清空历史", + "search.actions.close": "关闭搜索", + "search.actions.loadMore": "加载更多", + "search.actions.loadingMore": "正在加载…", + "search.actions.retry": "重试", + "search.actions.selectResult": "打开 {title}", + "search.command.description": "搜索整个工作区", + "search.description": "在工作区中查找内容", + "search.empty.description": "没有找到与“{query}”相关的内容", + "search.empty.title": "未找到结果", + "search.error.description": "请检查网络连接后重试。", + "search.error.title": "无法搜索“{query}”", + "search.history.remove": "从搜索历史中删除“{query}”", + "search.history.title": "搜索历史", + "search.initial.description": "输入关键词以搜索工作区。", + "search.initial.title": "开始搜索", + "search.loading": "正在搜索…", + "search.placeholder": "搜索工作区…", + "search.results.count": "{count} 条结果", + "search.title": "搜索", +} as const satisfies SearchMessageCatalog diff --git a/packages/search/src/messages.ts b/packages/search/src/messages.ts new file mode 100644 index 0000000..3140c83 --- /dev/null +++ b/packages/search/src/messages.ts @@ -0,0 +1,64 @@ +import type { MessageDescriptor } from "@workspace/i18n" + +export const searchMessages = { + clear: { id: "search.actions.clear", message: "Clear query" }, + clearHistory: { + id: "search.actions.clearHistory", + message: "Clear history", + }, + close: { id: "search.actions.close", message: "Close search" }, + loadMore: { id: "search.actions.loadMore", message: "Load more" }, + loadingMore: { + id: "search.actions.loadingMore", + message: "Loading…", + }, + retry: { id: "search.actions.retry", message: "Try again" }, + selectResult: { + id: "search.actions.selectResult", + message: "Open {title}", + }, + commandDescription: { + id: "search.command.description", + message: "Search across your workspace", + }, + description: { + id: "search.description", + message: "Find content across your workspace", + }, + emptyDescription: { + id: "search.empty.description", + message: "No results for “{query}”", + }, + emptyTitle: { id: "search.empty.title", message: "No results found" }, + errorDescription: { + id: "search.error.description", + message: "Check your connection and try again.", + }, + errorTitle: { + id: "search.error.title", + message: "Could not search for “{query}”", + }, + history: { id: "search.history.title", message: "Recent searches" }, + initialDescription: { + id: "search.initial.description", + message: "Enter a keyword to search your workspace.", + }, + initialTitle: { + id: "search.initial.title", + message: "Start searching", + }, + loading: { id: "search.loading", message: "Searching…" }, + placeholder: { + id: "search.placeholder", + message: "Search your workspace…", + }, + removeHistory: { + id: "search.history.remove", + message: "Remove “{query}” from recent searches", + }, + resultCount: { + id: "search.results.count", + message: "{count} results", + }, + title: { id: "search.title", message: "Search" }, +} as const satisfies Record diff --git a/packages/blocks/src/blocks/search/results.tsx b/packages/search/src/results.tsx similarity index 91% rename from packages/blocks/src/blocks/search/results.tsx rename to packages/search/src/results.tsx index 3d25b41..b492276 100644 --- a/packages/blocks/src/blocks/search/results.tsx +++ b/packages/search/src/results.tsx @@ -7,7 +7,7 @@ import { CornerDownLeftIcon, FileTextIcon, LoaderCircleIcon, -} from "lucide-react" +} from "@workspace/ui/components/icon" import { splitHighlightSegments } from "./highlight" import { searchMessages } from "./messages" @@ -124,9 +124,9 @@ function SearchResultRow({ )} - {item.meta?.map((line, index) => ( + {item.meta?.map((line) => ( @@ -158,16 +158,18 @@ function ResultMedia({ item }: { item: SearchResultItem }) { } function HighlightedText({ text, query }: { text: string; query: string }) { - return splitHighlightSegments(text, query).map((segment, index) => - segment.highlighted ? ( - + let offset = 0 + + return splitHighlightSegments(text, query).map((segment) => { + const key = `${offset}:${segment.highlighted ? "highlight" : "text"}` + offset += segment.text.length + + return segment.highlighted ? ( + {segment.text} ) : ( - {segment.text} + {segment.text} ) - ) + }) } diff --git a/packages/blocks/src/blocks/search/search.test.tsx b/packages/search/src/search.test.tsx similarity index 95% rename from packages/blocks/src/blocks/search/search.test.tsx rename to packages/search/src/search.test.tsx index 458d88d..918a9f0 100644 --- a/packages/blocks/src/blocks/search/search.test.tsx +++ b/packages/search/src/search.test.tsx @@ -14,7 +14,7 @@ import { SearchProvider } from "./context" import { SearchDialog } from "./dialog" import { searchDialogHandle } from "./handle" import { splitHighlightSegments } from "./highlight" -import { messages as englishMessages } from "./locales/en" +import { messages as englishMessages } from "./locales/en-US" import type { SearchAdapter } from "./types" class ResizeObserverStub { @@ -37,7 +37,7 @@ function renderSearch({ onSelect?: (event: { item: { id: string }; query: string }) => void }) { return render( - + { +describe("search package", () => { afterEach(() => { cleanup() window.localStorage.clear() diff --git a/packages/blocks/src/blocks/search/states.tsx b/packages/search/src/states.tsx similarity index 94% rename from packages/blocks/src/blocks/search/states.tsx rename to packages/search/src/states.tsx index b536f89..b9e6af6 100644 --- a/packages/blocks/src/blocks/search/states.tsx +++ b/packages/search/src/states.tsx @@ -7,7 +7,11 @@ import { EmptyMedia, EmptyTitle, } from "@workspace/ui/components/empty" -import { CircleAlertIcon, LoaderCircleIcon, SearchIcon } from "lucide-react" +import { + CircleAlertIcon, + LoaderCircleIcon, + SearchIcon, +} from "@workspace/ui/components/icon" import { searchMessages } from "./messages" diff --git a/packages/search/src/styles/globals.css b/packages/search/src/styles/globals.css new file mode 100644 index 0000000..f3275b1 --- /dev/null +++ b/packages/search/src/styles/globals.css @@ -0,0 +1 @@ +@source "../"; diff --git a/packages/blocks/src/blocks/search/trigger.tsx b/packages/search/src/trigger.tsx similarity index 100% rename from packages/blocks/src/blocks/search/trigger.tsx rename to packages/search/src/trigger.tsx diff --git a/packages/blocks/src/blocks/search/types.ts b/packages/search/src/types.ts similarity index 95% rename from packages/blocks/src/blocks/search/types.ts rename to packages/search/src/types.ts index 615d38b..b560d26 100644 --- a/packages/blocks/src/blocks/search/types.ts +++ b/packages/search/src/types.ts @@ -46,7 +46,7 @@ export interface SearchRequest { } /** - * The only data dependency of the search blocks. Implement this in the host + * The only data dependency of the search package. Implement this in the host * application with its own database, HTTP client, command registry, or index. */ export interface SearchAdapter { diff --git a/packages/blocks/src/blocks/search/use-search-history.ts b/packages/search/src/use-search-history.ts similarity index 100% rename from packages/blocks/src/blocks/search/use-search-history.ts rename to packages/search/src/use-search-history.ts diff --git a/packages/blocks/src/blocks/search/use-search.ts b/packages/search/src/use-search.ts similarity index 98% rename from packages/blocks/src/blocks/search/use-search.ts rename to packages/search/src/use-search.ts index 2018ba7..8e53e96 100644 --- a/packages/blocks/src/blocks/search/use-search.ts +++ b/packages/search/src/use-search.ts @@ -1,6 +1,6 @@ import * as React from "react" -import type { SearchCursor, SearchPage, SearchResultGroup } from "./types" +import type { SearchPage, SearchResultGroup } from "./types" import { useSearchContext } from "./context" interface UseSearchOptions { diff --git a/packages/search/tsconfig.json b/packages/search/tsconfig.json new file mode 100644 index 0000000..e5515ce --- /dev/null +++ b/packages/search/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "jsx": "react-jsx", + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "paths": { + "@workspace/i18n": ["../i18n/src/index.ts"], + "@workspace/ui/*": ["../ui/src/*"] + } + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file