@underverse-ui/underverse 1.0.211 → 2.0.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 CHANGED
@@ -1,791 +1,328 @@
1
- # Underverse UI
2
-
3
- Docs: https://underverse.infiniq.com.vn/vi/docs/underverse
4
-
5
- **Author:** Tran Van Bach
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`.
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` | 日本語 | 🇯🇵 |
26
-
27
- ## Requirements
28
-
29
- - Node >= 18
30
- - Peer dependencies: `react`, `react-dom`
31
- - Optional: `next`, `next-intl` (for Next.js projects)
32
-
33
- ## Agent-Readable Metadata
34
-
35
- For coding agents and automation tools:
36
-
37
- - `AGENTS.md`: concise usage and integration rules.
38
- - `api-reference.json`: generated export index from `src/index.ts`.
39
- - `llms.txt`: compact LLM-friendly quickstart.
40
- - `agent-recipes.json`: structured setup/use recipes.
41
-
42
- Regenerate API metadata:
43
-
44
- ```bash
45
- npm run generate:api
46
- ```
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/faker6996/underverse/main/public/logo.png" alt="Underverse UI" width="128" />
3
+ </p>
4
+
5
+ <h1 align="center">@underverse-ui/underverse</h1>
6
+
7
+ <p align="center">
8
+ Production-focused React components for forms, data-heavy interfaces, overlays, scheduling, and rich-text editing.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@underverse-ui/underverse"><img src="https://img.shields.io/npm/v/@underverse-ui/underverse?style=flat-square&label=npm" alt="npm version" /></a>
13
+ <a href="https://www.npmjs.com/package/@underverse-ui/underverse"><img src="https://img.shields.io/npm/dm/@underverse-ui/underverse?style=flat-square&label=downloads" alt="npm downloads" /></a>
14
+ <a href="https://github.com/faker6996/underverse/actions/workflows/quality.yml"><img src="https://github.com/faker6996/underverse/actions/workflows/quality.yml/badge.svg" alt="quality checks" /></a>
15
+ <a href="https://github.com/faker6996/underverse/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-22c55e?style=flat-square" alt="MIT license" /></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://underverse.infiniq.com.vn/vi/docs/underverse">Documentation</a>
20
+ ·
21
+ <a href="https://github.com/faker6996/underverse">GitHub</a>
22
+ ·
23
+ <a href="https://github.com/faker6996/underverse/blob/main/packages/underverse/CHANGELOG.md">Changelog</a>
24
+ ·
25
+ <a href="https://github.com/faker6996/underverse/issues">Issues</a>
26
+ </p>
27
+
28
+ ## Highlights
29
+
30
+ - More than 60 typed components, from basic controls to DataTable, CalendarTimeline, and UEditor.
31
+ - React 18+ support with ESM and CommonJS package outputs.
32
+ - Tailwind CSS 4 design tokens, dark-mode support, and global component configuration.
33
+ - Built-in English, Vietnamese, Korean, and Japanese UI messages.
34
+ - Keyboard and ARIA behavior covered by component interaction tests.
35
+ - Public API metadata and bundle budgets verified before publishing.
47
36
 
48
37
  ## Installation
49
38
 
50
39
  ```bash
51
- # Install the package
52
- npm i @underverse-ui/underverse
53
-
54
- # For Next.js projects (with next-intl)
55
- npm i react react-dom next next-intl
56
-
57
- # For standalone React projects (Vite, CRA, etc.)
58
- npm i react react-dom
59
- ```
60
-
61
- ## Tailwind CSS Configuration
62
-
63
- Components use color variables like `primary`, `secondary`, `destructive`, etc. Make sure your Tailwind theme/tokens include these variables.
64
-
65
- ---
66
-
67
- ## ⚡ Performance Optimization
68
-
69
- ### Optimize Package Imports (Next.js)
70
-
71
- For best performance, add `optimizePackageImports` to your Next.js config:
72
-
73
- ```js
74
- // next.config.js
75
- module.exports = {
76
- experimental: {
77
- optimizePackageImports: ["lucide-react", "@underverse-ui/underverse"],
78
- },
79
- };
40
+ npm install @underverse-ui/underverse
80
41
  ```
81
42
 
82
- This provides:
83
-
84
- - ✅ 15-70% faster dev boot
85
- - ✅ 28% faster builds
86
- - ✅ 40% faster cold starts
87
- - ✅ Automatic tree-shaking for barrel imports
43
+ The package declares its framework and feature integrations as peer dependencies. Use React 18 or newer and Tailwind CSS 4 in the consuming application.
88
44
 
89
- ### Dynamic Imports for Heavy Components
90
-
91
- For pages that conditionally show DataTable or DatePicker:
45
+ ## Quick start
92
46
 
93
47
  ```tsx
94
- import dynamic from "next/dynamic";
95
-
96
- const DataTable = dynamic(() => import("@underverse-ui/underverse").then((m) => m.DataTable), { ssr: false, loading: () => <Skeleton /> });
97
- ```
98
-
99
- ### Web Interface Guidelines Compliant
100
-
101
- All components follow [Vercel Web Interface Guidelines](https://github.com/vercel-labs/web-interface-guidelines):
48
+ "use client";
102
49
 
103
- - ✅ `focus-visible` ring (not `:focus`)
104
- - ✅ Label `htmlFor` attribute
105
- - ✅ ARIA attributes for accessibility
106
- - ✅ `overscroll-behavior: contain` for modals
107
- - ✅ Proper ellipsis (`…`) typography
108
- - ✅ Locale-aware date formatting with `Intl.DateTimeFormat`
109
-
110
- ---
111
-
112
- ## Package Exports
113
-
114
- Package có public entry point chính cho toàn bộ component:
115
-
116
- ```tsx
117
50
  import {
118
51
  Button,
119
- DataTable,
120
- Form,
121
- FormField,
122
- UEditor,
52
+ DatePicker,
53
+ TranslationProvider,
54
+ UnderverseConfigProvider,
123
55
  } from "@underverse-ui/underverse";
124
- ```
125
-
126
- Nếu chỉ dùng UEditor, ưu tiên entry chuyên biệt để giảm lượng mã phải tải và phân tích:
127
-
128
- ```tsx
129
- import UEditor, { type UEditorRef } from "@underverse-ui/underverse/ueditor";
130
- ```
131
-
132
- Các subpath khác như `@underverse-ui/underverse/form` chưa được publish ở version hiện tại.
133
-
134
- Lưu ý:
135
- - Nhiều component trong package là client component và nên dùng trong môi trường React client.
136
- - Form primitives yêu cầu `react-hook-form` và `@hookform/resolvers`.
137
- - `UEditor` và các component dựa trên Tiptap yêu cầu peer dependencies tương ứng.
138
-
139
- ---
140
-
141
- ## �🚀 Quick Start
142
-
143
- ### Overlay Scrollbars (Optional, Recommended)
144
56
 
145
- Underverse now uses **opt-in, component-level** OverlayScrollbars.
146
- There is no global DOM scanning, no default global mount, and no app-wide MutationObserver.
147
-
148
- ```tsx
149
- import "overlayscrollbars/overlayscrollbars.css";
150
- import { OverlayScrollbarProvider, ScrollArea, DataTable } from "@underverse-ui/underverse";
151
-
152
- function App() {
57
+ export function Example() {
153
58
  return (
154
- <OverlayScrollbarProvider theme="os-theme-underverse" autoHide="leave">
155
- <ScrollArea className="h-56 rounded-xl border border-border" useOverlayScrollbar>
156
- {/* long content */}
157
- </ScrollArea>
158
-
159
- <DataTable
160
- columns={columns}
161
- data={rows}
162
- useOverlayScrollbar
163
- />
164
- </OverlayScrollbarProvider>
59
+ <TranslationProvider locale="en">
60
+ <UnderverseConfigProvider config={{ borderMode: "lg" }}>
61
+ <div className="flex items-center gap-3">
62
+ <DatePicker onChange={(date) => console.log(date)} />
63
+ <Button>Continue</Button>
64
+ </div>
65
+ </UnderverseConfigProvider>
66
+ </TranslationProvider>
165
67
  );
166
68
  }
167
69
  ```
168
70
 
169
- Provider behavior:
170
-
171
- - Provider is **configuration only** (theme/options context).
172
- - Scrollbars initialize only on components explicitly enabled via `useOverlayScrollbar`.
173
- - Hard skip targets: `html`, `body`, `[data-radix-portal]`, `[role="dialog"]`, `[aria-modal="true"]`, `[data-sonner-toaster]`.
174
- - Per-node opt-out remains available via `data-os-ignore`.
175
-
176
- Provider props:
177
-
178
- - `enabled?: boolean`
179
- - `theme?: string`
180
- - `visibility?: "visible" | "hidden" | "auto"`
181
- - `autoHide?: "never" | "scroll" | "leave" | "move"`
182
- - `autoHideDelay?: number`
183
- - `dragScroll?: boolean`
184
- - `clickScroll?: boolean`
185
- - `exclude?: string` default: `html, body, [data-os-ignore], [data-radix-portal], [role='dialog'], [aria-modal='true'], [data-sonner-toaster]`
186
- - `selector?: string` (deprecated, ignored; kept for backward compatibility)
187
-
188
- Component-level enable flags:
189
-
190
- - `ScrollArea`: `useOverlayScrollbar?: boolean` (default `false`)
191
- - `className` styles the outer wrapper
192
- - `contentClassName` styles the scroll viewport
193
- - set border/radius explicitly; the primitive no longer hardcodes rounded corners
194
- - `Table`: `useOverlayScrollbar?: boolean` (default `false`)
195
- - `DataTable`: `useOverlayScrollbar?: boolean` (default `false`)
196
- - `Combobox`: `useOverlayScrollbar?: boolean` (default `false`)
197
- - `MultiCombobox`: `useOverlayScrollbar?: boolean` (default `false`)
198
- - `CategoryTreeSelect`: `useOverlayScrollbar?: boolean` (default `false`)
199
- - `Textarea`: `useOverlayScrollbar?: boolean` (default `false`)
200
- - `OverlayScrollArea`: dedicated wrapper for heavy scroll zones (`enabled` default `true`)
201
-
202
- When to use:
203
-
204
- - Long virtualized/table/list panels
205
- - Fixed-height navigation panels and log viewers
206
-
207
- When not to use:
71
+ `TranslationProvider` is optional. Components fall back to English when it is not mounted.
208
72
 
209
- - Normal form fields
210
- - Short modal/dialog content
211
- - Full page root scrolling
73
+ ## Tailwind CSS setup
212
74
 
213
- ### Standalone React (Vite, CRA, etc.)
75
+ Tailwind ignores dependencies in `node_modules` during automatic source detection. Register Underverse from the stylesheet that imports Tailwind:
214
76
 
215
- ```tsx
216
- import { TranslationProvider, Button, DatePicker, ToastProvider, useToast } from "@underverse-ui/underverse";
77
+ ```css
78
+ @import "tailwindcss";
79
+ @source "../node_modules/@underverse-ui/underverse/dist";
80
+ ```
217
81
 
218
- function App() {
219
- return (
220
- <TranslationProvider locale="vi">
221
- <ToastProvider>
222
- <MyComponent />
223
- </ToastProvider>
224
- </TranslationProvider>
225
- );
82
+ The source path is relative to that stylesheet, so adjust `../` when the file lives under `src/app` or another nested directory. See Tailwind's [explicit source registration](https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-registering-sources) documentation.
83
+
84
+ Underverse uses semantic Tailwind tokens. Applications that already use shadcn-style tokens can reuse them. A minimal starter map looks like this:
85
+
86
+ <details>
87
+ <summary>Show starter theme tokens</summary>
88
+
89
+ ```css
90
+ :root {
91
+ --background: oklch(0.98 0.005 255);
92
+ --foreground: oklch(0.22 0.012 255);
93
+ --card: oklch(1 0 0);
94
+ --card-foreground: var(--foreground);
95
+ --popover: oklch(1 0 0);
96
+ --popover-foreground: var(--foreground);
97
+ --primary: oklch(0.55 0.18 255);
98
+ --primary-foreground: oklch(0.985 0 0);
99
+ --secondary: oklch(0.58 0.12 280);
100
+ --secondary-foreground: oklch(0.985 0 0);
101
+ --muted: oklch(0.955 0.01 255);
102
+ --muted-foreground: oklch(0.4 0.012 255);
103
+ --accent: color-mix(in oklch, var(--primary) 16%, var(--background));
104
+ --accent-foreground: var(--foreground);
105
+ --destructive: oklch(0.56 0.19 22);
106
+ --destructive-foreground: oklch(0.985 0 0);
107
+ --success: oklch(0.52 0.16 145);
108
+ --success-foreground: oklch(0.985 0 0);
109
+ --warning: oklch(0.62 0.16 60);
110
+ --warning-foreground: oklch(0.16 0.012 255);
111
+ --info: oklch(0.62 0.14 210);
112
+ --info-foreground: oklch(0.985 0 0);
113
+ --border: oklch(0.7 0.01 255 / 22%);
114
+ --input: oklch(0.7 0.01 255 / 30%);
115
+ --ring: color-mix(in oklch, var(--primary) 70%, var(--background));
116
+ --primary-soft: color-mix(in oklch, var(--primary) 12%, var(--background));
117
+ --secondary-soft: color-mix(in oklch, var(--secondary) 12%, var(--background));
118
+ --destructive-soft: color-mix(in oklch, var(--destructive) 12%, var(--background));
119
+ --success-soft: color-mix(in oklch, var(--success) 12%, var(--background));
120
+ --warning-soft: color-mix(in oklch, var(--warning) 14%, var(--background));
121
+ --info-soft: color-mix(in oklch, var(--info) 12%, var(--background));
122
+ --input-focus: var(--ring);
123
+ --input-disabled: var(--muted);
124
+ --input-invalid: var(--destructive);
125
+ --surface-0: var(--background);
126
+ --surface-1: oklch(0.99 0.006 255);
127
+ --surface-2: oklch(0.98 0.007 255);
128
+ --surface-3: oklch(0.97 0.008 255);
129
+ --shadow-xs: 0 1px 2px rgb(0 0 0 / 5%);
130
+ --shadow-sm: 0 1px 3px rgb(0 0 0 / 10%);
131
+ --shadow-md: 0 4px 6px rgb(0 0 0 / 10%);
132
+ --shadow-lg: 0 10px 15px rgb(0 0 0 / 10%);
133
+ --shadow-xl: 0 20px 25px rgb(0 0 0 / 12%);
226
134
  }
227
135
 
228
- function MyComponent() {
229
- const { addToast } = useToast();
230
-
231
- return (
232
- <div>
233
- <DatePicker onChange={(date) => console.log(date)} />
234
- <Button onClick={() => addToast({ type: "success", message: "Hello!" })}>Click me</Button>
235
- </div>
236
- );
136
+ @theme inline {
137
+ --color-background: var(--background);
138
+ --color-foreground: var(--foreground);
139
+ --color-card: var(--card);
140
+ --color-card-foreground: var(--card-foreground);
141
+ --color-popover: var(--popover);
142
+ --color-popover-foreground: var(--popover-foreground);
143
+ --color-primary: var(--primary);
144
+ --color-primary-foreground: var(--primary-foreground);
145
+ --color-primary-soft: var(--primary-soft);
146
+ --color-secondary: var(--secondary);
147
+ --color-secondary-foreground: var(--secondary-foreground);
148
+ --color-secondary-soft: var(--secondary-soft);
149
+ --color-muted: var(--muted);
150
+ --color-muted-foreground: var(--muted-foreground);
151
+ --color-accent: var(--accent);
152
+ --color-accent-foreground: var(--accent-foreground);
153
+ --color-destructive: var(--destructive);
154
+ --color-destructive-foreground: var(--destructive-foreground);
155
+ --color-destructive-soft: var(--destructive-soft);
156
+ --color-success: var(--success);
157
+ --color-success-foreground: var(--success-foreground);
158
+ --color-success-soft: var(--success-soft);
159
+ --color-warning: var(--warning);
160
+ --color-warning-foreground: var(--warning-foreground);
161
+ --color-warning-soft: var(--warning-soft);
162
+ --color-info: var(--info);
163
+ --color-info-foreground: var(--info-foreground);
164
+ --color-info-soft: var(--info-soft);
165
+ --color-border: var(--border);
166
+ --color-input: var(--input);
167
+ --color-ring: var(--ring);
168
+ --color-input-focus: var(--input-focus);
169
+ --color-input-disabled: var(--input-disabled);
170
+ --color-input-invalid: var(--input-invalid);
171
+ --color-surface-0: var(--surface-0);
172
+ --color-surface-1: var(--surface-1);
173
+ --color-surface-2: var(--surface-2);
174
+ --color-surface-3: var(--surface-3);
175
+ --shadow-xs: var(--shadow-xs);
176
+ --shadow-sm: var(--shadow-sm);
177
+ --shadow-md: var(--shadow-md);
178
+ --shadow-lg: var(--shadow-lg);
179
+ --shadow-xl: var(--shadow-xl);
180
+ --ease-soft: cubic-bezier(0.25, 1, 0.5, 1);
237
181
  }
238
182
  ```
239
183
 
240
- ### Next.js (with next-intl)
241
-
242
- ```tsx
243
- import { Button, ToastProvider, useToast } from "@underverse-ui/underverse";
184
+ For the complete light/dark palette, see the [Underverse color system](https://github.com/faker6996/underverse/blob/main/docs/COLOR_SYSTEM.md).
244
185
 
245
- function App() {
246
- const { addToast } = useToast();
247
- return (
248
- <ToastProvider>
249
- <Button onClick={() => addToast({ type: "success", message: "Hello" })}>Click me</Button>
250
- </ToastProvider>
251
- );
252
- }
253
- ```
186
+ </details>
254
187
 
255
- ### Global UI Configuration (Optional)
188
+ ## Imports and bundle size
256
189
 
257
- You can configure global UI behaviors and styles (like default border radius) across all components using `UnderverseConfigProvider`.
190
+ The root entry is the standard public API:
258
191
 
259
192
  ```tsx
260
- import { UnderverseConfigProvider, Button, Input } from "@underverse-ui/underverse";
261
-
262
- function App() {
263
- return (
264
- <UnderverseConfigProvider
265
- config={{
266
- // Set default border radius for all supported components
267
- borderMode: "xl",
268
- // Override specific components
269
- input: {
270
- borderMode: "2xl",
271
- },
272
- button: {
273
- borderMode: "lg",
274
- }
275
- }}
276
- >
277
- <div className="space-y-4">
278
- {/* These components will inherit the global configuration */}
279
- <Input placeholder="I have 2xl border radius" />
280
- <Button>I have lg border radius</Button>
281
- <Combobox placeholder="I inherit xl border radius" />
282
-
283
- {/* Direct props override the global config */}
284
- <Button borderMode="sm">I override to sm</Button>
285
- </div>
286
- </UnderverseConfigProvider>
287
- );
288
- }
193
+ import { Button, DataTable, UEditor } from "@underverse-ui/underverse";
289
194
  ```
290
195
 
291
- ### AccessDenied (Interactive 403 / Upgrade State)
292
-
293
- The `AccessDenied` component renders a premium, highly interactive card for 403 Forbidden, 401 Unauthorized, or license upgrade restrictions.
294
- - **Interactive Robot Face**: Features an SVG illustration whose pupil tracks the cursor position in real-time. Includes an emergency flashing alarm light color-synced with the active variant.
295
- - **Spotlight Flashlight**: Hovering over the card casts a radial gradient spotlight that reveals a glowing, drop-shadowed status text (e.g., `"403"` or `"PRO"`) under the cursor's path.
296
- - **Customizable Code**: Customize the background status code or text using the `code` prop. Font size automatically shrinks if the length exceeds 3 characters.
297
- - **Icon Fallback**: Supports passing a custom React component to `icon` to render a glassmorphic static icon stage instead of the interactive robot face.
196
+ It is tree-shakeable and protected by a package bundle budget. Existing applications do not need to change their imports when upgrading.
298
197
 
299
- #### Usage Example
198
+ For an application that only uses the editor, the explicit UEditor entry creates a smaller, clearer module boundary:
300
199
 
301
200
  ```tsx
302
- import { AccessDenied, Button } from "@underverse-ui/underverse";
303
-
304
- function App() {
305
- return (
306
- <AccessDenied
307
- title="Tính năng cần nâng cấp"
308
- description="Báo cáo và phân tích trên Trang chủ chưa có trong gói license hiện tại. Hãy nâng cấp gói để sử dụng tính năng này."
309
- variant="warning" // "destructive" | "warning" | "info"
310
- code="PRO" // Customize background flashlight status text
311
- >
312
- <Button variant="warning">Nâng cấp gói ngay</Button>
313
- </AccessDenied>
314
- );
315
- }
316
- ```
317
-
318
- ## Exported Components
319
-
320
- ### Core Components
321
-
322
- - **Buttons:** `Button`
323
- - **Display:** `Badge`, `Card`, `Avatar`, `Skeleton`, `Progress`
324
- - **Form Inputs:** `Input`, `Textarea`, `Checkbox`, `Switch`, `Label`
325
-
326
- ### Feedback & Overlays
327
-
328
- - `Modal`, `ToastProvider`, `useToast`, `Tooltip`, `Popover`, `Sheet` (includes `Drawer`, `SlideOver`, `BottomSheet`, `SidebarSheet`), `Alert`, `GlobalLoading` (includes `PageLoading`, `InlineLoading`, `ButtonLoading`)
329
-
330
- ### Form Controls & Pickers
331
-
332
- - `RadioGroup`, `Slider`, `DatePicker`, `Combobox`, `MultiCombobox`, `CategoryTreeSelect`
333
-
334
- ### Navigation & Structure
335
-
336
- - `Breadcrumb`, `Tabs` (includes `SimpleTabs`, `PillTabs`, `VerticalTabs`), `DropdownMenu`, `Pagination`, `Section`, `ScrollArea`, `OverlayScrollArea`
337
-
338
- ### Data Display
339
-
340
- - `Table`, `DataTable`
341
-
342
- ### Media Components
343
-
344
- - `SmartImage`, `ImageUpload`, `Carousel`, `UEditor`
345
-
346
- ### Utilities
347
-
348
- - `ClientOnly`, `Loading`, `NotificationModal`, `AccessDenied`, `OverlayControls`
349
- - Headless controls: `ThemeToggle`, `LanguageSwitcher`
350
- - Utility functions: `cn`, `DateUtils`, style constants
351
-
352
- ## Important Notes
353
-
354
- - Library is i18n‑agnostic: components have sensible English defaults and accept text via props.
355
- - If your app uses `next-intl`, you can merge our ready‑made messages to localize built‑in texts.
356
- - `NotificationBell` is not exported (depends on project-specific API/socket implementations).
357
- - `FloatingContacts` remains app-only and is not exported from the package.
358
-
359
- ---
360
-
361
- ## 📦 Date Utilities
362
-
363
- The package includes standalone date utilities with locale support (no Next.js required):
364
-
365
- ```tsx
366
- import { DateUtils } from "@underverse-ui/underverse";
367
-
368
- // Format dates with locale
369
- DateUtils.formatDate(new Date(), "ko"); // "2026년 1월 5일"
370
- DateUtils.formatDate(new Date(), "ja"); // "2026年1月5日"
371
- DateUtils.formatDate(new Date(), "vi"); // "05/01/2026"
372
- DateUtils.formatDate(new Date(), "en"); // "January 5, 2026"
373
-
374
- // Relative time formatting
375
- DateUtils.formatTimeAgo(new Date(Date.now() - 3600000), "ko"); // "1시간 전"
376
- DateUtils.formatTimeAgo(new Date(Date.now() - 3600000), "ja"); // "1時間前"
377
-
378
- // Smart date formatting (Today, Yesterday, or full date)
379
- DateUtils.formatDateSmart(new Date(), "ja"); // "今日 14:30"
380
-
381
- // Utility checks
382
- DateUtils.isToday(new Date()); // true
383
- DateUtils.isYesterday(new Date(Date.now() - 86400000)); // true
384
-
385
- // Get day of week
386
- DateUtils.getDayOfWeek(new Date(), "ko"); // "일요일"
387
- DateUtils.getDayOfWeek(new Date(), "ja"); // "日曜日"
388
-
389
- // Form input formatting
390
- DateUtils.formatDateForInput(new Date()); // "2026-01-05"
391
- DateUtils.formatDateTimeForInput(new Date()); // "2026-01-05T14:30"
201
+ import UEditor, { type UEditorRef } from "@underverse-ui/underverse/ueditor";
392
202
  ```
393
203
 
394
- ### Available Date Functions
204
+ Do not deep-import files under `dist` or `src`; only the root and `/ueditor` entries are public contracts.
395
205
 
396
- | Function | Description |
397
- | ------------------------------- | ----------------------------------- |
398
- | `formatDate(date, locale)` | Full date format |
399
- | `formatDateShort(date, locale)` | Short date format |
400
- | `formatTime(date, locale)` | Time only (HH:mm) |
401
- | `formatDateTime(date, locale)` | Date + time |
402
- | `formatTimeAgo(date, locale)` | Relative time (e.g., "2 hours ago") |
403
- | `formatDateSmart(date, locale)` | Today/Yesterday/Full date |
404
- | `isToday(date)` | Check if date is today |
405
- | `isYesterday(date)` | Check if date is yesterday |
406
- | `getDayOfWeek(date, locale)` | Get localized day name |
407
- | `formatDateForInput(date)` | YYYY-MM-DD format |
408
- | `formatDateTimeForInput(date)` | YYYY-MM-DDTHH:mm format |
206
+ ## UEditor
409
207
 
410
- ---
411
-
412
- ## 🎨 Animation Utilities
413
-
414
- The package includes ShadCN-compatible animation utilities:
208
+ UEditor is a Tiptap-based rich-text editor with slash commands, menus, uploads, tables, formulas, cell formatting, resizing, and spreadsheet paste support.
415
209
 
416
210
  ```tsx
417
- import { useShadCNAnimations, injectAnimationStyles, getAnimationStyles } from "@underverse-ui/underverse";
418
-
419
- // React hook - automatically injects styles on mount
420
- function MyComponent() {
421
- useShadCNAnimations();
422
- return <div className="animate-accordion-down">Content</div>;
423
- }
424
-
425
- // Manual injection (for non-React usage)
426
- injectAnimationStyles();
427
-
428
- // Get CSS string for custom injection
429
- const cssString = getAnimationStyles();
430
- ```
431
-
432
- ### Available Animations
433
-
434
- | Class | Description |
435
- | ------------------------------ | ---------------------------- |
436
- | `animate-accordion-down` | Accordion expand animation |
437
- | `animate-accordion-up` | Accordion collapse animation |
438
- | `animate-caret-blink` | Blinking caret cursor |
439
- | `animate-fade-in` | Fade in effect |
440
- | `animate-fade-out` | Fade out effect |
441
- | `animate-slide-in-from-top` | Slide in from top |
442
- | `animate-slide-in-from-bottom` | Slide in from bottom |
443
- | `animate-slide-in-from-left` | Slide in from left |
444
- | `animate-slide-in-from-right` | Slide in from right |
445
- | `animate-zoom-in` | Zoom in effect |
446
- | `animate-zoom-out` | Zoom out effect |
447
-
448
- ---
449
-
450
- ## next-intl Integration (Next.js App Router)
451
-
452
- 1. Configure plugin and time zone (to avoid `ENVIRONMENT_FALLBACK`):
453
-
454
- ```ts
455
- // next.config.ts
456
- import createNextIntlPlugin from "next-intl/plugin";
457
-
458
- const withNextIntl = createNextIntlPlugin({
459
- locales: ["vi", "en"],
460
- defaultLocale: "vi",
461
- timeZone: "Asia/Ho_Chi_Minh", // important for SSR
462
- });
463
-
464
- export default withNextIntl({
465
- // your other Next config
466
- });
467
- ```
211
+ "use client";
468
212
 
469
- 2. Merge underverse messages with your app messages:
470
-
471
- ```tsx
472
- // app/layout.tsx (simplified)
473
- import { NextIntlClientProvider, getMessages } from "next-intl/server";
474
- import { underverseMessages } from "@underverse-ui/underverse";
213
+ import { useRef, useState } from "react";
214
+ import UEditor, { type UEditorRef } from "@underverse-ui/underverse/ueditor";
475
215
 
476
- export default async function RootLayout({ children }: { children: React.ReactNode }) {
477
- const appMessages = await getMessages();
478
- const locale = "vi"; // derive from params/headers
479
- const uv = underverseMessages[locale] || underverseMessages.en;
480
- const messages = { ...uv, ...appMessages }; // app overrides uv if overlaps
216
+ export function ArticleEditor() {
217
+ const editorRef = useRef<UEditorRef>(null);
218
+ const [html, setHtml] = useState("<p>Start writing…</p>");
481
219
 
482
220
  return (
483
- <html lang={locale}>
484
- <body>
485
- <NextIntlClientProvider locale={locale} messages={messages}>
486
- {children}
487
- </NextIntlClientProvider>
488
- </body>
489
- </html>
221
+ <UEditor
222
+ ref={editorRef}
223
+ content={html}
224
+ onHtmlChange={setHtml}
225
+ outputDebounceMs={120}
226
+ placeholder="Type '/' for commands…"
227
+ showCharacterCount
228
+ />
490
229
  );
491
230
  }
492
231
  ```
493
232
 
494
- 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.
233
+ Use `outputDebounceMs` for large controlled documents. Before persisting base64 images, call `editorRef.current?.prepareContentForSave()` with an upload handler.
495
234
 
496
- ---
235
+ [Read the UEditor guide](https://github.com/faker6996/underverse/blob/main/docs/underverseui-usage/UEditor.md) · [Read the table guide](https://github.com/faker6996/underverse/blob/main/docs/underverseui-usage/UEditor-Table-HuongDanSuDung.md)
497
236
 
498
- ## 🌐 TranslationProvider API
237
+ ## Internationalization
499
238
 
500
- For standalone React apps (without next-intl):
239
+ Standalone React applications can use the built-in provider:
501
240
 
502
241
  ```tsx
503
242
  import { TranslationProvider } from "@underverse-ui/underverse";
504
243
 
505
- function App() {
506
- return (
507
- <TranslationProvider
508
- locale="ko" // "en" | "vi" | "ko" | "ja"
509
- translations={{
510
- // Optional: override default translations
511
- Common: {
512
- close: "닫기 (custom)",
513
- },
514
- }}
515
- >
516
- {children}
517
- </TranslationProvider>
518
- );
519
- }
244
+ <TranslationProvider locale="vi">{children}</TranslationProvider>;
520
245
  ```
521
246
 
522
- ### TranslationProvider Props
523
-
524
- | Prop | Type | Default | Description |
525
- | -------------- | ------------------------------ | ----------- | ---------------------------- |
526
- | `locale` | `"en" \| "vi" \| "ko" \| "ja"` | `"en"` | Active locale |
527
- | `translations` | `Translations` | `undefined` | Custom translation overrides |
528
- | `children` | `ReactNode` | - | Child components |
529
-
530
- ---
531
-
532
- ## Message Keys Summary
533
-
534
- - `Common`: close, closeAlert, notifications, newNotification, readStatus, openLink, theme, lightTheme, darkTheme, systemTheme, density, compact, normal, comfortable, columns
535
- - `ValidationInput`: required, typeMismatch, pattern, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput, invalid
536
- - `Loading`: loadingPage, pleaseWait
537
- - `DatePicker`: placeholder, today, clear
538
- - `Pagination`: navigationLabel, showingResults ({startItem},{endItem},{totalItems}), firstPage, previousPage, previous, nextPage, next, lastPage, pageNumber ({page}), itemsPerPage, search, noOptions
539
- - `OCR.imageUpload`: dragDropText, browseFiles, supportedFormats
540
-
541
- ---
247
+ Supported locales:
542
248
 
543
- ## 📋 Exported Components
249
+ | Code | Language |
250
+ | --- | --- |
251
+ | `en` | English |
252
+ | `vi` | Tiếng Việt |
253
+ | `ko` | 한국어 |
254
+ | `ja` | 日本語 |
544
255
 
545
- ### Core Components
256
+ For a `next-intl` application, mount `NextIntlAdapter` inside `NextIntlClientProvider`. The adapter reads the active locale and messages, then falls back to the package's built-in locale messages for missing component strings.
546
257
 
547
- - **Buttons:** `Button`
548
- - **Display:** `Badge`, `Card`, `Avatar`, `Skeleton`, `Progress`
549
- - **Form Inputs:** `Input`, `PasswordInput`, `NumberInput`, `SearchInput`, `Textarea`, `Checkbox`, `Switch`, `Label`, `TagInput`
258
+ ## Optional integrations
550
259
 
551
- ### Feedback & Overlays
260
+ Install and configure only the integrations used by your application:
552
261
 
553
- - `Modal`, `ToastProvider`, `useToast`, `Tooltip`, `Popover`
554
- - `Sheet` (includes `Drawer`, `SlideOver`, `BottomSheet`, `SidebarSheet`)
555
- - `Alert`, `GlobalLoading` (includes `PageLoading`, `InlineLoading`, `ButtonLoading`)
262
+ | Feature | Integration |
263
+ | --- | --- |
264
+ | Forms and schema validation | `react-hook-form`, `@hookform/resolvers`, `zod` |
265
+ | UEditor | Tiptap, Lowlight, and Tippy peers declared by the package |
266
+ | Overlay scrollbars | `overlayscrollbars` and its stylesheet |
267
+ | Next.js image/i18n adapters | `next`, `next-intl` |
556
268
 
557
- ### Form Controls & Pickers
558
-
559
- - `RadioGroup`, `Slider`, `DatePicker`, `DateRangePicker`, `TimePicker`, `Calendar`
560
- - `Combobox`, `MultiCombobox`, `CategoryTreeSelect`, `ColorPicker`
561
-
562
- ### Navigation & Structure
563
-
564
- - `Breadcrumb`, `Tabs` (includes `SimpleTabs`, `PillTabs`, `VerticalTabs`)
565
- - `DropdownMenu`, `Pagination`, `SimplePagination`, `CompactPagination`
566
- - `Section`, `ScrollArea`
567
-
568
- ### Data Display
569
-
570
- - `Table`, `DataTable`, `List`, `Grid`, `Timeline`
571
-
572
- ### Media Components
573
-
574
- - `SmartImage`, `ImageUpload`, `Carousel`, `FallingIcons`, `Watermark`, `UEditor`
575
-
576
- ### Utilities
577
-
578
- - `ClientOnly`, `Loading`, `NotificationModal`, `AccessDenied`, `OverlayControls`
579
- - `ThemeToggle`, `LanguageSwitcher` (headless)
580
- - `cn`, `DateUtils`, `useShadCNAnimations`
581
-
582
- ---
583
-
584
- ## License
585
-
586
- MIT
587
-
588
- ## Author
589
-
590
- Tran Van Bach
591
-
592
- ---
593
-
594
- ## Headless Components Usage
595
-
596
- These variants avoid app-specific contexts and routing so you can wire them to your own state.
597
-
598
- ### ThemeToggle (headless)
269
+ Overlay scrollbars are opt-in and component-scoped:
599
270
 
600
271
  ```tsx
601
- import { ThemeToggle } from "@underverse-ui/underverse";
602
- import type { ThemeToggleProps, ThemeMode } from "@underverse-ui/underverse";
603
- import { useState } from "react";
604
-
605
- export default function ExampleThemeToggle() {
606
- const [theme, setTheme] = useState<ThemeMode>("system");
607
- return (
608
- <ThemeToggle
609
- theme={theme}
610
- onChange={setTheme}
611
- // optional labels
612
- labels={{ heading: "Theme", light: "Light", dark: "Dark", system: "System" }}
613
- />
614
- );
615
- }
616
- ```
617
-
618
- If you use `next-themes` or a custom context, pass your current theme and the setter to `onChange`.
619
-
620
- ### LanguageSwitcher (headless)
621
-
622
- ```tsx
623
- import { LanguageSwitcher } from "@underverse-ui/underverse";
624
- import type { LanguageOption } from "@underverse-ui/underverse";
625
- import { useRouter, usePathname } from "next/navigation";
626
-
627
- const locales: LanguageOption[] = [
628
- { code: "vi", name: "Tiếng Việt", flag: "🇻🇳" },
629
- { code: "en", name: "English", flag: "🇺🇸" },
630
- { code: "ko", name: "한국어", flag: "🇰🇷" },
631
- { code: "ja", name: "日本語", flag: "🇯🇵" },
632
- ];
633
-
634
- export default function ExampleLanguageSwitcher({ currentLocale }: { currentLocale: string }) {
635
- const router = useRouter();
636
- const pathname = usePathname();
637
-
638
- const onSwitch = (code: string) => {
639
- // Replace first segment as locale, e.g. /vi/... -> /en/...
640
- const segs = pathname.split("/");
641
- segs[1] = code;
642
- router.push(segs.join("/"));
643
- };
644
-
645
- return <LanguageSwitcher locales={locales} currentLocale={currentLocale} onSwitch={onSwitch} labels={{ heading: "Language" }} />;
646
- }
647
- ```
648
-
649
- ---
650
-
651
- ## 📁 Full Export Reference
652
-
653
- ```tsx
654
- // Core Components
655
- import {
656
- Button,
657
- Badge,
658
- Card,
659
- Avatar,
660
- Skeleton,
661
- Progress,
662
- Input,
663
- PasswordInput,
664
- NumberInput,
665
- SearchInput,
666
- Textarea,
667
- Checkbox,
668
- Switch,
669
- Label,
670
- TagInput,
671
- } from "@underverse-ui/underverse";
672
-
673
- // Overlays
674
- import {
675
- Modal,
676
- ToastProvider,
677
- useToast,
678
- Tooltip,
679
- Popover,
680
- Sheet,
681
- Drawer,
682
- SlideOver,
683
- BottomSheet,
684
- SidebarSheet,
685
- Alert,
686
- GlobalLoading,
687
- PageLoading,
688
- InlineLoading,
689
- ButtonLoading,
690
- } from "@underverse-ui/underverse";
691
-
692
- // Pickers
693
- import {
694
- DatePicker,
695
- DateRangePicker,
696
- TimePicker,
697
- Calendar,
698
- Combobox,
699
- MultiCombobox,
700
- CategoryTreeSelect,
701
- ColorPicker,
702
- RadioGroup,
703
- Slider,
704
- } from "@underverse-ui/underverse";
705
-
706
- // Navigation
272
+ import "overlayscrollbars/overlayscrollbars.css";
707
273
  import {
708
- Breadcrumb,
709
- Tabs,
710
- SimpleTabs,
711
- PillTabs,
712
- VerticalTabs,
713
- DropdownMenu,
714
- Pagination,
715
- SimplePagination,
716
- CompactPagination,
717
- Section,
274
+ OverlayScrollbarProvider,
718
275
  ScrollArea,
719
276
  } from "@underverse-ui/underverse";
720
277
 
721
- // Data Display
722
- import { Table, DataTable, List, Grid, Timeline, Watermark } from "@underverse-ui/underverse";
723
-
724
- // Media
725
- import { SmartImage, ImageUpload, Carousel, FallingIcons, UEditor } from "@underverse-ui/underverse";
278
+ <OverlayScrollbarProvider autoHide="leave">
279
+ <ScrollArea className="h-64" useOverlayScrollbar>
280
+ {content}
281
+ </ScrollArea>
282
+ </OverlayScrollbarProvider>;
283
+ ```
726
284
 
727
- // Utilities
728
- import {
729
- cn,
730
- DateUtils,
731
- useShadCNAnimations,
732
- injectAnimationStyles,
733
- ClientOnly,
734
- Loading,
735
- NotificationModal,
736
- AccessDenied,
737
- ThemeToggle,
738
- LanguageSwitcher,
739
- } from "@underverse-ui/underverse";
285
+ ## Component families
740
286
 
741
- // i18n
742
- import {
743
- TranslationProvider,
744
- useUnderverseTranslations,
745
- useUnderverseLocale,
746
- underverseMessages,
747
- getUnderverseMessages,
748
- } from "@underverse-ui/underverse";
287
+ | Area | Components |
288
+ | --- | --- |
289
+ | Inputs | Input, Textarea, CheckBox, RadioGroup, Switch, Slider, TagInput |
290
+ | Selection | Combobox, MultiCombobox, CategoryTreeSelect, ColorPicker |
291
+ | Date and time | Calendar, DatePicker, DateRangePicker, DateTimePicker, TimePicker, MonthYearPicker |
292
+ | Data | Table, DataTable, Pagination, Grid, List, Timeline |
293
+ | Navigation | Tabs, Breadcrumb, DropdownMenu, Section, ScrollArea |
294
+ | Overlays | Modal, Sheet, Popover, Tooltip, Toast, Alert |
295
+ | Media and content | SmartImage, ImageUpload, FileUpload, Carousel, EmojiPicker, StickerPicker, UEditor |
296
+ | Scheduling | CalendarTimeline |
749
297
 
750
- // Types
751
- import type {
752
- ButtonProps,
753
- InputProps,
754
- DatePickerProps,
755
- ComboboxProps,
756
- PaginationProps,
757
- DataTableColumn,
758
- Locale,
759
- Translations,
760
- } from "@underverse-ui/underverse";
761
- ```
298
+ Browse every component, example, and generated API contract in the [live documentation](https://underverse.infiniq.com.vn/vi/docs/underverse).
762
299
 
763
- ---
300
+ ## Compatibility
764
301
 
765
- ## 🧪 Testing
302
+ | Runtime | Support |
303
+ | --- | --- |
304
+ | React | 18 and newer |
305
+ | Node.js | 18 and newer |
306
+ | Next.js | 13 and newer |
307
+ | Tailwind CSS | 4.x |
308
+ | Module formats | ESM and CommonJS |
309
+ | TypeScript | Type declarations included |
766
310
 
767
- ### Test with React (Vite)
311
+ ## Package metadata
768
312
 
769
- ```bash
770
- # Create new Vite project
771
- npm create vite@latest my-test-app -- --template react-ts
772
- cd my-test-app
313
+ The npm package also ships:
773
314
 
774
- # Install underverse
775
- npm i @underverse-ui/underverse
315
+ - `api-reference.json` for a generated machine-readable export index.
316
+ - `llms.txt` and `agent-recipes.json` for coding assistants.
317
+ - `AGENTS.md` for package integration rules.
776
318
 
777
- # Add Tailwind CSS
778
- npm i -D tailwindcss postcss autoprefixer
779
- npx tailwindcss init -p
780
- ```
319
+ ## Support
781
320
 
782
- ### Test with Next.js
321
+ - [Documentation](https://underverse.infiniq.com.vn/vi/docs/underverse)
322
+ - [Issue tracker](https://github.com/faker6996/underverse/issues)
323
+ - [Security policy](https://github.com/faker6996/underverse/blob/main/SECURITY.md)
324
+ - [Contributing guide](https://github.com/faker6996/underverse/blob/main/CONTRIBUTING.md)
783
325
 
784
- ```bash
785
- # Create new Next.js project
786
- npx create-next-app@latest my-test-app --typescript --tailwind
787
- cd my-test-app
326
+ ## License
788
327
 
789
- # Install underverse
790
- npm i @underverse-ui/underverse next-intl
791
- ```
328
+ [MIT](https://github.com/faker6996/underverse/blob/main/LICENSE) © Tran Van Bach