diff --git a/diplomas/package-lock.json b/diplomas/package-lock.json index d81d08f..0b84187 100644 --- a/diplomas/package-lock.json +++ b/diplomas/package-lock.json @@ -8,11 +8,13 @@ "name": "diplomas", "version": "0.1.0", "dependencies": { - "@radix-ui/react-dialog": "^1.1.7", + "@radix-ui/react-dialog": "^1.1.11", "@radix-ui/react-label": "^2.1.4", "@radix-ui/react-select": "^2.1.7", + "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.9", + "@radix-ui/react-tooltip": "^1.2.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "diplomas": "file:", @@ -941,22 +943,22 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.7.tgz", - "integrity": "sha512-EIdma8C0C/I6kL6sO02avaCRqi3fmWJpxH6mqbVScorW6nNktzKJT/le7VPho3o/7wCsyRg3z0+Q+Obr0Gy/VQ==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.11.tgz", + "integrity": "sha512-yI7S1ipkP5/+99qhSI6nthfo/tR6bL6Zgxi/+1UO6qPa6UeM6nlafWcQ65vB4rU2XjgjMfMhI3k9Y5MztA62VQ==", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.6", + "@radix-ui/react-dismissable-layer": "1.1.7", "@radix-ui/react-focus-guards": "1.1.2", - "@radix-ui/react-focus-scope": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.4", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.5", - "@radix-ui/react-presence": "1.1.3", - "@radix-ui/react-primitive": "2.0.3", + "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-slot": "1.2.0", - "@radix-ui/react-use-controllable-state": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, @@ -975,6 +977,119 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.7.tgz", + "integrity": "sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.4.tgz", + "integrity": "sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.6.tgz", + "integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1169,9 +1284,9 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.3.tgz", - "integrity": "sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1350,6 +1465,50 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.4.tgz", + "integrity": "sha512-2fTm6PSiUm8YPq9W0E4reYuv01EE3aFSzt8edBiXqPHshF8N9+Kymt/k0/R+F3dkY5lQyB/zPtrP82phskLi7w==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", @@ -1396,29 +1555,6 @@ } } }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", - "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", @@ -1459,6 +1595,203 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.4.tgz", + "integrity": "sha512-DyW8VVeeMSSLFvAmnVnCwvI3H+1tpJFHT50r+tdOoMse9XqYDBCcyux8u3G2y+LOpt7fPQ6KKH0mhs+ce1+Z5w==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.4", + "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.4.tgz", + "integrity": "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.7.tgz", + "integrity": "sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.4.tgz", + "integrity": "sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.6.tgz", + "integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.0.tgz", + "integrity": "sha512-rQj0aAWOpCdCMRbI6pLQm8r7S2BM3YhTa0SzOYD55k+hJA8oo9J+H+9wLM9oMlZWOX/wJWPTzfDfmZkf7LvCfg==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", diff --git a/diplomas/package.json b/diplomas/package.json index 10e3bdf..3784ab0 100644 --- a/diplomas/package.json +++ b/diplomas/package.json @@ -9,11 +9,13 @@ "lint": "next lint" }, "dependencies": { - "@radix-ui/react-dialog": "^1.1.7", + "@radix-ui/react-dialog": "^1.1.11", "@radix-ui/react-label": "^2.1.4", "@radix-ui/react-select": "^2.1.7", + "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.9", + "@radix-ui/react-tooltip": "^1.2.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "diplomas": "file:", diff --git a/diplomas/src/components/app-sidebar.jsx b/diplomas/src/components/app-sidebar.jsx new file mode 100644 index 0000000..a94a5df --- /dev/null +++ b/diplomas/src/components/app-sidebar.jsx @@ -0,0 +1,106 @@ +import * as React from "react"; +import { GalleryVerticalEnd } from "lucide-react"; + +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, + SidebarRail, +} from "@/components/ui/sidebar"; + +// This is sample data. +const data = { + navMain: [ + { + title: "Alumnos", + url: "/", + items: [ + { + title: "Agregar manualmente", + url: "/Alumnos", + }, + { + title: "Agregar desde archivo", + url: "#", + }, + ], + }, + { + title: "Cursos", + url: "#", + items: [ + { + title: "Agregar curso manualmente", + url: "/Cursos", + }, + { + title: "Agregar desde archivo", + url: "#", + isActive: true, + }, + ], + }, + ], +}; + +export function AppSidebar({ ...props }) { + return ( + <Sidebar {...props}> + <SidebarHeader> + <SidebarMenu> + <SidebarMenuItem> + <SidebarMenuButton size="lg" asChild> + <div className="flex"> + <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"> + <GalleryVerticalEnd className="size-4" /> + </div> + <div className="flex flex-col gap-0.5 leading-none"> + <span className="font-medium">SIDAC</span> + <span className="">v1.0.0</span> + </div> + </div> + </SidebarMenuButton> + </SidebarMenuItem> + </SidebarMenu> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarMenu> + {data.navMain.map((item) => ( + <SidebarMenuItem key={item.title}> + <SidebarMenuButton asChild> + <a href={item.url} className="font-medium"> + {item.title} + </a> + </SidebarMenuButton> + {item.items?.length ? ( + <SidebarMenuSub> + {item.items.map((item) => ( + <SidebarMenuSubItem key={item.title}> + <SidebarMenuSubButton + asChild + isActive={item.isActive} + className="py-5" + > + <a href={item.url}>{item.title}</a> + </SidebarMenuSubButton> + </SidebarMenuSubItem> + ))} + </SidebarMenuSub> + ) : null} + </SidebarMenuItem> + ))} + </SidebarMenu> + </SidebarGroup> + </SidebarContent> + <SidebarRail /> + </Sidebar> + ); +} diff --git a/diplomas/src/components/formularios/CursosManual.jsx b/diplomas/src/components/formularios/CursosManual.jsx index 737bc19..5c34fd9 100644 --- a/diplomas/src/components/formularios/CursosManual.jsx +++ b/diplomas/src/components/formularios/CursosManual.jsx @@ -1,6 +1,9 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; const CursosManual = () => { + const [nombre, setNombre] = useState(""); + const [descripcion, setDescripcion] = useState(""); + const [competencia, setCompetencia] = useState(""); const manejarGuardar = () => { // Lógica para guardar el curso console.log({ nombre, descripcion, competencia }); @@ -8,40 +11,37 @@ const CursosManual = () => { return ( <div className="p-8 font-sans text-center"> - - - - <div className="max-w-md mx-auto bg-white p-6 rounded-md shadow"> - <h2 className="text-xl font-semibold mb-4">Nuevo curso</h2> - <input - type="text" - placeholder="Nombre del curso" - value={nombre} - onChange={(e) => setNombre(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3" - /> - <textarea - placeholder="Descripción" - value={descripcion} - onChange={(e) => setDescripcion(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24" - /> - <select - value={competencia} - onChange={(e) => setCompetencia(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md mb-4" - > - <option value="">Competencia</option> - <option value="competencia1">Competencia 1</option> - <option value="competencia2">Competencia 2</option> - </select> - <button - onClick={manejarGuardar} - className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md" - > - Guardar - </button> - </div> + <div className="max-w-md mx-auto bg-white p-6 rounded-md shadow"> + <h2 className="text-xl font-semibold mb-4">Nuevo curso</h2> + <input + type="text" + placeholder="Nombre del curso" + value={nombre} + onChange={(e) => setNombre(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3" + /> + <textarea + placeholder="Descripción" + value={descripcion} + onChange={(e) => setDescripcion(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24" + /> + <select + value={competencia} + onChange={(e) => setCompetencia(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-4" + > + <option value="">Competencia</option> + <option value="competencia1">Competencia 1</option> + <option value="competencia2">Competencia 2</option> + </select> + <button + onClick={manejarGuardar} + className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md" + > + Guardar + </button> + </div> </div> ); }; diff --git a/diplomas/src/components/layout/Layout.jsx b/diplomas/src/components/layout/Layout.jsx new file mode 100644 index 0000000..50545d2 --- /dev/null +++ b/diplomas/src/components/layout/Layout.jsx @@ -0,0 +1,17 @@ +"use client"; + +import { AppSidebar } from "@/components/app-sidebar"; +import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; + +export default function Layout({ children }) { + return ( + <SidebarProvider> + <div className="flex"> + <AppSidebar /> + <SidebarInset> + <div className="p-4 w-full">{children}</div> + </SidebarInset> + </div> + </SidebarProvider> + ); +} diff --git a/diplomas/src/components/ui/breadcrumb.jsx b/diplomas/src/components/ui/breadcrumb.jsx new file mode 100644 index 0000000..bbc0d92 --- /dev/null +++ b/diplomas/src/components/ui/breadcrumb.jsx @@ -0,0 +1,112 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Breadcrumb({ + ...props +}) { + return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />; +} + +function BreadcrumbList({ + className, + ...props +}) { + return ( + (<ol + data-slot="breadcrumb-list" + className={cn( + "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5", + className + )} + {...props} />) + ); +} + +function BreadcrumbItem({ + className, + ...props +}) { + return ( + (<li + data-slot="breadcrumb-item" + className={cn("inline-flex items-center gap-1.5", className)} + {...props} />) + ); +} + +function BreadcrumbLink({ + asChild, + className, + ...props +}) { + const Comp = asChild ? Slot : "a" + + return ( + (<Comp + data-slot="breadcrumb-link" + className={cn("hover:text-foreground transition-colors", className)} + {...props} />) + ); +} + +function BreadcrumbPage({ + className, + ...props +}) { + return ( + (<span + data-slot="breadcrumb-page" + role="link" + aria-disabled="true" + aria-current="page" + className={cn("text-foreground font-normal", className)} + {...props} />) + ); +} + +function BreadcrumbSeparator({ + children, + className, + ...props +}) { + return ( + (<li + data-slot="breadcrumb-separator" + role="presentation" + aria-hidden="true" + className={cn("[&>svg]:size-3.5", className)} + {...props}> + {children ?? <ChevronRight />} + </li>) + ); +} + +function BreadcrumbEllipsis({ + className, + ...props +}) { + return ( + (<span + data-slot="breadcrumb-ellipsis" + role="presentation" + aria-hidden="true" + className={cn("flex size-9 items-center justify-center", className)} + {...props}> + <MoreHorizontal className="size-4" /> + <span className="sr-only">More</span> + </span>) + ); +} + +export { + Breadcrumb, + BreadcrumbList, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbPage, + BreadcrumbSeparator, + BreadcrumbEllipsis, +} diff --git a/diplomas/src/components/ui/separator.jsx b/diplomas/src/components/ui/separator.jsx new file mode 100644 index 0000000..e82558c --- /dev/null +++ b/diplomas/src/components/ui/separator.jsx @@ -0,0 +1,27 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}) { + return ( + (<SeparatorPrimitive.Root + data-slot="separator-root" + decorative={decorative} + orientation={orientation} + className={cn( + "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", + className + )} + {...props} />) + ); +} + +export { Separator } diff --git a/diplomas/src/components/ui/sheet.jsx b/diplomas/src/components/ui/sheet.jsx new file mode 100644 index 0000000..faf27a9 --- /dev/null +++ b/diplomas/src/components/ui/sheet.jsx @@ -0,0 +1,138 @@ +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Sheet({ + ...props +}) { + return <SheetPrimitive.Root data-slot="sheet" {...props} />; +} + +function SheetTrigger({ + ...props +}) { + return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />; +} + +function SheetClose({ + ...props +}) { + return <SheetPrimitive.Close data-slot="sheet-close" {...props} />; +} + +function SheetPortal({ + ...props +}) { + return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />; +} + +function SheetOverlay({ + className, + ...props +}) { + return ( + (<SheetPrimitive.Overlay + data-slot="sheet-overlay" + className={cn( + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", + className + )} + {...props} />) + ); +} + +function SheetContent({ + className, + children, + side = "right", + ...props +}) { + return ( + (<SheetPortal> + <SheetOverlay /> + <SheetPrimitive.Content + data-slot="sheet-content" + className={cn( + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + side === "right" && + "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", + side === "left" && + "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", + side === "top" && + "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b", + side === "bottom" && + "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t", + className + )} + {...props}> + {children} + <SheetPrimitive.Close + className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"> + <XIcon className="size-4" /> + <span className="sr-only">Close</span> + </SheetPrimitive.Close> + </SheetPrimitive.Content> + </SheetPortal>) + ); +} + +function SheetHeader({ + className, + ...props +}) { + return ( + (<div + data-slot="sheet-header" + className={cn("flex flex-col gap-1.5 p-4", className)} + {...props} />) + ); +} + +function SheetFooter({ + className, + ...props +}) { + return ( + (<div + data-slot="sheet-footer" + className={cn("mt-auto flex flex-col gap-2 p-4", className)} + {...props} />) + ); +} + +function SheetTitle({ + className, + ...props +}) { + return ( + (<SheetPrimitive.Title + data-slot="sheet-title" + className={cn("text-foreground font-semibold", className)} + {...props} />) + ); +} + +function SheetDescription({ + className, + ...props +}) { + return ( + (<SheetPrimitive.Description + data-slot="sheet-description" + className={cn("text-muted-foreground text-sm", className)} + {...props} />) + ); +} + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/diplomas/src/components/ui/sidebar.jsx b/diplomas/src/components/ui/sidebar.jsx new file mode 100644 index 0000000..4192b68 --- /dev/null +++ b/diplomas/src/components/ui/sidebar.jsx @@ -0,0 +1,631 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva } from "class-variance-authority"; +import { PanelLeftIcon } from "lucide-react"; + +import { useIsMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +const SIDEBAR_COOKIE_NAME = "sidebar_state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "18rem"; +const SIDEBAR_WIDTH_ICON = "3rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; + +const SidebarContext = React.createContext(null); + +function useSidebar() { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider."); + } + + return context; +} + +function SidebarProvider({ + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props +}) { + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; + const setOpen = React.useCallback( + (value) => { + const openState = typeof value === "function" ? value(open) : value; + if (setOpenProp) { + setOpenProp(openState); + } else { + _setOpen(openState); + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + }, + [setOpenProp, open] + ); + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault(); + toggleSidebar(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed"; + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] + ); + + return ( + <SidebarContext.Provider value={contextValue}> + <TooltipProvider delayDuration={0}> + <div + data-slot="sidebar-wrapper" + style={{ + "--sidebar-width": SIDEBAR_WIDTH, + "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, + ...style, + }} + className={cn( + "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", + className + )} + {...props} + > + {children} + </div> + </TooltipProvider> + </SidebarContext.Provider> + ); +} + +function Sidebar({ + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props +}) { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); + + if (collapsible === "none") { + return ( + <div + data-slot="sidebar" + className={cn( + "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", + className + )} + {...props} + > + {children} + </div> + ); + } + + if (isMobile) { + return ( + <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> + <SheetContent + data-sidebar="sidebar" + data-slot="sidebar" + data-mobile="true" + className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden" + style={{ + "--sidebar-width": SIDEBAR_WIDTH_MOBILE, + }} + side={side} + > + <SheetHeader className="sr-only"> + <SheetTitle>Sidebar</SheetTitle> + <SheetDescription>Displays the mobile sidebar.</SheetDescription> + </SheetHeader> + <div className="flex h-full w-full flex-col">{children}</div> + </SheetContent> + </Sheet> + ); + } + + return ( + <div + className="group peer text-sidebar-foreground hidden md:block" + data-state={state} + data-collapsible={state === "collapsed" ? collapsible : ""} + data-variant={variant} + data-side={side} + data-slot="sidebar" + > + {/* This is what handles the sidebar gap on desktop */} + <div + data-slot="sidebar-gap" + className={cn( + "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", + "group-data-[collapsible=offcanvas]:w-0", + "group-data-[side=right]:rotate-180", + variant === "floating" || variant === "inset" + ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" + : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)" + )} + /> + <div + data-slot="sidebar-container" + className={cn( + "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", + side === "left" + ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" + : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", + // Adjust the padding for floating and inset variants. + variant === "floating" || variant === "inset" + ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" + : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", + className + )} + {...props} + > + <div + data-sidebar="sidebar" + data-slot="sidebar-inner" + className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm" + > + {children} + </div> + </div> + </div> + ); +} + +function SidebarTrigger({ className, onClick, ...props }) { + const { toggleSidebar } = useSidebar(); + + return ( + <Button + data-sidebar="trigger" + data-slot="sidebar-trigger" + variant="ghost" + size="icon" + className={cn("size-7", className)} + onClick={(event) => { + onClick?.(event); + toggleSidebar(); + }} + {...props} + > + <PanelLeftIcon /> + <span className="sr-only">Toggle Sidebar</span> + </Button> + ); +} + +function SidebarInset({ className, ...props }) { + return ( + <main + data-slot="sidebar-inset" + className={cn( + "bg-background relative flex w-full flex-1 flex-col", + "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2", + className + )} + {...props} + /> + ); +} + +function SidebarInput({ className, ...props }) { + return ( + <Input + data-slot="sidebar-input" + data-sidebar="input" + className={cn("bg-background h-8 w-full shadow-none", className)} + {...props} + /> + ); +} + +function SidebarHeader({ className, ...props }) { + return ( + <div + data-slot="sidebar-header" + data-sidebar="header" + className={cn("flex flex-col gap-2 p-2", className)} + {...props} + /> + ); +} + +function SidebarFooter({ className, ...props }) { + return ( + <div + data-slot="sidebar-footer" + data-sidebar="footer" + className={cn("flex flex-col gap-2 p-2", className)} + {...props} + /> + ); +} + +function SidebarSeparator({ className, ...props }) { + return ( + <Separator + data-slot="sidebar-separator" + data-sidebar="separator" + className={cn("bg-sidebar-border mx-2 w-auto", className)} + {...props} + /> + ); +} + +function SidebarContent({ className, ...props }) { + return ( + <div + data-slot="sidebar-content" + data-sidebar="content" + className={cn( + "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", + className + )} + {...props} + /> + ); +} + +function SidebarGroup({ className, ...props }) { + return ( + <div + data-slot="sidebar-group" + data-sidebar="group" + className={cn("relative flex w-full min-w-0 flex-col p-2", className)} + {...props} + /> + ); +} + +function SidebarGroupLabel({ className, asChild = false, ...props }) { + const Comp = asChild ? Slot : "div"; + + return ( + <Comp + data-slot="sidebar-group-label" + data-sidebar="group-label" + className={cn( + "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", + "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", + className + )} + {...props} + /> + ); +} + +function SidebarGroupAction({ className, asChild = false, ...props }) { + const Comp = asChild ? Slot : "button"; + + return ( + <Comp + data-slot="sidebar-group-action" + data-sidebar="group-action" + className={cn( + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", + // Increases the hit area of the button on mobile. + "after:absolute after:-inset-2 md:after:hidden", + "group-data-[collapsible=icon]:hidden", + className + )} + {...props} + /> + ); +} + +function SidebarGroupContent({ className, ...props }) { + return ( + <div + data-slot="sidebar-group-content" + data-sidebar="group-content" + className={cn("w-full text-sm", className)} + {...props} + /> + ); +} + +function SidebarMenu({ className, ...props }) { + return ( + <ul + data-slot="sidebar-menu" + data-sidebar="menu" + className={cn("flex w-full min-w-0 flex-col gap-1", className)} + {...props} + /> + ); +} + +function SidebarMenuItem({ className, ...props }) { + return ( + <li + data-slot="sidebar-menu-item" + data-sidebar="menu-item" + className={cn("group/menu-item relative", className)} + {...props} + /> + ); +} + +const sidebarMenuButtonVariants = cva( + "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", + { + variants: { + variant: { + default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", + outline: + "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", + }, + size: { + default: "h-8 text-sm", + sm: "h-7 text-xs", + lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +function SidebarMenuButton({ + asChild = false, + isActive = false, + variant = "default", + size = "default", + tooltip, + className, + ...props +}) { + const Comp = asChild ? Slot : "button"; + const { isMobile, state } = useSidebar(); + + const button = ( + <Comp + data-slot="sidebar-menu-button" + data-sidebar="menu-button" + data-size={size} + data-active={isActive} + className={cn(sidebarMenuButtonVariants({ variant, size }), className)} + {...props} + /> + ); + + if (!tooltip) { + return button; + } + + if (typeof tooltip === "string") { + tooltip = { + children: tooltip, + }; + } + + return ( + <Tooltip> + <TooltipTrigger asChild>{button}</TooltipTrigger> + <TooltipContent + side="right" + align="center" + hidden={state !== "collapsed" || isMobile} + {...tooltip} + /> + </Tooltip> + ); +} + +function SidebarMenuAction({ + className, + asChild = false, + showOnHover = false, + ...props +}) { + const Comp = asChild ? Slot : "button"; + + return ( + <Comp + data-slot="sidebar-menu-action" + data-sidebar="menu-action" + className={cn( + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", + // Increases the hit area of the button on mobile. + "after:absolute after:-inset-2 md:after:hidden", + "peer-data-[size=sm]/menu-button:top-1", + "peer-data-[size=default]/menu-button:top-1.5", + "peer-data-[size=lg]/menu-button:top-2.5", + "group-data-[collapsible=icon]:hidden", + showOnHover && + "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", + className + )} + {...props} + /> + ); +} + +function SidebarMenuBadge({ className, ...props }) { + return ( + <div + data-slot="sidebar-menu-badge" + data-sidebar="menu-badge" + className={cn( + "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none", + "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", + "peer-data-[size=sm]/menu-button:top-1", + "peer-data-[size=default]/menu-button:top-1.5", + "peer-data-[size=lg]/menu-button:top-2.5", + "group-data-[collapsible=icon]:hidden", + className + )} + {...props} + /> + ); +} + +function SidebarMenuSkeleton({ className, showIcon = false, ...props }) { + // Random width between 50 to 90%. + const width = React.useMemo(() => { + return `${Math.floor(Math.random() * 40) + 50}%`; + }, []); + + return ( + <div + data-slot="sidebar-menu-skeleton" + data-sidebar="menu-skeleton" + className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} + {...props} + > + {showIcon && ( + <Skeleton + className="size-4 rounded-md" + data-sidebar="menu-skeleton-icon" + /> + )} + <Skeleton + className="h-4 max-w-(--skeleton-width) flex-1" + data-sidebar="menu-skeleton-text" + style={{ + "--skeleton-width": width, + }} + /> + </div> + ); +} + +function SidebarMenuSub({ className, ...props }) { + return ( + <ul + data-slot="sidebar-menu-sub" + data-sidebar="menu-sub" + className={cn( + "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5", + "group-data-[collapsible=icon]:hidden", + className + )} + {...props} + /> + ); +} + +function SidebarMenuSubItem({ className, ...props }) { + return ( + <li + data-slot="sidebar-menu-sub-item" + data-sidebar="menu-sub-item" + className={cn("group/menu-sub-item relative", className)} + {...props} + /> + ); +} + +function SidebarMenuSubButton({ + asChild = false, + size = "md", + isActive = false, + className, + ...props +}) { + const Comp = asChild ? Slot : "a"; + + return ( + <Comp + data-slot="sidebar-menu-sub-button" + data-sidebar="menu-sub-button" + data-size={size} + data-active={isActive} + className={cn( + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", + "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", + size === "sm" && "text-xs", + size === "md" && "text-sm", + "group-data-[collapsible=icon]:hidden", + className + )} + {...props} + /> + ); +} + +export { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupAction, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarInput, + SidebarInset, + SidebarMenu, + SidebarMenuAction, + SidebarMenuBadge, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSkeleton, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, + SidebarProvider, + SidebarRail, + SidebarSeparator, + SidebarTrigger, + useSidebar, +}; diff --git a/diplomas/src/components/ui/skeleton.jsx b/diplomas/src/components/ui/skeleton.jsx new file mode 100644 index 0000000..22ee9b1 --- /dev/null +++ b/diplomas/src/components/ui/skeleton.jsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}) { + return ( + (<div + data-slot="skeleton" + className={cn("bg-accent animate-pulse rounded-md", className)} + {...props} />) + ); +} + +export { Skeleton } diff --git a/diplomas/src/components/ui/textarea.jsx b/diplomas/src/components/ui/textarea.jsx new file mode 100644 index 0000000..a513f82 --- /dev/null +++ b/diplomas/src/components/ui/textarea.jsx @@ -0,0 +1,20 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Textarea({ + className, + ...props +}) { + return ( + (<textarea + data-slot="textarea" + className={cn( + "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + className + )} + {...props} />) + ); +} + +export { Textarea } diff --git a/diplomas/src/components/ui/tooltip.jsx b/diplomas/src/components/ui/tooltip.jsx new file mode 100644 index 0000000..b597041 --- /dev/null +++ b/diplomas/src/components/ui/tooltip.jsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +function TooltipProvider({ + delayDuration = 0, + ...props +}) { + return (<TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />); +} + +function Tooltip({ + ...props +}) { + return ( + (<TooltipProvider> + <TooltipPrimitive.Root data-slot="tooltip" {...props} /> + </TooltipProvider>) + ); +} + +function TooltipTrigger({ + ...props +}) { + return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />; +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}) { + return ( + (<TooltipPrimitive.Portal> + <TooltipPrimitive.Content + data-slot="tooltip-content" + sideOffset={sideOffset} + className={cn( + "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", + className + )} + {...props}> + {children} + <TooltipPrimitive.Arrow + className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" /> + </TooltipPrimitive.Content> + </TooltipPrimitive.Portal>) + ); +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/diplomas/src/hooks/use-mobile.js b/diplomas/src/hooks/use-mobile.js new file mode 100644 index 0000000..9c47a0d --- /dev/null +++ b/diplomas/src/hooks/use-mobile.js @@ -0,0 +1,19 @@ +import * as React from "react" + +const MOBILE_BREAKPOINT = 768 + +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState(undefined) + + React.useEffect(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) + const onChange = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + } + mql.addEventListener("change", onChange) + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + return () => mql.removeEventListener("change", onChange); + }, []) + + return !!isMobile +} diff --git a/diplomas/src/pages/Alumnos.jsx b/diplomas/src/pages/Alumnos.jsx index e100973..48bf066 100644 --- a/diplomas/src/pages/Alumnos.jsx +++ b/diplomas/src/pages/Alumnos.jsx @@ -1,80 +1,54 @@ -import React from "react"; -import Link from "next/link"; +import React, { useState } from "react"; +import Layout from "@/components/layout/Layout"; import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -export default function Alumnos() { +const CursosManual = () => { + const [nombre, setNombre] = useState(""); + const [email, setEmail] = useState(""); + const [curso, setCurso] = useState(""); + const manejarGuardar = () => { + console.log({ nombre, email, curso }); + }; + return ( - <div className="flex flex-col items-center justify-center w-full h-screen text-black relative"> - <Link href="/" className="bg-blue-400 py-2 px-5 absolute top-5 left-5"> - Volver - </Link> - <Tabs defaultValue="account" className="w-[800px]"> - <TabsList className="grid w-full grid-cols-2"> - <TabsTrigger value="account" className="text-black"> - Registrar alumno manualmente - </TabsTrigger> - <TabsTrigger value="password" className="text-black"> - Registrar alumno por archivo - </TabsTrigger> - </TabsList> - <TabsContent value="account"> - <Card> - <CardHeader> - <CardTitle>Alumnos</CardTitle> - <CardDescription> - Make changes to your account here. Click save when youre done. - </CardDescription> - </CardHeader> - <CardContent className="space-y-2"> - <div className="space-y-1"> - <Label htmlFor="name">Name</Label> - <Input id="name" defaultValue="Pedro Duarte" /> - </div> - <div className="space-y-1"> - <Label htmlFor="username">Username</Label> - <Input id="username" defaultValue="@peduarte" /> - </div> - </CardContent> - <CardFooter> - <Button>Save changes</Button> - </CardFooter> - </Card> - </TabsContent> - <TabsContent value="password"> - <Card> - <CardHeader> - <CardTitle>Alumnos</CardTitle> - <CardDescription> - Change your password here. After saving, youll be logged out. - </CardDescription> - </CardHeader> - <CardContent className="space-y-2"> - <div className="space-y-1"> - <Label htmlFor="current">Current password</Label> - <Input id="current" type="password" /> - </div> - <div className="space-y-1"> - <Label htmlFor="new">New password</Label> - <Input id="new" type="password" /> - </div> - </CardContent> - <CardFooter> - <Button>Save password</Button> - </CardFooter> - </Card> - </TabsContent> - </Tabs> - </div> + <Layout> + <div className="w-[60vw] flex flex-col items-end justify-center"> + <div className="bg-white p-8 font-sans text-center w-[70%]"> + <h1 className="text-xl font-semibold mb-4 text-black"> + Nuevo alumno + </h1> + <Input + type="text" + placeholder="Nombre" + value={nombre} + onChange={(e) => setNombre(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3" + /> + <Input + type="text" + placeholder="Email" + value={nombre} + onChange={(e) => setEmail(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3" + /> + <Input + type="text" + placeholder="Curso" + value={nombre} + onChange={(e) => setCurso(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3" + /> + <Button + onClick={manejarGuardar} + className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md" + > + Guardar + </Button> + </div> + </div> + </Layout> ); -} +}; + +export default CursosManual; diff --git a/diplomas/src/pages/Cursos.jsx b/diplomas/src/pages/Cursos.jsx index 38e3294..01f4566 100644 --- a/diplomas/src/pages/Cursos.jsx +++ b/diplomas/src/pages/Cursos.jsx @@ -1,80 +1,61 @@ -import React from "react"; -import Link from "next/link"; +import Layout from "@/components/layout/Layout"; import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import React, { useState } from "react"; + +const CursosManual = () => { + const [nombre, setNombre] = useState(""); + const [descripcion, setDescripcion] = useState(""); + const [competencia, setCompetencia] = useState(""); + const manejarGuardar = () => { + console.log({ nombre, descripcion, competencia }); + }; -export default function Cursos() { return ( - <div className="flex flex-col items-center justify-center w-full h-screen text-black relative"> - <Link href="/" className="bg-blue-400 py-2 px-5 absolute top-5 left-5"> - Volver - </Link> - <Tabs defaultValue="account" className="w-[800px]"> - <TabsList className="grid w-full grid-cols-2"> - <TabsTrigger value="account" className="text-black"> - Registrar curso manualmente - </TabsTrigger> - <TabsTrigger value="password" className="text-black"> - Registrar curso por archivo - </TabsTrigger> - </TabsList> - <TabsContent value="account"> - <Card> - <CardHeader> - <CardTitle>Cursos</CardTitle> - <CardDescription> - Make changes to your account here. Click save when youre done. - </CardDescription> - </CardHeader> - <CardContent className="space-y-2"> - <div className="space-y-1"> - <Label htmlFor="name">Name</Label> - <Input id="name" defaultValue="Pedro Duarte" /> - </div> - <div className="space-y-1"> - <Label htmlFor="username">Username</Label> - <Input id="username" defaultValue="@peduarte" /> - </div> - </CardContent> - <CardFooter> - <Button>Save changes</Button> - </CardFooter> - </Card> - </TabsContent> - <TabsContent value="password"> - <Card> - <CardHeader> - <CardTitle>Cursos</CardTitle> - <CardDescription> - Change your password here. After saving, youll be logged out. - </CardDescription> - </CardHeader> - <CardContent className="space-y-2"> - <div className="space-y-1"> - <Label htmlFor="current">Current password</Label> - <Input id="current" type="password" /> - </div> - <div className="space-y-1"> - <Label htmlFor="new">New password</Label> - <Input id="new" type="password" /> - </div> - </CardContent> - <CardFooter> - <Button>Save password</Button> - </CardFooter> - </Card> - </TabsContent> - </Tabs> - </div> + <Layout> + <div className="w-[60vw] flex flex-col items-end justify-center"> + <div className="bg-white p-8 font-sans text-center w-[70%]"> + <h1 className="text-xl font-semibold mb-4 text-black">Nuevo curso</h1> + <Input + type="text" + placeholder="Nombre del curso" + value={nombre} + onChange={(e) => setNombre(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3" + /> + <Textarea + placeholder="Descripción" + value={descripcion} + onChange={(e) => setDescripcion(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24" + /> + <Select onValueChange={(value) => setCompetencia(value)}> + <SelectTrigger className="w-full px-3 py-2 border border-gray-300 rounded-md mb-4"> + <SelectValue placeholder="Competencia" /> + </SelectTrigger> + <SelectContent> + <SelectItem value="competencia1">Competencia 1</SelectItem> + <SelectItem value="competencia2">Competencia 2</SelectItem> + </SelectContent> + </Select> + <Button + onClick={manejarGuardar} + className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md" + > + Guardar + </Button> + </div> + </div> + </Layout> ); -} +}; + +export default CursosManual; diff --git a/diplomas/src/pages/index.js b/diplomas/src/pages/index.js index b7db1e3..3866205 100644 --- a/diplomas/src/pages/index.js +++ b/diplomas/src/pages/index.js @@ -1,37 +1,12 @@ -import Link from "next/link"; +import Layout from "@/components/layout/Layout"; export default function Home() { return ( - <div className="flex flex-col items-center justify-center w-full h-screen text-black"> - <h1 className="text-3xl font-bold mb-4">¿Qué quieres hacer?</h1> - <div className="flex"> - <Link href="/Alumnos"> - <div className="flex flex-col items-center"> - <div - style={{ - backgroundImage: "url('/alumnos.jpg')", - backgroundRepeat: "no-repeat", - backgroundSize: "cover", - }} - className="h-60 w-96 border rounded-3 m-5" - ></div> - <h1>Dar de Alumnos</h1> - </div> - </Link> - <Link href="/Cursos"> - <div className="flex flex-col items-center"> - <div - style={{ - backgroundImage: "url('/cursos.jpg')", - backgroundRepeat: "no-repeat", - backgroundSize: "cover", - }} - className="h-60 w-96 border rounded-3 m-5" - ></div> - <h1>Dar de Cursos</h1> - </div> - </Link> + <Layout> + <div className="flex flex-col items-center justify-center min-h-screen py-10"> + <h1 className="text-3xl font-bold mb-5">Productos juguetería</h1> + <div className="flex flex-wrap p-5">Hola</div> </div> - </div> + </Layout> ); }