@jdcpuwiz/homelab-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/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/index.cjs +602 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +302 -0
- package/dist/index.d.ts +302 -0
- package/dist/index.js +565 -0
- package/dist/index.js.map +1 -0
- package/globals.css +56 -0
- package/package.json +86 -0
- package/tailwind-preset.cjs +135 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JdCpuWiz
|
|
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
|
|
13
|
+
all 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,228 @@
|
|
|
1
|
+
# @jdcpuwiz/homelab-ui
|
|
2
|
+
|
|
3
|
+
Shared dark-theme UI primitives for the JdCpuWiz homelab. Doghouse is the canonical reference; this package is the extracted version every other project consumes.
|
|
4
|
+
|
|
5
|
+
**What you get**
|
|
6
|
+
|
|
7
|
+
- **Components** — `Sidebar`, `SidebarNavItem`, `Button`, `Modal`, `ConfirmDialog`, `PageHeader`, `EmptyState`, `Spinner`, `TagChips`
|
|
8
|
+
- **Tailwind preset** — full token namespace (`bg-sidebar`, `bg-card`, `text-brand`, `bg-status-success`, `rounded-widget`, `w-sidebar`, …)
|
|
9
|
+
- **Global CSS variables** — `--hl-brand`, `--hl-sidebar`, `--hl-card`, etc. Override at `:root` to re-skin a project.
|
|
10
|
+
- **Font wiring snippet** — Geist + Geist Mono + Orbitron via `next/font/google`, with the exact 6-line snippet to paste into `app/layout.tsx` (next/font is a build-time API that can't be re-exported — see below)
|
|
11
|
+
|
|
12
|
+
## Why this exists
|
|
13
|
+
|
|
14
|
+
Asset Den shipped 9 phases of "design-expert approved" code that turned out to have the **wrong fonts** because `next/font` was never imported — the CSS variable resolved to nothing and the whole app silently fell back to `ui-sans-serif`. Every homelab project was re-deriving the same primitives, sidebar shell, color palette, and font wiring; every project drifted. This package is the single source of truth so updates land everywhere.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @jdcpuwiz/homelab-ui
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Peer deps: `react >= 18`, `next >= 15` (only for font wiring — non-Next consumers wire their own), `tailwindcss ^3`.
|
|
23
|
+
|
|
24
|
+
## Setup (3 steps — copy-paste)
|
|
25
|
+
|
|
26
|
+
### 1. Wire fonts in `app/layout.tsx`
|
|
27
|
+
|
|
28
|
+
`next/font/google` is a build-time directive — Next's SWC plugin scans your **app source** for literal `const Foo = Font(...)` calls and a library can't re-export the loaders (bundling converts `const` to `var` and Next refuses). So the canonical setup is to paste this snippet directly into your layout:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import type { Metadata } from "next";
|
|
32
|
+
import { Geist, Geist_Mono, Orbitron } from "next/font/google";
|
|
33
|
+
import "@jdcpuwiz/homelab-ui/globals.css";
|
|
34
|
+
import "./globals.css"; // optional, your own project styles
|
|
35
|
+
|
|
36
|
+
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
|
|
37
|
+
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
|
|
38
|
+
const orbitron = Orbitron({ variable: "--font-orbitron", subsets: ["latin"] });
|
|
39
|
+
|
|
40
|
+
export const metadata: Metadata = { title: "My App" };
|
|
41
|
+
|
|
42
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
43
|
+
return (
|
|
44
|
+
<html lang="en" className="dark">
|
|
45
|
+
<body className={`${geistSans.variable} ${geistMono.variable} ${orbitron.variable} antialiased`}>
|
|
46
|
+
{children}
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The variable names (`--font-geist-sans`, `--font-geist-mono`, `--font-orbitron`) match exactly what the Tailwind preset references for `font-sans` / `font-mono` / `font-display`. If you'd rather not memorize them, import them as constants:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { FONT_CSS_VARIABLES } from "@jdcpuwiz/homelab-ui";
|
|
57
|
+
// → { sans: "--font-geist-sans", mono: "--font-geist-mono", display: "--font-orbitron" }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> **Sanity check after deploy:** open DevTools and run
|
|
61
|
+
> `getComputedStyle(document.body).fontFamily` — it MUST start with `"Geist"`.
|
|
62
|
+
> If it starts with `"ui-sans-serif"`, your font wiring is broken. See
|
|
63
|
+
> "Troubleshooting" below.
|
|
64
|
+
|
|
65
|
+
### 2. Apply the Tailwind preset in `tailwind.config.{js,ts}`
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
module.exports = {
|
|
69
|
+
presets: [require("@jdcpuwiz/homelab-ui/tailwind-preset")],
|
|
70
|
+
content: [
|
|
71
|
+
"./app/**/*.{ts,tsx}",
|
|
72
|
+
"./components/**/*.{ts,tsx}",
|
|
73
|
+
// REQUIRED: pull classes from the package's compiled JS
|
|
74
|
+
"./node_modules/@jdcpuwiz/homelab-ui/dist/**/*.{js,mjs,cjs}",
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Skipping the `node_modules/@jdcpuwiz/...` entry means Tailwind won't see the classes the package uses internally → broken styles. This is the single most common setup mistake.
|
|
80
|
+
|
|
81
|
+
### 3. Render the sidebar
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Sidebar, SidebarNavItem } from "@jdcpuwiz/homelab-ui";
|
|
85
|
+
import { LayoutDashboard, Server } from "lucide-react";
|
|
86
|
+
import Link from "next/link";
|
|
87
|
+
|
|
88
|
+
export default function AppShell({ children }) {
|
|
89
|
+
return (
|
|
90
|
+
<div className="flex h-screen overflow-hidden">
|
|
91
|
+
<Sidebar
|
|
92
|
+
logoSrc="/logo.png"
|
|
93
|
+
logoAlt="My App"
|
|
94
|
+
nav={
|
|
95
|
+
<>
|
|
96
|
+
<SidebarNavItem
|
|
97
|
+
label="Home"
|
|
98
|
+
icon={<LayoutDashboard size={16} />}
|
|
99
|
+
href="/"
|
|
100
|
+
render={({ href, className }, c) => (
|
|
101
|
+
<Link href={href} className={className}>
|
|
102
|
+
{c}
|
|
103
|
+
</Link>
|
|
104
|
+
)}
|
|
105
|
+
/>
|
|
106
|
+
<SidebarNavItem
|
|
107
|
+
label="Servers"
|
|
108
|
+
icon={<Server size={16} />}
|
|
109
|
+
href="/servers"
|
|
110
|
+
render={({ href, className }, c) => (
|
|
111
|
+
<Link href={href} className={className}>
|
|
112
|
+
{c}
|
|
113
|
+
</Link>
|
|
114
|
+
)}
|
|
115
|
+
/>
|
|
116
|
+
</>
|
|
117
|
+
}
|
|
118
|
+
footer="v0.1.0"
|
|
119
|
+
/>
|
|
120
|
+
<main className="flex-1 overflow-y-auto bg-[var(--hl-content)]">
|
|
121
|
+
{children}
|
|
122
|
+
</main>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Done. The app boots with the right fonts, sidebar, and theme.
|
|
129
|
+
|
|
130
|
+
## Components
|
|
131
|
+
|
|
132
|
+
| Component | Purpose |
|
|
133
|
+
|-------------------|---------|
|
|
134
|
+
| `Sidebar` | Doghouse-canonical shell. Slots: `widgets`, `nav`, `middle`, `lower`, `admin`, `footer`. w-60 fixed md+, off-canvas drawer on mobile (built-in hamburger + close X). |
|
|
135
|
+
| `SidebarNavItem` | Active = `bg-brand` + white text; inactive = `text-white/45` + `bg-white/5` hover. Supports `href` (auto `<a>`) or `render` (next/link wrapper). |
|
|
136
|
+
| `Button` | `variant` × `size`. Variants: `primary` (brand orange), `secondary` (card), `tertiary` (ghost), `danger` (red). Sizes: `sm`, `md`. |
|
|
137
|
+
| `Modal` | Overlay + click-outside + Esc + role=dialog + aria-* wiring + footer slot. `density: "form" \| "media"`. |
|
|
138
|
+
| `ConfirmDialog` | Modal + Button×2, Enter triggers confirm. `tone: "danger" \| "primary"`. |
|
|
139
|
+
| `PageHeader` | Title + description + back link + actions slot + optional header image (drop a PNG in `/public/` to brand). Supports breadcrumbs. |
|
|
140
|
+
| `EmptyState` | `panel` (warm card body) or `inline` (text-only) variant. |
|
|
141
|
+
| `Spinner` | `<Loader2 />` + label, sized for the dashboard's tertiary-text grey. |
|
|
142
|
+
| `TagChips` | Solid-color pills with auto white/black text contrast on yellow. Read-only `<span>` or interactive `<button>`. |
|
|
143
|
+
|
|
144
|
+
### Helpers
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { cn, tagTextColor } from "@jdcpuwiz/homelab-ui";
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- `cn(...inputs)` — clsx + tailwind-merge.
|
|
151
|
+
- `tagTextColor(hex)` — returns `#ffffff` or `#000000` depending on luminance. Use on any user-picked chip background so yellow doesn't render unreadable white text.
|
|
152
|
+
|
|
153
|
+
## Tokens
|
|
154
|
+
|
|
155
|
+
The Tailwind preset exposes the canonical homelab palette:
|
|
156
|
+
|
|
157
|
+
| Token | Use |
|
|
158
|
+
|---------------------------------------------------------|-----|
|
|
159
|
+
| `bg-sidebar` `bg-content` `bg-card` `bg-card-hover` `bg-card-warm` | Surfaces (darkest → lightest) |
|
|
160
|
+
| `bg-brand` `text-brand` `text-brand-ink` `bg-brand-hover` `ring-brand-glow` | Brand orange — **identity only, never status** |
|
|
161
|
+
| `bg-status-success` (`#15803d`), `bg-status-info` (`#1d4ed8`), `bg-status-warning` (`#eab308`, **black text**), `bg-status-danger` (`#b91c1c`), `bg-status-special` (`#6d28d9`), `bg-status-neutral` (`#6b7280`), `bg-status-empty` (`#4b5563`), `bg-status-primary` (= brand, **black text**) | Status pills — solid bg + white text per the global rule (yellow + primary take black). |
|
|
162
|
+
| `border-border` `border-input` `border-input-hover` | Borders |
|
|
163
|
+
| `bg-overlay` (`rgba(0,0,0,0.9)`) `bg-overlay-chip` (`rgba(0,0,0,0.6)`) | Modal backdrops / hover chips over images |
|
|
164
|
+
| `text-title` (`#aa89b7`) | h1 purple (doghouse convention) |
|
|
165
|
+
| `text-ink-primary` `text-ink-secondary` `text-ink-tertiary` `text-ink-disabled` | When `text-white/60` semantics aren't enough |
|
|
166
|
+
| `rounded-widget` (xl), `rounded-row` (lg), `rounded-chip`, `rounded-panel` (2xl) | Radius aliases |
|
|
167
|
+
| `w-sidebar` (15rem), `w-logo` `h-logo` (9rem) | Layout sizes |
|
|
168
|
+
| `font-sans` `font-mono` `font-display` | Geist / Geist Mono / Orbitron |
|
|
169
|
+
| `text-2xs` (10px / 14px) | Sidebar footers, grid captions |
|
|
170
|
+
|
|
171
|
+
### Overriding tokens per app
|
|
172
|
+
|
|
173
|
+
The CSS variables are namespaced (`--hl-*`). Redefine them at `:root` in your own CSS — the preset's color tokens reference the vars, so your override wins everywhere automatically.
|
|
174
|
+
|
|
175
|
+
```css
|
|
176
|
+
/* app/globals.css — re-skin your project */
|
|
177
|
+
:root {
|
|
178
|
+
--hl-brand: #00aaff; /* swap brand orange for blue */
|
|
179
|
+
--hl-sidebar: #0a1929;
|
|
180
|
+
--hl-card: #112844;
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Without Next.js
|
|
185
|
+
|
|
186
|
+
Every component works standalone. Just wire fonts yourself — load Geist + Geist Mono + Orbitron however your framework prefers (CSS `@import url(...)`, `<link>` tag, etc.) and set the same three CSS variables on `:root`:
|
|
187
|
+
|
|
188
|
+
```css
|
|
189
|
+
:root {
|
|
190
|
+
--font-geist-sans: "Geist", system-ui, sans-serif;
|
|
191
|
+
--font-geist-mono: "Geist Mono", ui-monospace, monospace;
|
|
192
|
+
--font-orbitron: "Orbitron", sans-serif;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Troubleshooting
|
|
197
|
+
|
|
198
|
+
### Fonts render as system sans-serif
|
|
199
|
+
|
|
200
|
+
Run `getComputedStyle(document.body).fontFamily` in DevTools. Symptoms:
|
|
201
|
+
|
|
202
|
+
- Returns `"ui-sans-serif", system-ui, ...` → the `next/font` snippet isn't pasted in `app/layout.tsx`, or `${geistSans.variable}` isn't applied to `<body>`'s className. Re-paste the snippet from "Setup → 1".
|
|
203
|
+
- Returns `"Geist", ...` → working as intended.
|
|
204
|
+
|
|
205
|
+
### Sidebar looks unstyled
|
|
206
|
+
|
|
207
|
+
You're missing the `node_modules/@jdcpuwiz/homelab-ui/dist/**` entry in your `tailwind.config` `content` array. Add it.
|
|
208
|
+
|
|
209
|
+
### Modal renders but Tailwind classes don't apply
|
|
210
|
+
|
|
211
|
+
Same as above — Tailwind isn't scanning the package's dist files.
|
|
212
|
+
|
|
213
|
+
### `bg-brand` is undefined
|
|
214
|
+
|
|
215
|
+
You skipped the Tailwind preset. Add `presets: [require("@jdcpuwiz/homelab-ui/tailwind-preset")]` to your `tailwind.config`. Alternatively, use the raw CSS variables: `style={{ backgroundColor: "var(--hl-brand)" }}`.
|
|
216
|
+
|
|
217
|
+
## Local development
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npm install
|
|
221
|
+
npm run build # tsup → dist/
|
|
222
|
+
npm run typecheck # tsc --noEmit
|
|
223
|
+
npm run ladle # reference renders at http://localhost:61000
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT
|