diff --git a/README.md b/README.md
index 79cc19d..244579a 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
## Why Forge?
-CSS is powerful, but hostile to humans at scale.
+CSS is powerful, but hostile.
### Problems with CSS
diff --git a/examples/navigation.tsx b/examples/navigation.tsx
index 2b9aac8..8bd7313 100644
--- a/examples/navigation.tsx
+++ b/examples/navigation.tsx
@@ -34,7 +34,7 @@ const TabSwitcher = define('TabSwitcher', {
},
selectors: {
- '.TabSwitcher_Input:checked + &': {
+ '@Input:checked + &': {
color: '#3b82f6',
borderBottom: '2px solid #3b82f6'
}
@@ -47,7 +47,7 @@ const TabSwitcher = define('TabSwitcher', {
borderRadius: 8,
selectors: {
- '.TabSwitcher_Input:checked ~ &': {
+ '@Input:checked ~ &': {
display: 'block'
}
}
@@ -115,11 +115,11 @@ const Pills = define('Pills', {
},
selectors: {
- '.Pills_Input:checked + &': {
+ '@Input:checked + &': {
background: '#3b82f6',
color: 'white'
},
- '.Pills_Input:checked + &:hover': {
+ '@Input:checked + &:hover': {
background: '#2563eb'
}
}
@@ -185,11 +185,11 @@ const VerticalNav = define('VerticalNav', {
},
selectors: {
- '.VerticalNav_Input:checked + &': {
+ '@Input:checked + &': {
background: '#eff6ff',
color: '#3b82f6',
},
- '.VerticalNav_Input:checked + &:hover': {
+ '@Input:checked + &:hover': {
background: '#dbeafe',
color: '#2563eb'
}
diff --git a/examples/spa/app.tsx b/examples/spa/app.tsx
index 427500b..f4a659a 100644
--- a/examples/spa/app.tsx
+++ b/examples/spa/app.tsx
@@ -6,6 +6,7 @@ import { NavigationExamplesContent } from '../navigation'
export const Main = define('SpaMain', {
base: 'div',
+ minHeight: '100%',
padding: '40px 20px',
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
background: '#f3f4f6',
@@ -24,6 +25,7 @@ const Link = define('Link', {
color: '#3b82f6',
textDecoration: 'none',
+ fontWeight: 500,
states: {
hover: {
@@ -31,6 +33,14 @@ const Link = define('Link', {
}
},
+ selectors: {
+ '&[aria-current]': {
+ color: '#1e40af',
+ fontWeight: 600,
+ textDecoration: 'underline'
+ }
+ },
+
render({ props, parts: { Root } }) {
const handleClick = (e: Event) => {
e.preventDefault()
@@ -161,18 +171,20 @@ export function route(path: string) {
}
export function App() {
+ const path = window.location.pathname
+
return (
- {route(window.location.pathname)}
+ {route(path)}
diff --git a/examples/spa/index.html b/examples/spa/index.html
index f77f967..4d19034 100644
--- a/examples/spa/index.html
+++ b/examples/spa/index.html
@@ -4,6 +4,15 @@
Forge SPA Examples
+
diff --git a/examples/spa/index.tsx b/examples/spa/index.tsx
index 5b64ab2..283c6dd 100644
--- a/examples/spa/index.tsx
+++ b/examples/spa/index.tsx
@@ -8,12 +8,12 @@ if (root) {
render(, root)
}
-// On route change, only update the content div
-function updateContent() {
- const contentDiv = document.getElementById('content')
- if (contentDiv)
- render(route(window.location.pathname), contentDiv)
+// On route change, re-render the whole app to update nav state
+function updateApp() {
+ if (root) {
+ render(, root)
+ }
}
-window.addEventListener('routechange', updateContent)
-window.addEventListener('popstate', updateContent)
+window.addEventListener('routechange', updateApp)
+window.addEventListener('popstate', updateApp)
diff --git a/examples/ssr/helpers.tsx b/examples/ssr/helpers.tsx
index ed74710..808736a 100644
--- a/examples/ssr/helpers.tsx
+++ b/examples/ssr/helpers.tsx
@@ -60,16 +60,27 @@ const NavLink = define('SSR_NavLink', {
color: '#3b82f6',
textDecoration: 'none',
+ fontWeight: 500,
states: {
hover: {
textDecoration: 'underline'
}
+ },
+
+ selectors: {
+ '&[aria-current]': {
+ color: '#1e40af',
+ fontWeight: 600,
+ textDecoration: 'underline'
+ }
}
})
export const Layout = define({
render({ props }) {
+ const path = props.path || ''
+
return (
@@ -81,11 +92,11 @@ export const Layout = define({
{props.children}
diff --git a/examples/ssr/pages.tsx b/examples/ssr/pages.tsx
index 9b0ffca..2483ee0 100644
--- a/examples/ssr/pages.tsx
+++ b/examples/ssr/pages.tsx
@@ -57,8 +57,8 @@ const ExampleCard = define('SSR_ExampleCard', {
}
})
-export const IndexPage = () => (
-
+export const IndexPage = ({ path }: any) => (
+
Explore component examples built with Forge
@@ -80,20 +80,20 @@ export const IndexPage = () => (
)
-export const ButtonExamplesPage = () => (
-
+export const ButtonExamplesPage = ({ path }: any) => (
+
)
-export const ProfileExamplesPage = () => (
-
+export const ProfileExamplesPage = ({ path }: any) => (
+
)
-export const NavigationExamplesPage = () => (
-
+export const NavigationExamplesPage = ({ path }: any) => (
+
)
diff --git a/server.tsx b/server.tsx
index 7c0afaf..1d9f6a2 100644
--- a/server.tsx
+++ b/server.tsx
@@ -7,13 +7,13 @@ const app = new Hono()
app.get('/', c => c.html())
-app.get('/ssr', c => c.html())
+app.get('/ssr', c => c.html())
-app.get('/ssr/profile', c => c.html())
+app.get('/ssr/profile', c => c.html())
-app.get('/ssr/buttons', c => c.html())
+app.get('/ssr/buttons', c => c.html())
-app.get('/ssr/navigation', c => c.html())
+app.get('/ssr/navigation', c => c.html())
app.get('/styles', c => c.text(stylesToCSS(styles)))
diff --git a/src/index.tsx b/src/index.tsx
index e7e40a4..884c5f3 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -134,30 +134,29 @@ function stateName(state: string): string {
return state.startsWith(':') ? state : `:${state}`
}
-// adds CSS styles for tag definition
-function registerStyles(name: string, def: TagDef) {
- const rootClassName = makeClassName(name)
- styles[rootClassName] ??= makeStyle(def)
+// Register base styles, selectors, and states for a class
+function registerClassStyles(name: string, className: string, def: TagDef) {
+ styles[className] ??= makeStyle(def)
for (let [selector, selectorDef] of Object.entries(def.selectors ?? {})) {
- selector = selector.replace('&', `.${rootClassName}`)
+ selector = selector.replace(/@(\w+)/g, (_, partName) => `.${makeClassName(name, partName)}`)
+ selector = selector.replace('&', `.${className}`)
if (styles[selector]) throw `${selector} already defined!`
styles[selector] = makeStyle(selectorDef)
}
for (const [state, style] of Object.entries(def.states ?? {}))
- styles[`${rootClassName}${stateName(state)}`] = makeStyle(style)
+ styles[`${className}${stateName(state)}`] = makeStyle(style)
+}
+
+// adds CSS styles for tag definition
+function registerStyles(name: string, def: TagDef) {
+ const rootClassName = makeClassName(name)
+ registerClassStyles(name, rootClassName, def)
for (const [partName, partDef] of Object.entries(def.parts ?? {})) {
const partClassName = makeClassName(name, partName)
- styles[partClassName] ??= makeStyle(partDef)
- for (let [selector, selectorDef] of Object.entries(partDef.selectors ?? {})) {
- selector = selector.replace('&', `.${partClassName}`)
- if (styles[selector]) throw `${selector} already defined!`
- styles[selector] = makeStyle(selectorDef)
- }
- for (const [state, style] of Object.entries(partDef.states ?? {}))
- styles[`${partClassName}${stateName(state)}`] = makeStyle(style)
+ registerClassStyles(name, partClassName, partDef)
}
for (const [variantName, variantConfig] of Object.entries(def.variants ?? {})) {
@@ -171,50 +170,22 @@ function registerStyles(name: string, def: TagDef) {
const variantDef = variantConfig as TagDef
const baseClassName = makeClassName(name)
const className = `${baseClassName}.${variantName}`
- styles[className] ??= makeStyle(variantDef)
- for (let [selector, selectorDef] of Object.entries(variantDef.selectors ?? {})) {
- selector = selector.replace('&', `.${className}`)
- if (styles[selector]) throw `${selector} already defined!`
- styles[selector] = makeStyle(selectorDef)
- }
- for (const [state, style] of Object.entries(variantDef.states ?? {}))
- styles[`${className}${stateName(state)}`] = makeStyle(style)
+ registerClassStyles(name, className, variantDef)
for (const [partName, partDef] of Object.entries(variantDef.parts ?? {})) {
const basePartClassName = makeClassName(name, partName)
const partClassName = `${basePartClassName}.${variantName}`
- styles[partClassName] ??= makeStyle(partDef)
- for (let [selector, selectorDef] of Object.entries(partDef.selectors ?? {})) {
- selector = selector.replace('&', `.${partClassName}`)
- if (styles[selector]) throw `${selector} already defined!`
- styles[selector] = makeStyle(selectorDef)
- }
- for (const [state, style] of Object.entries(partDef.states ?? {}))
- styles[`${partClassName}${stateName(state)}`] = makeStyle(style)
+ registerClassStyles(name, partClassName, partDef)
}
} else {
// Keyed variant - iterate over the keys
for (const [variantKey, variantDef] of Object.entries(variantConfig as Record)) {
const className = makeClassName(name, undefined, variantName, variantKey)
- styles[className] ??= makeStyle(variantDef)
- for (let [selector, selectorDef] of Object.entries(variantDef.selectors ?? {})) {
- selector = selector.replace('&', `.${className}`)
- if (styles[selector]) throw `${selector} already defined!`
- styles[selector] = makeStyle(selectorDef)
- }
- for (const [state, style] of Object.entries(variantDef.states ?? {}))
- styles[`${className}${stateName(state)}`] = makeStyle(style)
+ registerClassStyles(name, className, variantDef)
for (const [partName, partDef] of Object.entries(variantDef.parts ?? {})) {
const partClassName = makeClassName(name, partName, variantName, variantKey)
- styles[partClassName] ??= makeStyle(partDef)
- for (let [selector, selectorDef] of Object.entries(partDef.selectors ?? {})) {
- selector = selector.replace('&', `.${partClassName}`)
- if (styles[selector]) throw `${selector} already defined!`
- styles[selector] = makeStyle(selectorDef)
- }
- for (const [state, style] of Object.entries(partDef.states ?? {}))
- styles[`${partClassName}${stateName(state)}`] = makeStyle(style)
+ registerClassStyles(name, partClassName, partDef)
}
}
}