diff --git a/examples/recipePage/.gitignore b/examples/recipePage/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/recipePage/.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/examples/recipePage/.npmrc b/examples/recipePage/.npmrc new file mode 100644 index 00000000..6b5f38e8 --- /dev/null +++ b/examples/recipePage/.npmrc @@ -0,0 +1,2 @@ +save-exact = true +package-lock = false diff --git a/examples/recipePage/README.md b/examples/recipePage/README.md new file mode 100644 index 00000000..d551de88 --- /dev/null +++ b/examples/recipePage/README.md @@ -0,0 +1,23 @@ +# Frontend Mentor Recipe Page + +Here is the implementation in [Bau.js](https://github.com/grucloud/bau) of the [Frontend Mentor Recipe Page code challenge](https://www.frontendmentor.io/challenges/https://www.frontendmentor.io/challenges/recipe-page-KiTsR8QQKm) + +## Workflow + +Install the dependencies: + +```sh +npm install +``` + +Start a development server: + +```sh +npm run dev +``` + +Build a production version: + +```sh +npm run build +``` diff --git a/examples/recipePage/index.html b/examples/recipePage/index.html new file mode 100644 index 00000000..f31b3054 --- /dev/null +++ b/examples/recipePage/index.html @@ -0,0 +1,17 @@ + + + + + + + Recipe Page | FrontendMentor + + +
+ + + diff --git a/examples/recipePage/package.json b/examples/recipePage/package.json new file mode 100644 index 00000000..d2fab061 --- /dev/null +++ b/examples/recipePage/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontendmentor-recipe-page", + "private": true, + "version": "0.85.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^5.0.2", + "vite": "^5.2.11" + }, + "dependencies": { + "@grucloud/bau": "^0.85.0", + "@grucloud/bau-css": "^0.85.0", + "@grucloud/bau-ui": "^0.85.0" + } +} diff --git a/examples/recipePage/public/assets/images/favicon-32x32.png b/examples/recipePage/public/assets/images/favicon-32x32.png new file mode 100644 index 00000000..1e2df7f0 Binary files /dev/null and b/examples/recipePage/public/assets/images/favicon-32x32.png differ diff --git a/examples/recipePage/public/assets/images/image-omelette.jpeg b/examples/recipePage/public/assets/images/image-omelette.jpeg new file mode 100644 index 00000000..dac3a468 Binary files /dev/null and b/examples/recipePage/public/assets/images/image-omelette.jpeg differ diff --git a/examples/recipePage/src/main.ts b/examples/recipePage/src/main.ts new file mode 100644 index 00000000..ea28603b --- /dev/null +++ b/examples/recipePage/src/main.ts @@ -0,0 +1,20 @@ +import { createContext, type Context } from "@grucloud/bau-ui/context"; +import recipePage from "./recipePage"; + +import "./style.css"; + +const context = createContext(); + +const app = (context: Context) => { + const { bau } = context; + const { main } = bau.tags; + + const RecipePage = recipePage(context); + + return function () { + return main(RecipePage()); + }; +}; + +const App = app(context); +document.getElementById("app")?.replaceChildren(App()); diff --git a/examples/recipePage/src/recipePage.ts b/examples/recipePage/src/recipePage.ts new file mode 100644 index 00000000..f76d0c8d --- /dev/null +++ b/examples/recipePage/src/recipePage.ts @@ -0,0 +1,162 @@ +import { type Context } from "@grucloud/bau-ui/context"; + +export default function (context: Context) { + const { bau, css } = context; + const { + h1, + h2, + h3, + p, + article, + img, + section, + strong, + ul, + ol, + li, + table, + tbody, + th, + tr, + td, + } = bau.tags; + + const className = css` + max-width: 700px; + padding: 1.5rem; + margin: 5rem 0; + @media (max-width: 600px) { + margin: 0; + } + background-color: var(--White); + border-radius: 0.5rem; + + .img-omelete { + width: 100%; + border-radius: 0.5rem; + } + h1, + h2 { + font-family: "Young Serif"; + } + + h1 { + color: var(--Stone-900); + font-weight: 400; + font-size: 2.2em; + } + h2 { + color: var(--Brown-800); + font-weight: 400; + } + ul, + ol { + padding-left: 1rem; + li { + color: var(--WengeBrown); + padding: 0.3rem; + list-style-position: inside; + } + } + section.preparation-time { + background-color: var(--Rose-50); + padding: 1.2rem; + border-radius: 0.7rem; + h3 { + color: var(--Rose-800); + } + } + ol { + li::marker { + font-weight: 700; + color: var(--Brown-800); + } + } + table { + width: 100%; + border-collapse: collapse; + tbody { + tr { + border-bottom: 1px solid var(--Stone-150); + th { + padding: 1rem; + font-weight: normal; + text-align: start; + color: var(--Stone-600); + } + td { + font-weight: 700; + color: var(--Rose-800); + } + } + } + } + `; + + return function myComponent() { + return article( + { class: className }, + img({ + class: "img-omelete", + src: "./assets/images/image-omelette.jpeg", + alt: "omelette", + }), + h1("Simple Omelette Recipe"), + p( + "An easy and quick dish, perfect for any meal. This classic omelette combines beaten eggs cooked to perfection, optionally filled with your choice of cheese, vegetables, or meats." + ), + section( + { class: "preparation-time" }, + h3("Preparation Time"), + ul( + li(strong("Total: "), "Approximately 10 minutes"), + li(strong("Preparation: "), "5 minutes"), + li(strong("Cooking: "), "5 minutes") + ) + ), + h2("Ingredients"), + ul( + li("2-3 large eggs"), + li("Salt, to taste"), + li("Pepper, to taste"), + li("1 tablespoon of butter or oil"), + li("Optional fillings: cheese, diced vegetables, cooked meats, herbs") + ), + h2("Instructions"), + ol( + li( + strong("Beat the eggs: "), + " In a bowl, beat the eggs with a pinch of salt and pepper until they are well mixed. You can add a tablespoon of water or milk for a fluffier texture." + ), + li( + strong("Heat the pan: "), + "Place a non-stick frying pan over medium heat and add butter or oil." + ), + li( + strong("Add fillings (optional): "), + "When the eggs begin to set at the edges but are still slightly runny in the middle, sprinkle your chosen fillings over one half of the omelette." + ), + li( + strong("Fold and serve: "), + "As the omelette continues to cook, carefully lift one edge and fold it over the fillings. Let it cook for another minute, then slide it onto a plate." + ), + li( + strong("Enjoy: "), + "Serve hot, with additional salt and pepper if needed." + ) + ), + h2("Nutrition"), + p( + "The table below shows nutritional values per serving without the additional fillings." + ), + table( + tbody( + tr(th("Calories"), td("277kcal")), + tr(th("Carbs"), td("0g")), + tr(th("Protien"), td("20g")), + tr(th("Fat"), td("22g")) + ) + ) + ); + }; +} diff --git a/examples/recipePage/src/style.css b/examples/recipePage/src/style.css new file mode 100644 index 00000000..864ad02b --- /dev/null +++ b/examples/recipePage/src/style.css @@ -0,0 +1,28 @@ +@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&family=Young+Serif&display=swap"); + +* { + margin: 0; + box-sizing: border-box; +} + +:root { + --White: hsl(0, 0%, 100%); + --Stone-100: hsl(30, 54%, 90%); + --Stone-150: hsl(30, 18%, 87%); + --Stone-600: hsl(30, 10%, 34%); + --Stone-900: hsl(24, 5%, 18%); + + --Brown-800: hsl(14, 45%, 36%); + + --Rose-800: hsl(332, 51%, 32%); + --Rose-50: hsl(330, 100%, 98%); +} + +body { + font-family: "Outfit", sans-serif; + background-color: var(--Stone-100); + color: var(--Stone-600); + display: grid; + place-items: center; + max-width: 100vw; +} diff --git a/examples/recipePage/src/vite-env.d.ts b/examples/recipePage/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/recipePage/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/recipePage/tsconfig.json b/examples/recipePage/tsconfig.json new file mode 100644 index 00000000..75abdef2 --- /dev/null +++ b/examples/recipePage/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/recipePage/vite.config.js b/examples/recipePage/vite.config.js new file mode 100644 index 00000000..41713bec --- /dev/null +++ b/examples/recipePage/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "vite"; + +export default defineConfig(({ command, mode, ssrBuild }) => { + return { + server: { + open: true, + }, + }; +});