rocketjob-ui
v1.0.1
Published
Private UI component library for [RocketJob](https://rocketjob.ai).
Readme
@rocketjob/ui
Private UI component library for RocketJob.
Built on shadcn/ui + Radix UI, styled with Tailwind CSS (prefix rj-), distributed as dual ESM/CJS via tsup.
Table of Contents
- Installation
- Next.js setup
- Usage
- Adding shadcn components
- Creating custom components
- Storybook
- Building
- Testing
- Versioning & publishing
Installation
This package is hosted on GitHub Packages and is private. You must authenticate before installing.
1. Set up authentication
Create or edit ~/.npmrc (global) or .npmrc in your consuming project root:
@rocketjob:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}Set NODE_AUTH_TOKEN in your environment (or CI secrets) to a GitHub Personal Access Token with at least read:packages scope.
2. Install the package
npm install @rocketjob/ui
# or
pnpm add @rocketjob/ui
# or
yarn add @rocketjob/uiNext.js setup
1. Transpile the package
Add transpilePackages to your Next.js config so that Next.js processes the library's JSX/TSX:
// next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
transpilePackages: ['@rocketjob/ui'],
};2. Import the CSS
Import the compiled stylesheet once at the top level, e.g. in app/layout.tsx:
// app/layout.tsx
import '@rocketjob/ui/styles';3. Wrap your app with RootProvider
// app/layout.tsx
import '@rocketjob/ui/styles';
import { RootProvider } from '@rocketjob/ui';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<RootProvider
defaultTheme="system"
attribute="class"
enableSystem
toastPosition="bottom-right"
richColors
>
{children}
</RootProvider>
</body>
</html>
);
}All RootProvider props are optional — the component works with zero configuration.
Optional semantic colours
Override primary, secondary, destructive, success, info, and warning (plus matching foregrounds) with HSL triplets — same format as shadcn ("221 83% 53%", no hsl() wrapper):
<RootProvider
themeColors={{
primary: '221 83% 53%',
primaryForeground: '0 0% 100%',
success: '142 76% 36%',
warning: '38 92% 50%',
}}
>
{children}
</RootProvider>You can also use ThemeColorsProvider alone or call applyThemeColors(document.documentElement, { ... }) outside React.
Usage
Components
import { Button, Input, Dialog, DialogContent, DialogTrigger } from '@rocketjob/ui';
// Button
<Button variant="default" size="default">Get started</Button>
<Button variant="outline" size="sm">Cancel</Button>
<Button variant="destructive">Delete account</Button>
<Button variant="success">Saved</Button>
<Button variant="info">Note</Button>
<Button variant="warning">Review</Button>
// Input
<Input type="email" placeholder="[email protected]" />
// Dialog
<Dialog>
<DialogTrigger asChild>
<Button>Open dialog</Button>
</DialogTrigger>
<DialogContent>
<p>Dialog content here.</p>
</DialogContent>
</Dialog>API Client
import { createApiClient } from '@rocketjob/ui';
const api = createApiClient({
baseURL: process.env.NEXT_PUBLIC_API_URL,
timeout: 15_000,
getToken: () => localStorage.getItem('token'),
});
// Typed GET
const jobs = await api.get<Job[]>('/jobs');
// Typed POST
const job = await api.post<Job>('/jobs', { title: 'Software Engineer' });Hooks
import { useApiQuery, useApiMutation, useDebounce, useLocalStorage } from '@rocketjob/ui';
// Data fetching
const { data, isLoading } = useApiQuery<Job[]>(['jobs'], '/api/jobs');
// Mutations
const { mutate } = useApiMutation<Job, CreateJobInput>('/api/jobs', 'post');
// Debounce
const debouncedSearch = useDebounce(searchTerm, 300);
// LocalStorage
const [theme, setTheme] = useLocalStorage('theme', 'system');Environment variables
import { getEnv, useEnv } from '@rocketjob/ui';
// Server-safe utility (works in RSC, API routes, etc.)
const { apiBaseUrl, appEnv } = getEnv();
// Client hook (use in "use client" components)
function MyComponent() {
const { featureFlags } = useEnv();
// ...
}Supported variables (both NEXT_PUBLIC_* and REACT_APP_* prefixes are detected automatically):
| Variable | Resolved from |
|---|---|
| apiBaseUrl | NEXT_PUBLIC_API_URL or REACT_APP_API_URL |
| appEnv | NEXT_PUBLIC_APP_ENV, REACT_APP_ENV, or NODE_ENV |
| sentryDsn | NEXT_PUBLIC_SENTRY_DSN or REACT_APP_SENTRY_DSN |
| featureFlags | NEXT_PUBLIC_FEATURE_FLAGS or REACT_APP_FEATURE_FLAGS |
Utilities
import { cn, formatDate, formatCurrency, truncate, generateId } from '@rocketjob/ui';
cn('rj-flex', condition && 'rj-hidden', 'custom-class');
formatDate(new Date(), { year: 'numeric', month: 'short', day: 'numeric' });
formatCurrency(4999, 'USD');
truncate('A very long job description...', 80);
generateId(); // crypto.randomUUID() with fallbackAdding shadcn components
components.json is already configured at the package root. To add a new shadcn component:
# From packages/ui/
npx shadcn@latest add badgeThis drops the component into src/components/ui/. After adding:
- Review the generated file — replace any Tailwind classes that are missing the
rj-prefix. - Re-export the new component from
src/components/ui/index.ts. - Write a Storybook story in
stories/.
Creating custom components
Custom RocketJob-specific components live in src/components/custom/. They are built on top of shadcn/ui primitives.
- Create
src/components/custom/job-card.tsx:
"use client";
import * as React from "react";
import { cn } from "../../lib/utils";
export interface JobCardProps {
title: string;
company: string;
className?: string | undefined;
}
export function JobCard({ title, company, className }: JobCardProps) {
return (
<div
className={cn(
"rj-rounded-lg rj-border rj-bg-card rj-p-4 rj-shadow-sm",
className
)}
>
<h3 className="rj-text-lg rj-font-semibold">{title}</h3>
<p className="rj-text-sm rj-text-muted-foreground">{company}</p>
</div>
);
}- Export from
src/components/custom/index.ts:
export { JobCard } from "./job-card";
export type { JobCardProps } from "./job-card";- Write a story in
stories/JobCard.stories.tsxfollowing the same pattern asButton.stories.tsx.
Storybook
cd packages/ui
npm run storybook # dev server on http://localhost:6006
npm run build-storybook # static build to storybook-static/Storybook 8 is configured with:
- @storybook/addon-essentials — controls, actions, docs, viewport
- @storybook/addon-a11y — accessibility checks on every story
- @storybook/addon-interactions — play-function driven interaction testing
- @storybook/addon-themes — light/dark theme switcher in toolbar
Building
cd packages/ui
npm run buildThis runs two steps in sequence:
tsup— compiles TypeScript todist/index.mjs(ESM) anddist/index.js(CJS) with.d.tsdeclarations.tailwindcss— generatesdist/index.cssfromsrc/styles/globals.css.
For development with watch mode:
npm run dev # runs tsup --watch and tailwindcss --watch concurrentlyTesting
npm run test # run all tests once (CI-friendly)
npm run test:ui # open Vitest UI in browser
npm run typecheck # tsc --noEmit (strict + exactOptionalPropertyTypes)Tests use Vitest + jsdom + @testing-library/react.
Each component has its own __tests__/ directory alongside the source file.
Versioning & publishing
This package is private and published to GitHub Packages via GitHub Actions.
- Bump the version in
package.jsonfollowing semver. - Commit and push the version bump.
- Push a git tag matching the new version:
git tag v0.2.0
git push origin v0.2.0- The GitHub Actions workflow (defined in
.github/workflows/publish.ymlin the monorepo root) picks up the tag, runsnpm run build, and publishes tohttps://npm.pkg.github.com.
Never publish this package to the public npm registry.
"private": trueandpublishConfig.registryguard against accidental public releases.
