diff --git a/examples/interactive-rating-component/.gitignore b/examples/interactive-rating-component/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/examples/interactive-rating-component/.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/interactive-rating-component/.npmrc b/examples/interactive-rating-component/.npmrc new file mode 100644 index 00000000..6b5f38e8 --- /dev/null +++ b/examples/interactive-rating-component/.npmrc @@ -0,0 +1,2 @@ +save-exact = true +package-lock = false diff --git a/examples/interactive-rating-component/README.md b/examples/interactive-rating-component/README.md new file mode 100644 index 00000000..fc42d8e9 --- /dev/null +++ b/examples/interactive-rating-component/README.md @@ -0,0 +1,23 @@ +# Frontend Mentor Interactive Rating Component + +Here is the implementation in [Bau.js](https://github.com/grucloud/bau) of the [Frontend Mentor Interactive Rating Component code challenge](https://www.frontendmentor.io/challenges/interactive-rating-component-koxpeBUmI) + +## 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/interactive-rating-component/index.html b/examples/interactive-rating-component/index.html new file mode 100644 index 00000000..79fae46c --- /dev/null +++ b/examples/interactive-rating-component/index.html @@ -0,0 +1,17 @@ + + + + + + + Interactive Rating Component | FrontendMentor + + +
+ + + diff --git a/examples/interactive-rating-component/package.json b/examples/interactive-rating-component/package.json new file mode 100644 index 00000000..c2bd4e2b --- /dev/null +++ b/examples/interactive-rating-component/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontendmentor-interactive-rating-component", + "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/interactive-rating-component/public/assets/images/favicon-32x32.png b/examples/interactive-rating-component/public/assets/images/favicon-32x32.png new file mode 100644 index 00000000..1e2df7f0 Binary files /dev/null and b/examples/interactive-rating-component/public/assets/images/favicon-32x32.png differ diff --git a/examples/interactive-rating-component/public/assets/images/icon-star.svg b/examples/interactive-rating-component/public/assets/images/icon-star.svg new file mode 100644 index 00000000..a15741e8 --- /dev/null +++ b/examples/interactive-rating-component/public/assets/images/icon-star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/interactive-rating-component/public/assets/images/illustration-thank-you.svg b/examples/interactive-rating-component/public/assets/images/illustration-thank-you.svg new file mode 100644 index 00000000..bf22b942 --- /dev/null +++ b/examples/interactive-rating-component/public/assets/images/illustration-thank-you.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/interactive-rating-component/src/interactiveRating.ts b/examples/interactive-rating-component/src/interactiveRating.ts new file mode 100644 index 00000000..665c5c53 --- /dev/null +++ b/examples/interactive-rating-component/src/interactiveRating.ts @@ -0,0 +1,168 @@ +import { type Context } from "@grucloud/bau-ui/context"; + +const ratingKey = "rating"; +const submittedKey = "submitted"; + +export default function (context: Context) { + const { bau, css, window } = context; + const { h1, p, ul, li, button, form, img, picture, section, article, div } = + bau.tags; + + const className = css` + max-width: 400px; + margin: 1rem; + background-color: var(--clr-dark-700); + border-radius: 1em; + .panel { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 2rem; + } + + h1 { + margin: 0; + } + picture { + img { + background-color: var(--clr-dark-500); + border-radius: 50%; + padding: 1rem; + } + } + p { + color: var(--clr-neutral-300); + } + ul { + padding: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + + li { + list-style: none; + display: inline-flex; + align-items: center; + justify-content: center; + height: 3rem; + width: 3rem; + border-radius: 50%; + background-color: var(--clr-dark-500); + color: var(--clr-neutral-300); + cursor: pointer; + + &:hover { + background-color: var(--clr-dark-300); + color: var(--clr-neutral-100); + } + &.active { + color: white; + background-color: var(--clr-primary); + } + } + } + button { + padding: 1rem 0; + width: 100%; + border-radius: 1rem; + border: none; + cursor: pointer; + background-color: var(--clr-primary); + color: var(--clr-neutral-100); + font-size: 1rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.2rem; + transition: all 0.5s; + &:hover { + background-color: var(--clr-neutral-100); + color: var(--clr-primary); + } + } + + .thankyou { + align-items: center; + h1 { + font-size: 2rem; + } + .badge { + color: var(--clr-primary); + background-color: var(--clr-dark-500); + padding: 0.5rem 1rem; + border-radius: 1rem; + } + } + `; + + const search = new URLSearchParams(window.location.search); + const ratingState = bau.state(Number(search.get(ratingKey))); + const submittedState = bau.state(!!search.get(submittedKey)); + + const onRating = (rating: number) => () => { + const search = new URLSearchParams(window.location.search); + search.set(ratingKey, String(rating)); + window.history.pushState( + "", + "", + `?${search.toString()}${window.location.hash}` + ); + ratingState.val = rating; + }; + + const onsubmit = (event: any) => { + event.preventDefault(); + const search = new URLSearchParams(window.location.search); + search.set(submittedKey, "true"); + window.history.replaceState( + "", + "", + `?${search.toString()}${window.location.hash}` + ); + submittedState.val = true; + }; + + const InteractiveRatingContent = () => + form( + { class: "panel", onsubmit }, + picture(img({ src: "./assets/images/icon-star.svg", alt: "star" })), + h1("How did we do?"), + p( + "Please let us know how we did with your support request. All feedback is appreciated to help us improve our offering!" + ), + ul( + Array(5) + .fill("") + .map((_, i) => + li( + { + class: () => i + 1 === ratingState.val && "active", + onclick: onRating(i + 1), + }, + i + 1 + ) + ) + ), + button({ type: "submit" }, "Submit") + ); + + const ThankYou = () => + section( + { class: ["thankyou", "panel"] }, + img({ + src: "./assets/images/illustration-thank-you.svg", + alt: "Thank you", + }), + div({ class: "badge" }, "You selected ", ratingState.val, " out of 5"), + h1("Thank you"), + p( + "We appreciate you taking the time to give a rating. If you ever need more support, don’t hesitate to get in touch!" + ) + ); + + return function interactiveRating() { + return article({ class: className }, () => + submittedState.val ? ThankYou() : InteractiveRatingContent() + ); + }; +} diff --git a/examples/interactive-rating-component/src/main.ts b/examples/interactive-rating-component/src/main.ts new file mode 100644 index 00000000..060bfa23 --- /dev/null +++ b/examples/interactive-rating-component/src/main.ts @@ -0,0 +1,20 @@ +import { createContext, type Context } from "@grucloud/bau-ui/context"; +import interactiveRating from "./interactiveRating"; + +import "./style.css"; + +const context = createContext(); + +const app = (context: Context) => { + const { bau } = context; + const { main } = bau.tags; + + const InteractiveRating = interactiveRating(context); + + return function () { + return main(InteractiveRating()); + }; +}; + +const App = app(context); +document.getElementById("app")?.replaceChildren(App()); diff --git a/examples/interactive-rating-component/src/style.css b/examples/interactive-rating-component/src/style.css new file mode 100644 index 00000000..1408a821 --- /dev/null +++ b/examples/interactive-rating-component/src/style.css @@ -0,0 +1,31 @@ +@import url("https://fonts.googleapis.com/css2?family=Overpass:wght@400;700&display=swap"); + +* { + margin: 0; + box-sizing: border-box; +} + +:root { + --clr-primary: hsl(25, 97%, 53%); + + --clr-neutral-100: hsl(0, 0%, 100%); + --clr-neutral-300: hsl(217, 12%, 63%); + --clr-neutral-500: hsl(216, 12%, 54%); + --clr-dark-300: hsl(213, 19%, 50%); + + --clr-dark-500: hsl(213, 19%, 30%); + + --clr-dark-700: hsl(213, 19%, 18%); + --clr-dark-900: hsl(216, 12%, 8%); +} + +body { + font-family: "Overpass", sans-serif; + min-height: 100vh; + display: grid; + place-items: center; + background-color: var(--clr-dark-900); + color: var(--clr-neutral-100); + @media (max-width: 600px) { + } +} diff --git a/examples/interactive-rating-component/src/vite-env.d.ts b/examples/interactive-rating-component/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/interactive-rating-component/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/interactive-rating-component/tsconfig.json b/examples/interactive-rating-component/tsconfig.json new file mode 100644 index 00000000..75abdef2 --- /dev/null +++ b/examples/interactive-rating-component/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/interactive-rating-component/vite.config.js b/examples/interactive-rating-component/vite.config.js new file mode 100644 index 00000000..41713bec --- /dev/null +++ b/examples/interactive-rating-component/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "vite"; + +export default defineConfig(({ command, mode, ssrBuild }) => { + return { + server: { + open: true, + }, + }; +});