VelvetUi¶
SolidJS UI component library and shared frontend tooling standards. Published to GitHub Packages as @jankrajewskiit/ui.
- Repository: JanKrajewskiIT/velvet
- Package:
@jankrajewskiit/ui - Demo: demo.bluebraces.online
- Peers:
solid-js >=1.9,@solidjs/router >=0.15
Install¶
For GitHub Packages authentication, add to .npmrc:
Quick Start¶
import '@jankrajewskiit/ui/styles';
import { Button, ThemeProvider } from '@jankrajewskiit/ui';
const App = () => (
<ThemeProvider>
<Button variant="primary">Save</Button>
</ThemeProvider>
);
Shared Tooling Presets¶
VelvetUi exports configuration presets to enforce consistent standards across SolidJS projects.
ESLint¶
// eslint.config.mjs
import { createEslintConfig } from '@jankrajewskiit/ui/eslint-config';
export default createEslintConfig({
tsconfigRootDir: import.meta.dirname,
});
Prettier¶
TypeScript¶
// tsconfig.app.json
{
"extends": "@jankrajewskiit/ui/tsconfig/app-vite",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": { "~/*": ["src/*"] }
},
"include": ["src"]
}
// tsconfig.node.json
{
"extends": "@jankrajewskiit/ui/tsconfig/node-vite",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
},
"include": ["vite.config.ts"]
}
Vite¶
// vite.config.ts
import { defineConfig } from 'vite';
import { createSolidAppViteConfig } from '@jankrajewskiit/ui/vite-config';
export default defineConfig(
createSolidAppViteConfig({
server: { port: 5173 },
preview: { port: 5173 },
})
);
createSolidAppViteConfig applies defaults for vite-plugin-solid, alias ~ -> <project>/src, and solid-markdown optimizeDeps.
lint-staged¶
// .lintstagedrc.mjs
import { createLintStagedConfig } from '@jankrajewskiit/ui/lint-staged-config';
export default createLintStagedConfig();
Husky pre-commit¶
# .husky/pre-commit
. "$(dirname -- "$0")/../node_modules/@jankrajewskiit/ui/config/husky-pre-commit.sh"
Component Catalog¶
Actions and Forms¶
ActionRow, Autocomplete, Button, Checkbox, ColorPicker, DropdownButton, DropdownItem, DropdownMenu, FormField, FormGrid, FormLayout, IconButton, Input, LocaleSwitcher, RadioGroup, Select, Switch, TagInput, TextArea
Layouts¶
Footer, MainLayout, Navbar
Feedback and States¶
AccessDenied, Chip, EmptyState, ErrorFallback, ErrorMessage, LoadingContainer, Skeleton, Spinner, StatCard, TagChip, ThemeToggle
Overlays¶
ConfirmAction, ConfirmDialog, Dialog, DialogActions, DialogBody, DialogTitle, Drawer, Tooltip
Data Display¶
Accordion, AccordionItem, JsonDisplay, MarkdownRenderer, Pagination, RadarChart, ScoreRing, SectionCard, Stepper, Table, TableBody, TableCell, TableHead, TableHeader, TableRow
Workflows¶
AiResultPanel, AuthRedirect, EditableCollection, FileDropZone, FlipCard, MarkdownEditDialog, RequireAuth, Tabs, Timeline, Wizard
Theme, Hooks, Utilities¶
| Category | Exports |
|---|---|
| Theme | ThemeProvider, useTheme, ThemeChoice |
| Toast | ToastProvider, ToastViewport, useToast, addToast, removeToast, ToastMessage |
| Hooks | useClickOutside, useEscapeKey |
| Utils | getContrastTextColor, orEmpty, numberToString, emptyToNull, parseFloatOrNull |
| Types | Props, NavItem, RadioOption, WizardStep |
Source Structure¶
src/
components/
actions-and-forms/
layouts/
feedback-and-states/
overlays/
data-display/
workflows/
hooks/
models/
theme/
utils/
index.ts
config/
demo/
Design Principles¶
Components are generic and app-agnostic:
- No i18n calls — all text passed via props
- No auth calls — consumers handle auth at the call site
- Layout components use JSX slots for composition
- Every component forwards
class?: stringviaProps<T>
Scripts¶
npm run typecheck # TypeScript check
npm run lint # ESLint
npm run build # Build library
npm run test # Run unit tests
npm run verify # typecheck + lint + build + test
CI/CD¶
Publishing¶
Library is published to GitHub Packages on tags matching v* via .github/workflows/publish.yml.
Demo Deployment¶
The demo app deploys to https://demo.bluebraces.online on every push to main via .github/workflows/deploy-demo.yml.
Pipeline: verify library → build Docker image → push to GHCR → deploy via SSH
The demo container joins the aspire Docker network on deployment. Nginx routes demo.bluebraces.online to the velvet-demo container (config maintained in Recron at deploy/nginx/nginx.conf).
Required Secrets (GitHub Environment: production)¶
| Secret | Description |
|---|---|
VPS_HOST |
Hostinger VPS IP address |
VPS_USER |
SSH username (root) |
VPS_SSH_KEY |
Ed25519 private SSH key |
GHCR_TOKEN |
GitHub PAT with read:packages + write:packages |
Generating VPS_SSH_KEY¶
Windows (PowerShell):
ssh-keygen -t ed25519 -C "github-actions-deploy" -f $env:USERPROFILE\.ssh\velvet_deploy
Get-Content $env:USERPROFILE\.ssh\velvet_deploy.pub | ssh root@YOUR_VPS_IP "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# Display private key to copy to GitHub Secrets
Get-Content $env:USERPROFILE\.ssh\velvet_deploy
Linux/macOS:
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/velvet_deploy -N ""
ssh-copy-id -i ~/.ssh/velvet_deploy.pub root@YOUR_VPS_IP
cat ~/.ssh/velvet_deploy
Copy the entire private key (including -----BEGIN OPENSSH PRIVATE KEY----- header/footer) into VPS_SSH_KEY.
If the VPS is already configured for another project, the SSH key may already be in
authorized_keys. Reuse the existing private key value.
Generating GHCR_TOKEN¶
- GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Generate new token, set expiration (90 days recommended)
- Select scopes:
read:packages,write:packages - Add as
GHCR_TOKENin theproductionenvironment secrets
DNS¶
Add an A record in Hostinger hPanel (DNS Zone Editor):
| Type | Name | Target |
|---|---|---|
| A | demo |
VPS IP address |
SSL Certificate¶
The demo runs behind Traefik and shares the wildcard certificate. If the certificate was issued before demo.bluebraces.online was added, expand it on the VPS — see Deployment runbook.