diff --git a/README.md b/README.md index cfef057d0..d4c658da1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f ## Challenges -Check [all 48 challenges](https://angular-challenges.vercel.app/) +Check [all 49 challenges](https://angular-challenges.vercel.app/) ## Contributors ✨ diff --git a/apps/rxjs/hold-to-save-btn/.eslintrc.json b/apps/rxjs/hold-to-save-btn/.eslintrc.json new file mode 100644 index 000000000..8ebcbfd59 --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/.eslintrc.json @@ -0,0 +1,36 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": [ + "plugin:@nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "app", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "app", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/apps/rxjs/hold-to-save-btn/README.md b/apps/rxjs/hold-to-save-btn/README.md new file mode 100644 index 000000000..a062b329e --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/README.md @@ -0,0 +1,13 @@ +# Hold to send button + +> author: thomas-laforge + +### Run Application + +```bash +npx nx serve rxjs-hold-to-save-btn +``` + +### Documentation and Instruction + +Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/rxjs/49-hold-to-save-btn/). diff --git a/apps/rxjs/hold-to-save-btn/jest.config.ts b/apps/rxjs/hold-to-save-btn/jest.config.ts new file mode 100644 index 000000000..4604a777e --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'rxjs-hold-to-save-btn', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../coverage/apps/rxjs/hold-to-save-btn', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/apps/rxjs/hold-to-save-btn/project.json b/apps/rxjs/hold-to-save-btn/project.json new file mode 100644 index 000000000..2a2bfa9b2 --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/project.json @@ -0,0 +1,79 @@ +{ + "name": "rxjs-hold-to-save-btn", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "prefix": "app", + "sourceRoot": "apps/rxjs/hold-to-save-btn/src", + "tags": [], + "targets": { + "build": { + "executor": "@angular-devkit/build-angular:application", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/rxjs/hold-to-save-btn", + "index": "apps/rxjs/hold-to-save-btn/src/index.html", + "browser": "apps/rxjs/hold-to-save-btn/src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "apps/rxjs/hold-to-save-btn/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "apps/rxjs/hold-to-save-btn/src/favicon.ico", + "apps/rxjs/hold-to-save-btn/src/assets" + ], + "styles": ["apps/rxjs/hold-to-save-btn/src/styles.scss"], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "executor": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "rxjs-hold-to-save-btn:build:production" + }, + "development": { + "buildTarget": "rxjs-hold-to-save-btn:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "executor": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "rxjs-hold-to-save-btn:build" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/rxjs/hold-to-save-btn/jest.config.ts" + } + } + } +} diff --git a/apps/rxjs/hold-to-save-btn/src/app/app.component.ts b/apps/rxjs/hold-to-save-btn/src/app/app.component.ts new file mode 100644 index 000000000..ecfdf26ce --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/src/app/app.component.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + standalone: true, + imports: [], + selector: 'app-root', + template: ` +
+
+ + + +
+
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppComponent { + onSend() { + console.log('Save it!'); + } +} diff --git a/apps/rxjs/hold-to-save-btn/src/app/app.config.ts b/apps/rxjs/hold-to-save-btn/src/app/app.config.ts new file mode 100644 index 000000000..81a6edde4 --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/src/app/app.config.ts @@ -0,0 +1,5 @@ +import { ApplicationConfig } from '@angular/core'; + +export const appConfig: ApplicationConfig = { + providers: [], +}; diff --git a/apps/rxjs/hold-to-save-btn/src/assets/.gitkeep b/apps/rxjs/hold-to-save-btn/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/rxjs/hold-to-save-btn/src/favicon.ico b/apps/rxjs/hold-to-save-btn/src/favicon.ico new file mode 100644 index 000000000..317ebcb23 Binary files /dev/null and b/apps/rxjs/hold-to-save-btn/src/favicon.ico differ diff --git a/apps/rxjs/hold-to-save-btn/src/index.html b/apps/rxjs/hold-to-save-btn/src/index.html new file mode 100644 index 000000000..d0da51389 --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/src/index.html @@ -0,0 +1,13 @@ + + + + + rxjs-hold-to-save-btn + + + + + + + + diff --git a/apps/rxjs/hold-to-save-btn/src/main.ts b/apps/rxjs/hold-to-save-btn/src/main.ts new file mode 100644 index 000000000..f3a7223da --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/src/main.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { appConfig } from './app/app.config'; + +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err), +); diff --git a/apps/rxjs/hold-to-save-btn/src/styles.scss b/apps/rxjs/hold-to-save-btn/src/styles.scss new file mode 100644 index 000000000..c98dac907 --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/src/styles.scss @@ -0,0 +1,14 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +progress { + @apply h-4 w-full rounded-full bg-gray-200; +} +progress::-webkit-progress-bar { + @apply rounded-full bg-gray-200; +} + +progress::-webkit-progress-value { + @apply rounded-full bg-indigo-600; +} diff --git a/apps/rxjs/hold-to-save-btn/src/test-setup.ts b/apps/rxjs/hold-to-save-btn/src/test-setup.ts new file mode 100644 index 000000000..15de72a3c --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/src/test-setup.ts @@ -0,0 +1,2 @@ +import '@testing-library/jest-dom'; +import 'jest-preset-angular/setup-jest'; diff --git a/apps/rxjs/hold-to-save-btn/tailwind.config.js b/apps/rxjs/hold-to-save-btn/tailwind.config.js new file mode 100644 index 000000000..38183db2c --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/tailwind.config.js @@ -0,0 +1,14 @@ +const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind'); +const { join } = require('path'); + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'), + ...createGlobPatternsForDependencies(__dirname), + ], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/apps/rxjs/hold-to-save-btn/tsconfig.app.json b/apps/rxjs/hold-to-save-btn/tsconfig.app.json new file mode 100644 index 000000000..58220429a --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/tsconfig.app.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"], + "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/apps/rxjs/hold-to-save-btn/tsconfig.editor.json b/apps/rxjs/hold-to-save-btn/tsconfig.editor.json new file mode 100644 index 000000000..a8ac182c0 --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/tsconfig.editor.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*.ts"], + "compilerOptions": {}, + "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/apps/rxjs/hold-to-save-btn/tsconfig.json b/apps/rxjs/hold-to-save-btn/tsconfig.json new file mode 100644 index 000000000..4383e7eb8 --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.editor.json" + }, + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/apps/rxjs/hold-to-save-btn/tsconfig.spec.json b/apps/rxjs/hold-to-save-btn/tsconfig.spec.json new file mode 100644 index 000000000..1a4817a7d --- /dev/null +++ b/apps/rxjs/hold-to-save-btn/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node", "@testing-library/jest-dom"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/challenge-number.json b/challenge-number.json index 76578acdc..99ff72fd0 100644 --- a/challenge-number.json +++ b/challenge-number.json @@ -1,5 +1,5 @@ { - "total": 48, + "total": 49, "🟢": 18, "🟠": 121, "🔴": 209 diff --git a/docs/src/assets/rxjs/49/prototype.gif b/docs/src/assets/rxjs/49/prototype.gif new file mode 100644 index 000000000..32c33528c Binary files /dev/null and b/docs/src/assets/rxjs/49/prototype.gif differ diff --git a/docs/src/content/docs/challenges/rxjs/49-hold-to-send-btn.md b/docs/src/content/docs/challenges/rxjs/49-hold-to-send-btn.md new file mode 100644 index 000000000..b814a3bbd --- /dev/null +++ b/docs/src/content/docs/challenges/rxjs/49-hold-to-send-btn.md @@ -0,0 +1,46 @@ +--- +title: 🟠 Hold to save button +description: You're tasked with implementing Lucie's button design, requiring holding it for a set time to save, taking over from Sacha; functionalities include configuring duration, countdown initiation on "mousedown", progress bar reset on "mouseleave" or "mouseup", reflecting remaining time, and simulating save request on hold completion, using RxJS operators and ensuring declarative code. +author: timothy-alcaide +contributors: + - alcaidio +challengeNumber: 49 +command: rxjs-hold-to-save-btn +sidebar: + order: 19 + badge: New +--- + +## Context + +As a member of the development team, you have to respond to a specific request from the UX designer, 👩🏻‍🎨 Lucie, who has designed a button that must be held down for X amount of time to save a save request. + +Sacha 👶🏼 the trainee has already integrated the design but doesn't know how to perform the "holdable" functionality. + +So you're going to take over from him. + +## Functional expectation + +> "As a user, I would like to save something by holding down the button for a certain amount of time." + +Here the prototype made by Lucie : + +![prototype gif](../../../../../../docs/src/assets/rxjs/49/prototype.gif) + +## Acceptance Criteria + +1. We should be able to configure a maintenance duration in milliseconds. +2. Pressing and holding the button triggers the countdown on "mousedown" event. +3. On "mouseleave" or "mouseup" events, the progress bar is reset to 0. +4. The progress bar representing the remaining relative time should reflect the remaining time. +5. Simulates a backup request when the hold time is over (console log or alert). +6. You must maximize the use of RxJS operators and be as declarative as possible. + +
+ Tips 🤫 (if you really need it and after careful consideration) +
    +
  • Create the `HolddableDirective`
  • +
  • Use `TemplateRef` and `fromEvent` from rxjs to catch events OR `@HostListener`
  • +
  • Perhaps the following rxjs operators can help you: interval, takeUntil, switchmap, takeWhile/retry...
  • +
+
diff --git a/docs/src/content/docs/es/index.mdx b/docs/src/content/docs/es/index.mdx index 5a3b1bec7..ec911a1de 100644 --- a/docs/src/content/docs/es/index.mdx +++ b/docs/src/content/docs/es/index.mdx @@ -13,7 +13,7 @@ hero: icon: right-arrow variant: primary - text: Ir al Desafío más reciente - link: /es/challenges/forms/48-avoid-losing-form-data/ + link: /es/challenges/rxjs/49-hold-to-save-btn/ icon: rocket - text: Dar una estrella link: https://github.com/tomalaforge/angular-challenges @@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro'; import SubscriptionForm from '../../../components/SubscriptionForm.astro'; - - Este repositorio contiene 48 Desafíos relacionados con Angular, Nx, RxJS, Ngrx y Typescript. + + Este repositorio contiene 49 Desafíos relacionados con Angular, Nx, RxJS, Ngrx y Typescript. Estos desafíos se resuelven en torno a problemas de la vida real o características específicas para mejorar tus habilidades. diff --git a/docs/src/content/docs/fr/index.mdx b/docs/src/content/docs/fr/index.mdx index 0c796f809..125ce31c5 100644 --- a/docs/src/content/docs/fr/index.mdx +++ b/docs/src/content/docs/fr/index.mdx @@ -13,7 +13,7 @@ hero: icon: right-arrow variant: primary - text: Aller au dernier Challenge - link: /fr/challenges/forms/48-avoid-losing-form-data/ + link: /fr/challenges/rxjs/49-hold-to-save-btn/ icon: rocket - text: Donne une étoile link: https://github.com/tomalaforge/angular-challenges @@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro'; import SubscriptionForm from '../../../components/SubscriptionForm.astro'; - - Ce répertoire rassemble 48 Défis liés à Angular, Nx, RxJS, Ngrx et Typescript. Ces défis portent sur des problèmes réels ou des fonctionnalités spécifiques pour améliorer vos compétences. + + Ce répertoire rassemble 49 Défis liés à Angular, Nx, RxJS, Ngrx et Typescript. Ces défis portent sur des problèmes réels ou des fonctionnalités spécifiques pour améliorer vos compétences. diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index 4a6202f23..7cd5fca59 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -27,8 +27,8 @@ import MyIcon from '../../components/MyIcon.astro'; import SubscriptionForm from '../../components/SubscriptionForm.astro'; - - This repository gathers 48 Challenges related to Angular, Nx, RxJS, Ngrx and Typescript. + + This repository gathers 49 Challenges related to Angular, Nx, RxJS, Ngrx and Typescript. These challenges resolve around real-life issues or specific features to elevate your skills. diff --git a/docs/src/content/docs/pt/index.mdx b/docs/src/content/docs/pt/index.mdx index b963d02e4..64bd55a91 100644 --- a/docs/src/content/docs/pt/index.mdx +++ b/docs/src/content/docs/pt/index.mdx @@ -13,7 +13,7 @@ hero: icon: right-arrow variant: primary - text: Ir para o desafio mais recente - link: /pt/challenges/forms/48-avoid-losing-form-data/ + link: /pt/challenges/rxjs/49-hold-to-save-btn/ icon: rocket - text: Dar uma estrela link: https://github.com/tomalaforge/angular-challenges @@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro'; import SubscriptionForm from '../../../components/SubscriptionForm.astro'; - - Este repositório possui 48 Desafios relacionados a Angular, Nx, RxJS, + + Este repositório possui 49 Desafios relacionados a Angular, Nx, RxJS, Ngrx e Typescript. Esses desafios são voltados para problemas reais ou funcionalidades específicas afim de melhorar suas habilidades. diff --git a/docs/src/content/docs/ru/index.mdx b/docs/src/content/docs/ru/index.mdx index 8d06e1eb0..2e9cf344b 100644 --- a/docs/src/content/docs/ru/index.mdx +++ b/docs/src/content/docs/ru/index.mdx @@ -13,7 +13,7 @@ hero: icon: right-arrow variant: primary - text: Перейти к последней задаче - link: /ru/challenges/forms/48-avoid-losing-form-data/ + link: /ru/challenges/rxjs/49-hold-to-save-btn/ icon: rocket - text: Добавить звезду link: https://github.com/tomalaforge/angular-challenges @@ -26,8 +26,8 @@ import MyIcon from '../../../components/MyIcon.astro'; import SubscriptionForm from '../../../components/SubscriptionForm.astro'; - - Этот репозиторий содержит 47 испытаний, связанных с Angular, Nx, RxJS, Ngrx и Typescript. + + Этот репозиторий содержит 49 испытаний, связанных с Angular, Nx, RxJS, Ngrx и Typescript. Испытания основаны на реальных задачах или инструментах для того, чтобы прокачать вас.