@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 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
+ [![CI](https://github.com/SuperstellarLLC/n3rd-ai-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/SuperstellarLLC/n3rd-ai-ui/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/@n3rd-ai/ui)](https://npmjs.com/package/@n3rd-ai/ui)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@n3rd-ai/ui)](https://bundlephobia.com/package/@n3rd-ai/ui)
8
+ [![license](https://img.shields.io/npm/l/@n3rd-ai/ui)](./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 };