README+theme
This commit is contained in:
parent
c561207128
commit
f643f8b2eb
64
README.md
64
README.md
|
|
@ -1,37 +1,32 @@
|
||||||
# Forge
|
# ⚒️ forge
|
||||||
|
|
||||||
## Why Forge?
|
```
|
||||||
|
╔═╝╔═║╔═║╔═╝╔═╝
|
||||||
|
╔═╝║ ║╔╔╝║ ║╔═╝
|
||||||
|
╝ ══╝╝ ╝══╝══╝
|
||||||
|
```
|
||||||
|
|
||||||
CSS is powerful, but hostile.
|
## overview
|
||||||
|
|
||||||
### Problems with CSS
|
Forge is a typed, local, variant-driven way to organize CSS, built around TSX.
|
||||||
|
|
||||||
- Styles are **global and open** — anything can override anything.
|
## css problems
|
||||||
- There’s **no link** between a class in markup and its definition.
|
|
||||||
- Inline styles exist because there’s **no structured way to vary styles per instance**.
|
|
||||||
- Overrides are silent — conflicts happen without feedback.
|
|
||||||
- Complex components require selector gymnastics and reach-in styling.
|
|
||||||
|
|
||||||
### What Forge Does Instead
|
- Styles are global and open - anything can override anything anywhere.
|
||||||
|
- No IDE-friendly link between the class name in markup and its definition.
|
||||||
|
- All techniques are patterns a human must know and follow, not APIs.
|
||||||
|
- Errors happen silently.
|
||||||
|
|
||||||
- Styles are **local to components** and attached by generated handles, not strings.
|
## forge solutions
|
||||||
- **Parts** give components named sub-targets without selectors.
|
|
||||||
- **Variants** replace inline styles with typed, declarative parameters.
|
|
||||||
- Style composition is **deterministic** (known merge order, last-wins).
|
|
||||||
- Overlapping changes are **warned about in dev**, not silently ignored.
|
|
||||||
|
|
||||||
### What Forge Is
|
- All styles are local to your TSX components.
|
||||||
|
- Styles defined using TS typing.
|
||||||
|
- Component styles are made up of independently styled "Parts".
|
||||||
|
- "Variants" replace inline styles with typed, declarative parameters.
|
||||||
|
- Style composition is deterministic.
|
||||||
|
- Errors and feedback.
|
||||||
|
|
||||||
- A typed, local, variant-driven way to author CSS.
|
## examples
|
||||||
- A system that optimizes for **people typing at a keyboard**, not selectors in a cascade.
|
|
||||||
|
|
||||||
### What Forge Is Not
|
|
||||||
|
|
||||||
- Not a new component model.
|
|
||||||
- Not a new language.
|
|
||||||
- Not a CSS replacement — it compiles _to_ CSS, but removes the chaos.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { define } from "forge"
|
import { define } from "forge"
|
||||||
|
|
@ -43,7 +38,7 @@ export const Button = define("button", {
|
||||||
background: "blue",
|
background: "blue",
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
kind: {
|
status: {
|
||||||
danger: { background: "red" },
|
danger: { background: "red" },
|
||||||
warning: { background: "yellow" },
|
warning: { background: "yellow" },
|
||||||
}
|
}
|
||||||
|
|
@ -52,8 +47,8 @@ export const Button = define("button", {
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
<Button>Click me</Button>
|
<Button>Click me</Button>
|
||||||
<Button kind="danger">Click me carefully</Button>
|
<Button status="danger">Click me carefully</Button>
|
||||||
<Button kind="warning">Click me?</Button>
|
<Button status="warning">Click me?</Button>
|
||||||
|
|
||||||
export const Profile = define("div", {
|
export const Profile = define("div", {
|
||||||
padding: 50,
|
padding: 50,
|
||||||
|
|
@ -90,3 +85,14 @@ import { Profile } from './whatever'
|
||||||
|
|
||||||
<Profile pic={user.pic} bio={user.bio} />
|
<Profile pic={user.pic} bio={user.bio} />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## see it
|
||||||
|
|
||||||
|
Check out the `examples/` dir and view them at http://localhost:3300 by
|
||||||
|
cloning this repo and running the local web server:
|
||||||
|
|
||||||
|
```
|
||||||
|
bun install
|
||||||
|
bun dev
|
||||||
|
open http://localhost:3300
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,88 @@
|
||||||
import { createScope } from '../src'
|
import { createScope } from '../src'
|
||||||
import { ExampleSection } from './ssr/helpers'
|
import { ExampleSection, theme } from './ssr/helpers'
|
||||||
|
|
||||||
const { define } = createScope('Button')
|
const { define } = createScope('Button')
|
||||||
|
|
||||||
const Button = define('Root', {
|
const Button = define('Root', {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
|
|
||||||
padding: "12px 24px",
|
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
gap: 8,
|
gap: theme.spacing.xs,
|
||||||
background: "#3b82f6",
|
background: theme.colors.accent,
|
||||||
color: "white",
|
color: theme.colors.bg,
|
||||||
border: "none",
|
border: `1px solid ${theme.colors.accent}`,
|
||||||
borderRadius: 8,
|
borderRadius: theme.radius.sm,
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
fontWeight: 600,
|
fontWeight: 400,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
boxShadow: "0 4px 6px rgba(59, 130, 246, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
|
|
||||||
transform: "translateY(0)",
|
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
":not(:disabled):hover": {
|
":not(:disabled):hover": {
|
||||||
transform: 'translateY(-2px)',
|
background: theme.colors.accentDim,
|
||||||
filter: 'brightness(1.05)'
|
borderColor: theme.colors.accentDim,
|
||||||
},
|
},
|
||||||
":not(:disabled):active": {
|
":not(:disabled):active": {
|
||||||
transform: 'translateY(1px)',
|
transform: 'translateY(1px)',
|
||||||
boxShadow: '0 2px 3px rgba(0, 0, 0, 0.2)'
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
variants: {
|
variants: {
|
||||||
intent: {
|
intent: {
|
||||||
primary: {
|
primary: {
|
||||||
background: "#3b82f6",
|
background: theme.colors.accent,
|
||||||
color: "white",
|
color: theme.colors.bg,
|
||||||
boxShadow: "0 4px 6px rgba(59, 130, 246, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
|
border: `1px solid ${theme.colors.accent}`,
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
background: "#f3f4f6",
|
background: theme.colors.bgElevated,
|
||||||
color: "#374151",
|
color: theme.colors.fg,
|
||||||
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06)",
|
border: `1px solid ${theme.colors.border}`,
|
||||||
|
states: {
|
||||||
|
":not(:disabled):hover": {
|
||||||
|
borderColor: theme.colors.borderActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
background: "#ef4444",
|
background: "#ff0000",
|
||||||
color: "white",
|
color: theme.colors.bg,
|
||||||
boxShadow: "0 4px 6px rgba(239, 68, 68, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
|
border: "1px solid #ff0000",
|
||||||
|
states: {
|
||||||
|
":not(:disabled):hover": {
|
||||||
|
background: "#cc0000",
|
||||||
|
borderColor: "#cc0000",
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ghost: {
|
ghost: {
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
color: "#aaa",
|
color: theme.colors.fgMuted,
|
||||||
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1)",
|
border: `1px solid ${theme.colors.border}`,
|
||||||
border: "1px solid #eee",
|
states: {
|
||||||
|
":not(:disabled):hover": {
|
||||||
|
color: theme.colors.fg,
|
||||||
|
borderColor: theme.colors.borderActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
small: {
|
small: {
|
||||||
padding: "8px 16px",
|
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
|
||||||
fontSize: 14,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
large: {
|
large: {
|
||||||
padding: "16px 32px",
|
padding: `${theme.spacing.md}px ${theme.spacing.xl}px`,
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
opacity: 0.5,
|
opacity: 0.3,
|
||||||
cursor: "not-allowed",
|
cursor: "not-allowed",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -77,7 +90,7 @@ const Button = define('Root', {
|
||||||
|
|
||||||
const ButtonRow = define('Row', {
|
const ButtonRow = define('Row', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 16,
|
gap: theme.spacing.md,
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { ExampleSection } from './ssr/helpers'
|
import { ExampleSection, theme } from './ssr/helpers'
|
||||||
|
|
||||||
const Input = define('Input', {
|
const Input = define('Input', {
|
||||||
base: 'input',
|
base: 'input',
|
||||||
|
|
||||||
padding: '12px 16px',
|
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
border: '2px solid #e5e7eb',
|
border: `1px solid ${theme.colors.border}`,
|
||||||
borderRadius: 8,
|
borderRadius: theme.radius.sm,
|
||||||
background: 'white',
|
background: theme.colors.bgElevated,
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
|
|
@ -17,12 +17,11 @@ const Input = define('Input', {
|
||||||
states: {
|
states: {
|
||||||
':focus': {
|
':focus': {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
borderColor: '#3b82f6',
|
borderColor: theme.colors.borderActive,
|
||||||
boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)'
|
|
||||||
},
|
},
|
||||||
':disabled': {
|
':disabled': {
|
||||||
background: '#f3f4f6',
|
background: theme.colors.bg,
|
||||||
color: '#9ca3af',
|
color: theme.colors.fgDim,
|
||||||
cursor: 'not-allowed'
|
cursor: 'not-allowed'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -30,20 +29,18 @@ const Input = define('Input', {
|
||||||
variants: {
|
variants: {
|
||||||
status: {
|
status: {
|
||||||
error: {
|
error: {
|
||||||
borderColor: '#ef4444',
|
borderColor: '#ff0000',
|
||||||
states: {
|
states: {
|
||||||
':focus': {
|
':focus': {
|
||||||
borderColor: '#ef4444',
|
borderColor: '#ff0000',
|
||||||
boxShadow: '0 0 0 3px rgba(239, 68, 68, 0.1)'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
borderColor: '#10b981',
|
borderColor: theme.colors.accent,
|
||||||
states: {
|
states: {
|
||||||
':focus': {
|
':focus': {
|
||||||
borderColor: '#10b981',
|
borderColor: theme.colors.accent,
|
||||||
boxShadow: '0 0 0 3px rgba(16, 185, 129, 0.1)'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,12 +51,12 @@ const Input = define('Input', {
|
||||||
const Textarea = define('Textarea', {
|
const Textarea = define('Textarea', {
|
||||||
base: 'textarea',
|
base: 'textarea',
|
||||||
|
|
||||||
padding: '12px 16px',
|
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
border: '2px solid #e5e7eb',
|
border: `1px solid ${theme.colors.border}`,
|
||||||
borderRadius: 8,
|
borderRadius: theme.radius.sm,
|
||||||
background: 'white',
|
background: theme.colors.bgElevated,
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
minHeight: 120,
|
minHeight: 120,
|
||||||
|
|
@ -70,32 +67,31 @@ const Textarea = define('Textarea', {
|
||||||
states: {
|
states: {
|
||||||
':focus': {
|
':focus': {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
borderColor: '#3b82f6',
|
borderColor: theme.colors.borderActive,
|
||||||
boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const FormGroup = define('FormGroup', {
|
const FormGroup = define('FormGroup', {
|
||||||
marginBottom: 24,
|
marginBottom: theme.spacing.lg,
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Label: {
|
Label: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 600,
|
fontWeight: 400,
|
||||||
color: '#374151',
|
color: theme.colors.fg,
|
||||||
marginBottom: 8
|
marginBottom: theme.spacing.xs
|
||||||
},
|
},
|
||||||
Helper: {
|
Helper: {
|
||||||
fontSize: 13,
|
fontSize: 12,
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
marginTop: 6
|
marginTop: 6
|
||||||
},
|
},
|
||||||
Error: {
|
Error: {
|
||||||
fontSize: 13,
|
fontSize: 12,
|
||||||
color: '#ef4444',
|
color: '#ff0000',
|
||||||
marginTop: 6
|
marginTop: 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -116,29 +112,29 @@ const Checkbox = define('Checkbox', {
|
||||||
parts: {
|
parts: {
|
||||||
Input: {
|
Input: {
|
||||||
base: 'input[type=checkbox]',
|
base: 'input[type=checkbox]',
|
||||||
width: 20,
|
width: 18,
|
||||||
height: 20,
|
height: 18,
|
||||||
cursor: 'pointer'
|
cursor: 'pointer'
|
||||||
},
|
},
|
||||||
Label: {
|
Label: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 12,
|
gap: theme.spacing.sm,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
color: '#374151',
|
color: theme.colors.fgMuted,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
color: '#111827'
|
color: theme.colors.fg
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:disabled + &': {
|
'@Input:disabled + &': {
|
||||||
cursor: 'not-allowed',
|
cursor: 'not-allowed',
|
||||||
color: '#9ca3af'
|
color: theme.colors.fgDim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,19 +160,20 @@ const FormExamples = define('FormExamples', {
|
||||||
const Button = define('FormButton', {
|
const Button = define('FormButton', {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
|
|
||||||
padding: '12px 24px',
|
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
fontWeight: 600,
|
fontWeight: 400,
|
||||||
border: 'none',
|
border: `1px solid ${theme.colors.accent}`,
|
||||||
borderRadius: 8,
|
borderRadius: theme.radius.sm,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
background: '#3b82f6',
|
background: theme.colors.accent,
|
||||||
color: 'white',
|
color: theme.colors.bg,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#2563eb'
|
background: theme.colors.accentDim,
|
||||||
|
borderColor: theme.colors.accentDim,
|
||||||
},
|
},
|
||||||
':active': {
|
':active': {
|
||||||
transform: 'translateY(1px)'
|
transform: 'translateY(1px)'
|
||||||
|
|
@ -186,11 +183,12 @@ const Button = define('FormButton', {
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
secondary: {
|
secondary: {
|
||||||
background: '#6b7280',
|
background: theme.colors.bgElevated,
|
||||||
color: 'white',
|
color: theme.colors.fg,
|
||||||
|
border: `1px solid ${theme.colors.border}`,
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#4b5563'
|
borderColor: theme.colors.borderActive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,8 +198,8 @@ const Button = define('FormButton', {
|
||||||
|
|
||||||
const ButtonGroup = define('FormButtonGroup', {
|
const ButtonGroup = define('FormButtonGroup', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 12,
|
gap: theme.spacing.sm,
|
||||||
marginTop: 24
|
marginTop: theme.spacing.lg
|
||||||
})
|
})
|
||||||
|
|
||||||
export const FormExamplesContent = () => (
|
export const FormExamplesContent = () => (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { ExampleSection } from './ssr/helpers'
|
import { ExampleSection, theme } from './ssr/helpers'
|
||||||
|
|
||||||
const TabSwitcher = define('TabSwitcher', {
|
const TabSwitcher = define('TabSwitcher', {
|
||||||
parts: {
|
parts: {
|
||||||
|
|
@ -10,41 +10,41 @@ const TabSwitcher = define('TabSwitcher', {
|
||||||
TabBar: {
|
TabBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 0,
|
gap: 0,
|
||||||
borderBottom: '2px solid #e5e7eb',
|
borderBottom: `1px solid ${theme.colors.border}`,
|
||||||
marginBottom: 24,
|
marginBottom: theme.spacing.lg,
|
||||||
},
|
},
|
||||||
TabLabel: {
|
TabLabel: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
|
|
||||||
padding: '12px 24px',
|
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
marginBottom: -2,
|
marginBottom: -1,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
borderBottom: '2px solid transparent',
|
borderBottom: '1px solid transparent',
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked + &': {
|
'@Input:checked + &': {
|
||||||
color: '#3b82f6',
|
color: theme.colors.accent,
|
||||||
borderBottom: '2px solid #3b82f6'
|
borderBottom: `1px solid ${theme.colors.accent}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Content: {
|
Content: {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
padding: 20,
|
padding: theme.spacing.lg,
|
||||||
background: '#f9fafb',
|
background: theme.colors.bgElevated,
|
||||||
borderRadius: 8,
|
border: `1px solid ${theme.colors.border}`,
|
||||||
|
borderRadius: theme.radius.sm,
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked ~ &': {
|
'@Input:checked ~ &': {
|
||||||
|
|
@ -91,36 +91,37 @@ const Pills = define('Pills', {
|
||||||
},
|
},
|
||||||
PillBar: {
|
PillBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 8,
|
gap: theme.spacing.xs,
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
PillLabel: {
|
PillLabel: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
|
|
||||||
padding: '8px 16px',
|
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
|
||||||
background: '#f3f4f6',
|
background: theme.colors.bgElevated,
|
||||||
border: 'none',
|
border: `1px solid ${theme.colors.border}`,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#e5e7eb',
|
borderColor: theme.colors.borderActive,
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked + &': {
|
'@Input:checked + &': {
|
||||||
background: '#3b82f6',
|
background: theme.colors.accent,
|
||||||
color: 'white'
|
borderColor: theme.colors.accent,
|
||||||
|
color: theme.colors.bg
|
||||||
},
|
},
|
||||||
'@Input:checked + &:hover': {
|
'@Input:checked + &:hover': {
|
||||||
background: '#2563eb'
|
background: theme.colors.accentDim,
|
||||||
|
borderColor: theme.colors.accentDim,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,34 +165,36 @@ const VerticalNav = define('VerticalNav', {
|
||||||
NavLabel: {
|
NavLabel: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
|
|
||||||
padding: '12px 16px',
|
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 12,
|
gap: theme.spacing.sm,
|
||||||
|
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
borderRadius: 8,
|
border: `1px solid transparent`,
|
||||||
color: '#6b7280',
|
borderRadius: theme.radius.sm,
|
||||||
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#f3f4f6',
|
background: theme.colors.bgElevated,
|
||||||
color: '#111827',
|
borderColor: theme.colors.border,
|
||||||
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked + &': {
|
'@Input:checked + &': {
|
||||||
background: '#eff6ff',
|
background: theme.colors.bgElevated,
|
||||||
color: '#3b82f6',
|
borderColor: theme.colors.accent,
|
||||||
|
color: theme.colors.accent,
|
||||||
},
|
},
|
||||||
'@Input:checked + &:hover': {
|
'@Input:checked + &:hover': {
|
||||||
background: '#dbeafe',
|
borderColor: theme.colors.accentDim,
|
||||||
color: '#2563eb'
|
color: theme.colors.accentDim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -201,7 +204,7 @@ const VerticalNav = define('VerticalNav', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -232,33 +235,32 @@ const VerticalNav = define('VerticalNav', {
|
||||||
const Breadcrumbs = define('Breadcrumbs', {
|
const Breadcrumbs = define('Breadcrumbs', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
gap: theme.spacing.xs,
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Item: {
|
Item: {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
transition: 'color 0.2s ease',
|
transition: 'color 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
color: '#3b82f6',
|
color: theme.colors.accent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Separator: {
|
Separator: {
|
||||||
color: '#d1d5db',
|
color: theme.colors.fgDim,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
},
|
},
|
||||||
Current: {
|
Current: {
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -287,26 +289,25 @@ const Breadcrumbs = define('Breadcrumbs', {
|
||||||
const Tabs = define('Tabs', {
|
const Tabs = define('Tabs', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 0,
|
gap: 0,
|
||||||
borderBottom: '2px solid #e5e7eb',
|
borderBottom: `1px solid ${theme.colors.border}`,
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Tab: {
|
Tab: {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
padding: '12px 24px',
|
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
marginBottom: -2,
|
marginBottom: -1,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderBottom: '2px solid transparent',
|
borderBottom: '1px solid transparent',
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -316,8 +317,8 @@ const Tabs = define('Tabs', {
|
||||||
active: {
|
active: {
|
||||||
parts: {
|
parts: {
|
||||||
Tab: {
|
Tab: {
|
||||||
color: '#3b82f6',
|
color: theme.colors.accent,
|
||||||
borderBottom: '2px solid #3b82f6',
|
borderBottom: `1px solid ${theme.colors.accent}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -338,26 +339,25 @@ const Tabs = define('Tabs', {
|
||||||
|
|
||||||
const SimplePills = define('SimplePills', {
|
const SimplePills = define('SimplePills', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 8,
|
gap: theme.spacing.xs,
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Pill: {
|
Pill: {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
padding: '8px 16px',
|
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
|
||||||
background: '#f3f4f6',
|
background: theme.colors.bgElevated,
|
||||||
border: 'none',
|
border: `1px solid ${theme.colors.border}`,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#e5e7eb',
|
borderColor: theme.colors.borderActive,
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -367,12 +367,13 @@ const SimplePills = define('SimplePills', {
|
||||||
active: {
|
active: {
|
||||||
parts: {
|
parts: {
|
||||||
Pill: {
|
Pill: {
|
||||||
background: '#3b82f6',
|
background: theme.colors.accent,
|
||||||
color: 'white',
|
borderColor: theme.colors.accent,
|
||||||
|
color: theme.colors.bg,
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#2563eb',
|
background: theme.colors.accentDim,
|
||||||
color: 'white',
|
borderColor: theme.colors.accentDim,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -402,24 +403,24 @@ const SimpleVerticalNav = define('SimpleVerticalNav', {
|
||||||
parts: {
|
parts: {
|
||||||
NavItem: {
|
NavItem: {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
padding: '12px 16px',
|
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 12,
|
gap: theme.spacing.sm,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: 'none',
|
border: `1px solid transparent`,
|
||||||
borderRadius: 8,
|
borderRadius: theme.radius.sm,
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#f3f4f6',
|
background: theme.colors.bgElevated,
|
||||||
color: '#111827',
|
borderColor: theme.colors.border,
|
||||||
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -429,7 +430,7 @@ const SimpleVerticalNav = define('SimpleVerticalNav', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -437,12 +438,13 @@ const SimpleVerticalNav = define('SimpleVerticalNav', {
|
||||||
active: {
|
active: {
|
||||||
parts: {
|
parts: {
|
||||||
NavItem: {
|
NavItem: {
|
||||||
background: '#eff6ff',
|
background: theme.colors.bgElevated,
|
||||||
color: '#3b82f6',
|
borderColor: theme.colors.accent,
|
||||||
|
color: theme.colors.accent,
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#dbeafe',
|
borderColor: theme.colors.accentDim,
|
||||||
color: '#2563eb',
|
color: theme.colors.accentDim,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { ExampleSection } from './ssr/helpers'
|
import { ExampleSection, theme } from './ssr/helpers'
|
||||||
|
|
||||||
const UserProfile = define('UserProfile', {
|
const UserProfile = define('UserProfile', {
|
||||||
base: 'div',
|
base: 'div',
|
||||||
|
|
||||||
padding: 24,
|
padding: theme.spacing.lg,
|
||||||
maxWidth: 600,
|
maxWidth: 600,
|
||||||
margin: "0 auto",
|
margin: "0 auto",
|
||||||
background: "white",
|
background: theme.colors.bgElevated,
|
||||||
borderRadius: 12,
|
border: `1px solid ${theme.colors.border}`,
|
||||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
borderRadius: theme.radius.md,
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Header: {
|
Header: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 16,
|
gap: theme.spacing.md,
|
||||||
marginBottom: 16,
|
marginBottom: theme.spacing.md,
|
||||||
},
|
},
|
||||||
Avatar: {
|
Avatar: {
|
||||||
base: 'img',
|
base: 'img',
|
||||||
|
|
@ -24,34 +24,34 @@ const UserProfile = define('UserProfile', {
|
||||||
height: 64,
|
height: 64,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
objectFit: "cover",
|
objectFit: "cover",
|
||||||
border: "3px solid #e5e7eb",
|
border: `2px solid ${theme.colors.border}`,
|
||||||
},
|
},
|
||||||
Info: {
|
Info: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
Name: {
|
Name: {
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
fontSize: 20,
|
fontSize: 18,
|
||||||
fontWeight: 600,
|
fontWeight: 400,
|
||||||
color: "#111827",
|
color: theme.colors.fg,
|
||||||
},
|
},
|
||||||
Handle: {
|
Handle: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: "#6b7280",
|
color: theme.colors.fgMuted,
|
||||||
},
|
},
|
||||||
Bio: {
|
Bio: {
|
||||||
marginBottom: 16,
|
marginBottom: theme.spacing.md,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.6,
|
||||||
color: "#374151",
|
color: theme.colors.fgMuted,
|
||||||
wordWrap: "break-word",
|
wordWrap: "break-word",
|
||||||
},
|
},
|
||||||
Stats: {
|
Stats: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: 24,
|
gap: theme.spacing.lg,
|
||||||
paddingTop: 16,
|
paddingTop: theme.spacing.md,
|
||||||
borderTop: "1px solid #e5e7eb",
|
borderTop: `1px solid ${theme.colors.border}`,
|
||||||
},
|
},
|
||||||
Stat: {
|
Stat: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|
@ -60,12 +60,12 @@ const UserProfile = define('UserProfile', {
|
||||||
},
|
},
|
||||||
StatValue: {
|
StatValue: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 600,
|
fontWeight: 400,
|
||||||
color: "#111827",
|
color: theme.colors.fg,
|
||||||
},
|
},
|
||||||
StatLabel: {
|
StatLabel: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: "#6b7280",
|
color: theme.colors.fgMuted,
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -114,19 +114,7 @@ const UserProfile = define('UserProfile', {
|
||||||
verified: {
|
verified: {
|
||||||
parts: {
|
parts: {
|
||||||
Avatar: {
|
Avatar: {
|
||||||
border: "3px solid #3b82f6",
|
border: `2px solid ${theme.colors.accent}`,
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
dark: {
|
|
||||||
background: "#1f2937",
|
|
||||||
parts: {
|
|
||||||
Name: { color: "#f9fafb" },
|
|
||||||
Handle: { color: "#9ca3af" },
|
|
||||||
Bio: { color: "#d1d5db" },
|
|
||||||
Stats: { borderTop: "1px solid #374151" },
|
|
||||||
StatValue: { color: "#f9fafb" },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -207,10 +195,9 @@ export const ProfileExamplesContent = () => (
|
||||||
/>
|
/>
|
||||||
</ExampleSection>
|
</ExampleSection>
|
||||||
|
|
||||||
<ExampleSection title="Verified User (Dark Theme)">
|
<ExampleSection title="Verified User">
|
||||||
<UserProfile
|
<UserProfile
|
||||||
verified={true}
|
verified={true}
|
||||||
theme="dark"
|
|
||||||
name="Jordan Smith"
|
name="Jordan Smith"
|
||||||
username="jordansmith"
|
username="jordansmith"
|
||||||
avatarUrl="https://i.pravatar.cc/150?img=8"
|
avatarUrl="https://i.pravatar.cc/150?img=8"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { define } from '../../src'
|
import { define } from '../../src'
|
||||||
|
import { theme } from '../ssr/helpers'
|
||||||
import { ButtonExamplesContent } from '../button'
|
import { ButtonExamplesContent } from '../button'
|
||||||
import { ProfileExamplesContent } from '../profile'
|
import { ProfileExamplesContent } from '../profile'
|
||||||
import { NavigationExamplesContent } from '../navigation'
|
import { NavigationExamplesContent } from '../navigation'
|
||||||
|
|
@ -8,9 +9,10 @@ export const Main = define('SpaMain', {
|
||||||
base: 'div',
|
base: 'div',
|
||||||
|
|
||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
padding: '40px 20px',
|
padding: theme.spacing.xl,
|
||||||
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
fontFamily: theme.fonts.mono,
|
||||||
background: '#f3f4f6',
|
background: theme.colors.bg,
|
||||||
|
color: theme.colors.fg,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Container = define('SpaContainer', {
|
export const Container = define('SpaContainer', {
|
||||||
|
|
@ -24,21 +26,20 @@ export const Container = define('SpaContainer', {
|
||||||
const Link = define('Link', {
|
const Link = define('Link', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
color: '#3b82f6',
|
color: theme.colors.fgMuted,
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
fontWeight: 500,
|
fontSize: 14,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
textDecoration: 'underline'
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[aria-current]': {
|
'&[aria-current]': {
|
||||||
color: '#1e40af',
|
color: theme.colors.fg,
|
||||||
fontWeight: 600,
|
textDecoration: 'underline',
|
||||||
textDecoration: 'underline'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -61,52 +62,51 @@ const Nav = define('Nav', {
|
||||||
base: 'nav',
|
base: 'nav',
|
||||||
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 20,
|
gap: theme.spacing.lg,
|
||||||
marginBottom: 40,
|
marginBottom: theme.spacing.xl,
|
||||||
padding: 20,
|
padding: theme.spacing.lg,
|
||||||
background: 'white',
|
background: theme.colors.bgElevated,
|
||||||
borderRadius: 8,
|
border: `1px solid ${theme.colors.border}`,
|
||||||
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
|
borderRadius: theme.radius.sm,
|
||||||
})
|
})
|
||||||
|
|
||||||
const P = define('P', {
|
const P = define('P', {
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
marginBottom: 48,
|
marginBottom: theme.spacing.xxl,
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExamplesGrid = define('ExamplesGrid', {
|
const ExamplesGrid = define('ExamplesGrid', {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gap: 20,
|
gap: theme.spacing.lg,
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExampleCard = define('ExampleCard', {
|
const ExampleCard = define('ExampleCard', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
background: 'white',
|
background: theme.colors.bgElevated,
|
||||||
padding: 24,
|
padding: theme.spacing.lg,
|
||||||
borderRadius: 12,
|
border: `1px solid ${theme.colors.border}`,
|
||||||
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
|
borderRadius: theme.radius.sm,
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
transition: 'all 0.2s ease',
|
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
transform: 'translateY(-2px)',
|
borderColor: theme.colors.borderActive,
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
H2: {
|
H2: {
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
margin: '0 0 8px 0',
|
margin: `0 0 ${theme.spacing.sm}px 0`,
|
||||||
fontSize: 20,
|
fontSize: 18,
|
||||||
|
fontWeight: 400,
|
||||||
},
|
},
|
||||||
P: {
|
P: {
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
margin: 0,
|
margin: 0,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}
|
}
|
||||||
|
|
@ -130,27 +130,27 @@ const ExampleCard = define('ExampleCard', {
|
||||||
|
|
||||||
const HomePage = () => (
|
const HomePage = () => (
|
||||||
<>
|
<>
|
||||||
<P>Explore component examples built with Forge - Client-side SPA version</P>
|
<P>Client-side rendered examples. Click around, check the source.</P>
|
||||||
|
|
||||||
<ExamplesGrid>
|
<ExamplesGrid>
|
||||||
<ExampleCard href="/spa/profile"
|
<ExampleCard href="/spa/profile"
|
||||||
title="Profile Card"
|
title="Profile Card"
|
||||||
desc="User profile component with variants for size, theme, and verified status"
|
desc="Parts, variants, custom render. Size/theme switching."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/spa/buttons"
|
<ExampleCard href="/spa/buttons"
|
||||||
title="Buttons"
|
title="Buttons"
|
||||||
desc="Button component with intent, size, and disabled variants"
|
desc="Intent, size, disabled states. Basic variant patterns."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/spa/navigation"
|
<ExampleCard href="/spa/navigation"
|
||||||
title="Navigation"
|
title="Navigation"
|
||||||
desc="Navigation patterns including tabs, pills, vertical nav, and breadcrumbs"
|
desc="Tabs, pills, breadcrumbs, vertical nav. No router required."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/spa/form"
|
<ExampleCard href="/spa/form"
|
||||||
title="Forms"
|
title="Forms"
|
||||||
desc="Form inputs with validation states, checkboxes, textareas, and buttons"
|
desc="Inputs, validation states, checkboxes, textareas."
|
||||||
/>
|
/>
|
||||||
</ExamplesGrid>
|
</ExamplesGrid>
|
||||||
</>
|
</>
|
||||||
|
|
@ -179,6 +179,20 @@ export function route(path: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HomeLink = define('HomeLink', {
|
||||||
|
base: 'a',
|
||||||
|
|
||||||
|
color: theme.colors.fgMuted,
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontSize: 14,
|
||||||
|
|
||||||
|
states: {
|
||||||
|
hover: {
|
||||||
|
color: theme.colors.fg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const path = window.location.pathname
|
const path = window.location.pathname
|
||||||
|
|
||||||
|
|
@ -186,7 +200,7 @@ export function App() {
|
||||||
<Main>
|
<Main>
|
||||||
<Container>
|
<Container>
|
||||||
<Nav>
|
<Nav>
|
||||||
<a href="/" style="color: #3b82f6; text-decoration: none; font-weight: 500;">Home</a>
|
<HomeLink href="/">Home</HomeLink>
|
||||||
<Link href="/spa" aria-current={path === '/spa' || path === '/spa/' ? 'page' : undefined}>SPA Examples</Link>
|
<Link href="/spa" aria-current={path === '/spa' || path === '/spa/' ? 'page' : undefined}>SPA Examples</Link>
|
||||||
<Link href="/spa/profile" aria-current={path === '/spa/profile' ? 'page' : undefined}>Profile</Link>
|
<Link href="/spa/profile" aria-current={path === '/spa/profile' ? 'page' : undefined}>Profile</Link>
|
||||||
<Link href="/spa/buttons" aria-current={path === '/spa/buttons' ? 'page' : undefined}>Buttons</Link>
|
<Link href="/spa/buttons" aria-current={path === '/spa/buttons' ? 'page' : undefined}>Buttons</Link>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,51 @@
|
||||||
import { define, Styles } from '../../src'
|
import { define, Styles } from '../../src'
|
||||||
|
|
||||||
|
export const theme = {
|
||||||
|
colors: {
|
||||||
|
bg: '#0a0a0a',
|
||||||
|
bgElevated: '#111',
|
||||||
|
bgHover: '#1a1a1a',
|
||||||
|
|
||||||
|
fg: '#00ff00',
|
||||||
|
fgMuted: '#888',
|
||||||
|
fgDim: '#444',
|
||||||
|
|
||||||
|
border: '#222',
|
||||||
|
borderActive: '#00ff00',
|
||||||
|
|
||||||
|
accent: '#00ff00',
|
||||||
|
accentDim: '#008800',
|
||||||
|
},
|
||||||
|
|
||||||
|
fonts: {
|
||||||
|
mono: "'Monaco', 'Menlo', 'Consolas', monospace",
|
||||||
|
sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
||||||
|
},
|
||||||
|
|
||||||
|
spacing: {
|
||||||
|
xs: 8,
|
||||||
|
sm: 12,
|
||||||
|
md: 16,
|
||||||
|
lg: 24,
|
||||||
|
xl: 32,
|
||||||
|
xxl: 48,
|
||||||
|
},
|
||||||
|
|
||||||
|
radius: {
|
||||||
|
sm: 4,
|
||||||
|
md: 8,
|
||||||
|
lg: 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const Body = define('Body', {
|
export const Body = define('Body', {
|
||||||
base: 'body',
|
base: 'body',
|
||||||
|
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: '40px 20px',
|
padding: theme.spacing.xl,
|
||||||
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
fontFamily: theme.fonts.mono,
|
||||||
background: '#f3f4f6',
|
background: theme.colors.bg,
|
||||||
|
color: theme.colors.fg,
|
||||||
})
|
})
|
||||||
|
|
||||||
const Container = define('Container', {
|
const Container = define('Container', {
|
||||||
|
|
@ -17,20 +56,23 @@ const Container = define('Container', {
|
||||||
export const Header = define('Header', {
|
export const Header = define('Header', {
|
||||||
base: 'h1',
|
base: 'h1',
|
||||||
|
|
||||||
marginBottom: 40,
|
marginBottom: theme.spacing.xl,
|
||||||
color: '#111827'
|
color: theme.colors.fg,
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: 400,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ExampleSection = define('ExampleSection', {
|
export const ExampleSection = define('ExampleSection', {
|
||||||
marginBottom: 40,
|
marginBottom: theme.spacing.xl,
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Header: {
|
Header: {
|
||||||
base: 'h2',
|
base: 'h2',
|
||||||
|
|
||||||
marginBottom: 16,
|
marginBottom: theme.spacing.md,
|
||||||
color: '#374151',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 18
|
fontSize: 16,
|
||||||
|
fontWeight: 400,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render({ props, parts: { Root, Header } }) {
|
render({ props, parts: { Root, Header } }) {
|
||||||
|
|
@ -47,32 +89,31 @@ const Nav = define('SSR_Nav', {
|
||||||
base: 'nav',
|
base: 'nav',
|
||||||
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 20,
|
gap: theme.spacing.lg,
|
||||||
marginBottom: 40,
|
marginBottom: theme.spacing.xl,
|
||||||
padding: 20,
|
padding: theme.spacing.lg,
|
||||||
background: 'white',
|
background: theme.colors.bgElevated,
|
||||||
borderRadius: 8,
|
border: `1px solid ${theme.colors.border}`,
|
||||||
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
|
borderRadius: theme.radius.sm,
|
||||||
})
|
})
|
||||||
|
|
||||||
const NavLink = define('SSR_NavLink', {
|
const NavLink = define('SSR_NavLink', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
color: '#3b82f6',
|
color: theme.colors.fgMuted,
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
fontWeight: 500,
|
fontSize: 14,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
textDecoration: 'underline'
|
color: theme.colors.fg,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[aria-current]': {
|
'&[aria-current]': {
|
||||||
color: '#1e40af',
|
color: theme.colors.fg,
|
||||||
fontWeight: 600,
|
textDecoration: 'underline',
|
||||||
textDecoration: 'underline'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createScope, Styles } from '../../src'
|
import { createScope, Styles } from '../../src'
|
||||||
|
import { theme } from './helpers'
|
||||||
|
|
||||||
const { define } = createScope('Landing')
|
const { define } = createScope('Landing')
|
||||||
|
|
||||||
|
|
@ -6,85 +7,60 @@ const Page = define('Page', {
|
||||||
base: 'body',
|
base: 'body',
|
||||||
|
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: 0,
|
padding: theme.spacing.xl,
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
display: 'flex',
|
fontFamily: theme.fonts.mono,
|
||||||
alignItems: 'center',
|
background: theme.colors.bg,
|
||||||
justifyContent: 'center',
|
color: theme.colors.fg,
|
||||||
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const Container = define('Container', {
|
const Container = define('Container', {
|
||||||
textAlign: 'center',
|
maxWidth: 800,
|
||||||
color: 'white',
|
margin: '0 auto',
|
||||||
})
|
})
|
||||||
|
|
||||||
const Title = define('Title', {
|
const Pre = define('Pre', {
|
||||||
base: 'h1',
|
base: 'pre',
|
||||||
|
|
||||||
fontSize: 48,
|
fontSize: 14,
|
||||||
fontWeight: 700,
|
lineHeight: 1.4,
|
||||||
marginBottom: 50,
|
marginBottom: theme.spacing.xl,
|
||||||
color: 'white',
|
color: theme.colors.fg,
|
||||||
|
whiteSpace: 'pre',
|
||||||
})
|
})
|
||||||
|
|
||||||
const Subtitle = define('Subtitle', {
|
const P = define('P', {
|
||||||
base: 'p',
|
base: 'p',
|
||||||
|
|
||||||
fontSize: 20,
|
fontSize: 16,
|
||||||
marginBottom: 48,
|
lineHeight: 1.6,
|
||||||
color: 'rgba(255, 255, 255, 0.9)',
|
marginBottom: theme.spacing.xl,
|
||||||
|
color: theme.colors.fgMuted,
|
||||||
})
|
})
|
||||||
|
|
||||||
const ButtonGroup = define('ButtonGroup', {
|
const LinkSection = define('LinkSection', {
|
||||||
display: 'flex',
|
marginTop: theme.spacing.xxl,
|
||||||
gap: 50,
|
paddingTop: theme.spacing.xl,
|
||||||
justifyContent: 'center',
|
borderTop: `1px solid ${theme.colors.border}`,
|
||||||
flexWrap: 'wrap',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const ChoiceCard = define('ChoiceCard', {
|
const Link = define('Link', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
display: 'block',
|
display: 'inline-block',
|
||||||
padding: 40,
|
marginRight: theme.spacing.xl,
|
||||||
background: 'white',
|
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
||||||
borderRadius: 16,
|
background: theme.colors.bgElevated,
|
||||||
|
border: `1px solid ${theme.colors.border}`,
|
||||||
|
color: theme.colors.fg,
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
color: '#111827',
|
fontSize: 14,
|
||||||
boxShadow: '0 10px 30px rgba(0, 0, 0, 0.2)',
|
|
||||||
transition: 'all 0.3s ease',
|
|
||||||
minWidth: 250,
|
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
transform: 'translateY(-8px)',
|
background: theme.colors.bgHover,
|
||||||
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.3)',
|
borderColor: theme.colors.borderActive,
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
parts: {
|
|
||||||
Icon: {
|
|
||||||
fontSize: 48,
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
Title: {
|
|
||||||
base: 'h2',
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 600,
|
|
||||||
marginBottom: 8,
|
|
||||||
color: '#111827',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render({ props, parts: { Root, Icon, Title, Description } }) {
|
|
||||||
return (
|
|
||||||
<Root href={props.href}>
|
|
||||||
<Icon>{props.icon}</Icon>
|
|
||||||
<Title>{props.title}</Title>
|
|
||||||
</Root>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -93,26 +69,30 @@ export const LandingPage = () => (
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Forge - Choose Your Rendering Mode</title>
|
<title>forge</title>
|
||||||
<Styles />
|
<Styles />
|
||||||
</head>
|
</head>
|
||||||
<Page>
|
<Page>
|
||||||
<Container>
|
<Container>
|
||||||
<Title>Welcome to Forge</Title>
|
<Pre>{`╔═╝╔═║╔═║╔═╝╔═╝
|
||||||
|
╔═╝║ ║╔╔╝║ ║╔═╝
|
||||||
|
╝ ══╝╝ ╝══╝══╝`}</Pre>
|
||||||
|
|
||||||
<ButtonGroup>
|
<P>
|
||||||
<ChoiceCard
|
Typed, local, variant-driven CSS. No globals, no selector hell, no inline styles.
|
||||||
href="/ssr"
|
Built for TSX. Compiles to real CSS.
|
||||||
icon="🖥️"
|
</P>
|
||||||
title="SSR Examples"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ChoiceCard
|
<P>
|
||||||
href="/spa"
|
CSS is hostile to humans at scale. Forge fixes that by making styles local,
|
||||||
icon="⚡"
|
typed, and composable. Parts replace selectors. Variants replace inline styles.
|
||||||
title="SPA Examples"
|
Everything deterministic.
|
||||||
/>
|
</P>
|
||||||
</ButtonGroup>
|
|
||||||
|
<LinkSection>
|
||||||
|
<Link href="/ssr">SSR demos →</Link>
|
||||||
|
<Link href="/spa">SPA demos →</Link>
|
||||||
|
</LinkSection>
|
||||||
</Container>
|
</Container>
|
||||||
</Page>
|
</Page>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,47 @@
|
||||||
import { define } from '../../src'
|
import { define } from '../../src'
|
||||||
import { Layout } from './helpers'
|
import { Layout, theme } from './helpers'
|
||||||
import { ButtonExamplesContent } from '../button'
|
import { ButtonExamplesContent } from '../button'
|
||||||
import { ProfileExamplesContent } from '../profile'
|
import { ProfileExamplesContent } from '../profile'
|
||||||
import { NavigationExamplesContent } from '../navigation'
|
import { NavigationExamplesContent } from '../navigation'
|
||||||
import { FormExamplesContent } from '../form'
|
import { FormExamplesContent } from '../form'
|
||||||
|
|
||||||
const P = define('SSR_P', {
|
const P = define('SSR_P', {
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
marginBottom: 48,
|
marginBottom: theme.spacing.xxl,
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExamplesGrid = define('SSR_ExamplesGrid', {
|
const ExamplesGrid = define('SSR_ExamplesGrid', {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gap: 20,
|
gap: theme.spacing.lg,
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExampleCard = define('SSR_ExampleCard', {
|
const ExampleCard = define('SSR_ExampleCard', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
background: 'white',
|
background: theme.colors.bgElevated,
|
||||||
padding: 24,
|
padding: theme.spacing.lg,
|
||||||
borderRadius: 12,
|
border: `1px solid ${theme.colors.border}`,
|
||||||
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
|
borderRadius: theme.radius.sm,
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
transition: 'all 0.2s ease',
|
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
transform: 'translateY(-2px)',
|
borderColor: theme.colors.borderActive,
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
H2: {
|
H2: {
|
||||||
color: '#111827',
|
color: theme.colors.fg,
|
||||||
margin: '0 0 8px 0',
|
margin: `0 0 ${theme.spacing.sm}px 0`,
|
||||||
fontSize: 20,
|
fontSize: 18,
|
||||||
|
fontWeight: 400,
|
||||||
},
|
},
|
||||||
P: {
|
P: {
|
||||||
color: '#6b7280',
|
color: theme.colors.fgMuted,
|
||||||
margin: 0,
|
margin: 0,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}
|
}
|
||||||
|
|
@ -60,27 +59,27 @@ const ExampleCard = define('SSR_ExampleCard', {
|
||||||
|
|
||||||
export const IndexPage = ({ path }: any) => (
|
export const IndexPage = ({ path }: any) => (
|
||||||
<Layout title="Forge Examples" path={path}>
|
<Layout title="Forge Examples" path={path}>
|
||||||
<P>Explore component examples built with Forge</P>
|
<P>Server-rendered demos. View source to see the static CSS.</P>
|
||||||
|
|
||||||
<ExamplesGrid>
|
<ExamplesGrid>
|
||||||
<ExampleCard href="/ssr/profile"
|
<ExampleCard href="/ssr/profile"
|
||||||
title="Profile Card"
|
title="Profile Card"
|
||||||
desc="User profile component with variants for size, theme, and verified status"
|
desc="Parts, variants, custom render. Size/theme switching."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/ssr/buttons"
|
<ExampleCard href="/ssr/buttons"
|
||||||
title="Buttons"
|
title="Buttons"
|
||||||
desc="Button component with intent, size, and disabled variants"
|
desc="Intent, size, disabled states. Basic variant patterns."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/ssr/navigation"
|
<ExampleCard href="/ssr/navigation"
|
||||||
title="Navigation"
|
title="Navigation"
|
||||||
desc="Navigation patterns including tabs, pills, vertical nav, and breadcrumbs"
|
desc="Tabs, pills, breadcrumbs, vertical nav. No router required."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/ssr/form"
|
<ExampleCard href="/ssr/form"
|
||||||
title="Forms"
|
title="Forms"
|
||||||
desc="Form inputs with validation states, checkboxes, textareas, and buttons"
|
desc="Inputs, validation states, checkboxes, textareas."
|
||||||
/>
|
/>
|
||||||
</ExamplesGrid>
|
</ExamplesGrid>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user