diff --git a/package-lock.json b/package-lock.json index 10e09e8..94737ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "formularios_front", "version": "0.1.0", "dependencies": { + "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", "next": "15.2.4", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -19,6 +21,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.4", + "sass": "^1.86.1", "typescript": "^5" } }, @@ -825,6 +828,341 @@ "node": ">=12.4.0" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1656,6 +1994,41 @@ "dev": true, "license": "MIT" }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1788,6 +2161,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -3067,6 +3456,13 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", + "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3829,6 +4225,14 @@ } } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4198,6 +4602,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4373,6 +4791,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sass": { + "version": "1.86.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.1.tgz", + "integrity": "sha512-Yaok4XELL1L9Im/ZUClKu//D2OP1rOljKj0Gf34a+GzLbMveOzL7CfqYo+JUa5Xt1nhTCW+OcKp/FtR7/iqj1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", diff --git a/package.json b/package.json index 0bb5c13..a52fe6b 100644 --- a/package.json +++ b/package.json @@ -9,17 +9,20 @@ "lint": "next lint" }, "dependencies": { + "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", + "next": "15.2.4", "react": "^19.0.0", - "react-dom": "^19.0.0", - "next": "15.2.4" + "react-dom": "^19.0.0" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.4", - "@eslint/eslintrc": "^3" + "sass": "^1.86.1", + "typescript": "^5" } } diff --git a/public/file.svg b/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/globe.svg b/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/logo-fes.png b/public/logo-fes.png new file mode 100644 index 0000000..11e39c2 Binary files /dev/null and b/public/logo-fes.png differ diff --git a/public/logo-unam.png b/public/logo-unam.png new file mode 100644 index 0000000..9c169ee Binary files /dev/null and b/public/logo-unam.png differ diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/unam.png b/public/unam.png new file mode 100644 index 0000000..84f9d78 Binary files /dev/null and b/public/unam.png differ diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/window.svg b/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/globals.css b/src/app/globals.css deleted file mode 100644 index e3734be..0000000 --- a/src/app/globals.css +++ /dev/null @@ -1,42 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 42fc323..bc6d293 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,20 +1,21 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import { Roboto } from "next/font/google"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); +import "bootstrap-icons/font/bootstrap-icons.css"; +import "@/styles/sass/bootstrap.scss"; +import BootstrapClient from "@/components/bootstrap-client"; -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const roboto = Roboto({ + weight: ["100", "300", "400", "500", "700", "900"], subsets: ["latin"], }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Sistema de formularios", + description: "", + icons: { + icon: "/unam.png", + }, }; export default function RootLayout({ @@ -24,7 +25,8 @@ export default function RootLayout({ }>) { return ( - + + {children} diff --git a/src/app/page.module.css b/src/app/page.module.css deleted file mode 100644 index a11c8f3..0000000 --- a/src/app/page.module.css +++ /dev/null @@ -1,168 +0,0 @@ -.page { - --gray-rgb: 0, 0, 0; - --gray-alpha-200: rgba(var(--gray-rgb), 0.08); - --gray-alpha-100: rgba(var(--gray-rgb), 0.05); - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - - display: grid; - grid-template-rows: 20px 1fr 20px; - align-items: center; - justify-items: center; - min-height: 100svh; - padding: 80px; - gap: 64px; - font-family: var(--font-geist-sans); -} - -@media (prefers-color-scheme: dark) { - .page { - --gray-rgb: 255, 255, 255; - --gray-alpha-200: rgba(var(--gray-rgb), 0.145); - --gray-alpha-100: rgba(var(--gray-rgb), 0.06); - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - } -} - -.main { - display: flex; - flex-direction: column; - gap: 32px; - grid-row-start: 2; -} - -.main ol { - font-family: var(--font-geist-mono); - padding-left: 0; - margin: 0; - font-size: 14px; - line-height: 24px; - letter-spacing: -0.01em; - list-style-position: inside; -} - -.main li:not(:last-of-type) { - margin-bottom: 8px; -} - -.main code { - font-family: inherit; - background: var(--gray-alpha-100); - padding: 2px 4px; - border-radius: 4px; - font-weight: 600; -} - -.ctas { - display: flex; - gap: 16px; -} - -.ctas a { - appearance: none; - border-radius: 128px; - height: 48px; - padding: 0 20px; - border: none; - border: 1px solid transparent; - transition: - background 0.2s, - color 0.2s, - border-color 0.2s; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - line-height: 20px; - font-weight: 500; -} - -a.primary { - background: var(--foreground); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--gray-alpha-200); - min-width: 158px; -} - -.footer { - grid-row-start: 3; - display: flex; - gap: 24px; -} - -.footer a { - display: flex; - align-items: center; - gap: 8px; -} - -.footer img { - flex-shrink: 0; -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } - - .footer a:hover { - text-decoration: underline; - text-underline-offset: 4px; - } -} - -@media (max-width: 600px) { - .page { - padding: 32px; - padding-bottom: 80px; - } - - .main { - align-items: center; - } - - .main ol { - text-align: center; - } - - .ctas { - flex-direction: column; - } - - .ctas a { - font-size: 14px; - height: 40px; - padding: 0 16px; - } - - a.secondary { - min-width: auto; - } - - .footer { - flex-wrap: wrap; - align-items: center; - justify-content: center; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx index 84af2cb..7cf747c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,95 +1,14 @@ -import Image from "next/image"; -import styles from "./page.module.css"; +import Footer from "@/components/layout/footer"; +import Header from "@/components/layout/header"; +import React from "react"; -export default function Home() { +export default function Page() { return ( -
-
- Next.js logo -
    -
  1. - Get started by editing src/app/page.tsx. -
  2. -
  3. Save and see your changes instantly.
  4. -
- -
- - Vercel logomark - Deploy now - - - Read our docs - -
-
- +
+
+
+
+
); } diff --git a/src/components/bootstrap-client.tsx b/src/components/bootstrap-client.tsx new file mode 100644 index 0000000..f0f92f1 --- /dev/null +++ b/src/components/bootstrap-client.tsx @@ -0,0 +1,17 @@ +'use client'; +import { useEffect } from 'react'; + +export default function BootstrapClient() { + useEffect(() => { + const loadBootstrap = async () => { + if (typeof document !== 'undefined') { + // @ts-expect-error: Bootstrap is loaded dynamically + await import('bootstrap/dist/js/bootstrap.bundle.min.js'); + } + }; + + loadBootstrap(); + }, []); + + return null; +} 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..cb09ca5 --- /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/input.tsx b/src/components/input.tsx new file mode 100644 index 0000000..2b32e67 --- /dev/null +++ b/src/components/input.tsx @@ -0,0 +1,79 @@ +import React, { useState } from "react"; + +export interface InputProps + extends Omit, "className"> { + label?: string; + className?: { + container?: string; + label?: string; + input?: string; + }; +} + +/** + * Componente de entrada (`Input`) reutilizable en React. + * + * 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 {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. + * @param {string} [props.className.input] - Clases para el input en sí. + * @param {string} [props.type] - Tipo del input (`text`, `password`, `email`, etc.). + * @param {string} [props.name] - Nombre del input (útil para formularios). + * @param {string} [props.id] - ID personalizado para el input (para accesibilidad). + * @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 ``. + */ +export default function Input(props: InputProps) { + const { className, label, type, name, id, ...rest } = props; + const [showPassword, setShowPassword] = useState(false); + + const isPassword = type === "password"; + const inputType = isPassword && showPassword ? "text" : type; + const inputId = + id || name || (label ? `input-${label.replace(/\s+/g, "-")}` : undefined); + + const togglePasswordVisibility = () => setShowPassword(!showPassword); + + return ( +
+ {label && inputId && ( + + )} +
+ + {isPassword && ( + + )} +
+
+ ); +} diff --git a/src/components/layout/footer.tsx b/src/components/layout/footer.tsx new file mode 100644 index 0000000..486d041 --- /dev/null +++ b/src/components/layout/footer.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export default function Footer() { + const date = new Date(); + const year = date.getFullYear(); + + return ( +
+

+ Hecho en México. Todos los derechos reservados {year}. +

+

+ Esta página puede ser reproducida con fines no lucrativos, + siempre y cuando no se mutile, se cite la fuente completa y su + dirección electrónica. De otra forma, requiere permiso previo + por escrito de la institución. +

+
+ ); +} diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx new file mode 100644 index 0000000..9289b5f --- /dev/null +++ b/src/components/layout/header.tsx @@ -0,0 +1,32 @@ +import Image from 'next/image'; +import React from 'react'; +import Link from 'next/link'; + +export default function Header({ small = false }: { small?: boolean }) { + return ( +
+
+ + Logo de la UNAM + + + Logo de la fes Acatlàn + +
+
+ ); +} diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx new file mode 100644 index 0000000..18c9c3f --- /dev/null +++ b/src/components/navbar.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import Link from "next/link"; + +const Navbar: React.FC = () => { + return ( + + ); +}; + +export default Navbar; diff --git a/src/components/pagination.tsx b/src/components/pagination.tsx new file mode 100644 index 0000000..19a2ac2 --- /dev/null +++ b/src/components/pagination.tsx @@ -0,0 +1,105 @@ +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 && page !== currentPage) { + onPageChange(page); + } + }; + + const visiblePages = useMemo(() => { + const pages: (number | -1)[] = []; + + if (totalPages <= 5 + delta * 2) { + return Array.from({ length: totalPages }, (_, i) => i + 1); + } + + const left = Math.max(2, currentPage - delta); + const right = Math.min(totalPages - 1, currentPage + delta); + + pages.push(1); + if (left > 2) pages.push(-1); + + for (let i = left; i <= right; i++) { + pages.push(i); + } + + if (right < totalPages - 1) pages.push(-1); + pages.push(totalPages); + + return pages; + }, [currentPage, totalPages, delta]); + + return ( + + ); +} diff --git a/src/components/radio-option-group.tsx b/src/components/radio-option-group.tsx new file mode 100644 index 0000000..cc2bc2c --- /dev/null +++ b/src/components/radio-option-group.tsx @@ -0,0 +1,46 @@ +import React from "react"; + +export interface RadioOption { + label: string; + value: T; + disabled?: boolean; +} + +interface RadioOptionGroupProps { + name: string; + options: RadioOption[]; + selectedValue?: T; + onChange: (value: T) => void; + disabled?: boolean; + className?: string; +} + +export default function RadioOptionGroup({ + name, + options, + selectedValue, + onChange, + disabled = false, + className = "", +}: RadioOptionGroupProps) { + return ( +
+ {options.map((option, index) => ( + + ))} +
+ ); +} diff --git a/src/components/select.tsx b/src/components/select.tsx new file mode 100644 index 0000000..364d43c --- /dev/null +++ b/src/components/select.tsx @@ -0,0 +1,103 @@ +import React, { useId } from 'react'; + +interface Option { + value: string | number; + label: string; +} + +interface SelectProps + extends Omit, 'className'> { + label: string; + className?: { + container?: string; + label?: string; + input?: string; + }; + options: Option[]; + placeholder?: string; +} + +/** + * Componente `Select` reutilizable en React. + * + * Este componente renderiza un elemento ` + * + * @param {Object} props - Propiedades del componente. + * @param {string} props.label - Etiqueta visible asociada al campo. + * @param {Array<{ value: string | number, label: string }>} props.options - Lista de opciones disponibles en el select. + * @param {string} [props.placeholder] - Opción deshabilitada inicial que actúa como guía o título (si se proporciona). + * @param {string} [props.name] - Nombre del campo (útil en formularios). + * @param {string} [props.id] - ID del campo; si no se proporciona, se genera automáticamente. + * @param {string | number} [props.defaultValue] - Valor por defecto del select (modo no controlado). + * @param {string | number} [props.value] - Valor actual del select (modo controlado). + * @param {Object} [props.className] - Objeto para aplicar clases CSS personalizadas. + * @param {string} [props.className.container] - Clases del contenedor principal. + * @param {string} [props.className.label] - Clases de la etiqueta. + * @param {string} [props.className.input] - Clases del ``. + * + * @returns {JSX.Element} Un elemento ` + {placeholder && ( + + )} + {options.map((option) => ( + + ))} + +
+ ); +}; + +export default Select; diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx new file mode 100644 index 0000000..b380e0b --- /dev/null +++ b/src/components/sidebar.tsx @@ -0,0 +1,174 @@ +"use client"; +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import React, { useEffect, useState } from "react"; + +export interface SidebarLink { + icon?: string; + text?: string; + href?: string; + show?: boolean; + className?: string; + onClick?: () => void; + render?: () => React.ReactNode; +} + +interface SidebarProps { + id: string; + userName?: string; + userRole?: string; + logoSrc?: string; + appTitle?: string; + links: SidebarLink[]; +} + +export default function Sidebar({ + userName = "Usuario", + userRole = "Administrador", + logoSrc = "/unam.png", + appTitle = "Sistema de -------", + links, + id, +}: SidebarProps) { + const pathname = usePathname(); + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + const open = localStorage.getItem(id) === "true"; + setIsOpen(open); + }, [id]); + + const toggleSidebar = () => { + localStorage.setItem(id, `${!isOpen}`); + setIsOpen(!isOpen); + }; + + return ( + + ); +} diff --git a/src/components/table.tsx b/src/components/table.tsx new file mode 100644 index 0000000..68c5f31 --- /dev/null +++ b/src/components/table.tsx @@ -0,0 +1,196 @@ +import React, { useState, useMemo } from "react"; +import Pagination from "./pagination"; + +export interface Header { + key: keyof T; + label: string; + render?: (value: T[keyof T], row: T) => React.ReactNode; + width?: string; + className?: string; +} + +interface TableProps { + headers: Header[]; + data: T[]; + rowsPerPage?: number; + onRowClick?: (rowData: T) => void; + rowKey?: (row: T) => string | number; + className?: { + table?: string; + row?: string; + cell?: string; + header?: string; + }; + controlledPage?: number; + onPageChange?: (page: number) => void; +} + +export default function Table({ + headers, + data, + rowsPerPage = 10, + onRowClick, + rowKey, + className, + controlledPage, + onPageChange, +}: TableProps) { + const [sortConfig, setSortConfig] = useState<{ + key: keyof T; + direction: "asc" | "desc"; + } | null>(null); + + const [internalPage, setInternalPage] = useState(1); + const currentPage = controlledPage ?? internalPage; + const setPage = onPageChange ?? setInternalPage; + + const handleSort = (key: keyof T) => { + let direction: "asc" | "desc" = "asc"; + if ( + sortConfig && + sortConfig.key === key && + sortConfig.direction === "asc" + ) { + direction = "desc"; + } + setSortConfig({ key, direction }); + }; + + const sortedData = useMemo(() => { + if (!sortConfig) return data; + return [...data].sort((a, b) => { + if (a[sortConfig.key]! < b[sortConfig.key]!) + return sortConfig.direction === "asc" ? -1 : 1; + if (a[sortConfig.key]! > b[sortConfig.key]!) + return sortConfig.direction === "asc" ? 1 : -1; + return 0; + }); + }, [data, sortConfig]); + + const totalPages = Math.ceil(sortedData.length / rowsPerPage); + const paginatedData = sortedData.slice( + (currentPage - 1) * rowsPerPage, + currentPage * rowsPerPage + ); + + return ( +
+ + + {headers.map((header, index) => ( + + ))} + + + + {headers.map((header, index) => ( + + ))} + + + + {paginatedData.length === 0 ? ( + + + + ) : ( + paginatedData.map((row, rowIndex) => ( + onRowClick?.(row)} + className={`${onRowClick ? "clickable-row" : ""} ${ + className?.row || "" + }`} + > + {headers.map((header, cellIndex) => ( + + ))} + + )) + )} + +
handleSort(header.key)} + tabIndex={0} + aria-sort={ + sortConfig?.key === header.key + ? sortConfig.direction === "asc" + ? "ascending" + : "descending" + : "none" + } + style={{ width: header.width }} + className={`sortable ${className?.header || ""} ${ + header.className || "" + } ${ + sortConfig?.key === header.key + ? `sorted-${sortConfig.direction}` + : "" + }`} + > + {header.label} + {sortConfig?.key === header.key && ( + + )} +
+ No hay datos disponibles +
+ {header.render + ? header.render(row[header.key], row) + : (row[header.key] as React.ReactNode)} +
+ + {totalPages > 1 && ( + + )} +
+ ); +} + +interface TablaSkeletonProps { + rows?: number; + columns?: number; +} + +export function TableSkeleton({ rows = 5, columns = 5 }: TablaSkeletonProps) { + return ( + + + + {Array.from({ length: columns }).map((_, index) => ( + + ))} + + + + {Array.from({ length: rows }).map((_, rowIndex) => ( + + {Array.from({ length: columns }).map((_, colIndex) => ( + + ))} + + ))} + +
+ +
+ +
+ ); +} diff --git a/src/components/tabs.tsx b/src/components/tabs.tsx new file mode 100644 index 0000000..2be6821 --- /dev/null +++ b/src/components/tabs.tsx @@ -0,0 +1,74 @@ +import React, { useState, JSX } from "react"; + +interface Tab { + id: string; + title: string | JSX.Element; + content: JSX.Element; + disabled?: boolean; +} + +interface TabsProps { + tabs: Tab[]; + style?: "default" | "box"; + className?: { + container?: string; + tab?: string; + content?: string; + }; +} + +export default function Tabs({ + tabs, + style = "default", + className, +}: TabsProps) { + const [activeTab, setActiveTab] = useState(tabs[0]?.id || ""); + + return ( + <> +
    + {tabs.map((tab) => ( +
  • + +
  • + ))} +
+
+ {tabs.map((tab) => ( +
+ {activeTab === tab.id && tab.content} +
+ ))} +
+ + ); +} diff --git a/src/styles/sass/_extends.scss b/src/styles/sass/_extends.scss new file mode 100644 index 0000000..e326d1a --- /dev/null +++ b/src/styles/sass/_extends.scss @@ -0,0 +1,73 @@ +.box { + @extend .shadow-sm; + @extend .rounded; + @extend .bg-white; + @extend .p-3; + + &.active { + @extend .bg-primary; + @extend .bg-opacity-75; + @extend .text-white; + } + + &.disabled { + @extend .text-muted; + cursor: not-allowed !important; + } +} + +.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 new file mode 100644 index 0000000..b6199d6 --- /dev/null +++ b/src/styles/sass/_gradients.scss @@ -0,0 +1,26 @@ +// Este bloque crea una clase por cada color del tema, en cada dirección. +@each $color-name, $color-value in $theme-colors { + .bg-gradient-#{$color-name}-top { + background-image: linear-gradient( + to top, + rgba($color-value, 0.5), + $body-bg + ); + } + + .bg-gradient-#{$color-name}-bottom { + background-image: linear-gradient( + to bottom, + rgba($color-value, 0.5), + $body-bg + ); + } + + .bg-gradient-#{$color-name}-left { + background-image: linear-gradient(to left, rgba($color-value, 0.5), $body-bg); + } + + .bg-gradient-#{$color-name}-right { + background-image: linear-gradient(to right, rgba($color-value, 0.5), $body-bg); + } +} diff --git a/src/styles/sass/_hover.scss b/src/styles/sass/_hover.scss new file mode 100644 index 0000000..5f47014 --- /dev/null +++ b/src/styles/sass/_hover.scss @@ -0,0 +1,15 @@ +// Clases personalizadas de hover por color +@each $name, $color in $theme-colors { + .hover-bg-#{$name} { + transition: background-color 0.4s; + + &:hover, + &.active { + background-color: rgba($color, 0.75) !important; + .nav-link { + color: #ffffff !important; + } + } + } + } + \ No newline at end of file diff --git a/src/styles/sass/_sidebar.scss b/src/styles/sass/_sidebar.scss new file mode 100644 index 0000000..fa3de02 --- /dev/null +++ b/src/styles/sass/_sidebar.scss @@ -0,0 +1,90 @@ +.sidebar { + @extend .py-0; + @extend .box; + @extend .m-lg-2; + + .nav-item { + @extend .rounded-pill; + @extend .w-100; + transition: background-color 0.4s; + + // Hover por defecto (solo si no tiene clase hover-bg-*) + &:not([class*="hover-bg-"]):hover, + &:not([class*="hover-bg-"]).active { + @extend .bg-primary; + @extend .bg-opacity-75; + + .nav-link { + color: #ffffff !important; + } + } + + .nav-link { + i { + @extend .me-2; + } + } + } + + @include media-breakpoint-up(lg) { + @extend .py-lg-2; + position: sticky; + top: 0; + left: 0; + width: clamp(250px, 100%, 300px); + max-width: 300px; + transition: width 0.3s ease; + + .offcanvas-body { + display: block; + } + + &.open { + .nav-link { + overflow: hidden; + white-space: nowrap; + + span { + transition: opacity 0.3s, visibility 0.3s; + opacity: 1; + visibility: visible; + } + } + } + + &.close { + width: 80px; + + .nav-item:hover { + position: relative; + + &::after { + content: attr(data-tooltip); + position: absolute; + top: 50%; + left: 100%; + transform: translate(10px, -50%); + background-color: rgba(0, 0, 0, 0.7); + color: white; + padding: 5px 10px; + border-radius: 5px; + opacity: 0; + animation: fadeInOut 4s ease-in-out; + font-style: normal; + max-width: 200px; + } + } + + .nav-link { + overflow: hidden; + white-space: nowrap; + + span { + transition: opacity 0.3s, visibility 0.3s; + opacity: 0; + visibility: hidden; + } + } + } + } +} \ No newline at end of file diff --git a/src/styles/sass/_table.scss b/src/styles/sass/_table.scss new file mode 100644 index 0000000..29788e0 --- /dev/null +++ b/src/styles/sass/_table.scss @@ -0,0 +1,87 @@ +:root { + --bg-header-table: #eee9e9; + --border-row-table: #cecece; + --hover-background-color: #f0f0f0; + } + + .e-table { + width: 100%; + border-collapse: separate; + border-spacing: 0 10px; + + thead { + tr { + box-shadow: var(--bs-box-shadow-sm); + th { + background-color: var(--bg-header-table); + position: sticky; + top: 5px; + } + } + } + + th, + td { + padding: 12px 15px; + background-color: #ffffff; + transition: background-color 0.5s; + + &:first-child { + border-radius: 8px 0 0 8px; + } + + &:last-child { + border-radius: 0 8px 8px 0; + } + } + + tbody { + tr { + border-radius: 8px; + overflow: hidden; + box-shadow: var(--bs-box-shadow-sm); + + &.clickable-row { + cursor: pointer; + &:hover { + td { + background-color: var(--hover-background-color); + } + } + } + } + } + + @media (max-width: 768px) { + thead { + display: none; + } + + tbody { + tr { + display: flex; + flex-direction: column; + margin: 0 0 10px 10px; + border-radius: 8px; + + th, + td, + th:first-child, + td:first-child, + th:last-child, + td:last-child { + border-radius: 0; + border: 0; + } + + td[data-titulo]::before { + content: attr(data-titulo) ':'; + margin-right: 15px; + font-weight: 600; + color: #3e3e3e; + } + } + } + } + } + \ No newline at end of file diff --git a/src/styles/sass/_tabs.scss b/src/styles/sass/_tabs.scss new file mode 100644 index 0000000..b864ca3 --- /dev/null +++ b/src/styles/sass/_tabs.scss @@ -0,0 +1,16 @@ +.tab-pane.fade { + transition: opacity 10s linear !important; +} + +.nav-tabs.style-box { + border-bottom: 0cap; + gap: 0.8rem; + .nav-item .nav-link { + @extend .box; + @extend .py-2; + @extend .text-black; + + margin: 0 !important; + border: 0; + } +} diff --git a/src/styles/sass/_utilities.scss b/src/styles/sass/_utilities.scss new file mode 100644 index 0000000..47e6a4d --- /dev/null +++ b/src/styles/sass/_utilities.scss @@ -0,0 +1,19 @@ +// Archivo: _utilities.scss + +$hover-scales: ( + 1: 1.05, + 2: 1.1, + 3: 1.15, + 4: 1.2, + 5: 1.25 +); + +@each $key, $value in $hover-scales { + .scale-hover-#{$key} { + transition: transform 0.3s ease-in-out; + + &:hover { + transform: scale(#{$value}); + } + } +} diff --git a/src/styles/sass/_variables.scss b/src/styles/sass/_variables.scss new file mode 100644 index 0000000..c8acabc --- /dev/null +++ b/src/styles/sass/_variables.scss @@ -0,0 +1,8 @@ +/* Variables sobreescritas de bootstrap */ +$body-bg: #f5f5f7; +$placeholder-opacity-max: 0.2; +$placeholder-opacity-min: 0.1; + +$modal-content-border-color: none; +$modal-content-bg: #f5f5f7; +$modal-lg: 900px; diff --git a/src/styles/sass/bootstrap.scss b/src/styles/sass/bootstrap.scss new file mode 100644 index 0000000..2da8cbe --- /dev/null +++ b/src/styles/sass/bootstrap.scss @@ -0,0 +1,62 @@ +@import "bootstrap/scss/functions"; + +@import "variables"; + +@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, +); + +$theme-colors: map-merge($theme-colors, $custom-colors); + +@import "bootstrap/scss/maps"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/bootstrap"; + +@import "gradients"; +@import "extends"; +@import "table"; +@import "utilities"; +@import 'sidebar'; +@import 'hover'; +@import 'tabs'; + +//Add additional custom code here + +@include media-breakpoint-up(lg) { + .bd-layout { + display: grid; + grid-template-areas: "sidebar main"; + grid-template-columns: 1fr 5fr; + gap: $spacer * 1.5; + } +} + +@include media-breakpoint-up(lg) { + .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; + } + } +} +