diff --git a/docs/images/storybook-configured.png b/docs/images/storybook-configured.png index 25c859b..962b621 100644 Binary files a/docs/images/storybook-configured.png and b/docs/images/storybook-configured.png differ diff --git a/docs/install.md b/docs/install.md index 41a359c..120088f 100644 --- a/docs/install.md +++ b/docs/install.md @@ -128,10 +128,11 @@ npm run lint -- --fix { "editor.codeActionsOnSave": { "source.fixAll": "always", - "source.organizeImports": "always" + "source.organizeImports": "explicit" }, "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "css.lint.unknownAtRules": "ignore" } ``` @@ -151,7 +152,9 @@ npm run lint -- --fix "recommendations": [ "esbenp.prettier-vscode", "vitest.explorer", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "dbaeumer.vscode-eslint", + "usernamehw.errorlens" ] } ``` @@ -268,26 +271,49 @@ npm install --save-dev vitest @testing-library/react @vitejs/plugin-react jsdom Y creamos el archivo de configuración: -```js title="vitest.config.mjs" +```ts title="vitest.config.mts" +/* eslint-disable unicorn/prefer-module */ +import path from 'node:path' + +import react from '@vitejs/plugin-react' import { defineConfig } from 'vitest/config' export default defineConfig({ + // Add react plugin + plugins: [react()], + + // Configure nextjs default paths + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + // Specify the test environment test: { // Use jsdom environment environment: 'jsdom', - // Setup files to be run before each test - // setupFiles: ['./vitest.setup.js'], - + // Import assertion library or any global setup globals: true, - // Add react plugin - plugins: [react()], + // Setup files to be run before each test + setupFiles: ['./vitest.setup.mts'], + }, +}) +``` + +A continuación creamos el siguiente fichero: - // Enable if you're using React 18's server components or need top-level await support - // ssr: true, - } +```ts title="vitest.setup.mts" +import * as matchers from '@testing-library/jest-dom/matchers' +import { cleanup } from '@testing-library/react' +import { afterEach, expect } from 'vitest' + +expect.extend(matchers) + +afterEach(() => { + cleanup() }) ``` @@ -297,6 +323,31 @@ Y con el siguiente comando podremos añadir un comando test a npm: npm pkg set scripts.test="vitest" ``` +### Crear un test de ejemplo + +Para comprobar que vitest funciona correctamente vamos a crear un pequeño test. Si usas la plantilla en un futuro +este ya no te servirá, así que recuerda actualizarlo. + +```ts title="src/app/page.spec.tsx" +import { render, screen } from '@testing-library/react' +import { expect, test } from 'vitest' + +import Page from './page' + +test('App Router: Works with Server Components', () => { + render() + expect(screen.getByRole('main')).toBeDefined() +}) +``` + +Y a continuación ejecutamos las pruebas para comprobar que se ejecuta sin problemas: + +```bash +npm run test +``` + +Por defecto vitest se ejecuta en modo `watch`. Eso significa que al terminar de pasar los test no termina, sino que se queda observando si los ficheros cambian para volver a ejecutarlos. Resulta muy útil si haces TDD. Para terminar simplemente pulsa ++control+c++ en la consola. + !!! question "Ejercicio" Se deja como ejercicio, guardar los cambios en git con la configuración de Vitest y siguiendo conventional commits para el mensaje. diff --git a/docs/new-project.md b/docs/new-project.md index 821ae67..18d249d 100644 --- a/docs/new-project.md +++ b/docs/new-project.md @@ -1,4 +1,4 @@ -En este capítulo, aprenderemos a crear un nuevo proyecto de React utilizando Next.js y el framework de diseño NextUI. React es una biblioteca de JavaScript ampliamente utilizada para construir interfaces de usuario interactivas, mientras que Next.js es un framework de React que facilita la creación de aplicaciones web avanzadas con rutas y renderización del lado del servidor. NextUI es un marco de diseño que proporciona componentes de interfaz de usuario preestilizados y personalizables, lo que acelera el proceso de diseño y desarrollo. +En este capítulo, aprenderemos a crear un nuevo proyecto de React utilizando Next.js y el framework de diseño NextUI. React es una biblioteca de JavaScript ampliamente utilizada para construir interfaces de usuario interactivas, mientras que Next.js es un framework de React que facilita la creación de aplicaciones web avanzadas con rutas y renderización del lado del servidor. NextUI es una biblioteca de componentes que proporciona componentes de interfaz de usuario preestilizados y personalizables, lo que acelera el proceso de diseño y desarrollo. ## Instalación @@ -6,7 +6,7 @@ Crear un boilerplate desde cero para hacer funcionar múltiples herramientas de ## Dependencias -### Frameworks: NextJS +### Frameworks: Next.js Aunque es posible agregar React a un proyecto existente o crear una aplicación desde cero, la documentación oficial de React aconseja considerar el uso de un framework como Next.js para proyectos web más grandes. Esto se debe a que los frameworks proporcionan una estructura y herramientas adicionales que facilitan el desarrollo, el enrutamiento y la optimización del rendimiento de la aplicación. diff --git a/docs/nextui.md b/docs/nextui.md index 86c44a5..c9942d4 100644 --- a/docs/nextui.md +++ b/docs/nextui.md @@ -9,8 +9,7 @@ claro. npm install @nextui-org/react framer-motion @heroicons/react next-themes ``` -Ahora necesitamos configurar el fichero de estilos de `tailwindcss` para que -soporte los componentes de NextUI. +Ahora necesitamos configurar el fichero de estilos de `tailwindcss` para que soporte los componentes de NextUI. De paso vamos a eliminar la clave theme porque es una personalización de la plantilla de Next.js que no vamos a necesitar. ```typescript title="tailwind.config.ts" hl_lines="1 9 11 12" import { nextui } from '@nextui-org/react' @@ -25,32 +24,33 @@ const config: Config = { ], darkMode: 'class', plugins: [nextui()], - theme: { - extend: { - backgroundImage: { - 'gradient-conic': - 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - }, - }, - }, } export default config ``` +Y eliminamos los estilos que nos trae por defecto la plantilla de Next. Edita este archivo y deja solo +estas lineas: + +```css title="src/app/globals.css" +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + + ### Dark theme Para poder aplicar el tema oscuro o claro en nuestra aplicación, necesitamos configurar una librería llamada `next-theme`. En este caso NextUI también lo explica en la [web oficial](https://storiesv2.nextui.org/docs/customization/dark-mode). -Si hemos seguido las instrucciones correctamente, tendremos un fichero providers con el siguiente código: +Necesitamos, en cualquier caso crear un nuevo fichero con los providers de NextUI con el siguiente contenido: ```typescript title="src/app/providers.tsx" -"use client"; +'use client' -import {NextUIProvider} from '@nextui-org/react' -import {ThemeProvider as NextThemesProvider} from "next-themes"; +import { NextUIProvider } from '@nextui-org/react' +import { ThemeProvider as NextThemesProvider } from 'next-themes' -export function Providers({children}: { children: React.ReactNode }) { +export function Providers({ children }: { children: React.ReactNode }) { return ( @@ -62,7 +62,7 @@ export function Providers({children}: { children: React.ReactNode }) { ``` !!! info - La notación ```"use client";``` es exclusiva de algunos frameworks con soporte de `React Server Components`. Se verá su uso durante el curso. + La notación ```'use client'``` es exclusiva de algunos frameworks con soporte de `React Server Components`. Se verá su uso durante el curso. Y este archivo de providers se debe cargar en el layout principal: @@ -103,33 +103,36 @@ Para poder ver en funcionamiento el selector de tema claro/oscuro, vamos a crear Para eso crearemos el siguiente componente: ```typescript title="src/components/theme-switcher/theme-switcher.tsx" -"use client"; +'use client' -import { MoonIcon, SunIcon } from "@heroicons/react/24/solid"; -import { Switch } from "@nextui-org/react"; -import { useTheme } from "next-themes"; -import { useEffect, useState } from "react"; +import { MoonIcon, SunIcon } from '@heroicons/react/24/solid' +import { Switch } from '@nextui-org/react' +import { useTheme } from 'next-themes' +import { useEffect, useState } from 'react' +/** + * Theme dark/light switcher + */ export function ThemeSwitcher() { - const [mounted, setMounted] = useState(false); // (1)! - const { theme, setTheme } = useTheme(); // (2)! + const [mounted, setMounted] = useState(false) + const { setTheme, theme } = useTheme() useEffect(() => { - setMounted(true); - }, [setMounted]); // (3)! + setMounted(true) + }, [setMounted]) - if (!mounted) return null; + if (!mounted) return null return ( setTheme(theme === "dark" ? "light" : "dark")} + onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} startContent={} endContent={} /> - ); + ) } ``` @@ -150,8 +153,9 @@ export * from './theme-switcher' Vamos a probar a añadir nuestro componente a la pagina principal: -```typescript title="src/app/page.tsx" hl_lines="2 13" +``` { .typescript .no-copy title="src/app/page.tsx" hl_lines="3 14" linenums="1" } import Image from 'next/image' + import { ThemeSwitcher } from '@/components/theme-switcher' export default function Home() { @@ -162,7 +166,7 @@ export default function Home() { Get started by editing  src/app/page.tsx

-
+
// (1)! By{' '} @@ -175,13 +179,23 @@ export default function Home() { 1. Añadimos el componente antes del logo de Vercel. El IDE importará automáticamente el componente. -Y para que funcione vamos a hacer un par de cambios. Eliminamos los estilos que nos trae por defecto la plantilla de Next: +!!! warning + + En este caso no puedes copiar y pegar el código. Aprende a importar y usar + componentes. + + Hay dos maneras de escribir una marca HTML. Si la marca tiene contenido: + + ```html +
Content
+ ``` + + Y si no lo tiene, como es el caso de nuestro componente: + + ```html + + ``` -```css title="src/app/globals.css" -@tailwind base; -@tailwind components; -@tailwind utilities; -``` Ahora, ya tendremos la página principal con nuestro componente para cambiar al tema claro: diff --git a/docs/storybook.md b/docs/storybook.md index 38f9c7d..f17b208 100644 --- a/docs/storybook.md +++ b/docs/storybook.md @@ -19,12 +19,6 @@ npx storybook@latest init -t nextjs nextjs con el parámetro `-t nextjs`. -Seleccionaremos todas las respuestas por defecto. Con esto ya tendremos una serie de componentes básicos para que podamos ver su funcionamiento: - -```bash -npm run storybook -``` - !!! info La primera vez que se instala se inicia solo y nos muestra un tutorial. @@ -33,15 +27,22 @@ Se nos abrirá esta página con algunos componentes de ejemplo: ![Landing page de Storybook](images/storybook-landingpage.png) -!!! warning - Storybook crea una serie de historias de ejemplo en el directorio `src/stories`. Recuerda no guardarlas en git. - Para tener soporte de tema oscuro, necesitamos también instalar el siguiente paquete: ```bash npm install --save-dev @storybook/addon-themes ``` +!!! info + + Para instalar el nuevo plugin, cierra el servicio de storybook que tienes abierto con ++control+c++, y cuando + termines de configurarlo puedes volver a ejecutar storybook con esta orden: + + ```bash + npm run storybook + ``` + + ## Configuración de Storybook Storybook es una aplicación distinta a la de next, por tanto, no tiene por qué @@ -55,18 +56,19 @@ componentes. Vamos a empezar por el entorno de typescript. Necesitamos que Storybook entienad los paths que tenemos configurados en el archivo `tsconfig.json`. También vamos a aprovechar para mover la carpeta de historias `stories` a la raíz del repositorio: -```typescript title=".storybook/main.ts" hl_lines="2 5 11 20-29" +```typescript title=".storybook/main.ts" hl_lines="2 12 21-30" import type { StorybookConfig } from "@storybook/nextjs"; import path from "path"; const config: StorybookConfig = { - stories: ["../stories/**/*.mdx", "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-onboarding", - "@storybook/addon-interactions", - "@storybook/addon-themes", + '@storybook/addon-onboarding', + '@storybook/addon-links', + '@storybook/addon-essentials', + '@chromatic-com/storybook', + '@storybook/addon-interactions', + '@storybook/addon-themes', ], framework: { name: "@storybook/nextjs", @@ -94,31 +96,30 @@ export default config; A continuación vamos a cargar los providers a la hora de visualizar los componentes: -```typescript title=".storybook/preview.ts" linenums="1" hl_lines="1 6-15" -import "@/app/globals.css"; +```typescript title=".storybook/preview.ts" hl_lines="1-3 7-15" +import '@/app/globals.css' -import type {Preview} from '@storybook/react' -import {withThemeByClassName} from '@storybook/addon-themes'; +import { withThemeByClassName } from '@storybook/addon-themes' +import type { Preview } from '@storybook/react' const preview: Preview = { - decorators: [ - withThemeByClassName({ - themes: { - light: 'light', - dark: 'dark', - }, - defaultTheme: 'light', - }), - ], - parameters: { - actions: {argTypesRegex: '^on[A-Z].*'}, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, + decorators: [ + withThemeByClassName({ + themes: { + light: 'light', + dark: 'dark', + }, + defaultTheme: 'light', + }), + ], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, }, + }, } export default preview @@ -131,13 +132,17 @@ Una historia en Storybook es una representación visual y funcional de un compon Vamos a crear la historia del componente que creamos antes: -```typescript title="stories/components/theme-switcher.stories.ts" +```typescript title="src/stories/components/theme-switcher.stories.ts" import { Meta, StoryObj } from '@storybook/react' import { ThemeSwitcher } from '@/components/theme-switcher' const meta = { component: ThemeSwitcher, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], title: 'Components/ThemeSwitcher', } satisfies Meta @@ -147,10 +152,6 @@ type Story = StoryObj export const Basic: Story = {} ``` -!!! warning - Se ha cambiado la configuración para cargar las historias desde `/stories` en vez de `/src/stories`. Las historias que se han creado por - defecto las puedes eliminar o moverlas a la nueva carpeta. - Y aparecerá una nueva sección llamada componentes que tendrá nuestra historia. Ahora veremos nuestra historia de forma correcta. Podemos ya incluso borrar los componentes de ejemplo. @@ -159,4 +160,6 @@ Ahora veremos nuestra historia de forma correcta. Podemos ya incluso borrar los !!! question "Ejercicio" Guarda los cambios que hemos hecho y la historia que hemos creado en git. - No guardes los componentes de ejemplo. + Storybook crea una serie de historias de ejemplo en el directorio `src/stories`. + Recuerda no guardarlas en git. +