@n3rd-ai/ui 0.1.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 +22 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/Toast-qHlZE8FW.d.ts +16 -0
- package/dist/ascii-border-DpKIQMLP.d.ts +106 -0
- package/dist/chunk-CBVIEAN7.js +61 -0
- package/dist/chunk-MZO6ECNX.js +78 -0
- package/dist/hooks/index.d.ts +27 -0
- package/dist/hooks/index.js +52 -0
- package/dist/index.css +281 -0
- package/dist/index.d.ts +290 -0
- package/dist/index.js +881 -0
- package/dist/theme/theme/fonts.ts +21 -0
- package/dist/theme/theme/presets/classic.css +23 -0
- package/dist/theme/theme/presets/paper.css +21 -0
- package/dist/theme/theme/presets/retro.css +23 -0
- package/dist/theme/theme/presets/unicorn.css +5 -0
- package/dist/theme/theme/reset.css +86 -0
- package/dist/theme/theme/tokens.css +61 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +59 -0
- package/package.json +109 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial component library: Box, Stack, Row, Grid, Divider, Page
|
|
13
|
+
- Display components: Text, Heading, Badge, Metric, Table, Code, List, Logo, StatusLine, Footer
|
|
14
|
+
- Input components: Button, Input
|
|
15
|
+
- Feedback components: Toast, Alert, Progress, Skeleton
|
|
16
|
+
- Nav component
|
|
17
|
+
- Theme system with 4 presets: unicorn, classic, retro, paper
|
|
18
|
+
- CSS custom properties for full theme customization
|
|
19
|
+
- Primitives: ascii-border, cursor, typewriter, scanline
|
|
20
|
+
- Hooks: useTypewriter, useKeyboard, useToast
|
|
21
|
+
- Utility functions: drawBox, formatTable
|
|
22
|
+
- N3rdProvider with toast and scanline support
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Superstellar LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @n3rd-ai/ui
|
|
2
|
+
|
|
3
|
+
Terminal-first UI framework for Next.js. ASCII everything. Zero images. Pure text.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/SuperstellarLLC/n3rd-ai-ui/actions/workflows/ci.yml)
|
|
6
|
+
[](https://npmjs.com/package/@n3rd-ai/ui)
|
|
7
|
+
[](https://bundlephobia.com/package/@n3rd-ai/ui)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌──────────────────────────────────────────────────┐
|
|
12
|
+
│ Your product is an API. │
|
|
13
|
+
│ This gives it a face. │
|
|
14
|
+
│ │
|
|
15
|
+
│ 0 images. 0 icon fonts. 0 design decisions. │
|
|
16
|
+
│ Import. Wrap. Ship. │
|
|
17
|
+
└──────────────────────────────────────────────────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @n3rd-ai/ui
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// app/layout.tsx
|
|
30
|
+
import { N3rdProvider, N3rdFonts } from '@n3rd-ai/ui'
|
|
31
|
+
import '@n3rd-ai/ui/theme/unicorn.css'
|
|
32
|
+
|
|
33
|
+
export default function RootLayout({ children }) {
|
|
34
|
+
return (
|
|
35
|
+
<html lang="en" className={N3rdFonts.className}>
|
|
36
|
+
<body>
|
|
37
|
+
<N3rdProvider>{children}</N3rdProvider>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Components
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { Box, Text, Button, Metric, Badge, Table, Nav } from '@n3rd-ai/ui'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Box
|
|
51
|
+
|
|
52
|
+
The core primitive. Everything lives in a box.
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
<Box border="single" title="system status" accent="success">
|
|
56
|
+
<Text>All systems operational.</Text>
|
|
57
|
+
</Box>
|
|
58
|
+
|
|
59
|
+
// ┌─ system status ─────────────────┐
|
|
60
|
+
// │ All systems operational. │
|
|
61
|
+
// └──────────────────────────────────┘
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Button
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<Button variant="primary">SUBMIT</Button> // [ SUBMIT ]
|
|
68
|
+
<Button variant="danger">DELETE</Button> // [ DELETE ]
|
|
69
|
+
<Button loading>DEPLOYING</Button> // [ ⠋ DEPLOYING... ]
|
|
70
|
+
<Button href="/docs" external>DOCS</Button> // [ DOCS ↗ ]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Metric
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<Row>
|
|
77
|
+
<Metric value={99.97} suffix="%" label="uptime" accent="success" />
|
|
78
|
+
<Metric value={42} label="endpoints" />
|
|
79
|
+
<Metric value={1.2} suffix="s" label="avg response" accent="warning" />
|
|
80
|
+
</Row>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Table
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Table
|
|
87
|
+
columns={['project', 'status', 'score']}
|
|
88
|
+
rows={[
|
|
89
|
+
['autoallow.com', { text: 'DEPLOYED', accent: 'success' }, '87'],
|
|
90
|
+
['candlelit.ai', { text: 'BUILDING', accent: 'warning' }, '--'],
|
|
91
|
+
]}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
// ┌─────────────────┬──────────┬───────┐
|
|
95
|
+
// │ project │ status │ score │
|
|
96
|
+
// ├─────────────────┼──────────┼───────┤
|
|
97
|
+
// │ autoallow.com │ DEPLOYED │ 87 │
|
|
98
|
+
// │ candlelit.ai │ BUILDING │ -- │
|
|
99
|
+
// └─────────────────┴──────────┴───────┘
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Toast
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { useToast } from '@n3rd-ai/ui/hooks'
|
|
106
|
+
|
|
107
|
+
const toast = useToast()
|
|
108
|
+
|
|
109
|
+
toast.success('Deployment complete.') // [✓] Deployment complete.
|
|
110
|
+
toast.error('Connection refused.') // [✗] Connection refused.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Themes
|
|
114
|
+
|
|
115
|
+
Four built-in presets. Switch by importing a different CSS file:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import '@n3rd-ai/ui/theme/unicorn.css' // violet → pink → cyan (default)
|
|
119
|
+
import '@n3rd-ai/ui/theme/classic.css' // green on black
|
|
120
|
+
import '@n3rd-ai/ui/theme/retro.css' // amber on black
|
|
121
|
+
import '@n3rd-ai/ui/theme/paper.css' // black on white
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Override any token:
|
|
125
|
+
|
|
126
|
+
```css
|
|
127
|
+
:root {
|
|
128
|
+
--n3rd-accent-primary: #06b6d4;
|
|
129
|
+
--n3rd-gradient: linear-gradient(90deg, #06b6d4, #22d3ee, #67e8f9);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## All Components
|
|
134
|
+
|
|
135
|
+
| Category | Components | JS |
|
|
136
|
+
| -------- | ------------------------------------------------------------------------- | --------- |
|
|
137
|
+
| Layout | Box, Stack, Row, Grid, Divider, Page | 0kb (RSC) |
|
|
138
|
+
| Display | Text, Heading, Badge, Metric, Table, Code, List, Logo, StatusLine, Footer | 0kb (RSC) |
|
|
139
|
+
| Input | Button, Input | Client |
|
|
140
|
+
| Feedback | Toast, Alert, Progress, Skeleton | Client |
|
|
141
|
+
| Nav | Nav | Client |
|
|
142
|
+
|
|
143
|
+
**Primitives:** Cursor, Typewriter, Scanline
|
|
144
|
+
|
|
145
|
+
**Hooks:** `useTypewriter`, `useKeyboard`, `useToast`
|
|
146
|
+
|
|
147
|
+
**Utils:** `drawBox()`, `formatTable()`
|
|
148
|
+
|
|
149
|
+
## Border Styles
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<Box border="single"> // ┌──┐
|
|
153
|
+
<Box border="double"> // ╔══╗
|
|
154
|
+
<Box border="rounded"> // ╭──╮
|
|
155
|
+
<Box border="dashed"> // ┌╌╌┐
|
|
156
|
+
<Box border="none"> // no border
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Bundle Size
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Server components (Box, Text, Metric, etc.): 0kb JS
|
|
163
|
+
Client components (Button, Input, Toast): ~3-5kb gzipped
|
|
164
|
+
Theme CSS: ~2kb gzipped
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
[MIT](./LICENSE) - Superstellar LLC
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ToastContextValue {
|
|
5
|
+
success: (message: string) => void;
|
|
6
|
+
warning: (message: string) => void;
|
|
7
|
+
error: (message: string) => void;
|
|
8
|
+
info: (message: string) => void;
|
|
9
|
+
}
|
|
10
|
+
declare function ToastProvider({ children, duration, }: {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}): react_jsx_runtime.JSX.Element;
|
|
14
|
+
declare function useToast(): ToastContextValue;
|
|
15
|
+
|
|
16
|
+
export { ToastProvider as T, useToast as u };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
type BorderStyle = 'single' | 'double' | 'rounded' | 'dashed' | 'none';
|
|
2
|
+
declare const BORDER_CHARS: {
|
|
3
|
+
readonly single: {
|
|
4
|
+
readonly topLeft: "┌";
|
|
5
|
+
readonly topRight: "┐";
|
|
6
|
+
readonly bottomLeft: "└";
|
|
7
|
+
readonly bottomRight: "┘";
|
|
8
|
+
readonly horizontal: "─";
|
|
9
|
+
readonly vertical: "│";
|
|
10
|
+
readonly teeLeft: "├";
|
|
11
|
+
readonly teeRight: "┤";
|
|
12
|
+
readonly teeTop: "┬";
|
|
13
|
+
readonly teeBottom: "┴";
|
|
14
|
+
readonly cross: "┼";
|
|
15
|
+
};
|
|
16
|
+
readonly double: {
|
|
17
|
+
readonly topLeft: "╔";
|
|
18
|
+
readonly topRight: "╗";
|
|
19
|
+
readonly bottomLeft: "╚";
|
|
20
|
+
readonly bottomRight: "╝";
|
|
21
|
+
readonly horizontal: "═";
|
|
22
|
+
readonly vertical: "║";
|
|
23
|
+
readonly teeLeft: "╠";
|
|
24
|
+
readonly teeRight: "╣";
|
|
25
|
+
readonly teeTop: "╦";
|
|
26
|
+
readonly teeBottom: "╩";
|
|
27
|
+
readonly cross: "╬";
|
|
28
|
+
};
|
|
29
|
+
readonly rounded: {
|
|
30
|
+
readonly topLeft: "╭";
|
|
31
|
+
readonly topRight: "╮";
|
|
32
|
+
readonly bottomLeft: "╰";
|
|
33
|
+
readonly bottomRight: "╯";
|
|
34
|
+
readonly horizontal: "─";
|
|
35
|
+
readonly vertical: "│";
|
|
36
|
+
readonly teeLeft: "├";
|
|
37
|
+
readonly teeRight: "┤";
|
|
38
|
+
readonly teeTop: "┬";
|
|
39
|
+
readonly teeBottom: "┴";
|
|
40
|
+
readonly cross: "┼";
|
|
41
|
+
};
|
|
42
|
+
readonly dashed: {
|
|
43
|
+
readonly topLeft: "┌";
|
|
44
|
+
readonly topRight: "┐";
|
|
45
|
+
readonly bottomLeft: "└";
|
|
46
|
+
readonly bottomRight: "┘";
|
|
47
|
+
readonly horizontal: "╌";
|
|
48
|
+
readonly vertical: "╎";
|
|
49
|
+
readonly teeLeft: "├";
|
|
50
|
+
readonly teeRight: "┤";
|
|
51
|
+
readonly teeTop: "┬";
|
|
52
|
+
readonly teeBottom: "┴";
|
|
53
|
+
readonly cross: "┼";
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
declare function getBorderChars(style: BorderStyle): {
|
|
57
|
+
readonly topLeft: "┌";
|
|
58
|
+
readonly topRight: "┐";
|
|
59
|
+
readonly bottomLeft: "└";
|
|
60
|
+
readonly bottomRight: "┘";
|
|
61
|
+
readonly horizontal: "─";
|
|
62
|
+
readonly vertical: "│";
|
|
63
|
+
readonly teeLeft: "├";
|
|
64
|
+
readonly teeRight: "┤";
|
|
65
|
+
readonly teeTop: "┬";
|
|
66
|
+
readonly teeBottom: "┴";
|
|
67
|
+
readonly cross: "┼";
|
|
68
|
+
} | {
|
|
69
|
+
readonly topLeft: "╔";
|
|
70
|
+
readonly topRight: "╗";
|
|
71
|
+
readonly bottomLeft: "╚";
|
|
72
|
+
readonly bottomRight: "╝";
|
|
73
|
+
readonly horizontal: "═";
|
|
74
|
+
readonly vertical: "║";
|
|
75
|
+
readonly teeLeft: "╠";
|
|
76
|
+
readonly teeRight: "╣";
|
|
77
|
+
readonly teeTop: "╦";
|
|
78
|
+
readonly teeBottom: "╩";
|
|
79
|
+
readonly cross: "╬";
|
|
80
|
+
} | {
|
|
81
|
+
readonly topLeft: "╭";
|
|
82
|
+
readonly topRight: "╮";
|
|
83
|
+
readonly bottomLeft: "╰";
|
|
84
|
+
readonly bottomRight: "╯";
|
|
85
|
+
readonly horizontal: "─";
|
|
86
|
+
readonly vertical: "│";
|
|
87
|
+
readonly teeLeft: "├";
|
|
88
|
+
readonly teeRight: "┤";
|
|
89
|
+
readonly teeTop: "┬";
|
|
90
|
+
readonly teeBottom: "┴";
|
|
91
|
+
readonly cross: "┼";
|
|
92
|
+
} | {
|
|
93
|
+
readonly topLeft: "┌";
|
|
94
|
+
readonly topRight: "┐";
|
|
95
|
+
readonly bottomLeft: "└";
|
|
96
|
+
readonly bottomRight: "┘";
|
|
97
|
+
readonly horizontal: "╌";
|
|
98
|
+
readonly vertical: "╎";
|
|
99
|
+
readonly teeLeft: "├";
|
|
100
|
+
readonly teeRight: "┤";
|
|
101
|
+
readonly teeTop: "┬";
|
|
102
|
+
readonly teeBottom: "┴";
|
|
103
|
+
readonly cross: "┼";
|
|
104
|
+
} | null;
|
|
105
|
+
|
|
106
|
+
export { type BorderStyle as B, BORDER_CHARS as a, getBorderChars as g };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/primitives/ascii-border.ts
|
|
2
|
+
var BORDER_CHARS = {
|
|
3
|
+
single: {
|
|
4
|
+
topLeft: "\u250C",
|
|
5
|
+
topRight: "\u2510",
|
|
6
|
+
bottomLeft: "\u2514",
|
|
7
|
+
bottomRight: "\u2518",
|
|
8
|
+
horizontal: "\u2500",
|
|
9
|
+
vertical: "\u2502",
|
|
10
|
+
teeLeft: "\u251C",
|
|
11
|
+
teeRight: "\u2524",
|
|
12
|
+
teeTop: "\u252C",
|
|
13
|
+
teeBottom: "\u2534",
|
|
14
|
+
cross: "\u253C"
|
|
15
|
+
},
|
|
16
|
+
double: {
|
|
17
|
+
topLeft: "\u2554",
|
|
18
|
+
topRight: "\u2557",
|
|
19
|
+
bottomLeft: "\u255A",
|
|
20
|
+
bottomRight: "\u255D",
|
|
21
|
+
horizontal: "\u2550",
|
|
22
|
+
vertical: "\u2551",
|
|
23
|
+
teeLeft: "\u2560",
|
|
24
|
+
teeRight: "\u2563",
|
|
25
|
+
teeTop: "\u2566",
|
|
26
|
+
teeBottom: "\u2569",
|
|
27
|
+
cross: "\u256C"
|
|
28
|
+
},
|
|
29
|
+
rounded: {
|
|
30
|
+
topLeft: "\u256D",
|
|
31
|
+
topRight: "\u256E",
|
|
32
|
+
bottomLeft: "\u2570",
|
|
33
|
+
bottomRight: "\u256F",
|
|
34
|
+
horizontal: "\u2500",
|
|
35
|
+
vertical: "\u2502",
|
|
36
|
+
teeLeft: "\u251C",
|
|
37
|
+
teeRight: "\u2524",
|
|
38
|
+
teeTop: "\u252C",
|
|
39
|
+
teeBottom: "\u2534",
|
|
40
|
+
cross: "\u253C"
|
|
41
|
+
},
|
|
42
|
+
dashed: {
|
|
43
|
+
topLeft: "\u250C",
|
|
44
|
+
topRight: "\u2510",
|
|
45
|
+
bottomLeft: "\u2514",
|
|
46
|
+
bottomRight: "\u2518",
|
|
47
|
+
horizontal: "\u254C",
|
|
48
|
+
vertical: "\u254E",
|
|
49
|
+
teeLeft: "\u251C",
|
|
50
|
+
teeRight: "\u2524",
|
|
51
|
+
teeTop: "\u252C",
|
|
52
|
+
teeBottom: "\u2534",
|
|
53
|
+
cross: "\u253C"
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function getBorderChars(style) {
|
|
57
|
+
if (style === "none") return null;
|
|
58
|
+
return BORDER_CHARS[style];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { BORDER_CHARS, getBorderChars };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createContext, useState, useCallback, useContext } from 'react';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/components/feedback/Toast.tsx
|
|
5
|
+
var ICONS = {
|
|
6
|
+
success: "[\u2713]",
|
|
7
|
+
warning: "[!]",
|
|
8
|
+
error: "[\u2717]",
|
|
9
|
+
info: "[i]"
|
|
10
|
+
};
|
|
11
|
+
var COLORS = {
|
|
12
|
+
success: "var(--n3rd-accent-success)",
|
|
13
|
+
warning: "var(--n3rd-accent-warning)",
|
|
14
|
+
error: "var(--n3rd-accent-danger)",
|
|
15
|
+
info: "var(--n3rd-accent-info)"
|
|
16
|
+
};
|
|
17
|
+
var ToastContext = createContext(null);
|
|
18
|
+
var idCounter = 0;
|
|
19
|
+
function ToastProvider({
|
|
20
|
+
children,
|
|
21
|
+
duration = 4e3
|
|
22
|
+
}) {
|
|
23
|
+
const [toasts, setToasts] = useState([]);
|
|
24
|
+
const addToast = useCallback(
|
|
25
|
+
(message, type) => {
|
|
26
|
+
const id = ++idCounter;
|
|
27
|
+
setToasts((prev) => [...prev, { id, message, type }]);
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
30
|
+
}, duration);
|
|
31
|
+
},
|
|
32
|
+
[duration]
|
|
33
|
+
);
|
|
34
|
+
const toast = {
|
|
35
|
+
success: (msg) => addToast(msg, "success"),
|
|
36
|
+
warning: (msg) => addToast(msg, "warning"),
|
|
37
|
+
error: (msg) => addToast(msg, "error"),
|
|
38
|
+
info: (msg) => addToast(msg, "info")
|
|
39
|
+
};
|
|
40
|
+
const containerStyle = {
|
|
41
|
+
position: "fixed",
|
|
42
|
+
bottom: "var(--n3rd-space-6)",
|
|
43
|
+
right: "var(--n3rd-space-6)",
|
|
44
|
+
display: "flex",
|
|
45
|
+
flexDirection: "column",
|
|
46
|
+
gap: "var(--n3rd-space-2)",
|
|
47
|
+
zIndex: 9998,
|
|
48
|
+
fontFamily: "var(--n3rd-font)",
|
|
49
|
+
fontSize: "var(--n3rd-text-sm)"
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: toast, children: [
|
|
52
|
+
children,
|
|
53
|
+
toasts.length > 0 && /* @__PURE__ */ jsx("div", { style: containerStyle, children: toasts.map((t) => /* @__PURE__ */ jsxs(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
style: {
|
|
57
|
+
padding: "var(--n3rd-space-2) var(--n3rd-space-3)",
|
|
58
|
+
backgroundColor: "var(--n3rd-bg-secondary)",
|
|
59
|
+
border: `1px solid ${COLORS[t.type]}`,
|
|
60
|
+
color: "var(--n3rd-text-primary)",
|
|
61
|
+
animation: "n3rd-fade-in var(--n3rd-fade-duration) ease-out"
|
|
62
|
+
},
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ jsx("span", { style: { color: COLORS[t.type], marginRight: "var(--n3rd-space-2)" }, children: ICONS[t.type] }),
|
|
65
|
+
t.message
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
t.id
|
|
69
|
+
)) })
|
|
70
|
+
] });
|
|
71
|
+
}
|
|
72
|
+
function useToast() {
|
|
73
|
+
const ctx = useContext(ToastContext);
|
|
74
|
+
if (!ctx) throw new Error("useToast must be used within <ToastProvider> or <N3rdProvider>");
|
|
75
|
+
return ctx;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { ToastProvider, useToast };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { u as useToast } from '../Toast-qHlZE8FW.js';
|
|
2
|
+
import 'react/jsx-runtime';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
interface UseTypewriterOptions {
|
|
6
|
+
text: string;
|
|
7
|
+
speed?: number;
|
|
8
|
+
delay?: number;
|
|
9
|
+
onComplete?: () => void;
|
|
10
|
+
}
|
|
11
|
+
declare function useTypewriter({ text, speed, delay, onComplete }: UseTypewriterOptions): {
|
|
12
|
+
displayed: string;
|
|
13
|
+
done: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type KeyHandler = (e: KeyboardEvent) => void;
|
|
17
|
+
interface Shortcut {
|
|
18
|
+
key: string;
|
|
19
|
+
ctrl?: boolean;
|
|
20
|
+
meta?: boolean;
|
|
21
|
+
shift?: boolean;
|
|
22
|
+
alt?: boolean;
|
|
23
|
+
handler: KeyHandler;
|
|
24
|
+
}
|
|
25
|
+
declare function useKeyboard(shortcuts: Shortcut[]): void;
|
|
26
|
+
|
|
27
|
+
export { type UseTypewriterOptions, useKeyboard, useTypewriter };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export { useToast } from '../chunk-MZO6ECNX.js';
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
function useTypewriter({ text, speed = 50, delay = 0, onComplete }) {
|
|
5
|
+
const [displayed, setDisplayed] = useState("");
|
|
6
|
+
const [done, setDone] = useState(false);
|
|
7
|
+
const hasRun = useRef(false);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (hasRun.current) return;
|
|
10
|
+
hasRun.current = true;
|
|
11
|
+
let index = 0;
|
|
12
|
+
const timeout = setTimeout(() => {
|
|
13
|
+
const interval = setInterval(() => {
|
|
14
|
+
if (index < text.length) {
|
|
15
|
+
setDisplayed(text.slice(0, index + 1));
|
|
16
|
+
index++;
|
|
17
|
+
} else {
|
|
18
|
+
clearInterval(interval);
|
|
19
|
+
setDone(true);
|
|
20
|
+
onComplete?.();
|
|
21
|
+
}
|
|
22
|
+
}, speed);
|
|
23
|
+
return () => clearInterval(interval);
|
|
24
|
+
}, delay);
|
|
25
|
+
return () => clearTimeout(timeout);
|
|
26
|
+
}, [text, speed, delay, onComplete]);
|
|
27
|
+
return { displayed, done };
|
|
28
|
+
}
|
|
29
|
+
function useKeyboard(shortcuts) {
|
|
30
|
+
const handleKeyDown = useCallback(
|
|
31
|
+
(e) => {
|
|
32
|
+
for (const shortcut of shortcuts) {
|
|
33
|
+
const keyMatch = e.key.toLowerCase() === shortcut.key.toLowerCase();
|
|
34
|
+
const ctrlMatch = (shortcut.ctrl ?? false) === (e.ctrlKey || e.metaKey);
|
|
35
|
+
const shiftMatch = (shortcut.shift ?? false) === e.shiftKey;
|
|
36
|
+
const altMatch = (shortcut.alt ?? false) === e.altKey;
|
|
37
|
+
if (keyMatch && ctrlMatch && shiftMatch && altMatch) {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
shortcut.handler(e);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
[shortcuts]
|
|
45
|
+
);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
48
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
49
|
+
}, [handleKeyDown]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { useKeyboard, useTypewriter };
|