@underverse-ui/underverse 0.2.38 β†’ 0.2.40

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 CHANGED
@@ -4,25 +4,81 @@ Docs: https://underverse-sepia.vercel.app/vi/docs/underverse
4
4
 
5
5
  **Author:** Tran Van Bach
6
6
 
7
- A comprehensive UI component library for React/Next.js applications, extracted from the main project. Built with Tailwind CSS, `clsx`, and `tailwind-merge`. Some components support `next-intl` (optional).
7
+ A comprehensive UI component library for React/Next.js applications, extracted from the main project. Built with Tailwind CSS, `clsx`, and `tailwind-merge`.
8
+
9
+ ## ✨ Features
10
+
11
+ - 🎨 **60+ UI Components** - Buttons, Modals, DatePicker, DataTable, and more
12
+ - 🌐 **Multi-language Support** - Built-in translations for English, Vietnamese, Korean, Japanese
13
+ - ⚑ **Tree-shakeable** - Import only what you need
14
+ - πŸ”Œ **Flexible i18n** - Works with `next-intl` or standalone React
15
+ - 🎯 **TypeScript First** - Full type definitions included
16
+ - πŸŒ™ **Dark Mode Ready** - Supports light/dark themes via CSS variables
17
+
18
+ ## Supported Locales
19
+
20
+ | Locale | Language | Flag |
21
+ | ------ | ---------- | ---- |
22
+ | `en` | English | πŸ‡ΊπŸ‡Έ |
23
+ | `vi` | TiαΊΏng Việt | πŸ‡»πŸ‡³ |
24
+ | `ko` | ν•œκ΅­μ–΄ | πŸ‡°πŸ‡· |
25
+ | `ja` | ζ—₯本θͺž | πŸ‡―πŸ‡΅ |
8
26
 
9
27
  ## Requirements
28
+
10
29
  - Node >= 18
11
- - Peer dependencies: `react`, `react-dom`, `next`, `next-intl`
30
+ - Peer dependencies: `react`, `react-dom`
31
+ - Optional: `next`, `next-intl` (for Next.js projects)
12
32
 
13
33
  ## Installation
34
+
14
35
  ```bash
15
36
  # Install the package
16
37
  npm i @underverse-ui/underverse
17
38
 
18
- # Install peer dependencies (if not already in your app)
39
+ # For Next.js projects (with next-intl)
19
40
  npm i react react-dom next next-intl
41
+
42
+ # For standalone React projects (Vite, CRA, etc.)
43
+ npm i react react-dom
20
44
  ```
21
45
 
22
46
  ## Tailwind CSS Configuration
47
+
23
48
  Components use color variables like `primary`, `secondary`, `destructive`, etc. Make sure your Tailwind theme/tokens include these variables.
24
49
 
25
- ## Quick Start
50
+ ---
51
+
52
+ ## πŸš€ Quick Start
53
+
54
+ ### Standalone React (Vite, CRA, etc.)
55
+
56
+ ```tsx
57
+ import { TranslationProvider, Button, DatePicker, ToastProvider, useToast } from "@underverse-ui/underverse";
58
+
59
+ function App() {
60
+ return (
61
+ <TranslationProvider locale="vi">
62
+ <ToastProvider>
63
+ <MyComponent />
64
+ </ToastProvider>
65
+ </TranslationProvider>
66
+ );
67
+ }
68
+
69
+ function MyComponent() {
70
+ const { addToast } = useToast();
71
+
72
+ return (
73
+ <div>
74
+ <DatePicker onChange={(date) => console.log(date)} />
75
+ <Button onClick={() => addToast({ type: "success", message: "Hello!" })}>Click me</Button>
76
+ </div>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ### Next.js (with next-intl)
26
82
 
27
83
  ```tsx
28
84
  import { Button, ToastProvider, useToast } from "@underverse-ui/underverse";
@@ -31,9 +87,7 @@ function App() {
31
87
  const { addToast } = useToast();
32
88
  return (
33
89
  <ToastProvider>
34
- <Button onClick={() => addToast({ type: 'success', message: 'Hello' })}>
35
- Click me
36
- </Button>
90
+ <Button onClick={() => addToast({ type: "success", message: "Hello" })}>Click me</Button>
37
91
  </ToastProvider>
38
92
  );
39
93
  }
@@ -42,47 +96,146 @@ function App() {
42
96
  ## Exported Components
43
97
 
44
98
  ### Core Components
99
+
45
100
  - **Buttons:** `Button`
46
101
  - **Display:** `Badge`, `Card`, `Avatar`, `Skeleton`, `Progress`
47
102
  - **Form Inputs:** `Input`, `Textarea`, `Checkbox`, `Switch`, `Label`
48
103
 
49
104
  ### Feedback & Overlays
105
+
50
106
  - `Modal`, `ToastProvider`, `useToast`, `Tooltip`, `Popover`, `Sheet` (includes `Drawer`, `SlideOver`, `BottomSheet`, `SidebarSheet`), `Alert`, `GlobalLoading` (includes `PageLoading`, `InlineLoading`, `ButtonLoading`)
51
107
 
52
108
  ### Form Controls & Pickers
109
+
53
110
  - `RadioGroup`, `Slider`, `DatePicker`, `Combobox`, `MultiCombobox`, `CategoryTreeSelect`
54
111
 
55
112
  ### Navigation & Structure
113
+
56
114
  - `Breadcrumb`, `Tabs` (includes `SimpleTabs`, `PillTabs`, `VerticalTabs`), `DropdownMenu`, `Pagination`, `Section`, `ScrollArea`
57
115
 
58
116
  ### Data Display
117
+
59
118
  - `Table`, `DataTable`
60
119
 
61
120
  ### Media Components
121
+
62
122
  - `SmartImage`, `ImageUpload`, `Carousel`
63
123
 
64
124
  ### Utilities
125
+
65
126
  - `ClientOnly`, `Loading`, `NotificationModal`, `FloatingContacts`, `AccessDenied`
66
127
  - Headless controls: `ThemeToggle`, `LanguageSwitcher`
67
128
  - Utility functions: `cn`, `DateUtils`, style constants
68
129
 
69
130
  ## Important Notes
131
+
70
132
  - Library is i18n‑agnostic: components have sensible English defaults and accept text via props.
71
133
  - If your app uses `next-intl`, you can merge our ready‑made messages to localize built‑in texts.
72
134
  - `NotificationBell` is not exported (depends on project‑specific API/socket implementations).
73
135
 
136
+ ---
137
+
138
+ ## πŸ“¦ Date Utilities
139
+
140
+ The package includes standalone date utilities with locale support (no Next.js required):
141
+
142
+ ```tsx
143
+ import { DateUtils } from "@underverse-ui/underverse";
144
+
145
+ // Format dates with locale
146
+ DateUtils.formatDate(new Date(), "ko"); // "2026λ…„ 1μ›” 5일"
147
+ DateUtils.formatDate(new Date(), "ja"); // "2026εΉ΄1月5ζ—₯"
148
+ DateUtils.formatDate(new Date(), "vi"); // "05/01/2026"
149
+ DateUtils.formatDate(new Date(), "en"); // "January 5, 2026"
150
+
151
+ // Relative time formatting
152
+ DateUtils.formatTimeAgo(new Date(Date.now() - 3600000), "ko"); // "1μ‹œκ°„ μ „"
153
+ DateUtils.formatTimeAgo(new Date(Date.now() - 3600000), "ja"); // "1時間前"
154
+
155
+ // Smart date formatting (Today, Yesterday, or full date)
156
+ DateUtils.formatDateSmart(new Date(), "ja"); // "今ζ—₯ 14:30"
157
+
158
+ // Utility checks
159
+ DateUtils.isToday(new Date()); // true
160
+ DateUtils.isYesterday(new Date(Date.now() - 86400000)); // true
161
+
162
+ // Get day of week
163
+ DateUtils.getDayOfWeek(new Date(), "ko"); // "μΌμš”μΌ"
164
+ DateUtils.getDayOfWeek(new Date(), "ja"); // "ζ—₯ζ›œζ—₯"
165
+
166
+ // Form input formatting
167
+ DateUtils.formatDateForInput(new Date()); // "2026-01-05"
168
+ DateUtils.formatDateTimeForInput(new Date()); // "2026-01-05T14:30"
169
+ ```
170
+
171
+ ### Available Date Functions
172
+
173
+ | Function | Description |
174
+ | ------------------------------- | ----------------------------------- |
175
+ | `formatDate(date, locale)` | Full date format |
176
+ | `formatDateShort(date, locale)` | Short date format |
177
+ | `formatTime(date, locale)` | Time only (HH:mm) |
178
+ | `formatDateTime(date, locale)` | Date + time |
179
+ | `formatTimeAgo(date, locale)` | Relative time (e.g., "2 hours ago") |
180
+ | `formatDateSmart(date, locale)` | Today/Yesterday/Full date |
181
+ | `isToday(date)` | Check if date is today |
182
+ | `isYesterday(date)` | Check if date is yesterday |
183
+ | `getDayOfWeek(date, locale)` | Get localized day name |
184
+ | `formatDateForInput(date)` | YYYY-MM-DD format |
185
+ | `formatDateTimeForInput(date)` | YYYY-MM-DDTHH:mm format |
186
+
187
+ ---
188
+
189
+ ## 🎨 Animation Utilities
190
+
191
+ The package includes ShadCN-compatible animation utilities:
192
+
193
+ ```tsx
194
+ import { useShadCNAnimations, injectAnimationStyles, getAnimationStyles } from "@underverse-ui/underverse";
195
+
196
+ // React hook - automatically injects styles on mount
197
+ function MyComponent() {
198
+ useShadCNAnimations();
199
+ return <div className="animate-accordion-down">Content</div>;
200
+ }
201
+
202
+ // Manual injection (for non-React usage)
203
+ injectAnimationStyles();
204
+
205
+ // Get CSS string for custom injection
206
+ const cssString = getAnimationStyles();
207
+ ```
208
+
209
+ ### Available Animations
210
+
211
+ | Class | Description |
212
+ | ------------------------------ | ---------------------------- |
213
+ | `animate-accordion-down` | Accordion expand animation |
214
+ | `animate-accordion-up` | Accordion collapse animation |
215
+ | `animate-caret-blink` | Blinking caret cursor |
216
+ | `animate-fade-in` | Fade in effect |
217
+ | `animate-fade-out` | Fade out effect |
218
+ | `animate-slide-in-from-top` | Slide in from top |
219
+ | `animate-slide-in-from-bottom` | Slide in from bottom |
220
+ | `animate-slide-in-from-left` | Slide in from left |
221
+ | `animate-slide-in-from-right` | Slide in from right |
222
+ | `animate-zoom-in` | Zoom in effect |
223
+ | `animate-zoom-out` | Zoom out effect |
224
+
225
+ ---
226
+
74
227
  ## next-intl Integration (Next.js App Router)
75
228
 
76
- 1) Configure plugin and time zone (to avoid `ENVIRONMENT_FALLBACK`):
229
+ 1. Configure plugin and time zone (to avoid `ENVIRONMENT_FALLBACK`):
77
230
 
78
231
  ```ts
79
232
  // next.config.ts
80
- import createNextIntlPlugin from 'next-intl/plugin';
233
+ import createNextIntlPlugin from "next-intl/plugin";
81
234
 
82
235
  const withNextIntl = createNextIntlPlugin({
83
- locales: ['vi', 'en'],
84
- defaultLocale: 'vi',
85
- timeZone: 'Asia/Ho_Chi_Minh' // important for SSR
236
+ locales: ["vi", "en"],
237
+ defaultLocale: "vi",
238
+ timeZone: "Asia/Ho_Chi_Minh", // important for SSR
86
239
  });
87
240
 
88
241
  export default withNextIntl({
@@ -90,18 +243,18 @@ export default withNextIntl({
90
243
  });
91
244
  ```
92
245
 
93
- 2) Merge underverse messages with your app messages:
246
+ 2. Merge underverse messages with your app messages:
94
247
 
95
248
  ```tsx
96
249
  // app/layout.tsx (simplified)
97
- import {NextIntlClientProvider, getMessages} from 'next-intl/server';
98
- import {underverseMessages} from '@underverse-ui/underverse';
250
+ import { NextIntlClientProvider, getMessages } from "next-intl/server";
251
+ import { underverseMessages } from "@underverse-ui/underverse";
99
252
 
100
- export default async function RootLayout({children}:{children: React.ReactNode}) {
253
+ export default async function RootLayout({ children }: { children: React.ReactNode }) {
101
254
  const appMessages = await getMessages();
102
- const locale = 'vi'; // derive from params/headers
255
+ const locale = "vi"; // derive from params/headers
103
256
  const uv = underverseMessages[locale] || underverseMessages.en;
104
- const messages = {...uv, ...appMessages}; // app overrides uv if overlaps
257
+ const messages = { ...uv, ...appMessages }; // app overrides uv if overlaps
105
258
 
106
259
  return (
107
260
  <html lang={locale}>
@@ -115,7 +268,43 @@ export default async function RootLayout({children}:{children: React.ReactNode})
115
268
  }
116
269
  ```
117
270
 
118
- 3) Use components normally. Any built‑in texts (DatePicker/Pagination/DataTable/Alert/ImageUpload…) will use merged messages. You can still override labels via props if desired.
271
+ 3. Use components normally. Any built‑in texts (DatePicker/Pagination/DataTable/Alert/ImageUpload…) will use merged messages. You can still override labels via props if desired.
272
+
273
+ ---
274
+
275
+ ## 🌐 TranslationProvider API
276
+
277
+ For standalone React apps (without next-intl):
278
+
279
+ ```tsx
280
+ import { TranslationProvider } from "@underverse-ui/underverse";
281
+
282
+ function App() {
283
+ return (
284
+ <TranslationProvider
285
+ locale="ko" // "en" | "vi" | "ko" | "ja"
286
+ translations={{
287
+ // Optional: override default translations
288
+ Common: {
289
+ close: "λ‹«κΈ° (custom)",
290
+ },
291
+ }}
292
+ >
293
+ {children}
294
+ </TranslationProvider>
295
+ );
296
+ }
297
+ ```
298
+
299
+ ### TranslationProvider Props
300
+
301
+ | Prop | Type | Default | Description |
302
+ | -------------- | ------------------------------ | ----------- | ---------------------------- |
303
+ | `locale` | `"en" \| "vi" \| "ko" \| "ja"` | `"en"` | Active locale |
304
+ | `translations` | `Translations` | `undefined` | Custom translation overrides |
305
+ | `children` | `ReactNode` | - | Child components |
306
+
307
+ ---
119
308
 
120
309
  ## Message Keys Summary
121
310
 
@@ -126,6 +315,49 @@ export default async function RootLayout({children}:{children: React.ReactNode})
126
315
  - `Pagination`: navigationLabel, showingResults ({startItem},{endItem},{totalItems}), firstPage, previousPage, previous, nextPage, next, lastPage, pageNumber ({page}), itemsPerPage, search, noOptions
127
316
  - `OCR.imageUpload`: dragDropText, browseFiles, supportedFormats
128
317
 
318
+ ---
319
+
320
+ ## πŸ“‹ Exported Components
321
+
322
+ ### Core Components
323
+
324
+ - **Buttons:** `Button`
325
+ - **Display:** `Badge`, `Card`, `Avatar`, `Skeleton`, `Progress`
326
+ - **Form Inputs:** `Input`, `PasswordInput`, `NumberInput`, `SearchInput`, `Textarea`, `Checkbox`, `Switch`, `Label`, `TagInput`
327
+
328
+ ### Feedback & Overlays
329
+
330
+ - `Modal`, `ToastProvider`, `useToast`, `Tooltip`, `Popover`
331
+ - `Sheet` (includes `Drawer`, `SlideOver`, `BottomSheet`, `SidebarSheet`)
332
+ - `Alert`, `GlobalLoading` (includes `PageLoading`, `InlineLoading`, `ButtonLoading`)
333
+
334
+ ### Form Controls & Pickers
335
+
336
+ - `RadioGroup`, `Slider`, `DatePicker`, `DateRangePicker`, `TimePicker`, `Calendar`
337
+ - `Combobox`, `MultiCombobox`, `CategoryTreeSelect`, `ColorPicker`
338
+
339
+ ### Navigation & Structure
340
+
341
+ - `Breadcrumb`, `Tabs` (includes `SimpleTabs`, `PillTabs`, `VerticalTabs`)
342
+ - `DropdownMenu`, `Pagination`, `SimplePagination`, `CompactPagination`
343
+ - `Section`, `ScrollArea`
344
+
345
+ ### Data Display
346
+
347
+ - `Table`, `DataTable`, `List`, `Grid`, `Timeline`
348
+
349
+ ### Media Components
350
+
351
+ - `SmartImage`, `ImageUpload`, `Carousel`, `FallingIcons`, `Watermark`
352
+
353
+ ### Utilities
354
+
355
+ - `ClientOnly`, `Loading`, `NotificationModal`, `FloatingContacts`, `AccessDenied`
356
+ - `ThemeToggle`, `LanguageSwitcher` (headless)
357
+ - `cn`, `DateUtils`, `useShadCNAnimations`
358
+
359
+ ---
360
+
129
361
  ## License
130
362
 
131
363
  MIT
@@ -143,18 +375,18 @@ These variants avoid app-specific contexts and routing so you can wire them to y
143
375
  ### ThemeToggle (headless)
144
376
 
145
377
  ```tsx
146
- import { ThemeToggle } from '@underverse-ui/underverse';
147
- import type { ThemeToggleProps, ThemeMode } from '@underverse-ui/underverse';
148
- import { useState } from 'react';
378
+ import { ThemeToggle } from "@underverse-ui/underverse";
379
+ import type { ThemeToggleProps, ThemeMode } from "@underverse-ui/underverse";
380
+ import { useState } from "react";
149
381
 
150
382
  export default function ExampleThemeToggle() {
151
- const [theme, setTheme] = useState<ThemeMode>('system');
383
+ const [theme, setTheme] = useState<ThemeMode>("system");
152
384
  return (
153
385
  <ThemeToggle
154
386
  theme={theme}
155
387
  onChange={setTheme}
156
388
  // optional labels
157
- labels={{ heading: 'Theme', light: 'Light', dark: 'Dark', system: 'System' }}
389
+ labels={{ heading: "Theme", light: "Light", dark: "Dark", system: "System" }}
158
390
  />
159
391
  );
160
392
  }
@@ -165,15 +397,15 @@ If you use `next-themes` or a custom context, pass your current theme and the se
165
397
  ### LanguageSwitcher (headless)
166
398
 
167
399
  ```tsx
168
- import { LanguageSwitcher } from '@underverse-ui/underverse';
169
- import type { LanguageOption } from '@underverse-ui/underverse';
170
- import { useRouter, usePathname } from 'next/navigation';
400
+ import { LanguageSwitcher } from "@underverse-ui/underverse";
401
+ import type { LanguageOption } from "@underverse-ui/underverse";
402
+ import { useRouter, usePathname } from "next/navigation";
171
403
 
172
404
  const locales: LanguageOption[] = [
173
- { code: 'vi', name: 'TiαΊΏng Việt', flag: 'πŸ‡»πŸ‡³' },
174
- { code: 'en', name: 'English', flag: 'πŸ‡ΊπŸ‡Έ' },
175
- { code: 'ko', name: 'ν•œκ΅­μ–΄', flag: 'πŸ‡°πŸ‡·' },
176
- { code: 'ja', name: 'ζ—₯本θͺž', flag: 'πŸ‡―πŸ‡΅' }
405
+ { code: "vi", name: "TiαΊΏng Việt", flag: "πŸ‡»πŸ‡³" },
406
+ { code: "en", name: "English", flag: "πŸ‡ΊπŸ‡Έ" },
407
+ { code: "ko", name: "ν•œκ΅­μ–΄", flag: "πŸ‡°πŸ‡·" },
408
+ { code: "ja", name: "ζ—₯本θͺž", flag: "πŸ‡―πŸ‡΅" },
177
409
  ];
178
410
 
179
411
  export default function ExampleLanguageSwitcher({ currentLocale }: { currentLocale: string }) {
@@ -182,18 +414,156 @@ export default function ExampleLanguageSwitcher({ currentLocale }: { currentLoca
182
414
 
183
415
  const onSwitch = (code: string) => {
184
416
  // Replace first segment as locale, e.g. /vi/... -> /en/...
185
- const segs = pathname.split('/');
186
- segs[1] = code;
187
- router.push(segs.join('/'));
417
+ const segs = pathname.split("/");
418
+ segs[1] = code;
419
+ router.push(segs.join("/"));
188
420
  };
189
421
 
190
- return (
191
- <LanguageSwitcher
192
- locales={locales}
193
- currentLocale={currentLocale}
194
- onSwitch={onSwitch}
195
- labels={{ heading: 'Language' }}
196
- />
197
- );
422
+ return <LanguageSwitcher locales={locales} currentLocale={currentLocale} onSwitch={onSwitch} labels={{ heading: "Language" }} />;
198
423
  }
199
424
  ```
425
+
426
+ ---
427
+
428
+ ## πŸ“ Full Export Reference
429
+
430
+ ```tsx
431
+ // Core Components
432
+ import {
433
+ Button,
434
+ Badge,
435
+ Card,
436
+ Avatar,
437
+ Skeleton,
438
+ Progress,
439
+ Input,
440
+ PasswordInput,
441
+ NumberInput,
442
+ SearchInput,
443
+ Textarea,
444
+ Checkbox,
445
+ Switch,
446
+ Label,
447
+ TagInput,
448
+ } from "@underverse-ui/underverse";
449
+
450
+ // Overlays
451
+ import {
452
+ Modal,
453
+ ToastProvider,
454
+ useToast,
455
+ Tooltip,
456
+ Popover,
457
+ Sheet,
458
+ Drawer,
459
+ SlideOver,
460
+ BottomSheet,
461
+ SidebarSheet,
462
+ Alert,
463
+ GlobalLoading,
464
+ PageLoading,
465
+ InlineLoading,
466
+ ButtonLoading,
467
+ } from "@underverse-ui/underverse";
468
+
469
+ // Pickers
470
+ import {
471
+ DatePicker,
472
+ DateRangePicker,
473
+ TimePicker,
474
+ Calendar,
475
+ Combobox,
476
+ MultiCombobox,
477
+ CategoryTreeSelect,
478
+ ColorPicker,
479
+ RadioGroup,
480
+ Slider,
481
+ } from "@underverse-ui/underverse";
482
+
483
+ // Navigation
484
+ import {
485
+ Breadcrumb,
486
+ Tabs,
487
+ SimpleTabs,
488
+ PillTabs,
489
+ VerticalTabs,
490
+ DropdownMenu,
491
+ Pagination,
492
+ SimplePagination,
493
+ CompactPagination,
494
+ Section,
495
+ ScrollArea,
496
+ } from "@underverse-ui/underverse";
497
+
498
+ // Data Display
499
+ import { Table, DataTable, List, Grid, Timeline, Watermark } from "@underverse-ui/underverse";
500
+
501
+ // Media
502
+ import { SmartImage, ImageUpload, Carousel, FallingIcons } from "@underverse-ui/underverse";
503
+
504
+ // Utilities
505
+ import {
506
+ cn,
507
+ DateUtils,
508
+ useShadCNAnimations,
509
+ injectAnimationStyles,
510
+ ClientOnly,
511
+ Loading,
512
+ NotificationModal,
513
+ FloatingContacts,
514
+ AccessDenied,
515
+ ThemeToggle,
516
+ LanguageSwitcher,
517
+ } from "@underverse-ui/underverse";
518
+
519
+ // i18n
520
+ import {
521
+ TranslationProvider,
522
+ useUnderverseTranslations,
523
+ useUnderverseLocale,
524
+ underverseMessages,
525
+ getUnderverseMessages,
526
+ } from "@underverse-ui/underverse";
527
+
528
+ // Types
529
+ import type {
530
+ ButtonProps,
531
+ InputProps,
532
+ DatePickerProps,
533
+ ComboboxProps,
534
+ PaginationProps,
535
+ DataTableColumn,
536
+ Locale,
537
+ Translations,
538
+ } from "@underverse-ui/underverse";
539
+ ```
540
+
541
+ ---
542
+
543
+ ## πŸ§ͺ Testing
544
+
545
+ ### Test with React (Vite)
546
+
547
+ ```bash
548
+ # Create new Vite project
549
+ npm create vite@latest my-test-app -- --template react-ts
550
+ cd my-test-app
551
+
552
+ # Install underverse
553
+ npm i @underverse-ui/underverse
554
+
555
+ # Add Tailwind CSS
556
+ npm i -D tailwindcss postcss autoprefixer
557
+ npx tailwindcss init -p
558
+ ```
559
+
560
+ ### Test with Next.js
561
+
562
+ ```bash
563
+ # Create new Next.js project
564
+ npx create-next-app@latest my-test-app --typescript --tailwind
565
+ cd my-test-app
566
+
567
+ # Install underverse
568
+ npm i @underverse-ui/underverse next-intl
569
+ ```