diff --git a/examples/product-preview-card/.gitignore b/examples/product-preview-card/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/product-preview-card/.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/product-preview-card/.npmrc b/examples/product-preview-card/.npmrc new file mode 100644 index 00000000..6b5f38e8 --- /dev/null +++ b/examples/product-preview-card/.npmrc @@ -0,0 +1,2 @@ +save-exact = true +package-lock = false diff --git a/examples/product-preview-card/README.md b/examples/product-preview-card/README.md new file mode 100644 index 00000000..6d28f7d4 --- /dev/null +++ b/examples/product-preview-card/README.md @@ -0,0 +1,23 @@ +# Frontend Mentor Product Preview Card + +Here is the implementation in [Bau.js](https://github.com/grucloud/bau) of the [Frontend Mentor Product Preview Card code challenge](https://www.frontendmentor.io/challenges/product-preview-card-component-GO7UmttRfa/hub) + +## 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/product-preview-card/index.html b/examples/product-preview-card/index.html new file mode 100644 index 00000000..752cfab5 --- /dev/null +++ b/examples/product-preview-card/index.html @@ -0,0 +1,17 @@ + + + + + + + Product Preview Card | FrontendMentor + + +
+ + + diff --git a/examples/product-preview-card/package.json b/examples/product-preview-card/package.json new file mode 100644 index 00000000..231b420b --- /dev/null +++ b/examples/product-preview-card/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontendmentor-product-preview-card", + "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/product-preview-card/public/assets/images/favicon-32x32.png b/examples/product-preview-card/public/assets/images/favicon-32x32.png new file mode 100644 index 00000000..1e2df7f0 Binary files /dev/null and b/examples/product-preview-card/public/assets/images/favicon-32x32.png differ diff --git a/examples/product-preview-card/public/assets/images/icon-cart.svg b/examples/product-preview-card/public/assets/images/icon-cart.svg new file mode 100644 index 00000000..307ea617 --- /dev/null +++ b/examples/product-preview-card/public/assets/images/icon-cart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/product-preview-card/public/assets/images/image-product-desktop.jpg b/examples/product-preview-card/public/assets/images/image-product-desktop.jpg new file mode 100644 index 00000000..ca93ea19 Binary files /dev/null and b/examples/product-preview-card/public/assets/images/image-product-desktop.jpg differ diff --git a/examples/product-preview-card/public/assets/images/image-product-mobile.jpg b/examples/product-preview-card/public/assets/images/image-product-mobile.jpg new file mode 100644 index 00000000..e2076e35 Binary files /dev/null and b/examples/product-preview-card/public/assets/images/image-product-mobile.jpg differ diff --git a/examples/product-preview-card/src/main.ts b/examples/product-preview-card/src/main.ts new file mode 100644 index 00000000..4f2afe91 --- /dev/null +++ b/examples/product-preview-card/src/main.ts @@ -0,0 +1,20 @@ +import { createContext, type Context } from "@grucloud/bau-ui/context"; +import productPreviewCard from "./productPreviewCard"; + +import "./style.css"; + +const context = createContext(); + +const app = (context: Context) => { + const { bau } = context; + const { main } = bau.tags; + + const ProductPreviewCard = productPreviewCard(context); + + return function () { + return main(ProductPreviewCard()); + }; +}; + +const App = app(context); +document.getElementById("app")?.replaceChildren(App()); diff --git a/examples/product-preview-card/src/productPreviewCard.ts b/examples/product-preview-card/src/productPreviewCard.ts new file mode 100644 index 00000000..5ea085d6 --- /dev/null +++ b/examples/product-preview-card/src/productPreviewCard.ts @@ -0,0 +1,116 @@ +import { type Context } from "@grucloud/bau-ui/context"; + +export default function (context: Context) { + const { bau, css } = context; + const { h1, div, p, article, section, img, picture, source, span, button } = + bau.tags; + + const className = css` + display: grid; + margin: 1rem; + grid-template-columns: 350px 350px; + grid-template-rows: 1fr; + @media (max-width: 475px) { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + } + picture { + img { + display: block; + width: 100%; + border-radius: 1rem 0 0 1rem; + @media (max-width: 475px) { + border-radius: 1rem; + } + } + } + + .content { + background-color: white; + border-radius: 0 1rem 1rem 0; + padding: 1rem; + gap: 0.5rem; + display: flex; + flex-direction: column; + justify-content: space-around; + .category { + letter-spacing: 0.3rem; + text-transform: uppercase; + font-weight: 500; + color: var(--paragraph-color); + font-size: 1rem; + } + h1 { + font-family: "Fraunces", sans-serif; + font-size: 2rem; + } + p { + color: var(--paragraph-color); + } + .price-container { + display: inline-flex; + align-items: center; + gap: 1rem; + .price { + font-family: "Fraunces", sans-serif; + color: var(--btn-and-current-price-color); + font-size: 2rem; + } + .price-old { + font-size: 1rem; + color: var(--paragraph-color); + text-decoration: line-through; + } + } + + button { + width: 100%; + border-radius: 1rem; + background-color: var(--btn-and-current-price-color); + color: white; + border: none; + font-weight: 700; + padding: 0.8rem; + display: inline-flex; + gap: 0.4rem; + justify-content: center; + cursor: pointer; + transition: background-color 0.5s; + &::before { + content: url("./assets/images/icon-cart.svg"); + } + &:hover { + background-color: var(--hover-color); + } + } + } + `; + + return function myComponent() { + return article( + { class: className }, + picture( + source({ + srcset: "./assets/images/image-product-desktop.jpg", + media: "(min-width:476px)", + }), + img({ src: "./assets/images/image-product-mobile.jpg", alt: "Mobile" }) + ), + // img({ src: "./assets/images/image-product-desktop.jpg", alt: "Mobile" }), + section( + { class: "content" }, + div({ class: "category" }, "Perfume"), + h1("Gabrielle Essence Eau De Parfum"), + p( + "A floral, solar and voluptuous interpretation composed by Olivier Polge, Perfumer-Creator for the House of CHANEL." + ), + div( + { class: "price-container" }, + span({ class: "price" }, "$149.99"), + span({ class: "price-old" }, "$169.99") + ), + button("Add to Cart") + ) + ); + }; +} diff --git a/examples/product-preview-card/src/style.css b/examples/product-preview-card/src/style.css new file mode 100644 index 00000000..e61ab422 --- /dev/null +++ b/examples/product-preview-card/src/style.css @@ -0,0 +1,26 @@ +@import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@700&family=Montserrat:wght@500;700&display=swap"); + +* { + margin: 0; + box-sizing: border-box; +} + +:root { + --btn-and-current-price-color: hsl(158, 36%, 37%); + --product-title-color: hsl(212, 21%, 14%); + --paragraph-color: hsl(228, 12%, 48%); + --card-color: hsl(0, 0%, 100%); + --hover-color: hsl(157, 46%, 17%); +} + +body { + --background-color: hsl(30, 38%, 92%); + font-family: "Montserrat", sans-serif; + min-height: 100vh; + background-color: var(--background-color); + display: grid; + place-items: center; + + @media (max-width: 600px) { + } +} diff --git a/examples/product-preview-card/src/vite-env.d.ts b/examples/product-preview-card/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/product-preview-card/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/product-preview-card/tsconfig.json b/examples/product-preview-card/tsconfig.json new file mode 100644 index 00000000..75abdef2 --- /dev/null +++ b/examples/product-preview-card/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/product-preview-card/vite.config.js b/examples/product-preview-card/vite.config.js new file mode 100644 index 00000000..41713bec --- /dev/null +++ b/examples/product-preview-card/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "vite"; + +export default defineConfig(({ command, mode, ssrBuild }) => { + return { + server: { + open: true, + }, + }; +});