big stuff
This commit is contained in:
parent
399a398174
commit
4cf3e0e832
|
|
@ -1,5 +1,5 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { Layout, ExampleSection } from './helpers'
|
import { ExampleSection } from './ssr/helpers'
|
||||||
|
|
||||||
const Button = define('Button', {
|
const Button = define('Button', {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
|
|
@ -80,8 +80,8 @@ const ButtonRow = define('ButtonRow', {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ButtonExamplesPage = () => (
|
export const ButtonExamplesContent = () => (
|
||||||
<Layout title="Forge Button Component Examples">
|
<>
|
||||||
<ExampleSection title="Intents">
|
<ExampleSection title="Intents">
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
<Button intent="primary">Primary</Button>
|
<Button intent="primary">Primary</Button>
|
||||||
|
|
@ -117,5 +117,5 @@ export const ButtonExamplesPage = () => (
|
||||||
<Button intent="ghost" disabled>Ghost Disabled</Button>
|
<Button intent="ghost" disabled>Ghost Disabled</Button>
|
||||||
</ButtonRow>
|
</ButtonRow>
|
||||||
</ExampleSection>
|
</ExampleSection>
|
||||||
</Layout >
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,25 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { Layout, ExampleSection } from './helpers'
|
import { ExampleSection } from './ssr/helpers'
|
||||||
|
|
||||||
const Tabs = define('Tabs', {
|
const TabSwitcher = define('TabSwitcher', {
|
||||||
|
parts: {
|
||||||
|
Input: {
|
||||||
|
base: 'input',
|
||||||
|
display: 'none', // Hide radio inputs
|
||||||
|
},
|
||||||
|
TabBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 0,
|
gap: 0,
|
||||||
borderBottom: '2px solid #e5e7eb',
|
borderBottom: '2px solid #e5e7eb',
|
||||||
|
marginBottom: 24,
|
||||||
parts: {
|
},
|
||||||
Tab: {
|
TabLabel: {
|
||||||
base: 'button',
|
base: 'label',
|
||||||
|
|
||||||
padding: '12px 24px',
|
padding: '12px 24px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
marginBottom: -2,
|
marginBottom: -2,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: 'none',
|
|
||||||
borderBottom: '2px solid transparent',
|
borderBottom: '2px solid transparent',
|
||||||
color: '#6b7280',
|
color: '#6b7280',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
@ -27,50 +32,76 @@ const Tabs = define('Tabs', {
|
||||||
color: '#111827',
|
color: '#111827',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Content: {
|
||||||
|
display: 'none',
|
||||||
|
padding: 20,
|
||||||
|
background: '#f9fafb',
|
||||||
|
borderRadius: 8,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
variants: {
|
render({ props, parts: { Root, Input, TabBar, TabLabel, Content } }) {
|
||||||
active: {
|
|
||||||
parts: {
|
|
||||||
Tab: {
|
|
||||||
color: '#3b82f6',
|
|
||||||
borderBottom: '2px solid #3b82f6',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render({ props, parts: { Root, Tab } }) {
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<script dangerouslySetInnerHTML={{
|
{/* Hidden radio inputs */}
|
||||||
__html: `
|
{props.tabs?.map((tab: any, index: number) => (
|
||||||
const ClickTab = (label) => console.log('Tab clicked:', label)
|
<Input
|
||||||
`}} />
|
key={`input-${tab.id}`}
|
||||||
|
type="radio"
|
||||||
{props.items?.map((item: any) => (
|
id={`tab-${props.name}-${tab.id}`}
|
||||||
<Tab
|
name={props.name || 'tabs'}
|
||||||
key={item.id}
|
checked={index === 0}
|
||||||
active={item.active}
|
/>
|
||||||
onClick={`ClickTab("${item.label}")`}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</Tab>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* Tab labels */}
|
||||||
|
<TabBar>
|
||||||
|
{props.tabs?.map((tab: any) => (
|
||||||
|
<TabLabel key={`label-${tab.id}`} for={`tab-${props.name}-${tab.id}`}>
|
||||||
|
{tab.label}
|
||||||
|
</TabLabel>
|
||||||
|
))}
|
||||||
|
</TabBar>
|
||||||
|
|
||||||
|
{/* Tab content panels */}
|
||||||
|
{props.tabs?.map((tab: any) => (
|
||||||
|
<Content key={`content-${tab.id}`} class={`content-${props.name}-${tab.id}`}>
|
||||||
|
{tab.content}
|
||||||
|
</Content>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* CSS to show active tab and content */}
|
||||||
|
<style dangerouslySetInnerHTML={{
|
||||||
|
__html: props.tabs?.map((tab: any) => `
|
||||||
|
input#tab-${props.name}-${tab.id}:checked + .TabBar label[for="tab-${props.name}-${tab.id}"],
|
||||||
|
input#tab-${props.name}-${tab.id}:checked ~ .TabBar label[for="tab-${props.name}-${tab.id}"] {
|
||||||
|
color: #3b82f6 !important;
|
||||||
|
border-bottom: 2px solid #3b82f6 !important;
|
||||||
|
}
|
||||||
|
input#tab-${props.name}-${tab.id}:checked ~ .content-${props.name}-${tab.id} {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
`).join('\n')
|
||||||
|
}} />
|
||||||
</Root>
|
</Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const Pills = define('Pills', {
|
const Pills = define('Pills', {
|
||||||
|
parts: {
|
||||||
|
Input: {
|
||||||
|
base: 'input',
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
PillBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
parts: {
|
PillLabel: {
|
||||||
Pill: {
|
base: 'label',
|
||||||
base: 'button',
|
|
||||||
|
|
||||||
padding: '8px 16px',
|
padding: '8px 16px',
|
||||||
background: '#f3f4f6',
|
background: '#f3f4f6',
|
||||||
|
|
@ -91,49 +122,55 @@ const Pills = define('Pills', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
variants: {
|
render({ props, parts: { Root, Input, PillBar, PillLabel } }) {
|
||||||
active: {
|
|
||||||
parts: {
|
|
||||||
Pill: {
|
|
||||||
background: '#3b82f6',
|
|
||||||
color: 'white',
|
|
||||||
states: {
|
|
||||||
':hover': {
|
|
||||||
background: '#2563eb',
|
|
||||||
color: 'white',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render({ props, parts: { Root, Pill } }) {
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
{props.items?.map((item: any) => (
|
{props.items?.map((item: any, index: number) => (
|
||||||
<Pill
|
<Input
|
||||||
key={item.id}
|
key={`input-${item.id}`}
|
||||||
active={item.active}
|
type="radio"
|
||||||
onclick={() => console.log('Pill clicked:', item.label)}
|
id={`pill-${props.name}-${item.id}`}
|
||||||
>
|
name={props.name || 'pills'}
|
||||||
{item.label}
|
checked={index === 0}
|
||||||
</Pill>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<PillBar>
|
||||||
|
{props.items?.map((item: any) => (
|
||||||
|
<PillLabel key={`label-${item.id}`} for={`pill-${props.name}-${item.id}`}>
|
||||||
|
{item.label}
|
||||||
|
</PillLabel>
|
||||||
|
))}
|
||||||
|
</PillBar>
|
||||||
|
|
||||||
|
<style dangerouslySetInnerHTML={{
|
||||||
|
__html: props.items?.map((item: any) => `
|
||||||
|
input#pill-${props.name}-${item.id}:checked + .PillBar label[for="pill-${props.name}-${item.id}"],
|
||||||
|
input#pill-${props.name}-${item.id}:checked ~ .PillBar label[for="pill-${props.name}-${item.id}"] {
|
||||||
|
background: #3b82f6 !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
`).join('\n')
|
||||||
|
}} />
|
||||||
</Root>
|
</Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const VerticalNav = define('VerticalNav', {
|
const VerticalNav = define('VerticalNav', {
|
||||||
|
parts: {
|
||||||
|
Input: {
|
||||||
|
base: 'input',
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
NavBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
width: 240,
|
width: 240,
|
||||||
|
},
|
||||||
parts: {
|
NavLabel: {
|
||||||
NavItem: {
|
base: 'label',
|
||||||
base: 'a',
|
|
||||||
|
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
@ -145,7 +182,6 @@ const VerticalNav = define('VerticalNav', {
|
||||||
color: '#6b7280',
|
color: '#6b7280',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
textDecoration: 'none',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
|
|
@ -166,36 +202,37 @@ const VerticalNav = define('VerticalNav', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
variants: {
|
render({ props, parts: { Root, Input, NavBar, NavLabel, Icon } }) {
|
||||||
active: {
|
|
||||||
parts: {
|
|
||||||
NavItem: {
|
|
||||||
background: '#eff6ff',
|
|
||||||
color: '#3b82f6',
|
|
||||||
states: {
|
|
||||||
':hover': {
|
|
||||||
background: '#dbeafe',
|
|
||||||
color: '#2563eb',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render({ props, parts: { Root, NavItem, Icon } }) {
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
|
{props.items?.map((item: any, index: number) => (
|
||||||
|
<Input
|
||||||
|
key={`input-${item.id}`}
|
||||||
|
type="radio"
|
||||||
|
id={`nav-${props.name}-${item.id}`}
|
||||||
|
name={props.name || 'nav'}
|
||||||
|
checked={index === 0}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<NavBar>
|
||||||
{props.items?.map((item: any) => (
|
{props.items?.map((item: any) => (
|
||||||
<NavItem
|
<NavLabel key={`label-${item.id}`} for={`nav-${props.name}-${item.id}`}>
|
||||||
key={item.id}
|
|
||||||
active={item.active}
|
|
||||||
href={item.href || '#'}
|
|
||||||
>
|
|
||||||
{item.icon && <Icon>{item.icon}</Icon>}
|
{item.icon && <Icon>{item.icon}</Icon>}
|
||||||
{item.label}
|
{item.label}
|
||||||
</NavItem>
|
</NavLabel>
|
||||||
))}
|
))}
|
||||||
|
</NavBar>
|
||||||
|
|
||||||
|
<style dangerouslySetInnerHTML={{
|
||||||
|
__html: props.items?.map((item: any) => `
|
||||||
|
input#nav-${props.name}-${item.id}:checked + .NavBar label[for="nav-${props.name}-${item.id}"],
|
||||||
|
input#nav-${props.name}-${item.id}:checked ~ .NavBar label[for="nav-${props.name}-${item.id}"] {
|
||||||
|
background: #eff6ff !important;
|
||||||
|
color: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
`).join('\n')
|
||||||
|
}} />
|
||||||
</Root>
|
</Root>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -256,8 +293,188 @@ const Breadcrumbs = define('Breadcrumbs', {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const NavigationExamplesPage = () => (
|
const Tabs = define('Tabs', {
|
||||||
<Layout title="Forge Navigation Examples">
|
display: 'flex',
|
||||||
|
gap: 0,
|
||||||
|
borderBottom: '2px solid #e5e7eb',
|
||||||
|
|
||||||
|
parts: {
|
||||||
|
Tab: {
|
||||||
|
base: 'button',
|
||||||
|
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': {
|
||||||
|
color: '#111827',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
active: {
|
||||||
|
parts: {
|
||||||
|
Tab: {
|
||||||
|
color: '#3b82f6',
|
||||||
|
borderBottom: '2px solid #3b82f6',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ props, parts: { Root, Tab } }) {
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
{props.items?.map((item: any) => (
|
||||||
|
<Tab key={item.id} active={item.active}>
|
||||||
|
{item.label}
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const SimplePills = define('SimplePills', {
|
||||||
|
display: 'flex',
|
||||||
|
gap: 8,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
|
||||||
|
parts: {
|
||||||
|
Pill: {
|
||||||
|
base: 'button',
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#f3f4f6',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: 20,
|
||||||
|
color: '#6b7280',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
':hover': {
|
||||||
|
background: '#e5e7eb',
|
||||||
|
color: '#111827',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
active: {
|
||||||
|
parts: {
|
||||||
|
Pill: {
|
||||||
|
background: '#3b82f6',
|
||||||
|
color: 'white',
|
||||||
|
states: {
|
||||||
|
':hover': {
|
||||||
|
background: '#2563eb',
|
||||||
|
color: 'white',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ props, parts: { Root, Pill } }) {
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
{props.items?.map((item: any) => (
|
||||||
|
<Pill key={item.id} active={item.active}>
|
||||||
|
{item.label}
|
||||||
|
</Pill>
|
||||||
|
))}
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const SimpleVerticalNav = define('SimpleVerticalNav', {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
width: 240,
|
||||||
|
|
||||||
|
parts: {
|
||||||
|
NavItem: {
|
||||||
|
base: 'button',
|
||||||
|
padding: '12px 16px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
background: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: 8,
|
||||||
|
color: '#6b7280',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
textAlign: 'left',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
':hover': {
|
||||||
|
background: '#f3f4f6',
|
||||||
|
color: '#111827',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Icon: {
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 18,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
active: {
|
||||||
|
parts: {
|
||||||
|
NavItem: {
|
||||||
|
background: '#eff6ff',
|
||||||
|
color: '#3b82f6',
|
||||||
|
states: {
|
||||||
|
':hover': {
|
||||||
|
background: '#dbeafe',
|
||||||
|
color: '#2563eb',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ props, parts: { Root, NavItem, Icon } }) {
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
{props.items?.map((item: any) => (
|
||||||
|
<NavItem key={item.id} active={item.active}>
|
||||||
|
{item.icon && <Icon>{item.icon}</Icon>}
|
||||||
|
{item.label}
|
||||||
|
</NavItem>
|
||||||
|
))}
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const NavigationExamplesContent = () => (
|
||||||
|
<>
|
||||||
<ExampleSection title="Tabs">
|
<ExampleSection title="Tabs">
|
||||||
<Tabs items={[
|
<Tabs items={[
|
||||||
{ id: 1, label: 'Overview', active: true },
|
{ id: 1, label: 'Overview', active: true },
|
||||||
|
|
@ -268,7 +485,7 @@ export const NavigationExamplesPage = () => (
|
||||||
</ExampleSection>
|
</ExampleSection>
|
||||||
|
|
||||||
<ExampleSection title="Pills">
|
<ExampleSection title="Pills">
|
||||||
<Pills items={[
|
<SimplePills items={[
|
||||||
{ id: 1, label: 'All', active: true },
|
{ id: 1, label: 'All', active: true },
|
||||||
{ id: 2, label: 'Active', active: false },
|
{ id: 2, label: 'Active', active: false },
|
||||||
{ id: 3, label: 'Pending', active: false },
|
{ id: 3, label: 'Pending', active: false },
|
||||||
|
|
@ -277,13 +494,13 @@ export const NavigationExamplesPage = () => (
|
||||||
</ExampleSection>
|
</ExampleSection>
|
||||||
|
|
||||||
<ExampleSection title="Vertical Navigation">
|
<ExampleSection title="Vertical Navigation">
|
||||||
<VerticalNav items={[
|
<SimpleVerticalNav items={[
|
||||||
{ id: 1, label: 'Dashboard', icon: '📊', active: true, href: '#' },
|
{ id: 1, label: 'Dashboard', icon: '📊', active: true },
|
||||||
{ id: 2, label: 'Projects', icon: '📁', active: false, href: '#' },
|
{ id: 2, label: 'Projects', icon: '📁', active: false },
|
||||||
{ id: 3, label: 'Team', icon: '👥', active: false, href: '#' },
|
{ id: 3, label: 'Team', icon: '👥', active: false },
|
||||||
{ id: 4, label: 'Calendar', icon: '📅', active: false, href: '#' },
|
{ id: 4, label: 'Calendar', icon: '📅', active: false },
|
||||||
{ id: 5, label: 'Documents', icon: '📄', active: false, href: '#' },
|
{ id: 5, label: 'Documents', icon: '📄', active: false },
|
||||||
{ id: 6, label: 'Settings', icon: '⚙️', active: false, href: '#' },
|
{ id: 6, label: 'Settings', icon: '⚙️', active: false },
|
||||||
]} />
|
]} />
|
||||||
</ExampleSection>
|
</ExampleSection>
|
||||||
|
|
||||||
|
|
@ -295,5 +512,5 @@ export const NavigationExamplesPage = () => (
|
||||||
{ id: 4, label: 'Design Assets' },
|
{ id: 4, label: 'Design Assets' },
|
||||||
]} />
|
]} />
|
||||||
</ExampleSection>
|
</ExampleSection>
|
||||||
</Layout>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { Layout, ExampleSection } from './helpers'
|
import { ExampleSection } from './ssr/helpers'
|
||||||
|
|
||||||
const UserProfile = define('UserProfile', {
|
const UserProfile = define('UserProfile', {
|
||||||
base: 'div',
|
base: 'div',
|
||||||
|
|
@ -167,9 +167,8 @@ const UserProfile = define('UserProfile', {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Export the full example page
|
export const ProfileExamplesContent = () => (
|
||||||
export const ProfileExamplesPage = () => (
|
<>
|
||||||
<Layout title="Forge Profile Examples">
|
|
||||||
<ExampleSection title="Default Profile">
|
<ExampleSection title="Default Profile">
|
||||||
<UserProfile
|
<UserProfile
|
||||||
name="Sarah Chen"
|
name="Sarah Chen"
|
||||||
|
|
@ -235,5 +234,5 @@ export const ProfileExamplesPage = () => (
|
||||||
posts={567}
|
posts={567}
|
||||||
/>
|
/>
|
||||||
</ExampleSection>
|
</ExampleSection>
|
||||||
</Layout>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
180
examples/spa/app.tsx
Normal file
180
examples/spa/app.tsx
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
import { define } from '../../src'
|
||||||
|
import { ButtonExamplesContent } from '../button'
|
||||||
|
import { ProfileExamplesContent } from '../profile'
|
||||||
|
import { NavigationExamplesContent } from '../navigation'
|
||||||
|
|
||||||
|
export const Main = define('SpaMain', {
|
||||||
|
base: 'div',
|
||||||
|
|
||||||
|
padding: '40px 20px',
|
||||||
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
||||||
|
background: '#f3f4f6',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Container = define('SpaContainer', {
|
||||||
|
base: 'div',
|
||||||
|
|
||||||
|
maxWidth: 1200,
|
||||||
|
margin: '0 auto'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Simple client-side router
|
||||||
|
const Link = define('Link', {
|
||||||
|
base: 'a',
|
||||||
|
|
||||||
|
color: '#3b82f6',
|
||||||
|
textDecoration: 'none',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
hover: {
|
||||||
|
textDecoration: 'underline'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ props, parts: { Root } }) {
|
||||||
|
const handleClick = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.history.pushState({}, '', props.href)
|
||||||
|
window.dispatchEvent(new Event('routechange'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root {...props} onclick={handleClick}>
|
||||||
|
{props.children}
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const Nav = define('Nav', {
|
||||||
|
base: 'nav',
|
||||||
|
|
||||||
|
display: 'flex',
|
||||||
|
gap: 20,
|
||||||
|
marginBottom: 40,
|
||||||
|
padding: 20,
|
||||||
|
background: 'white',
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
|
||||||
|
})
|
||||||
|
|
||||||
|
const P = define('P', {
|
||||||
|
color: '#6b7280',
|
||||||
|
fontSize: 18,
|
||||||
|
marginBottom: 48,
|
||||||
|
})
|
||||||
|
|
||||||
|
const ExamplesGrid = define('ExamplesGrid', {
|
||||||
|
display: 'grid',
|
||||||
|
gap: 20,
|
||||||
|
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
||||||
|
})
|
||||||
|
|
||||||
|
const ExampleCard = define('ExampleCard', {
|
||||||
|
base: 'a',
|
||||||
|
|
||||||
|
background: 'white',
|
||||||
|
padding: 24,
|
||||||
|
borderRadius: 12,
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
display: 'block',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
hover: {
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
parts: {
|
||||||
|
H2: {
|
||||||
|
color: '#111827',
|
||||||
|
margin: '0 0 8px 0',
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
P: {
|
||||||
|
color: '#6b7280',
|
||||||
|
margin: 0,
|
||||||
|
fontSize: 14,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ props: { title, desc, ...props }, parts: { Root, H2, P } }) {
|
||||||
|
const handleClick = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.history.pushState({}, '', props.href)
|
||||||
|
window.dispatchEvent(new Event('routechange'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root {...props} onclick={handleClick}>
|
||||||
|
<H2>{title}</H2>
|
||||||
|
<P>{desc}</P>
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const HomePage = () => (
|
||||||
|
<>
|
||||||
|
<P>Explore component examples built with Forge - Client-side SPA version</P>
|
||||||
|
|
||||||
|
<ExamplesGrid>
|
||||||
|
<ExampleCard href="/spa/profile"
|
||||||
|
title="Profile Card"
|
||||||
|
desc="User profile component with variants for size, theme, and verified status"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ExampleCard href="/spa/buttons"
|
||||||
|
title="Buttons"
|
||||||
|
desc="Button component with intent, size, and disabled variants"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ExampleCard href="/spa/navigation"
|
||||||
|
title="Navigation"
|
||||||
|
desc="Navigation patterns including tabs, pills, vertical nav, and breadcrumbs"
|
||||||
|
/>
|
||||||
|
</ExamplesGrid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ProfilePage = () => <ProfileExamplesContent />
|
||||||
|
const ButtonsPage = () => <ButtonExamplesContent />
|
||||||
|
const NavigationPage = () => <NavigationExamplesContent />
|
||||||
|
|
||||||
|
export function route(path: string) {
|
||||||
|
switch (path) {
|
||||||
|
case '/spa':
|
||||||
|
case '/spa/':
|
||||||
|
return <HomePage />
|
||||||
|
case '/spa/profile':
|
||||||
|
return <ProfilePage />
|
||||||
|
case '/spa/buttons':
|
||||||
|
return <ButtonsPage />
|
||||||
|
case '/spa/navigation':
|
||||||
|
return <NavigationPage />
|
||||||
|
default:
|
||||||
|
return <P>404 Not Found</P>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
return (
|
||||||
|
<Main>
|
||||||
|
<Container>
|
||||||
|
<Nav>
|
||||||
|
<a href="/" style="color: #3b82f6; text-decoration: none;">Home</a>
|
||||||
|
<Link href="/spa">SPA Examples</Link>
|
||||||
|
<Link href="/spa/profile">Profile</Link>
|
||||||
|
<Link href="/spa/buttons">Buttons</Link>
|
||||||
|
<Link href="/spa/navigation">Navigation</Link>
|
||||||
|
</Nav>
|
||||||
|
<div id="content">
|
||||||
|
{route(window.location.pathname)}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</Main>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
examples/spa/index.html
Normal file
12
examples/spa/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Forge SPA Examples</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/spa.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
examples/spa/index.tsx
Normal file
19
examples/spa/index.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { render } from 'hono/jsx/dom'
|
||||||
|
import { App, route } from './app'
|
||||||
|
|
||||||
|
const root = document.getElementById('root')
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
if (root) {
|
||||||
|
render(<App />, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On route change, only update the content div
|
||||||
|
function updateContent() {
|
||||||
|
const contentDiv = document.getElementById('content')
|
||||||
|
if (contentDiv)
|
||||||
|
render(route(window.location.pathname), contentDiv)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('routechange', updateContent)
|
||||||
|
window.addEventListener('popstate', updateContent)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { define, Styles } from '../src'
|
import { define, Styles } from '../../src'
|
||||||
|
|
||||||
export const Body = define('Body', {
|
export const Body = define('Body', {
|
||||||
base: 'body',
|
base: 'body',
|
||||||
|
|
@ -43,6 +43,31 @@ export const ExampleSection = define('ExampleSection', {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const Nav = define('SSR_Nav', {
|
||||||
|
base: 'nav',
|
||||||
|
|
||||||
|
display: 'flex',
|
||||||
|
gap: 20,
|
||||||
|
marginBottom: 40,
|
||||||
|
padding: 20,
|
||||||
|
background: 'white',
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
|
||||||
|
})
|
||||||
|
|
||||||
|
const NavLink = define('SSR_NavLink', {
|
||||||
|
base: 'a',
|
||||||
|
|
||||||
|
color: '#3b82f6',
|
||||||
|
textDecoration: 'none',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
hover: {
|
||||||
|
textDecoration: 'underline'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const Layout = define({
|
export const Layout = define({
|
||||||
render({ props }) {
|
render({ props }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -55,6 +80,13 @@ export const Layout = define({
|
||||||
</head>
|
</head>
|
||||||
<Body>
|
<Body>
|
||||||
<Container>
|
<Container>
|
||||||
|
<Nav>
|
||||||
|
<NavLink href="/">Home</NavLink>
|
||||||
|
<NavLink href="/ssr">SSR Examples</NavLink>
|
||||||
|
<NavLink href="/ssr/profile">Profile</NavLink>
|
||||||
|
<NavLink href="/ssr/buttons">Buttons</NavLink>
|
||||||
|
<NavLink href="/ssr/navigation">Navigation</NavLink>
|
||||||
|
</Nav>
|
||||||
<Header>{props.title}</Header>
|
<Header>{props.title}</Header>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Container>
|
</Container>
|
||||||
117
examples/ssr/landing.tsx
Normal file
117
examples/ssr/landing.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { define, Styles } from '../../src'
|
||||||
|
|
||||||
|
const Page = define('LandingPage', {
|
||||||
|
base: 'body',
|
||||||
|
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
minHeight: '100vh',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
||||||
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
})
|
||||||
|
|
||||||
|
const Container = define('LandingContainer', {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'white',
|
||||||
|
})
|
||||||
|
|
||||||
|
const Title = define('LandingTitle', {
|
||||||
|
base: 'h1',
|
||||||
|
|
||||||
|
fontSize: 48,
|
||||||
|
fontWeight: 700,
|
||||||
|
marginBottom: 50,
|
||||||
|
color: 'white',
|
||||||
|
})
|
||||||
|
|
||||||
|
const Subtitle = define('LandingSubtitle', {
|
||||||
|
base: 'p',
|
||||||
|
|
||||||
|
fontSize: 20,
|
||||||
|
marginBottom: 48,
|
||||||
|
color: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
})
|
||||||
|
|
||||||
|
const ButtonGroup = define('LandingButtonGroup', {
|
||||||
|
display: 'flex',
|
||||||
|
gap: 50,
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
})
|
||||||
|
|
||||||
|
const ChoiceCard = define('LandingChoiceCard', {
|
||||||
|
base: 'a',
|
||||||
|
|
||||||
|
display: 'block',
|
||||||
|
padding: 40,
|
||||||
|
background: 'white',
|
||||||
|
borderRadius: 16,
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: '#111827',
|
||||||
|
boxShadow: '0 10px 30px rgba(0, 0, 0, 0.2)',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
minWidth: 250,
|
||||||
|
|
||||||
|
states: {
|
||||||
|
':hover': {
|
||||||
|
transform: 'translateY(-8px)',
|
||||||
|
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.3)',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const LandingPage = () => (
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Forge - Choose Your Rendering Mode</title>
|
||||||
|
<Styles />
|
||||||
|
</head>
|
||||||
|
<Page>
|
||||||
|
<Container>
|
||||||
|
<Title>Welcome to Forge</Title>
|
||||||
|
|
||||||
|
<ButtonGroup>
|
||||||
|
<ChoiceCard
|
||||||
|
href="/ssr"
|
||||||
|
icon="🖥️"
|
||||||
|
title="SSR Examples"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChoiceCard
|
||||||
|
href="/spa"
|
||||||
|
icon="⚡"
|
||||||
|
title="SPA Examples"
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Container>
|
||||||
|
</Page>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
import { define } from '../src'
|
import { define } from '../../src'
|
||||||
import { Layout } from './helpers'
|
import { Layout } from './helpers'
|
||||||
|
import { ButtonExamplesContent } from '../button'
|
||||||
|
import { ProfileExamplesContent } from '../profile'
|
||||||
|
import { NavigationExamplesContent } from '../navigation'
|
||||||
|
|
||||||
const P = define('P', {
|
const P = define('SSR_P', {
|
||||||
color: '#6b7280',
|
color: '#6b7280',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
marginBottom: 48,
|
marginBottom: 48,
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExamplesGrid = define('ExamplesGrid', {
|
const ExamplesGrid = define('SSR_ExamplesGrid', {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gap: 20,
|
gap: 20,
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))'
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExampleCard = define('ExampleCard', {
|
const ExampleCard = define('SSR_ExampleCard', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
background: 'white',
|
background: 'white',
|
||||||
|
|
@ -44,9 +47,9 @@ const ExampleCard = define('ExampleCard', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render({ props: { title, desc }, parts: { Root, H2, P } }) {
|
render({ props: { title, desc, ...rest }, parts: { Root, H2, P } }) {
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root {...rest}>
|
||||||
<H2>{title}</H2>
|
<H2>{title}</H2>
|
||||||
<P>{desc}</P>
|
<P>{desc}</P>
|
||||||
</Root>
|
</Root>
|
||||||
|
|
@ -59,20 +62,38 @@ export const IndexPage = () => (
|
||||||
<P>Explore component examples built with Forge</P>
|
<P>Explore component examples built with Forge</P>
|
||||||
|
|
||||||
<ExamplesGrid>
|
<ExamplesGrid>
|
||||||
<ExampleCard href="/profile"
|
<ExampleCard href="/ssr/profile"
|
||||||
title="Profile Card"
|
title="Profile Card"
|
||||||
desc="User profile component with variants for size, theme, and verified status"
|
desc="User profile component with variants for size, theme, and verified status"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/buttons"
|
<ExampleCard href="/ssr/buttons"
|
||||||
title="Buttons"
|
title="Buttons"
|
||||||
desc="Button component with intent, size, and disabled variants"
|
desc="Button component with intent, size, and disabled variants"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExampleCard href="/navigation"
|
<ExampleCard href="/ssr/navigation"
|
||||||
title="Navigation"
|
title="Navigation"
|
||||||
desc="Navigation patterns including tabs, pills, vertical nav, and breadcrumbs"
|
desc="Navigation patterns including tabs, pills, vertical nav, and breadcrumbs"
|
||||||
/>
|
/>
|
||||||
</ExamplesGrid>
|
</ExamplesGrid>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const ButtonExamplesPage = () => (
|
||||||
|
<Layout title="Forge Button Component Examples">
|
||||||
|
<ButtonExamplesContent />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ProfileExamplesPage = () => (
|
||||||
|
<Layout title="Forge Profile Examples">
|
||||||
|
<ProfileExamplesContent />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const NavigationExamplesPage = () => (
|
||||||
|
<Layout title="Forge Navigation Examples">
|
||||||
|
<NavigationExamplesContent />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --hot server.tsx",
|
"dev": "bun build:spa && bun run --hot server.tsx",
|
||||||
"test": "bun test"
|
"test": "bun test",
|
||||||
|
"build:spa": "bun build examples/spa/index.tsx --outfile dist/spa.js --target browser"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@types/bun": "latest"
|
||||||
|
|
|
||||||
37
server.tsx
37
server.tsx
|
|
@ -1,30 +1,31 @@
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { IndexPage } from './examples/index'
|
import { IndexPage, ProfileExamplesPage, ButtonExamplesPage, NavigationExamplesPage } from './examples/ssr/pages'
|
||||||
import { ProfileExamplesPage } from './examples/profile'
|
import { LandingPage } from './examples/ssr/landing'
|
||||||
import { ButtonExamplesPage } from './examples/button'
|
|
||||||
import { NavigationExamplesPage } from './examples/navigation'
|
|
||||||
import { styles, stylesToCSS } from './src'
|
import { styles, stylesToCSS } from './src'
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
app.get('/', c => {
|
app.get('/', c => c.html(<LandingPage />))
|
||||||
return c.html(<IndexPage />)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/profile', c => {
|
app.get('/ssr', c => c.html(<IndexPage />))
|
||||||
return c.html(<ProfileExamplesPage />)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/buttons', c => {
|
app.get('/ssr/profile', c => c.html(<ProfileExamplesPage />))
|
||||||
return c.html(<ButtonExamplesPage />)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/navigation', c => {
|
app.get('/ssr/buttons', c => c.html(<ButtonExamplesPage />))
|
||||||
return c.html(<NavigationExamplesPage />)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/styles', c => {
|
app.get('/ssr/navigation', c => c.html(<NavigationExamplesPage />))
|
||||||
return c.text(stylesToCSS(styles))
|
|
||||||
|
app.get('/styles', c => c.text(stylesToCSS(styles)))
|
||||||
|
|
||||||
|
app.get('/spa/*', async c => c.html(await Bun.file('./examples/spa/index.html').text()))
|
||||||
|
|
||||||
|
app.get('/spa.js', async c => {
|
||||||
|
const file = Bun.file('./dist/spa.js')
|
||||||
|
return new Response(file, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/javascript',
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,27 @@ import type { JSX } from 'hono/jsx'
|
||||||
import { type TagDef, UnitlessProps, NonStyleKeys } from './types'
|
import { type TagDef, UnitlessProps, NonStyleKeys } from './types'
|
||||||
|
|
||||||
export const styles: Record<string, Record<string, string>> = {}
|
export const styles: Record<string, Record<string, string>> = {}
|
||||||
|
|
||||||
|
// Use w/ SSR: <Styles/>
|
||||||
export const Styles = () => <style dangerouslySetInnerHTML={{ __html: stylesToCSS(styles) }} />
|
export const Styles = () => <style dangerouslySetInnerHTML={{ __html: stylesToCSS(styles) }} />
|
||||||
|
|
||||||
|
const isBrowser = typeof document !== 'undefined'
|
||||||
|
let styleElement: HTMLStyleElement | null = null
|
||||||
|
|
||||||
|
// automatically inject <style> tag into browser for SPA
|
||||||
|
function injectStylesInBrowser() {
|
||||||
|
if (!isBrowser) return
|
||||||
|
|
||||||
|
styleElement ??= document.getElementById('forge-styles') as HTMLStyleElement
|
||||||
|
|
||||||
|
if (!styleElement) {
|
||||||
|
styleElement = document.createElement('style')
|
||||||
|
styleElement.id = 'forge-styles'
|
||||||
|
document.head.appendChild(styleElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
styleElement.textContent = stylesToCSS(styles)
|
||||||
|
}
|
||||||
// turns style object into string CSS definition
|
// turns style object into string CSS definition
|
||||||
export function stylesToCSS(styles: Record<string, Record<string, string>>): string {
|
export function stylesToCSS(styles: Record<string, Record<string, string>>): string {
|
||||||
let out: string[] = []
|
let out: string[] = []
|
||||||
|
|
@ -152,6 +171,9 @@ function registerStyles(name: string, def: TagDef) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In browser, inject styles into DOM immediately
|
||||||
|
injectStylesInBrowser()
|
||||||
}
|
}
|
||||||
|
|
||||||
let anonComponents = 1
|
let anonComponents = 1
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Environment setup & latest features
|
// Environment setup & latest features
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext", "DOM"],
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "Preserve",
|
"module": "Preserve",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user