diff --git a/06_react/agenda.md b/06_react/agenda.md index cd00d37..c1223ee 100644 --- a/06_react/agenda.md +++ b/06_react/agenda.md @@ -591,7 +591,7 @@ Projektarbeit **Inhalt:** - Einführung in Hooks in React. -- Hooks in React. `useState`. +- Hooks in React. `useState`, `useReducer` **Übungen:** diff --git a/06_react/unterricht/tag36/01_components-with-hooks/.gitignore b/06_react/unterricht/tag36/01_components-with-hooks/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/06_react/unterricht/tag36/01_components-with-hooks/.oxlintrc.json b/06_react/unterricht/tag36/01_components-with-hooks/.oxlintrc.json new file mode 100644 index 0000000..1255078 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/README.md b/06_react/unterricht/tag36/01_components-with-hooks/README.md new file mode 100644 index 0000000..d937833 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the Oxlint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and Oxlint's TypeScript related rules in your project. diff --git a/06_react/unterricht/tag36/01_components-with-hooks/index.html b/06_react/unterricht/tag36/01_components-with-hooks/index.html new file mode 100644 index 0000000..80be639 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/index.html @@ -0,0 +1,13 @@ + + + + + + + components with hooks + + +
+ + + diff --git a/06_react/unterricht/tag35/04_components-with-hooks/package-lock.json b/06_react/unterricht/tag36/01_components-with-hooks/package-lock.json similarity index 100% rename from 06_react/unterricht/tag35/04_components-with-hooks/package-lock.json rename to 06_react/unterricht/tag36/01_components-with-hooks/package-lock.json diff --git a/06_react/unterricht/tag36/01_components-with-hooks/package.json b/06_react/unterricht/tag36/01_components-with-hooks/package.json new file mode 100644 index 0000000..1ebc627 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/package.json @@ -0,0 +1,26 @@ +{ + "name": "components-with-hooks", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "bootstrap": "^5.3.8", + "react": "^19.3.1", + "react-dom": "^19.3.1", + "react-icons": "^5.7.0" + }, + "devDependencies": { + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.2", + "oxlint": "^1.89.0", + "sass-embedded": "^1.106.1", + "vite": "^8.3.5" + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/public/favicon.svg b/06_react/unterricht/tag36/01_components-with-hooks/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/06_react/unterricht/tag36/01_components-with-hooks/public/icons.svg b/06_react/unterricht/tag36/01_components-with-hooks/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/06_react/unterricht/tag36/01_components-with-hooks/public/logo-js.min.svg b/06_react/unterricht/tag36/01_components-with-hooks/public/logo-js.min.svg new file mode 100644 index 0000000..4acd335 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/public/logo-js.min.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/App.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/App.jsx new file mode 100644 index 0000000..36b1aba --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/App.jsx @@ -0,0 +1,39 @@ +import HeaderMain from './components/headers/HeaderMain'; +import FooterMain from './components/footers/FooterMain'; + +import ExampleUseState from './components/examples/ExampleUseState'; +import ExampleUseReducer from './components/examples/ExampleUseReducer'; + +function App() { + return ( + <> + +
+
+

Example Hooks

+
+

Hooks-Regeln

+

Es gibt zwei wichtige Regeln für die Verwendung von Hooks:

+
    +
  • Hooks sollten nur in Funktionskomponenten oder anderen Hooks verwendet werden.
  • +
  • + Hooks sollten immer oben in der Komponente verwendet werden, nicht in Schleifen, Bedingungen oder + verschachtelten Funktionen. +
  • +
+
+
+

Components with hooks

+
+

Example useReducer

+ +
+

Example useState

+ +
+
+ + + ); +} +export default App; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/buttons/Button.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/buttons/Button.jsx new file mode 100644 index 0000000..1cbdc14 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/buttons/Button.jsx @@ -0,0 +1,17 @@ +const Button = (props) => { + const { variant = 'custom', children } = props; + + // Event-Handler ===== + const handleClick = (e) => { + console.log('Event:', e); + + console.log('Button clicked!'); + }; + + return ( + + ); +}; +export default Button; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/examples/ExampleUseReducer.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/examples/ExampleUseReducer.jsx new file mode 100644 index 0000000..d10ea12 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/examples/ExampleUseReducer.jsx @@ -0,0 +1,58 @@ +import { useReducer } from 'react'; +import { FaPlus, FaMinus } from 'react-icons/fa6'; + +const initialState = { + counter: 0, + // more props.... +}; + +// Reducer Funktion verwaltet das state Objekt. Über diese Funktion lässt sich das "komplexere" state-Objekt gezielt verändern. +function reducer(state, action) { + // action Objekt hat immer folgende Eigenschaften: {type: 'ACTION_NAME', payload: changeValue } + const { type } = action; + + switch (type) { + case 'INCREASE': + return { ...state, counter: state.counter + 1 }; + case 'DECREASE': + return { ...state, counter: state.counter - 1 }; + default: + console.warn('Action Type not found'); + return state; + } +} + +const ExampleUseReducer = (props) => { + // const { } = props; + + // Hooks - Top Level + // const [counter, setCounter] = useState(0); + const [state, dispatch] = useReducer(reducer, initialState); + + const handleClick = (e) => { + const btn = e.currentTarget; + const label = btn.ariaLabel; // btn.getAttribute('aria-label') + switch (label.toLowerCase()) { + case 'increase': + dispatch({ type: 'INCREASE' }); + break; + case 'decrease': + dispatch({ type: 'DECREASE' }); + break; + default: //nothing + } + }; + + return ( +
+ Counter: {state.counter} + + +
+ ); +}; +export default ExampleUseReducer; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/examples/ExampleUseState.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/examples/ExampleUseState.jsx new file mode 100644 index 0000000..1a93578 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/examples/ExampleUseState.jsx @@ -0,0 +1,36 @@ +import { useState } from 'react'; +import { FaPlus, FaMinus } from 'react-icons/fa6'; + +const ExampleUseState = (props) => { + // const { } = props; + const [counter, setCounter] = useState(0); + + const handleClick = (e) => { + const btn = e.currentTarget; + const label = btn.ariaLabel; // btn.getAttribute('aria-label') + switch (label.toLowerCase()) { + case 'increase': + setCounter(counter + 1); + break; + case 'decrease': + setCounter((prevState) => { + return prevState - 1; + }); + break; + default: //nothing + } + }; + + return ( +
+ Counter: {counter} + + +
+ ); +}; +export default ExampleUseState; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/footers/FooterMain.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/footers/FooterMain.jsx new file mode 100644 index 0000000..5006bda --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/footers/FooterMain.jsx @@ -0,0 +1,78 @@ +const FooterMain = (props) => { + // const { } = props; + + return ( + + ); +}; +export default FooterMain; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/forms/FormNewsletter.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/forms/FormNewsletter.jsx new file mode 100644 index 0000000..d8c7ec7 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/forms/FormNewsletter.jsx @@ -0,0 +1,35 @@ +const FormNewsletter = (props) => { + // const { } = props; + + // EventHandler + const handleSubmit = (e) => { + e.preventDefault(); // Standardverhalten unterbinden + console.log('Event: ', e); + }; + + return ( +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ ); +}; +export default FormNewsletter; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/headers/HeaderMain.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/headers/HeaderMain.jsx new file mode 100644 index 0000000..acbddb2 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/headers/HeaderMain.jsx @@ -0,0 +1,12 @@ +import LogoMain from '../logos/LogoMain'; + +const HeaderMain = () => { + return ( +
+
+ +
+
+ ); +}; +export default HeaderMain; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/icons/Logo.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/icons/Logo.jsx new file mode 100644 index 0000000..dd3b6f0 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/icons/Logo.jsx @@ -0,0 +1,48 @@ +const Logo = () => { + return ( + + + + + + + + + ); +}; +export default Logo; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/inputs/InputAmount.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/inputs/InputAmount.jsx new file mode 100644 index 0000000..d6ac3f5 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/inputs/InputAmount.jsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; +import { FaMinus, FaPlus } from 'react-icons/fa6'; + +const InputAmount = (props) => { + const [amount, setAmount] = useState(0); + + // const { } = props; + + // Eventhandler + const handleChange = (e) => { + const value = Number(e.target.value); // e.target.valueAsNumber + + // Guard + if (isNaN(value)) return; + + setAmount(value); + }; + const handleClick = (e) => { + console.log('Event: ', e); + console.log(e.currentTarget.name); + switch (e.currentTarget.name) { + case 'increase': + setAmount((prevState) => prevState + 1); + console.log('inc'); + break; + case 'decrease': + setAmount(amount - 1); + console.log('dec'); + break; + default: //nothing + } + }; + + return ( +
+
+ + + + + + + + +
+
+ ); +}; +export default InputAmount; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/components/logos/LogoMain.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/components/logos/LogoMain.jsx new file mode 100644 index 0000000..2649b9e --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/components/logos/LogoMain.jsx @@ -0,0 +1,15 @@ +import Logo from '../icons/Logo'; + +// import Logo from '/logo-js.min.svg'; +const LogoMain = (props) => { + // const { } = props; + + return ( +
+ {/* JS Course */} + +
Firmen Slogan
+
+ ); +}; +export default LogoMain; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/main.jsx b/06_react/unterricht/tag36/01_components-with-hooks/src/main.jsx new file mode 100644 index 0000000..1d66d55 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/main.jsx @@ -0,0 +1,14 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +// CSS Bootstrap +// import 'bootstrap/dist/css/bootstrap.min.css'; +import './styles/main.scss'; + +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/01_frameworks/_index.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/01_frameworks/_index.scss new file mode 100644 index 0000000..42edceb --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/01_frameworks/_index.scss @@ -0,0 +1,3 @@ +// Bootstrap 5.3.8 or 6 (6.0.0-alpha.1) straight from node_modules. +// @forward 'bootstrap/scss/bootstrap-grid' +@forward 'bootstrap/scss/bootstrap'; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_gloria-hallelujah-v24-latin.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_gloria-hallelujah-v24-latin.scss new file mode 100644 index 0000000..f46eba2 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_gloria-hallelujah-v24-latin.scss @@ -0,0 +1,8 @@ +// gloria-hallelujah-regular - latin +@font-face { + font-display: swap; + font-family: 'Gloria Hallelujah'; + font-style: normal; + font-weight: 400; + src: url('../../assets/fonts/gloria-hallelujah-v24-latin/gloria-hallelujah-v24-latin-regular.woff2') format('woff2'); +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_index.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_index.scss new file mode 100644 index 0000000..d377888 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_index.scss @@ -0,0 +1,3 @@ +// @forward 'gloria-hallelujah-v24-latin'; +// @forward 'lato-v25-latin'; +// @forward 'source-sans-3-v19-latin'; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_lato-v25-latin.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_lato-v25-latin.scss new file mode 100644 index 0000000..522cfda --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_lato-v25-latin.scss @@ -0,0 +1,89 @@ +// lato-100 - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: normal; + font-weight: 100; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-100.woff2') format('woff2'); +} + +// lato-100italic - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: italic; + font-weight: 100; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-100italic.woff2') format('woff2'); +} + +// lato-300 - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: normal; + font-weight: 300; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-300.woff2') format('woff2'); +} + +// lato-300italic - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: italic; + font-weight: 300; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-300italic.woff2') format('woff2'); +} + +// lato-regular - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: normal; + font-weight: 400; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-regular.woff2') format('woff2'); +} + +// lato-italic - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: italic; + font-weight: 400; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-italic.woff2') format('woff2'); +} + +// lato-700 - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: normal; + font-weight: 700; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-700.woff2') format('woff2'); +} + +// lato-700italic - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: italic; + font-weight: 700; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-700italic.woff2') format('woff2'); +} + +// lato-900 - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: normal; + font-weight: 900; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-900.woff2') format('woff2'); +} + +// lato-900italic - latin +@font-face { + font-display: swap; + font-family: 'Lato'; + font-style: italic; + font-weight: 900; + src: url('../../assets/fonts/lato-v25-latin/lato-v25-latin-900italic.woff2') format('woff2'); +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_source-sans-3-v19-latin.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_source-sans-3-v19-latin.scss new file mode 100644 index 0000000..b732c16 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/02_fonts/_source-sans-3-v19-latin.scss @@ -0,0 +1,143 @@ +// source-sans-3-200 - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 200; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-200.woff2') format('woff2'); +} + +// source-sans-3-200italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 200; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-200italic.woff2') format('woff2'); +} + +// source-sans-3-300 - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 300; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-300.woff2') format('woff2'); +} + +// source-sans-3-300italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 300; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-300italic.woff2') format('woff2'); +} + +// source-sans-3-regular - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 400; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-regular.woff2') format('woff2'); +} + +// source-sans-3-italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 400; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-italic.woff2') format('woff2'); +} + +// source-sans-3-500 - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 500; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-500.woff2') format('woff2'); +} + +// source-sans-3-500italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 500; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-500italic.woff2') format('woff2'); +} + +// source-sans-3-600 - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 600; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-600.woff2') format('woff2'); +} + +// source-sans-3-600italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 600; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-600italic.woff2') format('woff2'); +} + +// source-sans-3-700 - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 700; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-700.woff2') format('woff2'); +} + +// source-sans-3-700italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 700; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-700italic.woff2') format('woff2'); +} + +// source-sans-3-800 - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 800; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-800.woff2') format('woff2'); +} + +// source-sans-3-800italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 800; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-800italic.woff2') format('woff2'); +} + +// source-sans-3-900 - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: normal; + font-weight: 900; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-900.woff2') format('woff2'); +} + +// source-sans-3-900italic - latin +@font-face { + font-display: swap; + font-family: 'Source Sans 3'; + font-style: italic; + font-weight: 900; + src: url('../../assets/fonts/source-sans-3-v19-latin/source-sans-3-v19-latin-900italic.woff2') format('woff2'); +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_colors.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_colors.scss new file mode 100644 index 0000000..a550f3d --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_colors.scss @@ -0,0 +1,13 @@ +/* Color Theme Swatches in Hex */ +$color-1: #5c4b51; +$color-2: #8cbeb2; +$color-3: #f2ebbf; +$color-4: #f3b562; +$color-5: #f06060; + +$color-red: $color-5; +$color-white: white; // falls weiß geändert wird z.B.: #f8f8f8 +$color-black: #101010; + +$color-dark-blue: #283e4a; +$linear-gradient-dark: linear-gradient(180deg, rgba(40, 62, 74, 1) 0%, rgba(24, 32, 38, 1) 100%); diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_index.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_index.scss new file mode 100644 index 0000000..aaf4e12 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_index.scss @@ -0,0 +1,2 @@ +// Variables (colors, typography) are pulled in by the partials that need them via @use. +@forward 'theme'; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_theme.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_theme.scss new file mode 100644 index 0000000..26b9863 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_theme.scss @@ -0,0 +1,52 @@ +@use 'typography' as *; + +// Allgemeine Formatierung +*, +html { + box-sizing: border-box; +} + +html, +body, +#root, +#app { + height: 100%; +} +// Sticky Footer +body, +#root, +#app { + display: flex; + flex-direction: column; +} + +main, +#main { + flex: 1 0 auto; +} + +body > footer, +.footer-main { + flex-shrink: 0; +} +// sticky end + +body { + margin: 0; + padding: 0; + font-family: $font-copy; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: $font-headline; + font-weight: 600; +} + +blockquote { + font-family: $font-quote; +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_typography.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_typography.scss new file mode 100644 index 0000000..7c9b095 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/03_base/_typography.scss @@ -0,0 +1,9 @@ +$fallback-serif: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; +$fallback-sans-serif: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + +$font-headline: 'Lato', $fallback-serif; +$font-subclaim: $fallback-sans-serif; + +$font-menu: 'Source Sans 3', $fallback-sans-serif; +$font-copy: 'Source Sans 3', $fallback-sans-serif; +$font-quote: 'Gloria Hallelujah', $fallback-serif; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/04_plugins/_index.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/04_plugins/_index.scss new file mode 100644 index 0000000..bd5ba6a --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/04_plugins/_index.scss @@ -0,0 +1,7 @@ +// @use '@fortawesome/fontawesome-free/scss/variables' with ( +// $font-path: '@fa-webfonts' +// ); +// @use '@fortawesome/fontawesome-free/scss/fontawesome'; +// @use '@fortawesome/fontawesome-free/scss/solid'; + +// @use 'toastify-1.12/toastify'; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/04_plugins/toastify-1.12/_toastify.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/04_plugins/toastify-1.12/_toastify.scss new file mode 100644 index 0000000..e359a14 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/04_plugins/toastify-1.12/_toastify.scss @@ -0,0 +1,88 @@ +/*! + * Toastify js 1.12.0 + * https://github.com/apvarun/toastify-js + * @license MIT licensed + * + * Copyright (C) 2018 Varun A P + */ + +.toastify { + padding: 12px 20px; + color: #ffffff; + display: inline-block; + box-shadow: + 0 3px 6px -1px rgba(0, 0, 0, 0.12), + 0 10px 36px -4px rgba(77, 96, 232, 0.3); + background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5); + background: linear-gradient(135deg, #73a5ff, #5477f5); + position: fixed; + opacity: 0; + transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1); + border-radius: 2px; + cursor: pointer; + text-decoration: none; + max-width: calc(50% - 20px); + z-index: 2147483647; +} + +.toastify.on { + opacity: 1; +} + +.toast-close { + background: transparent; + border: 0; + color: white; + cursor: pointer; + font-family: inherit; + font-size: 1em; + opacity: 0.4; + padding: 0 5px; +} + +.toastify-right { + right: 15px; +} + +.toastify-left { + left: 15px; +} + +.toastify-top { + top: -150px; +} + +.toastify-bottom { + bottom: -150px; +} + +.toastify-rounded { + border-radius: 25px; +} + +.toastify-avatar { + width: 1.5em; + height: 1.5em; + margin: -7px 5px; + border-radius: 2px; +} + +.toastify-center { + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + max-width: fit-content; + max-width: -moz-fit-content; +} + +@media only screen and (max-width: 360px) { + .toastify-right, + .toastify-left { + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + max-width: fit-content; + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_button.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_button.scss new file mode 100644 index 0000000..1660f6b --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_button.scss @@ -0,0 +1,17 @@ +button, +input { + &.btn-custom { + background-color: #f0f0f0; + color: #333; + border: 1px solid #ccc; + padding: 8px 16px; + border-radius: 0px; + margin: 3px; + &:hover, + &:focus { + background-color: #e0e0e0; + color: #333; + border: 1px solid #ccc; + } + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_chuck-norris-joke.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_chuck-norris-joke.scss new file mode 100644 index 0000000..232b479 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_chuck-norris-joke.scss @@ -0,0 +1,38 @@ +@use '../03_base/colors' as *; + +.chuck-norris-joke { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + min-height: calc(100vh - 3.5rem); // full screen below the navbar + background: #555 url('../../assets/img/concrete-bg.jpg') center no-repeat fixed; + background-size: cover; + + &::after { + content: ''; + position: absolute; + z-index: 1; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(circle, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.8) 100%); + } + + > * { + position: relative; + z-index: 2; + } + + .joke-text { + color: $color-white; + text-align: center; + font-size: 3rem; + max-width: 80%; + padding: 2rem; + text-shadow: 2px 2px 5px rgba(black, 0.5); + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_footerMain.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_footerMain.scss new file mode 100644 index 0000000..fd73f7c --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_footerMain.scss @@ -0,0 +1,45 @@ +@use '../03_base/colors' as *; + +.footer-main { + background-color: $color-black; + background: $linear-gradient-dark; + padding: 3rem 0; + [class^='col'] { + color: $color-white; + a { + color: $color-white; + } + h2, + h3, + h4, + h5, + h6 { + font-size: 1.5rem; + color: $color-2; + padding: 0.5rem 0; + margin-bottom: 1rem; + letter-spacing: 2px; + } + } + // Mobile First + .row { + flex-direction: column; + > div { + &:nth-child(1) { + order: 3; + } + &:nth-child(2) { + order: 2; + } + &:nth-child(3) { + order: 1; + } + } + } + @media (min-width: 992px) { + // @include media-breakpoint-up(md) { + .row { + flex-direction: row; + } + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_header-main.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_header-main.scss new file mode 100644 index 0000000..2884477 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_header-main.scss @@ -0,0 +1,15 @@ +.m-header-main { + width: 100%; + height: 400px; + + // https://cssreference.io/property/background/ + background: #efefef url('../../assets/img/header-bg.webp') center no-repeat border-box; + background-size: cover; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + border: 20px solid rgba(white, 0.25); +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_index.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_index.scss new file mode 100644 index 0000000..484b0de --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_index.scss @@ -0,0 +1,8 @@ +@forward 'button'; +// @forward 'chuck-norris-joke'; +@forward 'footerMain'; +@forward 'header-main'; +@forward 'input-amount'; +@forward 'logo-main'; +// @forward 'menu-main'; +// @forward 'product-manager'; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_input-amount.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_input-amount.scss new file mode 100644 index 0000000..8701a7c --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_input-amount.scss @@ -0,0 +1,27 @@ +.m-input-amount { + .input-group { + .input-group-text { + padding: 0; + display: inline-block; + overflow: hidden; + button { + border-radius: 0; + } + } + input { + max-width: 85px; + text-align: center; + } + /* Für Chrome, Safari, Edge und Opera */ + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* Für Firefox */ + input[type='number'] { + appearance: textfield; + } + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_logo-main.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_logo-main.scss new file mode 100644 index 0000000..399bd12 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_logo-main.scss @@ -0,0 +1,21 @@ +.logo-main { + margin: 0; + + img, + svg { + width: 320px; + height: auto; + max-width: 320px; + } + + figcaption { + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_menu-main.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_menu-main.scss new file mode 100644 index 0000000..df4627e --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_menu-main.scss @@ -0,0 +1,20 @@ +@use '../03_base/colors' as *; +@use '../03_base/typography' as *; + +// Main navigation = Bootstrap 6 navbar (see includes/menu-main.html). +// Only the look from frontend-gulp is added here: black bar, uppercase links. +.menu-main { + background-color: $color-black; + font-family: $font-menu; + + .nav-link { + text-transform: uppercase; + } +} + +// Mobile drawer: Bootstrap 6 alpha keeps it white although the navbar is dark +// (data-bs-theme="dark"), which leaves the light link text invisible. Give it the bar's colour. +.menu-main .drawer { + background-color: $color-black; + color: $color-white; +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_product-manager.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_product-manager.scss new file mode 100644 index 0000000..4f3ed4d --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/05_modules/_product-manager.scss @@ -0,0 +1,47 @@ +.product-manager { + .nav-products-actions { + ul { + padding: 0; + margin: 0; + list-style: none; + display: flex; + justify-content: flex-end; + + li { + button { + margin: 0.5rem 0 0.5rem 0.5rem; + } + } + } + } + + table { + .th-actions { + width: 200px; + } + .th-dragger { + width: 50px; + } + + td { + vertical-align: middle; + button { + margin: 0 2px; + } + } + + tbody:empty::after { + content: 'loading products...'; + width: max-content; + display: inline-block; + color: #999; + padding: 1rem; + } + tfoot .row > * { + margin-bottom: 0.5rem; + @media (min-width: 992px) { + margin-bottom: 0; + } + } + } +} diff --git a/06_react/unterricht/tag36/01_components-with-hooks/src/styles/main.scss b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/main.scss new file mode 100644 index 0000000..297e74f --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/src/styles/main.scss @@ -0,0 +1,13 @@ +// Single stylesheet entry, linked in src/html/includes/head.html. +// Same folders as in frontend-gulp; @use instead of the deprecated @import. + +// 01 Frameworks +@use '01_frameworks' as *; +// 02 Fonts +@use '02_fonts' as *; +// 03 Base +@use '03_base' as *; +// 04 Plugins +@use '04_plugins' as *; +// 05 Modules +@use '05_modules' as *; diff --git a/06_react/unterricht/tag36/01_components-with-hooks/vite.config.js b/06_react/unterricht/tag36/01_components-with-hooks/vite.config.js new file mode 100644 index 0000000..24ad103 --- /dev/null +++ b/06_react/unterricht/tag36/01_components-with-hooks/vite.config.js @@ -0,0 +1,15 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + css: { + preprocessorOptions: { + scss: { + quietDeps: true, // Unix + silenceDeprecations: ['color-functions', 'global-builtin', 'import', 'legacy-js-api', 'if-function'], + }, + }, + }, +});