@underverse-ui/underverse 1.0.210 → 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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +243 -706
- package/api-reference.json +1 -1
- package/dist/{EmojiPicker-FEJJDAWZ.js → EmojiPicker-34YATB7Z.js} +4 -4
- package/dist/{chunk-F6A7HJE4.js → chunk-A7XL4RDV.js} +3 -3
- package/dist/{chunk-CYCBSKWI.js → chunk-KGNYK5YF.js} +2 -2
- package/dist/{chunk-AMAILXHP.js → chunk-LTIU5FQY.js} +4 -4
- package/dist/chunk-LTIU5FQY.js.map +1 -0
- package/dist/{chunk-GKY5DHGR.js → chunk-MNZVW2QX.js} +3 -3
- package/dist/{chunk-BRDGIALS.js → chunk-PLHJWXCW.js} +24 -30
- package/dist/{chunk-BRDGIALS.js.map → chunk-PLHJWXCW.js.map} +1 -1
- package/dist/{chunk-4E43DRP2.js → chunk-QIMAN4VF.js} +15 -9
- package/dist/{chunk-4E43DRP2.js.map → chunk-QIMAN4VF.js.map} +1 -1
- package/dist/index.cjs +156 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +136 -96
- package/dist/index.js.map +1 -1
- package/dist/{menu-bar-6GS5LLTD.js → menu-bar-SPAAI5X6.js} +4 -4
- package/dist/ueditor.cjs +12 -6
- package/dist/ueditor.cjs.map +1 -1
- package/dist/ueditor.js +4 -4
- package/package.json +3 -1
- package/dist/chunk-AMAILXHP.js.map +0 -1
- /package/dist/{EmojiPicker-FEJJDAWZ.js.map → EmojiPicker-34YATB7Z.js.map} +0 -0
- /package/dist/{chunk-F6A7HJE4.js.map → chunk-A7XL4RDV.js.map} +0 -0
- /package/dist/{chunk-CYCBSKWI.js.map → chunk-KGNYK5YF.js.map} +0 -0
- /package/dist/{chunk-GKY5DHGR.js.map → chunk-MNZVW2QX.js.map} +0 -0
- /package/dist/{menu-bar-6GS5LLTD.js.map → menu-bar-SPAAI5X6.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,791 +1,328 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
For pages that conditionally show DataTable or DatePicker:
|
|
45
|
+
## Quick start
|
|
92
46
|
|
|
93
47
|
```tsx
|
|
94
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
- Short modal/dialog content
|
|
211
|
-
- Full page root scrolling
|
|
73
|
+
## Tailwind CSS setup
|
|
212
74
|
|
|
213
|
-
|
|
75
|
+
Tailwind ignores dependencies in `node_modules` during automatic source detection. Register Underverse from the stylesheet that imports Tailwind:
|
|
214
76
|
|
|
215
|
-
```
|
|
216
|
-
import
|
|
77
|
+
```css
|
|
78
|
+
@import "tailwindcss";
|
|
79
|
+
@source "../node_modules/@underverse-ui/underverse/dist";
|
|
80
|
+
```
|
|
217
81
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
+
## Imports and bundle size
|
|
256
189
|
|
|
257
|
-
|
|
190
|
+
The root entry is the standard public API:
|
|
258
191
|
|
|
259
192
|
```tsx
|
|
260
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
204
|
+
Do not deep-import files under `dist` or `src`; only the root and `/ueditor` entries are public contracts.
|
|
395
205
|
|
|
396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
477
|
-
const
|
|
478
|
-
const
|
|
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
|
-
<
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
237
|
+
## Internationalization
|
|
499
238
|
|
|
500
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
249
|
+
| Code | Language |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `en` | English |
|
|
252
|
+
| `vi` | Tiếng Việt |
|
|
253
|
+
| `ko` | 한국어 |
|
|
254
|
+
| `ja` | 日本語 |
|
|
544
255
|
|
|
545
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260
|
+
Install and configure only the integrations used by your application:
|
|
552
261
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
278
|
+
<OverlayScrollbarProvider autoHide="leave">
|
|
279
|
+
<ScrollArea className="h-64" useOverlayScrollbar>
|
|
280
|
+
{content}
|
|
281
|
+
</ScrollArea>
|
|
282
|
+
</OverlayScrollbarProvider>;
|
|
283
|
+
```
|
|
726
284
|
|
|
727
|
-
|
|
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
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
311
|
+
## Package metadata
|
|
768
312
|
|
|
769
|
-
|
|
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
|
-
|
|
775
|
-
|
|
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
|
-
|
|
778
|
-
npm i -D tailwindcss postcss autoprefixer
|
|
779
|
-
npx tailwindcss init -p
|
|
780
|
-
```
|
|
319
|
+
## Support
|
|
781
320
|
|
|
782
|
-
|
|
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
|
-
|
|
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
|
-
|
|
790
|
-
npm i @underverse-ui/underverse next-intl
|
|
791
|
-
```
|
|
328
|
+
[MIT](https://github.com/faker6996/underverse/blob/main/LICENSE) © Tran Van Bach
|