@bzync/rui 0.0.8 → 0.0.9
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 +475 -61
- package/dist/components/richtext.cjs +1 -1
- package/dist/components/richtext.js +43 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
|
-
# rui
|
|
1
|
+
# @bzync/rui
|
|
2
|
+
|
|
3
|
+
Composable React UI components with scoped, fully customizable light and dark themes.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@bzync/rui)
|
|
6
|
+
[](https://www.npmjs.com/package/@bzync/rui)
|
|
7
|
+
[](https://bundlephobia.com/package/@bzync/rui)
|
|
8
|
+
[](https://www.npmjs.com/package/@bzync/rui)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
|
|
11
|
+
`@bzync/rui` (**r**eact **ui**) is the component library behind [Bzync](https://www.bzync.com).
|
|
12
|
+
It ships 79 components plus a small SVG chart set, a token-driven theming system, and
|
|
13
|
+
production-grade lifecycle primitives — with **zero runtime dependencies**.
|
|
14
|
+
|
|
15
|
+
- **Zero runtime dependencies.** `dependencies` is empty. `react`, `react-dom`, and
|
|
16
|
+
`framer-motion` are peer dependencies you already control.
|
|
17
|
+
- **Scoped theming.** `ThemeProvider` renders a `.rui-theme` scope, so you can run
|
|
18
|
+
multiple themes on one page or theme a subtree without touching `<html>`.
|
|
19
|
+
- **Token-driven.** Override accent and neutral scales, radii, fonts, spacing, shadows,
|
|
20
|
+
or any CSS variable through a single `palette` prop — per theme.
|
|
21
|
+
- **Light and dark, controlled or uncontrolled.** System-preference tracking and
|
|
22
|
+
persistent selection are built in.
|
|
23
|
+
- **Accessible by default.** Native semantics, keyboard behavior, focus management,
|
|
24
|
+
focus trapping, scroll-lock restoration, and `prefers-reduced-motion` support.
|
|
25
|
+
- **ESM and CJS.** Dual builds, per-component subpath exports, `sideEffects` metadata
|
|
26
|
+
for tree-shaking, and readable (unminified) published output.
|
|
27
|
+
- **Typed.** Written in TypeScript; declaration files ship with the package.
|
|
28
|
+
- **RSC-friendly.** Client components are marked `"use client"`; server components can
|
|
29
|
+
import and render them directly.
|
|
30
|
+
- **React 18.2 and React 19.**
|
|
31
|
+
|
|
32
|
+
## Table of contents
|
|
33
|
+
|
|
34
|
+
- [Installation](#installation)
|
|
35
|
+
- [Quick start](#quick-start)
|
|
36
|
+
- [The stylesheet](#the-stylesheet)
|
|
37
|
+
- [Theming](#theming)
|
|
38
|
+
- [Importing components](#importing-components)
|
|
39
|
+
- [Server components and SSR](#server-components-and-ssr)
|
|
40
|
+
- [Accessibility and motion](#accessibility-and-motion)
|
|
41
|
+
- [Component catalog](#component-catalog)
|
|
42
|
+
- [Common recipes](#common-recipes)
|
|
43
|
+
- [Charts](#charts)
|
|
44
|
+
- [Hooks and utilities](#hooks-and-utilities)
|
|
45
|
+
- [TypeScript](#typescript)
|
|
46
|
+
- [Browser and React support](#browser-and-react-support)
|
|
47
|
+
- [Versioning](#versioning)
|
|
48
|
+
- [Documentation](#documentation)
|
|
49
|
+
- [Contributing](#contributing)
|
|
50
|
+
- [Security](#security)
|
|
51
|
+
- [License](#license)
|
|
2
52
|
|
|
3
|
-
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npm install @bzync/rui framer-motion
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
pnpm add @bzync/rui framer-motion
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
yarn add @bzync/rui framer-motion
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`react` and `react-dom` (`^18.2.0 || ^19.0.0`) and `framer-motion` (`^13.1.0`) are peer
|
|
68
|
+
dependencies. `framer-motion` powers the animated overlays (Modal, Drawer, Popover,
|
|
69
|
+
Snackbar); it is kept off the critical path and loaded only by the components that use it.
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
4
72
|
|
|
5
73
|
```tsx
|
|
6
74
|
import { Button, ThemeProvider, ThemeToggle } from "@bzync/rui"
|
|
@@ -27,99 +95,445 @@ export function App() {
|
|
|
27
95
|
}
|
|
28
96
|
```
|
|
29
97
|
|
|
30
|
-
Every component accepts `className` (and native element props where applicable), so
|
|
31
|
-
changes compose with the defaults
|
|
32
|
-
system preference, persistent selection, scoped themes, custom accent/neutral scales, and any
|
|
33
|
-
CSS variable through `tokens`. `ThemeToggle` accepts custom icons, labels, and classes.
|
|
98
|
+
Every component accepts `className` (and native element props where applicable), so
|
|
99
|
+
one-off changes compose with the defaults instead of fighting them.
|
|
34
100
|
|
|
35
|
-
##
|
|
101
|
+
## The stylesheet
|
|
36
102
|
|
|
37
|
-
|
|
38
|
-
|
|
103
|
+
Import the core stylesheet once, near the application root:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import "@bzync/rui/styles.css"
|
|
39
107
|
```
|
|
40
108
|
|
|
41
|
-
|
|
109
|
+
It defines the semantic design tokens, the `.rui-theme` scope, the dark-mode variant,
|
|
110
|
+
and component styles. It does **not** download or bundle webfonts — `@bzync/rui` uses
|
|
111
|
+
system fallbacks by default to keep the stylesheet small and avoid unexpected network
|
|
112
|
+
requests. Load any font in your application and override `--font-sans`, `--font-display`,
|
|
113
|
+
and `--font-mono` on `.rui-theme` (or via `ThemeProvider`'s `fonts` option).
|
|
114
|
+
|
|
115
|
+
## Theming
|
|
116
|
+
|
|
117
|
+
### ThemeProvider
|
|
118
|
+
|
|
119
|
+
`ThemeProvider` establishes a theme scope. Place one near the root, or wrap any subtree
|
|
120
|
+
to give it its own theme.
|
|
42
121
|
|
|
43
122
|
```tsx
|
|
44
|
-
import "@bzync/rui
|
|
123
|
+
import { ThemeProvider } from "@bzync/rui"
|
|
124
|
+
|
|
125
|
+
<ThemeProvider
|
|
126
|
+
defaultTheme="system" // "light" | "dark" | "system"
|
|
127
|
+
storageKey="app-theme" // localStorage key, or false to disable persistence
|
|
128
|
+
applyToRoot // also toggle the `dark` class on <html>
|
|
129
|
+
palette={{ accent, neutral, radius, fonts, spacing, shadows, colors, tokens }}
|
|
130
|
+
lightPalette={{ /* overrides applied only in light mode */ }}
|
|
131
|
+
darkPalette={{ /* overrides applied only in dark mode */ }}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</ThemeProvider>
|
|
45
135
|
```
|
|
46
136
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
and override `--font-sans`, `--font-display`, and `--font-mono` on `.rui-theme`.
|
|
137
|
+
Controlled usage is supported by passing `theme` and `onThemeChange` instead of
|
|
138
|
+
`defaultTheme`.
|
|
50
139
|
|
|
51
|
-
|
|
140
|
+
**Palette shape** (`ThemePalette`, all fields optional):
|
|
52
141
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
142
|
+
| Field | Type | Purpose |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `accent` | `Partial<Record<50‥950, string>>` | Primary/action color scale |
|
|
145
|
+
| `neutral` | `Partial<Record<50‥950, string>>` | Grayscale / surfaces / borders |
|
|
146
|
+
| `colors` | `ThemeColors` | Named semantic colors (bg, surface, foreground, border, status colors, focus ring…) |
|
|
147
|
+
| `radius` | `Partial<Record<"sm"‥"2xl" \| "full", string>>` | Corner radii |
|
|
148
|
+
| `fonts` | `ThemeFonts` | `sans`, `display`, `mono` families |
|
|
149
|
+
| `spacing` | `Record<string, string>` | Spacing scale entries |
|
|
150
|
+
| `shadows` | `Partial<Record<"xs"‥"2xl", string>>` | Elevation shadows |
|
|
151
|
+
| `tokens` | `Partial<Record<\`--${string}\`, string \| number>>` | Any CSS custom property, escape hatch |
|
|
57
152
|
|
|
58
|
-
|
|
153
|
+
### useTheme
|
|
59
154
|
|
|
60
|
-
|
|
155
|
+
```tsx
|
|
156
|
+
import { useTheme } from "@bzync/rui"
|
|
157
|
+
|
|
158
|
+
function Example() {
|
|
159
|
+
const { theme, resolvedTheme, setTheme, toggleTheme } = useTheme()
|
|
160
|
+
// theme: "light" | "dark" | "system" (the user's selection)
|
|
161
|
+
// resolvedTheme: "light" | "dark" (after resolving "system")
|
|
162
|
+
return <button onClick={toggleTheme}>Now: {resolvedTheme}</button>
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`useTheme` must be called inside a `ThemeProvider`.
|
|
167
|
+
|
|
168
|
+
### ThemeToggle
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { ThemeToggle } from "@bzync/rui"
|
|
172
|
+
|
|
173
|
+
<ThemeToggle
|
|
174
|
+
showLabel
|
|
175
|
+
lightLabel="Light"
|
|
176
|
+
darkLabel="Dark"
|
|
177
|
+
lightIcon={<SunIcon />}
|
|
178
|
+
darkIcon={<MoonIcon />}
|
|
179
|
+
/>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Renders a button with `aria-pressed` and an `aria-label` that reflects the current state.
|
|
183
|
+
|
|
184
|
+
### Semantic tokens
|
|
185
|
+
|
|
186
|
+
Author custom components against the semantic tokens rather than raw palette values:
|
|
187
|
+
`bg`, `surface`, `surface-raised`, `surface-muted`, `foreground`, `muted-foreground`,
|
|
188
|
+
`border`, `border-strong`, `primary`, `primary-foreground`, `destructive`, the status
|
|
189
|
+
colors, and `focus-ring`. They adapt to light/dark and to any palette override
|
|
190
|
+
automatically.
|
|
191
|
+
|
|
192
|
+
## Importing components
|
|
193
|
+
|
|
194
|
+
The root entry re-exports everything for convenience:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
import { Button, Modal, DataTable } from "@bzync/rui"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Per-component subpath entries let a bundler pull in only what an application uses:
|
|
61
201
|
|
|
62
202
|
```tsx
|
|
63
203
|
import { Button } from "@bzync/rui/button"
|
|
204
|
+
import { Modal } from "@bzync/rui/modal"
|
|
205
|
+
import { BarChart } from "@bzync/rui/charts"
|
|
64
206
|
```
|
|
65
207
|
|
|
66
|
-
|
|
208
|
+
Both forms are tree-shakeable — the package sets `"sideEffects": ["**/*.css"]` and ships
|
|
209
|
+
ESM — but subpath imports keep dependency graphs smallest and are the recommended default
|
|
210
|
+
for libraries and performance-sensitive apps.
|
|
67
211
|
|
|
68
|
-
##
|
|
212
|
+
## Server components and SSR
|
|
69
213
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
214
|
+
Client components are marked with the `"use client"` directive, so React Server
|
|
215
|
+
Components and frameworks like Next.js App Router can import and render them directly.
|
|
216
|
+
`ThemeProvider` reads `localStorage` only on the client and resolves `"system"` with
|
|
217
|
+
`matchMedia`; render it in a client boundary and pass a `defaultTheme` so the server and
|
|
218
|
+
first client paint agree.
|
|
74
219
|
|
|
75
|
-
##
|
|
220
|
+
## Accessibility and motion
|
|
76
221
|
|
|
77
|
-
|
|
78
|
-
|
|
222
|
+
- Interactive components expose native element semantics and keyboard behavior.
|
|
223
|
+
- Modal dialogs label their content, trap keyboard focus, close on <kbd>Escape</kbd>,
|
|
224
|
+
restore focus to the previously focused element on close, and preserve the page's
|
|
225
|
+
prior scroll-lock state.
|
|
226
|
+
- Menus, listboxes, tabs, and toggles implement roving focus and arrow-key navigation.
|
|
227
|
+
- Non-essential animation inside the `.rui-theme` scope is disabled when the user has
|
|
228
|
+
`prefers-reduced-motion: reduce` set.
|
|
229
|
+
- Icon-only controls (`IconButton`, `InfoButton`, `CopyButton`) require an accessible
|
|
230
|
+
`label`.
|
|
231
|
+
|
|
232
|
+
## Component catalog
|
|
233
|
+
|
|
234
|
+
Import any of these from the root or from `@bzync/rui/<kebab-name>`.
|
|
235
|
+
|
|
236
|
+
### Providers and theming
|
|
237
|
+
|
|
238
|
+
| Component / export | Notes |
|
|
239
|
+
|---|---|
|
|
240
|
+
| `ThemeProvider` | Token-driven theme scope; light/dark, controlled or uncontrolled, persistence, `applyToRoot` |
|
|
241
|
+
| `useTheme()` | `{ theme, resolvedTheme, setTheme, toggleTheme }` |
|
|
242
|
+
| `ThemeToggle` | Accessible light/dark switch with custom icons and labels |
|
|
243
|
+
| `SnackbarProvider` / `useSnackbar()` | Programmatic toast notifications |
|
|
244
|
+
| `CommandProvider` / `CommandPalette` | App-wide searchable command palette |
|
|
245
|
+
| `cn()` | `clsx` + `tailwind-merge` class combiner |
|
|
246
|
+
|
|
247
|
+
### Actions
|
|
248
|
+
|
|
249
|
+
| Component | Key props |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `Button` | `variant: primary \| secondary \| ghost \| outline \| destructive \| link`, `size: sm \| md \| lg \| icon`, `loading`, `icon`, `iconPosition` |
|
|
252
|
+
| `ButtonGroup` | `orientation`, `aria-label`; wraps `Button` children with `role="group"` |
|
|
253
|
+
| `IconButton` / `InfoButton` | `label` (required for a11y), icon |
|
|
254
|
+
| `CopyButton` | `value` (text to copy), `label`, `timeout` |
|
|
255
|
+
| `Toggle` / `ToggleGroup` / `ToggleGroupItem` | `pressed` / `defaultPressed`, `onPressedChange`, `type: single \| multiple`, `variant`, `size`, `orientation`, `loop` |
|
|
256
|
+
| `BillingIntervalToggle` | `value: monthly \| yearly`, `onChange` |
|
|
257
|
+
|
|
258
|
+
### Forms
|
|
259
|
+
|
|
260
|
+
| Component | Key props |
|
|
261
|
+
|---|---|
|
|
262
|
+
| `Input` | `label`, `hint`, `error`, `prefix` / `suffix`, `size` |
|
|
263
|
+
| `Textarea` | `label`, `hint`, `error`, `rows` |
|
|
264
|
+
| `NumberInput` | `value` / `defaultValue`, `min`, `max`, `step`, `onChange` |
|
|
265
|
+
| `OtpInput` | `length`, `value`, `onChange`, `label` |
|
|
266
|
+
| `Select` | `options`, `label`, `multiple`, groups, searchable, `onChange` |
|
|
267
|
+
| `Autocomplete` | `options`, `multiple`, custom filter, single/multi triggers |
|
|
268
|
+
| `Checkbox` | `label`, `checked` / `defaultChecked`, `onCheckedChange` |
|
|
269
|
+
| `Radio` / `RadioGroup` | `value`, `onChange`, `options` |
|
|
270
|
+
| `Switch` | `label`, `checked`, `onCheckedChange` |
|
|
271
|
+
| `Slider` | `value` / `defaultValue`, `min`, `max`, `step`, `label` |
|
|
272
|
+
| `Rating` | `value` / `defaultValue`, `max`, `size`, `readOnly`, `onValueChange` |
|
|
273
|
+
| `DatePicker` | `label`, `value`, `onChange` |
|
|
274
|
+
| `Calendar` | `value: Date`, `onChange`, month/week views |
|
|
275
|
+
| `TimePicker` | `value` / `defaultValue`, `format: 12 \| 24`, `minuteStep`, `showSeconds`, `min` / `max`, `clearable` |
|
|
276
|
+
| `FileUpload` | `label`, `accept`, `multiple`, `onFilesChange` |
|
|
277
|
+
| `Label` | `htmlFor`, `required`, `hint` |
|
|
278
|
+
| `FormField` | `label`, `htmlFor`, `required`, `hint`, `error` — wraps a control |
|
|
279
|
+
| `Stepper` | `steps`, `activeStep` |
|
|
280
|
+
| `Kbd` | keyboard-key children |
|
|
281
|
+
|
|
282
|
+
### Display
|
|
283
|
+
|
|
284
|
+
| Component | Key props |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `Badge` / `Tag` | `variant`, `dot`; `Tag` adds `onRemove` |
|
|
287
|
+
| `Avatar` / `AvatarGroup` / `AvatarGroupOverflow` | `name`, `src`, `size`, initials fallback; group `spacing`, `count` |
|
|
288
|
+
| `Card` (`CardHeader` / `CardTitle` / `CardDescription` / `CardBody` / `CardFooter`) | composition |
|
|
289
|
+
| `Callout` / `Alert` | `title`, `variant`; `Alert` adds `dismissable`, `onDismiss` |
|
|
290
|
+
| `Stat` / `StatusDot` | `Stat`: `label`, `value`, `trend`, `trendValue`; `StatusDot`: `status`, `label` |
|
|
291
|
+
| `Tooltip` | `content`, trigger child |
|
|
292
|
+
| `Link` | `href`, `variant` |
|
|
293
|
+
| `Typography`: `Heading` / `Text` / `Prose` / `Time` | `Heading`: `as h1‥h6`, `size`, `tone`, `weight`, `balance`; `Text`: `variant`, `size`, number/date/currency formatting; `Time`: renders `<time datetime>` |
|
|
294
|
+
| `Code` / `InlineCode` / `CodeBlock` / `CodeEditor` | `code`, `filename`, `showLineNumbers`, `value` / `onChange` |
|
|
295
|
+
| `Blockquote` | `variant`, `size`, `cite`, `source` / `sourceHref` |
|
|
296
|
+
| `Currency` | `value: number \| bigint`, `currency`, `locale`, `accounting`, `tone`, `size` |
|
|
297
|
+
| `DescriptionList` (`DescriptionItem` / `DescriptionTerm` / `DescriptionDetails`) | `columns: 1 \| 2 \| 3`, `density`, `orientation` |
|
|
298
|
+
| `List` / `ListItem`, `Timeline`, `Tree` | item collections; `Tree` is expandable |
|
|
299
|
+
| `Table` (`TableHeader` / `TableHead` / `TableBody` / `TableRow` / `TableCell`) | styled primitive table |
|
|
300
|
+
| `Divider` / `Separator` | `orientation`, `variant`, `spacing`, optional `label` |
|
|
301
|
+
| `ScrollArea` | `orientation: vertical \| horizontal \| both`, `hideScrollbar`, `keyboardNavigable` |
|
|
302
|
+
| `AspectRatio` | `ratio: number` |
|
|
303
|
+
| `Skeleton` (+ `SkeletonAvatar` / `SkeletonCard` / `SkeletonTable` / `SkeletonText` / `SkeletonTopbar`) | loading placeholders |
|
|
304
|
+
| `Spinner` / `Progressbar` | `size`; `value`, `max` |
|
|
305
|
+
| `EmptyState` / `ErrorState` | `title`, `description`, `error`, action slot |
|
|
306
|
+
| `RichText` / `RichTextEditor` | rendered content and editor |
|
|
307
|
+
| `AuthBackdrop` | decorative auth-screen background |
|
|
308
|
+
|
|
309
|
+
### Overlays and feedback
|
|
310
|
+
|
|
311
|
+
| Component | Key props |
|
|
312
|
+
|---|---|
|
|
313
|
+
| `Modal` (`ModalHeader` / `ModalTitle` / `ModalDescription` / `ModalBody` / `ModalFooter`) | `open`, `onClose`, `title`, `size: sm‥7xl \| full`, `ModalBody` `scrollable` |
|
|
314
|
+
| `Drawer` | `open`, `onClose`, `title`, `size`, focus trap |
|
|
315
|
+
| `ConfirmDialog` | `open`, `onConfirm`, `onCancel`, `title` |
|
|
316
|
+
| `Popover` / `PopoverContent` | trigger, `open` / `onOpenChange` |
|
|
317
|
+
| `DropdownMenu` | `trigger`, `items: { label, onClick }[]` |
|
|
318
|
+
| `Command` / `CommandPalette` / `CommandProvider` | searchable command palette |
|
|
319
|
+
| `SnackbarProvider` / `useSnackbar()` | queue and dismiss toasts programmatically |
|
|
320
|
+
| `Tabs` (`TabsList` / `TabsTrigger` / `TabsContent`) | `value`, `onValueChange` |
|
|
321
|
+
| `Accordion` | `items: { id, trigger, content }[]` |
|
|
322
|
+
| `Pagination` | `page`, `totalPages`, `onPageChange` |
|
|
323
|
+
| `Terminal` / `TerminalBlock` / `TerminalEmulator` | virtual filesystem, runnable shell commands |
|
|
324
|
+
|
|
325
|
+
### Navigation and layout
|
|
326
|
+
|
|
327
|
+
| Component | Key props |
|
|
328
|
+
|---|---|
|
|
329
|
+
| `AppShell` (`AppShellHeader` / `AppShellBody` / `AppShellMain` / `Footer`) | app frame; `sticky`, `scrollable`, `fixed` |
|
|
330
|
+
| `Container` | `size: sm‥xl \| full`, `gutter` |
|
|
331
|
+
| `Stack` / `Inline` / `PageHeader` | spacing and header primitives |
|
|
332
|
+
| `Navbar` / `Sidebar` / `Topbar` / `BottomBar` | `items: NavigationItem[]`, `activeId`, `onSelect` |
|
|
333
|
+
| `NavigationLink` | `id`, `label`, `href`, `icon`, `badge`, `active`, `compact` |
|
|
334
|
+
| `Breadcrumb` | `items` |
|
|
335
|
+
| `SEO` | document head tags |
|
|
336
|
+
|
|
337
|
+
### Data
|
|
338
|
+
|
|
339
|
+
| Component | Key props |
|
|
340
|
+
|---|---|
|
|
341
|
+
| `DataTable<T>` | `columns: ColumnDef<T>[]` (`{ key, header, cell, sortable?, searchable?, align?, width? }`), `data: T[]`, `searchable`, `searchPlaceholder`, `pageSizeOptions`, `density`, `loading`, `emptyMessage`, `onRowClick`, `unstyled` — built-in sort, search, and pagination. `T` must have an `id`. |
|
|
342
|
+
|
|
343
|
+
## Common recipes
|
|
344
|
+
|
|
345
|
+
### Button
|
|
79
346
|
|
|
80
|
-
|
|
347
|
+
```tsx
|
|
348
|
+
import { Button } from "@bzync/rui/button"
|
|
81
349
|
|
|
82
|
-
|
|
83
|
-
|
|
350
|
+
<Button variant="primary" size="lg" loading={isSaving} onClick={save}>
|
|
351
|
+
Save changes
|
|
352
|
+
</Button>
|
|
84
353
|
|
|
85
|
-
|
|
354
|
+
<Button variant="outline" icon={<PlusIcon />} iconPosition="left">
|
|
355
|
+
New item
|
|
356
|
+
</Button>
|
|
357
|
+
```
|
|
86
358
|
|
|
87
|
-
|
|
88
|
-
`NPM_TOKEN`. The release workflow runs when a `v*` tag is pushed and requires the
|
|
89
|
-
tag to match the version in `package.json` exactly.
|
|
359
|
+
### A labelled, validated field
|
|
90
360
|
|
|
91
|
-
|
|
361
|
+
```tsx
|
|
362
|
+
import { FormField } from "@bzync/rui/form-field"
|
|
363
|
+
import { Input } from "@bzync/rui/input"
|
|
92
364
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
git push origin "v${VERSION}"
|
|
365
|
+
<FormField label="Email" htmlFor="email" required error={errors.email}>
|
|
366
|
+
<Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
|
367
|
+
</FormField>
|
|
97
368
|
```
|
|
98
369
|
|
|
99
|
-
|
|
370
|
+
### Select
|
|
100
371
|
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
|
|
372
|
+
```tsx
|
|
373
|
+
import { Select } from "@bzync/rui/select"
|
|
374
|
+
|
|
375
|
+
<Select
|
|
376
|
+
label="Environment"
|
|
377
|
+
options={[
|
|
378
|
+
{ label: "Production", value: "prod" },
|
|
379
|
+
{ label: "Staging", value: "staging" },
|
|
380
|
+
{ label: "Development", value: "dev" },
|
|
381
|
+
]}
|
|
382
|
+
value={env}
|
|
383
|
+
onChange={setEnv}
|
|
384
|
+
/>
|
|
104
385
|
```
|
|
105
386
|
|
|
106
|
-
|
|
107
|
-
release commit, and creates the matching version tag. Use `minor` or `major`
|
|
108
|
-
instead of `patch` when appropriate. Before publishing, GitHub Actions runs the
|
|
109
|
-
complete release check. It also generates npm provenance when the source
|
|
110
|
-
repository is public; npm does not support provenance from private GitHub
|
|
111
|
-
repositories.
|
|
387
|
+
### Modal
|
|
112
388
|
|
|
113
|
-
|
|
389
|
+
```tsx
|
|
390
|
+
import { Modal, ModalBody, ModalFooter } from "@bzync/rui/modal"
|
|
391
|
+
import { Button } from "@bzync/rui/button"
|
|
114
392
|
|
|
115
|
-
|
|
116
|
-
the fixes, create the next patch version, and push its new tag:
|
|
393
|
+
const [open, setOpen] = useState(false)
|
|
117
394
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
395
|
+
<Modal open={open} onClose={() => setOpen(false)} title="Delete project" size="sm">
|
|
396
|
+
<ModalBody>This action cannot be undone.</ModalBody>
|
|
397
|
+
<ModalFooter>
|
|
398
|
+
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
|
|
399
|
+
<Button variant="destructive" onClick={confirmDelete}>Delete</Button>
|
|
400
|
+
</ModalFooter>
|
|
401
|
+
</Modal>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Toasts
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import { SnackbarProvider, useSnackbar } from "@bzync/rui/snackbar"
|
|
408
|
+
|
|
409
|
+
function Root() {
|
|
410
|
+
return (
|
|
411
|
+
<SnackbarProvider>
|
|
412
|
+
<App />
|
|
413
|
+
</SnackbarProvider>
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function SaveButton() {
|
|
418
|
+
const { show } = useSnackbar()
|
|
419
|
+
return <Button onClick={() => show({ title: "Saved", variant: "success" })}>Save</Button>
|
|
420
|
+
}
|
|
123
421
|
```
|
|
124
422
|
|
|
125
|
-
|
|
423
|
+
`useSnackbar()` returns `{ show, dismiss, dismissAll }`. `show(opts)` returns the toast id;
|
|
424
|
+
pass a stable `id` in `opts` to replace an existing toast in place instead of stacking.
|
|
425
|
+
|
|
426
|
+
### DataTable
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import { DataTable } from "@bzync/rui/datatable"
|
|
430
|
+
|
|
431
|
+
type Row = { id: string; name: string; role: string; seats: number }
|
|
432
|
+
|
|
433
|
+
<DataTable<Row>
|
|
434
|
+
data={members}
|
|
435
|
+
columns={[
|
|
436
|
+
{ key: "name", header: "Name", cell: (r) => r.name, sortable: true, searchable: true },
|
|
437
|
+
{ key: "role", header: "Role", cell: (r) => r.role, sortable: true },
|
|
438
|
+
{ key: "seats", header: "Seats", cell: (r) => r.seats, align: "right", sortable: true },
|
|
439
|
+
]}
|
|
440
|
+
searchable
|
|
441
|
+
pageSizeOptions={[10, 25, 50]}
|
|
442
|
+
onRowClick={(row) => open(row.id)}
|
|
443
|
+
/>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Charts
|
|
447
|
+
|
|
448
|
+
The chart set is a separate subpath entry so it stays out of the main graph unless used.
|
|
449
|
+
Every chart renders plain SVG and takes data arrays — no canvas, no chart engine.
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
import { BarChart, LineChart, DonutChart } from "@bzync/rui/charts"
|
|
453
|
+
|
|
454
|
+
<BarChart data={[{ label: "Jan", value: 42 }, { label: "Feb", value: 55 }]} />
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Available: `BarChart`, `LineChart`, `MultiLineChart`, `DonutChart`, `ScatterChart`,
|
|
458
|
+
`GanttChart`, `HeatmapChart`, `RadarChart`, `FunnelChart`, `WaterfallChart`. See
|
|
459
|
+
[`src/components/charts`](./src/components/charts) for exact per-chart props.
|
|
460
|
+
|
|
461
|
+
## Hooks and utilities
|
|
462
|
+
|
|
463
|
+
Lifecycle-correct hooks (mount/update/unmount with cleanup), re-exported from the root:
|
|
464
|
+
|
|
465
|
+
| Hook | Purpose |
|
|
466
|
+
|---|---|
|
|
467
|
+
| `useIsMounted()` | Guard async setState after unmount |
|
|
468
|
+
| `useIsomorphicLayoutEffect()` | `useLayoutEffect` on the client, `useEffect` on the server |
|
|
469
|
+
| `usePrevious(value)` | Previous render's value |
|
|
470
|
+
| `useUpdateEffect(fn, deps)` | Effect that skips the first render |
|
|
471
|
+
| `useEventCallback(fn)` | Stable callback identity with fresh closure |
|
|
472
|
+
| `useControllableState(opts)` | Controlled/uncontrolled state pattern |
|
|
473
|
+
| `useMediaQuery(query)` | Subscribe to a media query |
|
|
474
|
+
| `useAbortSignal()` | Abort in-flight work on unmount |
|
|
475
|
+
| `useFocusTrap(ref, active)` | Trap focus within a container |
|
|
476
|
+
| `useOutsideClick(ref, handler)` | Detect clicks outside an element |
|
|
477
|
+
|
|
478
|
+
Utilities: `cn()`, focus helpers, `Portal`, and assertion helpers from `@bzync/rui/utils`;
|
|
479
|
+
`ErrorBoundary` from the root; `createSafeEffect` and mount helpers from `@bzync/rui`'s
|
|
480
|
+
lifecycle exports; `KEY`, `DURATIONS`, and `FOCUSABLE_SELECTOR` constants.
|
|
481
|
+
|
|
482
|
+
## TypeScript
|
|
483
|
+
|
|
484
|
+
`@bzync/rui` is written in TypeScript and ships `.d.ts` files for the root and every
|
|
485
|
+
subpath entry. Prop types, variant unions (`ButtonVariant`, `ButtonSize`, …), and the
|
|
486
|
+
theming types (`Theme`, `ThemePalette`, `ThemeColors`, `ColorShade`, …) are all exported.
|
|
487
|
+
No `@types/*` package is required.
|
|
488
|
+
|
|
489
|
+
## Browser and React support
|
|
490
|
+
|
|
491
|
+
- **React** `^18.2.0 || ^19.0.0`
|
|
492
|
+
- **Modern evergreen browsers.** The library relies on CSS custom properties, `matchMedia`,
|
|
493
|
+
and standard DOM APIs; no polyfills are bundled.
|
|
494
|
+
- **SSR / RSC** via the `"use client"` boundary described above.
|
|
495
|
+
|
|
496
|
+
## Bundle and dependencies
|
|
497
|
+
|
|
498
|
+
- `dependencies`: **none**. `clsx`/`tailwind-merge` logic is inlined; icons are inlined SVG.
|
|
499
|
+
- Published output is **not minified** — readable ESM and CJS ship to the registry so the
|
|
500
|
+
code is auditable; your bundler minifies the final app build.
|
|
501
|
+
- `sideEffects` is limited to `**/*.css`, so unused components are dropped by any
|
|
502
|
+
tree-shaking bundler.
|
|
503
|
+
|
|
504
|
+
## Versioning
|
|
505
|
+
|
|
506
|
+
`@bzync/rui` follows [semantic versioning](https://semver.org/). While the major version
|
|
507
|
+
is `0`, minor releases may contain breaking changes; pin a version or a tight range and
|
|
508
|
+
review the release notes before upgrading. Releases are published from CI with npm
|
|
509
|
+
[provenance](https://docs.npmjs.com/generating-provenance-statements) attestations.
|
|
510
|
+
|
|
511
|
+
## Documentation
|
|
512
|
+
|
|
513
|
+
Full component documentation and live, themeable demos are published at
|
|
514
|
+
**[bzync.github.io/rui](https://bzync.github.io/rui/)**. A push to `main` deploys the
|
|
515
|
+
latest docs via GitHub Pages.
|
|
516
|
+
|
|
517
|
+
## Contributing
|
|
518
|
+
|
|
519
|
+
Bug reports, accessibility fixes, documentation improvements, and focused component
|
|
520
|
+
contributions are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development
|
|
521
|
+
setup, the verification gate, and the release process, and
|
|
522
|
+
[CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
523
|
+
|
|
524
|
+
## Security
|
|
525
|
+
|
|
526
|
+
Report vulnerabilities privately using the process in [SECURITY.md](./SECURITY.md).
|
|
527
|
+
Please do not open public issues for security reports.
|
|
528
|
+
|
|
529
|
+
## License
|
|
530
|
+
|
|
531
|
+
[ISC](./LICENSE) © 2026 Bzync
|
|
532
|
+
|
|
533
|
+
## Maintainer
|
|
534
|
+
|
|
535
|
+
`@bzync/rui` is maintained by [Rayan Reynaldo](https://www.bzync.com), Founder of Bzync
|
|
536
|
+
([www.bzync.com](https://www.bzync.com)).
|
|
537
|
+
|
|
538
|
+
If `@bzync/rui` is useful to you, you can support its continued development on
|
|
539
|
+
[Buy Me a Coffee](https://buymeacoffee.com/adminjw).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../cn-B6R3HKzx.cjs");let t=require("react"),n=require("react/jsx-runtime");function r({onClick:t,title:r,active:i,children:a}){return(0,n.jsx)(`button`,{type:`button`,title:r,onMouseDown:e=>{e.preventDefault(),t()},className:e.t(`h-7 w-7 flex items-center justify-center rounded-md text-sm transition-colors`,i?`bg-black/10 dark:bg-white/15 text-foreground`:`text-slate-500 dark:text-slate-400 hover:text-foreground hover:bg-black/6 dark:hover:bg-white/8`),children:a})}function i(){return(0,n.jsx)(`div`,{className:`w-px h-5 bg-black/10 dark:bg-white/10 mx-0.5`})}function a(e){if(typeof document>`u`)return e;let t=document.createElement(`template`);t.innerHTML=e,t.content.querySelectorAll(`script, style, iframe, object, embed, link, meta`).forEach(e=>e.remove());let n=document.createTreeWalker(t.content,NodeFilter.SHOW_ELEMENT);for(;n.nextNode();){let e=n.currentNode;for(let t of Array.from(e.attributes)){let n=t.name.toLowerCase()
|
|
1
|
+
"use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../cn-B6R3HKzx.cjs");let t=require("react"),n=require("react/jsx-runtime");function r({onClick:t,title:r,active:i,children:a}){return(0,n.jsx)(`button`,{type:`button`,title:r,onMouseDown:e=>{e.preventDefault(),t()},className:e.t(`h-7 w-7 flex items-center justify-center rounded-md text-sm transition-colors`,i?`bg-black/10 dark:bg-white/15 text-foreground`:`text-slate-500 dark:text-slate-400 hover:text-foreground hover:bg-black/6 dark:hover:bg-white/8`),children:a})}function i(){return(0,n.jsx)(`div`,{className:`w-px h-5 bg-black/10 dark:bg-white/10 mx-0.5`})}var a=new Set([`href`,`src`,`xlink:href`,`action`,`formaction`,`poster`,`background`,`cite`,`data`,`ping`,`srcset`]),o=new Set([`http`,`https`,`mailto`,`tel`,`ftp`,`sms`]);function s(e){let t=e.replace(/[\u0000-\u0020\u007f-\u00a0]/g,``).toLowerCase(),n=/^([a-z][a-z0-9+.-]*):/.exec(t);return n?!o.has(n[1]):!1}function c(e){if(typeof document>`u`)return e;let t=document.createElement(`template`);t.innerHTML=e,t.content.querySelectorAll(`script, style, iframe, object, embed, link, meta, base, form`).forEach(e=>e.remove());let n=document.createTreeWalker(t.content,NodeFilter.SHOW_ELEMENT);for(;n.nextNode();){let e=n.currentNode;for(let t of Array.from(e.attributes)){let n=t.name.toLowerCase();if(n.startsWith(`on`)){e.removeAttribute(t.name);continue}a.has(n)&&s(t.value)&&e.removeAttribute(t.name)}}return t.innerHTML}function l(e,t){return t?typeof t==`function`?t(e):c(e):e}function u({value:a=``,onChange:o,placeholder:s=`Start typing…`,className:c,minHeight:u=160,sanitize:d=!1}){let f=(0,t.useRef)(null),p=(0,t.useRef)(a);(0,t.useEffect)(()=>{let e=l(a,d);f.current&&f.current.innerHTML!==e&&(f.current.innerHTML=e,p.current=e)},[a,d]);let m=(0,t.useCallback)(()=>{let e=f.current?.innerHTML??``,t=d?l(e,d):e;t!==p.current&&(p.current=t,o?.(t))},[o,d]);function h(e,t){try{document.execCommand(e,!1,t)}catch{}f.current?.focus(),m()}function g(e){try{return document.queryCommandState(e)}catch{return!1}}return(0,n.jsxs)(`div`,{className:e.t(`rounded-xl overflow-hidden border border-black/10 dark:border-white/10 bg-white dark:bg-navy-900 focus-within:ring-1 focus-within:ring-blue-500/40 focus-within:border-accent-500/30 transition-all`,c),children:[(0,n.jsxs)(`div`,{className:`flex flex-wrap items-center gap-0.5 px-2 py-2 border-b border-black/[0.07] dark:border-white/[0.07] bg-black/2 dark:bg-white/2`,children:[(0,n.jsx)(r,{title:`Bold (⌘B)`,onClick:()=>h(`bold`),active:g(`bold`),children:(0,n.jsx)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,children:(0,n.jsx)(`path`,{d:`M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8`})})}),(0,n.jsx)(r,{title:`Italic (⌘I)`,onClick:()=>h(`italic`),active:g(`italic`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`line`,{x1:`19`,y1:`4`,x2:`10`,y2:`4`}),(0,n.jsx)(`line`,{x1:`14`,y1:`20`,x2:`5`,y2:`20`}),(0,n.jsx)(`line`,{x1:`15`,y1:`4`,x2:`9`,y2:`20`})]})}),(0,n.jsx)(r,{title:`Underline (⌘U)`,onClick:()=>h(`underline`),active:g(`underline`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`path`,{d:`M6 4v6a6 6 0 0 0 12 0V4`}),(0,n.jsx)(`line`,{x1:`4`,y1:`20`,x2:`20`,y2:`20`})]})}),(0,n.jsx)(r,{title:`Strikethrough`,onClick:()=>h(`strikeThrough`),active:g(`strikeThrough`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`path`,{d:`M16 4H9a3 3 0 0 0-2.83 4M14 12a4 4 0 0 1 0 8H6`}),(0,n.jsx)(`line`,{x1:`4`,y1:`12`,x2:`20`,y2:`12`})]})}),(0,n.jsx)(i,{}),(0,n.jsx)(r,{title:`Heading 1`,onClick:()=>h(`formatBlock`,`<h1>`),children:(0,n.jsx)(`span`,{className:`text-[11px] font-bold`,children:`H1`})}),(0,n.jsx)(r,{title:`Heading 2`,onClick:()=>h(`formatBlock`,`<h2>`),children:(0,n.jsx)(`span`,{className:`text-[11px] font-bold`,children:`H2`})}),(0,n.jsx)(r,{title:`Paragraph`,onClick:()=>h(`formatBlock`,`<p>`),children:(0,n.jsx)(`span`,{className:`text-[11px] font-medium`,children:`P`})}),(0,n.jsx)(i,{}),(0,n.jsx)(r,{title:`Bullet list`,onClick:()=>h(`insertUnorderedList`),active:g(`insertUnorderedList`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`line`,{x1:`9`,y1:`6`,x2:`20`,y2:`6`}),(0,n.jsx)(`line`,{x1:`9`,y1:`12`,x2:`20`,y2:`12`}),(0,n.jsx)(`line`,{x1:`9`,y1:`18`,x2:`20`,y2:`18`}),(0,n.jsx)(`circle`,{cx:`4`,cy:`6`,r:`1.5`,fill:`currentColor`,stroke:`none`}),(0,n.jsx)(`circle`,{cx:`4`,cy:`12`,r:`1.5`,fill:`currentColor`,stroke:`none`}),(0,n.jsx)(`circle`,{cx:`4`,cy:`18`,r:`1.5`,fill:`currentColor`,stroke:`none`})]})}),(0,n.jsx)(r,{title:`Ordered list`,onClick:()=>h(`insertOrderedList`),active:g(`insertOrderedList`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`line`,{x1:`10`,y1:`6`,x2:`21`,y2:`6`}),(0,n.jsx)(`line`,{x1:`10`,y1:`12`,x2:`21`,y2:`12`}),(0,n.jsx)(`line`,{x1:`10`,y1:`18`,x2:`21`,y2:`18`}),(0,n.jsx)(`path`,{d:`M4 6h1v4`}),(0,n.jsx)(`path`,{d:`M4 10h2`}),(0,n.jsx)(`path`,{d:`M6 18H4c0-1 2-2 2-3s-1-1.5-2-1`})]})}),(0,n.jsx)(r,{title:`Blockquote`,onClick:()=>h(`formatBlock`,`<blockquote>`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`path`,{d:`M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z`}),(0,n.jsx)(`path`,{d:`M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z`})]})}),(0,n.jsx)(i,{}),(0,n.jsx)(r,{title:`Undo (⌘Z)`,onClick:()=>h(`undo`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`path`,{d:`M3 7v6h6`}),(0,n.jsx)(`path`,{d:`M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13`})]})}),(0,n.jsx)(r,{title:`Redo (⌘⇧Z)`,onClick:()=>h(`redo`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`path`,{d:`M21 7v6h-6`}),(0,n.jsx)(`path`,{d:`M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7`})]})}),(0,n.jsx)(r,{title:`Clear formatting`,onClick:()=>h(`removeFormat`),children:(0,n.jsxs)(`svg`,{width:`13`,height:`13`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,n.jsx)(`path`,{d:`M4 7V4h16v3`}),(0,n.jsx)(`path`,{d:`M5 20h6`}),(0,n.jsx)(`path`,{d:`M13 4 8 20`}),(0,n.jsx)(`line`,{x1:`3`,y1:`3`,x2:`21`,y2:`21`})]})})]}),(0,n.jsxs)(`div`,{className:`relative`,children:[(0,n.jsx)(`div`,{ref:f,contentEditable:!0,suppressContentEditableWarning:!0,onInput:m,onBlur:m,style:{minHeight:u},className:e.t(`px-4 py-3 text-sm text-slate-800 dark:text-slate-200 focus:outline-none`,`prose prose-sm max-w-none`,`[&_h1]:text-xl [&_h1]:font-bold [&_h1]:mb-2 [&_h1]:text-gray-900 dark:[&_h1]:text-white`,`[&_h2]:text-lg [&_h2]:font-semibold [&_h2]:mb-1.5 [&_h2]:text-gray-900 dark:[&_h2]:text-white`,`[&_p]:mb-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:mb-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:mb-2`,`[&_blockquote]:border-l-2 [&_blockquote]:border-accent-400 [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_blockquote]:italic`)}),!a&&(0,n.jsx)(`div`,{className:`pointer-events-none absolute top-3 left-4 text-sm text-slate-400 dark:text-slate-600 select-none`,"aria-hidden":!0,children:s})]})]})}exports.RichTextEditor=u;
|
|
@@ -17,34 +17,61 @@ function o({ onClick: t, title: n, active: r, children: a }) {
|
|
|
17
17
|
function s() {
|
|
18
18
|
return /* @__PURE__ */ i("div", { className: "w-px h-5 bg-black/10 dark:bg-white/10 mx-0.5" });
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
var c = /* @__PURE__ */ new Set([
|
|
21
|
+
"href",
|
|
22
|
+
"src",
|
|
23
|
+
"xlink:href",
|
|
24
|
+
"action",
|
|
25
|
+
"formaction",
|
|
26
|
+
"poster",
|
|
27
|
+
"background",
|
|
28
|
+
"cite",
|
|
29
|
+
"data",
|
|
30
|
+
"ping",
|
|
31
|
+
"srcset"
|
|
32
|
+
]), l = /* @__PURE__ */ new Set([
|
|
33
|
+
"http",
|
|
34
|
+
"https",
|
|
35
|
+
"mailto",
|
|
36
|
+
"tel",
|
|
37
|
+
"ftp",
|
|
38
|
+
"sms"
|
|
39
|
+
]);
|
|
40
|
+
function u(e) {
|
|
41
|
+
let t = e.replace(/[\u0000-\u0020\u007f-\u00a0]/g, "").toLowerCase(), n = /^([a-z][a-z0-9+.-]*):/.exec(t);
|
|
42
|
+
return n ? !l.has(n[1]) : !1;
|
|
43
|
+
}
|
|
44
|
+
function d(e) {
|
|
21
45
|
if (typeof document > "u") return e;
|
|
22
46
|
let t = document.createElement("template");
|
|
23
|
-
t.innerHTML = e, t.content.querySelectorAll("script, style, iframe, object, embed, link, meta").forEach((e) => e.remove());
|
|
47
|
+
t.innerHTML = e, t.content.querySelectorAll("script, style, iframe, object, embed, link, meta, base, form").forEach((e) => e.remove());
|
|
24
48
|
let n = document.createTreeWalker(t.content, NodeFilter.SHOW_ELEMENT);
|
|
25
49
|
for (; n.nextNode();) {
|
|
26
50
|
let e = n.currentNode;
|
|
27
51
|
for (let t of Array.from(e.attributes)) {
|
|
28
|
-
let n = t.name.toLowerCase()
|
|
29
|
-
(n.startsWith("on")
|
|
52
|
+
let n = t.name.toLowerCase();
|
|
53
|
+
if (n.startsWith("on")) {
|
|
54
|
+
e.removeAttribute(t.name);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
c.has(n) && u(t.value) && e.removeAttribute(t.name);
|
|
30
58
|
}
|
|
31
59
|
}
|
|
32
|
-
|
|
33
|
-
return r = r.replace(/\son\w+\s*=\s*(['"])[^'"]*\1/gi, ""), r;
|
|
60
|
+
return t.innerHTML;
|
|
34
61
|
}
|
|
35
|
-
function
|
|
36
|
-
return t ? typeof t == "function" ? t(e) :
|
|
62
|
+
function f(e, t) {
|
|
63
|
+
return t ? typeof t == "function" ? t(e) : d(e) : e;
|
|
37
64
|
}
|
|
38
|
-
function
|
|
65
|
+
function p({ value: c = "", onChange: l, placeholder: u = "Start typing…", className: d, minHeight: p = 160, sanitize: m = !1 }) {
|
|
39
66
|
let h = r(null), g = r(c);
|
|
40
67
|
n(() => {
|
|
41
|
-
let e =
|
|
68
|
+
let e = f(c, m);
|
|
42
69
|
h.current && h.current.innerHTML !== e && (h.current.innerHTML = e, g.current = e);
|
|
43
70
|
}, [c, m]);
|
|
44
71
|
let _ = t(() => {
|
|
45
|
-
let e = h.current?.innerHTML ?? "", t = m ?
|
|
46
|
-
t !== g.current && (g.current = t,
|
|
47
|
-
}, [
|
|
72
|
+
let e = h.current?.innerHTML ?? "", t = m ? f(e, m) : e;
|
|
73
|
+
t !== g.current && (g.current = t, l?.(t));
|
|
74
|
+
}, [l, m]);
|
|
48
75
|
function v(e, t) {
|
|
49
76
|
try {
|
|
50
77
|
document.execCommand(e, !1, t);
|
|
@@ -59,7 +86,7 @@ function u({ value: c = "", onChange: u, placeholder: d = "Start typing…", cla
|
|
|
59
86
|
}
|
|
60
87
|
}
|
|
61
88
|
return /* @__PURE__ */ a("div", {
|
|
62
|
-
className: e("rounded-xl overflow-hidden border border-black/10 dark:border-white/10 bg-white dark:bg-navy-900 focus-within:ring-1 focus-within:ring-blue-500/40 focus-within:border-accent-500/30 transition-all",
|
|
89
|
+
className: e("rounded-xl overflow-hidden border border-black/10 dark:border-white/10 bg-white dark:bg-navy-900 focus-within:ring-1 focus-within:ring-blue-500/40 focus-within:border-accent-500/30 transition-all", d),
|
|
63
90
|
children: [/* @__PURE__ */ a("div", {
|
|
64
91
|
className: "flex flex-wrap items-center gap-0.5 px-2 py-2 border-b border-black/[0.07] dark:border-white/[0.07] bg-black/2 dark:bg-white/2",
|
|
65
92
|
children: [
|
|
@@ -361,10 +388,10 @@ function u({ value: c = "", onChange: u, placeholder: d = "Start typing…", cla
|
|
|
361
388
|
}), !c && /* @__PURE__ */ i("div", {
|
|
362
389
|
className: "pointer-events-none absolute top-3 left-4 text-sm text-slate-400 dark:text-slate-600 select-none",
|
|
363
390
|
"aria-hidden": !0,
|
|
364
|
-
children:
|
|
391
|
+
children: u
|
|
365
392
|
})]
|
|
366
393
|
})]
|
|
367
394
|
});
|
|
368
395
|
}
|
|
369
396
|
//#endregion
|
|
370
|
-
export {
|
|
397
|
+
export { p as RichTextEditor };
|