forge/examples/navigation.tsx
Chris Wanstrath f643f8b2eb README+theme
2025-12-29 13:17:19 -08:00

519 lines
12 KiB
TypeScript

import { define } from '../src'
import { ExampleSection, theme } from './ssr/helpers'
const TabSwitcher = define('TabSwitcher', {
parts: {
Input: {
base: 'input[type=radio]',
display: 'none',
},
TabBar: {
display: 'flex',
gap: 0,
borderBottom: `1px solid ${theme.colors.border}`,
marginBottom: theme.spacing.lg,
},
TabLabel: {
base: 'label',
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
position: 'relative',
marginBottom: -1,
background: 'transparent',
borderBottom: '1px solid transparent',
color: theme.colors.fgMuted,
fontSize: 14,
cursor: 'pointer',
transition: 'all 0.2s ease',
states: {
':hover': {
color: theme.colors.fg,
}
},
selectors: {
'@Input:checked + &': {
color: theme.colors.accent,
borderBottom: `1px solid ${theme.colors.accent}`
}
}
},
Content: {
display: 'none',
padding: theme.spacing.lg,
background: theme.colors.bgElevated,
border: `1px solid ${theme.colors.border}`,
borderRadius: theme.radius.sm,
selectors: {
'@Input:checked ~ &': {
display: 'block'
}
}
}
},
render({ props, parts: { Root, Input, TabBar, TabLabel, Content } }) {
return (
<Root>
<TabBar>
{props.tabs?.map((tab: any, index: number) => (
<>
<Input
key={`input-${tab.id}`}
id={`${props.name}-${tab.id}`}
name={props.name || 'tabs'}
checked={index === 0}
/>
<TabLabel key={`label-${tab.id}`} for={`${props.name}-${tab.id}`}>
{tab.label}
</TabLabel>
</>
))}
</TabBar>
{props.tabs?.map((tab: any) => (
<Content key={`content-${tab.id}`}>
{tab.content}
</Content>
))}
</Root>
)
}
})
const Pills = define('Pills', {
parts: {
Input: {
base: 'input[type=radio]',
display: 'none',
},
PillBar: {
display: 'flex',
gap: theme.spacing.xs,
flexWrap: 'wrap',
},
PillLabel: {
base: 'label',
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
background: theme.colors.bgElevated,
border: `1px solid ${theme.colors.border}`,
borderRadius: 20,
color: theme.colors.fgMuted,
fontSize: 14,
cursor: 'pointer',
transition: 'all 0.2s ease',
states: {
':hover': {
borderColor: theme.colors.borderActive,
color: theme.colors.fg,
}
},
selectors: {
'@Input:checked + &': {
background: theme.colors.accent,
borderColor: theme.colors.accent,
color: theme.colors.bg
},
'@Input:checked + &:hover': {
background: theme.colors.accentDim,
borderColor: theme.colors.accentDim,
}
}
}
},
render({ props, parts: { Root, Input, PillBar, PillLabel } }) {
return (
<Root>
<PillBar>
{props.items?.map((item: any, index: number) => (
<>
<Input
key={`input-${item.id}`}
id={`${props.name}-${item.id}`}
name={props.name || 'pills'}
checked={index === 0}
/>
<PillLabel key={`label-${item.id}`} for={`${props.name}-${item.id}`}>
{item.label}
</PillLabel>
</>
))}
</PillBar>
</Root>
)
}
})
const VerticalNav = define('VerticalNav', {
parts: {
Input: {
base: 'input[type=radio]',
display: 'none',
},
NavBar: {
display: 'flex',
flexDirection: 'column',
gap: 4,
width: 240,
},
NavLabel: {
base: 'label',
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
display: 'flex',
alignItems: 'center',
gap: theme.spacing.sm,
background: 'transparent',
border: `1px solid transparent`,
borderRadius: theme.radius.sm,
color: theme.colors.fgMuted,
fontSize: 14,
cursor: 'pointer',
transition: 'all 0.2s ease',
states: {
':hover': {
background: theme.colors.bgElevated,
borderColor: theme.colors.border,
color: theme.colors.fg,
}
},
selectors: {
'@Input:checked + &': {
background: theme.colors.bgElevated,
borderColor: theme.colors.accent,
color: theme.colors.accent,
},
'@Input:checked + &:hover': {
borderColor: theme.colors.accentDim,
color: theme.colors.accentDim
}
}
},
Icon: {
width: 20,
height: 20,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 16,
}
},
render({ props, parts: { Root, Input, NavBar, NavLabel, Icon } }) {
return (
<Root>
<NavBar>
{props.items?.map((item: any, index: number) => (
<>
<Input
key={`input-${item.id}`}
id={`${props.name}-${item.id}`}
name={props.name || 'nav'}
checked={index === 0}
/>
<NavLabel key={`label-${item.id}`} for={`${props.name}-${item.id}`}>
{item.icon && <Icon>{item.icon}</Icon>}
{item.label}
</NavLabel>
</>
))}
</NavBar>
</Root>
)
}
})
const Breadcrumbs = define('Breadcrumbs', {
display: 'flex',
alignItems: 'center',
gap: theme.spacing.xs,
flexWrap: 'wrap',
parts: {
Item: {
base: 'a',
color: theme.colors.fgMuted,
fontSize: 14,
textDecoration: 'none',
transition: 'color 0.2s ease',
states: {
':hover': {
color: theme.colors.accent,
}
}
},
Separator: {
color: theme.colors.fgDim,
fontSize: 14,
userSelect: 'none',
},
Current: {
color: theme.colors.fg,
fontSize: 14,
}
},
render({ props, parts: { Root, Item, Separator, Current } }) {
return (
<Root>
{props.items?.map((item: any, index: number) => (
<>
{index === props.items.length - 1 ? (
<Current key={item.id}>{item.label}</Current>
) : (
<>
<Item key={item.id} href={item.href || '#'}>
{item.label}
</Item>
<Separator>/</Separator>
</>
)}
</>
))}
</Root>
)
}
})
const Tabs = define('Tabs', {
display: 'flex',
gap: 0,
borderBottom: `1px solid ${theme.colors.border}`,
parts: {
Tab: {
base: 'button',
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
position: 'relative',
marginBottom: -1,
background: 'transparent',
border: 'none',
borderBottom: '1px solid transparent',
color: theme.colors.fgMuted,
fontSize: 14,
cursor: 'pointer',
transition: 'all 0.2s ease',
states: {
':hover': {
color: theme.colors.fg,
}
}
}
},
variants: {
active: {
parts: {
Tab: {
color: theme.colors.accent,
borderBottom: `1px solid ${theme.colors.accent}`,
}
}
}
},
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: theme.spacing.xs,
flexWrap: 'wrap',
parts: {
Pill: {
base: 'button',
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
background: theme.colors.bgElevated,
border: `1px solid ${theme.colors.border}`,
borderRadius: 20,
color: theme.colors.fgMuted,
fontSize: 14,
cursor: 'pointer',
transition: 'all 0.2s ease',
states: {
':hover': {
borderColor: theme.colors.borderActive,
color: theme.colors.fg,
}
}
}
},
variants: {
active: {
parts: {
Pill: {
background: theme.colors.accent,
borderColor: theme.colors.accent,
color: theme.colors.bg,
states: {
':hover': {
background: theme.colors.accentDim,
borderColor: theme.colors.accentDim,
}
}
}
}
}
},
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: `${theme.spacing.sm}px ${theme.spacing.md}px`,
display: 'flex',
alignItems: 'center',
gap: theme.spacing.sm,
background: 'transparent',
border: `1px solid transparent`,
borderRadius: theme.radius.sm,
color: theme.colors.fgMuted,
fontSize: 14,
textAlign: 'left',
cursor: 'pointer',
transition: 'all 0.2s ease',
states: {
':hover': {
background: theme.colors.bgElevated,
borderColor: theme.colors.border,
color: theme.colors.fg,
}
}
},
Icon: {
width: 20,
height: 20,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 16,
}
},
variants: {
active: {
parts: {
NavItem: {
background: theme.colors.bgElevated,
borderColor: theme.colors.accent,
color: theme.colors.accent,
states: {
':hover': {
borderColor: theme.colors.accentDim,
color: theme.colors.accentDim,
}
}
}
}
}
},
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">
<TabSwitcher
name="demo-tabs"
tabs={[
{ id: 'overview', label: 'Overview', content: <p>Overview content</p> },
{ id: 'analytics', label: 'Analytics', content: <p>Analytics content</p> },
{ id: 'reports', label: 'Reports', content: <p>Reports content</p> },
{ id: 'settings', label: 'Settings', content: <p>Settings content</p> },
]}
/>
</ExampleSection>
<ExampleSection title="Pills">
<Pills
name="demo-pills"
items={[
{ id: 'all', label: 'All' },
{ id: 'active', label: 'Active' },
{ id: 'pending', label: 'Pending' },
{ id: 'archived', label: 'Archived' },
]}
/>
</ExampleSection>
<ExampleSection title="Vertical Navigation">
<VerticalNav
name="demo-nav"
items={[
{ id: 'dashboard', label: 'Dashboard', icon: '📊' },
{ id: 'projects', label: 'Projects', icon: '📁' },
{ id: 'team', label: 'Team', icon: '👥' },
{ id: 'calendar', label: 'Calendar', icon: '📅' },
{ id: 'documents', label: 'Documents', icon: '📄' },
{ id: 'settings', label: 'Settings', icon: '⚙️' },
]}
/>
</ExampleSection>
<ExampleSection title="Breadcrumbs">
<Breadcrumbs items={[
{ id: 1, label: 'Home', href: '#' },
{ id: 2, label: 'Projects', href: '#' },
{ id: 3, label: 'Website Redesign', href: '#' },
{ id: 4, label: 'Design Assets' },
]} />
</ExampleSection>
</>
)