diff --git a/README.md b/README.md
index 171e0a5..1301f7a 100644
--- a/README.md
+++ b/README.md
@@ -6,16 +6,15 @@ Example:
import { define } from "forge"
export const Button = define("button", {
- layout: {
- padding: 20,
- },
- look: {
- background: "blue",
- },
+ base: "button",
+
+ padding: 20,
+ background: "blue",
+
variants: {
kind: {
- danger: { look: { background: "red" } },
- warning: { look: { background: "yellow" } },
+ danger: { background: "red" },
+ warning: { background: "yellow" },
}
},
})
@@ -26,35 +25,31 @@ export const Button = define("button", {
export const Profile = define("div", {
- layout: {
- padding: 50,
- },
- look: {
- background: "red",
- },
+ padding: 50,
+ background: "red",
parts: {
- header: { layout: { display: "flex" } },
- avatar: { layout: { width: 50 } },
- bio: { look: { color: "gray" } },
+ Header: { display: "flex" },
+ Avatar: { base: 'img', width: 50 },
+ Bio: { color: "gray" },
},
variants: {
size: {
small: {
- parts: { avatar: { layout: { width: 20 }}}
+ parts: { Avatar: { width: 20 }}
}
}
},
- render({ props, parts: { root, header, avatar, bio } }) {
+ render({ props, parts: { Root, Header, Avatar, Bio } }) {
return (
-
-
-
+
+
+
)
},
})
diff --git a/examples/button.tsx b/examples/button.tsx
index 12e2b3b..f51e4ef 100644
--- a/examples/button.tsx
+++ b/examples/button.tsx
@@ -4,109 +4,80 @@ import { Layout, ExampleSection } from './helpers'
const Button = define('Button', {
base: 'button',
- layout: {
- padding: "12px 24px",
- display: "inline-flex",
- alignItems: "center",
- justifyContent: "center",
- gap: 8,
- },
-
- look: {
- background: "#3b82f6",
- color: "white",
- border: "none",
- borderRadius: 8,
- fontSize: 16,
- fontWeight: 600,
- cursor: "pointer",
- transition: "all 0.2s ease",
- userSelect: "none",
- boxShadow: "0 4px 6px rgba(59, 130, 246, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
- transform: "translateY(0)",
- },
+ padding: "12px 24px",
+ display: "inline-flex",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: 8,
+ background: "#3b82f6",
+ color: "white",
+ border: "none",
+ borderRadius: 8,
+ fontSize: 16,
+ fontWeight: 600,
+ cursor: "pointer",
+ transition: "all 0.2s ease",
+ 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: {
":not(:disabled):hover": {
- look: {
- transform: 'translateY(-2px) !important',
- filter: 'brightness(1.05)'
- }
+ transform: 'translateY(-2px) !important',
+ filter: 'brightness(1.05)'
},
":not(:disabled):active": {
- look: {
- transform: 'translateY(1px) !important',
- boxShadow: '0 2px 3px rgba(0, 0, 0, 0.2) !important'
- }
+ transform: 'translateY(1px) !important',
+ boxShadow: '0 2px 3px rgba(0, 0, 0, 0.2) !important'
},
},
variants: {
intent: {
primary: {
- look: {
- background: "#3b82f6",
- color: "white",
- boxShadow: "0 4px 6px rgba(59, 130, 246, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
- },
+ background: "#3b82f6",
+ color: "white",
+ boxShadow: "0 4px 6px rgba(59, 130, 246, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
},
secondary: {
- look: {
- background: "#f3f4f6",
- color: "#374151",
- boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06)",
- },
+ background: "#f3f4f6",
+ color: "#374151",
+ boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06)",
},
danger: {
- look: {
- background: "#ef4444",
- color: "white",
- boxShadow: "0 4px 6px rgba(239, 68, 68, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
- },
+ background: "#ef4444",
+ color: "white",
+ boxShadow: "0 4px 6px rgba(239, 68, 68, 0.4), 0 2px 4px rgba(0, 0, 0, 0.1)",
},
ghost: {
- look: {
- background: "transparent",
- color: "#aaa",
- boxShadow: "0 4px 6px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1)",
- border: "1px solid #eee",
- },
+ background: "transparent",
+ color: "#aaa",
+ boxShadow: "0 4px 6px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1)",
+ border: "1px solid #eee",
},
},
size: {
small: {
- layout: {
- padding: "8px 16px",
- },
- look: {
- fontSize: 14,
- },
+ padding: "8px 16px",
+ fontSize: 14,
},
large: {
- layout: {
- padding: "16px 32px",
- },
- look: {
- fontSize: 18,
- },
+ padding: "16px 32px",
+ fontSize: 18,
},
},
disabled: {
- look: {
- opacity: 0.5,
- cursor: "not-allowed",
- },
+ opacity: 0.5,
+ cursor: "not-allowed",
},
},
})
const ButtonRow = define('ButtonRow', {
- layout: {
- display: 'flex',
- gap: 16,
- flexWrap: 'wrap',
- alignItems: 'center',
- }
+ display: 'flex',
+ gap: 16,
+ flexWrap: 'wrap',
+ alignItems: 'center',
})
export const ButtonExamplesPage = () => (
diff --git a/examples/helpers.tsx b/examples/helpers.tsx
index ea46141..b294d82 100644
--- a/examples/helpers.tsx
+++ b/examples/helpers.tsx
@@ -2,48 +2,35 @@ import { define, Styles } from '../src'
export const Body = define('Body', {
base: 'body',
- layout: {
- margin: 0,
- padding: '40px 20px',
- },
- look: {
- fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
- background: '#f3f4f6',
- }
+ margin: 0,
+ padding: '40px 20px',
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
+ background: '#f3f4f6',
})
const Container = define('Container', {
- layout: {
- maxWidth: 1200,
- margin: '0 auto'
- }
+ maxWidth: 1200,
+ margin: '0 auto'
})
export const Header = define('Header', {
base: 'h1',
- layout: {
- marginBottom: 40,
- },
- look: {
- color: '#111827'
- }
+
+ marginBottom: 40,
+ color: '#111827'
})
export const ExampleSection = define('ExampleSection', {
- layout: {
- marginBottom: 40
- },
+ marginBottom: 40,
+
parts: {
Header: {
base: 'h2',
- layout: {
- marginBottom: 16
- },
- look: {
- color: '#374151',
- fontSize: 18
- }
+
+ marginBottom: 16,
+ color: '#374151',
+ fontSize: 18
}
},
render({ props, parts: { Root, Header } }) {
diff --git a/examples/navigation.tsx b/examples/navigation.tsx
index 571285b..1d46b4a 100644
--- a/examples/navigation.tsx
+++ b/examples/navigation.tsx
@@ -2,37 +2,29 @@ import { define } from '../src'
import { Layout, ExampleSection } from './helpers'
const Tabs = define('Tabs', {
- layout: {
- display: 'flex',
- gap: 0,
- },
- look: {
- borderBottom: '2px solid #e5e7eb',
- },
+ display: 'flex',
+ gap: 0,
+ borderBottom: '2px solid #e5e7eb',
parts: {
Tab: {
base: 'button',
- layout: {
- padding: '12px 24px',
- position: 'relative',
- marginBottom: -2,
- },
- look: {
- background: 'transparent',
- border: 'none',
- borderBottom: '2px solid transparent',
- color: '#6b7280',
- fontSize: 14,
- fontWeight: 500,
- cursor: 'pointer',
- transition: 'all 0.2s ease',
- },
+
+ padding: '12px 24px',
+ position: 'relative',
+ marginBottom: -2,
+ background: 'transparent',
+ border: 'none',
+ borderBottom: '2px solid transparent',
+ color: '#6b7280',
+ fontSize: 14,
+ fontWeight: 500,
+ cursor: 'pointer',
+ transition: 'all 0.2s ease',
+
states: {
':hover': {
- look: {
- color: '#111827',
- }
+ color: '#111827',
}
}
}
@@ -42,10 +34,8 @@ const Tabs = define('Tabs', {
active: {
parts: {
Tab: {
- look: {
- color: '#3b82f6',
- borderBottom: '2px solid #3b82f6',
- },
+ color: '#3b82f6',
+ borderBottom: '2px solid #3b82f6',
}
}
}
@@ -69,34 +59,28 @@ const Tabs = define('Tabs', {
})
const Pills = define('Pills', {
- layout: {
- display: 'flex',
- gap: 8,
- flexWrap: 'wrap',
- },
+ display: 'flex',
+ gap: 8,
+ flexWrap: 'wrap',
parts: {
Pill: {
base: 'button',
- layout: {
- padding: '8px 16px',
- },
- look: {
- background: '#f3f4f6',
- border: 'none',
- borderRadius: 20,
- color: '#6b7280',
- fontSize: 14,
- fontWeight: 500,
- cursor: 'pointer',
- transition: 'all 0.2s ease',
- },
+
+ padding: '8px 16px',
+ background: '#f3f4f6',
+ border: 'none',
+ borderRadius: 20,
+ color: '#6b7280',
+ fontSize: 14,
+ fontWeight: 500,
+ cursor: 'pointer',
+ transition: 'all 0.2s ease',
+
states: {
':hover': {
- look: {
- background: '#e5e7eb',
- color: '#111827',
- }
+ background: '#e5e7eb',
+ color: '#111827',
}
}
}
@@ -106,16 +90,12 @@ const Pills = define('Pills', {
active: {
parts: {
Pill: {
- look: {
- background: '#3b82f6',
- color: 'white',
- },
+ background: '#3b82f6',
+ color: 'white',
states: {
':hover': {
- look: {
- background: '#2563eb',
- color: 'white',
- }
+ background: '#2563eb',
+ color: 'white',
}
}
}
@@ -141,52 +121,43 @@ const Pills = define('Pills', {
})
const VerticalNav = define('VerticalNav', {
- layout: {
- display: 'flex',
- flexDirection: 'column',
- gap: 4,
- width: 240,
- },
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 4,
+ width: 240,
parts: {
NavItem: {
base: 'a',
- layout: {
- padding: '12px 16px',
- display: 'flex',
- alignItems: 'center',
- gap: 12,
- },
- look: {
- background: 'transparent',
- borderRadius: 8,
- color: '#6b7280',
- fontSize: 14,
- fontWeight: 500,
- textDecoration: 'none',
- cursor: 'pointer',
- transition: 'all 0.2s ease',
- },
+
+ padding: '12px 16px',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 12,
+
+ background: 'transparent',
+ borderRadius: 8,
+ color: '#6b7280',
+ fontSize: 14,
+ fontWeight: 500,
+ textDecoration: 'none',
+ cursor: 'pointer',
+ transition: 'all 0.2s ease',
+
states: {
':hover': {
- look: {
- background: '#f3f4f6',
- color: '#111827',
- }
+ background: '#f3f4f6',
+ color: '#111827',
}
}
},
Icon: {
- layout: {
- width: 20,
- height: 20,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- },
- look: {
- fontSize: 18,
- }
+ width: 20,
+ height: 20,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ fontSize: 18,
}
},
@@ -194,16 +165,12 @@ const VerticalNav = define('VerticalNav', {
active: {
parts: {
NavItem: {
- look: {
- background: '#eff6ff',
- color: '#3b82f6',
- },
+ background: '#eff6ff',
+ color: '#3b82f6',
states: {
':hover': {
- look: {
- background: '#dbeafe',
- color: '#2563eb',
- }
+ background: '#dbeafe',
+ color: '#2563eb',
}
}
}
@@ -230,43 +197,35 @@ const VerticalNav = define('VerticalNav', {
})
const Breadcrumbs = define('Breadcrumbs', {
- layout: {
- display: 'flex',
- alignItems: 'center',
- gap: 8,
- flexWrap: 'wrap',
- },
+ display: 'flex',
+ alignItems: 'center',
+ gap: 8,
+ flexWrap: 'wrap',
parts: {
Item: {
base: 'a',
- look: {
- color: '#6b7280',
- fontSize: 14,
- textDecoration: 'none',
- transition: 'color 0.2s ease',
- },
+
+ color: '#6b7280',
+ fontSize: 14,
+ textDecoration: 'none',
+ transition: 'color 0.2s ease',
+
states: {
':hover': {
- look: {
- color: '#3b82f6',
- }
+ color: '#3b82f6',
}
}
},
Separator: {
- look: {
- color: '#d1d5db',
- fontSize: 14,
- userSelect: 'none',
- }
+ color: '#d1d5db',
+ fontSize: 14,
+ userSelect: 'none',
},
Current: {
- look: {
- color: '#111827',
- fontSize: 14,
- fontWeight: 500,
- }
+ color: '#111827',
+ fontSize: 14,
+ fontWeight: 500,
}
},
diff --git a/examples/profile.tsx b/examples/profile.tsx
index a79fdfc..7422174 100644
--- a/examples/profile.tsx
+++ b/examples/profile.tsx
@@ -4,167 +4,129 @@ import { Layout, ExampleSection } from './helpers'
const UserProfile = define('UserProfile', {
base: 'div',
- layout: {
- padding: 24,
- maxWidth: 600,
- margin: "0 auto",
- },
-
- look: {
- background: "white",
- borderRadius: 12,
- boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
- },
+ padding: 24,
+ maxWidth: 600,
+ margin: "0 auto",
+ background: "white",
+ borderRadius: 12,
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
parts: {
Header: {
- layout: {
- display: "flex",
- alignItems: "center",
- gap: 16,
- marginBottom: 16,
- },
+ display: "flex",
+ alignItems: "center",
+ gap: 16,
+ marginBottom: 16,
},
Avatar: {
base: 'img',
- layout: {
- width: 64,
- height: 64,
- },
- look: {
- borderRadius: "50%",
- objectFit: "cover",
- border: "3px solid #e5e7eb",
- },
+ width: 64,
+ height: 64,
+ borderRadius: "50%",
+ objectFit: "cover",
+ border: "3px solid #e5e7eb",
},
Info: {
- layout: {
- flex: 1,
- },
+ flex: 1,
},
Name: {
- layout: {
- marginBottom: 4,
- },
- look: {
- fontSize: 20,
- fontWeight: 600,
- color: "#111827",
- },
+ marginBottom: 4,
+ fontSize: 20,
+ fontWeight: 600,
+ color: "#111827",
},
Handle: {
- look: {
- fontSize: 14,
- color: "#6b7280",
- },
+ fontSize: 14,
+ color: "#6b7280",
},
Bio: {
- layout: {
- marginBottom: 16,
- width: "100%",
- },
- look: {
- fontSize: 14,
- lineHeight: 1.6,
- color: "#374151",
- wordWrap: "break-word",
- },
+ marginBottom: 16,
+ width: "100%",
+ fontSize: 14,
+ lineHeight: 1.6,
+ color: "#374151",
+ wordWrap: "break-word",
},
Stats: {
- layout: {
- display: "flex",
- gap: 24,
- paddingTop: 16,
- },
- look: {
- borderTop: "1px solid #e5e7eb",
- },
+ display: "flex",
+ gap: 24,
+ paddingTop: 16,
+ borderTop: "1px solid #e5e7eb",
},
Stat: {
- layout: {
- display: "flex",
- flexDirection: "column",
- gap: 4,
- },
+ display: "flex",
+ flexDirection: "column",
+ gap: 4,
},
StatValue: {
- look: {
- fontSize: 18,
- fontWeight: 600,
- color: "#111827",
- },
+ fontSize: 18,
+ fontWeight: 600,
+ color: "#111827",
},
StatLabel: {
- look: {
- fontSize: 12,
- color: "#6b7280",
- textTransform: "uppercase",
- },
+ fontSize: 12,
+ color: "#6b7280",
+ textTransform: "uppercase",
},
},
variants: {
size: {
compact: {
- layout: { padding: 16, maxWidth: 300 },
+ padding: 16,
+ maxWidth: 300,
parts: {
- Avatar: { layout: { width: 48, height: 48 } },
- Name: { look: { fontSize: 16 } },
- Bio: { look: { fontSize: 13 } },
+ Avatar: { width: 48, height: 48 },
+ Name: { fontSize: 16 },
+ Bio: { fontSize: 13 },
},
},
skinny: {
- layout: { padding: 20, maxWidth: 125 },
+ padding: 20,
+ maxWidth: 125,
parts: {
Header: {
- layout: {
- flexDirection: "column",
- alignItems: "center",
- gap: 12,
- }
+ flexDirection: "column",
+ alignItems: "center",
+ gap: 12,
},
- Avatar: { layout: { width: 80, height: 80 } },
- Info: { layout: { flex: 0, width: "100%" } },
- Name: { look: { textAlign: "center", fontSize: 18 } },
- Handle: { look: { textAlign: "center" } },
- Bio: { look: { textAlign: "center", fontSize: 13 } },
+ Avatar: { width: 80, height: 80 },
+ Info: { flex: 0, width: "100%" },
+ Name: { textAlign: "center", fontSize: 18 },
+ Handle: { textAlign: "center" },
+ Bio: { textAlign: "center", fontSize: 13 },
Stats: {
- layout: {
- flexDirection: "column",
- gap: 16,
- }
+ flexDirection: "column",
+ gap: 16,
},
- Stat: { layout: { alignItems: "center" } },
+ Stat: { alignItems: "center" },
},
},
large: {
- layout: { padding: 32, maxWidth: 800 },
+ padding: 32,
+ maxWidth: 800,
parts: {
- Avatar: { layout: { width: 96, height: 96 } },
- Name: { look: { fontSize: 24 } },
+ Avatar: { width: 96, height: 96 },
+ Name: { fontSize: 24 },
},
},
},
verified: {
parts: {
Avatar: {
- look: {
- border: "3px solid #3b82f6",
- },
+ border: "3px solid #3b82f6",
},
},
},
theme: {
dark: {
- look: {
- background: "#1f2937",
- },
+ background: "#1f2937",
parts: {
- Name: { look: { color: "#f9fafb" } },
- Handle: { look: { color: "#9ca3af" } },
- Bio: { look: { color: "#d1d5db" } },
- Stats: { look: { borderTop: "1px solid #374151" } },
- StatValue: { look: { color: "#f9fafb" } },
+ Name: { color: "#f9fafb" },
+ Handle: { color: "#9ca3af" },
+ Bio: { color: "#d1d5db" },
+ Stats: { borderTop: "1px solid #374151" },
+ StatValue: { color: "#f9fafb" },
},
},
},
diff --git a/src/index.tsx b/src/index.tsx
index 107ec27..c75339c 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,5 +1,5 @@
import type { JSX } from 'hono/jsx'
-import { type TagDef, UnitlessProps } from './types'
+import { type TagDef, UnitlessProps, NonStyleKeys } from './types'
export const styles: Record> = {}
export const Styles = () =>
@@ -45,8 +45,10 @@ function camelToDash(name: string): string {
function makeStyle(def: TagDef) {
const style: Record = {}
- for (const [name, value] of Object.entries(Object.assign({}, def.layout ?? {}, def.look ?? {})))
+ for (const [name, value] of Object.entries(def)) {
+ if (NonStyleKeys.has(name)) continue
style[camelToDash(name)] = `${typeof value === 'number' && !UnitlessProps.has(name) ? `${value}px` : value}`
+ }
return style
}
@@ -57,28 +59,37 @@ function makeComponent(baseName: string, rootDef: TagDef, rootProps: Record {
+ const classNames = [makeClassName(baseName, partName)]
- for (const [key, value] of Object.entries(rootProps)) {
- const variantConfig = rootDef.variants?.[key]
- if (!variantConfig) continue
+ const allProps = { ...rootProps, ...props }
- const variantName = key
- const variantKey = value
+ for (const [key, value] of Object.entries(allProps)) {
+ const variantConfig = rootDef.variants?.[key]
+ if (!variantConfig) continue
- let variantDef: TagDef | undefined
- if ('parts' in variantConfig || 'layout' in variantConfig || 'look' in variantConfig) {
- if (value === true) variantDef = variantConfig as TagDef
- } else {
- variantDef = (variantConfig as Record)[value as string]
+ // Remove variant prop from being passed to HTML element
+ delete props[key]
+
+ const variantName = key
+ const variantKey = value
+
+ let variantDef: TagDef | undefined
+ // Distinguish boolean variants from keyed variants:
+ // - Boolean variants: component({ variant: true }) → variantConfig is a TagDef
+ // - Keyed variants: component({ variant: 'key' }) → variantConfig[key] is a TagDef
+ if (value === true) {
+ variantDef = variantConfig as TagDef
+ } else if (typeof value === 'string') {
+ variantDef = (variantConfig as Record)[value]
+ }
+ if (!variantDef) continue
+
+ classNames.push(variantKey === true ? variantName : `${variantName}-${variantKey}`)
}
- if (!variantDef) continue
- classNames.push(variantKey === true ? variantName : `${variantName}-${variantKey}`)
+ return {children}
}
-
- return ({ children, ...props }: { children: any, [key: string]: any }) =>
- {children}
}
// adds CSS styles for tag definition
@@ -98,13 +109,17 @@ function registerStyles(name: string, def: TagDef) {
}
for (const [variantName, variantConfig] of Object.entries(def.variants ?? {})) {
- // Check if it's a boolean variant (has layout/look/parts directly) or a keyed variant
- if ('parts' in variantConfig || 'layout' in variantConfig || 'look' in variantConfig) {
- // Boolean variant - treat variantConfig as TagDef
+ // Detect boolean vs keyed variants by checking if config has structural keys or looks like a TagDef
+ const isBooleanVariant = 'parts' in variantConfig || 'styles' in variantConfig || 'states' in variantConfig ||
+ // If first key is camelCase or contains CSS-like properties, treat as boolean variant
+ Object.keys(variantConfig).some(k => k !== k.toLowerCase() || typeof (variantConfig as any)[k] !== 'object')
+
+ if (isBooleanVariant) {
+ // Boolean variant - variantConfig is a TagDef
const variantDef = variantConfig as TagDef
const baseClassName = makeClassName(name)
const className = `${baseClassName}.${variantName}`
- styles[className] ??= makeStyle({ layout: variantDef.layout, look: variantDef.look })
+ styles[className] ??= makeStyle(variantDef)
for (const [state, style] of Object.entries(variantDef.states ?? {}))
styles[`${className}${state}`] = makeStyle(style)
@@ -119,7 +134,7 @@ function registerStyles(name: string, def: TagDef) {
// 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({ layout: variantDef.layout, look: variantDef.look })
+ styles[className] ??= makeStyle(variantDef)
for (const [state, style] of Object.entries(variantDef.states ?? {}))
styles[`${className}${state}`] = makeStyle(style)
diff --git a/src/tests/index.test.tsx b/src/tests/index.test.tsx
index 19424b0..9a130c6 100644
--- a/src/tests/index.test.tsx
+++ b/src/tests/index.test.tsx
@@ -5,7 +5,7 @@ import { renderToString, getStylesCSS, parseCSS } from './test_helpers'
describe('define - basic functionality', () => {
test('creates a component function', () => {
const Component = define({
- layout: { display: 'flex' }
+ display: 'flex'
})
expect(typeof Component).toBe('function')
@@ -13,7 +13,7 @@ describe('define - basic functionality', () => {
test('component returns a JSX element', () => {
const Component = define({
- layout: { display: 'flex' }
+ display: 'flex'
})
const result = Component({})
@@ -23,7 +23,7 @@ describe('define - basic functionality', () => {
test('applies className to rendered element', () => {
const Component = define('MyComponent', {
- layout: { display: 'flex' }
+ display: 'flex'
})
const html = renderToString(Component({}))
@@ -31,8 +31,8 @@ describe('define - basic functionality', () => {
})
test('generates unique anonymous component names', () => {
- const Component1 = define({ layout: { display: 'flex' } })
- const Component2 = define({ layout: { display: 'block' } })
+ const Component1 = define({ display: 'flex' })
+ const Component2 = define({ display: 'block' })
const html1 = renderToString(Component1({}))
const html2 = renderToString(Component2({}))
@@ -45,7 +45,7 @@ describe('define - basic functionality', () => {
test('renders default div element', () => {
const Component = define('DivTest', {
- layout: { display: 'flex' }
+ display: 'flex'
})
const html = renderToString(Component({}))
@@ -56,7 +56,7 @@ describe('define - basic functionality', () => {
test('respects custom base element', () => {
const Component = define('ButtonTest', {
base: 'button',
- look: { color: 'blue' }
+ color: 'blue'
})
const html = renderToString(Component({}))
@@ -83,11 +83,9 @@ describe('define - basic functionality', () => {
describe('CSS generation - camelCase to kebab-case', () => {
test('converts camelCase properties to kebab-case', () => {
define('CamelTest', {
- layout: {
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'space-between'
- }
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'space-between'
})
const css = getStylesCSS()
@@ -98,10 +96,8 @@ describe('CSS generation - camelCase to kebab-case', () => {
test('handles consecutive capital letters', () => {
define('ConsecutiveTest', {
- look: {
- backgroundColor: 'red',
- borderRadius: 5
- }
+ backgroundColor: 'red',
+ borderRadius: 5
})
const css = getStylesCSS()
@@ -113,12 +109,10 @@ describe('CSS generation - camelCase to kebab-case', () => {
describe('CSS generation - numeric values and units', () => {
test('adds px unit to numeric layout values', () => {
define('NumericTest', {
- layout: {
- width: 100,
- height: 200,
- padding: 16,
- margin: 8
- }
+ width: 100,
+ height: 200,
+ padding: 16,
+ margin: 8
})
const styles = parseCSS(getStylesCSS())
@@ -130,12 +124,10 @@ describe('CSS generation - numeric values and units', () => {
test('preserves string values without adding px', () => {
define('StringTest', {
- layout: {
- width: '100%',
- height: 'auto',
- margin: '0 auto',
- padding: '1rem'
- }
+ width: '100%',
+ height: 'auto',
+ margin: '0 auto',
+ padding: '1rem'
})
const styles = parseCSS(getStylesCSS())
@@ -147,18 +139,14 @@ describe('CSS generation - numeric values and units', () => {
test('does not add px to unitless properties', () => {
define('UnitlessTest', {
- layout: {
- flex: 1,
- flexGrow: 2,
- flexShrink: 1,
- zIndex: 10,
- order: 3
- },
- look: {
- opacity: 0.5,
- fontWeight: 700,
- lineHeight: 1.5
- }
+ flex: 1,
+ flexGrow: 2,
+ flexShrink: 1,
+ zIndex: 10,
+ order: 3,
+ opacity: 0.5,
+ fontWeight: 700,
+ lineHeight: 1.5
})
const styles = parseCSS(getStylesCSS())
@@ -174,14 +162,10 @@ describe('CSS generation - numeric values and units', () => {
test('handles numeric zero values correctly', () => {
define('ZeroTest', {
- layout: {
- margin: 0,
- padding: 0,
- zIndex: 0
- },
- look: {
- opacity: 0
- }
+ margin: 0,
+ padding: 0,
+ zIndex: 0,
+ opacity: 0
})
const styles = parseCSS(getStylesCSS())
@@ -195,12 +179,10 @@ describe('CSS generation - numeric values and units', () => {
describe('CSS generation - layout and look', () => {
test('generates CSS for layout properties', () => {
define('LayoutTest', {
- layout: {
- display: 'flex',
- flexDirection: 'column',
- gap: 16,
- padding: 20
- }
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 16,
+ padding: 20
})
const css = getStylesCSS()
@@ -213,12 +195,10 @@ describe('CSS generation - layout and look', () => {
test('generates CSS for look properties', () => {
define('LookTest', {
- look: {
- color: 'blue',
- backgroundColor: 'white',
- fontSize: 16,
- fontWeight: 600
- }
+ color: 'blue',
+ backgroundColor: 'white',
+ fontSize: 16,
+ fontWeight: 600
})
const css = getStylesCSS()
@@ -231,14 +211,10 @@ describe('CSS generation - layout and look', () => {
test('combines layout and look properties', () => {
define('CombinedTest', {
- layout: {
- display: 'flex',
- padding: 16
- },
- look: {
- color: 'blue',
- backgroundColor: 'white'
- }
+ display: 'flex',
+ padding: 16,
+ color: 'blue',
+ backgroundColor: 'white'
})
const styles = parseCSS(getStylesCSS())
@@ -253,11 +229,11 @@ describe('CSS generation - layout and look', () => {
describe('CSS generation - parts', () => {
test('generates separate CSS for each part', () => {
define('PartTest', {
- layout: { display: 'flex' },
+ display: 'flex',
parts: {
- Header: { base: 'header', look: { color: 'red', fontSize: 20 } },
- Body: { base: 'main', layout: { padding: 20 } },
- Footer: { base: 'footer', look: { fontSize: 12 } }
+ Header: { base: 'header', color: 'red', fontSize: 20 },
+ Body: { base: 'main', padding: 20 },
+ Footer: { base: 'footer', fontSize: 12 }
}
})
@@ -271,7 +247,7 @@ describe('CSS generation - parts', () => {
test('part className format is ComponentName_PartName', () => {
define('ComponentWithParts', {
parts: {
- MyPart: { look: { color: 'green' } }
+ MyPart: { color: 'green' }
}
})
@@ -345,10 +321,10 @@ describe('components with parts', () => {
describe('variants - boolean variants', () => {
test('applies boolean variant class when true', () => {
const Component = define('BoolVariant', {
- layout: { display: 'flex' },
+ display: 'flex',
variants: {
primary: {
- look: { color: 'blue' }
+ color: 'blue'
}
}
})
@@ -362,7 +338,7 @@ describe('variants - boolean variants', () => {
const Component = define('BoolVariantFalse', {
variants: {
active: {
- look: { backgroundColor: 'green' }
+ backgroundColor: 'green'
}
}
})
@@ -379,10 +355,10 @@ describe('variants - boolean variants', () => {
test('generates CSS for component with boolean variant', () => {
define('BoolVariantCSS', {
- layout: { display: 'block' },
+ display: 'block',
variants: {
active: {
- look: { backgroundColor: 'green' }
+ backgroundColor: 'green'
}
}
})
@@ -398,9 +374,9 @@ describe('variants - string/enum variants', () => {
const Component = define('StringVariant', {
variants: {
size: {
- small: { layout: { padding: 8 } },
- medium: { layout: { padding: 16 } },
- large: { layout: { padding: 24 } }
+ small: { padding: 8 },
+ medium: { padding: 16 },
+ large: { padding: 24 }
}
}
})
@@ -416,9 +392,9 @@ describe('variants - string/enum variants', () => {
define('ColorVariant', {
variants: {
color: {
- red: { look: { color: 'red', backgroundColor: '#ffeeee' } },
- blue: { look: { color: 'blue', backgroundColor: '#eeeeff' } },
- green: { look: { color: 'green', backgroundColor: '#eeffee' } }
+ red: { color: 'red', backgroundColor: '#ffeeee' },
+ blue: { color: 'blue', backgroundColor: '#eeeeff' },
+ green: { color: 'green', backgroundColor: '#eeffee' }
}
}
})
@@ -433,12 +409,12 @@ describe('variants - string/enum variants', () => {
const Component = define('MultiVariant', {
variants: {
size: {
- small: { layout: { padding: 8 } },
- large: { layout: { padding: 24 } }
+ small: { padding: 8 },
+ large: { padding: 24 }
},
color: {
- red: { look: { color: 'red' } },
- blue: { look: { color: 'blue' } }
+ red: { color: 'red' },
+ blue: { color: 'blue' }
}
}
})
@@ -452,7 +428,7 @@ describe('variants - string/enum variants', () => {
const Component = define('UndefinedVariant', {
variants: {
size: {
- small: { layout: { padding: 8 } }
+ small: { padding: 8 }
}
}
})
@@ -466,7 +442,7 @@ describe('variants - string/enum variants', () => {
const Component = define('NonVariantProps', {
variants: {
size: {
- small: { layout: { padding: 8 } }
+ small: { padding: 8 }
}
}
})
@@ -488,12 +464,12 @@ describe('variants with parts', () => {
theme: {
dark: {
parts: {
- Header: { look: { color: 'white', backgroundColor: 'black' } }
+ Header: { color: 'white', backgroundColor: 'black' }
}
},
light: {
parts: {
- Header: { look: { color: 'black', backgroundColor: 'white' } }
+ Header: { color: 'black', backgroundColor: 'white' }
}
}
}
@@ -514,7 +490,7 @@ describe('variants with parts', () => {
},
variants: {
size: {
- large: { layout: { padding: 20 } }
+ large: { padding: 20 }
}
},
render: ({ props, parts }) => {
@@ -531,7 +507,7 @@ describe('variants with parts', () => {
describe('custom render function', () => {
test('uses custom render when provided', () => {
const Component = define('CustomRender', {
- layout: { display: 'flex' },
+ display: 'flex',
render: ({ props, parts }) => {
return {props.children}
}
@@ -596,7 +572,7 @@ describe('Styles component', () => {
})
test('Styles renders to HTML with CSS', () => {
- define('StylesComp1', { layout: { width: 100 } })
+ define('StylesComp1', { width: 100 })
const html = renderToString(define.Styles())
expect(html).toContain('