From 27ac2dff23dd9eda5ea43bad337a7bb726fe20f1 Mon Sep 17 00:00:00 2001 From: Sascha Date: Wed, 13 Aug 2025 14:47:55 +0200 Subject: [PATCH] =?UTF-8?q?Men=C3=BCleisten=20Prototyp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 69 ------------- src/app/page.tsx | 52 +++------- src/components/ThemeToggle.tsx | 16 +++ src/components/layout/navigation.tsx | 86 ++++++++++++++++ src/main.tsx | 13 ++- src/scss/App.scss | 70 +++----------- src/scss/globals.scss | 1 + src/scss/index.scss | 67 ------------- src/scss/themes.scss | 22 +++++ src/scss/topbar.scss | 140 +++++++++++++++++++++++++++ src/scss/variables.scss | 2 + 11 files changed, 298 insertions(+), 240 deletions(-) create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/scss/globals.scss delete mode 100644 src/scss/index.scss create mode 100644 src/scss/themes.scss create mode 100644 src/scss/topbar.scss create mode 100644 src/scss/variables.scss diff --git a/README.md b/README.md index 7959ce4..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,69 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default tseslint.config([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - ...tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - ...tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - ...tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default tseslint.config([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` diff --git a/src/app/page.tsx b/src/app/page.tsx index 533d73c..4ddd3fb 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,46 +1,20 @@ -import { useState } from 'react' -import reactLogo from '../assets/react.svg' -import viteLogo from '../assets/vite.svg' -import '../scss/App.scss' +import { useState, useEffect } from 'react'; +import '../scss/App.scss'; +import Navbar from '../components/layout/Navigation'; -function App() { - const [count, setCount] = useState(3) +function PortfolioApp() { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme); + }, [theme]); return ( <> -
-
John Doe
-
- - - - - - -
-
-
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

+ +

Aktuelles Theme: {theme}

- ) + ); } -export default App +export default PortfolioApp; diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx new file mode 100644 index 0000000..7ebb097 --- /dev/null +++ b/src/components/ThemeToggle.tsx @@ -0,0 +1,16 @@ +type Props = { + theme: 'light' | 'dark'; + setTheme: React.Dispatch>; +}; + +export default function ThemeToggle({ theme, setTheme }: Props) { + const toggleTheme = () => { + setTheme(theme === 'light' ? 'dark' : 'light'); + }; + + return ( + + ); +} diff --git a/src/components/layout/navigation.tsx b/src/components/layout/navigation.tsx index e69de29..e24cc71 100644 --- a/src/components/layout/navigation.tsx +++ b/src/components/layout/navigation.tsx @@ -0,0 +1,86 @@ +import { useState } from 'react'; +import ThemeToggle from '../ThemeToggle'; + +type Props = { + theme: 'light' | 'dark'; + setTheme: React.Dispatch>; +}; + +export default function Navbar({ theme, setTheme }: Props) { + const [menuOpen, setMenuOpen] = useState(false); + return ( + // TODO Centralize strings, create buttons via loops +
+
Sascha Bach
+
+ + + + + + + + {/* Burger Button für Mobile */} +
setMenuOpen(!menuOpen)} + > + + + +
+ + {/* Optional: Mobile Menu Dropdown */} + {menuOpen && ( +
+ + + + + + +
+ )} +
+
+ ); +} +/*About + + Services + + Skills + + Certifications + + + Projects + + + Contact + */ diff --git a/src/main.tsx b/src/main.tsx index 9f5552f..171d58a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,9 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './scss/index.scss' -import App from './app/page.tsx' +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import PortfolioApp from './app/page.tsx'; createRoot(document.getElementById('root')!).render( - - , -) + + +); diff --git a/src/scss/App.scss b/src/scss/App.scss index 39eb93b..564abad 100644 --- a/src/scss/App.scss +++ b/src/scss/App.scss @@ -1,63 +1,17 @@ -#root { - width: 100% +@use 'themes' as t; +@use 'topbar'; + +// Standard Theme (Light) +:root { + @include t.theme-vars(t.$light-theme); } -.navbar { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1em; - background-color: #fff; - color: black; - - &__container { - &__button { - font-feature-settings: inherit; - font-variation-settings: inherit; - letter-spacing: inherit; - color: inherit; - border-radius: 0; - background-color: transparent; - opacity: 1; - } - } +// Dark Theme aktiv, wenn `data-theme="dark"` am html oder body +[data-theme='dark'] { + @include t.theme-vars(t.$dark-theme); } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; +body { + background-color: var(--color-background); + color: var(--color-text); } - -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} - -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -/* -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} */ - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} \ No newline at end of file diff --git a/src/scss/globals.scss b/src/scss/globals.scss new file mode 100644 index 0000000..eb7d2fc --- /dev/null +++ b/src/scss/globals.scss @@ -0,0 +1 @@ +@forward 'variables'; diff --git a/src/scss/index.scss b/src/scss/index.scss deleted file mode 100644 index cef41d7..0000000 --- a/src/scss/index.scss +++ /dev/null @@ -1,67 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #999; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; - color: red; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/scss/themes.scss b/src/scss/themes.scss new file mode 100644 index 0000000..b7f7edb --- /dev/null +++ b/src/scss/themes.scss @@ -0,0 +1,22 @@ +@use 'sass:map'; + +// Light Theme Farben +$light-theme: ( + primary: #000000, + background: #ffffff, + text: #000000, +); + +// Dark Theme Farben +$dark-theme: ( + primary: #f59e0b, + background: #1f2937, + text: #f9fafb, +); + +// Mixin: SCSS-Map → CSS-Variablen schreiben +@mixin theme-vars($theme) { + --color-primary: #{map.get($theme, primary)}; + --color-background: #{map.get($theme, background)}; + --color-text: #{map.get($theme, text)}; +} diff --git a/src/scss/topbar.scss b/src/scss/topbar.scss new file mode 100644 index 0000000..1184e78 --- /dev/null +++ b/src/scss/topbar.scss @@ -0,0 +1,140 @@ +@use 'globals'; + +.navbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1em; + background: var(--color-background); + color: var(--color-text); + height: 30px; + + &__name { + font-weight: bold; + font-size: 1.25rem; + } + // Desktop Bar + &__container { + &__button { + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + border-color: unset; + background-color: transparent; + opacity: 1; + + padding: 0.6rem 1.2rem; + border: none; + border-radius: 8px; + cursor: pointer; + transition: box-shadow 0.2s ease, transform 0.2s ease; + + &:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + transform: translateY(-2px); + } + + &:active { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transform: translateY(0); + } + } + } + // Burger-Button für mobile Geräte + &__burger-button { + flex-direction: column; + justify-content: space-between; + width: 20px; + height: 17px; + background: none; + border: none; + cursor: pointer; + padding: 0; + + &__item { + display: block; + height: 4px; + width: 100%; + background-color: var(--color-text); + border-radius: 2px; + transition: all 0.3s ease; + margin-top: 2px; + } + + &:hover .navbar__container__burger-button__item { + background-color: var(--color-primary); + } + + &.active { + .navbar__container__burger-button__item:nth-child(1) { + transform: rotate(45deg) translate(5px, 5px); + } + .navbar__container__burger-button__item:nth-child(2) { + opacity: 0; + } + .navbar__container__burger-button__item:nth-child(3) { + transform: rotate(-45deg) translate(5px, -5px); + } + .navbar__container__burger-button__item:nth-child(n + 4) { + opacity: 0; + } + } + } + + /* Mobile Menu Dropdown */ + .navbar__mobile-menu { + display: flex; + flex-direction: column; + position: absolute; + right: 0; + background: var(--color-background); + border: 1px solid var(--color-primary); + border-radius: 8px; + padding: 0.5em; + gap: 0.5em; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + z-index: 10; + + button { + background: none; + border: none; + color: var(--color-text); + padding: 0.5em 1em; + text-align: right; + cursor: pointer; + border-radius: 4px; + transition: background 0.2s; + + &:hover { + background: rgba(0, 0, 0, 0.05); + } + } + } +} + +// Mobile-Only Styles +@media (max-width: globals.$mobile-breakpoint) { + .navbar__container__button { + display: none; + } +} + +@media (min-width: globals.$desktop-breakpoint) { + .navbar__burger-button { + display: none; + } +} + +.theme-toggle { + background: none; + border: none; + font-size: 1rem; + cursor: pointer; + transition: transform 0.2s ease; + width: 20px; + &:hover { + transform: scale(1.2); + } +} diff --git a/src/scss/variables.scss b/src/scss/variables.scss new file mode 100644 index 0000000..cfe6bd6 --- /dev/null +++ b/src/scss/variables.scss @@ -0,0 +1,2 @@ +$mobile-breakpoint: 768px; +$desktop-breakpoint: 769px;