themes
This commit is contained in:
parent
f66a47d0bb
commit
855112ac82
|
|
@ -8,7 +8,8 @@
|
||||||
|
|
||||||
## overview
|
## overview
|
||||||
|
|
||||||
Forge is a typed, local, variant-driven way to organize CSS, built around TSX.
|
Forge is a typed, local, variant-driven way to organize CSS and create
|
||||||
|
self-contained TSX components out of discrete parts.
|
||||||
|
|
||||||
## css problems
|
## css problems
|
||||||
|
|
||||||
|
|
@ -24,7 +25,8 @@ Forge is a typed, local, variant-driven way to organize CSS, built around TSX.
|
||||||
- Component styles are made up of independently styled "Parts".
|
- Component styles are made up of independently styled "Parts".
|
||||||
- "Variants" replace inline styles with typed, declarative parameters.
|
- "Variants" replace inline styles with typed, declarative parameters.
|
||||||
- Style composition is deterministic.
|
- Style composition is deterministic.
|
||||||
- Errors and feedback.
|
- Themes are easy.
|
||||||
|
- Errors and feedback are provided.
|
||||||
|
|
||||||
## examples
|
## examples
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
import { createScope } from '../src'
|
import { createScope } from '../src'
|
||||||
import { ExampleSection, theme } from './ssr/helpers'
|
import { ExampleSection } from './ssr/helpers'
|
||||||
|
import { theme } from './ssr/themes'
|
||||||
|
|
||||||
const { define } = createScope('Button')
|
const { define } = createScope('Button')
|
||||||
|
|
||||||
const Button = define('Root', {
|
const Button = define('Root', {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
|
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-lg')}`,
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
gap: theme.spacing.xs,
|
gap: theme('spacing-xs'),
|
||||||
background: theme.colors.accent,
|
background: theme('colors-accent'),
|
||||||
color: theme.colors.bg,
|
color: theme('colors-bg'),
|
||||||
border: `1px solid ${theme.colors.accent}`,
|
border: `1px solid ${theme('colors-accent')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
|
@ -23,8 +24,8 @@ const Button = define('Root', {
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
":not(:disabled):hover": {
|
":not(:disabled):hover": {
|
||||||
background: theme.colors.accentDim,
|
background: theme('colors-accentDim'),
|
||||||
borderColor: theme.colors.accentDim,
|
borderColor: theme('colors-accentDim'),
|
||||||
},
|
},
|
||||||
":not(:disabled):active": {
|
":not(:disabled):active": {
|
||||||
transform: 'translateY(1px)',
|
transform: 'translateY(1px)',
|
||||||
|
|
@ -34,23 +35,23 @@ const Button = define('Root', {
|
||||||
variants: {
|
variants: {
|
||||||
intent: {
|
intent: {
|
||||||
primary: {
|
primary: {
|
||||||
background: theme.colors.accent,
|
background: theme('colors-accent'),
|
||||||
color: theme.colors.bg,
|
color: theme('colors-bg'),
|
||||||
border: `1px solid ${theme.colors.accent}`,
|
border: `1px solid ${theme('colors-accent')}`,
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
states: {
|
states: {
|
||||||
":not(:disabled):hover": {
|
":not(:disabled):hover": {
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
background: "#ff0000",
|
background: "#ff0000",
|
||||||
color: theme.colors.bg,
|
color: theme('colors-bg'),
|
||||||
border: "1px solid #ff0000",
|
border: "1px solid #ff0000",
|
||||||
states: {
|
states: {
|
||||||
":not(:disabled):hover": {
|
":not(:disabled):hover": {
|
||||||
|
|
@ -61,23 +62,23 @@ const Button = define('Root', {
|
||||||
},
|
},
|
||||||
ghost: {
|
ghost: {
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
states: {
|
states: {
|
||||||
":not(:disabled):hover": {
|
":not(:disabled):hover": {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
small: {
|
small: {
|
||||||
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
|
padding: `${theme('spacing-xs')} ${theme('spacing-md')}`,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
large: {
|
large: {
|
||||||
padding: `${theme.spacing.md}px ${theme.spacing.xl}px`,
|
padding: `${theme('spacing-md')} ${theme('spacing-xl')}`,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -90,7 +91,7 @@ const Button = define('Root', {
|
||||||
|
|
||||||
const ButtonRow = define('Row', {
|
const ButtonRow = define('Row', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing.md,
|
gap: theme('spacing-md'),
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { ExampleSection, theme } from './ssr/helpers'
|
import { ExampleSection } from './ssr/helpers'
|
||||||
|
import { theme } from './ssr/themes'
|
||||||
|
|
||||||
const Input = define('Input', {
|
const Input = define('Input', {
|
||||||
base: 'input',
|
base: 'input',
|
||||||
|
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-md')}`,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
color: theme.colors.fg,
|
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,11 +18,11 @@ const Input = define('Input', {
|
||||||
states: {
|
states: {
|
||||||
':focus': {
|
':focus': {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
},
|
},
|
||||||
':disabled': {
|
':disabled': {
|
||||||
background: theme.colors.bg,
|
background: theme('colors-bg'),
|
||||||
color: theme.colors.fgDim,
|
color: theme('colors-fgDim'),
|
||||||
cursor: 'not-allowed'
|
cursor: 'not-allowed'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -37,10 +38,10 @@ const Input = define('Input', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
borderColor: theme.colors.accent,
|
borderColor: theme('colors-accent'),
|
||||||
states: {
|
states: {
|
||||||
':focus': {
|
':focus': {
|
||||||
borderColor: theme.colors.accent,
|
borderColor: theme('colors-accent'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -51,12 +52,12 @@ const Input = define('Input', {
|
||||||
const Textarea = define('Textarea', {
|
const Textarea = define('Textarea', {
|
||||||
base: 'textarea',
|
base: 'textarea',
|
||||||
|
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-md')}`,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
minHeight: 120,
|
minHeight: 120,
|
||||||
|
|
@ -67,13 +68,13 @@ const Textarea = define('Textarea', {
|
||||||
states: {
|
states: {
|
||||||
':focus': {
|
':focus': {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const FormGroup = define('FormGroup', {
|
const FormGroup = define('FormGroup', {
|
||||||
marginBottom: theme.spacing.lg,
|
marginBottom: theme('spacing-lg'),
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Label: {
|
Label: {
|
||||||
|
|
@ -81,12 +82,12 @@ const FormGroup = define('FormGroup', {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
marginBottom: theme.spacing.xs
|
marginBottom: theme('spacing-xs')
|
||||||
},
|
},
|
||||||
Helper: {
|
Helper: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
marginTop: 6
|
marginTop: 6
|
||||||
},
|
},
|
||||||
Error: {
|
Error: {
|
||||||
|
|
@ -120,21 +121,21 @@ const Checkbox = define('Checkbox', {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing.sm,
|
gap: theme('spacing-sm'),
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
color: theme.colors.fg
|
color: theme('colors-fg')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:disabled + &': {
|
'@Input:disabled + &': {
|
||||||
cursor: 'not-allowed',
|
cursor: 'not-allowed',
|
||||||
color: theme.colors.fgDim
|
color: theme('colors-fgDim')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,20 +161,20 @@ const FormExamples = define('FormExamples', {
|
||||||
const Button = define('FormButton', {
|
const Button = define('FormButton', {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
|
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-lg')}`,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
border: `1px solid ${theme.colors.accent}`,
|
border: `1px solid ${theme('colors-accent')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
background: theme.colors.accent,
|
background: theme('colors-accent'),
|
||||||
color: theme.colors.bg,
|
color: theme('colors-bg'),
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: theme.colors.accentDim,
|
background: theme('colors-accentDim'),
|
||||||
borderColor: theme.colors.accentDim,
|
borderColor: theme('colors-accentDim'),
|
||||||
},
|
},
|
||||||
':active': {
|
':active': {
|
||||||
transform: 'translateY(1px)'
|
transform: 'translateY(1px)'
|
||||||
|
|
@ -183,12 +184,12 @@ const Button = define('FormButton', {
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
secondary: {
|
secondary: {
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -198,8 +199,8 @@ const Button = define('FormButton', {
|
||||||
|
|
||||||
const ButtonGroup = define('FormButtonGroup', {
|
const ButtonGroup = define('FormButtonGroup', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing.sm,
|
gap: theme('spacing-sm'),
|
||||||
marginTop: theme.spacing.lg
|
marginTop: theme('spacing-lg')
|
||||||
})
|
})
|
||||||
|
|
||||||
export const FormExamplesContent = () => (
|
export const FormExamplesContent = () => (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { ExampleSection, theme } from './ssr/helpers'
|
import { ExampleSection } from './ssr/helpers'
|
||||||
|
import { theme } from './ssr/themes'
|
||||||
|
|
||||||
const TabSwitcher = define('TabSwitcher', {
|
const TabSwitcher = define('TabSwitcher', {
|
||||||
parts: {
|
parts: {
|
||||||
|
|
@ -10,41 +11,41 @@ const TabSwitcher = define('TabSwitcher', {
|
||||||
TabBar: {
|
TabBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 0,
|
gap: 0,
|
||||||
borderBottom: `1px solid ${theme.colors.border}`,
|
borderBottom: `1px solid ${theme('colors-border')}`,
|
||||||
marginBottom: theme.spacing.lg,
|
marginBottom: theme('spacing-lg'),
|
||||||
},
|
},
|
||||||
TabLabel: {
|
TabLabel: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
|
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-lg')}`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
marginBottom: -1,
|
marginBottom: -1,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
borderBottom: '1px solid transparent',
|
borderBottom: '1px solid transparent',
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked + &': {
|
'@Input:checked + &': {
|
||||||
color: theme.colors.accent,
|
color: theme('colors-accent'),
|
||||||
borderBottom: `1px solid ${theme.colors.accent}`
|
borderBottom: `1px solid ${theme('colors-accent')}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Content: {
|
Content: {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
padding: theme.spacing.lg,
|
padding: theme('spacing-lg'),
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked ~ &': {
|
'@Input:checked ~ &': {
|
||||||
|
|
@ -91,37 +92,37 @@ const Pills = define('Pills', {
|
||||||
},
|
},
|
||||||
PillBar: {
|
PillBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing.xs,
|
gap: theme('spacing-xs'),
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
PillLabel: {
|
PillLabel: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
|
|
||||||
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
|
padding: `${theme('spacing-xs')} ${theme('spacing-md')}`,
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked + &': {
|
'@Input:checked + &': {
|
||||||
background: theme.colors.accent,
|
background: theme('colors-accent'),
|
||||||
borderColor: theme.colors.accent,
|
borderColor: theme('colors-accent'),
|
||||||
color: theme.colors.bg
|
color: theme('colors-bg')
|
||||||
},
|
},
|
||||||
'@Input:checked + &:hover': {
|
'@Input:checked + &:hover': {
|
||||||
background: theme.colors.accentDim,
|
background: theme('colors-accentDim'),
|
||||||
borderColor: theme.colors.accentDim,
|
borderColor: theme('colors-accentDim'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,36 +166,36 @@ const VerticalNav = define('VerticalNav', {
|
||||||
NavLabel: {
|
NavLabel: {
|
||||||
base: 'label',
|
base: 'label',
|
||||||
|
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-md')}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing.sm,
|
gap: theme('spacing-sm'),
|
||||||
|
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: `1px solid transparent`,
|
border: `1px solid transparent`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme('colors-border'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'@Input:checked + &': {
|
'@Input:checked + &': {
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
borderColor: theme.colors.accent,
|
borderColor: theme('colors-accent'),
|
||||||
color: theme.colors.accent,
|
color: theme('colors-accent'),
|
||||||
},
|
},
|
||||||
'@Input:checked + &:hover': {
|
'@Input:checked + &:hover': {
|
||||||
borderColor: theme.colors.accentDim,
|
borderColor: theme('colors-accentDim'),
|
||||||
color: theme.colors.accentDim
|
color: theme('colors-accentDim')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -235,31 +236,31 @@ const VerticalNav = define('VerticalNav', {
|
||||||
const Breadcrumbs = define('Breadcrumbs', {
|
const Breadcrumbs = define('Breadcrumbs', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing.xs,
|
gap: theme('spacing-xs'),
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Item: {
|
Item: {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
color: theme.colors.fgMuted,
|
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: theme.colors.accent,
|
color: theme('colors-accent'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Separator: {
|
Separator: {
|
||||||
color: theme.colors.fgDim,
|
color: theme('colors-fgDim'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
},
|
},
|
||||||
Current: {
|
Current: {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -289,25 +290,25 @@ const Breadcrumbs = define('Breadcrumbs', {
|
||||||
const Tabs = define('Tabs', {
|
const Tabs = define('Tabs', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 0,
|
gap: 0,
|
||||||
borderBottom: `1px solid ${theme.colors.border}`,
|
borderBottom: `1px solid ${theme('colors-border')}`,
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Tab: {
|
Tab: {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-lg')}`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
marginBottom: -1,
|
marginBottom: -1,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderBottom: '1px solid transparent',
|
borderBottom: '1px solid transparent',
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -317,8 +318,8 @@ const Tabs = define('Tabs', {
|
||||||
active: {
|
active: {
|
||||||
parts: {
|
parts: {
|
||||||
Tab: {
|
Tab: {
|
||||||
color: theme.colors.accent,
|
color: theme('colors-accent'),
|
||||||
borderBottom: `1px solid ${theme.colors.accent}`,
|
borderBottom: `1px solid ${theme('colors-accent')}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -339,25 +340,25 @@ const Tabs = define('Tabs', {
|
||||||
|
|
||||||
const SimplePills = define('SimplePills', {
|
const SimplePills = define('SimplePills', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing.xs,
|
gap: theme('spacing-xs'),
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Pill: {
|
Pill: {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
|
padding: `${theme('spacing-xs')} ${theme('spacing-md')}`,
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -367,13 +368,13 @@ const SimplePills = define('SimplePills', {
|
||||||
active: {
|
active: {
|
||||||
parts: {
|
parts: {
|
||||||
Pill: {
|
Pill: {
|
||||||
background: theme.colors.accent,
|
background: theme('colors-accent'),
|
||||||
borderColor: theme.colors.accent,
|
borderColor: theme('colors-accent'),
|
||||||
color: theme.colors.bg,
|
color: theme('colors-bg'),
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: theme.colors.accentDim,
|
background: theme('colors-accentDim'),
|
||||||
borderColor: theme.colors.accentDim,
|
borderColor: theme('colors-accentDim'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -403,14 +404,14 @@ const SimpleVerticalNav = define('SimpleVerticalNav', {
|
||||||
parts: {
|
parts: {
|
||||||
NavItem: {
|
NavItem: {
|
||||||
base: 'button',
|
base: 'button',
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-md')}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: theme.spacing.sm,
|
gap: theme('spacing-sm'),
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: `1px solid transparent`,
|
border: `1px solid transparent`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
@ -418,9 +419,9 @@ const SimpleVerticalNav = define('SimpleVerticalNav', {
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme('colors-border'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -438,13 +439,13 @@ const SimpleVerticalNav = define('SimpleVerticalNav', {
|
||||||
active: {
|
active: {
|
||||||
parts: {
|
parts: {
|
||||||
NavItem: {
|
NavItem: {
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
borderColor: theme.colors.accent,
|
borderColor: theme('colors-accent'),
|
||||||
color: theme.colors.accent,
|
color: theme('colors-accent'),
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
borderColor: theme.colors.accentDim,
|
borderColor: theme('colors-accentDim'),
|
||||||
color: theme.colors.accentDim,
|
color: theme('colors-accentDim'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
import { define } from '../src'
|
import { define } from '../src'
|
||||||
import { ExampleSection, theme } from './ssr/helpers'
|
import { ExampleSection } from './ssr/helpers'
|
||||||
|
import { theme } from './ssr/themes'
|
||||||
|
|
||||||
const UserProfile = define('UserProfile', {
|
const UserProfile = define('UserProfile', {
|
||||||
base: 'div',
|
base: 'div',
|
||||||
|
|
||||||
padding: theme.spacing.lg,
|
padding: theme('spacing-lg'),
|
||||||
maxWidth: 600,
|
maxWidth: 600,
|
||||||
margin: "0 auto",
|
margin: "0 auto",
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.md,
|
borderRadius: theme('radius-md'),
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Header: {
|
Header: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing.md,
|
gap: theme('spacing-md'),
|
||||||
marginBottom: theme.spacing.md,
|
marginBottom: theme('spacing-md'),
|
||||||
},
|
},
|
||||||
Avatar: {
|
Avatar: {
|
||||||
base: 'img',
|
base: 'img',
|
||||||
|
|
@ -24,7 +25,7 @@ const UserProfile = define('UserProfile', {
|
||||||
height: 64,
|
height: 64,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
objectFit: "cover",
|
objectFit: "cover",
|
||||||
border: `2px solid ${theme.colors.border}`,
|
border: `2px solid ${theme('colors-border')}`,
|
||||||
},
|
},
|
||||||
Info: {
|
Info: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -33,25 +34,25 @@ const UserProfile = define('UserProfile', {
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
},
|
},
|
||||||
Handle: {
|
Handle: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
},
|
},
|
||||||
Bio: {
|
Bio: {
|
||||||
marginBottom: theme.spacing.md,
|
marginBottom: theme('spacing-md'),
|
||||||
width: "100%",
|
width: "100%",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.6,
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
wordWrap: "break-word",
|
wordWrap: "break-word",
|
||||||
},
|
},
|
||||||
Stats: {
|
Stats: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: theme.spacing.lg,
|
gap: theme('spacing-lg'),
|
||||||
paddingTop: theme.spacing.md,
|
paddingTop: theme('spacing-md'),
|
||||||
borderTop: `1px solid ${theme.colors.border}`,
|
borderTop: `1px solid ${theme('colors-border')}`,
|
||||||
},
|
},
|
||||||
Stat: {
|
Stat: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|
@ -61,11 +62,11 @@ const UserProfile = define('UserProfile', {
|
||||||
StatValue: {
|
StatValue: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
},
|
},
|
||||||
StatLabel: {
|
StatLabel: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -114,7 +115,7 @@ const UserProfile = define('UserProfile', {
|
||||||
verified: {
|
verified: {
|
||||||
parts: {
|
parts: {
|
||||||
Avatar: {
|
Avatar: {
|
||||||
border: `2px solid ${theme.colors.accent}`,
|
border: `2px solid ${theme('colors-accent')}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,68 @@
|
||||||
import { define } from '../../src'
|
import { define } from '../../src'
|
||||||
import { theme } from '../ssr/helpers'
|
import { theme } from '../ssr/themes'
|
||||||
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'
|
||||||
|
|
||||||
|
// ThemePicker component
|
||||||
|
const ThemePicker = define('SpaThemePicker', {
|
||||||
|
marginLeft: 'auto',
|
||||||
|
|
||||||
|
parts: {
|
||||||
|
Select: {
|
||||||
|
base: 'select',
|
||||||
|
|
||||||
|
padding: `${theme('spacing-xs')} ${theme('spacing-md')}`,
|
||||||
|
background: theme('colors-bgElevated'),
|
||||||
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
|
borderRadius: theme('radius-sm'),
|
||||||
|
color: theme('colors-fg'),
|
||||||
|
fontSize: 14,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme('colors-borderActive'),
|
||||||
|
},
|
||||||
|
':focus': {
|
||||||
|
outline: 'none',
|
||||||
|
borderColor: theme('colors-borderActive'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ parts: { Root, Select } }) {
|
||||||
|
const handleChange = (e: Event) => {
|
||||||
|
const target = e.target as HTMLSelectElement
|
||||||
|
const themeName = target.value
|
||||||
|
document.body.setAttribute('data-theme', themeName)
|
||||||
|
localStorage.setItem('theme', themeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
<Select id="theme-select" onchange={handleChange}>
|
||||||
|
<option value="dark">Dark</option>
|
||||||
|
<option value="light">Light</option>
|
||||||
|
</Select>
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const Main = define('SpaMain', {
|
export const Main = define('SpaMain', {
|
||||||
base: 'div',
|
base: 'div',
|
||||||
|
|
||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
padding: theme.spacing.xl,
|
height: '100%',
|
||||||
fontFamily: theme.fonts.mono,
|
padding: theme('spacing-xl'),
|
||||||
background: theme.colors.bg,
|
fontFamily: theme('fonts-mono'),
|
||||||
color: theme.colors.fg,
|
background: theme('colors-bg'),
|
||||||
|
color: theme('colors-fg'),
|
||||||
|
boxSizing: 'border-box',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Container = define('SpaContainer', {
|
export const Container = define('SpaContainer', {
|
||||||
|
|
@ -26,19 +76,19 @@ export const Container = define('SpaContainer', {
|
||||||
const Link = define('Link', {
|
const Link = define('Link', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[aria-current]': {
|
'&[aria-current]': {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -62,51 +112,51 @@ const Nav = define('Nav', {
|
||||||
base: 'nav',
|
base: 'nav',
|
||||||
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing.lg,
|
gap: theme('spacing-lg'),
|
||||||
marginBottom: theme.spacing.xl,
|
marginBottom: theme('spacing-xl'),
|
||||||
padding: theme.spacing.lg,
|
padding: theme('spacing-lg'),
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const P = define('P', {
|
const P = define('P', {
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
marginBottom: theme.spacing.xxl,
|
marginBottom: theme('spacing-xxl'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExamplesGrid = define('ExamplesGrid', {
|
const ExamplesGrid = define('ExamplesGrid', {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gap: theme.spacing.lg,
|
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: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
padding: theme.spacing.lg,
|
padding: theme('spacing-lg'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
H2: {
|
H2: {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
margin: `0 0 ${theme.spacing.sm}px 0`,
|
margin: `0 0 ${theme('spacing-sm')} 0`,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
},
|
},
|
||||||
P: {
|
P: {
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
margin: 0,
|
margin: 0,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}
|
}
|
||||||
|
|
@ -182,13 +232,13 @@ export function route(path: string) {
|
||||||
const HomeLink = define('HomeLink', {
|
const HomeLink = define('HomeLink', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -206,6 +256,7 @@ export function App() {
|
||||||
<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>
|
||||||
<Link href="/spa/navigation" aria-current={path === '/spa/navigation' ? 'page' : undefined}>Navigation</Link>
|
<Link href="/spa/navigation" aria-current={path === '/spa/navigation' ? 'page' : undefined}>Navigation</Link>
|
||||||
<Link href="/spa/form" aria-current={path === '/spa/form' ? 'page' : undefined}>Forms</Link>
|
<Link href="/spa/form" aria-current={path === '/spa/form' ? 'page' : undefined}>Forms</Link>
|
||||||
|
<ThemePicker />
|
||||||
</Nav>
|
</Nav>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
{route(path)}
|
{route(path)}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,30 @@
|
||||||
<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">
|
||||||
<link rel="stylesheet" href="/main.css"/>
|
|
||||||
<title>Forge SPA Examples</title>
|
<title>Forge SPA Examples</title>
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body data-theme="dark">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/spa.js"></script>
|
<script type="module" src="/spa.js"></script>
|
||||||
|
<script>
|
||||||
|
// Load saved theme and apply it
|
||||||
|
const savedTheme = localStorage.getItem('theme') || 'dark'
|
||||||
|
document.body.setAttribute('data-theme', savedTheme)
|
||||||
|
|
||||||
|
// Set initial select value after page loads
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const select = document.getElementById('theme-select')
|
||||||
|
if (select) select.value = savedTheme
|
||||||
|
})
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,30 @@
|
||||||
export default {
|
export default {
|
||||||
colors: {
|
'colors-bg': '#0a0a0a',
|
||||||
bg: '#0a0a0a',
|
'colors-bgElevated': '#111',
|
||||||
bgElevated: '#111',
|
'colors-bgHover': '#1a1a1a',
|
||||||
bgHover: '#1a1a1a',
|
|
||||||
|
|
||||||
fg: '#00ff00',
|
'colors-fg': '#00ff00',
|
||||||
fgMuted: '#888',
|
'colors-fgMuted': '#888',
|
||||||
fgDim: '#444',
|
'colors-fgDim': '#444',
|
||||||
|
|
||||||
border: '#222',
|
'colors-border': '#222',
|
||||||
borderActive: '#00ff00',
|
'colors-borderActive': '#00ff00',
|
||||||
|
|
||||||
accent: '#00ff00',
|
'colors-accent': '#00ff00',
|
||||||
accentDim: '#008800',
|
'colors-accentDim': '#008800',
|
||||||
},
|
|
||||||
|
|
||||||
fonts: {
|
'fonts-mono': "'Monaco', 'Menlo', 'Consolas', monospace",
|
||||||
mono: "'Monaco', 'Menlo', 'Consolas', monospace",
|
'fonts-sans': "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
||||||
sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
||||||
},
|
|
||||||
|
|
||||||
spacing: {
|
'spacing-xs': '8px',
|
||||||
xs: 8,
|
'spacing-sm': '12px',
|
||||||
sm: 12,
|
'spacing-md': '16px',
|
||||||
md: 16,
|
'spacing-lg': '24px',
|
||||||
lg: 24,
|
'spacing-xl': '32px',
|
||||||
xl: 32,
|
'spacing-xxl': '48px',
|
||||||
xxl: 48,
|
|
||||||
},
|
'radius-sm': '4px',
|
||||||
|
'radius-md': '8px',
|
||||||
|
'radius-lg': '12px',
|
||||||
|
} as const
|
||||||
|
|
||||||
radius: {
|
|
||||||
sm: 4,
|
|
||||||
md: 8,
|
|
||||||
lg: 12,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
import { define, Styles } from '../../src'
|
import { define } from '../../src'
|
||||||
import darkTheme from './darkTheme'
|
import { theme } from './themes'
|
||||||
|
|
||||||
export const theme = darkTheme
|
|
||||||
|
|
||||||
export const Body = define('Body', {
|
export const Body = define('Body', {
|
||||||
base: 'body',
|
base: 'body',
|
||||||
|
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: theme.spacing.xl,
|
padding: theme('spacing-xl'),
|
||||||
fontFamily: theme.fonts.mono,
|
fontFamily: theme('fonts-mono'),
|
||||||
background: theme.colors.bg,
|
background: theme('colors-bg'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const Container = define('Container', {
|
const Container = define('Container', {
|
||||||
|
|
@ -21,21 +19,21 @@ const Container = define('Container', {
|
||||||
export const Header = define('Header', {
|
export const Header = define('Header', {
|
||||||
base: 'h1',
|
base: 'h1',
|
||||||
|
|
||||||
marginBottom: theme.spacing.xl,
|
marginBottom: theme('spacing-xl'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const ExampleSection = define('ExampleSection', {
|
export const ExampleSection = define('ExampleSection', {
|
||||||
marginBottom: theme.spacing.xl,
|
marginBottom: theme('spacing-xl'),
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
Header: {
|
Header: {
|
||||||
base: 'h2',
|
base: 'h2',
|
||||||
|
|
||||||
marginBottom: theme.spacing.md,
|
marginBottom: theme('spacing-md'),
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
}
|
}
|
||||||
|
|
@ -54,39 +52,96 @@ const Nav = define({
|
||||||
base: 'nav',
|
base: 'nav',
|
||||||
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing.lg,
|
gap: theme('spacing-lg'),
|
||||||
marginBottom: theme.spacing.xl,
|
marginBottom: theme('spacing-xl'),
|
||||||
padding: theme.spacing.lg,
|
padding: theme('spacing-lg'),
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const NavLink = define({
|
const NavLink = define({
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[aria-current]': {
|
'&[aria-current]': {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ThemePicker = define('ThemePicker', {
|
||||||
|
marginLeft: 'auto',
|
||||||
|
|
||||||
|
parts: {
|
||||||
|
Select: {
|
||||||
|
base: 'select',
|
||||||
|
|
||||||
|
padding: `${theme('spacing-xs')} ${theme('spacing-md')}`,
|
||||||
|
background: theme('colors-bgElevated'),
|
||||||
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
|
borderRadius: theme('radius-sm'),
|
||||||
|
color: theme('colors-fg'),
|
||||||
|
fontSize: 14,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme('colors-borderActive'),
|
||||||
|
},
|
||||||
|
':focus': {
|
||||||
|
outline: 'none',
|
||||||
|
borderColor: theme('colors-borderActive'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render({ parts: { Root, Select } }) {
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
<Select id="theme-select" onchange="window.switchTheme(this.value)">
|
||||||
|
<option value="dark">Dark</option>
|
||||||
|
<option value="light">Light</option>
|
||||||
|
</Select>
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const Layout = define({
|
export const Layout = define({
|
||||||
render({ props }) {
|
render({ props }) {
|
||||||
const path = props.path || ''
|
const path = props.path || ''
|
||||||
|
|
||||||
|
const themeScript = `
|
||||||
|
function switchTheme(themeName) {
|
||||||
|
document.body.setAttribute('data-theme', themeName)
|
||||||
|
localStorage.setItem('theme', themeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.switchTheme = switchTheme
|
||||||
|
|
||||||
|
// Load saved theme or default to dark
|
||||||
|
const savedTheme = localStorage.getItem('theme') || 'dark'
|
||||||
|
document.body.setAttribute('data-theme', savedTheme)
|
||||||
|
|
||||||
|
// Set initial select value
|
||||||
|
const select = document.getElementById('theme-select')
|
||||||
|
if (select) select.value = savedTheme
|
||||||
|
`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -104,10 +159,12 @@ export const Layout = define({
|
||||||
<NavLink href="/ssr/buttons" aria-current={path === '/ssr/buttons' ? 'page' : undefined}>Buttons</NavLink>
|
<NavLink href="/ssr/buttons" aria-current={path === '/ssr/buttons' ? 'page' : undefined}>Buttons</NavLink>
|
||||||
<NavLink href="/ssr/navigation" aria-current={path === '/ssr/navigation' ? 'page' : undefined}>Navigation</NavLink>
|
<NavLink href="/ssr/navigation" aria-current={path === '/ssr/navigation' ? 'page' : undefined}>Navigation</NavLink>
|
||||||
<NavLink href="/ssr/form" aria-current={path === '/ssr/form' ? 'page' : undefined}>Forms</NavLink>
|
<NavLink href="/ssr/form" aria-current={path === '/ssr/form' ? 'page' : undefined}>Forms</NavLink>
|
||||||
|
<ThemePicker />
|
||||||
</Nav>
|
</Nav>
|
||||||
<Header>{props.title}</Header>
|
<Header>{props.title}</Header>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Container>
|
</Container>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: themeScript }}></script>
|
||||||
</Body>
|
</Body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { createScope, Styles } from '../../src'
|
import { createScope, Styles } from '../../src'
|
||||||
import { theme } from './helpers'
|
import { theme } from './themes'
|
||||||
|
|
||||||
const { define } = createScope('Landing')
|
const { define } = createScope('Landing')
|
||||||
|
|
||||||
|
|
@ -7,11 +7,11 @@ const Page = define('Page', {
|
||||||
base: 'body',
|
base: 'body',
|
||||||
|
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: theme.spacing.xl,
|
padding: theme('spacing-xl'),
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
fontFamily: theme.fonts.mono,
|
fontFamily: theme('fonts-mono'),
|
||||||
background: theme.colors.bg,
|
background: theme('colors-bg'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const Container = define('Container', {
|
const Container = define('Container', {
|
||||||
|
|
@ -24,8 +24,8 @@ const Pre = define('Pre', {
|
||||||
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.4,
|
||||||
marginBottom: theme.spacing.xl,
|
marginBottom: theme('spacing-xl'),
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
whiteSpace: 'pre',
|
whiteSpace: 'pre',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -34,37 +34,51 @@ const P = define('P', {
|
||||||
|
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
lineHeight: 1.6,
|
lineHeight: 1.6,
|
||||||
marginBottom: theme.spacing.xl,
|
marginBottom: theme('spacing-xl'),
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const LinkSection = define('LinkSection', {
|
const LinkSection = define('LinkSection', {
|
||||||
marginTop: theme.spacing.xxl,
|
marginTop: theme('spacing-xxl'),
|
||||||
paddingTop: theme.spacing.xl,
|
paddingTop: theme('spacing-xl'),
|
||||||
borderTop: `1px solid ${theme.colors.border}`,
|
borderTop: `1px solid ${theme('colors-border')}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const Link = define('Link', {
|
const Link = define('Link', {
|
||||||
base: 'a',
|
base: 'a',
|
||||||
|
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
marginRight: theme.spacing.xl,
|
marginRight: theme('spacing-xl'),
|
||||||
padding: `${theme.spacing.sm}px ${theme.spacing.lg}px`,
|
padding: `${theme('spacing-sm')} ${theme('spacing-lg')}`,
|
||||||
background: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
':hover': {
|
':hover': {
|
||||||
background: theme.colors.bgHover,
|
background: theme('colors-bgHover'),
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const LandingPage = () => (
|
export const LandingPage = () => {
|
||||||
|
const themeScript = `
|
||||||
|
function switchTheme(themeName) {
|
||||||
|
document.body.setAttribute('data-theme', themeName)
|
||||||
|
localStorage.setItem('theme', themeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.switchTheme = switchTheme
|
||||||
|
|
||||||
|
// Load saved theme or default to dark
|
||||||
|
const savedTheme = localStorage.getItem('theme') || 'dark'
|
||||||
|
document.body.setAttribute('data-theme', savedTheme)
|
||||||
|
`
|
||||||
|
|
||||||
|
return (
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
@ -72,7 +86,7 @@ export const LandingPage = () => (
|
||||||
<title>forge</title>
|
<title>forge</title>
|
||||||
<Styles />
|
<Styles />
|
||||||
</head>
|
</head>
|
||||||
<Page>
|
<Page data-theme="dark">
|
||||||
<Container>
|
<Container>
|
||||||
<Pre>{`╔═╝╔═║╔═║╔═╝╔═╝
|
<Pre>{`╔═╝╔═║╔═║╔═╝╔═╝
|
||||||
╔═╝║ ║╔╔╝║ ║╔═╝
|
╔═╝║ ║╔╔╝║ ║╔═╝
|
||||||
|
|
@ -94,6 +108,8 @@ export const LandingPage = () => (
|
||||||
<Link href="/spa">SPA demos →</Link>
|
<Link href="/spa">SPA demos →</Link>
|
||||||
</LinkSection>
|
</LinkSection>
|
||||||
</Container>
|
</Container>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: themeScript }}></script>
|
||||||
</Page>
|
</Page>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
29
examples/ssr/lightTheme.tsx
Normal file
29
examples/ssr/lightTheme.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export default {
|
||||||
|
'colors-bg': '#f5f5f0',
|
||||||
|
'colors-bgElevated': '#fff',
|
||||||
|
'colors-bgHover': '#e8e8e0',
|
||||||
|
|
||||||
|
'colors-fg': '#0a0a0a',
|
||||||
|
'colors-fgMuted': '#666',
|
||||||
|
'colors-fgDim': '#999',
|
||||||
|
|
||||||
|
'colors-border': '#ddd',
|
||||||
|
'colors-borderActive': '#008800',
|
||||||
|
|
||||||
|
'colors-accent': '#008800',
|
||||||
|
'colors-accentDim': '#00aa00',
|
||||||
|
|
||||||
|
'fonts-mono': "'Monaco', 'Menlo', 'Consolas', monospace",
|
||||||
|
'fonts-sans': "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
||||||
|
|
||||||
|
'spacing-xs': '8px',
|
||||||
|
'spacing-sm': '12px',
|
||||||
|
'spacing-md': '16px',
|
||||||
|
'spacing-lg': '24px',
|
||||||
|
'spacing-xl': '32px',
|
||||||
|
'spacing-xxl': '48px',
|
||||||
|
|
||||||
|
'radius-sm': '4px',
|
||||||
|
'radius-md': '8px',
|
||||||
|
'radius-lg': '12px',
|
||||||
|
} as const
|
||||||
|
|
@ -1,47 +1,48 @@
|
||||||
import { define } from '../../src'
|
import { define } from '../../src'
|
||||||
import { Layout, theme } from './helpers'
|
import { Layout } from './helpers'
|
||||||
|
import { theme } from './themes'
|
||||||
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: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
marginBottom: theme.spacing.xxl,
|
marginBottom: theme('spacing-xxl'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const ExamplesGrid = define('SSR_ExamplesGrid', {
|
const ExamplesGrid = define('SSR_ExamplesGrid', {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gap: theme.spacing.lg,
|
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: theme.colors.bgElevated,
|
background: theme('colors-bgElevated'),
|
||||||
padding: theme.spacing.lg,
|
padding: theme('spacing-lg'),
|
||||||
border: `1px solid ${theme.colors.border}`,
|
border: `1px solid ${theme('colors-border')}`,
|
||||||
borderRadius: theme.radius.sm,
|
borderRadius: theme('radius-sm'),
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
|
|
||||||
states: {
|
states: {
|
||||||
hover: {
|
hover: {
|
||||||
borderColor: theme.colors.borderActive,
|
borderColor: theme('colors-borderActive'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parts: {
|
parts: {
|
||||||
H2: {
|
H2: {
|
||||||
color: theme.colors.fg,
|
color: theme('colors-fg'),
|
||||||
margin: `0 0 ${theme.spacing.sm}px 0`,
|
margin: `0 0 ${theme('spacing-sm')} 0`,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
},
|
},
|
||||||
P: {
|
P: {
|
||||||
color: theme.colors.fgMuted,
|
color: theme('colors-fgMuted'),
|
||||||
margin: 0,
|
margin: 0,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
examples/ssr/themes.tsx
Normal file
10
examples/ssr/themes.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { createTheme, createThemedVar } from '../../src'
|
||||||
|
import darkTheme from './darkTheme'
|
||||||
|
import lightTheme from './lightTheme'
|
||||||
|
|
||||||
|
// Register themes and get typed keys back
|
||||||
|
const dark = createTheme('dark', darkTheme)
|
||||||
|
const light = createTheme('light', lightTheme)
|
||||||
|
|
||||||
|
// Create a typed themeVar function
|
||||||
|
export const theme = createThemedVar({ dark, light })
|
||||||
|
|
@ -2,6 +2,58 @@ 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>> = {}
|
||||||
|
const themes: Record<string, Record<string, any>> = {}
|
||||||
|
|
||||||
|
// Type registry for theme variables (will be auto-populated)
|
||||||
|
let registeredThemeKeys: Set<string> = new Set()
|
||||||
|
|
||||||
|
// Clear all registered styles
|
||||||
|
export function clearStyles() {
|
||||||
|
for (const key in styles) delete styles[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a theme with CSS custom properties
|
||||||
|
export function createTheme<const T extends Record<string, string | number>>(
|
||||||
|
name: string,
|
||||||
|
values: T
|
||||||
|
): T {
|
||||||
|
themes[name] = values as Record<string, string>
|
||||||
|
|
||||||
|
// track for runtime validation
|
||||||
|
Object.keys(values).forEach(key => registeredThemeKeys.add(key))
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate CSS for all registered themes
|
||||||
|
export function themesToCSS(): string {
|
||||||
|
let out: string[] = []
|
||||||
|
|
||||||
|
for (const [name, vars] of Object.entries(themes)) {
|
||||||
|
out.push(`[data-theme="${name}"] {`)
|
||||||
|
for (const [key, value] of Object.entries(vars)) {
|
||||||
|
out.push(` --theme-${key}: ${value};`)
|
||||||
|
}
|
||||||
|
out.push(`}\n`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper type to extract theme keys from multiple theme objects
|
||||||
|
type ThemeKeys<T> = T extends Record<string, any> ? keyof T : never
|
||||||
|
|
||||||
|
// Create a typed themeVar function from your themes
|
||||||
|
export function createThemedVar<T extends Record<string, any>>(_themes: T) {
|
||||||
|
return function themeVar<K extends ThemeKeys<T[keyof T]>>(name: K): string {
|
||||||
|
return `var(--theme-${name as string})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic themeVar (untyped fallback)
|
||||||
|
export function themeVar(name: string): string {
|
||||||
|
return `var(--theme-${name as string})`
|
||||||
|
}
|
||||||
|
|
||||||
// All CSS styles inside <style></style.
|
// All CSS styles inside <style></style.
|
||||||
// Use w/ SSR: <Styles/>
|
// Use w/ SSR: <Styles/>
|
||||||
|
|
@ -29,6 +81,14 @@ function injectStylesInBrowser() {
|
||||||
export function stylesToCSS(): string {
|
export function stylesToCSS(): string {
|
||||||
let out: string[] = []
|
let out: string[] = []
|
||||||
|
|
||||||
|
// Include theme CSS first
|
||||||
|
const themeCSS = themesToCSS()
|
||||||
|
if (themeCSS) {
|
||||||
|
out.push(themeCSS)
|
||||||
|
out.push('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then component styles
|
||||||
for (const [selector, style] of Object.entries(styles)) {
|
for (const [selector, style] of Object.entries(styles)) {
|
||||||
if (Object.keys(style).length === 0) continue
|
if (Object.keys(style).length === 0) continue
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user