Initial Commit

This commit is contained in:
Matthew McPeak 2026-05-18 15:30:44 -04:00
commit 9baf870446
Signed by: McPeakML
GPG key ID: 3D64A2E70F58D07C
35 changed files with 3318 additions and 0 deletions

8
.editorconfig Normal file
View file

@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

48
.gitignore vendored Normal file
View file

@ -0,0 +1,48 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/
# Vite
*.timestamp-*-*.mjs
packages/*/dist
packages/*/node_modules
packages/*/.storybook-static
packages/*/storybook-static
# Storybook
.storybook/node_modules
node_modules/.cache

10
.oxlintrc.json Normal file
View file

@ -0,0 +1,10 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue", "vitest"],
"env": {
"browser": true
},
"categories": {
"correctness": "error"
}
}

6
.prettierrc.json Normal file
View file

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}

10
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
"recommendations": [
"Vue.volar",
"vitest.explorer",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"oxc.oxc-vscode",
"esbenp.prettier-vscode"
]
}

133
README.md Normal file
View file

@ -0,0 +1,133 @@
# nychthemeron
A CSS-reactive Vue component library built on PrimeVue (Unstyled), shipped with the nychthemeron theme as a default.
## About
nychthemeron provides a curated set of Vue 3 components powered by PrimeVue's unstyled components and the nychthemeron theme system. This approach gives you complete control over component styling while maintaining a consistent, polished design language out of the box.
### Features
- **CSS-Reactive**: Components respond to CSS changes without requiring JavaScript updates
- **PrimeVue Powered**: Built on the robust, accessible PrimeVue unstyled component library
- **Theme Included**: Ships with the nychthemeron theme, ready to use immediately
- **TypeScript**: Full type support for Vue 3 and TypeScript projects
- **Customizable**: Override or extend the default theme to match your design system
## Quick Start
### Installation
```bash
npm install @nychthemeron/library primevue vue@^3.5.0
```
### Basic Usage
Import the library components and styles:
```vue
<script setup lang="ts">
import { NychButton } from '@nychthemeron/library'
</script>
<template>
<NychButton label="Click me" />
</template>
<style>
@import '@nychthemeron/library/style';
@import '@nychthemeron/library/theme';
</style>
```
## Components
- **NychButton** - Customizable button component
More components coming soon.
## Customization
### Using the Default Theme
The nychthemeron theme is included by default:
```css
@import '@nychthemeron/library/theme';
```
### Custom Styling
Override or extend component styles by importing only the base styles and providing your own CSS:
```css
@import '@nychthemeron/library/style';
/* Your custom theme */
:root {
--primary-color: #your-color;
/* More CSS variables */
}
```
## Project Structure
This is a monorepo containing:
- **`packages/library`** - The component library package
- **`packages/playground`** - Development environment and examples
## Development
### Setup
```bash
bun install
```
### Development Server
Start the playground to develop and test components:
```bash
bun dev
```
### Build
Build the library for production:
```bash
bun build
```
### Testing
Run unit tests:
```bash
bun test
```
### Linting and Formatting
```bash
bun lint # Check and fix linting issues
bun format # Format code with Prettier
bun type-check # Run TypeScript type checking
```
## Requirements
- **Node.js**: >=20.19.0 or >=22.12.0
- **Vue**: ^3.5.0
- **PrimeVue**: ^4.5.0
## Browser Support
Supports all modern browsers with CSS Grid and CSS Variables support.
## License
MIT

1378
bun.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,708 @@
# Library with Playground Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Transform nychthemeron into a monorepo with a Vue component library (with PrimeVue + Tailwind) and interactive Storybook playground.
**Architecture:** Bun workspaces manage two independent packages — library (builds to ESM) and playground (Storybook dev server and static build). Shared configs at root level.
**Tech Stack:** Vue 3, PrimeVue, Tailwind CSS, Vite, Storybook, Vitest, TypeScript, Bun workspaces
---
### Task 1: Create monorepo directory structure
**Files:**
- Create: `packages/library/` directory
- Create: `packages/playground/` directory
- [ ] **Step 1: Create packages directories**
```bash
mkdir -p packages/library/src/{components,composables}
mkdir -p packages/library/__tests__
mkdir -p packages/playground/.storybook
mkdir -p packages/playground/stories
```
- [ ] **Step 2: Verify structure**
```bash
ls -la packages/
# Expected output shows: library/ and playground/ directories
```
---
### Task 2: Move existing source to library package
**Files:**
- Move: `src/``packages/library/src/`
- Delete: `vite.config.ts`, `vitest.config.ts`, `index.html` (will recreate in packages)
- [ ] **Step 1: Move src directory**
```bash
mv src packages/library/src
```
- [ ] **Step 2: Verify move**
```bash
ls packages/library/src/
# Expected: App.vue, main.ts, __tests__/
```
- [ ] **Step 3: Delete old root-level config files**
```bash
rm vite.config.ts vitest.config.ts index.html
```
---
### Task 3: Update root package.json for monorepo
**Files:**
- Modify: `package.json`
- [ ] **Step 1: Add workspaces and update root config**
Replace the entire `package.json` with:
```json
{
"name": "nychthemeron",
"version": "0.0.0",
"private": true,
"type": "module",
"workspaces": [
"packages/*"
],
"scripts": {
"dev": "bun -r --cwd packages/playground dev",
"build": "bun -r --cwd packages/library build && bun -r --cwd packages/playground build",
"preview": "bun -r --cwd packages/playground preview",
"type-check": "vue-tsc --build",
"lint": "eslint packages/*/src --fix",
"format": "prettier --write --experimental-cli packages/*/src",
"test": "bun -r --cwd packages/library test:unit"
},
"devDependencies": {
"@tsconfig/node24": "^24.0.4",
"@types/jsdom": "^28.0.1",
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.6",
"@vitest/eslint-plugin": "^1.6.16",
"@vue/eslint-config-typescript": "^14.7.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.9.1",
"eslint": "^10.2.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-oxlint": "~1.60.0",
"eslint-plugin-vue": "~10.8.0",
"jiti": "^2.6.1",
"jsdom": "^29.0.2",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.60.0",
"prettier": "3.8.3",
"typescript": "~6.0.0",
"vite": "^8.0.8",
"vite-plugin-vue-devtools": "^8.1.1",
"vitest": "^4.1.4",
"vue": "^3.5.32",
"vue-tsc": "^3.2.6"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
}
```
- [ ] **Step 2: Install dependencies for new workspace structure**
```bash
bun install
```
Expected output: Bun detects workspaces and creates symlinks in each `packages/*/node_modules`
---
### Task 4: Create library package.json
**Files:**
- Create: `packages/library/package.json`
- [ ] **Step 1: Create library package config**
```json
{
"name": "@nychthemeron/library",
"version": "0.1.0",
"type": "module",
"exports": {
".": {
"import": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "vite build",
"test:unit": "vitest"
},
"peerDependencies": {
"vue": "^3.5.0",
"primevue": "^18.0.0"
},
"devDependencies": {
"@vitest/coverage-v8": "^2.0.0",
"@vue/test-utils": "^2.4.6"
}
}
```
Write this to `packages/library/package.json`
- [ ] **Step 2: Verify file exists**
```bash
cat packages/library/package.json | grep '"name"'
# Expected: "@nychthemeron/library"
```
---
### Task 5: Create library tsconfig.json
**Files:**
- Create: `packages/library/tsconfig.json`
- [ ] **Step 1: Create library TypeScript config**
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": false
},
"include": [
"src/**/*.ts",
"src/**/*.vue"
],
"exclude": [
"dist",
"**/*.spec.ts"
]
}
```
Write this to `packages/library/tsconfig.json`
---
### Task 6: Create library vite.config.ts
**Files:**
- Create: `packages/library/vite.config.ts`
- [ ] **Step 1: Create Vite library config**
```typescript
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
name: 'NychthemeronLibrary',
formats: ['es'],
fileName: () => 'index.js',
},
rollupOptions: {
external: ['vue', 'primevue'],
output: {
globals: {
vue: 'Vue',
primevue: 'PrimeVue',
},
},
},
},
})
```
Write this to `packages/library/vite.config.ts`
---
### Task 7: Create library vitest.config.ts
**Files:**
- Create: `packages/library/vitest.config.ts`
- [ ] **Step 1: Create Vitest config**
```typescript
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom',
include: ['**/*.spec.ts'],
},
})
```
Write this to `packages/library/vitest.config.ts`
---
### Task 8: Create library export structure
**Files:**
- Create: `packages/library/src/components/index.ts`
- Create: `packages/library/src/composables/index.ts`
- Modify: `packages/library/src/index.ts`
- [ ] **Step 1: Create components index (empty for now)**
```typescript
// Re-export all components here as they're added
// export { default as Button } from './Button/Button.vue'
// export { default as DatePicker } from './DatePicker/DatePicker.vue'
```
Write this to `packages/library/src/components/index.ts`
- [ ] **Step 2: Create composables index (empty for now)**
```typescript
// Re-export all composables here as they're added
// export { useDate } from './useDate'
```
Write this to `packages/library/src/composables/index.ts`
- [ ] **Step 3: Create root library entry point**
```typescript
export * from './components'
export * from './composables'
```
Write this to `packages/library/src/index.ts`
- [ ] **Step 4: Remove old App.vue and main.ts from library (they were for the app)**
```bash
rm packages/library/src/App.vue packages/library/src/main.ts
```
---
### Task 9: Create playground package.json
**Files:**
- Create: `packages/playground/package.json`
- [ ] **Step 1: Create playground package config**
```json
{
"name": "@nychthemeron/playground",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "storybook dev -p 6006",
"build": "storybook build",
"preview": "npx vite preview"
},
"dependencies": {
"@nychthemeron/library": "workspace:*",
"primevue": "^18.0.0",
"vue": "^3.5.32"
},
"devDependencies": {
"@storybook/addon-essentials": "^8.0.0",
"@storybook/addon-interactions": "^8.0.0",
"@storybook/addon-links": "^8.0.0",
"@storybook/blocks": "^8.0.0",
"@storybook/vue3": "^8.0.0",
"@storybook/vue3-vite": "^8.0.0",
"storybook": "^8.0.0",
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"typescript": "~6.0.0",
"vue": "^3.5.32"
}
}
```
Write this to `packages/playground/package.json`
---
### Task 10: Create playground tsconfig.json
**Files:**
- Create: `packages/playground/tsconfig.json`
- [ ] **Step 1: Create playground TypeScript config**
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": false,
"jsx": "preserve",
"lib": [
"dom",
"dom.iterable",
"esnext"
]
},
"include": [
".storybook/**/*.ts",
".storybook/**/*.tsx",
".storybook/**/*.vue",
"stories/**/*.ts",
"stories/**/*.tsx",
"stories/**/*.vue"
]
}
```
Write this to `packages/playground/tsconfig.json`
---
### Task 11: Create playground Tailwind config
**Files:**
- Create: `packages/playground/tailwind.config.ts`
- [ ] **Step 1: Create Tailwind config**
```typescript
import type { Config } from 'tailwindcss'
export default {
content: [
'./.storybook/**/*.{ts,tsx,vue}',
'./stories/**/*.{ts,tsx,vue}',
'../library/src/**/*.vue',
],
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
500: '#0ea5e9',
900: '#0c2d6b',
},
},
},
},
plugins: [],
} satisfies Config
```
Write this to `packages/playground/tailwind.config.ts`
---
### Task 12: Create Storybook main config
**Files:**
- Create: `packages/playground/.storybook/main.ts`
- [ ] **Step 1: Create Storybook main configuration**
```typescript
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
stories: [
'../stories/**/*.stories.ts',
],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-links',
],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
core: {
disableTelemetry: true,
},
}
export default config
```
Write this to `packages/playground/.storybook/main.ts`
---
### Task 13: Create Storybook preview config
**Files:**
- Create: `packages/playground/.storybook/preview.ts`
- [ ] **Step 1: Create Storybook preview configuration**
```typescript
import type { Preview } from '@storybook/vue3'
import 'primevue/resources/themes/lara-light-blue/theme.css'
import 'primevue/resources/primevue.min.css'
import '../tailwind.css'
const preview: Preview = {
parameters: {
layout: 'centered',
docs: {
description: {
component: 'Component documentation',
},
},
},
}
export default preview
```
Write this to `packages/playground/.storybook/preview.ts`
---
### Task 14: Create Tailwind CSS entry point
**Files:**
- Create: `packages/playground/tailwind.css`
- [ ] **Step 1: Create Tailwind directives file**
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Write this to `packages/playground/tailwind.css`
---
### Task 15: Create example story
**Files:**
- Create: `packages/playground/stories/Example.stories.ts`
- [ ] **Step 1: Create example story for testing**
```typescript
import type { Meta, StoryObj } from '@storybook/vue3'
const meta = {
title: 'Example/Welcome',
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Welcome: Story = {
render: () => ({
template: `
<div class="p-8 text-center">
<h1 class="text-3xl font-bold text-primary-500 mb-4">
Nychthemeron Component Library
</h1>
<p class="text-gray-600">
Interactive playground for Vue components with PrimeVue + Tailwind
</p>
</div>
`,
}),
}
```
Write this to `packages/playground/stories/Example.stories.ts`
---
### Task 16: Install dependencies and verify structure
**Files:**
- None (verification only)
- [ ] **Step 1: Install dependencies for all packages**
```bash
bun install
```
Expected: All packages get dependencies installed, workspace links created
- [ ] **Step 2: Verify workspace structure**
```bash
ls -la packages/library/node_modules/@nychthemeron/library
# Expected: symlink to ../..
ls -la packages/playground/node_modules/@nychthemeron/library
# Expected: symlink to ../../../library
```
- [ ] **Step 3: Type-check across monorepo**
```bash
bun type-check
```
Expected: No TypeScript errors
---
### Task 17: Test dev server startup
**Files:**
- None (verification only)
- [ ] **Step 1: Start Storybook playground**
```bash
bun dev
```
Expected output:
```
Storybook 8.x started
Local: http://localhost:6006
```
- [ ] **Step 2: Verify Storybook loads in browser**
Open `http://localhost:6006` in a browser.
Expected: Storybook UI loads with "Example/Welcome" story visible showing the welcome message.
- [ ] **Step 3: Verify story renders correctly**
In Storybook, click "Example/Welcome" → verify the welcome message displays with correct styling.
Expected:
- Title: "Nychthemeron Component Library" (blue, large)
- Subtitle: "Interactive playground..." (gray, smaller)
- [ ] **Step 4: Stop dev server**
```bash
# Press Ctrl+C in terminal
```
---
### Task 18: Test library build
**Files:**
- None (verification only)
- [ ] **Step 1: Build library**
```bash
bun -r --cwd packages/library build
```
Expected output:
```
✓ 123 modules transformed
dist/index.js 1.23 kb
dist/index.d.ts 0.5 kb
```
- [ ] **Step 2: Verify dist files exist**
```bash
ls packages/library/dist/
```
Expected:
```
index.d.ts
index.js
```
- [ ] **Step 3: Verify exports are correct**
```bash
head -20 packages/library/dist/index.js
```
Expected: JavaScript code with ES module exports (starts with `import` or defines exports)
---
### Task 19: Initialize git for monorepo
**Files:**
- None (git initialization only)
- [ ] **Step 1: Initialize git repo (if not already)**
```bash
git init
```
- [ ] **Step 2: Add gitignore entries for workspace**
Append to `.gitignore`:
```
packages/*/dist
packages/*/node_modules
packages/*/.storybook-static
```
- [ ] **Step 3: Create initial commit (SKIP - user requested no commits)**
Note: Commit skipped per user request
---
## Self-Review
**Spec coverage:**
- ✅ Monorepo structure (Task 1)
- ✅ Bun workspaces setup (Tasks 2-3)
- ✅ Library package creation (Tasks 4-8)
- ✅ Composables structure (Task 8)
- ✅ Playground/Storybook setup (Tasks 9-15)
- ✅ Tailwind integration (Tasks 11, 14)
- ✅ PrimeVue initialization (Task 13)
- ✅ Dev workflow verification (Task 17)
- ✅ Build verification (Task 18)
- ✅ Git initialization (Task 19)
**Placeholder scan:** No TBDs, "fill in later", incomplete steps. All code blocks are complete and exact.
**Type consistency:** Package names consistent (`@nychthemeron/library`, `@nychthemeron/playground`). All import paths and config keys match across tasks.
**Scope:** Plan produces working, testable monorepo. Library builds to `dist/`, Storybook starts and loads stories. Next phase (adding components) is separate.

View file

@ -0,0 +1,284 @@
# Library with Playground Design
**Date:** 2026-05-11
**Project:** nychthemeron
**Status:** Approved
## Overview
Transform nychthemeron from a single Vue app into a monorepo containing a Vue component library built on PrimeVue with Tailwind CSS styling, accompanied by a Storybook playground for interactive documentation and development.
## Goals
- Build a reusable Vue component library with custom Tailwind-based styling on PrimeVue
- Provide interactive documentation via Storybook for developers and stakeholders
- Enable fast, hot-reload development workflow
- Support both components and composables (e.g., date utilities for DatePicker)
- Keep internal/private — no npm publishing required
## Architecture
### Monorepo Structure
```
nychthemeron/
├── packages/
│ ├── library/ # Vue component library
│ │ ├── src/
│ │ │ ├── components/ # Vue components (DatePicker, Button, etc.)
│ │ │ ├── composables/ # Reusable logic (useDate, etc.)
│ │ │ └── index.ts # Barrel export for all exports
│ │ ├── __tests__/ # Unit tests
│ │ ├── package.json # Library metadata
│ │ ├── tsconfig.json # Library TS config
│ │ └── vite.config.ts # Library build config
│ │
│ └── playground/ # Storybook playground
│ ├── .storybook/
│ │ ├── main.ts # Storybook configuration
│ │ └── preview.ts # Global setup (Tailwind, PrimeVue)
│ ├── stories/ # Component stories (.stories.ts)
│ ├── package.json # Playground metadata
│ ├── tsconfig.json # Storybook TS config
│ └── tailwind.config.ts # Shared Tailwind config
├── package.json # Root workspace config (bun workspaces)
├── tsconfig.json # Shared base TS config
├── eslint.config.ts # Shared ESLint config
├── prettier.config.json # Shared Prettier config
└── docs/ # Documentation
```
### Dependency Model
- **Root:** Shared dev dependencies (TypeScript, ESLint, Prettier, bun workspace config)
- **Library:** Declares PrimeVue, Vue 3 as peer dependencies; exports components + composables
- **Playground:** Imports `@nychthemeron/library` and PrimeVue; bundles Storybook
### Technology Stack
| Layer | Technology |
|-------|-----------|
| Framework | Vue 3 |
| Component Base | PrimeVue |
| Styling | Tailwind CSS + custom CSS layer |
| Build (Library) | Vite (library mode) |
| Build (Playground) | Vite + Storybook |
| Testing | Vitest + Vue Test Utils |
| Package Manager | Bun (with workspaces) |
| Type Checking | TypeScript + vue-tsc |
## Library Package Details
### Export Model
**index.ts** exports all components and composables:
```typescript
export * from './components'
export * from './composables'
```
Consumers import as:
```typescript
import { DatePicker, Button } from '@nychthemeron/library'
import { useDate } from '@nychthemeron/library'
```
### Component Structure
Each component lives in its own folder:
```
src/components/
├── DatePicker/
│ ├── DatePicker.vue
│ └── DatePicker.spec.ts
├── Button/
│ ├── Button.vue
│ └── Button.spec.ts
└── index.ts
```
### Composables Structure
Reusable logic in `src/composables/`:
```
src/composables/
├── useDate.ts # Date utilities (parsing, formatting, etc.)
├── index.ts
```
Components use composables internally; consumers can also import them directly.
### Tailwind Integration
- `tailwind.config.ts` extends PrimeVue theme with custom utilities
- Library does NOT bundle Tailwind — consumers must include it in their setup
- Supports portability: teams can apply custom Tailwind themes independently
### Build Output
- ESM modules (primary) + optional CommonJS
- TypeScript declarations (`.d.ts`)
- Sourcemaps for debugging
- No dependency bundling (peer deps only)
## Playground Package Details
### Storybook Configuration
- **Addon:** Vue 3 builder
- **Global Setup:** `preview.ts` configures:
- Tailwind CSS
- PrimeVue theme setup
- Global styles
- **Stories Location:** `stories/` directory
### Story Structure
Each component gets a `.stories.ts` file:
```typescript
// stories/DatePicker.stories.ts
import DatePicker from '../components/DatePicker.vue'
export default {
component: DatePicker,
tags: ['autodocs'],
}
export const Default = {}
export const WithCustomTheme = { args: { /* ... */ } }
```
Stories serve triple duty:
1. Interactive documentation for developers
2. Visual regression baseline
3. Live testing environment during dev
### Preview Configuration
`preview.ts` sets up:
- Tailwind CSS
- PrimeVue initialization
- Global CSS imports
- Example theme configuration
## Development Workflow
### Scripts
**Root-level (`bun` commands):**
```json
{
"dev": "bun -r --cwd packages/playground dev",
"build": "bun -r --cwd packages/library build && bun -r --cwd packages/playground build",
"lint": "eslint packages/*/src",
"type-check": "vue-tsc --build",
"test": "bun -r --cwd packages/library test:unit"
}
```
**Library scripts:**
- `bun dev` — (unused for lib, optional for local testing)
- `bun build` — Vite library build
- `bun test:unit` — Vitest
**Playground scripts:**
- `bun dev` — Start Storybook dev server (port 6006)
- `bun build` — Build static Storybook site
### Developer Experience
1. **Start playground:** `bun dev` (root) → Storybook on `localhost:6006`
2. **Edit component:** Modify `packages/library/src/components/DatePicker/DatePicker.vue`
3. **See update:** Playground hot-reloads via Storybook
4. **View story:** Open `stories/DatePicker.stories.ts` to add/update examples
5. **Test:** Run `bun test` in library package
### Hot Reload
- Library changes trigger Storybook rebuild
- Playground imports `@nychthemeron/library` from source (no build step during dev)
## Testing Strategy
### Unit Tests
- **Location:** `packages/library/__tests__/`
- **Framework:** Vitest + Vue Test Utils
- **Coverage:** Component logic, composables, edge cases
- **Command:** `bun test` (from library or root)
### Visual Documentation
- Storybook stories serve as visual baseline
- Can be extended with snapshot testing if needed
### Type Safety
- `vue-tsc --build` runs across monorepo
- CI/local checks ensure no type errors
## Build & Deployment
### Library Build
- **Output:** `packages/library/dist/`
- **Formats:** ESM (primary) + optional CommonJS
- **Declarations:** Full TypeScript support
- **Size:** Optimized with tree-shaking enabled
### Playground Build
- **Output:** `packages/playground/storybook-static/`
- **Deploy:** Can be served on internal server, wiki, or documentation site
- **Audience:** Internal team, stakeholders, designers
### Internal Distribution
No npm publishing. Teams use library by:
- Cloning/pulling monorepo
- Installing dependencies: `bun install`
- Importing components from `@nychthemeron/library`
## Dependencies
### Library Package
**Peer Dependencies:**
- `vue@^3.5.0`
- `primevue@^18.0.0` (or latest)
**Dev Dependencies:** (shared from root)
- TypeScript, Vite, ESLint, Prettier
### Playground Package
**Dependencies:**
- `@nychthemeron/library` (local workspace package)
- `primevue`
- `vue`
**Dev Dependencies:**
- Storybook (@storybook/vue3)
- Vite, TypeScript
## Success Criteria
- ✅ Monorepo structure set up with bun workspaces
- ✅ Library builds to `dist/` with exports
- ✅ Storybook starts with `bun dev` and hot-reloads on library changes
- ✅ Components use PrimeVue + Tailwind CSS
- ✅ Composables (e.g., `useDate`) are accessible
- ✅ TypeScript declarations present and correct
- ✅ Unit tests run and pass
- ✅ Shared config (ESLint, Prettier, tsconfig) enforced across packages
## Next Steps
1. Restructure project into monorepo layout
2. Set up bun workspaces in root `package.json`
3. Create library package with Vite lib config
4. Create playground package with Storybook
5. Add shared configurations
6. Implement first component (e.g., Button or DatePicker)
7. Create corresponding Storybook stories
8. Test hot-reload workflow

3
env.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
/// <reference types="vite/client" />
declare module '*.css' {}

32
eslint.config.ts Normal file
View file

@ -0,0 +1,32 @@
import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginVitest from '@vitest/eslint-plugin'
import pluginOxlint from 'eslint-plugin-oxlint'
import skipFormatting from 'eslint-config-prettier/flat'
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{vue,ts,mts,tsx}'],
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
...pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
{
...pluginVitest.configs.recommended,
files: ['src/**/__tests__/*'],
},
...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'),
skipFormatting,
)

47
package.json Normal file
View file

@ -0,0 +1,47 @@
{
"name": "nychthemeron",
"version": "0.0.0",
"private": true,
"type": "module",
"workspaces": [
"packages/*"
],
"scripts": {
"dev": "cd packages/playground && bun run dev",
"build": "cd packages/library && bun run build && cd ../playground && bun run build",
"preview": "cd packages/playground && bun run preview",
"type-check": "vue-tsc --build",
"lint": "eslint packages/*/src --fix",
"format": "prettier --write --experimental-cli packages/*/src",
"test": "cd packages/library && bun run test:unit"
},
"devDependencies": {
"@tsconfig/node24": "^24.0.4",
"@types/jsdom": "^28.0.1",
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.6",
"primevue": "^4.5.0",
"@vitest/eslint-plugin": "^1.6.16",
"@vue/eslint-config-typescript": "^14.7.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.9.1",
"eslint": "^10.2.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-oxlint": "~1.60.0",
"eslint-plugin-vue": "~10.8.0",
"jiti": "^2.6.1",
"jsdom": "^29.0.2",
"npm-run-all2": "^8.0.4",
"oxlint": "~1.60.0",
"prettier": "3.8.3",
"typescript": "~6.0.0",
"vite": "^8.0.8",
"vite-plugin-vue-devtools": "^8.1.1",
"vitest": "^4.1.4",
"vue": "^3.5.32",
"vue-tsc": "^3.2.6"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
}

View file

@ -0,0 +1,37 @@
{
"name": "@nychthemeron/library",
"version": "0.1.0",
"type": "module",
"exports": {
".": {
"import": "./dist/index.js"
},
"./components": {
"import": "./src/components/index.ts"
},
"./style": {
"import": "./src/assets/css/nychthemeron.css",
"types": "./src/index.d.ts"
},
"./theme": {
"import": "./src/assets/theme/theme.css",
"types": "./src/index.d.ts"
}
},
"files": [
"dist",
"src/assets"
],
"scripts": {
"build": "vite build",
"test:unit": "vitest"
},
"peerDependencies": {
"vue": "^3.5.0",
"primevue": "^4.5.0"
},
"devDependencies": {
"@vitest/coverage-v8": "^2.0.0",
"@vue/test-utils": "^2.4.6"
}
}

View file

@ -0,0 +1,149 @@
/* ============================================================
nychthemeron.css
Toggle themes by setting data-theme on <html>:
<html data-theme="hades">
<html data-theme="apollo">
============================================================ */
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600&family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500&display=swap');
/* ── HADES ───────────────────────────────────────────────── */
[data-theme='hades'] {
/* surfaces */
--surface-0: #18160f;
--surface-1: #28241a;
--surface-2: #2e2a1e;
--surface-3: #322e22;
--track: #32302a;
/* borders */
--border: #4a4538;
--border-hi: #5a5040;
--border-lo: #38342a;
/* primary */
--primary: #d4b278;
--primary-fg: #0e0c08;
/* semantic */
--warn: #e08840; /* phlegethon */
--warn-subtle: #281808;
--warn-border: #4a2c10;
--danger: #9ab4c8; /* styx */
--danger-subtle: #141a22;
--danger-border: #2a3848;
--info: #88aec8; /* cocytus */
--info-subtle: #101e2c;
--info-border: #1e3850;
--muted: #c8c0a8; /* lethe */
--muted-subtle: #201e18;
--muted-border: #403c30;
--neutral: #7a6e60; /* acheron */
--success: #6aaa7a; /* laurel */
--success-subtle: #0e2214;
--success-border: #1e4228;
/* text */
--text-high: #f4f0e8;
--text-body: #d0c8b8;
--text-muted: #908878;
--text-dim: #807868;
--text-label: #c0b8a8;
/* focus ring */
--focus-ring: 0 0 0 3px rgba(212, 178, 120, 0.18);
/* fonts */
--font-serif: 'Cinzel', Georgia, serif;
--font-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-mono: 'IBM Plex Mono', monospace;
}
/* ── APOLLO ──────────────────────────────────────────────── */
[data-theme='apollo'] {
/* surfaces */
--surface-0: #faf7f2;
--surface-1: #f4f0e8;
--surface-2: #eee9df;
--surface-3: #e4ddd0;
--track: #e4ddd0;
/* borders */
--border: #bab2a0;
--border-hi: #c0b8a8;
--border-lo: #ccc4b4;
/* primary */
--primary: #b8860b;
--primary-fg: #fff8e8;
/* semantic */
--warn: #9a6020; /* harmony */
--warn-subtle: #fdf3e0;
--warn-border: #e8d0a0;
--danger: #7a1a1a; /* plague */
--danger-subtle: #faeaea;
--danger-border: #c8a0a0;
--info: #3a5a8a; /* oracle */
--info-subtle: #e8eef8;
--info-border: #b0c4e0;
--muted: #a09080; /* lethe */
--muted-subtle: #ede8de;
--muted-border: #d4cdc0;
--neutral: #8a8070; /* marble */
--success: #5a7a40; /* laurel */
--success-subtle: #eef4e8;
--success-border: #c0d4a8;
/* text */
--text-high: #1a1610;
--text-body: #3a3020;
--text-muted: #5a5040;
--text-dim: #8a8070;
--text-label: #5a5040;
/* focus ring */
--focus-ring: 0 0 0 3px rgba(184, 134, 11, 0.12);
/* fonts */
--font-serif: 'Cinzel', Georgia, serif;
--font-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-mono: 'IBM Plex Mono', monospace;
}
/* ── BASE RESET ──────────────────────────────────────────── */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-family: var(--font-sans);
font-size: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
background-color: var(--surface-0);
color: var(--text-body);
min-height: 100vh;
}
*:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}

View file

@ -0,0 +1,6 @@
@import url('../css/nychthemeron.css');
.nych-button-primary {
background-color: var(--primary);
color: var(--primary-fg);
}

View file

@ -0,0 +1,41 @@
<script setup lang="ts">
import { Button } from 'primevue'
import { computed } from 'vue'
interface Props {
label?: string
variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
size?: 'small' | 'normal' | 'large'
disabled?: boolean
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
label: 'Button',
variant: 'primary',
size: 'normal',
disabled: false,
loading: false,
})
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
const buttonClass = computed(() => ({
[`nych-button-${props.variant}`]: true,
}))
</script>
<template>
<Button
unstyled
:label="label"
:class="buttonClass"
:disabled="disabled || loading"
:loading="loading"
@click="emit('click', $event)"
/>
</template>
<style lang="css"></style>

View file

@ -0,0 +1,3 @@
// Re-export all components here as they're added
export { default as NychButton } from './NychButton.vue'
// export { default as DatePicker } from './DatePicker/DatePicker.vue'

View file

@ -0,0 +1,2 @@
// Re-export all composables here as they're added
// export { useDate } from './useDate'

2
packages/library/src/index.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
declare module '@nychthemeron/library/style';
declare module '@nychthemeron/library/theme';

View file

@ -0,0 +1,2 @@
export * from '~/components'
//export * from '~/composables'

View file

@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import App from '~/App.vue'
describe('App', () => {
it('mounts renders properly', () => {
const wrapper = mount(App)
expect(wrapper.text()).toContain('You did it!')
})
})

View file

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": false,
"paths": {
"@/*": ["./src/*"],
"~/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.vue", "tests/**/*.ts"],
"exclude": ["dist"]
}

View file

@ -0,0 +1,29 @@
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'~': fileURLToPath(new URL('./src', import.meta.url)),
},
},
build: {
lib: {
entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
name: 'NychthemeronLibrary',
formats: ['es'],
fileName: () => 'index.js',
},
rollupOptions: {
external: ['vue', 'primevue'],
output: {
globals: {
vue: 'Vue',
primevue: 'PrimeVue',
},
},
},
},
})

View file

@ -0,0 +1,17 @@
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath } from 'node:url'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'~': fileURLToPath(new URL('./src', import.meta.url)),
},
},
test: {
globals: true,
environment: 'jsdom',
include: ['**/*.spec.ts'],
},
})

View file

@ -0,0 +1,25 @@
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
stories: ['../stories/**/*.stories.ts'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-links',
],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
core: {
disableTelemetry: true,
},
viteFinal: (config) => {
if (config.build) {
config.build.chunkSizeWarningLimit = 1000
}
return config
},
}
export default config

View file

@ -0,0 +1,54 @@
import type { Preview } from '@storybook/vue3'
import '@nychthemeron/library/theme'
const preview: Preview = {
parameters: {
layout: 'centered',
toolbar: {
theme: {
items: [
{ value: 'hades', title: 'Dark (Hades)', left: '🌙' },
{ value: 'apollo', title: 'Light (Apollo)', left: '☀️' },
],
dynamicTitle: true,
},
},
docs: {
description: {
component: 'Component documentation',
},
},
},
decorators: [
(story, context) => {
const theme = context.parameters.toolbar?.theme
? context.globals.theme || 'hades'
: 'hades'
return {
setup() {
document.documentElement.setAttribute('data-theme', theme)
},
components: { story },
template: '<story />',
}
},
],
globalTypes: {
theme: {
name: 'Theme',
description: 'Global theme for all components',
defaultValue: 'hades',
toolbar: {
icon: 'paintbrush',
items: [
{ value: 'hades', title: 'Dark (Hades)', left: '🌙' },
{ value: 'apollo', title: 'Light (Apollo)', left: '☀️' },
],
dynamicTitle: true,
},
},
},
}
export default preview

View file

@ -0,0 +1,26 @@
{
"name": "@nychthemeron/playground",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "storybook dev -p 6006",
"build": "storybook build",
"preview": "npx vite preview"
},
"dependencies": {
"@nychthemeron/library": "workspace:*",
"primevue": "^4.5.0",
"vue": "^3.5.32"
},
"devDependencies": {
"@storybook/addon-essentials": "^8.0.0",
"@storybook/addon-interactions": "^8.0.0",
"@storybook/addon-links": "^8.0.0",
"@storybook/blocks": "^8.0.0",
"@storybook/vue3": "^8.0.0",
"@storybook/vue3-vite": "^8.0.0",
"storybook": "^8.0.0",
"typescript": "~6.0.0",
"vue": "^3.5.32"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,134 @@
import type { Meta, StoryObj } from '@storybook/vue3'
import { ref } from 'vue'
import { NychButton } from '@nychthemeron/library/components'
const meta = {
title: 'Components/Button',
component: NychButton,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: { type: 'select' },
options: ['primary', 'secondary', 'success', 'warning', 'danger'],
description: 'Button variant style',
},
size: {
control: { type: 'select' },
options: ['small', 'normal', 'large'],
description: 'Button size',
},
disabled: {
control: { type: 'boolean' },
description: 'Disable the button',
},
loading: {
control: { type: 'boolean' },
description: 'Show loading state',
},
label: {
control: { type: 'text' },
description: 'Button label text',
},
},
} satisfies Meta<typeof NychButton>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {
label: 'Primary Button',
variant: 'primary',
},
}
export const Secondary: Story = {
args: {
label: 'Secondary Button',
variant: 'secondary',
},
}
export const Success: Story = {
args: {
label: 'Success Button',
variant: 'success',
},
}
export const Warning: Story = {
args: {
label: 'Warning Button',
variant: 'warning',
},
}
export const Danger: Story = {
args: {
label: 'Danger Button',
variant: 'danger',
},
}
export const Disabled: Story = {
args: {
label: 'Disabled Button',
disabled: true,
},
}
export const Loading: Story = {
args: {
label: 'Loading Button',
loading: true,
},
}
export const Sizes: Story = {
render: () => ({
components: { NychButton },
setup() {
return {}
},
template: `
<div class="flex gap-4 items-center">
<NychButton label="Small" size="small" />
<NychButton label="Normal" size="normal" />
<NychButton label="Large" size="large" />
</div>
`,
}),
}
export const Interactive: Story = {
render: () => ({
components: { NychButton },
setup() {
const clickCount = ref(0)
const isLoading = ref(false)
const handleClick = () => {
clickCount.value++
isLoading.value = true
setTimeout(() => {
isLoading.value = false
}, 1000)
}
return { clickCount, isLoading, handleClick }
},
template: `
<div class="text-center">
<NychButton
label="Click Me"
@click="handleClick"
:loading="isLoading"
/>
<p class="mt-4 text-lg">Clicks: {{ clickCount }}</p>
</div>
`,
}),
}

View file

@ -0,0 +1,27 @@
import type { Meta, StoryObj } from '@storybook/vue3'
const meta = {
title: 'Example/Welcome',
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Welcome: Story = {
render: () => ({
template: `
<div class="p-8 text-center">
<h1 class="text-3xl font-bold text-primary-500 mb-4">
Nychthemeron Component Library
</h1>
<p class="text-gray-600">
Interactive playground for Vue components with PrimeVue + Tailwind
</p>
</div>
`,
}),
}

View file

@ -0,0 +1,22 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": false,
"jsx": "preserve",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"rootDir": "."
},
"include": [
"../../env.d.ts",
".storybook/**/*.ts",
".storybook/**/*.tsx",
".storybook/**/*.vue",
"stories/**/*.ts",
"stories/**/*.tsx",
"stories/**/*.vue"
]
}

View file

@ -0,0 +1,12 @@
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'~': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})

8
tsconfig.json Normal file
View file

@ -0,0 +1,8 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

27
tsconfig.node.json Normal file
View file

@ -0,0 +1,27 @@
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
{
"extends": "@tsconfig/node24/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
// Most tools use transpilation instead of Node.js's native type-stripping.
// Bundler mode provides a smoother developer experience.
"module": "preserve",
"moduleResolution": "bundler",
// Include Node.js types and avoid accidentally including other `@types/*` packages.
"types": ["node"],
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
"noEmit": true,
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
// Specified here to keep it out of the root directory.
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
}
}