Add hex background to showcase with interactive controls
- Render HexGridBackground globally on every page - Add dedicated Hex Background page with sliders for all 8 props - Lift hex props state to App for real-time global updates - Add three, @react-three/fiber, hex-background deps Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
858
package-lock.json
generated
858
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,12 +25,15 @@
|
|||||||
"clean": "rm -rf dist release"
|
"clean": "rm -rf dist release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dangerousthings/hex-background": "^0.2.0",
|
||||||
"@dangerousthings/react": "*",
|
"@dangerousthings/react": "*",
|
||||||
"@dangerousthings/tokens": "*",
|
"@dangerousthings/tokens": "*",
|
||||||
"@dangerousthings/web": "*",
|
"@dangerousthings/web": "*",
|
||||||
|
"@react-three/fiber": "^9.5.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-icons": "^5.6.0"
|
"react-icons": "^5.6.0",
|
||||||
|
"three": "^0.183.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dangerousthings/tailwind-preset": "*",
|
"@dangerousthings/tailwind-preset": "*",
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { themes } from '@dangerousthings/tokens';
|
import { themes } from '@dangerousthings/tokens';
|
||||||
import type { ThemeBrand, ThemeMode } from '@dangerousthings/tokens';
|
import type { ThemeBrand, ThemeMode } from '@dangerousthings/tokens';
|
||||||
|
import { HexGridBackground } from '@dangerousthings/hex-background';
|
||||||
|
import type { HexGridBackgroundProps } from '@dangerousthings/hex-background';
|
||||||
import { HomePage } from './pages/HomePage';
|
import { HomePage } from './pages/HomePage';
|
||||||
import { BevelsPage } from './pages/BevelsPage';
|
import { BevelsPage } from './pages/BevelsPage';
|
||||||
import { GlowsPage } from './pages/GlowsPage';
|
|
||||||
import { FormsPage } from './pages/FormsPage';
|
import { FormsPage } from './pages/FormsPage';
|
||||||
import { AnimationsPage } from './pages/AnimationsPage';
|
import { AnimationsPage } from './pages/AnimationsPage';
|
||||||
import { CardsAdvancedPage } from './pages/CardsAdvancedPage';
|
import { CardsAdvancedPage } from './pages/CardsAdvancedPage';
|
||||||
import { TokensPage } from './pages/TokensPage';
|
import { TokensPage } from './pages/TokensPage';
|
||||||
|
import { HexBackgroundPage } from './pages/HexBackgroundPage';
|
||||||
|
|
||||||
const navPages = [
|
const navPages = [
|
||||||
{ hash: '', label: 'Home' },
|
{ hash: '', label: 'Home' },
|
||||||
{ hash: 'bevels', label: 'Bevels' },
|
{ hash: 'bevels', label: 'Bevels' },
|
||||||
{ hash: 'glows', label: 'Glows' },
|
|
||||||
{ hash: 'forms', label: 'Forms' },
|
{ hash: 'forms', label: 'Forms' },
|
||||||
{ hash: 'animations', label: 'Animations' },
|
{ hash: 'animations', label: 'Animations' },
|
||||||
{ hash: 'cards-advanced', label: 'Advanced Cards' },
|
{ hash: 'cards-advanced', label: 'Advanced Cards' },
|
||||||
|
{ hash: 'hex-background', label: 'Hex Background' },
|
||||||
{ hash: 'tokens', label: 'Tokens' },
|
{ hash: 'tokens', label: 'Tokens' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ export function App() {
|
|||||||
const [brand, setBrand] = useState<ThemeBrand>('dt');
|
const [brand, setBrand] = useState<ThemeBrand>('dt');
|
||||||
const [theme, setTheme] = useState<ThemeMode>('dark');
|
const [theme, setTheme] = useState<ThemeMode>('dark');
|
||||||
const [currentHash, setCurrentHash] = useState('');
|
const [currentHash, setCurrentHash] = useState('');
|
||||||
|
const [hexProps, setHexProps] = useState<HexGridBackgroundProps>({});
|
||||||
|
|
||||||
// Sync brand/theme to <html> so CSS custom properties cascade everywhere
|
// Sync brand/theme to <html> so CSS custom properties cascade everywhere
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -43,10 +46,10 @@ export function App() {
|
|||||||
function renderPage() {
|
function renderPage() {
|
||||||
switch (currentHash) {
|
switch (currentHash) {
|
||||||
case 'bevels': return <BevelsPage />;
|
case 'bevels': return <BevelsPage />;
|
||||||
case 'glows': return <GlowsPage />;
|
|
||||||
case 'forms': return <FormsPage />;
|
case 'forms': return <FormsPage />;
|
||||||
case 'animations': return <AnimationsPage />;
|
case 'animations': return <AnimationsPage />;
|
||||||
case 'cards-advanced': return <CardsAdvancedPage />;
|
case 'cards-advanced': return <CardsAdvancedPage />;
|
||||||
|
case 'hex-background': return <HexBackgroundPage hexProps={hexProps} onHexPropsChange={setHexProps} />;
|
||||||
case 'tokens': return <TokensPage brand={brand} />;
|
case 'tokens': return <TokensPage brand={brand} />;
|
||||||
default: return <HomePage />;
|
default: return <HomePage />;
|
||||||
}
|
}
|
||||||
@@ -92,6 +95,7 @@ export function App() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
<HexGridBackground {...hexProps} />
|
||||||
<main id="content">
|
<main id="content">
|
||||||
{renderPage()}
|
{renderPage()}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import type { HexGridBackgroundProps } from '@dangerousthings/hex-background';
|
||||||
|
import { Section } from '../components/Section';
|
||||||
|
|
||||||
|
const DEFAULTS: Required<HexGridBackgroundProps> = {
|
||||||
|
opacity: 0.5,
|
||||||
|
hexRadius: 0.5,
|
||||||
|
margin: 0.05,
|
||||||
|
maxHeight: 3,
|
||||||
|
animationInterval: 1500,
|
||||||
|
cameraSpeed: 0.02,
|
||||||
|
cameraRadius: 8,
|
||||||
|
fov: 40,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SliderConfig {
|
||||||
|
key: keyof HexGridBackgroundProps;
|
||||||
|
label: string;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sliders: SliderConfig[] = [
|
||||||
|
{ key: 'opacity', label: 'Opacity', min: 0, max: 1, step: 0.05 },
|
||||||
|
{ key: 'hexRadius', label: 'Hex Radius', min: 0.1, max: 2, step: 0.1 },
|
||||||
|
{ key: 'margin', label: 'Margin', min: 0, max: 0.5, step: 0.01 },
|
||||||
|
{ key: 'maxHeight', label: 'Max Height', min: 0.5, max: 10, step: 0.5 },
|
||||||
|
{ key: 'animationInterval', label: 'Animation Interval (ms)', min: 200, max: 5000, step: 100 },
|
||||||
|
{ key: 'cameraSpeed', label: 'Camera Speed', min: 0, max: 0.2, step: 0.005 },
|
||||||
|
{ key: 'cameraRadius', label: 'Camera Radius', min: 3, max: 20, step: 0.5 },
|
||||||
|
{ key: 'fov', label: 'Field of View', min: 20, max: 90, step: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface HexBackgroundPageProps {
|
||||||
|
hexProps: HexGridBackgroundProps;
|
||||||
|
onHexPropsChange: (props: HexGridBackgroundProps) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HexBackgroundPage({ hexProps, onHexPropsChange }: HexBackgroundPageProps) {
|
||||||
|
const handleChange = (key: keyof HexGridBackgroundProps, value: number) => {
|
||||||
|
onHexPropsChange({ ...hexProps, [key]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
onHexPropsChange({ ...DEFAULTS });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 className="page-title">Hex Background</h1>
|
||||||
|
<p className="page-subtitle">
|
||||||
|
3D hexagon grid background powered by Three.js + React Three Fiber.
|
||||||
|
Adjust parameters below — changes apply to the global background in real time.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Section title="Parameters" description="All HexGridBackground props exposed as controls.">
|
||||||
|
<div style={{ display: 'grid', gap: 'var(--space-4)' }}>
|
||||||
|
{sliders.map(({ key, label, min, max, step }) => (
|
||||||
|
<div key={key} style={{ display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'center', gap: 'var(--space-4)' }}>
|
||||||
|
<label style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
|
<span style={{ fontSize: '0.8rem', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
|
value={hexProps[key] ?? DEFAULTS[key]}
|
||||||
|
onChange={e => handleChange(key, parseFloat(e.target.value))}
|
||||||
|
style={{ width: '100%', accentColor: 'var(--color-primary)' }}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<span style={{
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
color: 'var(--color-primary)',
|
||||||
|
minWidth: '4.5em',
|
||||||
|
textAlign: 'right',
|
||||||
|
}}>
|
||||||
|
{hexProps[key] ?? DEFAULTS[key]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="dt-btn mode-normal"
|
||||||
|
onClick={handleReset}
|
||||||
|
type="button"
|
||||||
|
style={{ marginTop: 'var(--space-6)' }}
|
||||||
|
>
|
||||||
|
Reset to Defaults
|
||||||
|
</button>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Usage" description="Drop-in component for any React app with Three.js.">
|
||||||
|
<div className="terminal dt-accent-top">
|
||||||
|
<code>{`import { HexGridBackground } from '@dangerousthings/hex-background';
|
||||||
|
|
||||||
|
// Full-viewport fixed background (renders behind content)
|
||||||
|
<HexGridBackground
|
||||||
|
opacity={${hexProps.opacity ?? DEFAULTS.opacity}}
|
||||||
|
hexRadius={${hexProps.hexRadius ?? DEFAULTS.hexRadius}}
|
||||||
|
maxHeight={${hexProps.maxHeight ?? DEFAULTS.maxHeight}}
|
||||||
|
cameraSpeed={${hexProps.cameraSpeed ?? DEFAULTS.cameraSpeed}}
|
||||||
|
/>`}</code>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user