Compare commits
8 Commits
0069775ed4
...
d0e095c979
| Author | SHA1 | Date | |
|---|---|---|---|
| d0e095c979 | |||
| 684a3d8662 | |||
| 6a2a4c0f05 | |||
| f535eea085 | |||
| 09e9e03767 | |||
| 8d20fd4492 | |||
| cc3d2497b7 | |||
| 8dc45d526f |
22
.vscode/launch.json
vendored
Normal file
22
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "msedge",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Edge against localhost",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
bun.lock
28
bun.lock
@@ -9,9 +9,11 @@
|
|||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
|
"@stepperize/react": "^5.1.9",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@tanstack/react-devtools": "^0.7.0",
|
"@tanstack/react-devtools": "^0.7.0",
|
||||||
"@tanstack/react-query": "^5.66.5",
|
"@tanstack/react-query": "^5.66.5",
|
||||||
@@ -244,18 +246,24 @@
|
|||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||||
|
|
||||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||||
|
|
||||||
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
|
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
|
||||||
|
|
||||||
"@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="],
|
"@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||||
|
|
||||||
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="],
|
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="],
|
||||||
|
|
||||||
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
|
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
|
||||||
|
|
||||||
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
|
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
|
||||||
|
|
||||||
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
|
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
|
||||||
@@ -276,6 +284,8 @@
|
|||||||
|
|
||||||
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
|
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="],
|
"@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="],
|
||||||
|
|
||||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
|
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
|
||||||
@@ -294,6 +304,8 @@
|
|||||||
|
|
||||||
"@radix-ui/react-use-layout-effect": ["@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" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
"@radix-ui/react-use-layout-effect": ["@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" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
|
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
|
||||||
|
|
||||||
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@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" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
|
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@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" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
|
||||||
@@ -362,6 +374,10 @@
|
|||||||
|
|
||||||
"@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="],
|
"@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="],
|
||||||
|
|
||||||
|
"@stepperize/core": ["@stepperize/core@1.2.7", "", { "peerDependencies": { "typescript": ">=5.0.2" } }, "sha512-XiUwLZ0XRAfaDK6AzWVgqvI/BcrylyplhUXKO8vzgRw0FTmyMKHAAbQLDvU//ZJAqnmG2cSLZDSkcwLxU5zSYA=="],
|
||||||
|
|
||||||
|
"@stepperize/react": ["@stepperize/react@5.1.9", "", { "dependencies": { "@stepperize/core": "1.2.7" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-yBgw1I5Tx6/qZB4xTdVBaPGfTqH5aYS1WFB5vtR8+fwPeqd3YNuOnQ1pJM6w/xV/gvryuy31hbFw080lZc+/hw=="],
|
||||||
|
|
||||||
"@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.6.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.0", "@typescript-eslint/types": "^8.47.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw=="],
|
"@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.6.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.0", "@typescript-eslint/types": "^8.47.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
||||||
@@ -1294,6 +1310,12 @@
|
|||||||
|
|
||||||
"@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
"@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-collection/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||||
|
|
||||||
"@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
"@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||||
|
|
||||||
"@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
"@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
@@ -1316,6 +1338,12 @@
|
|||||||
|
|
||||||
"@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
"@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-select/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||||
|
|
||||||
"@radix-ui/react-tooltip/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
"@radix-ui/react-tooltip/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||||
|
|
||||||
"@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
"@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "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" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|||||||
@@ -21,9 +21,11 @@
|
|||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
|
"@stepperize/react": "^5.1.9",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@tanstack/react-devtools": "^0.7.0",
|
"@tanstack/react-devtools": "^0.7.0",
|
||||||
"@tanstack/react-query": "^5.66.5",
|
"@tanstack/react-query": "^5.66.5",
|
||||||
|
|||||||
74
src/components/CircularProgress.tsx
Normal file
74
src/components/CircularProgress.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
interface CircularProgressProps {
|
||||||
|
current: number
|
||||||
|
total: number
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CircularProgress({
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
className,
|
||||||
|
}: CircularProgressProps) {
|
||||||
|
// Configuración interna del SVG (Coordenadas 100x100)
|
||||||
|
const center = 50
|
||||||
|
const strokeWidth = 8 // Grosor de la línea
|
||||||
|
const radius = 40 // Radio (dejamos margen para el borde)
|
||||||
|
const circumference = 2 * Math.PI * radius
|
||||||
|
|
||||||
|
// Cálculo del porcentaje inverso (para que se llene correctamente)
|
||||||
|
const percentage = (current / total) * 100
|
||||||
|
const strokeDashoffset = circumference - (percentage / 100) * circumference
|
||||||
|
|
||||||
|
return (
|
||||||
|
// CAMBIO CLAVE 1: 'size-24' (96px) da mucho más aire que 'size-16'
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'relative flex size-20 items-center justify-center',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* CAMBIO CLAVE 2: Contenedor de texto con inset-0 para centrado perfecto */}
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
|
<span className="mb-1 text-sm leading-none font-medium text-slate-500">
|
||||||
|
Paso
|
||||||
|
</span>
|
||||||
|
<span className="text-base leading-none font-bold text-slate-900">
|
||||||
|
{current}{' '}
|
||||||
|
<span className="text-base font-normal text-slate-400">
|
||||||
|
/ {total}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* SVG con viewBox para escalar automáticamente */}
|
||||||
|
<svg className="size-full -rotate-90" viewBox="0 0 100 100">
|
||||||
|
{/* Círculo de Fondo (Gris claro) */}
|
||||||
|
<circle
|
||||||
|
cx={center}
|
||||||
|
cy={center}
|
||||||
|
r={radius}
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
fill="transparent"
|
||||||
|
className="text-slate-100"
|
||||||
|
/>
|
||||||
|
{/* Círculo de Progreso (Verde/Color principal) */}
|
||||||
|
<circle
|
||||||
|
cx={center}
|
||||||
|
cy={center}
|
||||||
|
r={radius}
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
fill="transparent"
|
||||||
|
strokeDasharray={circumference}
|
||||||
|
strokeDashoffset={strokeDashoffset}
|
||||||
|
strokeLinecap="round"
|
||||||
|
className="text-primary transition-all duration-500 ease-out"
|
||||||
|
// Nota: usa text-primary para tomar el color de tu tema, o pon text-green-500
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
42
src/components/planes/App.css
Normal file
42
src/components/planes/App.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#root {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 6em;
|
||||||
|
padding: 1.5em;
|
||||||
|
will-change: filter;
|
||||||
|
transition: filter 300ms;
|
||||||
|
}
|
||||||
|
.logo:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #646cffaa);
|
||||||
|
}
|
||||||
|
.logo.react:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
a:nth-of-type(2) .logo {
|
||||||
|
animation: logo-spin infinite 20s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-the-docs {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
163
src/components/planes/exampleStepper.tsx
Normal file
163
src/components/planes/exampleStepper.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { defineStepper } from '@stepperize/react'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
// import './App.css'
|
||||||
|
|
||||||
|
const { useStepper, steps, utils } = defineStepper(
|
||||||
|
{
|
||||||
|
id: 'shipping',
|
||||||
|
title: 'Shipping',
|
||||||
|
description: 'Enter your shipping details',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'payment',
|
||||||
|
title: 'Payment',
|
||||||
|
description: 'Enter your payment details',
|
||||||
|
},
|
||||||
|
{ id: 'complete', title: 'Complete', description: 'Checkout complete' },
|
||||||
|
)
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const stepper = useStepper()
|
||||||
|
|
||||||
|
const currentIndex = utils.getIndex(stepper.current.id)
|
||||||
|
return (
|
||||||
|
<div className="w-[450px] space-y-6 rounded-lg border p-6">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<h2 className="text-lg font-medium">Checkout</h2>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Step {currentIndex + 1} of {steps.length}
|
||||||
|
</span>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Checkout Steps" className="group my-4">
|
||||||
|
<ol
|
||||||
|
className="flex items-center justify-between gap-2"
|
||||||
|
aria-orientation="horizontal"
|
||||||
|
>
|
||||||
|
{stepper.all.map((step, index, array) => (
|
||||||
|
<React.Fragment key={step.id}>
|
||||||
|
<li className="flex flex-shrink-0 items-center gap-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
variant={index <= currentIndex ? 'default' : 'secondary'}
|
||||||
|
aria-current={
|
||||||
|
stepper.current.id === step.id ? 'step' : undefined
|
||||||
|
}
|
||||||
|
aria-posinset={index + 1}
|
||||||
|
aria-setsize={steps.length}
|
||||||
|
aria-selected={stepper.current.id === step.id}
|
||||||
|
className="flex size-10 items-center justify-center rounded-full"
|
||||||
|
onClick={() => stepper.goTo(step.id)}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</Button>
|
||||||
|
<span className="text-sm font-medium">{step.title}</span>
|
||||||
|
</li>
|
||||||
|
{index < array.length - 1 && (
|
||||||
|
<Separator
|
||||||
|
className={`flex-1 ${
|
||||||
|
index < currentIndex ? 'bg-primary' : 'bg-muted'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{stepper.switch({
|
||||||
|
shipping: () => <ShippingComponent />,
|
||||||
|
payment: () => <PaymentComponent />,
|
||||||
|
complete: () => <CompleteComponent />,
|
||||||
|
})}
|
||||||
|
{!stepper.isLast ? (
|
||||||
|
<div className="flex justify-end gap-4">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={stepper.prev}
|
||||||
|
disabled={stepper.isFirst}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button onClick={stepper.next}>
|
||||||
|
{stepper.isLast ? 'Complete' : 'Next'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button onClick={stepper.reset}>Reset</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShippingComponent = () => {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label htmlFor="name" className="text-start text-sm font-medium">
|
||||||
|
Name
|
||||||
|
</label>
|
||||||
|
<Input id="name" placeholder="John Doe" className="w-full" />
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label htmlFor="address" className="text-start text-sm font-medium">
|
||||||
|
Address
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
id="address"
|
||||||
|
placeholder="123 Main St, Anytown USA"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PaymentComponent = () => {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label htmlFor="card-number" className="text-start text-sm font-medium">
|
||||||
|
Card Number
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="card-number"
|
||||||
|
placeholder="4111 1111 1111 1111"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label
|
||||||
|
htmlFor="expiry-date"
|
||||||
|
className="text-start text-sm font-medium"
|
||||||
|
>
|
||||||
|
Expiry Date
|
||||||
|
</label>
|
||||||
|
<Input id="expiry-date" placeholder="MM/YY" className="w-full" />
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label htmlFor="cvc" className="text-start text-sm font-medium">
|
||||||
|
CVC
|
||||||
|
</label>
|
||||||
|
<Input id="cvc" placeholder="123" className="w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompleteComponent = () => {
|
||||||
|
return <h3 className="py-4 text-lg font-medium">Stepper complete 🔥</h3>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
534
src/components/stepper.tsx
Normal file
534
src/components/stepper.tsx
Normal file
@@ -0,0 +1,534 @@
|
|||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
|
import * as Stepperize from '@stepperize/react'
|
||||||
|
import { cva } from 'class-variance-authority'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import type { VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const StepperContext = React.createContext<Stepper.ConfigProps | null>(null)
|
||||||
|
|
||||||
|
const useStepperProvider = (): Stepper.ConfigProps => {
|
||||||
|
const context = React.useContext(StepperContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useStepper must be used within a StepperProvider.')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const defineStepper = <const Steps extends Array<Stepperize.Step>>(
|
||||||
|
...steps: Steps
|
||||||
|
): Stepper.DefineProps<Steps> => {
|
||||||
|
const { Scoped, useStepper, ...rest } = Stepperize.defineStepper(...steps)
|
||||||
|
|
||||||
|
const StepperContainer = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: Omit<React.ComponentProps<'div'>, 'children'> & {
|
||||||
|
children:
|
||||||
|
| React.ReactNode
|
||||||
|
| ((props: { methods: Stepperize.Stepper<Steps> }) => React.ReactNode)
|
||||||
|
}) => {
|
||||||
|
const methods = useStepper()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
date-component="stepper"
|
||||||
|
className={cn('w-full', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{typeof children === 'function' ? children({ methods }) : children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
useStepper,
|
||||||
|
Stepper: {
|
||||||
|
Provider: ({
|
||||||
|
variant = 'horizontal',
|
||||||
|
labelOrientation = 'horizontal',
|
||||||
|
tracking = false,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<StepperContext.Provider
|
||||||
|
value={{ variant, labelOrientation, tracking }}
|
||||||
|
>
|
||||||
|
<Scoped
|
||||||
|
initialStep={props.initialStep}
|
||||||
|
initialMetadata={props.initialMetadata}
|
||||||
|
>
|
||||||
|
<StepperContainer className={className} {...props}>
|
||||||
|
{children}
|
||||||
|
</StepperContainer>
|
||||||
|
</Scoped>
|
||||||
|
</StepperContext.Provider>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Navigation: ({
|
||||||
|
children,
|
||||||
|
'aria-label': ariaLabel = 'Stepper Navigation',
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { variant } = useStepperProvider()
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
date-component="stepper-navigation"
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
role="tablist"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ol
|
||||||
|
date-component="stepper-navigation-list"
|
||||||
|
className={classForNavigationList({ variant: variant })}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Step: ({ children, className, icon, ...props }) => {
|
||||||
|
const { variant, labelOrientation } = useStepperProvider()
|
||||||
|
const { current } = useStepper()
|
||||||
|
|
||||||
|
const utils = rest.utils
|
||||||
|
const steps = rest.steps
|
||||||
|
|
||||||
|
const stepIndex = utils.getIndex(props.of)
|
||||||
|
const step = steps[stepIndex]
|
||||||
|
const currentIndex = utils.getIndex(current.id)
|
||||||
|
|
||||||
|
const isLast = utils.getLast().id === props.of
|
||||||
|
const isActive = current.id === props.of
|
||||||
|
|
||||||
|
const dataState = getStepState(currentIndex, stepIndex)
|
||||||
|
const childMap = useStepChildren(children)
|
||||||
|
|
||||||
|
const title = childMap.get('title')
|
||||||
|
const description = childMap.get('description')
|
||||||
|
const panel = childMap.get('panel')
|
||||||
|
|
||||||
|
if (variant === 'circle') {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
date-component="stepper-step"
|
||||||
|
className={cn(
|
||||||
|
'flex shrink-0 items-center gap-4 rounded-md transition-colors',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CircleStepIndicator
|
||||||
|
currentStep={stepIndex + 1}
|
||||||
|
totalSteps={steps.length}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
date-component="stepper-step-content"
|
||||||
|
className="flex flex-col items-start gap-1"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<li
|
||||||
|
date-component="stepper-step"
|
||||||
|
className={cn([
|
||||||
|
'group peer relative flex items-center gap-2',
|
||||||
|
'data-[variant=vertical]:flex-row',
|
||||||
|
'data-[label-orientation=vertical]:w-full',
|
||||||
|
'data-[label-orientation=vertical]:flex-col',
|
||||||
|
'data-[label-orientation=vertical]:justify-center',
|
||||||
|
])}
|
||||||
|
data-variant={variant}
|
||||||
|
data-label-orientation={labelOrientation}
|
||||||
|
data-state={dataState}
|
||||||
|
data-disabled={props.disabled}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
id={`step-${step.id}`}
|
||||||
|
date-component="stepper-step-indicator"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
tabIndex={dataState !== 'inactive' ? 0 : -1}
|
||||||
|
className="rounded-full"
|
||||||
|
variant={dataState !== 'inactive' ? 'default' : 'secondary'}
|
||||||
|
size="icon"
|
||||||
|
aria-controls={`step-panel-${props.of}`}
|
||||||
|
aria-current={isActive ? 'step' : undefined}
|
||||||
|
aria-posinset={stepIndex + 1}
|
||||||
|
aria-setsize={steps.length}
|
||||||
|
aria-selected={isActive}
|
||||||
|
onKeyDown={(e) =>
|
||||||
|
onStepKeyDown(
|
||||||
|
e,
|
||||||
|
utils.getNext(props.of),
|
||||||
|
utils.getPrev(props.of),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{icon ?? stepIndex + 1}
|
||||||
|
</Button>
|
||||||
|
{variant === 'horizontal' && labelOrientation === 'vertical' && (
|
||||||
|
<StepperSeparator
|
||||||
|
orientation="horizontal"
|
||||||
|
labelOrientation={labelOrientation}
|
||||||
|
isLast={isLast}
|
||||||
|
state={dataState}
|
||||||
|
disabled={props.disabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
date-component="stepper-step-content"
|
||||||
|
className="flex flex-col items-start"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{variant === 'horizontal' && labelOrientation === 'horizontal' && (
|
||||||
|
<StepperSeparator
|
||||||
|
orientation="horizontal"
|
||||||
|
isLast={isLast}
|
||||||
|
state={dataState}
|
||||||
|
disabled={props.disabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{variant === 'vertical' && (
|
||||||
|
<div className="flex gap-4">
|
||||||
|
{!isLast && (
|
||||||
|
<div className="flex justify-center ps-[calc(var(--spacing)_*_4.5_-_1px)]">
|
||||||
|
<StepperSeparator
|
||||||
|
orientation="vertical"
|
||||||
|
isLast={isLast}
|
||||||
|
state={dataState}
|
||||||
|
disabled={props.disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="my-3 flex-1 ps-4">{panel}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Title,
|
||||||
|
Description,
|
||||||
|
Panel: ({ children, asChild, ...props }) => {
|
||||||
|
const Comp = asChild ? Slot : 'div'
|
||||||
|
const { tracking } = useStepperProvider()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
date-component="stepper-step-panel"
|
||||||
|
ref={(node) => scrollIntoStepperPanel(node, tracking)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Controls: ({ children, className, asChild, ...props }) => {
|
||||||
|
const Comp = asChild ? Slot : 'div'
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
date-component="stepper-controls"
|
||||||
|
className={cn('flex justify-end gap-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Title = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
asChild,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'h4'> & { asChild?: boolean }) => {
|
||||||
|
const Comp = asChild ? Slot : 'h4'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
date-component="stepper-step-title"
|
||||||
|
className={cn('text-base font-medium', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Description = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
asChild,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'p'> & { asChild?: boolean }) => {
|
||||||
|
const Comp = asChild ? Slot : 'p'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
date-component="stepper-step-description"
|
||||||
|
className={cn('text-muted-foreground text-sm', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Comp>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepperSeparator = ({
|
||||||
|
orientation,
|
||||||
|
isLast,
|
||||||
|
labelOrientation,
|
||||||
|
state,
|
||||||
|
disabled,
|
||||||
|
}: {
|
||||||
|
isLast: boolean
|
||||||
|
state: string
|
||||||
|
disabled?: boolean
|
||||||
|
} & VariantProps<typeof classForSeparator>) => {
|
||||||
|
if (isLast) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
date-component="stepper-separator"
|
||||||
|
data-orientation={orientation}
|
||||||
|
data-state={state}
|
||||||
|
data-disabled={disabled}
|
||||||
|
role="separator"
|
||||||
|
tabIndex={-1}
|
||||||
|
className={classForSeparator({ orientation, labelOrientation })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CircleStepIndicator = ({
|
||||||
|
currentStep,
|
||||||
|
totalSteps,
|
||||||
|
size = 80,
|
||||||
|
strokeWidth = 6,
|
||||||
|
}: Stepper.CircleStepIndicatorProps) => {
|
||||||
|
const radius = (size - strokeWidth) / 2
|
||||||
|
const circumference = radius * 2 * Math.PI
|
||||||
|
const fillPercentage = (currentStep / totalSteps) * 100
|
||||||
|
const dashOffset = circumference - (circumference * fillPercentage) / 100
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
date-component="stepper-step-indicator"
|
||||||
|
role="progressbar"
|
||||||
|
aria-valuenow={currentStep}
|
||||||
|
aria-valuemin={1}
|
||||||
|
aria-valuemax={totalSteps}
|
||||||
|
tabIndex={-1}
|
||||||
|
className="relative inline-flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<svg width={size} height={size}>
|
||||||
|
<title>Step Indicator</title>
|
||||||
|
<circle
|
||||||
|
cx={size / 2}
|
||||||
|
cy={size / 2}
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
className="text-muted-foreground"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={size / 2}
|
||||||
|
cy={size / 2}
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeDasharray={circumference}
|
||||||
|
strokeDashoffset={dashOffset}
|
||||||
|
className="text-primary transition-all duration-300 ease-in-out"
|
||||||
|
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<span className="text-sm font-medium" aria-live="polite">
|
||||||
|
{currentStep} of {totalSteps}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const classForNavigationList = cva('flex gap-2', {
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
horizontal: 'flex-row items-center justify-between',
|
||||||
|
vertical: 'flex-col',
|
||||||
|
circle: 'flex-row items-center justify-between',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const classForSeparator = cva(
|
||||||
|
[
|
||||||
|
'bg-muted',
|
||||||
|
'data-[state=completed]:bg-primary data-[disabled]:opacity-50',
|
||||||
|
'transition-all duration-300 ease-in-out',
|
||||||
|
],
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
orientation: {
|
||||||
|
horizontal: 'h-0.5 flex-1',
|
||||||
|
vertical: 'h-full w-0.5',
|
||||||
|
},
|
||||||
|
labelOrientation: {
|
||||||
|
vertical:
|
||||||
|
'absolute top-5 right-[calc(-50%+20px)] left-[calc(50%+30px)] block shrink-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function scrollIntoStepperPanel(
|
||||||
|
node: HTMLDivElement | null,
|
||||||
|
tracking?: boolean,
|
||||||
|
) {
|
||||||
|
if (tracking) {
|
||||||
|
node?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStepChildren = (children: React.ReactNode) => {
|
||||||
|
return React.useMemo(() => extractChildren(children), [children])
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractChildren = (children: React.ReactNode) => {
|
||||||
|
const childrenArray = React.Children.toArray(children)
|
||||||
|
const map = new Map<string, React.ReactNode>()
|
||||||
|
|
||||||
|
for (const child of childrenArray) {
|
||||||
|
if (React.isValidElement(child)) {
|
||||||
|
if (child.type === Title) {
|
||||||
|
map.set('title', child)
|
||||||
|
} else if (child.type === Description) {
|
||||||
|
map.set('description', child)
|
||||||
|
} else {
|
||||||
|
map.set('panel', child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
const onStepKeyDown = (
|
||||||
|
e: React.KeyboardEvent<HTMLButtonElement>,
|
||||||
|
nextStep: Stepperize.Step,
|
||||||
|
prevStep: Stepperize.Step,
|
||||||
|
) => {
|
||||||
|
const { key } = e
|
||||||
|
const directions = {
|
||||||
|
next: ['ArrowRight', 'ArrowDown'],
|
||||||
|
prev: ['ArrowLeft', 'ArrowUp'],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directions.next.includes(key) || directions.prev.includes(key)) {
|
||||||
|
const direction = directions.next.includes(key) ? 'next' : 'prev'
|
||||||
|
const step = direction === 'next' ? nextStep : prevStep
|
||||||
|
|
||||||
|
if (!step) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepElement = document.getElementById(`step-${step.id}`)
|
||||||
|
if (!stepElement) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActive =
|
||||||
|
stepElement.parentElement?.getAttribute('data-state') !== 'inactive'
|
||||||
|
if (isActive || direction === 'prev') {
|
||||||
|
stepElement.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStepState = (currentIndex: number, stepIndex: number) => {
|
||||||
|
if (currentIndex === stepIndex) {
|
||||||
|
return 'active'
|
||||||
|
}
|
||||||
|
if (currentIndex > stepIndex) {
|
||||||
|
return 'completed'
|
||||||
|
}
|
||||||
|
return 'inactive'
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Stepper {
|
||||||
|
export type StepperVariant = 'horizontal' | 'vertical' | 'circle'
|
||||||
|
export type StepperLabelOrientation = 'horizontal' | 'vertical'
|
||||||
|
|
||||||
|
export type ConfigProps = {
|
||||||
|
variant?: StepperVariant
|
||||||
|
labelOrientation?: StepperLabelOrientation
|
||||||
|
tracking?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DefineProps<Steps extends Array<Stepperize.Step>> = Omit<
|
||||||
|
Stepperize.StepperReturn<Steps>,
|
||||||
|
'Scoped'
|
||||||
|
> & {
|
||||||
|
Stepper: {
|
||||||
|
Provider: (
|
||||||
|
props: Omit<Stepperize.ScopedProps<Steps>, 'children'> &
|
||||||
|
Omit<React.ComponentProps<'div'>, 'children'> &
|
||||||
|
Stepper.ConfigProps & {
|
||||||
|
children:
|
||||||
|
| React.ReactNode
|
||||||
|
| ((props: {
|
||||||
|
methods: Stepperize.Stepper<Steps>
|
||||||
|
}) => React.ReactNode)
|
||||||
|
},
|
||||||
|
) => React.ReactElement
|
||||||
|
Navigation: (props: React.ComponentProps<'nav'>) => React.ReactElement
|
||||||
|
Step: (
|
||||||
|
props: React.ComponentProps<'button'> & {
|
||||||
|
of: Stepperize.Get.Id<Steps>
|
||||||
|
icon?: React.ReactNode
|
||||||
|
},
|
||||||
|
) => React.ReactElement
|
||||||
|
Title: (props: AsChildProps<'h4'>) => React.ReactElement
|
||||||
|
Description: (props: AsChildProps<'p'>) => React.ReactElement
|
||||||
|
Panel: (props: AsChildProps<'div'>) => React.ReactElement
|
||||||
|
Controls: (props: AsChildProps<'div'>) => React.ReactElement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CircleStepIndicatorProps = {
|
||||||
|
currentStep: number
|
||||||
|
totalSteps: number
|
||||||
|
size?: number
|
||||||
|
strokeWidth?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AsChildProps<T extends React.ElementType> = React.ComponentProps<T> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export { defineStepper }
|
||||||
188
src/components/ui/select.tsx
Normal file
188
src/components/ui/select.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Select({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
|
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectGroup({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectValue({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectTrigger({
|
||||||
|
className,
|
||||||
|
size = "default",
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
|
size?: "sm" | "default"
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
data-slot="select-trigger"
|
||||||
|
data-size={size}
|
||||||
|
className={cn(
|
||||||
|
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]: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 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
position = "item-aligned",
|
||||||
|
align = "center",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
data-slot="select-content"
|
||||||
|
className={cn(
|
||||||
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
align={align}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectLabel({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
data-slot="select-label"
|
||||||
|
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
data-slot="select-item"
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
data-slot="select-item-indicator"
|
||||||
|
className="absolute right-2 flex size-3.5 items-center justify-center"
|
||||||
|
>
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
data-slot="select-separator"
|
||||||
|
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollUpButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
data-slot="select-scroll-up-button"
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUpIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollDownButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
data-slot="select-scroll-down-button"
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDownIcon className="size-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
}
|
||||||
26
src/components/ui/separator.tsx
Normal file
26
src/components/ui/separator.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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
|
||||||
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
data-slot="separator"
|
||||||
|
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 }
|
||||||
18
src/components/ui/textarea.tsx
Normal file
18
src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
|
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 }
|
||||||
@@ -9,16 +9,27 @@
|
|||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
import { Route as PlanesRouteImport } from './routes/planes'
|
import { Route as Stepper2RouteImport } from './routes/stepper2'
|
||||||
|
import { Route as StepperRouteImport } from './routes/stepper'
|
||||||
import { Route as LoginRouteImport } from './routes/login'
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
import { Route as DashboardRouteImport } from './routes/dashboard'
|
import { Route as DashboardRouteImport } from './routes/dashboard'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as PlanesIndexRouteImport } from './routes/planes/index'
|
|
||||||
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
|
import { Route as DemoTanstackQueryRouteImport } from './routes/demo/tanstack-query'
|
||||||
|
import { Route as PlanesListaRouteRouteImport } from './routes/planes/_lista/route'
|
||||||
|
import { Route as PlanesPlanIdRouteRouteImport } from './routes/planes/$planId/route'
|
||||||
|
import { Route as AsignaturasListaRouteRouteImport } from './routes/asignaturas/_lista/route'
|
||||||
|
import { Route as AsignaturasAsignaturaIdRouteRouteImport } from './routes/asignaturas/$asignaturaId/route'
|
||||||
|
import { Route as PlanesListaNuevoRouteImport } from './routes/planes/_lista/nuevo'
|
||||||
|
import { Route as AsignaturasListaNuevaRouteImport } from './routes/asignaturas/_lista/nueva'
|
||||||
|
|
||||||
const PlanesRoute = PlanesRouteImport.update({
|
const Stepper2Route = Stepper2RouteImport.update({
|
||||||
id: '/planes',
|
id: '/stepper2',
|
||||||
path: '/planes',
|
path: '/stepper2',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const StepperRoute = StepperRouteImport.update({
|
||||||
|
id: '/stepper',
|
||||||
|
path: '/stepper',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const LoginRoute = LoginRouteImport.update({
|
const LoginRoute = LoginRouteImport.update({
|
||||||
@@ -36,40 +47,85 @@ const IndexRoute = IndexRouteImport.update({
|
|||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const PlanesIndexRoute = PlanesIndexRouteImport.update({
|
|
||||||
id: '/',
|
|
||||||
path: '/',
|
|
||||||
getParentRoute: () => PlanesRoute,
|
|
||||||
} as any)
|
|
||||||
const DemoTanstackQueryRoute = DemoTanstackQueryRouteImport.update({
|
const DemoTanstackQueryRoute = DemoTanstackQueryRouteImport.update({
|
||||||
id: '/demo/tanstack-query',
|
id: '/demo/tanstack-query',
|
||||||
path: '/demo/tanstack-query',
|
path: '/demo/tanstack-query',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const PlanesListaRouteRoute = PlanesListaRouteRouteImport.update({
|
||||||
|
id: '/planes/_lista',
|
||||||
|
path: '/planes',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const PlanesPlanIdRouteRoute = PlanesPlanIdRouteRouteImport.update({
|
||||||
|
id: '/planes/$planId',
|
||||||
|
path: '/planes/$planId',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const AsignaturasListaRouteRoute = AsignaturasListaRouteRouteImport.update({
|
||||||
|
id: '/asignaturas/_lista',
|
||||||
|
path: '/asignaturas',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const AsignaturasAsignaturaIdRouteRoute =
|
||||||
|
AsignaturasAsignaturaIdRouteRouteImport.update({
|
||||||
|
id: '/asignaturas/$asignaturaId',
|
||||||
|
path: '/asignaturas/$asignaturaId',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const PlanesListaNuevoRoute = PlanesListaNuevoRouteImport.update({
|
||||||
|
id: '/nuevo',
|
||||||
|
path: '/nuevo',
|
||||||
|
getParentRoute: () => PlanesListaRouteRoute,
|
||||||
|
} as any)
|
||||||
|
const AsignaturasListaNuevaRoute = AsignaturasListaNuevaRouteImport.update({
|
||||||
|
id: '/nueva',
|
||||||
|
path: '/nueva',
|
||||||
|
getParentRoute: () => AsignaturasListaRouteRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/dashboard': typeof DashboardRoute
|
'/dashboard': typeof DashboardRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/planes': typeof PlanesRouteWithChildren
|
'/stepper': typeof StepperRoute
|
||||||
|
'/stepper2': typeof Stepper2Route
|
||||||
|
'/asignaturas/$asignaturaId': typeof AsignaturasAsignaturaIdRouteRoute
|
||||||
|
'/asignaturas': typeof AsignaturasListaRouteRouteWithChildren
|
||||||
|
'/planes/$planId': typeof PlanesPlanIdRouteRoute
|
||||||
|
'/planes': typeof PlanesListaRouteRouteWithChildren
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
'/planes/': typeof PlanesIndexRoute
|
'/asignaturas/nueva': typeof AsignaturasListaNuevaRoute
|
||||||
|
'/planes/nuevo': typeof PlanesListaNuevoRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/dashboard': typeof DashboardRoute
|
'/dashboard': typeof DashboardRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
|
'/stepper': typeof StepperRoute
|
||||||
|
'/stepper2': typeof Stepper2Route
|
||||||
|
'/asignaturas/$asignaturaId': typeof AsignaturasAsignaturaIdRouteRoute
|
||||||
|
'/asignaturas': typeof AsignaturasListaRouteRouteWithChildren
|
||||||
|
'/planes/$planId': typeof PlanesPlanIdRouteRoute
|
||||||
|
'/planes': typeof PlanesListaRouteRouteWithChildren
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
'/planes': typeof PlanesIndexRoute
|
'/asignaturas/nueva': typeof AsignaturasListaNuevaRoute
|
||||||
|
'/planes/nuevo': typeof PlanesListaNuevoRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/dashboard': typeof DashboardRoute
|
'/dashboard': typeof DashboardRoute
|
||||||
'/login': typeof LoginRoute
|
'/login': typeof LoginRoute
|
||||||
'/planes': typeof PlanesRouteWithChildren
|
'/stepper': typeof StepperRoute
|
||||||
|
'/stepper2': typeof Stepper2Route
|
||||||
|
'/asignaturas/$asignaturaId': typeof AsignaturasAsignaturaIdRouteRoute
|
||||||
|
'/asignaturas/_lista': typeof AsignaturasListaRouteRouteWithChildren
|
||||||
|
'/planes/$planId': typeof PlanesPlanIdRouteRoute
|
||||||
|
'/planes/_lista': typeof PlanesListaRouteRouteWithChildren
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
'/planes/': typeof PlanesIndexRoute
|
'/asignaturas/_lista/nueva': typeof AsignaturasListaNuevaRoute
|
||||||
|
'/planes/_lista/nuevo': typeof PlanesListaNuevoRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
@@ -77,36 +133,72 @@ export interface FileRouteTypes {
|
|||||||
| '/'
|
| '/'
|
||||||
| '/dashboard'
|
| '/dashboard'
|
||||||
| '/login'
|
| '/login'
|
||||||
|
| '/stepper'
|
||||||
|
| '/stepper2'
|
||||||
|
| '/asignaturas/$asignaturaId'
|
||||||
|
| '/asignaturas'
|
||||||
|
| '/planes/$planId'
|
||||||
| '/planes'
|
| '/planes'
|
||||||
| '/demo/tanstack-query'
|
| '/demo/tanstack-query'
|
||||||
| '/planes/'
|
| '/asignaturas/nueva'
|
||||||
|
| '/planes/nuevo'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/' | '/dashboard' | '/login' | '/demo/tanstack-query' | '/planes'
|
to:
|
||||||
|
| '/'
|
||||||
|
| '/dashboard'
|
||||||
|
| '/login'
|
||||||
|
| '/stepper'
|
||||||
|
| '/stepper2'
|
||||||
|
| '/asignaturas/$asignaturaId'
|
||||||
|
| '/asignaturas'
|
||||||
|
| '/planes/$planId'
|
||||||
|
| '/planes'
|
||||||
|
| '/demo/tanstack-query'
|
||||||
|
| '/asignaturas/nueva'
|
||||||
|
| '/planes/nuevo'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/dashboard'
|
| '/dashboard'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/planes'
|
| '/stepper'
|
||||||
|
| '/stepper2'
|
||||||
|
| '/asignaturas/$asignaturaId'
|
||||||
|
| '/asignaturas/_lista'
|
||||||
|
| '/planes/$planId'
|
||||||
|
| '/planes/_lista'
|
||||||
| '/demo/tanstack-query'
|
| '/demo/tanstack-query'
|
||||||
| '/planes/'
|
| '/asignaturas/_lista/nueva'
|
||||||
|
| '/planes/_lista/nuevo'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
DashboardRoute: typeof DashboardRoute
|
DashboardRoute: typeof DashboardRoute
|
||||||
LoginRoute: typeof LoginRoute
|
LoginRoute: typeof LoginRoute
|
||||||
PlanesRoute: typeof PlanesRouteWithChildren
|
StepperRoute: typeof StepperRoute
|
||||||
|
Stepper2Route: typeof Stepper2Route
|
||||||
|
AsignaturasAsignaturaIdRouteRoute: typeof AsignaturasAsignaturaIdRouteRoute
|
||||||
|
AsignaturasListaRouteRoute: typeof AsignaturasListaRouteRouteWithChildren
|
||||||
|
PlanesPlanIdRouteRoute: typeof PlanesPlanIdRouteRoute
|
||||||
|
PlanesListaRouteRoute: typeof PlanesListaRouteRouteWithChildren
|
||||||
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
'/planes': {
|
'/stepper2': {
|
||||||
id: '/planes'
|
id: '/stepper2'
|
||||||
path: '/planes'
|
path: '/stepper2'
|
||||||
fullPath: '/planes'
|
fullPath: '/stepper2'
|
||||||
preLoaderRoute: typeof PlanesRouteImport
|
preLoaderRoute: typeof Stepper2RouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/stepper': {
|
||||||
|
id: '/stepper'
|
||||||
|
path: '/stepper'
|
||||||
|
fullPath: '/stepper'
|
||||||
|
preLoaderRoute: typeof StepperRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/login': {
|
'/login': {
|
||||||
@@ -130,13 +222,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof IndexRouteImport
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/planes/': {
|
|
||||||
id: '/planes/'
|
|
||||||
path: '/'
|
|
||||||
fullPath: '/planes/'
|
|
||||||
preLoaderRoute: typeof PlanesIndexRouteImport
|
|
||||||
parentRoute: typeof PlanesRoute
|
|
||||||
}
|
|
||||||
'/demo/tanstack-query': {
|
'/demo/tanstack-query': {
|
||||||
id: '/demo/tanstack-query'
|
id: '/demo/tanstack-query'
|
||||||
path: '/demo/tanstack-query'
|
path: '/demo/tanstack-query'
|
||||||
@@ -144,25 +229,85 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DemoTanstackQueryRouteImport
|
preLoaderRoute: typeof DemoTanstackQueryRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/planes/_lista': {
|
||||||
|
id: '/planes/_lista'
|
||||||
|
path: '/planes'
|
||||||
|
fullPath: '/planes'
|
||||||
|
preLoaderRoute: typeof PlanesListaRouteRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/planes/$planId': {
|
||||||
|
id: '/planes/$planId'
|
||||||
|
path: '/planes/$planId'
|
||||||
|
fullPath: '/planes/$planId'
|
||||||
|
preLoaderRoute: typeof PlanesPlanIdRouteRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/asignaturas/_lista': {
|
||||||
|
id: '/asignaturas/_lista'
|
||||||
|
path: '/asignaturas'
|
||||||
|
fullPath: '/asignaturas'
|
||||||
|
preLoaderRoute: typeof AsignaturasListaRouteRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/asignaturas/$asignaturaId': {
|
||||||
|
id: '/asignaturas/$asignaturaId'
|
||||||
|
path: '/asignaturas/$asignaturaId'
|
||||||
|
fullPath: '/asignaturas/$asignaturaId'
|
||||||
|
preLoaderRoute: typeof AsignaturasAsignaturaIdRouteRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/planes/_lista/nuevo': {
|
||||||
|
id: '/planes/_lista/nuevo'
|
||||||
|
path: '/nuevo'
|
||||||
|
fullPath: '/planes/nuevo'
|
||||||
|
preLoaderRoute: typeof PlanesListaNuevoRouteImport
|
||||||
|
parentRoute: typeof PlanesListaRouteRoute
|
||||||
|
}
|
||||||
|
'/asignaturas/_lista/nueva': {
|
||||||
|
id: '/asignaturas/_lista/nueva'
|
||||||
|
path: '/nueva'
|
||||||
|
fullPath: '/asignaturas/nueva'
|
||||||
|
preLoaderRoute: typeof AsignaturasListaNuevaRouteImport
|
||||||
|
parentRoute: typeof AsignaturasListaRouteRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlanesRouteChildren {
|
interface AsignaturasListaRouteRouteChildren {
|
||||||
PlanesIndexRoute: typeof PlanesIndexRoute
|
AsignaturasListaNuevaRoute: typeof AsignaturasListaNuevaRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlanesRouteChildren: PlanesRouteChildren = {
|
const AsignaturasListaRouteRouteChildren: AsignaturasListaRouteRouteChildren = {
|
||||||
PlanesIndexRoute: PlanesIndexRoute,
|
AsignaturasListaNuevaRoute: AsignaturasListaNuevaRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlanesRouteWithChildren =
|
const AsignaturasListaRouteRouteWithChildren =
|
||||||
PlanesRoute._addFileChildren(PlanesRouteChildren)
|
AsignaturasListaRouteRoute._addFileChildren(
|
||||||
|
AsignaturasListaRouteRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface PlanesListaRouteRouteChildren {
|
||||||
|
PlanesListaNuevoRoute: typeof PlanesListaNuevoRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlanesListaRouteRouteChildren: PlanesListaRouteRouteChildren = {
|
||||||
|
PlanesListaNuevoRoute: PlanesListaNuevoRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlanesListaRouteRouteWithChildren =
|
||||||
|
PlanesListaRouteRoute._addFileChildren(PlanesListaRouteRouteChildren)
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
DashboardRoute: DashboardRoute,
|
DashboardRoute: DashboardRoute,
|
||||||
LoginRoute: LoginRoute,
|
LoginRoute: LoginRoute,
|
||||||
PlanesRoute: PlanesRouteWithChildren,
|
StepperRoute: StepperRoute,
|
||||||
|
Stepper2Route: Stepper2Route,
|
||||||
|
AsignaturasAsignaturaIdRouteRoute: AsignaturasAsignaturaIdRouteRoute,
|
||||||
|
AsignaturasListaRouteRoute: AsignaturasListaRouteRouteWithChildren,
|
||||||
|
PlanesPlanIdRouteRoute: PlanesPlanIdRouteRoute,
|
||||||
|
PlanesListaRouteRoute: PlanesListaRouteRouteWithChildren,
|
||||||
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
|
|||||||
9
src/routes/asignaturas/$asignaturaId/route.tsx
Normal file
9
src/routes/asignaturas/$asignaturaId/route.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/asignaturas/$asignaturaId')({
|
||||||
|
component: RouteComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/asignaturas/$asignaturaId"!</div>
|
||||||
|
}
|
||||||
1083
src/routes/asignaturas/_lista/nueva.tsx
Normal file
1083
src/routes/asignaturas/_lista/nueva.tsx
Normal file
File diff suppressed because it is too large
Load Diff
15
src/routes/asignaturas/_lista/route.tsx
Normal file
15
src/routes/asignaturas/_lista/route.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/asignaturas/_lista')({
|
||||||
|
component: RouteComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<main className="bg-background min-h-screen w-full">
|
||||||
|
<div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-4 py-6 md:px-6 lg:px-8">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
10
src/routes/planes/$planId/route.tsx
Normal file
10
src/routes/planes/$planId/route.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/planes/$planId')({
|
||||||
|
component: RouteComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const { planId } = Route.useParams()
|
||||||
|
return <div>Hello "/planes/{planId}"!</div>
|
||||||
|
}
|
||||||
1204
src/routes/planes/_lista/nuevo.tsx
Normal file
1204
src/routes/planes/_lista/nuevo.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute, Outlet, useNavigate } from '@tanstack/react-router'
|
||||||
import * as Icons from 'lucide-react'
|
import * as Icons from 'lucide-react'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
@@ -8,11 +8,12 @@ import BarraBusqueda from '@/components/planes/BarraBusqueda'
|
|||||||
import Filtro from '@/components/planes/Filtro'
|
import Filtro from '@/components/planes/Filtro'
|
||||||
import PlanEstudiosCard from '@/components/planes/PlanEstudiosCard'
|
import PlanEstudiosCard from '@/components/planes/PlanEstudiosCard'
|
||||||
|
|
||||||
export const Route = createFileRoute('/planes')({
|
export const Route = createFileRoute('/planes/_lista')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
const navigate = useNavigate()
|
||||||
type Facultad = { id: string; nombre: string; color: string }
|
type Facultad = { id: string; nombre: string; color: string }
|
||||||
type Carrera = { id: string; nombre: string; facultadId: string }
|
type Carrera = { id: string; nombre: string; facultadId: string }
|
||||||
type Plan = {
|
type Plan = {
|
||||||
@@ -230,6 +231,9 @@ function RouteComponent() {
|
|||||||
}
|
}
|
||||||
aria-label="Nuevo plan de estudios"
|
aria-label="Nuevo plan de estudios"
|
||||||
title="Nuevo plan de estudios"
|
title="Nuevo plan de estudios"
|
||||||
|
onClick={() => {
|
||||||
|
navigate({ to: '/planes/nuevo' })
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Icons.Plus className="" />
|
<Icons.Plus className="" />
|
||||||
Nuevo plan de estudios
|
Nuevo plan de estudios
|
||||||
@@ -313,6 +317,7 @@ function RouteComponent() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
)
|
)
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
import { AppLayout } from '@/components/layout/AppLayout'
|
|
||||||
import { PlanGrid } from '@/components/plans/PlanGrid'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/planes/')({
|
|
||||||
component: PlanesPage,
|
|
||||||
})
|
|
||||||
|
|
||||||
function PlanesPage() {
|
|
||||||
return (
|
|
||||||
<AppLayout>
|
|
||||||
<PlanGrid />
|
|
||||||
</AppLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
65
src/routes/stepper.tsx
Normal file
65
src/routes/stepper.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
import { defineStepper } from '@/components/stepper'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/stepper')({
|
||||||
|
component: MyFirstStepper,
|
||||||
|
})
|
||||||
|
|
||||||
|
const stepperInstance = defineStepper(
|
||||||
|
{ id: 'step-1', title: 'Step 1' },
|
||||||
|
{ id: 'step-2', title: 'Step 2' },
|
||||||
|
{ id: 'step-3', title: 'Step 3' },
|
||||||
|
)
|
||||||
|
|
||||||
|
export function MyFirstStepper() {
|
||||||
|
return (
|
||||||
|
<stepperInstance.Stepper.Provider className="space-y-4">
|
||||||
|
{({ methods }) => (
|
||||||
|
<>
|
||||||
|
<stepperInstance.Stepper.Navigation>
|
||||||
|
{methods.all.map((step) => (
|
||||||
|
<stepperInstance.Stepper.Step
|
||||||
|
of={step.id}
|
||||||
|
onClick={() => methods.goTo(step.id)}
|
||||||
|
>
|
||||||
|
<stepperInstance.Stepper.Title>
|
||||||
|
{step.title}
|
||||||
|
</stepperInstance.Stepper.Title>
|
||||||
|
</stepperInstance.Stepper.Step>
|
||||||
|
))}
|
||||||
|
</stepperInstance.Stepper.Navigation>
|
||||||
|
{methods.switch({
|
||||||
|
'step-1': (step) => <Content id={step.id} />,
|
||||||
|
'step-2': (step) => <Content id={step.id} />,
|
||||||
|
'step-3': (step) => <Content id={step.id} />,
|
||||||
|
})}
|
||||||
|
<stepperInstance.Stepper.Controls>
|
||||||
|
{!methods.isLast && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={methods.prev}
|
||||||
|
disabled={methods.isFirst}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button onClick={methods.isLast ? methods.reset : methods.next}>
|
||||||
|
{methods.isLast ? 'Reset' : 'Next'}
|
||||||
|
</Button>
|
||||||
|
</stepperInstance.Stepper.Controls>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</stepperInstance.Stepper.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Content = ({ id }: { id: string }) => {
|
||||||
|
return (
|
||||||
|
<stepperInstance.Stepper.Panel className="h-50 content-center rounded border bg-slate-50 p-8">
|
||||||
|
<p className="text-xl font-normal">Content for {id}</p>
|
||||||
|
</stepperInstance.Stepper.Panel>
|
||||||
|
)
|
||||||
|
}
|
||||||
82
src/routes/stepper2.tsx
Normal file
82
src/routes/stepper2.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
import { CircularProgress } from '@/components/CircularProgress'
|
||||||
|
import { defineStepper } from '@/components/stepper' // Tu wrapper
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/stepper2')({
|
||||||
|
component: MobileStepperView,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 1. Definimos los pasos igual que siempre
|
||||||
|
const myStepper = defineStepper(
|
||||||
|
{ id: 'contact', title: 'Contact Details' },
|
||||||
|
{ id: 'shipping', title: 'Shipping Information' },
|
||||||
|
{ id: 'billing', title: 'Billing Address' },
|
||||||
|
{ id: 'review', title: 'Payment Review' },
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function MobileStepperView() {
|
||||||
|
return (
|
||||||
|
// Usa el Provider del wrapper para tener el contexto
|
||||||
|
<myStepper.Stepper.Provider>
|
||||||
|
{({ methods }) => {
|
||||||
|
// Calculamos índices para el gráfico
|
||||||
|
const currentIndex =
|
||||||
|
methods.all.findIndex((s) => s.id === methods.current.id) + 1
|
||||||
|
const totalSteps = methods.all.length
|
||||||
|
const nextStep = methods.all[currentIndex] // El paso siguiente (si existe)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full flex-col bg-white p-4">
|
||||||
|
{/* --- AQUÍ ESTÁ LA MAGIA (Tu UI Personalizada) --- */}
|
||||||
|
<div className="mb-6 flex items-center gap-4">
|
||||||
|
{/* El Gráfico Circular */}
|
||||||
|
<CircularProgress current={currentIndex} total={totalSteps} />
|
||||||
|
|
||||||
|
{/* Los Textos */}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<h2 className="text-lg font-bold text-slate-900">
|
||||||
|
{methods.current.title}
|
||||||
|
</h2>
|
||||||
|
{nextStep && (
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Next: {nextStep.title}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* ----------------------------------------------- */}
|
||||||
|
|
||||||
|
{/* El contenido de los pasos (Switch) */}
|
||||||
|
<div className="flex-1">
|
||||||
|
{methods.switch({
|
||||||
|
contact: () => <div>Formulario Contacto...</div>,
|
||||||
|
shipping: () => <div>Formulario Envío...</div>,
|
||||||
|
billing: () => <div>Formulario Facturación...</div>,
|
||||||
|
review: () => <div>Resumen...</div>,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Controles de Navegación (Footer) */}
|
||||||
|
<div className="mt-4 flex justify-between">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={methods.prev}
|
||||||
|
disabled={methods.isFirst}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="bg-red-500 text-white hover:bg-red-600"
|
||||||
|
onClick={methods.next}
|
||||||
|
>
|
||||||
|
{methods.isLast ? 'Finish' : 'Next'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</myStepper.Stepper.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user