added react frontend

This commit is contained in:
Vyacheslav K
2025-05-21 14:40:45 +03:00
parent d3e7607d15
commit cca57995d1
3 changed files with 263 additions and 63 deletions

View File

@@ -1,42 +1,74 @@
#root { .glass {
max-width: 1280px; background: rgba(31, 41, 55, 0.2);
margin: 0 auto; backdrop-filter: blur(10px);
padding: 2rem; -webkit-backdrop-filter: blur(10px);
text-align: center; border: 1px solid rgba(147, 51, 234, 0.3);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
} }
.logo { .glass:hover {
height: 6em; background: rgba(147, 51, 234, 0.2);
padding: 1.5em; border-color: rgba(147, 51, 234, 0.7);
will-change: filter; box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
} }
@keyframes logo-spin { .glass_enabled {
from { background: rgba(30, 240, 135, 0.2);
transform: rotate(0deg); border-color: rgba(51, 234, 127, 0.7);
} box-shadow: 0 6px 12px rgba(30, 240, 135, 0.3);
to {
transform: rotate(360deg);
}
} }
@media (prefers-reduced-motion: no-preference) { .gauge {
a:nth-of-type(2) .logo { width: 64px;
animation: logo-spin infinite 20s linear; height: 64px;
} background: conic-gradient(#a758f1 0deg, #7f0bfa var(--value), #4b5563 0);
box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
} }
.card { .gauge::before {
padding: 2em; content: '';
width: 48px;
height: 48px;
background: rgba(31, 41, 55, 0.9);
border-radius: 50%;
position: absolute;
} }
.read-the-docs { .gauge span {
color: #888; color: #d8b4fe;
font-size: 12px;
font-weight: bold;
z-index: 1;
} }
.clock,
.number {
font-size: 3rem;
color: #d8b4fe;
font-weight: bold;
}
input[type="range"] {
accent-color: #9333ea;
box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
}
.tile-content {
min-height: 12rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 1rem;
}
.gauge-tile-content {
justify-content: flex-start;
align-items: flex-start;
}

View File

@@ -1,35 +1,203 @@
import { useState } from 'react' import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css' import './App.css'
function App() {
const [count, setCount] = useState(0)
return ( const TextTile = ({ label }) => (
<> <div className="tile-content">
<div> <span className="text-purple-400 text-lg font-semibold">{label}</span>
<a href="https://vite.dev" target="_blank"> </div>
<img src={viteLogo} className="logo" alt="Vite logo" /> );
</a>
<a href="https://react.dev" target="_blank"> const ImageTile = ({ imageUrl, label }) => (
<img src={reactLogo} className="logo react" alt="React logo" /> <div className="tile-content">
</a> <img src={imageUrl} className="rounded-lg w-[6.3rem] h-[6.3rem] object-cover" alt={label} />
</div> <span className="text-purple-400 text-lg font-semibold">{label}</span>
<h1>Vite + React</h1> </div>
<div className="card"> );
<button onClick={() => setCount((count) => count + 1)}>
count is {count} const ClockTile = ({ id }) => {
</button> const [time, setTime] = useState("");
<p> const [date, setDate] = useState("");
Edit <code>src/App.jsx</code> and save to test HMR
</p> useEffect(() => {
</div> const updateClock = () => {
<p className="read-the-docs"> const now = new Date();
Click on the Vite and React logos to learn more const time = now.toLocaleTimeString('ru-RU', { hour12: false });
</p> const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' };
</> const date = now.toLocaleDateString('ru-RU', options)
) .replace(/,/, '')
} .replace(/(\d+)\s(\S+)/, '$1 $2')
.replace(/г\./, '');
setTime(time);
setDate(date.charAt(0).toUpperCase() + date.slice(1));
};
updateClock();
const interval = setInterval(updateClock, 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="tile-content">
<span className="text-5xl font-bold text-purple-200">{time}</span>
<span className="text-purple-400 text-lg font-semibold">{date}</span>
</div>
);
};
const GaugeTile = ({ gauges }) => (
<div className="tile-content gauge-tile-content">
<div className="flex flex-col gap-4">
{gauges.map((g, index) => (
<div key={index} className="flex items-center gap-4">
<div className="gauge" style={{ "--value": `${g.value}deg` }}>
<span>{Math.round((g.value / 360) * 100)}%</span>
</div>
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
</div>
))}
</div>
</div>
);
const SliderTile = ({ sliders, tileId, updateSliderValue }) => {
return (
<div className="tile-content">
<span className="text-purple-400 text-lg font-semibold">Slider Controls</span>
<div className="flex flex-col w-full gap-4">
{sliders.map((s, index) => (
<div key={index} className="flex flex-col items-start">
<div className="flex items-center gap-2">
<span className="text-purple-400 text-sm font-semibold">{s.label}</span>
<span className="text-purple-300 text-sm font-normal">{s.value}</span>
</div>
<input
type="range"
min="0"
max="100"
value={s.value}
onChange={(e) => updateSliderValue(tileId, index, e.target.value)}
className="w-full"
/>
</div>
))}
</div>
</div>
);
};
const NumberTile = ({ value, label }) => (
<div className="tile-content">
<span className="text-5xl font-bold text-purple-200">{value}</span>
<span className="text-purple-400 text-lg font-semibold">{label}</span>
</div>
);
const App = () => {
const [tiles, setTiles] = useState([
{ id: 1, type: "text", label: "Tile 1" },
{ id: 2, type: "gauge", gauges: [{ value: 75, label: "Metric 1" }, { value: 120, label: "Metric 2" }] },
{ id: 3, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 1" },
{ id: 4, type: "text", label: "Tile 4" },
{ id: 5, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 2" },
{ id: 6, type: "clock" },
{ id: 7, type: "slider", sliders: [{ value: 50, label: "Slider 1" }, { value: 75, label: "Slider 2" }] },
{ id: 8, type: "text", label: "Tile 5 (Double)" },
{ id: 9, type: "text", label: "Tile 6" },
{ id: 10, type: "number", value: 42, label: "Data Point" }
]);
const updateGaugeValue = (tileId, gaugeIndex) => {
setTiles(prevTiles =>
prevTiles.map(tile => {
if (tile.id === tileId && tile.type === "gauge") {
const newGauges = [...tile.gauges];
newGauges[gaugeIndex] = {
...newGauges[gaugeIndex],
value: (newGauges[gaugeIndex].value + 10) % 360
};
return { ...tile, gauges: newGauges };
}
return tile;
})
);
};
const updateSliderValue = (tileId, sliderIndex, newValue) => {
setTiles(prevTiles =>
prevTiles.map(tile => {
if (tile.id === tileId && tile.type === "slider") {
const newSliders = [...tile.sliders];
newSliders[sliderIndex] = {
...newSliders[sliderIndex],
value: parseInt(newValue)
};
return { ...tile, sliders: newSliders };
}
return tile;
})
);
};
const updateNumberValue = (tileId) => {
setTiles(prevTiles =>
prevTiles.map(tile => {
if (tile.id === tileId && tile.type === "number") {
return { ...tile, value: tile.value + 1 };
}
return tile;
})
);
};
return (
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-4">
{tiles.map(tile => (
<div
key={tile.id}
className={`glass rounded-lg ${tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" ? "w-96 h-48" : "w-48 h-48"} relative`}
>
{tile.type === "text" && <TextTile label={tile.label} />}
{tile.type === "image" && <ImageTile imageUrl={tile.imageUrl} label={tile.label} />}
{tile.type === "clock" && <ClockTile id={tile.id} />}
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
{tile.type === "slider" && <SliderTile sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
{tile.type === "number" && <NumberTile value={tile.value} label={tile.label} />}
{(tile.type === "gauge" || tile.type === "slider" || tile.type === "number") && (
<div className="absolute top-2 right-2 flex gap-2">
{tile.type === "gauge" && tile.gauges.map((_, index) => (
<button
key={index}
onClick={() => updateGaugeValue(tile.id, index)}
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
>
Update Gauge {index + 1}
</button>
))}
{tile.type === "slider" && tile.sliders.map((_, index) => (
<button
key={index}
onClick={() => updateSliderValue(tile.id, index, (parseInt(tile.sliders[index].value) + 10) % 101)}
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
>
Increment Slider {index + 1}
</button>
))}
{tile.type === "number" && (
<button
onClick={() => updateNumberValue(tile.id)}
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
>
Increment Number
</button>
)}
</div>
)}
</div>
))}
</div>
</div>
);
};
export default App export default App

View File

@@ -4,7 +4,7 @@ import './index.css'
import App from './App.jsx' import App from './App.jsx'
createRoot(document.getElementById('root')).render( createRoot(document.getElementById('root')).render(
<StrictMode> <body className='bg-gray-900 min-h-screen p-4'>
<App /> <App />
</StrictMode>, </body>
) )