diff --git a/README.md b/README.md
index a15823f..8fa36d8 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
# 🐺 howl
-Howl is a fork of `werewolf-ui`, without any Tailwind. A minimal, zero-dependency React component library.
+Howl is a fork of `werewolf-ui`, without any Tailwind.
+
+A minimal, single-dependency component library built on forge
+for hype/hono.
## Installation
diff --git a/TAGS.md b/TAGS.md
new file mode 100644
index 0000000..aed257f
--- /dev/null
+++ b/TAGS.md
@@ -0,0 +1,556 @@
+# HOWL Component Library - Complete Tags Reference
+
+Built with [Forge](https://github.com/user/forge) - a CSS-in-JS framework with theme support.
+
+## Quick Start
+
+```tsx
+import { Styles, Button, VStack } from 'howl'
+
+// Include in your document head to inject CSS
+
+
+
+
+
+
+
+
+
+
+```
+
+## Theming
+
+Howl includes light and dark themes. Set `data-theme="light"` or `data-theme="dark"` on your root element.
+
+### Customizing the Theme
+
+```tsx
+import { extendThemes } from 'howl'
+
+extendThemes({
+ light: { 'colors-primary': '#8b5cf6' },
+ dark: { 'colors-primary': '#a78bfa' }
+})
+```
+
+---
+
+## Table of Contents
+- [Layout Components](#layout-components)
+ - [VStack](#vstack)
+ - [HStack](#hstack)
+ - [Grid](#grid)
+ - [Section](#section)
+- [Container Components](#container-components)
+ - [Box](#box)
+- [Typography Components](#typography-components)
+ - [H1, H2, H3, H4, H5](#h1-h2-h3-h4-h5)
+ - [Text](#text)
+ - [SmallText](#smalltext)
+- [Interactive Components](#interactive-components)
+ - [Button](#button)
+ - [Input](#input)
+ - [Select](#select)
+- [Media Components](#media-components)
+ - [Image](#image)
+ - [Avatar](#avatar)
+ - [Icon](#icon)
+ - [IconLink](#iconlink)
+- [Utility Components](#utility-components)
+ - [Divider](#divider)
+ - [Placeholder](#placeholder)
+- [Type Definitions](#type-definitions)
+
+---
+
+## Layout Components
+
+### VStack
+
+Vertical stack layout component using flexbox or CSS Grid for flexible vertical layouts.
+
+**Props:**
+- `v?: "start" | "center" | "end" | "between" | "around" | "evenly"` - Main axis alignment (vertical)
+- `h?: "start" | "center" | "end" | "stretch" | "baseline"` - Cross axis alignment (horizontal)
+- `gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 12` - Gap between items (multiplied by 4px)
+- `rows?: number[]` - Custom row sizing fractions (e.g., [2, 1] for 2/3 and 1/3 height)
+- `wrap?: boolean` - Enable flex-wrap
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Child elements
+
+**Examples:**
+```tsx
+Content
+Content
+Content
+```
+
+---
+
+### HStack
+
+Horizontal stack layout component using flexbox or CSS Grid for flexible horizontal layouts.
+
+**Props:**
+- `h?: "start" | "center" | "end" | "between" | "around" | "evenly"` - Main axis alignment (horizontal)
+- `v?: "start" | "center" | "end" | "stretch" | "baseline"` - Cross axis alignment (vertical)
+- `gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 12` - Gap between items (multiplied by 4px)
+- `cols?: number[]` - Custom column sizing fractions (e.g., [7, 3] for 70% and 30% width)
+- `wrap?: boolean` - Enable flex-wrap
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Child elements
+
+**Examples:**
+```tsx
+Content
+Content
+Content
+```
+
+---
+
+### Grid
+
+CSS Grid component for creating multi-column layouts.
+
+**Props:**
+- `cols?: 1 | 2 | 3 | 4 | 5 | 6 | 7` - Number of columns. Default: 2
+- `gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 12` - Gap between items (multiplied by 4px). Default: 4
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Child elements
+
+**Examples:**
+```tsx
+Items
+Items
+```
+
+---
+
+### Section
+
+Wrapper component that adds padding and vertical stacking with gap control.
+
+**Props:**
+- `gap?: 0 | 1 | 2 | 3 | 4 | 6 | 8 | 12` - Gap between children (multiplied by 4px). Default: 8
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Child elements
+
+**Examples:**
+```tsx
+Content
+Content
+```
+
+---
+
+## Container Components
+
+### Box
+
+Generic container component. Use color variants for common patterns.
+
+**Variants:**
+- `Box` - Plain container (no default styles)
+- `RedBox` - Red background, white text, 4px padding
+- `GreenBox` - Green background, white text, 4px padding
+- `BlueBox` - Blue background, white text, 4px padding
+- `GrayBox` - Gray background, 16px padding
+
+**Props:**
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Child elements
+
+**Examples:**
+```tsx
+Content
+Red content
+Blue content
+```
+
+---
+
+## Typography Components
+
+### H1, H2, H3, H4, H5
+
+Heading components with preset font sizes and weights.
+
+**Props (all heading components):**
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Child elements
+
+**Sizing:**
+- `H1` - 24px, bold
+- `H2` - 20px, bold
+- `H3` - 18px, semibold (600)
+- `H4` - 16px, semibold (600)
+- `H5` - 14px, medium (500)
+
+**Examples:**
+```tsx
+
Heading 1
+
Heading 2
+
Heading 3
+```
+
+---
+
+### Text
+
+Standard paragraph text component.
+
+**Props:**
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Child elements
+
+**Sizing:** 14px
+
+**Examples:**
+```tsx
+Regular text
+```
+
+---
+
+### SmallText
+
+Small paragraph text component.
+
+**Props:** Same as Text
+
+**Sizing:** 12px
+
+**Examples:**
+```tsx
+Small text
+```
+
+---
+
+## Interactive Components
+
+### Button
+
+Clickable button component with multiple variants and sizes.
+
+**Props (extends HTML button attributes):**
+- `variant?: "primary" | "secondary" | "outline" | "ghost" | "destructive"` - Button style. Default: "primary"
+- `size?: "sm" | "md" | "lg"` - Button size. Default: "md"
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- Plus all native button attributes (onClick, disabled, type, etc.)
+
+**Variants:**
+- `primary` - Primary color background, white text
+- `secondary` - Secondary color background, white text
+- `outline` - Transparent background, border
+- `ghost` - Transparent background, no border
+- `destructive` - Destructive color background, white text
+
+**Sizes:**
+- `sm` - 32px height
+- `md` - 40px height
+- `lg` - 48px height
+
+**Examples:**
+```tsx
+
+
+
+
+
+```
+
+---
+
+### Input
+
+Text input component with optional label positioning.
+
+**Props (extends HTML input attributes):**
+- `labelPosition?: "above" | "left" | "right"` - Label position relative to input. Default: "above"
+- `children?: any` - Label text (passed as children)
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- Plus all native input attributes (type, placeholder, value, disabled, etc.)
+
+**Examples:**
+```tsx
+
+
+Label
+Name
+Label
+```
+
+---
+
+### Select
+
+Dropdown select component with optional label positioning.
+
+**Props:**
+- `options: SelectOption[]` - Array of options (required). SelectOption = { value: string, label: string, disabled?: boolean }
+- `placeholder?: string` - Placeholder text
+- `labelPosition?: "above" | "left" | "right"` - Label position relative to select. Default: "above"
+- `children?: any` - Label text (passed as children)
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- Plus all native select attributes (value, onChange, disabled, etc.)
+
+**Examples:**
+```tsx
+
+
+
+
+```
+
+---
+
+## Media Components
+
+### Image
+
+Image component with object-fit support.
+
+**Props:**
+- `src: string` - Image URL (required)
+- `alt?: string` - Alt text for accessibility
+- `width?: number` - Width in pixels
+- `height?: number` - Height in pixels
+- `objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down"` - CSS object-fit property
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+
+**Examples:**
+```tsx
+
+
+
+```
+
+---
+
+### Avatar
+
+Avatar image component with size and rounded variants.
+
+**Props:**
+- `src: string` - Image URL (required)
+- `alt?: string` - Alt text for accessibility
+- `size?: 24 | 32 | 48 | 64 | 96 | 128` - Avatar size in pixels. Default: 32
+- `rounded?: boolean` - Make avatar circular
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+
+**Examples:**
+```tsx
+
+
+
+```
+
+---
+
+### Icon
+
+Icon component using Lucide static SVG icons.
+
+**Props:**
+- `name: IconName` - Icon name from Lucide (required). Available: Heart, Star, Home, ExternalLink, Mail, Phone, Download, Settings, Shield, Sun, Zap, and many more
+- `size?: 4 | 5 | 6 | 8 | 10 | 12` - Icon size in Tailwind units (multiplied by 4px). Default: 6 (24px)
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+
+**Examples:**
+```tsx
+
+
+
+```
+
+---
+
+### IconLink
+
+Icon wrapped in an anchor link.
+
+**Props (extends Icon props):**
+- All Icon props plus:
+- `href?: string` - Link URL. Default: "#"
+- `target?: string` - Link target (e.g., "_blank")
+
+**Examples:**
+```tsx
+
+
+
+```
+
+---
+
+## Utility Components
+
+### Divider
+
+Horizontal divider line with optional text.
+
+**Props:**
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `children?: any` - Text to display on the line
+
+**Examples:**
+```tsx
+
+OR
+```
+
+---
+
+### Placeholder
+
+Object containing placeholder generator utilities for Avatar and Image components.
+
+#### Placeholder.Avatar
+
+Generates placeholder avatars using DiceBear API.
+
+**Props:**
+- `seed?: string` - Seed for consistent avatar generation. Default: "seed"
+- `type?: DicebearStyleName` - Avatar style. Default: "dylan"
+- `size?: number` - Avatar size in pixels. Default: 32
+- `transparent?: boolean` - Use transparent background
+- `rounded?: boolean` - Make circular
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+- `alt?: string` - Alt text
+
+**Available DiceBear Styles:**
+adventurer, adventurer-neutral, avataaars, avataaars-neutral, big-ears, big-ears-neutral, big-smile, bottts, bottts-neutral, croodles, croodles-neutral, dylan, fun-emoji, glass, icons, identicon, initials, lorelei, lorelei-neutral, micah, miniavs, notionists, notionists-neutral, open-peeps, personas, pixel-art, pixel-art-neutral, rings, shapes, thumbs
+
+**Examples:**
+```tsx
+
+
+
+```
+
+#### Placeholder.Image
+
+Generates placeholder images using Picsum Photos API.
+
+**Props:**
+- `width?: number` - Image width in pixels. Default: 200
+- `height?: number` - Image height in pixels. Default: 200
+- `seed?: number` - Seed for consistent image generation. Default: 1
+- `alt?: string` - Alt text. Default: "Placeholder image"
+- `objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down"` - CSS object-fit
+- `class?: string` - CSS class name
+- `style?: JSX.CSSProperties` - Inline styles
+- `id?: string` - HTML id attribute
+- `ref?: any` - React ref
+
+**Examples:**
+```tsx
+
+
+
+```
+
+---
+
+## Type Definitions
+
+### SelectOption
+
+Type for Select component options:
+```tsx
+{
+ value: string // Option value
+ label: string // Display text
+ disabled?: boolean // Disable option
+}
+```
+
+---
+
+## Export Summary
+
+```tsx
+import {
+ // Utilities
+ Styles, // CSS injection component - include in
+ theme, // Theme function for accessing CSS variables
+ extendThemes, // Override theme values globally
+ cn, // Class name helper
+
+ // Layout
+ VStack, HStack, Grid, Section,
+
+ // Container
+ Box, RedBox, GreenBox, BlueBox, GrayBox,
+
+ // Typography
+ H1, H2, H3, H4, H5, Text, SmallText,
+
+ // Interactive
+ Button, Input, Select,
+
+ // Media
+ Image, Avatar, Icon, IconLink,
+
+ // Utility
+ Divider, Placeholder,
+
+ // Types
+ type ButtonProps,
+ type ImageProps,
+ type AvatarProps,
+ type InputProps,
+ type SelectProps,
+ type SelectOption,
+ type IconName,
+} from 'howl'
+```
diff --git a/bun.lock b/bun.lock
index 9bf8c8f..fde4fbe 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,9 +1,11 @@
{
"lockfileVersion": 1,
+ "configVersion": 1,
"workspaces": {
"": {
"name": "howl",
"dependencies": {
+ "forge": "git+https://git.nose.space/defunkt/forge",
"hono": "^4.10.7",
"lucide-static": "^0.555.0",
},
@@ -16,13 +18,15 @@
},
},
"packages": {
- "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
+ "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
- "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
+ "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
- "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
+ "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
- "hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
+ "forge": ["forge@git+https://git.nose.space/defunkt/forge#e14821c6995e82088efdfe16e53021afe471ac83", { "dependencies": { "hono": "^4.11.3" }, "peerDependencies": { "typescript": "^5" } }, "e14821c6995e82088efdfe16e53021afe471ac83"],
+
+ "hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
"lucide-static": ["lucide-static@0.555.0", "", {}, "sha512-FMMaYYsEYsUA6xlEzIMoKEV3oGnxIIvAN+AtLmYXvlTJptJTveJjVBQwvtA/zZLrD6KLEu89G95dQYlhivw5jQ=="],
diff --git a/package.json b/package.json
index b0e2f96..a10c87b 100644
--- a/package.json
+++ b/package.json
@@ -7,13 +7,14 @@
"dev": "bun run --hot test/server.tsx"
},
"devDependencies": {
- "@types/bun": "latest"
+ "@types/bun": "latest",
+ "hono": "^4.10.7"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
- "hono": "^4.10.7",
+ "forge": "git+https://git.nose.space/defunkt/forge",
"lucide-static": "^0.555.0"
}
}
\ No newline at end of file
diff --git a/src/avatar.tsx b/src/avatar.tsx
index fd56c02..6bb0601 100644
--- a/src/avatar.tsx
+++ b/src/avatar.tsx
@@ -1,61 +1,63 @@
-import { Section } from "./section"
-import { H2, Text } from "./text"
-import "hono/jsx"
-import type { FC, JSX } from "hono/jsx"
-import { VStack, HStack } from "./stack"
-import { CodeExamples } from "./code"
-import { cn } from "./cn"
+import { define } from 'forge'
+import { theme } from './theme'
+import { Section } from './section'
+import { H2, Text } from './text'
+import { VStack, HStack } from './stack'
-export type AvatarProps = JSX.IntrinsicElements["img"] & {
- size?: number
- rounded?: boolean
-}
+export const Avatar = define('Avatar', {
+ base: 'img',
+ objectFit: 'cover',
-export const Avatar: FC = (props) => {
- const { src, size = 32, rounded, class: className, style, id, ref, alt = "", ...rest } = props
+ variants: {
+ size: {
+ 24: { width: 24, height: 24 },
+ 32: { width: 32, height: 32 },
+ 48: { width: 48, height: 48 },
+ 64: { width: 64, height: 64 },
+ 96: { width: 96, height: 96 },
+ },
+ rounded: {
+ borderRadius: theme('radius-full'),
+ },
+ },
+})
- const avatarStyle: JSX.CSSProperties = {
- width: `${size}px`,
- height: `${size}px`,
- borderRadius: rounded ? "9999px" : undefined,
- ...(style as JSX.CSSProperties),
- }
-
- return
-}
+export type AvatarProps = Parameters[0]
export const Test = () => {
const sampleImages = [
- "https://picsum.photos/seed/3/200/200",
- "https://picsum.photos/seed/2/200/200",
- "https://picsum.photos/seed/8/200/200",
- "https://picsum.photos/seed/9/200/200",
+ 'https://picsum.photos/seed/3/200/200',
+ 'https://picsum.photos/seed/2/200/200',
+ 'https://picsum.photos/seed/8/200/200',
+ 'https://picsum.photos/seed/9/200/200',
]
return (
- {/* API Usage Examples */}
- ',
- '',
- '',
- '',
- ]}
- />
-
{/* Size variations */}