@uniweb/kit 0.1.10 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -236,4 +236,88 @@ export function useSearchIndex(website, options = {}) {
236
236
  }
237
237
  }
238
238
 
239
+ /**
240
+ * Hook for Cmd/Ctrl+K keyboard shortcut to open search
241
+ *
242
+ * @param {Function|Object} callbacks - Either onOpen function, or { onOpen, onPreload }
243
+ *
244
+ * @example
245
+ * // Simple usage
246
+ * useSearchShortcut(() => setSearchOpen(true))
247
+ *
248
+ * // With preload
249
+ * useSearchShortcut({
250
+ * onOpen: () => setSearchOpen(true),
251
+ * onPreload: () => search.preload()
252
+ * })
253
+ */
254
+ export function useSearchShortcut(callbacks) {
255
+ const { onOpen, onPreload } = typeof callbacks === 'function'
256
+ ? { onOpen: callbacks, onPreload: null }
257
+ : callbacks
258
+
259
+ useEffect(() => {
260
+ const handleKeyDown = (e) => {
261
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
262
+ e.preventDefault()
263
+ onPreload?.()
264
+ onOpen()
265
+ }
266
+ }
267
+
268
+ window.addEventListener('keydown', handleKeyDown)
269
+ return () => window.removeEventListener('keydown', handleKeyDown)
270
+ }, [onOpen, onPreload])
271
+ }
272
+
273
+ /**
274
+ * Hook that wraps useSearch with intent-based preloading
275
+ *
276
+ * Provides handlers to trigger preload on user intent (hover, focus, touch)
277
+ * rather than on component mount. This saves bandwidth for users who never search.
278
+ *
279
+ * @param {Object} website - Website instance from @uniweb/core
280
+ * @param {Object} options - Options passed to useSearch
281
+ * @returns {Object} Search state, methods, and intent handlers
282
+ *
283
+ * @example
284
+ * function SearchButton({ onClick }) {
285
+ * const { intentProps, triggerPreload } = useSearchWithIntent(website)
286
+ *
287
+ * useSearchShortcut({
288
+ * onOpen: onClick,
289
+ * onPreload: triggerPreload,
290
+ * })
291
+ *
292
+ * return (
293
+ * <button onClick={onClick} {...intentProps}>
294
+ * Search
295
+ * </button>
296
+ * )
297
+ * }
298
+ */
299
+ export function useSearchWithIntent(website, options = {}) {
300
+ const search = useSearch(website, options)
301
+ const hasPreloaded = useRef(false)
302
+
303
+ const triggerPreload = useCallback(() => {
304
+ if (hasPreloaded.current) return
305
+ hasPreloaded.current = true
306
+ search.preload()
307
+ }, [search])
308
+
309
+ // Intent handlers - spread onto interactive elements
310
+ const intentProps = useMemo(() => ({
311
+ onMouseEnter: triggerPreload,
312
+ onFocus: triggerPreload,
313
+ onTouchStart: triggerPreload,
314
+ }), [triggerPreload])
315
+
316
+ return {
317
+ ...search,
318
+ triggerPreload,
319
+ intentProps,
320
+ }
321
+ }
322
+
239
323
  export default useSearch
@@ -23,4 +23,4 @@
23
23
 
24
24
  export { createSearchClient, loadSearchIndex, clearSearchCache } from './client.js'
25
25
  export { buildSnippet, highlightMatches, escapeHtml } from './snippets.js'
26
- export { useSearch, useSearchIndex } from './hooks.js'
26
+ export { useSearch, useSearchIndex, useSearchShortcut, useSearchWithIntent } from './hooks.js'
@@ -8,8 +8,8 @@
8
8
 
9
9
  import React, { useState, useCallback, forwardRef, useImperativeHandle } from 'react'
10
10
  import { cn } from '../../utils/index.js'
11
- import { FileLogo } from '../FileLogo/index.js'
12
- import { Image } from '../Image/index.js'
11
+ import { FileLogo } from '../../components/FileLogo/index.js'
12
+ import { Image } from '../../components/Image/index.js'
13
13
  import { useWebsite } from '../../hooks/useWebsite.js'
14
14
 
15
15
  /**
@@ -9,6 +9,75 @@ import { twMerge, twJoin } from 'tailwind-merge'
9
9
  // Re-export tailwind-merge utilities
10
10
  export { twMerge, twJoin }
11
11
 
12
+ // ─────────────────────────────────────────────────────────────────
13
+ // Locale Utilities
14
+ // ─────────────────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Common locale display names (native language names)
18
+ * Used as fallback when site.yml doesn't specify labels.
19
+ * Tree-shakeable: only included if foundation uses getLocaleLabel().
20
+ */
21
+ export const LOCALE_DISPLAY_NAMES = {
22
+ en: 'English',
23
+ es: 'Español',
24
+ fr: 'Français',
25
+ de: 'Deutsch',
26
+ it: 'Italiano',
27
+ pt: 'Português',
28
+ nl: 'Nederlands',
29
+ pl: 'Polski',
30
+ ru: 'Русский',
31
+ ja: '日本語',
32
+ ko: '한국어',
33
+ zh: '中文',
34
+ 'zh-CN': '简体中文',
35
+ 'zh-TW': '繁體中文',
36
+ ar: 'العربية',
37
+ he: 'עברית',
38
+ hi: 'हिन्दी',
39
+ th: 'ไทย',
40
+ vi: 'Tiếng Việt',
41
+ tr: 'Türkçe',
42
+ uk: 'Українська',
43
+ cs: 'Čeština',
44
+ el: 'Ελληνικά',
45
+ hu: 'Magyar',
46
+ ro: 'Română',
47
+ sv: 'Svenska',
48
+ da: 'Dansk',
49
+ fi: 'Suomi',
50
+ no: 'Norsk',
51
+ id: 'Bahasa Indonesia',
52
+ ms: 'Bahasa Melayu'
53
+ }
54
+
55
+ /**
56
+ * Get display label for a locale
57
+ * Priority: locale.label (from site config) → LOCALE_DISPLAY_NAMES → code.toUpperCase()
58
+ *
59
+ * @param {Object|string} locale - Locale object {code, label?} or locale code string
60
+ * @returns {string} Display label for the locale
61
+ *
62
+ * @example
63
+ * getLocaleLabel({ code: 'es', label: 'Spanish' }) // 'Spanish'
64
+ * getLocaleLabel({ code: 'es' }) // 'Español'
65
+ * getLocaleLabel('es') // 'Español'
66
+ * getLocaleLabel({ code: 'xx' }) // 'XX'
67
+ */
68
+ export function getLocaleLabel(locale) {
69
+ // Handle string input (just a code)
70
+ if (typeof locale === 'string') {
71
+ return LOCALE_DISPLAY_NAMES[locale] || locale.toUpperCase()
72
+ }
73
+
74
+ // Handle object input
75
+ if (!locale || !locale.code) return ''
76
+
77
+ // Priority: explicit label → known display name → uppercase code
78
+ return locale.label || LOCALE_DISPLAY_NAMES[locale.code] || locale.code.toUpperCase()
79
+ }
80
+
12
81
  /**
13
82
  * Merge class names with Tailwind CSS conflict resolution
14
83
  * @param {...string} classes - Class names to merge