/ |/ \ / \ / \ / | $$$$$$$$//$$$$$$ |$$$$$$$ |/$$$$$$ |$$$$$$$$/ $$ |__ $$ | $$ |$$ |__$$ |$$ | _$$/ $$ |__ $$ | $$ | $$ |$$ $$< $$ |/ |$$ | $$$$$/ $$ | $$ |$$$$$$$ |$$ |$$$$ |$$$$$/ $$ | $$ \__$$ |$$ | $$ |$$ \__$$ |$$ |_____ $$ | $$ $$/ $$ | $$ |$$ $$/ $$ | $$/ $$$$$$/ $$/ $$/ $$$$$$/ $$$$$$$$/
218 lines
6.4 KiB
TypeScript
218 lines
6.4 KiB
TypeScript
import { define } from 'forge'
|
|
import { theme } from './theme'
|
|
import * as icons from 'lucide-static'
|
|
import { Grid } from './grid'
|
|
import { VStack } from './stack'
|
|
import { Section } from './section'
|
|
import { H2, Text } from './text'
|
|
|
|
export type IconName = keyof typeof icons
|
|
|
|
// Icon wrapper - the SVG is injected via dangerouslySetInnerHTML
|
|
const IconWrapper = define('Icon', {
|
|
display: 'block',
|
|
flexShrink: 0,
|
|
})
|
|
|
|
// IconLink wrapper
|
|
const IconLinkWrapper = define('IconLink', {
|
|
base: 'a',
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
transition: 'opacity 0.2s',
|
|
|
|
states: {
|
|
':hover': {
|
|
opacity: 0.7,
|
|
},
|
|
},
|
|
})
|
|
|
|
type IconProps = Parameters<typeof IconWrapper>[0] & {
|
|
name: IconName
|
|
size?: number
|
|
}
|
|
|
|
type IconLinkProps = Parameters<typeof IconLinkWrapper>[0] & {
|
|
name: IconName
|
|
size?: number
|
|
}
|
|
|
|
function sizeToPixels(size: number): number {
|
|
return size * 4
|
|
}
|
|
|
|
export const Icon = (props: IconProps) => {
|
|
const { name, size = 6, style, ...rest } = props
|
|
|
|
const iconSvg = icons[name]
|
|
|
|
if (!iconSvg) {
|
|
throw new Error(`Icon "${name}" not found in Lucide icons`)
|
|
}
|
|
|
|
const pixelSize = sizeToPixels(size)
|
|
const iconStyle = {
|
|
width: `${pixelSize}px`,
|
|
height: `${pixelSize}px`,
|
|
...style,
|
|
}
|
|
|
|
// Modify the SVG string to include our custom attributes
|
|
const modifiedSvg = iconSvg
|
|
.replace(/width="[^"]*"/, '')
|
|
.replace(/height="[^"]*"/, '')
|
|
.replace(/class="[^"]*"/, '')
|
|
.replace(
|
|
/<svg([^>]*)>/,
|
|
`<svg$1 style="display: block; flex-shrink: 0; width: ${pixelSize}px; height: ${pixelSize}px;">`
|
|
)
|
|
|
|
return <IconWrapper dangerouslySetInnerHTML={{ __html: modifiedSvg }} style={iconStyle} {...rest} />
|
|
}
|
|
|
|
export const IconLink = (props: IconLinkProps) => {
|
|
const { href = '#', name, size, ...rest } = props
|
|
|
|
return (
|
|
<IconLinkWrapper href={href} {...rest}>
|
|
<Icon name={name} size={size} />
|
|
</IconLinkWrapper>
|
|
)
|
|
}
|
|
|
|
export const Test = () => {
|
|
return (
|
|
<Section>
|
|
{/* Size variations */}
|
|
<VStack gap={4}>
|
|
<H2>Icon Size Variations</H2>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
{([3, 4, 5, 6, 8, 10, 12, 16] as const).map((size) => (
|
|
<VStack h="center" gap={2} key={size}>
|
|
<Icon name="Heart" size={size} />
|
|
<Text>{size}</Text>
|
|
</VStack>
|
|
))}
|
|
</div>
|
|
</VStack>
|
|
|
|
{/* Styling with CSS classes */}
|
|
<VStack gap={4}>
|
|
<H2>Styling with CSS Classes</H2>
|
|
<Grid cols={5} gap={6}>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Star" size={12} />
|
|
<Text>Default</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Star" size={12} style={{ color: '#3b82f6' }} />
|
|
<Text>Blue Color</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Star" size={12} class="stroke-1" />
|
|
<Text>Thin Stroke</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Star" size={12} style={{ color: '#fbbf24', fill: 'currentColor', stroke: 'none' }} />
|
|
<Text>Filled</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Star" size={12} style={{ color: '#a855f7', transition: 'color 0.2s' }} />
|
|
<Text>Hover Effect</Text>
|
|
</VStack>
|
|
</Grid>
|
|
</VStack>
|
|
|
|
{/* Advanced styling */}
|
|
<VStack gap={4}>
|
|
<H2>Advanced Styling</H2>
|
|
<Grid cols={4} gap={6}>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Heart" size={12} style={{ color: '#ef4444', fill: 'currentColor', stroke: 'none' }} />
|
|
<Text>Filled Heart</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Shield" size={12} style={{ color: '#16a34a', strokeWidth: '2' }} />
|
|
<Text>Thick Stroke</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Sun" size={12} style={{ color: '#eab308' }} />
|
|
<Text>Sun Icon</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<Icon name="Zap" size={12} style={{ color: '#60a5fa', filter: 'drop-shadow(0 4px 6px rgba(0,0,0,0.3))' }} />
|
|
<Text>Drop Shadow</Text>
|
|
</VStack>
|
|
</Grid>
|
|
</VStack>
|
|
|
|
{/* Icon links */}
|
|
<VStack gap={4}>
|
|
<H2>Icon Links</H2>
|
|
<div style={{ display: 'flex', gap: '24px' }}>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink name="Home" size={8} href="/" />
|
|
<Text>Home Link</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink name="ExternalLink" size={8} href="https://example.com" target="_blank" />
|
|
<Text>External Link</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink name="Mail" size={8} href="mailto:hello@example.com" />
|
|
<Text>Email Link</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink name="Phone" size={8} href="tel:+1234567890" />
|
|
<Text>Phone Link</Text>
|
|
</VStack>
|
|
</div>
|
|
</VStack>
|
|
|
|
{/* Styled icon links */}
|
|
<VStack gap={4}>
|
|
<H2>Styled Icon Links</H2>
|
|
<Grid cols={4} gap={6}>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink
|
|
name="Download"
|
|
size={8}
|
|
href="#"
|
|
style={{
|
|
backgroundColor: '#3b82f6',
|
|
color: 'white',
|
|
padding: '8px',
|
|
borderRadius: '8px',
|
|
}}
|
|
/>
|
|
<Text>Button Style</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink
|
|
name="Settings"
|
|
size={8}
|
|
href="#"
|
|
style={{
|
|
border: '2px solid #d1d5db',
|
|
padding: '8px',
|
|
borderRadius: '9999px',
|
|
}}
|
|
/>
|
|
<Text>Circle Border</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink name="Heart" size={8} href="#" style={{ color: '#ef4444' }} />
|
|
<Text>Red Heart</Text>
|
|
</VStack>
|
|
<VStack h="center" gap={2}>
|
|
<IconLink name="Star" size={8} href="#" style={{ color: '#fbbf24', fill: 'currentColor' }} />
|
|
<Text>Filled Star</Text>
|
|
</VStack>
|
|
</Grid>
|
|
</VStack>
|
|
</Section>
|
|
)
|
|
}
|