@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.
- package/README.md +355 -155
- package/package.json +2 -2
- package/src/components/Link/Link.jsx +4 -2
- package/src/hooks/index.js +11 -0
- package/src/hooks/useInView.js +200 -0
- package/src/hooks/useThemeData.js +256 -0
- package/src/hooks/useVersion.js +128 -0
- package/src/index.js +15 -2
- package/src/search/hooks.js +84 -0
- package/src/search/index.js +1 -1
- package/src/styled/Asset/Asset.jsx +2 -2
- package/src/utils/index.js +69 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @uniweb/kit
|
|
2
2
|
|
|
3
|
-
Standard component library for Uniweb foundations.
|
|
3
|
+
Standard component library for Uniweb foundations. Tree-shakeable utilities, components, and hooks for building foundation components.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,53 +8,58 @@ Standard component library for Uniweb foundations.
|
|
|
8
8
|
npm install @uniweb/kit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Tree-Shaking Benefits
|
|
12
|
+
|
|
13
|
+
Kit is designed to be bundled into your foundation (not externalized like `@uniweb/core`). This means:
|
|
14
|
+
|
|
15
|
+
- **Only what you use is bundled** — Import 3 components? Only those 3 end up in your foundation
|
|
16
|
+
- **No runtime overhead** — Unused code is eliminated at build time
|
|
17
|
+
- **Customizable** — Override or extend any component without carrying dead code
|
|
18
|
+
- **Small foundations** — A minimal foundation using just `Link` and `useWebsite` stays tiny
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// vite.config.js - Kit is bundled, core is external
|
|
22
|
+
export default {
|
|
23
|
+
build: {
|
|
24
|
+
rollupOptions: {
|
|
25
|
+
external: ['react', 'react-dom', 'react-router-dom', '@uniweb/core']
|
|
26
|
+
// Note: @uniweb/kit is NOT in external — it gets tree-shaken
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
11
32
|
## Quick Start
|
|
12
33
|
|
|
13
34
|
```jsx
|
|
14
|
-
import { Link, Image,
|
|
35
|
+
import { Link, Image, useWebsite } from '@uniweb/kit'
|
|
15
36
|
|
|
16
|
-
function Hero() {
|
|
37
|
+
function Hero({ content }) {
|
|
17
38
|
const { localize } = useWebsite()
|
|
18
39
|
|
|
19
40
|
return (
|
|
20
|
-
<
|
|
21
|
-
<Image src=
|
|
22
|
-
<h1>{localize({ en: 'Welcome',
|
|
41
|
+
<div>
|
|
42
|
+
<Image src={content.imgs[0]?.url} alt="Hero" />
|
|
43
|
+
<h1>{localize({ en: 'Welcome', es: 'Bienvenido' })}</h1>
|
|
23
44
|
<Link to="/about">Learn More</Link>
|
|
24
|
-
</
|
|
45
|
+
</div>
|
|
25
46
|
)
|
|
26
47
|
}
|
|
27
48
|
```
|
|
28
49
|
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
Kit provides the standard primitives that make Uniweb foundations work correctly:
|
|
32
|
-
|
|
33
|
-
1. **Handle Uniweb conventions** — Components like `Link` and `Image` understand topic links, locale prefixes, and asset resolution automatically.
|
|
34
|
-
|
|
35
|
-
2. **Runtime integration** — The `useWebsite()` hook gives you access to the website instance, localization functions, and routing utilities.
|
|
36
|
-
|
|
37
|
-
3. **Semantic parser output** — Typography components (`H1`, `P`, `Text`) handle both strings and string arrays correctly, matching the semantic parser's output format.
|
|
38
|
-
|
|
39
|
-
4. **Portability** — Your foundation works identically in development, static builds, and the Uniweb platform.
|
|
50
|
+
## Exports Overview
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
| Import Path | Purpose |
|
|
53
|
+
|-------------|---------|
|
|
54
|
+
| `@uniweb/kit` | Core components, hooks, and utilities |
|
|
55
|
+
| `@uniweb/kit/styled` | Pre-styled components (Section, SidebarLayout, etc.) |
|
|
56
|
+
| `@uniweb/kit/search` | Search client and hooks (requires Fuse.js) |
|
|
42
57
|
|
|
43
|
-
|
|
44
|
-
|---------------------|---------------------------|
|
|
45
|
-
| Links and navigation | Custom button styles |
|
|
46
|
-
| Images with filters | Specialized image treatments |
|
|
47
|
-
| Text rendering | Domain-specific formatting |
|
|
48
|
-
| Video embeds | Custom media players |
|
|
49
|
-
| Website/locale access | Business logic |
|
|
50
|
-
|
|
51
|
-
Kit components are designed to be composed and styled, not replaced wholesale. Wrap them, extend them, or use them as building blocks.
|
|
58
|
+
---
|
|
52
59
|
|
|
53
60
|
## Components
|
|
54
61
|
|
|
55
|
-
###
|
|
56
|
-
|
|
57
|
-
#### Link
|
|
62
|
+
### Link
|
|
58
63
|
|
|
59
64
|
Smart link component with routing, downloads, and auto-generated accessible titles.
|
|
60
65
|
|
|
@@ -70,11 +75,11 @@ import { Link } from '@uniweb/kit'
|
|
|
70
75
|
| Prop | Type | Description |
|
|
71
76
|
|------|------|-------------|
|
|
72
77
|
| `to` / `href` | `string` | Destination URL |
|
|
73
|
-
| `title` | `string` | Tooltip (auto-generated if
|
|
78
|
+
| `title` | `string` | Tooltip (auto-generated if omitted) |
|
|
74
79
|
| `target` | `string` | Link target |
|
|
75
80
|
| `download` | `boolean` | Force download behavior |
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
### Image
|
|
78
83
|
|
|
79
84
|
Versatile image component with filters and profile integration.
|
|
80
85
|
|
|
@@ -90,13 +95,13 @@ import { Image } from '@uniweb/kit'
|
|
|
90
95
|
|------|------|-------------|
|
|
91
96
|
| `src` / `url` | `string` | Image URL |
|
|
92
97
|
| `alt` | `string` | Alt text |
|
|
93
|
-
| `size` | `string` |
|
|
98
|
+
| `size` | `string` | Preset: xs, sm, md, lg, xl, 2xl, full |
|
|
94
99
|
| `rounded` | `boolean\|string` | Border radius |
|
|
95
100
|
| `filter` | `object` | CSS filters: blur, brightness, contrast, grayscale, saturate, sepia |
|
|
96
101
|
| `profile` | `object` | Profile for avatar/banner images |
|
|
97
102
|
| `type` | `string` | Image type: avatar, banner |
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
### SafeHtml
|
|
100
105
|
|
|
101
106
|
Safely render HTML with topic link resolution.
|
|
102
107
|
|
|
@@ -107,7 +112,7 @@ import { SafeHtml } from '@uniweb/kit'
|
|
|
107
112
|
<SafeHtml value='<a href="topic:about">About</a>' />
|
|
108
113
|
```
|
|
109
114
|
|
|
110
|
-
|
|
115
|
+
### Icon
|
|
111
116
|
|
|
112
117
|
SVG icon component with built-in icons and URL loading.
|
|
113
118
|
|
|
@@ -119,28 +124,38 @@ import { Icon } from '@uniweb/kit'
|
|
|
119
124
|
<Icon svg="<svg>...</svg>" />
|
|
120
125
|
```
|
|
121
126
|
|
|
122
|
-
Built-in
|
|
127
|
+
Built-in: check, alert, user, heart, settings, star, close, menu, chevronDown, chevronRight, externalLink, download, play
|
|
123
128
|
|
|
124
|
-
###
|
|
129
|
+
### SocialIcon
|
|
125
130
|
|
|
126
|
-
|
|
131
|
+
Social media platform icons with automatic detection.
|
|
127
132
|
|
|
128
|
-
|
|
133
|
+
```jsx
|
|
134
|
+
import { SocialIcon, getSocialPlatform, filterSocialLinks } from '@uniweb/kit'
|
|
135
|
+
|
|
136
|
+
<SocialIcon platform="twitter" size={24} />
|
|
137
|
+
<SocialIcon url="https://twitter.com/example" />
|
|
138
|
+
|
|
139
|
+
// Utilities
|
|
140
|
+
getSocialPlatform('https://linkedin.com/in/user') // 'linkedin'
|
|
141
|
+
filterSocialLinks(links) // Filter to only social links
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Supported: facebook, twitter, x, linkedin, instagram, youtube, github, medium, pinterest, tiktok, discord, mastodon, bluesky, email, phone, orcid, researchgate, googlescholar
|
|
145
|
+
|
|
146
|
+
### Typography
|
|
147
|
+
|
|
148
|
+
Smart typography components for rendering semantic parser output.
|
|
129
149
|
|
|
130
150
|
```jsx
|
|
131
151
|
import { Text, H1, H2, P, PlainText } from '@uniweb/kit'
|
|
132
152
|
|
|
133
|
-
// Using semantic aliases (recommended)
|
|
134
153
|
<H1 text="Main Title" />
|
|
135
154
|
<H2 text={["Multi-line", "Subtitle"]} />
|
|
136
155
|
<P text="A paragraph of content" />
|
|
137
156
|
<P text={["First paragraph", "Second paragraph"]} />
|
|
138
157
|
|
|
139
|
-
//
|
|
140
|
-
<Text text="Hello" as="h1" />
|
|
141
|
-
<Text text={["Line 1", "Line 2"]} as="h2" />
|
|
142
|
-
|
|
143
|
-
// Plain text (HTML tags shown as text)
|
|
158
|
+
// Plain text (HTML shown as text)
|
|
144
159
|
<PlainText text="Show <strong>tags</strong> as text" />
|
|
145
160
|
```
|
|
146
161
|
|
|
@@ -150,19 +165,11 @@ import { Text, H1, H2, P, PlainText } from '@uniweb/kit'
|
|
|
150
165
|
| `as` | `string` | Tag: 'h1'-'h6', 'p', 'div', 'span' |
|
|
151
166
|
| `html` | `boolean` | Render as HTML (default: true) |
|
|
152
167
|
| `lineAs` | `string` | Tag for array items |
|
|
153
|
-
| `className` | `string` | CSS classes |
|
|
154
168
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
**Key behaviors**:
|
|
158
|
-
- Empty strings/arrays return `null` (no empty elements)
|
|
159
|
-
- Headings with arrays: all lines wrapped in single heading tag
|
|
160
|
-
- Paragraphs with arrays: each line gets its own `<p>` tag
|
|
169
|
+
Aliases: `H1`, `H2`, `H3`, `H4`, `H5`, `H6`, `P`, `Span`, `Div`, `PlainText`
|
|
161
170
|
|
|
162
171
|
### Media
|
|
163
172
|
|
|
164
|
-
#### Media
|
|
165
|
-
|
|
166
173
|
Video player for YouTube, Vimeo, and local videos.
|
|
167
174
|
|
|
168
175
|
```jsx
|
|
@@ -173,17 +180,7 @@ import { Media } from '@uniweb/kit'
|
|
|
173
180
|
<Media src="https://youtube.com/..." thumbnail="/poster.jpg" facade />
|
|
174
181
|
```
|
|
175
182
|
|
|
176
|
-
|
|
177
|
-
|------|------|-------------|
|
|
178
|
-
| `src` | `string` | Video URL |
|
|
179
|
-
| `thumbnail` | `string` | Poster image URL |
|
|
180
|
-
| `autoplay` | `boolean` | Auto-play video |
|
|
181
|
-
| `muted` | `boolean` | Mute video |
|
|
182
|
-
| `loop` | `boolean` | Loop video |
|
|
183
|
-
| `controls` | `boolean` | Show controls |
|
|
184
|
-
| `facade` | `boolean` | Show thumbnail with play button |
|
|
185
|
-
|
|
186
|
-
#### FileLogo
|
|
183
|
+
### FileLogo
|
|
187
184
|
|
|
188
185
|
File type icons based on filename.
|
|
189
186
|
|
|
@@ -191,143 +188,364 @@ File type icons based on filename.
|
|
|
191
188
|
import { FileLogo } from '@uniweb/kit'
|
|
192
189
|
|
|
193
190
|
<FileLogo filename="report.pdf" size="32" />
|
|
194
|
-
<FileLogo filename="data.xlsx" />
|
|
195
191
|
```
|
|
196
192
|
|
|
197
|
-
|
|
193
|
+
### MediaIcon
|
|
198
194
|
|
|
199
|
-
|
|
195
|
+
Platform icons (YouTube, Vimeo, etc.).
|
|
200
196
|
|
|
201
197
|
```jsx
|
|
202
198
|
import { MediaIcon } from '@uniweb/kit'
|
|
203
199
|
|
|
204
|
-
<MediaIcon type="
|
|
205
|
-
<MediaIcon type="linkedin" className="text-blue-600" />
|
|
200
|
+
<MediaIcon type="youtube" size="24" />
|
|
206
201
|
```
|
|
207
202
|
|
|
208
|
-
|
|
203
|
+
### Asset
|
|
209
204
|
|
|
210
|
-
|
|
205
|
+
File preview with download functionality.
|
|
211
206
|
|
|
212
|
-
|
|
207
|
+
```jsx
|
|
208
|
+
import { Asset } from '@uniweb/kit'
|
|
209
|
+
|
|
210
|
+
<Asset value="document.pdf" profile={profile} />
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
213
214
|
|
|
214
|
-
|
|
215
|
+
## Hooks
|
|
216
|
+
|
|
217
|
+
### useWebsite
|
|
218
|
+
|
|
219
|
+
Access website instance and utilities.
|
|
215
220
|
|
|
216
221
|
```jsx
|
|
217
|
-
import {
|
|
222
|
+
import { useWebsite } from '@uniweb/kit'
|
|
218
223
|
|
|
219
|
-
|
|
224
|
+
function MyComponent() {
|
|
225
|
+
const {
|
|
226
|
+
website, // Website instance
|
|
227
|
+
localize, // Localize multilingual values
|
|
228
|
+
makeHref, // Transform hrefs (topic:, locale prefixes)
|
|
229
|
+
getLanguage, // Current language code
|
|
230
|
+
getLanguages // Available languages
|
|
231
|
+
} = useWebsite()
|
|
220
232
|
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
<div>Column 2</div>
|
|
224
|
-
</Section>
|
|
233
|
+
return <div>{localize({ en: 'Hello', fr: 'Bonjour' })}</div>
|
|
234
|
+
}
|
|
225
235
|
```
|
|
226
236
|
|
|
227
|
-
|
|
228
|
-
|------|------|-------------|
|
|
229
|
-
| `content` | `object\|array` | Content to render |
|
|
230
|
-
| `block` | `object` | Block object from runtime |
|
|
231
|
-
| `width` | `string` | sm, md, lg, xl, 2xl, full |
|
|
232
|
-
| `columns` | `string` | 1, 2, 3, 4 |
|
|
233
|
-
| `padding` | `string` | none, sm, md, lg, xl |
|
|
237
|
+
### useActiveRoute
|
|
234
238
|
|
|
235
|
-
|
|
239
|
+
Detect active navigation state.
|
|
236
240
|
|
|
237
|
-
|
|
241
|
+
```jsx
|
|
242
|
+
import { useActiveRoute } from '@uniweb/kit'
|
|
243
|
+
|
|
244
|
+
function NavLink({ page }) {
|
|
245
|
+
const { isActive, isActiveOrAncestor } = useActiveRoute()
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<Link
|
|
249
|
+
to={page.route}
|
|
250
|
+
className={isActiveOrAncestor(page) ? 'font-bold' : ''}
|
|
251
|
+
>
|
|
252
|
+
{page.title}
|
|
253
|
+
</Link>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### useScrolled
|
|
259
|
+
|
|
260
|
+
Detect scroll position for sticky headers.
|
|
238
261
|
|
|
239
262
|
```jsx
|
|
240
|
-
import {
|
|
263
|
+
import { useScrolled } from '@uniweb/kit'
|
|
241
264
|
|
|
242
|
-
|
|
265
|
+
function Header() {
|
|
266
|
+
const scrolled = useScrolled(50) // Threshold in pixels
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<header className={scrolled ? 'shadow-md' : ''}>
|
|
270
|
+
...
|
|
271
|
+
</header>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
243
274
|
```
|
|
244
275
|
|
|
245
|
-
|
|
276
|
+
### useMobileMenu
|
|
246
277
|
|
|
247
|
-
|
|
278
|
+
Mobile menu state management.
|
|
248
279
|
|
|
249
280
|
```jsx
|
|
250
|
-
import {
|
|
281
|
+
import { useMobileMenu } from '@uniweb/kit'
|
|
282
|
+
|
|
283
|
+
function Navbar() {
|
|
284
|
+
const { isOpen, toggle, close } = useMobileMenu()
|
|
251
285
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
286
|
+
return (
|
|
287
|
+
<>
|
|
288
|
+
<button onClick={toggle}>Menu</button>
|
|
289
|
+
{isOpen && <MobileMenu onClose={close} />}
|
|
290
|
+
</>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
257
293
|
```
|
|
258
294
|
|
|
259
|
-
###
|
|
295
|
+
### useAccordion
|
|
260
296
|
|
|
261
|
-
|
|
297
|
+
Accordion/FAQ state management.
|
|
262
298
|
|
|
263
|
-
|
|
299
|
+
```jsx
|
|
300
|
+
import { useAccordion } from '@uniweb/kit'
|
|
301
|
+
|
|
302
|
+
function FAQ({ items }) {
|
|
303
|
+
const { isOpen, toggle } = useAccordion()
|
|
304
|
+
|
|
305
|
+
return items.map((item, i) => (
|
|
306
|
+
<div key={i}>
|
|
307
|
+
<button onClick={() => toggle(i)}>{item.question}</button>
|
|
308
|
+
{isOpen(i) && <p>{item.answer}</p>}
|
|
309
|
+
</div>
|
|
310
|
+
))
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### useInView
|
|
315
|
+
|
|
316
|
+
Viewport intersection detection for lazy loading and animations.
|
|
264
317
|
|
|
265
318
|
```jsx
|
|
266
|
-
import {
|
|
319
|
+
import { useInView, useIsInView } from '@uniweb/kit'
|
|
267
320
|
|
|
268
|
-
|
|
269
|
-
|
|
321
|
+
function AnimatedSection() {
|
|
322
|
+
const { ref, inView } = useInView({ threshold: 0.2, once: true })
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<div ref={ref} className={inView ? 'animate-fade-in' : 'opacity-0'}>
|
|
326
|
+
Content appears when scrolled into view
|
|
327
|
+
</div>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Simple boolean version
|
|
332
|
+
function LazyImage({ src }) {
|
|
333
|
+
const [ref, isInView] = useIsInView()
|
|
334
|
+
return <div ref={ref}>{isInView && <img src={src} />}</div>
|
|
335
|
+
}
|
|
270
336
|
```
|
|
271
337
|
|
|
272
|
-
|
|
338
|
+
### useGridLayout
|
|
273
339
|
|
|
274
|
-
|
|
340
|
+
Responsive grid utilities.
|
|
275
341
|
|
|
276
342
|
```jsx
|
|
277
|
-
import {
|
|
343
|
+
import { useGridLayout, getGridClasses } from '@uniweb/kit'
|
|
278
344
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
345
|
+
function Gallery({ items }) {
|
|
346
|
+
const { columns } = useGridLayout(items.length, { maxColumns: 4 })
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<div className={getGridClasses(columns)}>
|
|
350
|
+
{items.map(item => <Card key={item.id} {...item} />)}
|
|
351
|
+
</div>
|
|
352
|
+
)
|
|
353
|
+
}
|
|
284
354
|
```
|
|
285
355
|
|
|
286
|
-
|
|
356
|
+
### Theme Hooks
|
|
287
357
|
|
|
288
|
-
|
|
358
|
+
Access site theming data at runtime.
|
|
289
359
|
|
|
290
|
-
|
|
360
|
+
```jsx
|
|
361
|
+
import {
|
|
362
|
+
useThemeData,
|
|
363
|
+
useThemeColor,
|
|
364
|
+
useThemeColorVar,
|
|
365
|
+
useColorContext,
|
|
366
|
+
useAppearance
|
|
367
|
+
} from '@uniweb/kit'
|
|
368
|
+
|
|
369
|
+
function ThemedComponent({ block }) {
|
|
370
|
+
// Full theme access
|
|
371
|
+
const theme = useThemeData()
|
|
372
|
+
const palettes = theme?.getPaletteNames() // ['primary', 'secondary', ...]
|
|
373
|
+
|
|
374
|
+
// Get specific color
|
|
375
|
+
const primaryColor = useThemeColor('primary', 500) // '#3b82f6'
|
|
376
|
+
const primaryVar = useThemeColorVar('primary', 600) // 'var(--primary-600)'
|
|
377
|
+
|
|
378
|
+
// Context-aware (light/medium/dark sections)
|
|
379
|
+
const context = useColorContext(block) // 'light' | 'medium' | 'dark'
|
|
380
|
+
|
|
381
|
+
// Dark mode
|
|
382
|
+
const { scheme, toggle, canToggle } = useAppearance()
|
|
383
|
+
|
|
384
|
+
return (
|
|
385
|
+
<div style={{ color: primaryColor }}>
|
|
386
|
+
{canToggle && (
|
|
387
|
+
<button onClick={toggle}>
|
|
388
|
+
{scheme === 'dark' ? 'Light' : 'Dark'}
|
|
389
|
+
</button>
|
|
390
|
+
)}
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Search (`@uniweb/kit/search`)
|
|
399
|
+
|
|
400
|
+
Full-text search powered by Fuse.js. Requires `fuse.js` as a peer dependency in your foundation.
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npm install fuse.js
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### useSearch
|
|
407
|
+
|
|
408
|
+
Main search hook with debouncing and state management.
|
|
291
409
|
|
|
292
410
|
```jsx
|
|
293
|
-
import {
|
|
411
|
+
import { useSearch } from '@uniweb/kit/search'
|
|
294
412
|
|
|
295
|
-
function
|
|
296
|
-
const {
|
|
297
|
-
|
|
298
|
-
localize, // Localize multilingual values
|
|
299
|
-
makeHref, // Transform hrefs
|
|
300
|
-
getLanguage, // Current language
|
|
301
|
-
getLanguages // Available languages
|
|
302
|
-
} = useWebsite()
|
|
413
|
+
function SearchComponent() {
|
|
414
|
+
const { website } = useWebsite()
|
|
415
|
+
const { query, results, isLoading, isEnabled, preload } = useSearch(website)
|
|
303
416
|
|
|
304
|
-
|
|
417
|
+
if (!isEnabled) return null
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<div>
|
|
421
|
+
<input onChange={e => query(e.target.value)} placeholder="Search..." />
|
|
422
|
+
{isLoading && <span>Searching...</span>}
|
|
423
|
+
{results.map(r => (
|
|
424
|
+
<a key={r.id} href={r.href}>{r.title}</a>
|
|
425
|
+
))}
|
|
426
|
+
</div>
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### useSearchWithIntent
|
|
432
|
+
|
|
433
|
+
Intent-based preloading — loads search index on hover/focus instead of page load.
|
|
434
|
+
|
|
435
|
+
```jsx
|
|
436
|
+
import { useSearchWithIntent, useSearchShortcut } from '@uniweb/kit/search'
|
|
437
|
+
|
|
438
|
+
function SearchButton({ onClick }) {
|
|
439
|
+
const { website } = useWebsite()
|
|
440
|
+
const { triggerPreload, intentProps } = useSearchWithIntent(website)
|
|
441
|
+
|
|
442
|
+
// Cmd/Ctrl+K shortcut with preload
|
|
443
|
+
useSearchShortcut({
|
|
444
|
+
onOpen: onClick,
|
|
445
|
+
onPreload: triggerPreload,
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
return (
|
|
449
|
+
<button onClick={onClick} {...intentProps}>
|
|
450
|
+
Search
|
|
451
|
+
</button>
|
|
452
|
+
)
|
|
305
453
|
}
|
|
306
454
|
```
|
|
307
455
|
|
|
308
|
-
|
|
456
|
+
This saves bandwidth — the search index only loads when users show intent to search.
|
|
457
|
+
|
|
458
|
+
### useSearchShortcut
|
|
459
|
+
|
|
460
|
+
Keyboard shortcut for opening search.
|
|
461
|
+
|
|
462
|
+
```jsx
|
|
463
|
+
import { useSearchShortcut } from '@uniweb/kit/search'
|
|
464
|
+
|
|
465
|
+
// Simple
|
|
466
|
+
useSearchShortcut(() => setSearchOpen(true))
|
|
467
|
+
|
|
468
|
+
// With preload on shortcut
|
|
469
|
+
useSearchShortcut({
|
|
470
|
+
onOpen: () => setSearchOpen(true),
|
|
471
|
+
onPreload: () => searchClient.preload()
|
|
472
|
+
})
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### createSearchClient
|
|
476
|
+
|
|
477
|
+
Low-level search client for advanced use.
|
|
478
|
+
|
|
479
|
+
```jsx
|
|
480
|
+
import { createSearchClient } from '@uniweb/kit/search'
|
|
481
|
+
|
|
482
|
+
const client = createSearchClient(website, {
|
|
483
|
+
fuseOptions: { threshold: 0.3 },
|
|
484
|
+
defaultLimit: 10
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
// Query
|
|
488
|
+
const results = await client.query('authentication', { limit: 5 })
|
|
489
|
+
|
|
490
|
+
// Preload index
|
|
491
|
+
await client.preload()
|
|
492
|
+
|
|
493
|
+
// Check status
|
|
494
|
+
client.isEnabled()
|
|
495
|
+
client.getIndexUrl()
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Styled Components (`@uniweb/kit/styled`)
|
|
501
|
+
|
|
502
|
+
Pre-styled components with Tailwind CSS. Import separately to keep core kit dependency-free.
|
|
503
|
+
|
|
504
|
+
```jsx
|
|
505
|
+
import { Section, SidebarLayout, Disclaimer } from '@uniweb/kit/styled'
|
|
506
|
+
|
|
507
|
+
<Section width="lg" padding="md" className="bg-gray-50">
|
|
508
|
+
<h1>Welcome</h1>
|
|
509
|
+
</Section>
|
|
510
|
+
|
|
511
|
+
<SidebarLayout sidebar={<Nav />} sidebarPosition="left">
|
|
512
|
+
<main>Content</main>
|
|
513
|
+
</SidebarLayout>
|
|
514
|
+
|
|
515
|
+
<Disclaimer
|
|
516
|
+
title="Terms of Service"
|
|
517
|
+
content="<p>Please read our terms...</p>"
|
|
518
|
+
triggerText="View Terms"
|
|
519
|
+
/>
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Utilities
|
|
309
525
|
|
|
310
526
|
```jsx
|
|
311
527
|
import { cn, stripTags, isExternalUrl, isFileUrl, detectMediaType } from '@uniweb/kit'
|
|
312
528
|
|
|
313
|
-
// Merge Tailwind classes
|
|
529
|
+
// Merge Tailwind classes (uses tailwind-merge)
|
|
314
530
|
cn('px-4 py-2', 'bg-blue-500', condition && 'opacity-50')
|
|
315
531
|
|
|
316
532
|
// Strip HTML tags
|
|
317
|
-
stripTags('<p>Hello</p>')
|
|
533
|
+
stripTags('<p>Hello</p>') // "Hello"
|
|
318
534
|
|
|
319
535
|
// URL utilities
|
|
320
|
-
isExternalUrl('https://google.com')
|
|
321
|
-
isFileUrl('/files/doc.pdf')
|
|
322
|
-
detectMediaType('https://youtube.com/...')
|
|
536
|
+
isExternalUrl('https://google.com') // true
|
|
537
|
+
isFileUrl('/files/doc.pdf') // true
|
|
538
|
+
detectMediaType('https://youtube.com/...') // 'youtube'
|
|
323
539
|
```
|
|
324
540
|
|
|
541
|
+
---
|
|
542
|
+
|
|
325
543
|
## Architecture
|
|
326
544
|
|
|
327
545
|
```
|
|
328
546
|
┌─────────────────────────────────────────────────────────────┐
|
|
329
547
|
│ Foundation (your code) │
|
|
330
|
-
│ ├── imports @uniweb/kit (bundled
|
|
548
|
+
│ ├── imports @uniweb/kit (bundled, tree-shaken) │
|
|
331
549
|
│ └── @uniweb/core marked as external │
|
|
332
550
|
└─────────────────────────────────────────────────────────────┘
|
|
333
551
|
│
|
|
@@ -340,29 +558,11 @@ detectMediaType('https://youtube.com/...') // 'youtube'
|
|
|
340
558
|
└─────────────────────────────────────────────────────────────┘
|
|
341
559
|
```
|
|
342
560
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
1. **Use kit components** for common UI patterns
|
|
346
|
-
2. **Use hooks** like `useWebsite` for localization and routing
|
|
347
|
-
3. **Don't access `globalThis.uniweb`** directly - use kit's abstractions
|
|
348
|
-
4. **Bundle kit, externalize core** - kit gets tree-shaken into your foundation
|
|
561
|
+
### Why bundle kit but externalize core?
|
|
349
562
|
|
|
350
|
-
|
|
351
|
-
// vite.config.js
|
|
352
|
-
export default {
|
|
353
|
-
build: {
|
|
354
|
-
rollupOptions: {
|
|
355
|
-
// Kit is bundled (tree-shaken), core is external (provided by runtime)
|
|
356
|
-
external: ['react', 'react-dom', 'react-router-dom', '@uniweb/core']
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
```
|
|
563
|
+
- **Kit**: Different foundations may use different subsets of kit. Tree-shaking ensures each foundation only includes what it uses.
|
|
361
564
|
|
|
362
|
-
|
|
363
|
-
- Tree-shake unused kit components
|
|
364
|
-
- Override or extend kit components
|
|
365
|
-
- Bring your own alternative components
|
|
565
|
+
- **Core**: Contains the Website, Page, and Block classes that must be singletons. The runtime provides these — foundations reference them via the external import.
|
|
366
566
|
|
|
367
567
|
## License
|
|
368
568
|
|