diff --git a/package-lock.json b/package-lock.json index ebb1a85..05fd0ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,19 +12,33 @@ "bootstrap-icons": "^1.11.3", "next": "15.2.3", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-syntax-highlighter": "^15.6.1" }, "devDependencies": { "@eslint/eslintrc": "^3", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", "eslint-config-next": "15.2.3", "sass": "^1.86.0", "typescript": "^5" } }, + "node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", @@ -1210,6 +1224,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1254,6 +1277,22 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.27.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.27.0.tgz", @@ -2105,6 +2144,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2172,6 +2241,16 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3042,6 +3121,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3122,6 +3214,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3390,6 +3490,48 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3449,6 +3591,30 @@ "node": ">= 0.4" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3601,6 +3767,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3659,6 +3835,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -4026,6 +4212,20 @@ "loose-envify": "cli.js" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4381,6 +4581,24 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4475,6 +4693,15 @@ "node": ">= 0.8.0" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4487,6 +4714,19 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4546,6 +4786,23 @@ "dev": true, "license": "MIT" }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -4583,6 +4840,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -5005,6 +5292,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -5551,6 +5848,15 @@ "node": ">=0.10.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 0f43efe..09c1b1e 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,15 @@ "bootstrap-icons": "^1.11.3", "next": "15.2.3", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-syntax-highlighter": "^15.6.1" }, "devDependencies": { "@eslint/eslintrc": "^3", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", "eslint-config-next": "15.2.3", "sass": "^1.86.0", diff --git a/public/input.png b/public/input.png index 8eae39b..099f75c 100644 Binary files a/public/input.png and b/public/input.png differ diff --git a/public/options.png b/public/options.png new file mode 100644 index 0000000..b1db4b8 Binary files /dev/null and b/public/options.png differ diff --git a/public/pagination.png b/public/pagination.png new file mode 100644 index 0000000..dc0bc41 Binary files /dev/null and b/public/pagination.png differ diff --git a/public/select.png b/public/select.png new file mode 100644 index 0000000..82afd52 Binary files /dev/null and b/public/select.png differ diff --git a/public/table.png b/public/table.png index fb3418b..3d1d70c 100644 Binary files a/public/table.png and b/public/table.png differ diff --git a/src/app/(components)/button/page.tsx b/src/app/(components)/button/page.tsx new file mode 100644 index 0000000..f7d1e20 --- /dev/null +++ b/src/app/(components)/button/page.tsx @@ -0,0 +1,96 @@ +"use client"; + +import IndiceContenido from "@/components/content-index"; +import VistaComponente from "@/components/vista-componente"; +import Button from "@/components/button"; + +export default function Page() { + + return ( + <> + + +
+

+ 🧩 Componente: Button +

+ +

+ El componente <Button /> permite crear botones con + estilos de Bootstrap, variantes personalizables, íconos opcionales y + soporte completo para props de HTML estándar. +

+ +
+ + alert("¡Hola mundo!")}> + Clic aquí +`} + > + + + + + Guardar +`} + > + + + +
+

⚙️ Props del componente

+
    +
  • + variant?: string – Clase base del botón. Ej:{" "} + "primary", "danger". +
  • +
  • + outline?: boolean – Si el botón debe usar estilo{" "} + outline. +
  • +
  • + icon?: string | ReactNode – Ícono Bootstrap (por + nombre) o componente React. +
  • +
  • + size?: "sm" | "lg" – Tamaño del botón. +
  • +
  • + className?: string – Clases adicionales para el + botón. +
  • +
  • + type?: "button" | "submit" | "reset" – Tipo de botón. +
  • +
  • + disabled?: boolean – Desactiva el botón. +
  • +
  • + children: ReactNode – Contenido del botón. +
  • +
  • + ...rest – Cualquier otra prop válida de{" "} + <button>. +
  • +
+
+
+ + ); +} diff --git a/src/app/(components)/input/page.tsx b/src/app/(components)/input/page.tsx new file mode 100644 index 0000000..c38e1d8 --- /dev/null +++ b/src/app/(components)/input/page.tsx @@ -0,0 +1,187 @@ +"use client"; + +import IndiceContenido from "@/components/content-index"; +import Input from "@/components/input"; +import VistaComponente from "@/components/vista-componente"; +import { inputBasicExample, inputPasswordExample, inputStylesExample } from "@/snippets/input/input-examples"; +import { useState } from "react"; + +export default function Page() { + const [value, setValue] = useState(""); + const [password, setPassword] = useState(""); + + return ( + <> + +
+

+ 🔤 Componente: Input +

+ +

+ El componente <Input /> es un campo de entrada + reutilizable con soporte para etiquetas, tipos como{" "} + password, accesibilidad mejorada, y personalización de + estilos. Además, incluye un botón para mostrar/ocultar contraseñas + cuando el tipo es password. +

+ +
+ + + setValue(e.target.value)} + /> + + + + setPassword(e.target.value)} + /> + + + + + + +
+

Props del componente

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropTipoDescripción
+ label? + + string + Etiqueta asociada al campo de entrada.
+ type? + + string + + Tipo del input. Soporta cualquier tipo válido de HTML como{" "} + text, password, email, + etc. +
+ value? + + string + Valor del input en modo controlado.
+ onChange? + + + (e: React.ChangeEvent<HTMLInputElement>) => void + + Función que se ejecuta al cambiar el valor del input.
+ name? + + string + Nombre del campo, útil en formularios.
+ id? + + string + + ID personalizado para el input. Si no se proporciona, se + genera automáticamente. +
+ className? + + {`{ container?, label?, input? }`} + + Permite personalizar los estilos del contenedor, la etiqueta y + el input. +
+ ...rest + + InputHTMLAttributes<HTMLInputElement> + + Cualquier otra prop válida de un <input />{" "} + estándar. +
+
+
+ + ); +} diff --git a/src/app/(components)/layout.tsx b/src/app/(components)/layout.tsx new file mode 100644 index 0000000..cb55daf --- /dev/null +++ b/src/app/(components)/layout.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export default function ComponentsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} diff --git a/src/app/(components)/options/page.tsx b/src/app/(components)/options/page.tsx new file mode 100644 index 0000000..4d9a7ff --- /dev/null +++ b/src/app/(components)/options/page.tsx @@ -0,0 +1,200 @@ +"use client"; + +import React, { useState } from "react"; +import RadioOptionGroup, { RadioOption } from "@/components/radio-option-group"; +import CheckboxOptionGroup, { + CheckboxOption, +} from "@/components/checkbox-option-group"; +import VistaComponente from "@/components/vista-componente"; +import { CodeBlock } from "@/components/code-block"; +import { + checkboxBasicExample, + checkboxLimitExample, + radioBasicExample, + radioDisabledExample, +} from "@/snippets/options/option-examples"; +import IndiceContenido from "@/components/content-index"; + +export default function RadioCheckboxDocs() { + const [selectedRadio, setSelectedRadio] = useState("op1"); + + const [checkboxBasicSelected, setCheckboxBasicSelected] = useState([ + "a", + ]); + const [checkboxLimitSelected, setCheckboxLimitSelected] = useState( + [] + ); + + const radioOptions: RadioOption[] = [ + { label: "Opción 1", value: "op1" }, + { label: "Opción 2", value: "op2" }, + { label: "Opción 3", value: "op3" }, + ]; + + const checkboxOptions: CheckboxOption[] = [ + { label: "Activo", value: "a" }, + { label: "Inactivo", value: "b" }, + { label: "Pendiente", value: "c" }, + ]; + + const handleCheckboxChange = ( + value: string, + checked: boolean, + setValues: React.Dispatch> + ) => { + setValues((prev) => + checked ? [...prev, value] : prev.filter((v) => v !== value) + ); + }; + + return ( + <> + +
+

+ Componentes: RadioOptionGroup y{" "} + CheckboxOptionGroup +

+ +

+ Estos componentes permiten mostrar grupos de opciones radio o + checkbox, para seleccionar una o varias opciones respectivamente. Son + útiles en formularios, encuestas o filtros. Los estilos de estos + componentes se basan en Bootstrap y se encuentran el el archivo{" "} + @/styles/sass/_extneds.scss y es el selector{" "} + .option. +

+ + + +
+ + + + + + + {}} + /> + + + + + handleCheckboxChange(value, checked, setCheckboxBasicSelected) + } + /> + + + + + handleCheckboxChange(value, checked, setCheckboxLimitSelected) + } + maxSelected={2} + /> + + +
+

Props comunes

+
    +
  • + name: Nombre del grupo de inputs. +
  • +
  • + options: Lista de opciones con label,{" "} + value y opcionalmente disabled. +
  • +
  • + className?: Clases CSS adicionales. +
  • +
+ +

+ Props de RadioOptionGroup +

+
    +
  • + selectedValue?: Valor seleccionado actualmente. +
  • +
  • + onChange: Función que se ejecuta al cambiar de + opción. +
  • +
  • + disabled?: Deshabilita todo el grupo. +
  • +
+ +

+ ☑️ Props de CheckboxOptionGroup +

+
    +
  • + selectedValues: Lista de valores seleccionados. +
  • +
  • + onChange: Función que se ejecuta al marcar o + desmarcar una opción. +
  • +
  • + maxSelected?: Número máximo de opciones + seleccionables. +
  • +
  • + disabled?: Deshabilita todo el grupo. +
  • +
+
+
+ + ); +} diff --git a/src/app/(components)/pagination/page.tsx b/src/app/(components)/pagination/page.tsx new file mode 100644 index 0000000..5609caf --- /dev/null +++ b/src/app/(components)/pagination/page.tsx @@ -0,0 +1,107 @@ +"use client"; + +import React, { useState } from "react"; +import VistaComponente from "@/components/vista-componente"; +import { CodeBlock } from "@/components/code-block"; +import Pagination from "@/components/pagination"; +import { + paginationBasic, + paginationWithDelta, +} from "@/snippets/pagination/pagination-example"; +import IndiceContenido from "@/components/content-index"; + +export default function Page() { + const [currentPage1, setCurrentPage1] = useState(1); + const [currentPage2, setCurrentPage2] = useState(6); + + return ( + <> + +
+

+ 📄 Componente: Pagination +

+ +

+ El componente <Pagination /> permite navegar entre + páginas de forma controlada. Es completamente reutilizable y permite + mostrar botones de anterior/siguiente y paginación con saltos. +

+ + + +
+ + {/* Ejemplo básico */} + + +

+ Página actual: {currentPage1} +

+
+ + {/* Ejemplo con delta */} + + +

+ Página actual: {currentPage2} +

+
+ + {/* Props */} +
+

⚙️ Props del componente

+
    +
  • + totalPages: number — Total de páginas + disponibles. +
  • +
  • + currentPage: number — Página actual. +
  • +
  • + onPageChange: (page: number) => void — + Callback que se ejecuta al cambiar de página. +
  • +
  • + delta?: number — Número de páginas vecinas + visibles (por defecto: 1). +
  • +
  • + className?: string — Clases CSS + personalizadas para el contenedor. +
  • +
+
+
+ + ); +} diff --git a/src/app/(components)/select/page.tsx b/src/app/(components)/select/page.tsx new file mode 100644 index 0000000..386aae1 --- /dev/null +++ b/src/app/(components)/select/page.tsx @@ -0,0 +1,123 @@ +"use client"; + +import IndiceContenido from "@/components/content-index"; +import Select from "@/components/select"; +import VistaComponente from "@/components/vista-componente"; +import { countrySelectSnippet, languageSelectSnippet } from "@/snippets/select/select"; +import { useState } from "react"; + +export default function Page() { + const [pais, setPais] = useState(""); + + const opcionesPais = [ + { value: "mx", label: "México" }, + { value: "us", label: "Estados Unidos" }, + { value: "ca", label: "Canadá" }, + ]; + + return ( + <> + +
+

+ 📑 Componente: Select +

+ +

+ El componente <Select /> permite mostrar una lista + desplegable con opciones personalizables, soporta etiquetas, estilos + por clase, y es compatible con formularios. +

+ +
+ + + + + +
+

⚙️ Props del componente

+
    +
  • + label: string – Etiqueta visible del campo. +
  • +
  • + + options: { value: string | number; label: string + }[] + {" "} + – Opciones a mostrar. +
  • +
  • + placeholder?: string – Texto inicial deshabilitado. +
  • +
  • + name?: string – Nombre del campo. +
  • +
  • + id?: string – ID del campo (se genera si no se + proporciona). +
  • +
  • + defaultValue?: string | number – Valor por defecto + (modo no controlado). +
  • +
  • + value?: string | number – Valor actual del select + (modo controlado). +
  • +
  • + className?: { container?, label?, input? }{" "} + – Clases personalizadas. +
  • +
  • + ...rest – Cualquier otra prop válida de{" "} + <select>. +
  • +
+
+
+ + ); +} diff --git a/src/app/(components)/table/page.tsx b/src/app/(components)/table/page.tsx new file mode 100644 index 0000000..2831f8d --- /dev/null +++ b/src/app/(components)/table/page.tsx @@ -0,0 +1,325 @@ +"use client"; + +import React, { useState } from "react"; +import Table, { Header, TableSkeleton } from "@/components/table"; +import VistaComponente from "@/components/vista-componente"; +import { + tableBasicExample, + tableSkeletonExample, +} from "@/snippets/table/table-basic"; +import { tableClickable } from "@/snippets/table/table-clickable"; +import { tableAutoPagination } from "@/snippets/table/table-auto-pagination"; +import { tableCustomPagination } from "@/snippets/table/table-custom-pagination"; +import { tableCustomCellRender } from "@/snippets/table/table-custom-cell-render"; +import { tableColumnWidth } from "@/snippets/table/table-column-width"; +import { CodeBlock } from "@/components/code-block"; +import Input from "@/components/input"; +import { tableFilter } from "@/snippets/table/table-filter"; +import IndiceContenido from "@/components/content-index"; + +export default function Page() { + const headers: Header<{ + name: string; + age: number; + email: string; + acciones?: null; + }>[] = [ + { key: "name", label: "Nombre", width: "30%" }, + { key: "age", label: "Edad", width: "15%" }, + { key: "email", label: "Correo", width: "40%" }, + ]; + + const baseData = [ + { name: "Juan", age: 20, email: "juan@example.com" }, + { name: "María", age: 25, email: "maria@example.com" }, + { name: "Pedro", age: 30, email: "pedro@example.com" }, + { name: "Luisa", age: 28, email: "luisa@example.com" }, + ]; + + const [currentPage, setCurrentPage] = useState(1); + const [search, setSearch] = useState(""); + + const filteredData = baseData.filter((item) => + item.name.toLowerCase().includes(search.toLowerCase()) + ); + + return ( + <> + +
+

+ Componente: Table +

+ +

+ El componente <Table /> permite mostrar datos en + forma tabular, con funcionalidades como ordenamiento, paginación, + renderizado personalizado, clic en filas y un estado de carga + esqueleto. Los estilos de esta tabla se encuentran en{" "} + @/styles/sass/_table.scss +

+ + + +
+ + + + + + {/* SKELETON */} + + + + + +
alert(`Seleccionaste a ${row.name}`)} + /> + + + +
+ + + +
+ + + +
( + + ), + }, + ]} + data={baseData.map((d) => ({ ...d, acciones: null }))} + /> + + + +
+ + + {/* FILTRO */} + + setSearch(e.target.value)} + /> +
+ + + +
({ ...item, id: index }))} + rowKey={(row) => row.email} + /> + + + {/* PROPS */} +
+

Props del componente

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropTipoDescripción
+ headers + + Header<T>[] + + Columnas de la tabla. Cada header puede tener key, label, + width, y una función render. +
+ data + + T[] + Datos a mostrar en la tabla.
+ rowsPerPage? + + number + + Número de filas por página (por defecto: 10). +
+ onRowClick? + + (row: T) => void + Función que se ejecuta al hacer clic en una fila.
+ rowKey? + + (row: T) => string | number + + Función para obtener un identificador único de cada fila + (evita usar index). +
+ className? + + {"{ table?, row?, cell?, header? }"} + + Clases personalizadas para la tabla, filas, celdas y + encabezados. +
+ controlledPage? + + number + Página actual controlada desde el componente padre.
+ onPageChange? + + (page: number) => void + Función callback que se dispara al cambiar de página.
+ +
+ + ); +} diff --git a/src/app/input/page.tsx b/src/app/input/page.tsx deleted file mode 100644 index 77f1452..0000000 --- a/src/app/input/page.tsx +++ /dev/null @@ -1,122 +0,0 @@ -"use client"; - -import React from "react"; -import Input from "@/components/input"; - -export default function Page() { - return ( -
-

- 📦 Componente: Input -

- -

- El componente {``} encapsula un campo de entrada - HTML estándar con mejoras en accesibilidad, estilo y experiencia de - usuario. Soporta etiquetas, clases personalizadas, inputs - deshabilitados, y funcionalidad para mostrar u ocultar contraseñas. -

- -
- -
-

🔹 Uso básico

-

Un campo de texto simple con etiqueta y placeholder.

- -
-          {``}
-        
-
- -
- -
-

🔐 Campo de tipo contraseña

-

- Incluye un botón para mostrar u ocultar el contenido del campo. -

- -
-          {``}
-        
-
- -
- -
-

🚫 Input deshabilitado

- -
-          {``}
-        
-
- -
- -
-

🎨 Personalización de clases

-

- Puedes personalizar el estilo de cada parte del input: contenedor, - etiqueta e input. -

- -
-          {``}
-        
-
- -
- -
-

📏 Input en contenedor reducido

-

Se adapta a contenedores pequeños sin problemas.

-
- -
-
-          {`
- -
`}
-
-
-
- ); -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b64257c..53df080 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Roboto } from "next/font/google"; import 'bootstrap-icons/font/bootstrap-icons.css'; import '@/styles/sass/bootstrap.scss'; +import Navbar from "@/components/navbar"; const roboto = Roboto({ weight: ["100", "300", "400", "500", "700", "900"], @@ -25,6 +26,7 @@ export default function RootLayout({ return ( + {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index 0f6d9b4..98bf2e4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,28 +6,40 @@ import React from "react"; export default function Page() { return (
-
-
-
- CIDWA -
-

- Libreria de componentes de CIDWA -

+
+
+ CIDWA
-
+

Libreria de componentes de CIDWA

+
+
+

Componentes

+
+
+ +
+
+ +
+
+ +
diff --git a/src/app/table/page.tsx b/src/app/table/page.tsx deleted file mode 100644 index 6b70e07..0000000 --- a/src/app/table/page.tsx +++ /dev/null @@ -1,225 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import Table, { Header, TablaSkeleton } from "@/components/table"; -import VistaComponente from "@/components/vista-componente"; -import { tableBasicExample } from "@/snippets/table/table-basic"; - -export default function Page() { - const headers: Header<{ - name: string; - age: number; - email: string; - acciones?: null; - }>[] = [ - { key: "name", label: "Nombre", width: "30%" }, - { key: "age", label: "Edad", width: "15%" }, - { key: "email", label: "Correo", width: "40%" }, - ]; - - const baseData = [ - { name: "Juan", age: 20, email: "juan@example.com" }, - { name: "María", age: 25, email: "maria@example.com" }, - { name: "Pedro", age: 30, email: "pedro@example.com" }, - { name: "Luisa", age: 28, email: "luisa@example.com" }, - ]; - - const [currentPage, setCurrentPage] = useState(1); - const [search, setSearch] = useState(""); - - const filteredData = baseData.filter((item) => - item.name.toLowerCase().includes(search.toLowerCase()) - ); - - return ( -
-

- 📊 Componente: Table -

- -

- El componente <Table /> permite mostrar datos en - forma tabular, con funcionalidades como ordenamiento, paginación, - renderizado personalizado, clic en filas y un estado de carga esqueleto. -

- -
- - {/* USO BÁSICO */} - - - - - {/* FILAS CLICABLES */} - -
alert(`Seleccionaste a ${row.name}`)} - /> - - - {/* PAGINACIÓN */} - -
- - - {/* CONTROL DE PÁGINA EXTERNO */} - -
- - - {/* RENDER PERSONALIZADO */} - -
( - - ), - }, - ]} - data={baseData.map((d) => ({ ...d, acciones: null }))} - /> - - - {/* PERSONALIZACIÓN DE ANCHO */} - -
- - - {/* ANCHO AUTOMÁTICO */} - -
- - - {/* SKELETON */} - - - - - {/* ROW KEY */} - -
({ ...item, id: index }))} - rowKey={(row) => row.email} - /> - - - {/* FILTRO */} - - setSearch(e.target.value)} - /> -
- - - {/* PROPS */} -
-

⚙️ Props del componente

-
    -
  • - headers: Columnas de la tabla (con key, label, render, - width...) -
  • -
  • - data: Datos a mostrar. -
  • -
  • - rowsPerPage?: Número de filas por página (por defecto: - 10). -
  • -
  • - onRowClick?: Función al hacer clic en una fila. -
  • -
  • - rowKey?: Función que retorna un ID único para cada - fila. -
  • -
  • - className?: Estilos personalizados (tabla, fila, celda, - header). -
  • -
  • - controlledPage?: Página actual controlada externamente. -
  • -
  • - onPageChange?: Callback para cambiar la página. -
  • -
-
- - ); -} diff --git a/src/components/button.tsx b/src/components/button.tsx new file mode 100644 index 0000000..dd46dc3 --- /dev/null +++ b/src/components/button.tsx @@ -0,0 +1,72 @@ +import React from 'react'; + +/** + * Botón reutilizable con soporte para variantes de Bootstrap y personalización de íconos. + * + * @component + */ +interface ButtonProps extends React.ButtonHTMLAttributes { + /** + * Variante Bootstrap o personalizada. Ej: `primary`, `danger`, `mi-clase-custom`. + * @default "primary" + */ + variant?: string; + + /** + * Si se usa variante `outline` (por ejemplo, `btn-outline-primary`). + * @default false + */ + outline?: boolean; + + /** + * Ícono Bootstrap (`string`, como `check-circle`) o componente React. + */ + icon?: string | React.ReactNode; + + /** + * Tamaño del botón: `sm` o `lg` para tamaños Bootstrap. + */ + size?: 'sm' | 'lg'; + + /** + * Clases adicionales personalizadas para el botón. + */ + className?: string; +} + +/** + * Componente `Button` con estilos de Bootstrap y soporte para íconos. + */ +const Button: React.FC = ({ + children, + type = 'button', + icon, + className = '', + variant = 'primary', + outline = false, + disabled = false, + size, + ...rest +}) => { + const btnVariant = outline ? `btn-outline-${variant}` : `btn-${variant}`; + const sizeClass = size ? `btn-${size}` : ''; + const combinedClasses = `btn ${btnVariant} ${sizeClass} ${className}`.trim(); + + return ( + + ); +}; + +export default Button; diff --git a/src/components/checkbox-option-group.tsx b/src/components/checkbox-option-group.tsx new file mode 100644 index 0000000..66720ac --- /dev/null +++ b/src/components/checkbox-option-group.tsx @@ -0,0 +1,58 @@ +import React from "react"; + +export interface CheckboxOption { + label: string; + value: T; + disabled?: boolean; +} + +interface CheckboxOptionGroupProps { + name: string; + options: CheckboxOption[]; + selectedValues: T[]; + onChange: (value: T, checked: boolean) => void; + maxSelected?: number; + disabled?: boolean; + className?: string; +} + +export default function CheckboxOptionGroup({ + name, + options, + selectedValues, + onChange, + maxSelected, + disabled = false, + className = "", +}: CheckboxOptionGroupProps) { + const isSelected = (value: T) => selectedValues.includes(value); + + return ( +
+ {options.map((option, index) => { + const checked = isSelected(option.value); + const limitReached = + maxSelected !== undefined && + selectedValues.length >= maxSelected && + !checked; + + return ( + + ); + })} +
+ ); +} diff --git a/src/components/code-block.tsx b/src/components/code-block.tsx index ddfa8dd..bdf4c1e 100644 --- a/src/components/code-block.tsx +++ b/src/components/code-block.tsx @@ -1,13 +1,20 @@ +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'; + export const CodeBlock = ({ code }: { code: string }) => (
-
-      {code}
-    
+ + {code} +
); diff --git a/src/components/content-index.tsx b/src/components/content-index.tsx new file mode 100644 index 0000000..069d313 --- /dev/null +++ b/src/components/content-index.tsx @@ -0,0 +1,23 @@ +interface IndiceContenidoProps { + items: { + id: string; + label: string; + }[]; +} + +export default function IndiceContenido({ items }: IndiceContenidoProps) { + return ( + + ); +} diff --git a/src/components/input.tsx b/src/components/input.tsx index 87bb9d3..2b32e67 100644 --- a/src/components/input.tsx +++ b/src/components/input.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; export interface InputProps extends Omit, "className"> { - label: string; + label?: string; className?: { container?: string; label?: string; @@ -13,13 +13,13 @@ export interface InputProps /** * Componente de entrada (`Input`) reutilizable en React. * - * Este componente renderiza un campo de entrada (``) con una etiqueta y + * Este componente renderiza un campo de entrada (``) con una etiqueta opcional y * funcionalidad para mostrar u ocultar contraseñas cuando el tipo es `password`. * También permite personalizar clases CSS y es accesible mediante `aria-label` y `htmlFor`. * * @component * @param {Object} props - Propiedades del componente. - * @param {string} props.label - Etiqueta visible asociada al campo de entrada. + * @param {string} [props.label] - Etiqueta visible asociada al campo de entrada. * @param {Object} [props.className] - Clases CSS personalizables para distintos elementos. * @param {string} [props.className.container] - Clases para el contenedor del input. * @param {string} [props.className.label] - Clases para la etiqueta del input. @@ -30,29 +30,6 @@ export interface InputProps * @param {string} [props.value] - Valor del input (para uso controlado). * @param {function} [props.onChange] - Manejador del cambio de valor del input. * @param {React.InputHTMLAttributes} [rest] - Atributos adicionales compatibles con ``. - * - * @example - * // Ejemplo de uso con contraseña: - * - * - * @example - * // Ejemplo de input controlado: - * setEmail(e.target.value)} - * /> */ export default function Input(props: InputProps) { const { className, label, type, name, id, ...rest } = props; @@ -60,18 +37,21 @@ export default function Input(props: InputProps) { const isPassword = type === "password"; const inputType = isPassword && showPassword ? "text" : type; - const inputId = id || name || `input-${label.replace(/\s+/g, "-")}`; + const inputId = + id || name || (label ? `input-${label.replace(/\s+/g, "-")}` : undefined); const togglePasswordVisibility = () => setShowPassword(!showPassword); return ( -
- +
+ {label && inputId && ( + + )}
{ + return ( + + ); +}; + +export default Navbar; diff --git a/src/components/pagination.tsx b/src/components/pagination.tsx index 7514b3e..19a2ac2 100644 --- a/src/components/pagination.tsx +++ b/src/components/pagination.tsx @@ -1,54 +1,52 @@ -import React, { useMemo } from 'react'; +import React, { useMemo } from "react"; interface PaginationProps { totalPages: number; currentPage: number; onPageChange: (page: number) => void; + delta?: number; } export default function Pagination({ currentPage, onPageChange, totalPages, + delta = 1, }: PaginationProps) { const handlePageChange = (page: number) => { - if (page >= 1 && page <= totalPages) { + if (page >= 1 && page <= totalPages && page !== currentPage) { onPageChange(page); } }; - // Lógica para mostrar paginación compacta cuando hay muchas páginas const visiblePages = useMemo(() => { - const pages: number[] = []; - const delta = 0; // Cuántas páginas cercanas a la actual mostrar + const pages: (number | -1)[] = []; - if (totalPages <= 5) { + if (totalPages <= 5 + delta * 2) { return Array.from({ length: totalPages }, (_, i) => i + 1); } - const leftBoundary = Math.max(1, currentPage - delta); - const rightBoundary = Math.min(totalPages, currentPage + delta); + const left = Math.max(2, currentPage - delta); + const right = Math.min(totalPages - 1, currentPage + delta); - if (leftBoundary > 2) { - pages.push(1, -1); // -1 es un marcador de "..." - } + pages.push(1); + if (left > 2) pages.push(-1); - for (let i = leftBoundary; i <= rightBoundary; i++) { + for (let i = left; i <= right; i++) { pages.push(i); } - if (rightBoundary < totalPages - 1) { - pages.push(-1, totalPages); - } + if (right < totalPages - 1) pages.push(-1); + pages.push(totalPages); return pages; - }, [currentPage, totalPages]); + }, [currentPage, totalPages, delta]); return (
+ ); +}; + +export default Select; diff --git a/src/components/table.tsx b/src/components/table.tsx index 7acc404..b90bae2 100644 --- a/src/components/table.tsx +++ b/src/components/table.tsx @@ -169,7 +169,7 @@ interface TablaSkeletonProps { columns?: number; } -export function TablaSkeleton({ rows = 5, columns = 5 }: TablaSkeletonProps) { +export function TableSkeleton({ rows = 5, columns = 5 }: TablaSkeletonProps) { return (
diff --git a/src/components/vista-componente.tsx b/src/components/vista-componente.tsx index 8afb9ea..8a215ce 100644 --- a/src/components/vista-componente.tsx +++ b/src/components/vista-componente.tsx @@ -1,11 +1,13 @@ -import React, { useState } from "react"; import { CodeBlock } from "./code-block"; +import Button from "./button"; +import { useState } from "react"; interface VistaComponenteProps { title: string; description?: string; code?: string; children: React.ReactNode; + id: string; } export default function VistaComponente({ @@ -13,11 +15,12 @@ export default function VistaComponente({ description, code, children, + id, }: VistaComponenteProps) { const [showCode, setShowCode] = useState(false); return ( -
+

{title}

{description &&

{description}

} @@ -26,12 +29,15 @@ export default function VistaComponente({ {code && (
- + {showCode && }
)} diff --git a/src/snippets/input/input-examples.ts b/src/snippets/input/input-examples.ts new file mode 100644 index 0000000..4d3ff71 --- /dev/null +++ b/src/snippets/input/input-examples.ts @@ -0,0 +1,23 @@ +export const inputBasicExample = ` setValue(e.target.value)} +/>`; + +export const inputPasswordExample = ` setPassword(e.target.value)} +/>`; + +export const inputStylesExample = ``; diff --git a/src/snippets/options/option-examples.ts b/src/snippets/options/option-examples.ts new file mode 100644 index 0000000..93ca2b4 --- /dev/null +++ b/src/snippets/options/option-examples.ts @@ -0,0 +1,59 @@ +export const radioBasicExample = ` + +`; + +export const radioDisabledExample = ` + {}} +/> +`; + +export const checkboxBasicExample = ` + { + setSelectedCheckboxes((prev) => + checked ? [...prev, value] : prev.filter((v) => v !== value) + ); + }} +/> +`; + +export const checkboxLimitExample = ` + { + setSelectedCheckboxes((prev) => + checked ? [...prev, value] : prev.filter((v) => v !== value) + ); + }} + maxSelected={2} +/> +`; diff --git a/src/snippets/pagination/pagination-example.ts b/src/snippets/pagination/pagination-example.ts new file mode 100644 index 0000000..3e60188 --- /dev/null +++ b/src/snippets/pagination/pagination-example.ts @@ -0,0 +1,16 @@ +export const paginationBasic = ` + +`; + +export const paginationWithDelta = ` + +`; diff --git a/src/snippets/select/select.tsx b/src/snippets/select/select.tsx new file mode 100644 index 0000000..fcbad84 --- /dev/null +++ b/src/snippets/select/select.tsx @@ -0,0 +1,32 @@ +export const countrySelectSnippet = ` + +`; \ No newline at end of file diff --git a/src/snippets/table/table-auto-pagination.ts b/src/snippets/table/table-auto-pagination.ts new file mode 100644 index 0000000..fde61f7 --- /dev/null +++ b/src/snippets/table/table-auto-pagination.ts @@ -0,0 +1,30 @@ +export const tableAutoPagination = `import React from "react"; +import Table, { Header } from "@/components/table"; + +export default function Page() { + const headers: Header<{ + name: string; + age: number; + email: string; + }>[] = [ + { key: "name", label: "Nombre"}, + { key: "age", label: "Edad" }, + { key: "email", label: "Correo" }, + ]; + + const baseData = [ + { name: "Juan", age: 20, email: "juan@example.com" }, + { name: "María", age: 25, email: "maria@example.com" }, + { name: "Pedro", age: 30, email: "pedro@example.com" }, + { name: "Luisa", age: 28, email: "luisa@example.com" }, + ]; + + return ( +
+ ) +} +`; diff --git a/src/snippets/table/table-basic.ts b/src/snippets/table/table-basic.ts index 857b439..2684ce2 100644 --- a/src/snippets/table/table-basic.ts +++ b/src/snippets/table/table-basic.ts @@ -1,4 +1,36 @@ -// src/code-snippets/table-basic.ts -export const tableBasicExample = ` -
+export const tableBasicExample = `import React from "react"; +import Table, { Header } from "@/components/table"; + +export default function Page() { + const headers: Header<{ + name: string; + age: number; + email: string; + }>[] = [ + { key: "name", label: "Nombre"}, + { key: "age", label: "Edad" }, + { key: "email", label: "Correo" }, + ]; + + const baseData = [ + { name: "Juan", age: 20, email: "juan@example.com" }, + { name: "María", age: 25, email: "maria@example.com" }, + { name: "Pedro", age: 30, email: "pedro@example.com" }, + { name: "Luisa", age: 28, email: "luisa@example.com" }, + ]; + + return ( +
+ ) +} +`; + +export const tableSkeletonExample = `import React from "react"; +import { TableSkeleton } from "@/components/table"; + +export default function Page() { + return ( + + ) +} `; diff --git a/src/snippets/table/table-clickable.ts b/src/snippets/table/table-clickable.ts new file mode 100644 index 0000000..700ca76 --- /dev/null +++ b/src/snippets/table/table-clickable.ts @@ -0,0 +1,27 @@ +export const tableClickable = `export default function Page() { + const headers: Header<{ + name: string; + age: number; + email: string; + }>[] = [ + { key: "name", label: "Nombre"}, + { key: "age", label: "Edad" }, + { key: "email", label: "Correo" }, + ]; + + const baseData = [ + { name: "Juan", age: 20, email: "juan@example.com" }, + { name: "María", age: 25, email: "maria@example.com" }, + { name: "Pedro", age: 30, email: "pedro@example.com" }, + { name: "Luisa", age: 28, email: "luisa@example.com" }, + ]; + + return ( +
alert(\`Seleccionaste a \${row.name}\`)} + /> + ) +} +`; diff --git a/src/snippets/table/table-column-width.ts b/src/snippets/table/table-column-width.ts new file mode 100644 index 0000000..1fdfc8d --- /dev/null +++ b/src/snippets/table/table-column-width.ts @@ -0,0 +1,23 @@ +export const tableColumnWidth = `interface RowData { + name: string; + age: number; + email: string; +} + +export default function Page() { + const headers: Header[] = [ + { key: "name", label: "Nombre", width: "50%" }, + { key: "age", label: "Edad", width: "15%" }, + { key: "email", label: "Correo electrónico", width: "35%" }, + ]; + + const data: RowData[] = [ + { name: "Juan", age: 20, email: "juan@example.com" }, + { name: "María", age: 25, email: "maria@example.com" }, + { name: "Pedro", age: 30, email: "pedro@example.com" }, + { name: "Luisa", age: 28, email: "luisa@example.com" }, + ]; + + return
; +} +`; diff --git a/src/snippets/table/table-custom-cell-render.ts b/src/snippets/table/table-custom-cell-render.ts new file mode 100644 index 0000000..4874dcb --- /dev/null +++ b/src/snippets/table/table-custom-cell-render.ts @@ -0,0 +1,37 @@ +export const tableCustomCellRender = ` +interface RowData { + name: string; + age: number; + email: string; + acciones?: null; +} + +export default function Page() { + const headers: Header[] = [ + { key: "name", label: "Nombre", width: "30%" }, + { key: "age", label: "Edad", width: "15%" }, + { key: "email", label: "Correo", width: "40%" }, + { + key: "acciones", + label: "Acciones", + width: "15%", + render: (_, row) => ( + + ), + }, + ]; + + const data = [ + { name: "Juan", age: 20, email: "juan@example.com", acciones: null }, + { name: "María", age: 25, email: "maria@example.com", acciones: null }, + { name: "Pedro", age: 30, email: "pedro@example.com", acciones: null }, + ]; + + return
; +} +`; diff --git a/src/snippets/table/table-custom-pagination.ts b/src/snippets/table/table-custom-pagination.ts new file mode 100644 index 0000000..121e5f2 --- /dev/null +++ b/src/snippets/table/table-custom-pagination.ts @@ -0,0 +1,50 @@ +export const tableCustomPagination = `interface RowData { + name: string; + age: number; + email: string; +} + +export default function Page() { + const headers: Header[] = [ + { key: "name", label: "Nombre" }, + { key: "age", label: "Edad" }, + { key: "email", label: "Correo" }, + ]; + + const [currentPage, setCurrentPage] = useState(1); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const rowsPerPage = 2; + + useEffect(() => { + setLoading(true); + + fetch(\`https://api.example.com/users?page=\${currentPage}&limit=\${rowsPerPage}\`) + .then((res) => res.json()) + .then((json) => { + setData(json.data); + }) + .catch((err) => { + console.error("Error fetching data:", err); + setData([]); + }) + .finally(() => setLoading(false)); + }, [currentPage]); + + return ( + <> + {loading ? ( + + ) : ( +
+ )} + + ) +} +`; diff --git a/src/snippets/table/table-filter.ts b/src/snippets/table/table-filter.ts new file mode 100644 index 0000000..522d3fa --- /dev/null +++ b/src/snippets/table/table-filter.ts @@ -0,0 +1,42 @@ +export const tableFilter = `import React, { useState } from "react"; +import Table, { Header } from "@/components/table"; +import Input from "@/components/input"; + +interface RowData { + name: string; + age: number; + email: string; +} + +export default function Page() { + const headers: Header[] = [ + { key: "name", label: "Nombre", width: "30%" }, + { key: "age", label: "Edad", width: "15%" }, + { key: "email", label: "Correo", width: "40%" }, + ]; + + const data: RowData[] = [ + { name: "Juan", age: 20, email: "juan@example.com" }, + { name: "María", age: 25, email: "maria@example.com" }, + { name: "Pedro", age: 30, email: "pedro@example.com" }, + { name: "Luisa", age: 28, email: "luisa@example.com" }, + ]; + + const [search, setSearch] = useState(""); + + const filteredData = data.filter((item) => + item.name.toLowerCase().includes(search.toLowerCase()) + ); + + return ( + <> + setSearch(e.target.value)} + /> +
+ + ); +} +`; diff --git a/src/styles/sass/_extends.scss b/src/styles/sass/_extends.scss index c4accbc..e2d4d70 100644 --- a/src/styles/sass/_extends.scss +++ b/src/styles/sass/_extends.scss @@ -10,3 +10,59 @@ @extend .text-white; } } + +.unlink { + @extend .text-decoration-none; + @extend .text-dark; +} + +.flex-center { + @extend .d-flex; + @extend .justify-content-center; + @extend .align-items-center; +} + +.option { + @extend .box; + @extend .py-2; + + cursor: pointer; + transition: all 0.3s; + + &:has(input:checked) { + @extend .bg-primary; + @extend .bg-opacity-75; + @extend .text-white; + } + + input[type='radio']:checked { + border: 1px solid #ffffff; + @extend .bg-primary; + @extend .bg-opacity-75; + } + + &:hover { + background-color: #f1f1f1 !important; + } + + &:has(input:disabled) { + @extend .bg-light; + @extend .text-muted; + cursor: not-allowed; + } +} + +.border-top-azul { + position: relative; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 5px; + background: $azul; + border-radius: 5px 5px 0 0; + } +} diff --git a/src/styles/sass/_gradients.scss b/src/styles/sass/_gradients.scss index a51ebcb..e9db7e4 100644 --- a/src/styles/sass/_gradients.scss +++ b/src/styles/sass/_gradients.scss @@ -3,7 +3,7 @@ .bg-gradient-#{$color-name}-top { background-image: linear-gradient( to top, - $color-value, + rgba($color-value, 0.5), $body-bg ); } @@ -11,24 +11,16 @@ .bg-gradient-#{$color-name}-bottom { background-image: linear-gradient( to bottom, - $color-value, + rgba($color-value, 0.3), $body-bg ); } .bg-gradient-#{$color-name}-left { - background-image: linear-gradient( - to left, - $color-value, - $body-bg - ); + background-image: linear-gradient(to left, rgba($color-value, 0.5), $body-bg); } .bg-gradient-#{$color-name}-right { - background-image: linear-gradient( - to right, - $color-value, - $body-bg - ); + background-image: linear-gradient(to right, rgba($color-value, 0.5), $body-bg); } } diff --git a/src/styles/sass/bootstrap.scss b/src/styles/sass/bootstrap.scss index 121c755..1aa33e6 100644 --- a/src/styles/sass/bootstrap.scss +++ b/src/styles/sass/bootstrap.scss @@ -1,28 +1,57 @@ -@import 'bootstrap/scss/functions'; +@import "bootstrap/scss/functions"; -@import 'variables'; +@import "variables"; -@import 'bootstrap/scss/variables'; -@import 'bootstrap/scss/variables-dark'; +@import "bootstrap/scss/variables"; +@import "bootstrap/scss/variables-dark"; // Include any default map overrides here $azul: #003d79; $dorado: #d59f0f; $custom-colors: ( - 'azul': $azul, - 'dorado': $dorado, + "azul": $azul, + "dorado": $dorado, ); $theme-colors: map-merge($theme-colors, $custom-colors); -@import 'gradients'; -@import 'extends'; -@import 'table'; -@import 'utilities'; +@import "gradients"; +@import "extends"; +@import "table"; +@import "utilities"; -@import 'bootstrap/scss/maps'; -@import 'bootstrap/scss/mixins'; -@import 'bootstrap/scss/bootstrap'; +@import "bootstrap/scss/maps"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/bootstrap"; + +//Add additional custom code here + +@media (min-width: 992px) { + .bd-layout { + display: grid; + grid-template-areas: "sidebar main"; + grid-template-columns: 1fr 5fr; + gap: 1.5rem; + } +} + +.bd-sidebar { + grid-area: sidebar; + position: sticky; + top: ($spacer * 3); + height: calc(100vh - 6rem); +} + +ul.list-unstyled li { + padding: 0.5rem 0; + a { + color: $azul; + transition: color 0.2s; + text-decoration: none; + &:hover { + color: $dorado; + } + } +} -//Add additional custom code here \ No newline at end of file