devjas1
commited on
Commit
·
80b95e8
0
Parent(s):
feat: Inital commit from recovered state
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +0 -0
- .gitignore +13 -0
- .replit +39 -0
- LICENSE.md +201 -0
- attached_assets/Pasted-Here-s-a-high-to-medium-level-outline-formatted-roadmap-for-turning-your-existing-embeddinggemma-30-1757473542795_1757473542795.txt +82 -0
- cli.py +100 -0
- client/index.html +16 -0
- client/src/App.tsx +31 -0
- client/src/components/sections/commands.tsx +117 -0
- client/src/components/sections/configuration.tsx +105 -0
- client/src/components/sections/deployment.tsx +124 -0
- client/src/components/sections/modules.tsx +395 -0
- client/src/components/sections/overview.tsx +154 -0
- client/src/components/ui/accordion.tsx +56 -0
- client/src/components/ui/alert-dialog.tsx +139 -0
- client/src/components/ui/alert.tsx +59 -0
- client/src/components/ui/aspect-ratio.tsx +5 -0
- client/src/components/ui/avatar.tsx +50 -0
- client/src/components/ui/badge.tsx +36 -0
- client/src/components/ui/breadcrumb.tsx +115 -0
- client/src/components/ui/button.tsx +56 -0
- client/src/components/ui/calendar.tsx +68 -0
- client/src/components/ui/card.tsx +79 -0
- client/src/components/ui/carousel.tsx +260 -0
- client/src/components/ui/chart.tsx +365 -0
- client/src/components/ui/checkbox.tsx +28 -0
- client/src/components/ui/collapsible.tsx +11 -0
- client/src/components/ui/command.tsx +151 -0
- client/src/components/ui/context-menu.tsx +198 -0
- client/src/components/ui/dialog.tsx +122 -0
- client/src/components/ui/drawer.tsx +118 -0
- client/src/components/ui/dropdown-menu.tsx +198 -0
- client/src/components/ui/form.tsx +178 -0
- client/src/components/ui/hover-card.tsx +29 -0
- client/src/components/ui/input-otp.tsx +69 -0
- client/src/components/ui/input.tsx +22 -0
- client/src/components/ui/label.tsx +24 -0
- client/src/components/ui/menubar.tsx +256 -0
- client/src/components/ui/navigation-menu.tsx +128 -0
- client/src/components/ui/navigation.tsx +72 -0
- client/src/components/ui/pagination.tsx +117 -0
- client/src/components/ui/popover.tsx +29 -0
- client/src/components/ui/progress.tsx +28 -0
- client/src/components/ui/radio-group.tsx +42 -0
- client/src/components/ui/resizable.tsx +45 -0
- client/src/components/ui/scroll-area.tsx +46 -0
- client/src/components/ui/select.tsx +160 -0
- client/src/components/ui/separator.tsx +29 -0
- client/src/components/ui/sheet.tsx +140 -0
- client/src/components/ui/sidebar.tsx +771 -0
.gitattributes
ADDED
File without changes
|
.gitignore
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
node_modules
|
2 |
+
dist
|
3 |
+
.DS_Store
|
4 |
+
server/public
|
5 |
+
vite.config.ts.*
|
6 |
+
*.tar.gz
|
7 |
+
.agent_state*
|
8 |
+
.vscode
|
9 |
+
.latest*
|
10 |
+
rapid*
|
11 |
+
ac4a*
|
12 |
+
*.bin
|
13 |
+
*.gguf
|
.replit
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
modules = ["nodejs-20", "web", "postgresql-16"]
|
2 |
+
run = "npm run dev"
|
3 |
+
hidden = [".config", ".git", "generated-icon.png", "node_modules", "dist"]
|
4 |
+
|
5 |
+
[nix]
|
6 |
+
channel = "stable-24_05"
|
7 |
+
|
8 |
+
[deployment]
|
9 |
+
deploymentTarget = "autoscale"
|
10 |
+
build = ["npm", "run", "build"]
|
11 |
+
run = ["npm", "run", "start"]
|
12 |
+
|
13 |
+
[[ports]]
|
14 |
+
localPort = 5000
|
15 |
+
externalPort = 80
|
16 |
+
|
17 |
+
[env]
|
18 |
+
PORT = "5000"
|
19 |
+
|
20 |
+
[workflows]
|
21 |
+
runButton = "Project"
|
22 |
+
|
23 |
+
[[workflows.workflow]]
|
24 |
+
name = "Project"
|
25 |
+
mode = "parallel"
|
26 |
+
author = "agent"
|
27 |
+
|
28 |
+
[[workflows.workflow.tasks]]
|
29 |
+
task = "workflow.run"
|
30 |
+
args = "Start application"
|
31 |
+
|
32 |
+
[[workflows.workflow]]
|
33 |
+
name = "Start application"
|
34 |
+
author = "agent"
|
35 |
+
|
36 |
+
[[workflows.workflow.tasks]]
|
37 |
+
task = "shell.exec"
|
38 |
+
args = "npm run dev"
|
39 |
+
waitForPort = 5000
|
LICENSE.md
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Apache License
|
2 |
+
Version 2.0, January 2004
|
3 |
+
http://www.apache.org/licenses/
|
4 |
+
|
5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6 |
+
|
7 |
+
1. Definitions.
|
8 |
+
|
9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
11 |
+
|
12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13 |
+
the copyright owner that is granting the License.
|
14 |
+
|
15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
16 |
+
other entities that control, are controlled by, or are under common
|
17 |
+
control with that entity. For the purposes of this definition,
|
18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
19 |
+
direction or management of such entity, whether by contract or
|
20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22 |
+
|
23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24 |
+
exercising permissions granted by this License.
|
25 |
+
|
26 |
+
"Source" form shall mean the preferred form for making modifications,
|
27 |
+
including but not limited to software source code, documentation
|
28 |
+
source, and configuration files.
|
29 |
+
|
30 |
+
"Object" form shall mean any form resulting from mechanical
|
31 |
+
transformation or translation of a Source form, including but
|
32 |
+
not limited to compiled object code, generated documentation,
|
33 |
+
and conversions to other media types.
|
34 |
+
|
35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
36 |
+
Object form, made available under the License, as indicated by a
|
37 |
+
copyright notice that is included in or attached to the work
|
38 |
+
(an example is provided in the Appendix below).
|
39 |
+
|
40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41 |
+
form, that is based on (or derived from) the Work and for which the
|
42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
44 |
+
of this License, Derivative Works shall not include works that remain
|
45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46 |
+
the Work and Derivative Works thereof.
|
47 |
+
|
48 |
+
"Contribution" shall mean any work of authorship, including
|
49 |
+
the original version of the Work and any modifications or additions
|
50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
54 |
+
means any form of electronic, verbal, or written communication sent
|
55 |
+
to the Licensor or its representatives, including but not limited to
|
56 |
+
communication on electronic mailing lists, source code control systems,
|
57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
59 |
+
excluding communication that is conspicuously marked or otherwise
|
60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
61 |
+
|
62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
64 |
+
subsequently incorporated within the Work.
|
65 |
+
|
66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
71 |
+
Work and such Derivative Works in Source or Object form.
|
72 |
+
|
73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76 |
+
(except as stated in this section) patent license to make, have made,
|
77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78 |
+
where such license applies only to those patent claims licensable
|
79 |
+
by such Contributor that are necessarily infringed by their
|
80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
82 |
+
institute patent litigation against any entity (including a
|
83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84 |
+
or a Contribution incorporated within the Work constitutes direct
|
85 |
+
or contributory patent infringement, then any patent licenses
|
86 |
+
granted to You under this License for that Work shall terminate
|
87 |
+
as of the date such litigation is filed.
|
88 |
+
|
89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
90 |
+
Work or Derivative Works thereof in any medium, with or without
|
91 |
+
modifications, and in Source or Object form, provided that You
|
92 |
+
meet the following conditions:
|
93 |
+
|
94 |
+
(a) You must give any other recipients of the Work or
|
95 |
+
Derivative Works a copy of this License; and
|
96 |
+
|
97 |
+
(b) You must cause any modified files to carry prominent notices
|
98 |
+
stating that You changed the files; and
|
99 |
+
|
100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
101 |
+
that You distribute, all copyright, patent, trademark, and
|
102 |
+
attribution notices from the Source form of the Work,
|
103 |
+
excluding those notices that do not pertain to any part of
|
104 |
+
the Derivative Works; and
|
105 |
+
|
106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107 |
+
distribution, then any Derivative Works that You distribute must
|
108 |
+
include a readable copy of the attribution notices contained
|
109 |
+
within such NOTICE file, excluding those notices that do not
|
110 |
+
pertain to any part of the Derivative Works, in at least one
|
111 |
+
of the following places: within a NOTICE text file distributed
|
112 |
+
as part of the Derivative Works; within the Source form or
|
113 |
+
documentation, if provided along with the Derivative Works; or,
|
114 |
+
within a display generated by the Derivative Works, if and
|
115 |
+
wherever such third-party notices normally appear. The contents
|
116 |
+
of the NOTICE file are for informational purposes only and
|
117 |
+
do not modify the License. You may add Your own attribution
|
118 |
+
notices within Derivative Works that You distribute, alongside
|
119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
120 |
+
that such additional attribution notices cannot be construed
|
121 |
+
as modifying the License.
|
122 |
+
|
123 |
+
You may add Your own copyright statement to Your modifications and
|
124 |
+
may provide additional or different license terms and conditions
|
125 |
+
for use, reproduction, or distribution of Your modifications, or
|
126 |
+
for any such Derivative Works as a whole, provided Your use,
|
127 |
+
reproduction, and distribution of the Work otherwise complies with
|
128 |
+
the conditions stated in this License.
|
129 |
+
|
130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
132 |
+
by You to the Licensor shall be under the terms and conditions of
|
133 |
+
this License, without any additional terms or conditions.
|
134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135 |
+
the terms of any separate license agreement you may have executed
|
136 |
+
with Licensor regarding such Contributions.
|
137 |
+
|
138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
140 |
+
except as required for reasonable and customary use in describing the
|
141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
142 |
+
|
143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144 |
+
agreed to in writing, Licensor provides the Work (and each
|
145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147 |
+
implied, including, without limitation, any warranties or conditions
|
148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150 |
+
appropriateness of using or redistributing the Work and assume any
|
151 |
+
risks associated with Your exercise of permissions under this License.
|
152 |
+
|
153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
154 |
+
whether in tort (including negligence), contract, or otherwise,
|
155 |
+
unless required by applicable law (such as deliberate and grossly
|
156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157 |
+
liable to You for damages, including any direct, indirect, special,
|
158 |
+
incidental, or consequential damages of any character arising as a
|
159 |
+
result of this License or out of the use or inability to use the
|
160 |
+
Work (including but not limited to damages for loss of goodwill,
|
161 |
+
work stoppage, computer failure or malfunction, or any and all
|
162 |
+
other commercial damages or losses), even if such Contributor
|
163 |
+
has been advised of the possibility of such damages.
|
164 |
+
|
165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168 |
+
or other liability obligations and/or rights consistent with this
|
169 |
+
License. However, in accepting such obligations, You may act only
|
170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171 |
+
of any other Contributor, and only if You agree to indemnify,
|
172 |
+
defend, and hold each Contributor harmless for any liability
|
173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
174 |
+
of your accepting any such warranty or additional liability.
|
175 |
+
|
176 |
+
END OF TERMS AND CONDITIONS
|
177 |
+
|
178 |
+
APPENDIX: How to apply the Apache License to your work.
|
179 |
+
|
180 |
+
To apply the Apache License to your work, attach the following
|
181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
182 |
+
replaced with your own identifying information. (Don't include
|
183 |
+
the brackets!) The text should be enclosed in the appropriate
|
184 |
+
comment syntax for the file format. We also recommend that a
|
185 |
+
file or class name and description of purpose be included on the
|
186 |
+
same "printed page" as the copyright notice for easier
|
187 |
+
identification within third-party archives.
|
188 |
+
|
189 |
+
Copyright [yyyy] [name of copyright owner]
|
190 |
+
|
191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
192 |
+
you may not use this file except in compliance with the License.
|
193 |
+
You may obtain a copy of the License at
|
194 |
+
|
195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
196 |
+
|
197 |
+
Unless required by applicable law or agreed to in writing, software
|
198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200 |
+
See the License for the specific language governing permissions and
|
201 |
+
limitations under the License.
|
attached_assets/Pasted-Here-s-a-high-to-medium-level-outline-formatted-roadmap-for-turning-your-existing-embeddinggemma-30-1757473542795_1757473542795.txt
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Here’s a high-to-medium-level, outline-formatted roadmap for turning your existing embeddinggemma-300m setup into a unified CLI tool powered by Phi-2. No code snippets—just clear steps and gap-checks.
|
2 |
+
|
3 |
+
1. Define Your Modular Surface
|
4 |
+
Embedder
|
5 |
+
|
6 |
+
Uses google/embeddinggemma-300m to turn text into vectors
|
7 |
+
|
8 |
+
Retriever
|
9 |
+
|
10 |
+
Indexes and searches embeddings (e.g., FAISS or Chroma)
|
11 |
+
|
12 |
+
Generator
|
13 |
+
|
14 |
+
Runs Phi-2 (2.7 B) for response generation based on retrieved context
|
15 |
+
|
16 |
+
CLI
|
17 |
+
|
18 |
+
Ties everything together with a command-line interface (e.g., Typer)
|
19 |
+
|
20 |
+
2. Prepare Your Conda Environment
|
21 |
+
Confirm sentence-transformers and transformers are installed
|
22 |
+
|
23 |
+
Add your vector store library (FAISS/Chroma)
|
24 |
+
|
25 |
+
Install any quantization/runtime helpers for Phi-2 (GGUF, bitsandbytes, etc.)
|
26 |
+
|
27 |
+
Allocate model cache paths under your project (e.g., ./models/)
|
28 |
+
|
29 |
+
3. Acquire and Optimize Phi-2
|
30 |
+
Download the Phi-2 weights from Hugging Face or Azure AI Studio
|
31 |
+
|
32 |
+
Quantize to Q4/Q5 to fit within ~6–8 GB of RAM on your i5/16 GB rig
|
33 |
+
|
34 |
+
Validate loading speed and memory footprint in isolation before integration
|
35 |
+
|
36 |
+
4. Sketch Your Data Flow
|
37 |
+
User Input: typed command + query text
|
38 |
+
|
39 |
+
Embedding: call Gemma → vector
|
40 |
+
|
41 |
+
Retrieval: query vector → top-k document vectors
|
42 |
+
|
43 |
+
Context Assembly: stitch retrieved snippets
|
44 |
+
|
45 |
+
Generation: feed context + user prompt into Phi-2 → text answer
|
46 |
+
|
47 |
+
Output: print or save response
|
48 |
+
|
49 |
+
5. Fill Logic Holes
|
50 |
+
You need a vector store backend; plain in-memory lists won’t scale
|
51 |
+
|
52 |
+
Chunk larger documents before embedding (e.g., 500 token windows)
|
53 |
+
|
54 |
+
Watch Phi-2’s context window (typically ~2 K tokens) and trim if needed
|
55 |
+
|
56 |
+
Plan caching: don’t re-embed the same files on every run
|
57 |
+
|
58 |
+
Design a config file for model paths, thresholds, and CLI defaults
|
59 |
+
|
60 |
+
6. Organize Your Directory
|
61 |
+
Code
|
62 |
+
gemma-phi2-cli/
|
63 |
+
├── models/
|
64 |
+
│ ├── embeddinggemma-300m/
|
65 |
+
│ └── phi2-quantized/
|
66 |
+
├── data/
|
67 |
+
│ └── index/ # FAISS/Chroma files
|
68 |
+
├── src/
|
69 |
+
│ ├── embedder.py # wraps Gemma
|
70 |
+
│ ├── retriever.py # builds & queries index
|
71 |
+
│ ├── generator.py # wraps Phi-2
|
72 |
+
│ └── cli.py # Typer entry point
|
73 |
+
├── config.yaml # model locations, hyperparams
|
74 |
+
└── README.md
|
75 |
+
7. Plan Your CLI Commands
|
76 |
+
init → create or update index from a target directory
|
77 |
+
|
78 |
+
search → semantic lookup, returns file names + scores
|
79 |
+
|
80 |
+
ask → retrieval + generation in one step
|
81 |
+
|
82 |
+
serve → optional local API for integrations
|
cli.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import typer
|
2 |
+
|
3 |
+
from src.embedder import embed_documents
|
4 |
+
from src.retriever import search_documents
|
5 |
+
from src.generator import generate_commit_message, fallback_commit_message
|
6 |
+
from src.diff_analyzer import get_staged_diff_chunks
|
7 |
+
from src.config_loader import load_config
|
8 |
+
|
9 |
+
|
10 |
+
# Initialize Typer app
|
11 |
+
app = typer.Typer()
|
12 |
+
config = load_config("config.yaml")
|
13 |
+
print("Config loaded:", config)
|
14 |
+
|
15 |
+
|
16 |
+
# ===== CLI Commands =====
|
17 |
+
# `init` — Index Documents
|
18 |
+
@app.command()
|
19 |
+
def init(path: str = typer.Argument(...)):
|
20 |
+
embeddings = embed_documents(path, config)
|
21 |
+
if embeddings:
|
22 |
+
typer.echo(f"Documents indexed successfully: {len(embeddings)} documents")
|
23 |
+
else:
|
24 |
+
typer.echo("No documents were indexed.")
|
25 |
+
|
26 |
+
|
27 |
+
# `search` — Semantic Lookup
|
28 |
+
@app.command()
|
29 |
+
def search(query: str):
|
30 |
+
"""
|
31 |
+
Perform a semantic search on indexed documents.
|
32 |
+
|
33 |
+
Args:
|
34 |
+
query (str): The search query string.
|
35 |
+
"""
|
36 |
+
results = search_documents(query, config)
|
37 |
+
for i, result in enumerate(results):
|
38 |
+
typer.echo(f"[{i+1}] {result}")
|
39 |
+
|
40 |
+
|
41 |
+
# `ask` — Retrieval-Augmented Generation (RAG)
|
42 |
+
@app.command()
|
43 |
+
def ask(query: str):
|
44 |
+
"""
|
45 |
+
Perform a retrieval-augmented generation (RAG) process to answer a user query.
|
46 |
+
|
47 |
+
Args:
|
48 |
+
query (str): The user query string.
|
49 |
+
"""
|
50 |
+
context = search_documents(query, config)
|
51 |
+
prompt = "\n".join(context) + f"\n\nUser question: {query}"
|
52 |
+
try:
|
53 |
+
response = generate_commit_message(prompt, config)
|
54 |
+
except (
|
55 |
+
RuntimeError
|
56 |
+
): # Replace with the specific exception type raised by generate_commit_message
|
57 |
+
response = fallback_commit_message([])
|
58 |
+
typer.echo(response)
|
59 |
+
|
60 |
+
|
61 |
+
# `commit` — Git Diff + Message Generation
|
62 |
+
@app.command()
|
63 |
+
def commit(preview: bool = True, apply: bool = False, dry_run: bool = False):
|
64 |
+
"""
|
65 |
+
Generate a commit message based on staged Git diff chunks.
|
66 |
+
|
67 |
+
Args:
|
68 |
+
preview (bool): If True, display the suggested commit message.
|
69 |
+
apply (bool): If True, apply the commit with the generated message.
|
70 |
+
dry_run (bool): If True, display staged files and chunks without generating a message.
|
71 |
+
"""
|
72 |
+
file_list, diff_chunks = get_staged_diff_chunks()
|
73 |
+
|
74 |
+
if dry_run:
|
75 |
+
typer.echo(f"Files staged: {len(file_list)}")
|
76 |
+
typer.echo(f"Chunks: {len(diff_chunks)}")
|
77 |
+
typer.echo(
|
78 |
+
f"Estimated tokens: {sum(len(chunk.split()) for chunk in diff_chunks)}"
|
79 |
+
)
|
80 |
+
return
|
81 |
+
|
82 |
+
prompt = "\n".join(diff_chunks) + "\n\nGenerate a commit message:"
|
83 |
+
try:
|
84 |
+
message = generate_commit_message(prompt, config)
|
85 |
+
except Exception:
|
86 |
+
message = fallback_commit_message(file_list)
|
87 |
+
|
88 |
+
if preview:
|
89 |
+
typer.echo(f"Suggested commit message:\n{message}")
|
90 |
+
if apply:
|
91 |
+
import subprocess
|
92 |
+
|
93 |
+
subprocess.run(["git", "commit", "-m", message])
|
94 |
+
|
95 |
+
|
96 |
+
from sentence_transformers import SentenceTransformer
|
97 |
+
|
98 |
+
model = SentenceTransformer("./models/embeddinggemma-300m")
|
99 |
+
emb = model.encode("Test string")
|
100 |
+
print("Embedding shape:", emb.shape) # Should be (768,)
|
client/index.html
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
6 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
7 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
8 |
+
<link href="https://fonts.googleapis.com/css2?family=Architects+Daughter&family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Fira+Code:[email protected]&family=Geist+Mono:[email protected]&family=Geist:[email protected]&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Lora:ital,wght@0,400..700;1,400..700&family=Merriweather:ital,opsz,wght@0,18..144,300..900;1,18..144,300..900&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Outfit:[email protected]&family=Oxanium:[email protected]&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100..900;1,100..900&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&family=Space+Grotesk:[email protected]&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
9 |
+
</head>
|
10 |
+
<body>
|
11 |
+
<div id="root"></div>
|
12 |
+
<script type="module" src="/src/main.tsx"></script>
|
13 |
+
<!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment -->
|
14 |
+
<script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
|
15 |
+
</body>
|
16 |
+
</html>
|
client/src/App.tsx
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Switch, Route } from "wouter";
|
2 |
+
import { queryClient } from "./lib/queryClient";
|
3 |
+
import { QueryClientProvider } from "@tanstack/react-query";
|
4 |
+
import { Toaster } from "@/components/ui/toaster";
|
5 |
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
6 |
+
import Dashboard from "./pages/dashboard";
|
7 |
+
import NotFound from "@/pages/not-found";
|
8 |
+
|
9 |
+
function Router() {
|
10 |
+
return (
|
11 |
+
<Switch>
|
12 |
+
<Route path="/" component={Dashboard} />
|
13 |
+
<Route component={NotFound} />
|
14 |
+
</Switch>
|
15 |
+
);
|
16 |
+
}
|
17 |
+
|
18 |
+
function App() {
|
19 |
+
return (
|
20 |
+
<QueryClientProvider client={queryClient}>
|
21 |
+
<TooltipProvider>
|
22 |
+
<div className="dark">
|
23 |
+
<Toaster />
|
24 |
+
<Router />
|
25 |
+
</div>
|
26 |
+
</TooltipProvider>
|
27 |
+
</QueryClientProvider>
|
28 |
+
);
|
29 |
+
}
|
30 |
+
|
31 |
+
export default App;
|
client/src/components/sections/commands.tsx
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Card } from "@/components/ui/card";
|
2 |
+
import { TerminalComponent } from "@/components/ui/terminal";
|
3 |
+
import { Terminal, Play, Settings } from "lucide-react";
|
4 |
+
|
5 |
+
export function CommandsSection() {
|
6 |
+
const coreCommands = [
|
7 |
+
{
|
8 |
+
command: "gemma-phi2-cli init [path]",
|
9 |
+
description: "Initialize or update index from target directory",
|
10 |
+
color: "text-accent"
|
11 |
+
},
|
12 |
+
{
|
13 |
+
command: "gemma-phi2-cli search [query]",
|
14 |
+
description: "Semantic lookup returning file names and scores",
|
15 |
+
color: "text-accent"
|
16 |
+
},
|
17 |
+
{
|
18 |
+
command: "gemma-phi2-cli ask [question]",
|
19 |
+
description: "Retrieval + generation in one step",
|
20 |
+
color: "text-accent"
|
21 |
+
},
|
22 |
+
{
|
23 |
+
command: "gemma-phi2-cli serve",
|
24 |
+
description: "Launch local API server for integrations",
|
25 |
+
color: "text-accent"
|
26 |
+
},
|
27 |
+
{
|
28 |
+
command: "gemma-phi2-cli commit [--preview|--apply]",
|
29 |
+
description: "Generate commit messages from staged changes",
|
30 |
+
color: "text-accent"
|
31 |
+
},
|
32 |
+
{
|
33 |
+
command: "gemma-phi2-cli commit --dry-run",
|
34 |
+
description: "Show file count, chunks, and token estimates",
|
35 |
+
color: "text-accent"
|
36 |
+
}
|
37 |
+
];
|
38 |
+
|
39 |
+
const utilityCommands = [
|
40 |
+
{
|
41 |
+
command: "gemma-phi2-cli status",
|
42 |
+
description: "Show current configuration and model status",
|
43 |
+
color: "text-accent"
|
44 |
+
},
|
45 |
+
{
|
46 |
+
command: "gemma-phi2-cli config [key] [value]",
|
47 |
+
description: "Get or set configuration parameters",
|
48 |
+
color: "text-accent"
|
49 |
+
},
|
50 |
+
{
|
51 |
+
command: "gemma-phi2-cli clear",
|
52 |
+
description: "Clear vector store and cached embeddings",
|
53 |
+
color: "text-accent"
|
54 |
+
},
|
55 |
+
{
|
56 |
+
command: "gemma-phi2-cli --help",
|
57 |
+
description: "Display detailed help and usage examples",
|
58 |
+
color: "text-accent"
|
59 |
+
},
|
60 |
+
{
|
61 |
+
command: "gemma-phi2-cli commit --preview",
|
62 |
+
description: "Preview generated commit message without applying",
|
63 |
+
color: "text-accent"
|
64 |
+
},
|
65 |
+
{
|
66 |
+
command: "gemma-phi2-cli config --list-presets",
|
67 |
+
description: "Show available commit style presets",
|
68 |
+
color: "text-accent"
|
69 |
+
}
|
70 |
+
];
|
71 |
+
|
72 |
+
return (
|
73 |
+
<section id="commands" className="space-y-6">
|
74 |
+
<h2 className="text-2xl font-bold">CLI Commands</h2>
|
75 |
+
|
76 |
+
<div className="grid gap-6">
|
77 |
+
<TerminalComponent />
|
78 |
+
|
79 |
+
<div className="grid md:grid-cols-2 gap-6">
|
80 |
+
<Card className="p-4 border-border">
|
81 |
+
<h4 className="font-semibold mb-4 flex items-center">
|
82 |
+
<Play className="mr-2 text-green-400 w-4 h-4" />
|
83 |
+
Core Commands
|
84 |
+
</h4>
|
85 |
+
<div className="space-y-3 text-sm">
|
86 |
+
{coreCommands.map((cmd, index) => (
|
87 |
+
<Card key={index} className="bg-secondary/50 p-3 border-border">
|
88 |
+
<div className={`font-mono ${cmd.color} mb-1`} data-testid={`command-${index}`}>
|
89 |
+
{cmd.command}
|
90 |
+
</div>
|
91 |
+
<div className="text-muted-foreground">{cmd.description}</div>
|
92 |
+
</Card>
|
93 |
+
))}
|
94 |
+
</div>
|
95 |
+
</Card>
|
96 |
+
|
97 |
+
<Card className="p-4 border-border">
|
98 |
+
<h4 className="font-semibold mb-4 flex items-center">
|
99 |
+
<Settings className="mr-2 text-yellow-400 w-4 h-4" />
|
100 |
+
Utility Commands
|
101 |
+
</h4>
|
102 |
+
<div className="space-y-3 text-sm">
|
103 |
+
{utilityCommands.map((cmd, index) => (
|
104 |
+
<Card key={index} className="bg-secondary/50 p-3 border-border">
|
105 |
+
<div className={`font-mono ${cmd.color} mb-1`} data-testid={`utility-command-${index}`}>
|
106 |
+
{cmd.command}
|
107 |
+
</div>
|
108 |
+
<div className="text-muted-foreground">{cmd.description}</div>
|
109 |
+
</Card>
|
110 |
+
))}
|
111 |
+
</div>
|
112 |
+
</Card>
|
113 |
+
</div>
|
114 |
+
</div>
|
115 |
+
</section>
|
116 |
+
);
|
117 |
+
}
|
client/src/components/sections/configuration.tsx
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Card } from "@/components/ui/card";
|
2 |
+
import { Button } from "@/components/ui/button";
|
3 |
+
import { FileCode, Edit, Save } from "lucide-react";
|
4 |
+
|
5 |
+
export function ConfigurationSection() {
|
6 |
+
const configContent = `# Gemma-Phi2-CLI Configuration
|
7 |
+
models:
|
8 |
+
embedder:
|
9 |
+
path: "./models/embeddinggemma-300m"
|
10 |
+
batch_size: 32
|
11 |
+
device: "cpu"
|
12 |
+
generator:
|
13 |
+
path: "./models/phi2-quantized.gguf"
|
14 |
+
quantization: "Q4_0"
|
15 |
+
max_tokens: 2048
|
16 |
+
temperature: 0.7
|
17 |
+
format: "gguf"
|
18 |
+
|
19 |
+
embedding:
|
20 |
+
dim: 768
|
21 |
+
truncate_to: 128
|
22 |
+
sentence_transformers: true
|
23 |
+
|
24 |
+
vector_store:
|
25 |
+
type: "faiss"
|
26 |
+
index_path: "./data/index"
|
27 |
+
persistence: true
|
28 |
+
|
29 |
+
retrieval:
|
30 |
+
top_k: 5
|
31 |
+
similarity_threshold: 0.75
|
32 |
+
rerank: false
|
33 |
+
|
34 |
+
performance:
|
35 |
+
cache_embeddings: true
|
36 |
+
chunk_size: 500
|
37 |
+
chunk_overlap: 50
|
38 |
+
|
39 |
+
commit:
|
40 |
+
tone: "imperative"
|
41 |
+
style: "conventional" # Options: conventional | descriptive | terse
|
42 |
+
max_length: 72
|
43 |
+
preview_by_default: true
|
44 |
+
fallback_enabled: true
|
45 |
+
dry_run_default: false
|
46 |
+
|
47 |
+
logging:
|
48 |
+
level: "INFO"
|
49 |
+
file: "./logs/gemma-phi2.log"
|
50 |
+
verbose: true
|
51 |
+
telemetry: false
|
52 |
+
rotate_logs: true
|
53 |
+
|
54 |
+
environment:
|
55 |
+
conda_env: "gemma-phi2-cli"
|
56 |
+
python_version: "3.10"
|
57 |
+
platform: "windows"`;
|
58 |
+
|
59 |
+
return (
|
60 |
+
<section id="configuration" className="space-y-6">
|
61 |
+
<h2 className="text-2xl font-bold">Configuration Management</h2>
|
62 |
+
|
63 |
+
<Card className="border-border">
|
64 |
+
<div className="flex items-center justify-between p-4 border-b border-border">
|
65 |
+
<h3 className="font-semibold flex items-center">
|
66 |
+
<FileCode className="mr-2 text-yellow-400 w-4 h-4" />
|
67 |
+
config.yaml
|
68 |
+
</h3>
|
69 |
+
<div className="flex items-center space-x-2">
|
70 |
+
<Button variant="secondary" size="sm" data-testid="button-edit-config">
|
71 |
+
<Edit className="mr-1 w-3 h-3" />
|
72 |
+
Edit
|
73 |
+
</Button>
|
74 |
+
<Button size="sm" data-testid="button-save-config">
|
75 |
+
<Save className="mr-1 w-3 h-3" />
|
76 |
+
Save
|
77 |
+
</Button>
|
78 |
+
</div>
|
79 |
+
</div>
|
80 |
+
<div className="p-4">
|
81 |
+
<div className="bg-black/50 rounded-lg p-4 font-mono text-sm overflow-x-auto">
|
82 |
+
<div className="syntax-highlight">
|
83 |
+
{configContent.split('\n').map((line, index) => {
|
84 |
+
let className = '';
|
85 |
+
if (line.trim().startsWith('#')) {
|
86 |
+
className = 'comment';
|
87 |
+
} else if (line.includes(':') && !line.trim().startsWith(' ')) {
|
88 |
+
className = 'keyword';
|
89 |
+
} else if (line.includes('"')) {
|
90 |
+
className = 'string';
|
91 |
+
}
|
92 |
+
|
93 |
+
return (
|
94 |
+
<div key={index} className={className} data-testid={`config-line-${index}`}>
|
95 |
+
{line || '\u00A0'}
|
96 |
+
</div>
|
97 |
+
);
|
98 |
+
})}
|
99 |
+
</div>
|
100 |
+
</div>
|
101 |
+
</div>
|
102 |
+
</Card>
|
103 |
+
</section>
|
104 |
+
);
|
105 |
+
}
|
client/src/components/sections/deployment.tsx
ADDED
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Card } from "@/components/ui/card";
|
2 |
+
import { Button } from "@/components/ui/button";
|
3 |
+
import { Progress } from "@/components/ui/progress";
|
4 |
+
import { Server, Download, Rocket } from "lucide-react";
|
5 |
+
|
6 |
+
export function DeploymentSection() {
|
7 |
+
const systemRequirements = [
|
8 |
+
{ label: "CPU", value: "Intel i5+ / AMD Ryzen 5+" },
|
9 |
+
{ label: "RAM", value: "16 GB minimum" },
|
10 |
+
{ label: "Storage", value: "10 GB+ SSD" },
|
11 |
+
{ label: "Platform", value: "Windows 11 (primary)" },
|
12 |
+
{ label: "Python", value: "3.10 (Conda managed)" },
|
13 |
+
{ label: "Models", value: "CPU-only execution" }
|
14 |
+
];
|
15 |
+
|
16 |
+
const installationSteps = [
|
17 |
+
{ name: "Conda Environment", progress: 100, status: "✓ Complete", color: "bg-green-400" },
|
18 |
+
{ name: "sentence-transformers", progress: 100, status: "✓ Complete", color: "bg-green-400" },
|
19 |
+
{ name: "FAISS + Typer + PyYAML", progress: 100, status: "✓ Complete", color: "bg-green-400" },
|
20 |
+
{ name: "EmbeddingGemma-300m", progress: 75, status: "⟳ Downloading", color: "bg-yellow-400" },
|
21 |
+
{ name: "Phi-2 GGUF (Q4_0)", progress: 0, status: "⏳ Pending", color: "bg-muted" }
|
22 |
+
];
|
23 |
+
|
24 |
+
const quickStartSteps = [
|
25 |
+
{
|
26 |
+
number: 1,
|
27 |
+
title: "Conda Setup",
|
28 |
+
commands: ["conda env create -f environment.yml", "conda activate gemma-phi2-cli", "pip install -e ."],
|
29 |
+
color: "bg-primary"
|
30 |
+
},
|
31 |
+
{
|
32 |
+
number: 2,
|
33 |
+
title: "Model Download",
|
34 |
+
commands: ["python download_models.py", "# Downloads EmbeddingGemma + Phi-2 GGUF"],
|
35 |
+
color: "bg-accent"
|
36 |
+
},
|
37 |
+
{
|
38 |
+
number: 3,
|
39 |
+
title: "CLI Usage",
|
40 |
+
commands: ["gemma-phi2-cli init ./my-project", "git add .", "gemma-phi2-cli commit --preview"],
|
41 |
+
color: "bg-green-400"
|
42 |
+
}
|
43 |
+
];
|
44 |
+
|
45 |
+
return (
|
46 |
+
<section id="deployment" className="space-y-6">
|
47 |
+
<h2 className="text-2xl font-bold">Deployment & Setup</h2>
|
48 |
+
|
49 |
+
<div className="grid md:grid-cols-2 gap-6">
|
50 |
+
{/* System Requirements */}
|
51 |
+
<Card className="p-4 border-border">
|
52 |
+
<h3 className="font-semibold mb-4 flex items-center">
|
53 |
+
<Server className="mr-2 text-blue-400 w-4 h-4" />
|
54 |
+
System Requirements
|
55 |
+
</h3>
|
56 |
+
<div className="space-y-3 text-sm">
|
57 |
+
{systemRequirements.map((req, index) => (
|
58 |
+
<div key={index} className="flex justify-between items-center py-2 border-b border-border/50">
|
59 |
+
<span className="text-muted-foreground">{req.label}</span>
|
60 |
+
<span className="font-medium" data-testid={`requirement-${req.label.toLowerCase()}`}>
|
61 |
+
{req.value}
|
62 |
+
</span>
|
63 |
+
</div>
|
64 |
+
))}
|
65 |
+
</div>
|
66 |
+
</Card>
|
67 |
+
|
68 |
+
{/* Installation Progress */}
|
69 |
+
<Card className="p-4 border-border">
|
70 |
+
<h3 className="font-semibold mb-4 flex items-center">
|
71 |
+
<Download className="mr-2 text-green-400 w-4 h-4" />
|
72 |
+
Installation Progress
|
73 |
+
</h3>
|
74 |
+
<div className="space-y-4">
|
75 |
+
{installationSteps.map((step, index) => (
|
76 |
+
<div key={index} className="space-y-2">
|
77 |
+
<div className="flex justify-between text-sm">
|
78 |
+
<span>{step.name}</span>
|
79 |
+
<span className={
|
80 |
+
step.status.includes("Complete") ? "text-green-400" :
|
81 |
+
step.status.includes("Downloading") ? "text-yellow-400" :
|
82 |
+
"text-muted-foreground"
|
83 |
+
} data-testid={`install-status-${index}`}>
|
84 |
+
{step.status}
|
85 |
+
</span>
|
86 |
+
</div>
|
87 |
+
<Progress
|
88 |
+
value={step.progress}
|
89 |
+
className="w-full h-2"
|
90 |
+
data-testid={`install-progress-${index}`}
|
91 |
+
/>
|
92 |
+
</div>
|
93 |
+
))}
|
94 |
+
</div>
|
95 |
+
</Card>
|
96 |
+
</div>
|
97 |
+
|
98 |
+
{/* Quick Start Guide */}
|
99 |
+
<Card className="p-6 border-border">
|
100 |
+
<h3 className="font-semibold mb-4 flex items-center">
|
101 |
+
<Rocket className="mr-2 text-primary w-4 h-4" />
|
102 |
+
Quick Start Guide
|
103 |
+
</h3>
|
104 |
+
<div className="grid md:grid-cols-3 gap-6">
|
105 |
+
{quickStartSteps.map((step, index) => (
|
106 |
+
<div key={index} className="space-y-3">
|
107 |
+
<div className={`w-8 h-8 ${step.color} rounded-full flex items-center justify-center text-black font-bold`}>
|
108 |
+
{step.number}
|
109 |
+
</div>
|
110 |
+
<h4 className="font-medium">{step.title}</h4>
|
111 |
+
<Card className="bg-black/50 p-2 font-mono text-sm border-border">
|
112 |
+
{step.commands.map((command, cmdIndex) => (
|
113 |
+
<div key={cmdIndex} data-testid={`quickstart-command-${index}-${cmdIndex}`}>
|
114 |
+
{command}
|
115 |
+
</div>
|
116 |
+
))}
|
117 |
+
</Card>
|
118 |
+
</div>
|
119 |
+
))}
|
120 |
+
</div>
|
121 |
+
</Card>
|
122 |
+
</section>
|
123 |
+
);
|
124 |
+
}
|
client/src/components/sections/modules.tsx
ADDED
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Card } from "@/components/ui/card";
|
2 |
+
import { Input } from "@/components/ui/input";
|
3 |
+
import { Label } from "@/components/ui/label";
|
4 |
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
5 |
+
import { Badge } from "@/components/ui/badge";
|
6 |
+
import { Brain, Search, Wand2, GitBranch, ChevronDown, CheckCircle, Clock } from "lucide-react";
|
7 |
+
import { useState } from "react";
|
8 |
+
|
9 |
+
export function ModulesSection() {
|
10 |
+
const [expandedModules, setExpandedModules] = useState<Set<string>>(new Set(["embedder"]));
|
11 |
+
|
12 |
+
const toggleModule = (moduleId: string) => {
|
13 |
+
const newExpanded = new Set(expandedModules);
|
14 |
+
if (newExpanded.has(moduleId)) {
|
15 |
+
newExpanded.delete(moduleId);
|
16 |
+
} else {
|
17 |
+
newExpanded.add(moduleId);
|
18 |
+
}
|
19 |
+
setExpandedModules(newExpanded);
|
20 |
+
};
|
21 |
+
|
22 |
+
return (
|
23 |
+
<section id="modules" className="space-y-6">
|
24 |
+
<h2 className="text-2xl font-bold">Module Configuration</h2>
|
25 |
+
|
26 |
+
{/* Embedder Module */}
|
27 |
+
<Card className="border-border overflow-hidden">
|
28 |
+
<div className="p-4 bg-secondary/30 border-b border-border">
|
29 |
+
<div className="flex items-center justify-between">
|
30 |
+
<div className="flex items-center space-x-3">
|
31 |
+
<Brain className="text-primary w-5 h-5" />
|
32 |
+
<h3 className="font-semibold">Embedder Module</h3>
|
33 |
+
<Badge className="bg-green-500/20 text-green-400 hover:bg-green-500/30">
|
34 |
+
Active
|
35 |
+
</Badge>
|
36 |
+
</div>
|
37 |
+
<button
|
38 |
+
onClick={() => toggleModule("embedder")}
|
39 |
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
40 |
+
data-testid="toggle-embedder"
|
41 |
+
>
|
42 |
+
<ChevronDown
|
43 |
+
className={`w-4 h-4 transition-transform ${
|
44 |
+
expandedModules.has("embedder") ? "rotate-180" : ""
|
45 |
+
}`}
|
46 |
+
/>
|
47 |
+
</button>
|
48 |
+
</div>
|
49 |
+
</div>
|
50 |
+
{expandedModules.has("embedder") && (
|
51 |
+
<div className="p-4 space-y-4">
|
52 |
+
<div className="grid md:grid-cols-2 gap-4">
|
53 |
+
<div>
|
54 |
+
<Label htmlFor="embedder-path" className="text-sm font-medium mb-2 block">Model Path</Label>
|
55 |
+
<Input
|
56 |
+
id="embedder-path"
|
57 |
+
type="text"
|
58 |
+
defaultValue="./models/embeddinggemma-300m/"
|
59 |
+
className="font-mono"
|
60 |
+
data-testid="input-embedder-path"
|
61 |
+
/>
|
62 |
+
</div>
|
63 |
+
<div>
|
64 |
+
<Label htmlFor="embedder-batch" className="text-sm font-medium mb-2 block">Batch Size</Label>
|
65 |
+
<Input
|
66 |
+
id="embedder-batch"
|
67 |
+
type="number"
|
68 |
+
defaultValue="32"
|
69 |
+
data-testid="input-embedder-batch"
|
70 |
+
/>
|
71 |
+
</div>
|
72 |
+
</div>
|
73 |
+
<Card className="bg-muted/20 p-4 border-border">
|
74 |
+
<h4 className="font-medium mb-2 flex items-center">
|
75 |
+
<i className="fas fa-code mr-2 text-accent"></i>
|
76 |
+
Implementation Status
|
77 |
+
</h4>
|
78 |
+
<div className="space-y-2 text-sm">
|
79 |
+
<div className="flex items-center space-x-2">
|
80 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
81 |
+
<span>Model loading interface</span>
|
82 |
+
</div>
|
83 |
+
<div className="flex items-center space-x-2">
|
84 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
85 |
+
<span>Text preprocessing pipeline</span>
|
86 |
+
</div>
|
87 |
+
<div className="flex items-center space-x-2">
|
88 |
+
<Clock className="text-yellow-400 w-4 h-4" />
|
89 |
+
<span>Batch processing optimization</span>
|
90 |
+
</div>
|
91 |
+
</div>
|
92 |
+
</Card>
|
93 |
+
</div>
|
94 |
+
)}
|
95 |
+
</Card>
|
96 |
+
|
97 |
+
{/* Retriever Module */}
|
98 |
+
<Card className="border-border overflow-hidden">
|
99 |
+
<div className="p-4 bg-secondary/30 border-b border-border">
|
100 |
+
<div className="flex items-center justify-between">
|
101 |
+
<div className="flex items-center space-x-3">
|
102 |
+
<Search className="text-accent w-5 h-5" />
|
103 |
+
<h3 className="font-semibold">Retriever Module</h3>
|
104 |
+
<Badge className="bg-green-500/20 text-green-400 hover:bg-green-500/30">
|
105 |
+
Active
|
106 |
+
</Badge>
|
107 |
+
</div>
|
108 |
+
<button
|
109 |
+
onClick={() => toggleModule("retriever")}
|
110 |
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
111 |
+
data-testid="toggle-retriever"
|
112 |
+
>
|
113 |
+
<ChevronDown
|
114 |
+
className={`w-4 h-4 transition-transform ${
|
115 |
+
expandedModules.has("retriever") ? "rotate-180" : ""
|
116 |
+
}`}
|
117 |
+
/>
|
118 |
+
</button>
|
119 |
+
</div>
|
120 |
+
</div>
|
121 |
+
{expandedModules.has("retriever") && (
|
122 |
+
<div className="p-4 space-y-4">
|
123 |
+
<div className="grid md:grid-cols-3 gap-4">
|
124 |
+
<div>
|
125 |
+
<Label htmlFor="vector-store" className="text-sm font-medium mb-2 block">Vector Store</Label>
|
126 |
+
<Select defaultValue="faiss">
|
127 |
+
<SelectTrigger id="vector-store" data-testid="select-vector-store">
|
128 |
+
<SelectValue />
|
129 |
+
</SelectTrigger>
|
130 |
+
<SelectContent>
|
131 |
+
<SelectItem value="faiss">FAISS</SelectItem>
|
132 |
+
<SelectItem value="chroma">Chroma</SelectItem>
|
133 |
+
<SelectItem value="pinecone">Pinecone</SelectItem>
|
134 |
+
</SelectContent>
|
135 |
+
</Select>
|
136 |
+
</div>
|
137 |
+
<div>
|
138 |
+
<Label htmlFor="top-k" className="text-sm font-medium mb-2 block">Top-K Results</Label>
|
139 |
+
<Input
|
140 |
+
id="top-k"
|
141 |
+
type="number"
|
142 |
+
defaultValue="5"
|
143 |
+
data-testid="input-top-k"
|
144 |
+
/>
|
145 |
+
</div>
|
146 |
+
<div>
|
147 |
+
<Label htmlFor="similarity-threshold" className="text-sm font-medium mb-2 block">Similarity Threshold</Label>
|
148 |
+
<Input
|
149 |
+
id="similarity-threshold"
|
150 |
+
type="number"
|
151 |
+
defaultValue="0.7"
|
152 |
+
step="0.1"
|
153 |
+
data-testid="input-similarity-threshold"
|
154 |
+
/>
|
155 |
+
</div>
|
156 |
+
</div>
|
157 |
+
<Card className="bg-muted/20 p-4 border-border">
|
158 |
+
<h4 className="font-medium mb-2">Index Statistics</h4>
|
159 |
+
<div className="grid md:grid-cols-3 gap-4 text-sm">
|
160 |
+
<div className="text-center">
|
161 |
+
<div className="text-2xl font-bold text-primary" data-testid="stat-documents">1,247</div>
|
162 |
+
<div className="text-muted-foreground">Documents</div>
|
163 |
+
</div>
|
164 |
+
<div className="text-center">
|
165 |
+
<div className="text-2xl font-bold text-accent" data-testid="stat-embeddings">89,432</div>
|
166 |
+
<div className="text-muted-foreground">Embeddings</div>
|
167 |
+
</div>
|
168 |
+
<div className="text-center">
|
169 |
+
<div className="text-2xl font-bold text-green-400" data-testid="stat-size">2.3GB</div>
|
170 |
+
<div className="text-muted-foreground">Index Size</div>
|
171 |
+
</div>
|
172 |
+
</div>
|
173 |
+
</Card>
|
174 |
+
</div>
|
175 |
+
)}
|
176 |
+
</Card>
|
177 |
+
|
178 |
+
{/* DiffAnalyzer Module */}
|
179 |
+
<Card className="border-border overflow-hidden">
|
180 |
+
<div className="p-4 bg-secondary/30 border-b border-border">
|
181 |
+
<div className="flex items-center justify-between">
|
182 |
+
<div className="flex items-center space-x-3">
|
183 |
+
<GitBranch className="text-orange-400 w-5 h-5" />
|
184 |
+
<h3 className="font-semibold">DiffAnalyzer Module</h3>
|
185 |
+
<Badge className="bg-green-500/20 text-green-400 hover:bg-green-500/30">
|
186 |
+
Active
|
187 |
+
</Badge>
|
188 |
+
</div>
|
189 |
+
<button
|
190 |
+
onClick={() => toggleModule("diffanalyzer")}
|
191 |
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
192 |
+
data-testid="toggle-diffanalyzer"
|
193 |
+
>
|
194 |
+
<ChevronDown
|
195 |
+
className={`w-4 h-4 transition-transform ${
|
196 |
+
expandedModules.has("diffanalyzer") ? "rotate-180" : ""
|
197 |
+
}`}
|
198 |
+
/>
|
199 |
+
</button>
|
200 |
+
</div>
|
201 |
+
</div>
|
202 |
+
{expandedModules.has("diffanalyzer") && (
|
203 |
+
<div className="p-4 space-y-4">
|
204 |
+
<div className="grid md:grid-cols-2 gap-4">
|
205 |
+
<div>
|
206 |
+
<Label htmlFor="chunk-size" className="text-sm font-medium mb-2 block">Chunk Size</Label>
|
207 |
+
<Input
|
208 |
+
id="chunk-size"
|
209 |
+
type="number"
|
210 |
+
defaultValue="500"
|
211 |
+
data-testid="input-chunk-size"
|
212 |
+
/>
|
213 |
+
</div>
|
214 |
+
<div>
|
215 |
+
<Label htmlFor="max-files" className="text-sm font-medium mb-2 block">Max Files</Label>
|
216 |
+
<Input
|
217 |
+
id="max-files"
|
218 |
+
type="number"
|
219 |
+
defaultValue="50"
|
220 |
+
data-testid="input-max-files"
|
221 |
+
/>
|
222 |
+
</div>
|
223 |
+
</div>
|
224 |
+
<Card className="bg-muted/20 p-4 border-border">
|
225 |
+
<h4 className="font-medium mb-2 flex items-center">
|
226 |
+
<i className="fas fa-git mr-2 text-orange-400"></i>
|
227 |
+
Git Integration
|
228 |
+
</h4>
|
229 |
+
<div className="space-y-2 text-sm">
|
230 |
+
<div className="flex items-center space-x-2">
|
231 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
232 |
+
<span>Git diff parsing</span>
|
233 |
+
</div>
|
234 |
+
<div className="flex items-center space-x-2">
|
235 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
236 |
+
<span>Staged changes detection</span>
|
237 |
+
</div>
|
238 |
+
<div className="flex items-center space-x-2">
|
239 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
240 |
+
<span>Logical section chunking</span>
|
241 |
+
</div>
|
242 |
+
</div>
|
243 |
+
</Card>
|
244 |
+
<Card className="bg-black/50 p-4 border-border">
|
245 |
+
<h4 className="font-medium mb-2 flex items-center">
|
246 |
+
<i className="fas fa-code-branch mr-2 text-blue-400"></i>
|
247 |
+
Sample Diff Chunk
|
248 |
+
</h4>
|
249 |
+
<div className="font-mono text-xs space-y-1">
|
250 |
+
<div className="text-muted-foreground">diff --git a/src/embedder.py b/src/embedder.py</div>
|
251 |
+
<div className="text-muted-foreground">index 1a2b3c4..5d6e7f8 100644</div>
|
252 |
+
<div className="text-muted-foreground">--- a/src/embedder.py</div>
|
253 |
+
<div className="text-muted-foreground">+++ b/src/embedder.py</div>
|
254 |
+
<div className="text-cyan-400">@@ -45,6 +45,12 @@ class Embedder:</div>
|
255 |
+
<div className="text-red-400">- return self.model.encode(texts)</div>
|
256 |
+
<div className="text-green-400">+ embeddings = self.model.encode(texts)</div>
|
257 |
+
<div className="text-green-400">+ # Cache embeddings for faster retrieval</div>
|
258 |
+
<div className="text-green-400">+ self._cache_embeddings(texts, embeddings)</div>
|
259 |
+
<div className="text-green-400">+ return embeddings</div>
|
260 |
+
</div>
|
261 |
+
</Card>
|
262 |
+
</div>
|
263 |
+
)}
|
264 |
+
</Card>
|
265 |
+
|
266 |
+
{/* Generator Module */}
|
267 |
+
<Card className="border-border overflow-hidden">
|
268 |
+
<div className="p-4 bg-secondary/30 border-b border-border">
|
269 |
+
<div className="flex items-center justify-between">
|
270 |
+
<div className="flex items-center space-x-3">
|
271 |
+
<Wand2 className="text-purple-400 w-5 h-5" />
|
272 |
+
<h3 className="font-semibold">Generator Module</h3>
|
273 |
+
<Badge className="bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30">
|
274 |
+
Loading
|
275 |
+
</Badge>
|
276 |
+
</div>
|
277 |
+
<button
|
278 |
+
onClick={() => toggleModule("generator")}
|
279 |
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
280 |
+
data-testid="toggle-generator"
|
281 |
+
>
|
282 |
+
<ChevronDown
|
283 |
+
className={`w-4 h-4 transition-transform ${
|
284 |
+
expandedModules.has("generator") ? "rotate-180" : ""
|
285 |
+
}`}
|
286 |
+
/>
|
287 |
+
</button>
|
288 |
+
</div>
|
289 |
+
</div>
|
290 |
+
{expandedModules.has("generator") && (
|
291 |
+
<div className="p-4 space-y-4">
|
292 |
+
<div className="grid md:grid-cols-2 gap-4">
|
293 |
+
<div>
|
294 |
+
<Label htmlFor="generator-model" className="text-sm font-medium mb-2 block">Model</Label>
|
295 |
+
<Input
|
296 |
+
id="generator-model"
|
297 |
+
type="text"
|
298 |
+
defaultValue="microsoft/phi-2"
|
299 |
+
className="font-mono"
|
300 |
+
data-testid="input-generator-model"
|
301 |
+
/>
|
302 |
+
</div>
|
303 |
+
<div>
|
304 |
+
<Label htmlFor="quantization" className="text-sm font-medium mb-2 block">Quantization</Label>
|
305 |
+
<Select defaultValue="4bit">
|
306 |
+
<SelectTrigger id="quantization" data-testid="select-quantization">
|
307 |
+
<SelectValue />
|
308 |
+
</SelectTrigger>
|
309 |
+
<SelectContent>
|
310 |
+
<SelectItem value="4bit">4-bit (Q4)</SelectItem>
|
311 |
+
<SelectItem value="5bit">5-bit (Q5)</SelectItem>
|
312 |
+
<SelectItem value="8bit">8-bit (Q8)</SelectItem>
|
313 |
+
<SelectItem value="16bit">16-bit (FP16)</SelectItem>
|
314 |
+
</SelectContent>
|
315 |
+
</Select>
|
316 |
+
</div>
|
317 |
+
<div>
|
318 |
+
<Label htmlFor="commit-style" className="text-sm font-medium mb-2 block">Commit Style</Label>
|
319 |
+
<Select defaultValue="conventional">
|
320 |
+
<SelectTrigger id="commit-style" data-testid="select-commit-style">
|
321 |
+
<SelectValue />
|
322 |
+
</SelectTrigger>
|
323 |
+
<SelectContent>
|
324 |
+
<SelectItem value="conventional">Conventional (feat:, fix:)</SelectItem>
|
325 |
+
<SelectItem value="descriptive">Descriptive (detailed)</SelectItem>
|
326 |
+
<SelectItem value="terse">Terse (minimal)</SelectItem>
|
327 |
+
</SelectContent>
|
328 |
+
</Select>
|
329 |
+
</div>
|
330 |
+
</div>
|
331 |
+
<div className="grid md:grid-cols-3 gap-4">
|
332 |
+
<div>
|
333 |
+
<Label htmlFor="max-tokens" className="text-sm font-medium mb-2 block">Max Tokens</Label>
|
334 |
+
<Input
|
335 |
+
id="max-tokens"
|
336 |
+
type="number"
|
337 |
+
defaultValue="2048"
|
338 |
+
data-testid="input-max-tokens"
|
339 |
+
/>
|
340 |
+
</div>
|
341 |
+
<div>
|
342 |
+
<Label htmlFor="temperature" className="text-sm font-medium mb-2 block">Temperature</Label>
|
343 |
+
<Input
|
344 |
+
id="temperature"
|
345 |
+
type="number"
|
346 |
+
defaultValue="0.7"
|
347 |
+
step="0.1"
|
348 |
+
data-testid="input-temperature"
|
349 |
+
/>
|
350 |
+
</div>
|
351 |
+
<div>
|
352 |
+
<Label htmlFor="top-p" className="text-sm font-medium mb-2 block">Top-P</Label>
|
353 |
+
<Input
|
354 |
+
id="top-p"
|
355 |
+
type="number"
|
356 |
+
defaultValue="0.9"
|
357 |
+
step="0.1"
|
358 |
+
data-testid="input-top-p"
|
359 |
+
/>
|
360 |
+
</div>
|
361 |
+
</div>
|
362 |
+
<Card className="bg-muted/20 p-4 border-border">
|
363 |
+
<h4 className="font-medium mb-2 flex items-center">
|
364 |
+
<i className="fas fa-code-commit mr-2 text-purple-400"></i>
|
365 |
+
Commit Message Generation
|
366 |
+
</h4>
|
367 |
+
<div className="space-y-2 text-sm">
|
368 |
+
<div className="flex items-center space-x-2">
|
369 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
370 |
+
<span>Conventional commit format</span>
|
371 |
+
</div>
|
372 |
+
<div className="flex items-center space-x-2">
|
373 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
374 |
+
<span>Imperative tone enforcement</span>
|
375 |
+
</div>
|
376 |
+
<div className="flex items-center space-x-2">
|
377 |
+
<Clock className="text-yellow-400 w-4 h-4" />
|
378 |
+
<span>Context-aware suggestions</span>
|
379 |
+
</div>
|
380 |
+
<div className="flex items-center space-x-2">
|
381 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
382 |
+
<span>Fallback logic for model failures</span>
|
383 |
+
</div>
|
384 |
+
<div className="flex items-center space-x-2">
|
385 |
+
<CheckCircle className="text-green-400 w-4 h-4" />
|
386 |
+
<span>Style presets (conventional/descriptive/terse)</span>
|
387 |
+
</div>
|
388 |
+
</div>
|
389 |
+
</Card>
|
390 |
+
</div>
|
391 |
+
)}
|
392 |
+
</Card>
|
393 |
+
</section>
|
394 |
+
);
|
395 |
+
}
|
client/src/components/sections/overview.tsx
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Card } from "@/components/ui/card";
|
2 |
+
import { Badge } from "@/components/ui/badge";
|
3 |
+
import { Brain, Search, Wand2, FolderTree, GitCommit } from "lucide-react";
|
4 |
+
|
5 |
+
export function OverviewSection() {
|
6 |
+
const features = [
|
7 |
+
{
|
8 |
+
icon: Brain,
|
9 |
+
title: "EmbeddingGemma-300m",
|
10 |
+
description: "High-precision text embedding for semantic search",
|
11 |
+
color: "text-primary"
|
12 |
+
},
|
13 |
+
{
|
14 |
+
icon: Search,
|
15 |
+
title: "FAISS Vector Store",
|
16 |
+
description: "Efficient similarity search and clustering",
|
17 |
+
color: "text-accent"
|
18 |
+
},
|
19 |
+
{
|
20 |
+
icon: Wand2,
|
21 |
+
title: "Phi-2 Generator",
|
22 |
+
description: "2.7B parameter model for contextual responses",
|
23 |
+
color: "text-purple-400"
|
24 |
+
},
|
25 |
+
{
|
26 |
+
icon: FolderTree,
|
27 |
+
title: "Commit Assistant",
|
28 |
+
description: "Git diff analysis and intelligent commit message generation",
|
29 |
+
color: "text-orange-400"
|
30 |
+
}
|
31 |
+
];
|
32 |
+
|
33 |
+
const projectStructure = [
|
34 |
+
{ type: "folder", name: "gemma-phi2-cli/", level: 0 },
|
35 |
+
{ type: "folder", name: "models/", level: 1, badge: "AI Models" },
|
36 |
+
{ type: "folder", name: "embeddinggemma-300m/", level: 2 },
|
37 |
+
{ type: "folder", name: "phi2-quantized/", level: 2 },
|
38 |
+
{ type: "folder", name: "data/", level: 1 },
|
39 |
+
{ type: "folder", name: "index/", level: 2, badge: "Vector Store" },
|
40 |
+
{ type: "folder", name: "src/", level: 1 },
|
41 |
+
{ type: "file", name: "embedder.py", level: 2 },
|
42 |
+
{ type: "file", name: "retriever.py", level: 2 },
|
43 |
+
{ type: "file", name: "generator.py", level: 2 },
|
44 |
+
{ type: "file", name: "diff_analyzer.py", level: 2 },
|
45 |
+
{ type: "file", name: "cli.py", level: 2 },
|
46 |
+
{ type: "folder", name: "tests/", level: 1, badge: "Unit Tests" },
|
47 |
+
{ type: "file", name: "test_embedder.py", level: 2 },
|
48 |
+
{ type: "file", name: "test_retriever.py", level: 2 },
|
49 |
+
{ type: "file", name: "test_generator.py", level: 2 },
|
50 |
+
{ type: "folder", name: "examples/", level: 1, badge: "Sample Repo" },
|
51 |
+
{ type: "file", name: "sample_repo/", level: 2 },
|
52 |
+
{ type: "file", name: "config.yaml", level: 1 },
|
53 |
+
{ type: "file", name: "environment.yml", level: 1 },
|
54 |
+
{ type: "file", name: "README.md", level: 1 },
|
55 |
+
];
|
56 |
+
|
57 |
+
return (
|
58 |
+
<section id="overview" className="space-y-6">
|
59 |
+
<div className="gradient-border">
|
60 |
+
<div className="p-6">
|
61 |
+
<div className="flex items-start justify-between mb-4">
|
62 |
+
<div>
|
63 |
+
<h2 className="text-2xl font-bold mb-2">Project Overview</h2>
|
64 |
+
<p className="text-muted-foreground">
|
65 |
+
A seamless CLI assistant that combines EmbeddingGemma's precision with Phi-2's generative capabilities.
|
66 |
+
</p>
|
67 |
+
</div>
|
68 |
+
<Badge className="bg-green-500/20 text-green-400 hover:bg-green-500/30">
|
69 |
+
v1.0.0
|
70 |
+
</Badge>
|
71 |
+
</div>
|
72 |
+
|
73 |
+
<div className="grid md:grid-cols-3 gap-4 mb-6">
|
74 |
+
{features.map((feature, index) => (
|
75 |
+
<Card key={index} className="bg-secondary/50 p-4 border-border">
|
76 |
+
<div className="flex items-center space-x-3 mb-2">
|
77 |
+
<feature.icon className={`${feature.color} w-5 h-5`} />
|
78 |
+
<h3 className="font-semibold">{feature.title}</h3>
|
79 |
+
</div>
|
80 |
+
<p className="text-sm text-muted-foreground">{feature.description}</p>
|
81 |
+
</Card>
|
82 |
+
))}
|
83 |
+
</div>
|
84 |
+
|
85 |
+
<Card className="bg-muted/30 p-4 border-border">
|
86 |
+
<h3 className="font-semibold mb-3 flex items-center">
|
87 |
+
<GitCommit className="mr-2 text-orange-400 w-4 h-4" />
|
88 |
+
Data Flow Pipeline
|
89 |
+
</h3>
|
90 |
+
<div className="space-y-3 mb-6">
|
91 |
+
<div className="flex items-center justify-between bg-black/30 p-3 rounded">
|
92 |
+
<div className="flex items-center space-x-2">
|
93 |
+
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center text-xs font-bold">1</div>
|
94 |
+
<span className="text-sm">Git Diff Analysis</span>
|
95 |
+
</div>
|
96 |
+
<span className="text-muted-foreground text-xs">diff_analyzer.py</span>
|
97 |
+
</div>
|
98 |
+
<div className="flex justify-center">
|
99 |
+
<div className="text-muted-foreground">↓</div>
|
100 |
+
</div>
|
101 |
+
<div className="flex items-center justify-between bg-black/30 p-3 rounded">
|
102 |
+
<div className="flex items-center space-x-2">
|
103 |
+
<div className="w-6 h-6 bg-green-500 rounded-full flex items-center justify-center text-xs font-bold">2</div>
|
104 |
+
<span className="text-sm">Text Embedding</span>
|
105 |
+
</div>
|
106 |
+
<span className="text-muted-foreground text-xs">embedder.py</span>
|
107 |
+
</div>
|
108 |
+
<div className="flex justify-center">
|
109 |
+
<div className="text-muted-foreground">↓</div>
|
110 |
+
</div>
|
111 |
+
<div className="flex items-center justify-between bg-black/30 p-3 rounded">
|
112 |
+
<div className="flex items-center space-x-2">
|
113 |
+
<div className="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center text-xs font-bold">3</div>
|
114 |
+
<span className="text-sm">Context Retrieval</span>
|
115 |
+
</div>
|
116 |
+
<span className="text-muted-foreground text-xs">retriever.py</span>
|
117 |
+
</div>
|
118 |
+
<div className="flex justify-center">
|
119 |
+
<div className="text-muted-foreground">↓</div>
|
120 |
+
</div>
|
121 |
+
<div className="flex items-center justify-between bg-black/30 p-3 rounded">
|
122 |
+
<div className="flex items-center space-x-2">
|
123 |
+
<div className="w-6 h-6 bg-orange-500 rounded-full flex items-center justify-center text-xs font-bold">4</div>
|
124 |
+
<span className="text-sm">Message Generation</span>
|
125 |
+
</div>
|
126 |
+
<span className="text-muted-foreground text-xs">generator.py</span>
|
127 |
+
</div>
|
128 |
+
</div>
|
129 |
+
</Card>
|
130 |
+
|
131 |
+
<Card className="bg-muted/30 p-4 border-border">
|
132 |
+
<h3 className="font-semibold mb-3 flex items-center">
|
133 |
+
<FolderTree className="mr-2 text-primary w-4 h-4" />
|
134 |
+
Project Structure
|
135 |
+
</h3>
|
136 |
+
<div className="font-mono text-sm space-y-1 text-muted-foreground">
|
137 |
+
{projectStructure.map((item, index) => (
|
138 |
+
<div key={index} className="flex items-center" style={{ marginLeft: `${item.level * 16}px` }}>
|
139 |
+
<i className={`${item.type === 'folder' ? 'fas fa-folder text-blue-400' : 'fas fa-file text-green-400'} mr-2`}></i>
|
140 |
+
<span>{item.name}</span>
|
141 |
+
{item.badge && (
|
142 |
+
<Badge className="ml-2 text-xs bg-secondary px-2 py-1">
|
143 |
+
{item.badge}
|
144 |
+
</Badge>
|
145 |
+
)}
|
146 |
+
</div>
|
147 |
+
))}
|
148 |
+
</div>
|
149 |
+
</Card>
|
150 |
+
</div>
|
151 |
+
</div>
|
152 |
+
</section>
|
153 |
+
);
|
154 |
+
}
|
client/src/components/ui/accordion.tsx
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
3 |
+
import { ChevronDown } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const Accordion = AccordionPrimitive.Root
|
8 |
+
|
9 |
+
const AccordionItem = React.forwardRef<
|
10 |
+
React.ElementRef<typeof AccordionPrimitive.Item>,
|
11 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
12 |
+
>(({ className, ...props }, ref) => (
|
13 |
+
<AccordionPrimitive.Item
|
14 |
+
ref={ref}
|
15 |
+
className={cn("border-b", className)}
|
16 |
+
{...props}
|
17 |
+
/>
|
18 |
+
))
|
19 |
+
AccordionItem.displayName = "AccordionItem"
|
20 |
+
|
21 |
+
const AccordionTrigger = React.forwardRef<
|
22 |
+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
23 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
24 |
+
>(({ className, children, ...props }, ref) => (
|
25 |
+
<AccordionPrimitive.Header className="flex">
|
26 |
+
<AccordionPrimitive.Trigger
|
27 |
+
ref={ref}
|
28 |
+
className={cn(
|
29 |
+
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
30 |
+
className
|
31 |
+
)}
|
32 |
+
{...props}
|
33 |
+
>
|
34 |
+
{children}
|
35 |
+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
36 |
+
</AccordionPrimitive.Trigger>
|
37 |
+
</AccordionPrimitive.Header>
|
38 |
+
))
|
39 |
+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
40 |
+
|
41 |
+
const AccordionContent = React.forwardRef<
|
42 |
+
React.ElementRef<typeof AccordionPrimitive.Content>,
|
43 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
44 |
+
>(({ className, children, ...props }, ref) => (
|
45 |
+
<AccordionPrimitive.Content
|
46 |
+
ref={ref}
|
47 |
+
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
48 |
+
{...props}
|
49 |
+
>
|
50 |
+
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
51 |
+
</AccordionPrimitive.Content>
|
52 |
+
))
|
53 |
+
|
54 |
+
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
55 |
+
|
56 |
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
client/src/components/ui/alert-dialog.tsx
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
import { buttonVariants } from "@/components/ui/button"
|
6 |
+
|
7 |
+
const AlertDialog = AlertDialogPrimitive.Root
|
8 |
+
|
9 |
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
10 |
+
|
11 |
+
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
12 |
+
|
13 |
+
const AlertDialogOverlay = React.forwardRef<
|
14 |
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
15 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
16 |
+
>(({ className, ...props }, ref) => (
|
17 |
+
<AlertDialogPrimitive.Overlay
|
18 |
+
className={cn(
|
19 |
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
20 |
+
className
|
21 |
+
)}
|
22 |
+
{...props}
|
23 |
+
ref={ref}
|
24 |
+
/>
|
25 |
+
))
|
26 |
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
27 |
+
|
28 |
+
const AlertDialogContent = React.forwardRef<
|
29 |
+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
30 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
31 |
+
>(({ className, ...props }, ref) => (
|
32 |
+
<AlertDialogPortal>
|
33 |
+
<AlertDialogOverlay />
|
34 |
+
<AlertDialogPrimitive.Content
|
35 |
+
ref={ref}
|
36 |
+
className={cn(
|
37 |
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
38 |
+
className
|
39 |
+
)}
|
40 |
+
{...props}
|
41 |
+
/>
|
42 |
+
</AlertDialogPortal>
|
43 |
+
))
|
44 |
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
45 |
+
|
46 |
+
const AlertDialogHeader = ({
|
47 |
+
className,
|
48 |
+
...props
|
49 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
50 |
+
<div
|
51 |
+
className={cn(
|
52 |
+
"flex flex-col space-y-2 text-center sm:text-left",
|
53 |
+
className
|
54 |
+
)}
|
55 |
+
{...props}
|
56 |
+
/>
|
57 |
+
)
|
58 |
+
AlertDialogHeader.displayName = "AlertDialogHeader"
|
59 |
+
|
60 |
+
const AlertDialogFooter = ({
|
61 |
+
className,
|
62 |
+
...props
|
63 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
64 |
+
<div
|
65 |
+
className={cn(
|
66 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
67 |
+
className
|
68 |
+
)}
|
69 |
+
{...props}
|
70 |
+
/>
|
71 |
+
)
|
72 |
+
AlertDialogFooter.displayName = "AlertDialogFooter"
|
73 |
+
|
74 |
+
const AlertDialogTitle = React.forwardRef<
|
75 |
+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
76 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
77 |
+
>(({ className, ...props }, ref) => (
|
78 |
+
<AlertDialogPrimitive.Title
|
79 |
+
ref={ref}
|
80 |
+
className={cn("text-lg font-semibold", className)}
|
81 |
+
{...props}
|
82 |
+
/>
|
83 |
+
))
|
84 |
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
85 |
+
|
86 |
+
const AlertDialogDescription = React.forwardRef<
|
87 |
+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
88 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
89 |
+
>(({ className, ...props }, ref) => (
|
90 |
+
<AlertDialogPrimitive.Description
|
91 |
+
ref={ref}
|
92 |
+
className={cn("text-sm text-muted-foreground", className)}
|
93 |
+
{...props}
|
94 |
+
/>
|
95 |
+
))
|
96 |
+
AlertDialogDescription.displayName =
|
97 |
+
AlertDialogPrimitive.Description.displayName
|
98 |
+
|
99 |
+
const AlertDialogAction = React.forwardRef<
|
100 |
+
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
101 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
102 |
+
>(({ className, ...props }, ref) => (
|
103 |
+
<AlertDialogPrimitive.Action
|
104 |
+
ref={ref}
|
105 |
+
className={cn(buttonVariants(), className)}
|
106 |
+
{...props}
|
107 |
+
/>
|
108 |
+
))
|
109 |
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
110 |
+
|
111 |
+
const AlertDialogCancel = React.forwardRef<
|
112 |
+
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
113 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
114 |
+
>(({ className, ...props }, ref) => (
|
115 |
+
<AlertDialogPrimitive.Cancel
|
116 |
+
ref={ref}
|
117 |
+
className={cn(
|
118 |
+
buttonVariants({ variant: "outline" }),
|
119 |
+
"mt-2 sm:mt-0",
|
120 |
+
className
|
121 |
+
)}
|
122 |
+
{...props}
|
123 |
+
/>
|
124 |
+
))
|
125 |
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
126 |
+
|
127 |
+
export {
|
128 |
+
AlertDialog,
|
129 |
+
AlertDialogPortal,
|
130 |
+
AlertDialogOverlay,
|
131 |
+
AlertDialogTrigger,
|
132 |
+
AlertDialogContent,
|
133 |
+
AlertDialogHeader,
|
134 |
+
AlertDialogFooter,
|
135 |
+
AlertDialogTitle,
|
136 |
+
AlertDialogDescription,
|
137 |
+
AlertDialogAction,
|
138 |
+
AlertDialogCancel,
|
139 |
+
}
|
client/src/components/ui/alert.tsx
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const alertVariants = cva(
|
7 |
+
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default: "bg-background text-foreground",
|
12 |
+
destructive:
|
13 |
+
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
14 |
+
},
|
15 |
+
},
|
16 |
+
defaultVariants: {
|
17 |
+
variant: "default",
|
18 |
+
},
|
19 |
+
}
|
20 |
+
)
|
21 |
+
|
22 |
+
const Alert = React.forwardRef<
|
23 |
+
HTMLDivElement,
|
24 |
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
25 |
+
>(({ className, variant, ...props }, ref) => (
|
26 |
+
<div
|
27 |
+
ref={ref}
|
28 |
+
role="alert"
|
29 |
+
className={cn(alertVariants({ variant }), className)}
|
30 |
+
{...props}
|
31 |
+
/>
|
32 |
+
))
|
33 |
+
Alert.displayName = "Alert"
|
34 |
+
|
35 |
+
const AlertTitle = React.forwardRef<
|
36 |
+
HTMLParagraphElement,
|
37 |
+
React.HTMLAttributes<HTMLHeadingElement>
|
38 |
+
>(({ className, ...props }, ref) => (
|
39 |
+
<h5
|
40 |
+
ref={ref}
|
41 |
+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
))
|
45 |
+
AlertTitle.displayName = "AlertTitle"
|
46 |
+
|
47 |
+
const AlertDescription = React.forwardRef<
|
48 |
+
HTMLParagraphElement,
|
49 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
50 |
+
>(({ className, ...props }, ref) => (
|
51 |
+
<div
|
52 |
+
ref={ref}
|
53 |
+
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
))
|
57 |
+
AlertDescription.displayName = "AlertDescription"
|
58 |
+
|
59 |
+
export { Alert, AlertTitle, AlertDescription }
|
client/src/components/ui/aspect-ratio.tsx
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
2 |
+
|
3 |
+
const AspectRatio = AspectRatioPrimitive.Root
|
4 |
+
|
5 |
+
export { AspectRatio }
|
client/src/components/ui/avatar.tsx
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const Avatar = React.forwardRef<
|
9 |
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
10 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
11 |
+
>(({ className, ...props }, ref) => (
|
12 |
+
<AvatarPrimitive.Root
|
13 |
+
ref={ref}
|
14 |
+
className={cn(
|
15 |
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
16 |
+
className
|
17 |
+
)}
|
18 |
+
{...props}
|
19 |
+
/>
|
20 |
+
))
|
21 |
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
22 |
+
|
23 |
+
const AvatarImage = React.forwardRef<
|
24 |
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
25 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
26 |
+
>(({ className, ...props }, ref) => (
|
27 |
+
<AvatarPrimitive.Image
|
28 |
+
ref={ref}
|
29 |
+
className={cn("aspect-square h-full w-full", className)}
|
30 |
+
{...props}
|
31 |
+
/>
|
32 |
+
))
|
33 |
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
34 |
+
|
35 |
+
const AvatarFallback = React.forwardRef<
|
36 |
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
37 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
38 |
+
>(({ className, ...props }, ref) => (
|
39 |
+
<AvatarPrimitive.Fallback
|
40 |
+
ref={ref}
|
41 |
+
className={cn(
|
42 |
+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
43 |
+
className
|
44 |
+
)}
|
45 |
+
{...props}
|
46 |
+
/>
|
47 |
+
))
|
48 |
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
49 |
+
|
50 |
+
export { Avatar, AvatarImage, AvatarFallback }
|
client/src/components/ui/badge.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const badgeVariants = cva(
|
7 |
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default:
|
12 |
+
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
13 |
+
secondary:
|
14 |
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
15 |
+
destructive:
|
16 |
+
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
17 |
+
outline: "text-foreground",
|
18 |
+
},
|
19 |
+
},
|
20 |
+
defaultVariants: {
|
21 |
+
variant: "default",
|
22 |
+
},
|
23 |
+
}
|
24 |
+
)
|
25 |
+
|
26 |
+
export interface BadgeProps
|
27 |
+
extends React.HTMLAttributes<HTMLDivElement>,
|
28 |
+
VariantProps<typeof badgeVariants> {}
|
29 |
+
|
30 |
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
31 |
+
return (
|
32 |
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
33 |
+
)
|
34 |
+
}
|
35 |
+
|
36 |
+
export { Badge, badgeVariants }
|
client/src/components/ui/breadcrumb.tsx
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const Breadcrumb = React.forwardRef<
|
8 |
+
HTMLElement,
|
9 |
+
React.ComponentPropsWithoutRef<"nav"> & {
|
10 |
+
separator?: React.ReactNode
|
11 |
+
}
|
12 |
+
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
13 |
+
Breadcrumb.displayName = "Breadcrumb"
|
14 |
+
|
15 |
+
const BreadcrumbList = React.forwardRef<
|
16 |
+
HTMLOListElement,
|
17 |
+
React.ComponentPropsWithoutRef<"ol">
|
18 |
+
>(({ className, ...props }, ref) => (
|
19 |
+
<ol
|
20 |
+
ref={ref}
|
21 |
+
className={cn(
|
22 |
+
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
23 |
+
className
|
24 |
+
)}
|
25 |
+
{...props}
|
26 |
+
/>
|
27 |
+
))
|
28 |
+
BreadcrumbList.displayName = "BreadcrumbList"
|
29 |
+
|
30 |
+
const BreadcrumbItem = React.forwardRef<
|
31 |
+
HTMLLIElement,
|
32 |
+
React.ComponentPropsWithoutRef<"li">
|
33 |
+
>(({ className, ...props }, ref) => (
|
34 |
+
<li
|
35 |
+
ref={ref}
|
36 |
+
className={cn("inline-flex items-center gap-1.5", className)}
|
37 |
+
{...props}
|
38 |
+
/>
|
39 |
+
))
|
40 |
+
BreadcrumbItem.displayName = "BreadcrumbItem"
|
41 |
+
|
42 |
+
const BreadcrumbLink = React.forwardRef<
|
43 |
+
HTMLAnchorElement,
|
44 |
+
React.ComponentPropsWithoutRef<"a"> & {
|
45 |
+
asChild?: boolean
|
46 |
+
}
|
47 |
+
>(({ asChild, className, ...props }, ref) => {
|
48 |
+
const Comp = asChild ? Slot : "a"
|
49 |
+
|
50 |
+
return (
|
51 |
+
<Comp
|
52 |
+
ref={ref}
|
53 |
+
className={cn("transition-colors hover:text-foreground", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
)
|
57 |
+
})
|
58 |
+
BreadcrumbLink.displayName = "BreadcrumbLink"
|
59 |
+
|
60 |
+
const BreadcrumbPage = React.forwardRef<
|
61 |
+
HTMLSpanElement,
|
62 |
+
React.ComponentPropsWithoutRef<"span">
|
63 |
+
>(({ className, ...props }, ref) => (
|
64 |
+
<span
|
65 |
+
ref={ref}
|
66 |
+
role="link"
|
67 |
+
aria-disabled="true"
|
68 |
+
aria-current="page"
|
69 |
+
className={cn("font-normal text-foreground", className)}
|
70 |
+
{...props}
|
71 |
+
/>
|
72 |
+
))
|
73 |
+
BreadcrumbPage.displayName = "BreadcrumbPage"
|
74 |
+
|
75 |
+
const BreadcrumbSeparator = ({
|
76 |
+
children,
|
77 |
+
className,
|
78 |
+
...props
|
79 |
+
}: React.ComponentProps<"li">) => (
|
80 |
+
<li
|
81 |
+
role="presentation"
|
82 |
+
aria-hidden="true"
|
83 |
+
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
|
84 |
+
{...props}
|
85 |
+
>
|
86 |
+
{children ?? <ChevronRight />}
|
87 |
+
</li>
|
88 |
+
)
|
89 |
+
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
90 |
+
|
91 |
+
const BreadcrumbEllipsis = ({
|
92 |
+
className,
|
93 |
+
...props
|
94 |
+
}: React.ComponentProps<"span">) => (
|
95 |
+
<span
|
96 |
+
role="presentation"
|
97 |
+
aria-hidden="true"
|
98 |
+
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
99 |
+
{...props}
|
100 |
+
>
|
101 |
+
<MoreHorizontal className="h-4 w-4" />
|
102 |
+
<span className="sr-only">More</span>
|
103 |
+
</span>
|
104 |
+
)
|
105 |
+
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
106 |
+
|
107 |
+
export {
|
108 |
+
Breadcrumb,
|
109 |
+
BreadcrumbList,
|
110 |
+
BreadcrumbItem,
|
111 |
+
BreadcrumbLink,
|
112 |
+
BreadcrumbPage,
|
113 |
+
BreadcrumbSeparator,
|
114 |
+
BreadcrumbEllipsis,
|
115 |
+
}
|
client/src/components/ui/button.tsx
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const buttonVariants = cva(
|
8 |
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
9 |
+
{
|
10 |
+
variants: {
|
11 |
+
variant: {
|
12 |
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
13 |
+
destructive:
|
14 |
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
15 |
+
outline:
|
16 |
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
17 |
+
secondary:
|
18 |
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
19 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
20 |
+
link: "text-primary underline-offset-4 hover:underline",
|
21 |
+
},
|
22 |
+
size: {
|
23 |
+
default: "h-10 px-4 py-2",
|
24 |
+
sm: "h-9 rounded-md px-3",
|
25 |
+
lg: "h-11 rounded-md px-8",
|
26 |
+
icon: "h-10 w-10",
|
27 |
+
},
|
28 |
+
},
|
29 |
+
defaultVariants: {
|
30 |
+
variant: "default",
|
31 |
+
size: "default",
|
32 |
+
},
|
33 |
+
}
|
34 |
+
)
|
35 |
+
|
36 |
+
export interface ButtonProps
|
37 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
38 |
+
VariantProps<typeof buttonVariants> {
|
39 |
+
asChild?: boolean
|
40 |
+
}
|
41 |
+
|
42 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
43 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
44 |
+
const Comp = asChild ? Slot : "button"
|
45 |
+
return (
|
46 |
+
<Comp
|
47 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
48 |
+
ref={ref}
|
49 |
+
{...props}
|
50 |
+
/>
|
51 |
+
)
|
52 |
+
}
|
53 |
+
)
|
54 |
+
Button.displayName = "Button"
|
55 |
+
|
56 |
+
export { Button, buttonVariants }
|
client/src/components/ui/calendar.tsx
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { ChevronLeft, ChevronRight } from "lucide-react"
|
3 |
+
import { DayPicker } from "react-day-picker"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
import { buttonVariants } from "@/components/ui/button"
|
7 |
+
|
8 |
+
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
9 |
+
|
10 |
+
function Calendar({
|
11 |
+
className,
|
12 |
+
classNames,
|
13 |
+
showOutsideDays = true,
|
14 |
+
...props
|
15 |
+
}: CalendarProps) {
|
16 |
+
return (
|
17 |
+
<DayPicker
|
18 |
+
showOutsideDays={showOutsideDays}
|
19 |
+
className={cn("p-3", className)}
|
20 |
+
classNames={{
|
21 |
+
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
22 |
+
month: "space-y-4",
|
23 |
+
caption: "flex justify-center pt-1 relative items-center",
|
24 |
+
caption_label: "text-sm font-medium",
|
25 |
+
nav: "space-x-1 flex items-center",
|
26 |
+
nav_button: cn(
|
27 |
+
buttonVariants({ variant: "outline" }),
|
28 |
+
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
29 |
+
),
|
30 |
+
nav_button_previous: "absolute left-1",
|
31 |
+
nav_button_next: "absolute right-1",
|
32 |
+
table: "w-full border-collapse space-y-1",
|
33 |
+
head_row: "flex",
|
34 |
+
head_cell:
|
35 |
+
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
36 |
+
row: "flex w-full mt-2",
|
37 |
+
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
38 |
+
day: cn(
|
39 |
+
buttonVariants({ variant: "ghost" }),
|
40 |
+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
41 |
+
),
|
42 |
+
day_range_end: "day-range-end",
|
43 |
+
day_selected:
|
44 |
+
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
45 |
+
day_today: "bg-accent text-accent-foreground",
|
46 |
+
day_outside:
|
47 |
+
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
48 |
+
day_disabled: "text-muted-foreground opacity-50",
|
49 |
+
day_range_middle:
|
50 |
+
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
51 |
+
day_hidden: "invisible",
|
52 |
+
...classNames,
|
53 |
+
}}
|
54 |
+
components={{
|
55 |
+
IconLeft: ({ className, ...props }) => (
|
56 |
+
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
|
57 |
+
),
|
58 |
+
IconRight: ({ className, ...props }) => (
|
59 |
+
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
|
60 |
+
),
|
61 |
+
}}
|
62 |
+
{...props}
|
63 |
+
/>
|
64 |
+
)
|
65 |
+
}
|
66 |
+
Calendar.displayName = "Calendar"
|
67 |
+
|
68 |
+
export { Calendar }
|
client/src/components/ui/card.tsx
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
|
3 |
+
import { cn } from "@/lib/utils"
|
4 |
+
|
5 |
+
const Card = React.forwardRef<
|
6 |
+
HTMLDivElement,
|
7 |
+
React.HTMLAttributes<HTMLDivElement>
|
8 |
+
>(({ className, ...props }, ref) => (
|
9 |
+
<div
|
10 |
+
ref={ref}
|
11 |
+
className={cn(
|
12 |
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
13 |
+
className
|
14 |
+
)}
|
15 |
+
{...props}
|
16 |
+
/>
|
17 |
+
))
|
18 |
+
Card.displayName = "Card"
|
19 |
+
|
20 |
+
const CardHeader = React.forwardRef<
|
21 |
+
HTMLDivElement,
|
22 |
+
React.HTMLAttributes<HTMLDivElement>
|
23 |
+
>(({ className, ...props }, ref) => (
|
24 |
+
<div
|
25 |
+
ref={ref}
|
26 |
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
27 |
+
{...props}
|
28 |
+
/>
|
29 |
+
))
|
30 |
+
CardHeader.displayName = "CardHeader"
|
31 |
+
|
32 |
+
const CardTitle = React.forwardRef<
|
33 |
+
HTMLDivElement,
|
34 |
+
React.HTMLAttributes<HTMLDivElement>
|
35 |
+
>(({ className, ...props }, ref) => (
|
36 |
+
<div
|
37 |
+
ref={ref}
|
38 |
+
className={cn(
|
39 |
+
"text-2xl font-semibold leading-none tracking-tight",
|
40 |
+
className
|
41 |
+
)}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
))
|
45 |
+
CardTitle.displayName = "CardTitle"
|
46 |
+
|
47 |
+
const CardDescription = React.forwardRef<
|
48 |
+
HTMLDivElement,
|
49 |
+
React.HTMLAttributes<HTMLDivElement>
|
50 |
+
>(({ className, ...props }, ref) => (
|
51 |
+
<div
|
52 |
+
ref={ref}
|
53 |
+
className={cn("text-sm text-muted-foreground", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
))
|
57 |
+
CardDescription.displayName = "CardDescription"
|
58 |
+
|
59 |
+
const CardContent = React.forwardRef<
|
60 |
+
HTMLDivElement,
|
61 |
+
React.HTMLAttributes<HTMLDivElement>
|
62 |
+
>(({ className, ...props }, ref) => (
|
63 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
64 |
+
))
|
65 |
+
CardContent.displayName = "CardContent"
|
66 |
+
|
67 |
+
const CardFooter = React.forwardRef<
|
68 |
+
HTMLDivElement,
|
69 |
+
React.HTMLAttributes<HTMLDivElement>
|
70 |
+
>(({ className, ...props }, ref) => (
|
71 |
+
<div
|
72 |
+
ref={ref}
|
73 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
74 |
+
{...props}
|
75 |
+
/>
|
76 |
+
))
|
77 |
+
CardFooter.displayName = "CardFooter"
|
78 |
+
|
79 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
client/src/components/ui/carousel.tsx
ADDED
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import useEmblaCarousel, {
|
3 |
+
type UseEmblaCarouselType,
|
4 |
+
} from "embla-carousel-react"
|
5 |
+
import { ArrowLeft, ArrowRight } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
import { Button } from "@/components/ui/button"
|
9 |
+
|
10 |
+
type CarouselApi = UseEmblaCarouselType[1]
|
11 |
+
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
12 |
+
type CarouselOptions = UseCarouselParameters[0]
|
13 |
+
type CarouselPlugin = UseCarouselParameters[1]
|
14 |
+
|
15 |
+
type CarouselProps = {
|
16 |
+
opts?: CarouselOptions
|
17 |
+
plugins?: CarouselPlugin
|
18 |
+
orientation?: "horizontal" | "vertical"
|
19 |
+
setApi?: (api: CarouselApi) => void
|
20 |
+
}
|
21 |
+
|
22 |
+
type CarouselContextProps = {
|
23 |
+
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
24 |
+
api: ReturnType<typeof useEmblaCarousel>[1]
|
25 |
+
scrollPrev: () => void
|
26 |
+
scrollNext: () => void
|
27 |
+
canScrollPrev: boolean
|
28 |
+
canScrollNext: boolean
|
29 |
+
} & CarouselProps
|
30 |
+
|
31 |
+
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
32 |
+
|
33 |
+
function useCarousel() {
|
34 |
+
const context = React.useContext(CarouselContext)
|
35 |
+
|
36 |
+
if (!context) {
|
37 |
+
throw new Error("useCarousel must be used within a <Carousel />")
|
38 |
+
}
|
39 |
+
|
40 |
+
return context
|
41 |
+
}
|
42 |
+
|
43 |
+
const Carousel = React.forwardRef<
|
44 |
+
HTMLDivElement,
|
45 |
+
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
46 |
+
>(
|
47 |
+
(
|
48 |
+
{
|
49 |
+
orientation = "horizontal",
|
50 |
+
opts,
|
51 |
+
setApi,
|
52 |
+
plugins,
|
53 |
+
className,
|
54 |
+
children,
|
55 |
+
...props
|
56 |
+
},
|
57 |
+
ref
|
58 |
+
) => {
|
59 |
+
const [carouselRef, api] = useEmblaCarousel(
|
60 |
+
{
|
61 |
+
...opts,
|
62 |
+
axis: orientation === "horizontal" ? "x" : "y",
|
63 |
+
},
|
64 |
+
plugins
|
65 |
+
)
|
66 |
+
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
67 |
+
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
68 |
+
|
69 |
+
const onSelect = React.useCallback((api: CarouselApi) => {
|
70 |
+
if (!api) {
|
71 |
+
return
|
72 |
+
}
|
73 |
+
|
74 |
+
setCanScrollPrev(api.canScrollPrev())
|
75 |
+
setCanScrollNext(api.canScrollNext())
|
76 |
+
}, [])
|
77 |
+
|
78 |
+
const scrollPrev = React.useCallback(() => {
|
79 |
+
api?.scrollPrev()
|
80 |
+
}, [api])
|
81 |
+
|
82 |
+
const scrollNext = React.useCallback(() => {
|
83 |
+
api?.scrollNext()
|
84 |
+
}, [api])
|
85 |
+
|
86 |
+
const handleKeyDown = React.useCallback(
|
87 |
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
88 |
+
if (event.key === "ArrowLeft") {
|
89 |
+
event.preventDefault()
|
90 |
+
scrollPrev()
|
91 |
+
} else if (event.key === "ArrowRight") {
|
92 |
+
event.preventDefault()
|
93 |
+
scrollNext()
|
94 |
+
}
|
95 |
+
},
|
96 |
+
[scrollPrev, scrollNext]
|
97 |
+
)
|
98 |
+
|
99 |
+
React.useEffect(() => {
|
100 |
+
if (!api || !setApi) {
|
101 |
+
return
|
102 |
+
}
|
103 |
+
|
104 |
+
setApi(api)
|
105 |
+
}, [api, setApi])
|
106 |
+
|
107 |
+
React.useEffect(() => {
|
108 |
+
if (!api) {
|
109 |
+
return
|
110 |
+
}
|
111 |
+
|
112 |
+
onSelect(api)
|
113 |
+
api.on("reInit", onSelect)
|
114 |
+
api.on("select", onSelect)
|
115 |
+
|
116 |
+
return () => {
|
117 |
+
api?.off("select", onSelect)
|
118 |
+
}
|
119 |
+
}, [api, onSelect])
|
120 |
+
|
121 |
+
return (
|
122 |
+
<CarouselContext.Provider
|
123 |
+
value={{
|
124 |
+
carouselRef,
|
125 |
+
api: api,
|
126 |
+
opts,
|
127 |
+
orientation:
|
128 |
+
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
129 |
+
scrollPrev,
|
130 |
+
scrollNext,
|
131 |
+
canScrollPrev,
|
132 |
+
canScrollNext,
|
133 |
+
}}
|
134 |
+
>
|
135 |
+
<div
|
136 |
+
ref={ref}
|
137 |
+
onKeyDownCapture={handleKeyDown}
|
138 |
+
className={cn("relative", className)}
|
139 |
+
role="region"
|
140 |
+
aria-roledescription="carousel"
|
141 |
+
{...props}
|
142 |
+
>
|
143 |
+
{children}
|
144 |
+
</div>
|
145 |
+
</CarouselContext.Provider>
|
146 |
+
)
|
147 |
+
}
|
148 |
+
)
|
149 |
+
Carousel.displayName = "Carousel"
|
150 |
+
|
151 |
+
const CarouselContent = React.forwardRef<
|
152 |
+
HTMLDivElement,
|
153 |
+
React.HTMLAttributes<HTMLDivElement>
|
154 |
+
>(({ className, ...props }, ref) => {
|
155 |
+
const { carouselRef, orientation } = useCarousel()
|
156 |
+
|
157 |
+
return (
|
158 |
+
<div ref={carouselRef} className="overflow-hidden">
|
159 |
+
<div
|
160 |
+
ref={ref}
|
161 |
+
className={cn(
|
162 |
+
"flex",
|
163 |
+
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
164 |
+
className
|
165 |
+
)}
|
166 |
+
{...props}
|
167 |
+
/>
|
168 |
+
</div>
|
169 |
+
)
|
170 |
+
})
|
171 |
+
CarouselContent.displayName = "CarouselContent"
|
172 |
+
|
173 |
+
const CarouselItem = React.forwardRef<
|
174 |
+
HTMLDivElement,
|
175 |
+
React.HTMLAttributes<HTMLDivElement>
|
176 |
+
>(({ className, ...props }, ref) => {
|
177 |
+
const { orientation } = useCarousel()
|
178 |
+
|
179 |
+
return (
|
180 |
+
<div
|
181 |
+
ref={ref}
|
182 |
+
role="group"
|
183 |
+
aria-roledescription="slide"
|
184 |
+
className={cn(
|
185 |
+
"min-w-0 shrink-0 grow-0 basis-full",
|
186 |
+
orientation === "horizontal" ? "pl-4" : "pt-4",
|
187 |
+
className
|
188 |
+
)}
|
189 |
+
{...props}
|
190 |
+
/>
|
191 |
+
)
|
192 |
+
})
|
193 |
+
CarouselItem.displayName = "CarouselItem"
|
194 |
+
|
195 |
+
const CarouselPrevious = React.forwardRef<
|
196 |
+
HTMLButtonElement,
|
197 |
+
React.ComponentProps<typeof Button>
|
198 |
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
199 |
+
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
200 |
+
|
201 |
+
return (
|
202 |
+
<Button
|
203 |
+
ref={ref}
|
204 |
+
variant={variant}
|
205 |
+
size={size}
|
206 |
+
className={cn(
|
207 |
+
"absolute h-8 w-8 rounded-full",
|
208 |
+
orientation === "horizontal"
|
209 |
+
? "-left-12 top-1/2 -translate-y-1/2"
|
210 |
+
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
211 |
+
className
|
212 |
+
)}
|
213 |
+
disabled={!canScrollPrev}
|
214 |
+
onClick={scrollPrev}
|
215 |
+
{...props}
|
216 |
+
>
|
217 |
+
<ArrowLeft className="h-4 w-4" />
|
218 |
+
<span className="sr-only">Previous slide</span>
|
219 |
+
</Button>
|
220 |
+
)
|
221 |
+
})
|
222 |
+
CarouselPrevious.displayName = "CarouselPrevious"
|
223 |
+
|
224 |
+
const CarouselNext = React.forwardRef<
|
225 |
+
HTMLButtonElement,
|
226 |
+
React.ComponentProps<typeof Button>
|
227 |
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
228 |
+
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
229 |
+
|
230 |
+
return (
|
231 |
+
<Button
|
232 |
+
ref={ref}
|
233 |
+
variant={variant}
|
234 |
+
size={size}
|
235 |
+
className={cn(
|
236 |
+
"absolute h-8 w-8 rounded-full",
|
237 |
+
orientation === "horizontal"
|
238 |
+
? "-right-12 top-1/2 -translate-y-1/2"
|
239 |
+
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
240 |
+
className
|
241 |
+
)}
|
242 |
+
disabled={!canScrollNext}
|
243 |
+
onClick={scrollNext}
|
244 |
+
{...props}
|
245 |
+
>
|
246 |
+
<ArrowRight className="h-4 w-4" />
|
247 |
+
<span className="sr-only">Next slide</span>
|
248 |
+
</Button>
|
249 |
+
)
|
250 |
+
})
|
251 |
+
CarouselNext.displayName = "CarouselNext"
|
252 |
+
|
253 |
+
export {
|
254 |
+
type CarouselApi,
|
255 |
+
Carousel,
|
256 |
+
CarouselContent,
|
257 |
+
CarouselItem,
|
258 |
+
CarouselPrevious,
|
259 |
+
CarouselNext,
|
260 |
+
}
|
client/src/components/ui/chart.tsx
ADDED
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as RechartsPrimitive from "recharts"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
// Format: { THEME_NAME: CSS_SELECTOR }
|
9 |
+
const THEMES = { light: "", dark: ".dark" } as const
|
10 |
+
|
11 |
+
export type ChartConfig = {
|
12 |
+
[k in string]: {
|
13 |
+
label?: React.ReactNode
|
14 |
+
icon?: React.ComponentType
|
15 |
+
} & (
|
16 |
+
| { color?: string; theme?: never }
|
17 |
+
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
18 |
+
)
|
19 |
+
}
|
20 |
+
|
21 |
+
type ChartContextProps = {
|
22 |
+
config: ChartConfig
|
23 |
+
}
|
24 |
+
|
25 |
+
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
26 |
+
|
27 |
+
function useChart() {
|
28 |
+
const context = React.useContext(ChartContext)
|
29 |
+
|
30 |
+
if (!context) {
|
31 |
+
throw new Error("useChart must be used within a <ChartContainer />")
|
32 |
+
}
|
33 |
+
|
34 |
+
return context
|
35 |
+
}
|
36 |
+
|
37 |
+
const ChartContainer = React.forwardRef<
|
38 |
+
HTMLDivElement,
|
39 |
+
React.ComponentProps<"div"> & {
|
40 |
+
config: ChartConfig
|
41 |
+
children: React.ComponentProps<
|
42 |
+
typeof RechartsPrimitive.ResponsiveContainer
|
43 |
+
>["children"]
|
44 |
+
}
|
45 |
+
>(({ id, className, children, config, ...props }, ref) => {
|
46 |
+
const uniqueId = React.useId()
|
47 |
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
48 |
+
|
49 |
+
return (
|
50 |
+
<ChartContext.Provider value={{ config }}>
|
51 |
+
<div
|
52 |
+
data-chart={chartId}
|
53 |
+
ref={ref}
|
54 |
+
className={cn(
|
55 |
+
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
56 |
+
className
|
57 |
+
)}
|
58 |
+
{...props}
|
59 |
+
>
|
60 |
+
<ChartStyle id={chartId} config={config} />
|
61 |
+
<RechartsPrimitive.ResponsiveContainer>
|
62 |
+
{children}
|
63 |
+
</RechartsPrimitive.ResponsiveContainer>
|
64 |
+
</div>
|
65 |
+
</ChartContext.Provider>
|
66 |
+
)
|
67 |
+
})
|
68 |
+
ChartContainer.displayName = "Chart"
|
69 |
+
|
70 |
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
71 |
+
const colorConfig = Object.entries(config).filter(
|
72 |
+
([, config]) => config.theme || config.color
|
73 |
+
)
|
74 |
+
|
75 |
+
if (!colorConfig.length) {
|
76 |
+
return null
|
77 |
+
}
|
78 |
+
|
79 |
+
return (
|
80 |
+
<style
|
81 |
+
dangerouslySetInnerHTML={{
|
82 |
+
__html: Object.entries(THEMES)
|
83 |
+
.map(
|
84 |
+
([theme, prefix]) => `
|
85 |
+
${prefix} [data-chart=${id}] {
|
86 |
+
${colorConfig
|
87 |
+
.map(([key, itemConfig]) => {
|
88 |
+
const color =
|
89 |
+
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
90 |
+
itemConfig.color
|
91 |
+
return color ? ` --color-${key}: ${color};` : null
|
92 |
+
})
|
93 |
+
.join("\n")}
|
94 |
+
}
|
95 |
+
`
|
96 |
+
)
|
97 |
+
.join("\n"),
|
98 |
+
}}
|
99 |
+
/>
|
100 |
+
)
|
101 |
+
}
|
102 |
+
|
103 |
+
const ChartTooltip = RechartsPrimitive.Tooltip
|
104 |
+
|
105 |
+
const ChartTooltipContent = React.forwardRef<
|
106 |
+
HTMLDivElement,
|
107 |
+
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
108 |
+
React.ComponentProps<"div"> & {
|
109 |
+
hideLabel?: boolean
|
110 |
+
hideIndicator?: boolean
|
111 |
+
indicator?: "line" | "dot" | "dashed"
|
112 |
+
nameKey?: string
|
113 |
+
labelKey?: string
|
114 |
+
}
|
115 |
+
>(
|
116 |
+
(
|
117 |
+
{
|
118 |
+
active,
|
119 |
+
payload,
|
120 |
+
className,
|
121 |
+
indicator = "dot",
|
122 |
+
hideLabel = false,
|
123 |
+
hideIndicator = false,
|
124 |
+
label,
|
125 |
+
labelFormatter,
|
126 |
+
labelClassName,
|
127 |
+
formatter,
|
128 |
+
color,
|
129 |
+
nameKey,
|
130 |
+
labelKey,
|
131 |
+
},
|
132 |
+
ref
|
133 |
+
) => {
|
134 |
+
const { config } = useChart()
|
135 |
+
|
136 |
+
const tooltipLabel = React.useMemo(() => {
|
137 |
+
if (hideLabel || !payload?.length) {
|
138 |
+
return null
|
139 |
+
}
|
140 |
+
|
141 |
+
const [item] = payload
|
142 |
+
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
|
143 |
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
144 |
+
const value =
|
145 |
+
!labelKey && typeof label === "string"
|
146 |
+
? config[label as keyof typeof config]?.label || label
|
147 |
+
: itemConfig?.label
|
148 |
+
|
149 |
+
if (labelFormatter) {
|
150 |
+
return (
|
151 |
+
<div className={cn("font-medium", labelClassName)}>
|
152 |
+
{labelFormatter(value, payload)}
|
153 |
+
</div>
|
154 |
+
)
|
155 |
+
}
|
156 |
+
|
157 |
+
if (!value) {
|
158 |
+
return null
|
159 |
+
}
|
160 |
+
|
161 |
+
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
162 |
+
}, [
|
163 |
+
label,
|
164 |
+
labelFormatter,
|
165 |
+
payload,
|
166 |
+
hideLabel,
|
167 |
+
labelClassName,
|
168 |
+
config,
|
169 |
+
labelKey,
|
170 |
+
])
|
171 |
+
|
172 |
+
if (!active || !payload?.length) {
|
173 |
+
return null
|
174 |
+
}
|
175 |
+
|
176 |
+
const nestLabel = payload.length === 1 && indicator !== "dot"
|
177 |
+
|
178 |
+
return (
|
179 |
+
<div
|
180 |
+
ref={ref}
|
181 |
+
className={cn(
|
182 |
+
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
183 |
+
className
|
184 |
+
)}
|
185 |
+
>
|
186 |
+
{!nestLabel ? tooltipLabel : null}
|
187 |
+
<div className="grid gap-1.5">
|
188 |
+
{payload.map((item, index) => {
|
189 |
+
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
190 |
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
191 |
+
const indicatorColor = color || item.payload.fill || item.color
|
192 |
+
|
193 |
+
return (
|
194 |
+
<div
|
195 |
+
key={item.dataKey}
|
196 |
+
className={cn(
|
197 |
+
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
198 |
+
indicator === "dot" && "items-center"
|
199 |
+
)}
|
200 |
+
>
|
201 |
+
{formatter && item?.value !== undefined && item.name ? (
|
202 |
+
formatter(item.value, item.name, item, index, item.payload)
|
203 |
+
) : (
|
204 |
+
<>
|
205 |
+
{itemConfig?.icon ? (
|
206 |
+
<itemConfig.icon />
|
207 |
+
) : (
|
208 |
+
!hideIndicator && (
|
209 |
+
<div
|
210 |
+
className={cn(
|
211 |
+
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
212 |
+
{
|
213 |
+
"h-2.5 w-2.5": indicator === "dot",
|
214 |
+
"w-1": indicator === "line",
|
215 |
+
"w-0 border-[1.5px] border-dashed bg-transparent":
|
216 |
+
indicator === "dashed",
|
217 |
+
"my-0.5": nestLabel && indicator === "dashed",
|
218 |
+
}
|
219 |
+
)}
|
220 |
+
style={
|
221 |
+
{
|
222 |
+
"--color-bg": indicatorColor,
|
223 |
+
"--color-border": indicatorColor,
|
224 |
+
} as React.CSSProperties
|
225 |
+
}
|
226 |
+
/>
|
227 |
+
)
|
228 |
+
)}
|
229 |
+
<div
|
230 |
+
className={cn(
|
231 |
+
"flex flex-1 justify-between leading-none",
|
232 |
+
nestLabel ? "items-end" : "items-center"
|
233 |
+
)}
|
234 |
+
>
|
235 |
+
<div className="grid gap-1.5">
|
236 |
+
{nestLabel ? tooltipLabel : null}
|
237 |
+
<span className="text-muted-foreground">
|
238 |
+
{itemConfig?.label || item.name}
|
239 |
+
</span>
|
240 |
+
</div>
|
241 |
+
{item.value && (
|
242 |
+
<span className="font-mono font-medium tabular-nums text-foreground">
|
243 |
+
{item.value.toLocaleString()}
|
244 |
+
</span>
|
245 |
+
)}
|
246 |
+
</div>
|
247 |
+
</>
|
248 |
+
)}
|
249 |
+
</div>
|
250 |
+
)
|
251 |
+
})}
|
252 |
+
</div>
|
253 |
+
</div>
|
254 |
+
)
|
255 |
+
}
|
256 |
+
)
|
257 |
+
ChartTooltipContent.displayName = "ChartTooltip"
|
258 |
+
|
259 |
+
const ChartLegend = RechartsPrimitive.Legend
|
260 |
+
|
261 |
+
const ChartLegendContent = React.forwardRef<
|
262 |
+
HTMLDivElement,
|
263 |
+
React.ComponentProps<"div"> &
|
264 |
+
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
265 |
+
hideIcon?: boolean
|
266 |
+
nameKey?: string
|
267 |
+
}
|
268 |
+
>(
|
269 |
+
(
|
270 |
+
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
271 |
+
ref
|
272 |
+
) => {
|
273 |
+
const { config } = useChart()
|
274 |
+
|
275 |
+
if (!payload?.length) {
|
276 |
+
return null
|
277 |
+
}
|
278 |
+
|
279 |
+
return (
|
280 |
+
<div
|
281 |
+
ref={ref}
|
282 |
+
className={cn(
|
283 |
+
"flex items-center justify-center gap-4",
|
284 |
+
verticalAlign === "top" ? "pb-3" : "pt-3",
|
285 |
+
className
|
286 |
+
)}
|
287 |
+
>
|
288 |
+
{payload.map((item) => {
|
289 |
+
const key = `${nameKey || item.dataKey || "value"}`
|
290 |
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
291 |
+
|
292 |
+
return (
|
293 |
+
<div
|
294 |
+
key={item.value}
|
295 |
+
className={cn(
|
296 |
+
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
297 |
+
)}
|
298 |
+
>
|
299 |
+
{itemConfig?.icon && !hideIcon ? (
|
300 |
+
<itemConfig.icon />
|
301 |
+
) : (
|
302 |
+
<div
|
303 |
+
className="h-2 w-2 shrink-0 rounded-[2px]"
|
304 |
+
style={{
|
305 |
+
backgroundColor: item.color,
|
306 |
+
}}
|
307 |
+
/>
|
308 |
+
)}
|
309 |
+
{itemConfig?.label}
|
310 |
+
</div>
|
311 |
+
)
|
312 |
+
})}
|
313 |
+
</div>
|
314 |
+
)
|
315 |
+
}
|
316 |
+
)
|
317 |
+
ChartLegendContent.displayName = "ChartLegend"
|
318 |
+
|
319 |
+
// Helper to extract item config from a payload.
|
320 |
+
function getPayloadConfigFromPayload(
|
321 |
+
config: ChartConfig,
|
322 |
+
payload: unknown,
|
323 |
+
key: string
|
324 |
+
) {
|
325 |
+
if (typeof payload !== "object" || payload === null) {
|
326 |
+
return undefined
|
327 |
+
}
|
328 |
+
|
329 |
+
const payloadPayload =
|
330 |
+
"payload" in payload &&
|
331 |
+
typeof payload.payload === "object" &&
|
332 |
+
payload.payload !== null
|
333 |
+
? payload.payload
|
334 |
+
: undefined
|
335 |
+
|
336 |
+
let configLabelKey: string = key
|
337 |
+
|
338 |
+
if (
|
339 |
+
key in payload &&
|
340 |
+
typeof payload[key as keyof typeof payload] === "string"
|
341 |
+
) {
|
342 |
+
configLabelKey = payload[key as keyof typeof payload] as string
|
343 |
+
} else if (
|
344 |
+
payloadPayload &&
|
345 |
+
key in payloadPayload &&
|
346 |
+
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
347 |
+
) {
|
348 |
+
configLabelKey = payloadPayload[
|
349 |
+
key as keyof typeof payloadPayload
|
350 |
+
] as string
|
351 |
+
}
|
352 |
+
|
353 |
+
return configLabelKey in config
|
354 |
+
? config[configLabelKey]
|
355 |
+
: config[key as keyof typeof config]
|
356 |
+
}
|
357 |
+
|
358 |
+
export {
|
359 |
+
ChartContainer,
|
360 |
+
ChartTooltip,
|
361 |
+
ChartTooltipContent,
|
362 |
+
ChartLegend,
|
363 |
+
ChartLegendContent,
|
364 |
+
ChartStyle,
|
365 |
+
}
|
client/src/components/ui/checkbox.tsx
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
3 |
+
import { Check } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const Checkbox = React.forwardRef<
|
8 |
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
9 |
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
10 |
+
>(({ className, ...props }, ref) => (
|
11 |
+
<CheckboxPrimitive.Root
|
12 |
+
ref={ref}
|
13 |
+
className={cn(
|
14 |
+
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
15 |
+
className
|
16 |
+
)}
|
17 |
+
{...props}
|
18 |
+
>
|
19 |
+
<CheckboxPrimitive.Indicator
|
20 |
+
className={cn("flex items-center justify-center text-current")}
|
21 |
+
>
|
22 |
+
<Check className="h-4 w-4" />
|
23 |
+
</CheckboxPrimitive.Indicator>
|
24 |
+
</CheckboxPrimitive.Root>
|
25 |
+
))
|
26 |
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
27 |
+
|
28 |
+
export { Checkbox }
|
client/src/components/ui/collapsible.tsx
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
4 |
+
|
5 |
+
const Collapsible = CollapsiblePrimitive.Root
|
6 |
+
|
7 |
+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
8 |
+
|
9 |
+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
10 |
+
|
11 |
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
client/src/components/ui/command.tsx
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { type DialogProps } from "@radix-ui/react-dialog"
|
3 |
+
import { Command as CommandPrimitive } from "cmdk"
|
4 |
+
import { Search } from "lucide-react"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
8 |
+
|
9 |
+
const Command = React.forwardRef<
|
10 |
+
React.ElementRef<typeof CommandPrimitive>,
|
11 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
12 |
+
>(({ className, ...props }, ref) => (
|
13 |
+
<CommandPrimitive
|
14 |
+
ref={ref}
|
15 |
+
className={cn(
|
16 |
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
17 |
+
className
|
18 |
+
)}
|
19 |
+
{...props}
|
20 |
+
/>
|
21 |
+
))
|
22 |
+
Command.displayName = CommandPrimitive.displayName
|
23 |
+
|
24 |
+
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
25 |
+
return (
|
26 |
+
<Dialog {...props}>
|
27 |
+
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
28 |
+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
29 |
+
{children}
|
30 |
+
</Command>
|
31 |
+
</DialogContent>
|
32 |
+
</Dialog>
|
33 |
+
)
|
34 |
+
}
|
35 |
+
|
36 |
+
const CommandInput = React.forwardRef<
|
37 |
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
38 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
39 |
+
>(({ className, ...props }, ref) => (
|
40 |
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
41 |
+
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
42 |
+
<CommandPrimitive.Input
|
43 |
+
ref={ref}
|
44 |
+
className={cn(
|
45 |
+
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
46 |
+
className
|
47 |
+
)}
|
48 |
+
{...props}
|
49 |
+
/>
|
50 |
+
</div>
|
51 |
+
))
|
52 |
+
|
53 |
+
CommandInput.displayName = CommandPrimitive.Input.displayName
|
54 |
+
|
55 |
+
const CommandList = React.forwardRef<
|
56 |
+
React.ElementRef<typeof CommandPrimitive.List>,
|
57 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
58 |
+
>(({ className, ...props }, ref) => (
|
59 |
+
<CommandPrimitive.List
|
60 |
+
ref={ref}
|
61 |
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
62 |
+
{...props}
|
63 |
+
/>
|
64 |
+
))
|
65 |
+
|
66 |
+
CommandList.displayName = CommandPrimitive.List.displayName
|
67 |
+
|
68 |
+
const CommandEmpty = React.forwardRef<
|
69 |
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
70 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
71 |
+
>((props, ref) => (
|
72 |
+
<CommandPrimitive.Empty
|
73 |
+
ref={ref}
|
74 |
+
className="py-6 text-center text-sm"
|
75 |
+
{...props}
|
76 |
+
/>
|
77 |
+
))
|
78 |
+
|
79 |
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
80 |
+
|
81 |
+
const CommandGroup = React.forwardRef<
|
82 |
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
83 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
84 |
+
>(({ className, ...props }, ref) => (
|
85 |
+
<CommandPrimitive.Group
|
86 |
+
ref={ref}
|
87 |
+
className={cn(
|
88 |
+
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
89 |
+
className
|
90 |
+
)}
|
91 |
+
{...props}
|
92 |
+
/>
|
93 |
+
))
|
94 |
+
|
95 |
+
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
96 |
+
|
97 |
+
const CommandSeparator = React.forwardRef<
|
98 |
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
99 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
100 |
+
>(({ className, ...props }, ref) => (
|
101 |
+
<CommandPrimitive.Separator
|
102 |
+
ref={ref}
|
103 |
+
className={cn("-mx-1 h-px bg-border", className)}
|
104 |
+
{...props}
|
105 |
+
/>
|
106 |
+
))
|
107 |
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
108 |
+
|
109 |
+
const CommandItem = React.forwardRef<
|
110 |
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
111 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
112 |
+
>(({ className, ...props }, ref) => (
|
113 |
+
<CommandPrimitive.Item
|
114 |
+
ref={ref}
|
115 |
+
className={cn(
|
116 |
+
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
117 |
+
className
|
118 |
+
)}
|
119 |
+
{...props}
|
120 |
+
/>
|
121 |
+
))
|
122 |
+
|
123 |
+
CommandItem.displayName = CommandPrimitive.Item.displayName
|
124 |
+
|
125 |
+
const CommandShortcut = ({
|
126 |
+
className,
|
127 |
+
...props
|
128 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
129 |
+
return (
|
130 |
+
<span
|
131 |
+
className={cn(
|
132 |
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
133 |
+
className
|
134 |
+
)}
|
135 |
+
{...props}
|
136 |
+
/>
|
137 |
+
)
|
138 |
+
}
|
139 |
+
CommandShortcut.displayName = "CommandShortcut"
|
140 |
+
|
141 |
+
export {
|
142 |
+
Command,
|
143 |
+
CommandDialog,
|
144 |
+
CommandInput,
|
145 |
+
CommandList,
|
146 |
+
CommandEmpty,
|
147 |
+
CommandGroup,
|
148 |
+
CommandItem,
|
149 |
+
CommandShortcut,
|
150 |
+
CommandSeparator,
|
151 |
+
}
|
client/src/components/ui/context-menu.tsx
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
3 |
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const ContextMenu = ContextMenuPrimitive.Root
|
8 |
+
|
9 |
+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
10 |
+
|
11 |
+
const ContextMenuGroup = ContextMenuPrimitive.Group
|
12 |
+
|
13 |
+
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
14 |
+
|
15 |
+
const ContextMenuSub = ContextMenuPrimitive.Sub
|
16 |
+
|
17 |
+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
18 |
+
|
19 |
+
const ContextMenuSubTrigger = React.forwardRef<
|
20 |
+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
21 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
22 |
+
inset?: boolean
|
23 |
+
}
|
24 |
+
>(({ className, inset, children, ...props }, ref) => (
|
25 |
+
<ContextMenuPrimitive.SubTrigger
|
26 |
+
ref={ref}
|
27 |
+
className={cn(
|
28 |
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
29 |
+
inset && "pl-8",
|
30 |
+
className
|
31 |
+
)}
|
32 |
+
{...props}
|
33 |
+
>
|
34 |
+
{children}
|
35 |
+
<ChevronRight className="ml-auto h-4 w-4" />
|
36 |
+
</ContextMenuPrimitive.SubTrigger>
|
37 |
+
))
|
38 |
+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
39 |
+
|
40 |
+
const ContextMenuSubContent = React.forwardRef<
|
41 |
+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
42 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
43 |
+
>(({ className, ...props }, ref) => (
|
44 |
+
<ContextMenuPrimitive.SubContent
|
45 |
+
ref={ref}
|
46 |
+
className={cn(
|
47 |
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 origin-[--radix-context-menu-content-transform-origin]",
|
48 |
+
className
|
49 |
+
)}
|
50 |
+
{...props}
|
51 |
+
/>
|
52 |
+
))
|
53 |
+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
54 |
+
|
55 |
+
const ContextMenuContent = React.forwardRef<
|
56 |
+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
57 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
58 |
+
>(({ className, ...props }, ref) => (
|
59 |
+
<ContextMenuPrimitive.Portal>
|
60 |
+
<ContextMenuPrimitive.Content
|
61 |
+
ref={ref}
|
62 |
+
className={cn(
|
63 |
+
"z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 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 origin-[--radix-context-menu-content-transform-origin]",
|
64 |
+
className
|
65 |
+
)}
|
66 |
+
{...props}
|
67 |
+
/>
|
68 |
+
</ContextMenuPrimitive.Portal>
|
69 |
+
))
|
70 |
+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
71 |
+
|
72 |
+
const ContextMenuItem = React.forwardRef<
|
73 |
+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
74 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
75 |
+
inset?: boolean
|
76 |
+
}
|
77 |
+
>(({ className, inset, ...props }, ref) => (
|
78 |
+
<ContextMenuPrimitive.Item
|
79 |
+
ref={ref}
|
80 |
+
className={cn(
|
81 |
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
82 |
+
inset && "pl-8",
|
83 |
+
className
|
84 |
+
)}
|
85 |
+
{...props}
|
86 |
+
/>
|
87 |
+
))
|
88 |
+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
89 |
+
|
90 |
+
const ContextMenuCheckboxItem = React.forwardRef<
|
91 |
+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
92 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
93 |
+
>(({ className, children, checked, ...props }, ref) => (
|
94 |
+
<ContextMenuPrimitive.CheckboxItem
|
95 |
+
ref={ref}
|
96 |
+
className={cn(
|
97 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
98 |
+
className
|
99 |
+
)}
|
100 |
+
checked={checked}
|
101 |
+
{...props}
|
102 |
+
>
|
103 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
104 |
+
<ContextMenuPrimitive.ItemIndicator>
|
105 |
+
<Check className="h-4 w-4" />
|
106 |
+
</ContextMenuPrimitive.ItemIndicator>
|
107 |
+
</span>
|
108 |
+
{children}
|
109 |
+
</ContextMenuPrimitive.CheckboxItem>
|
110 |
+
))
|
111 |
+
ContextMenuCheckboxItem.displayName =
|
112 |
+
ContextMenuPrimitive.CheckboxItem.displayName
|
113 |
+
|
114 |
+
const ContextMenuRadioItem = React.forwardRef<
|
115 |
+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
116 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
117 |
+
>(({ className, children, ...props }, ref) => (
|
118 |
+
<ContextMenuPrimitive.RadioItem
|
119 |
+
ref={ref}
|
120 |
+
className={cn(
|
121 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
122 |
+
className
|
123 |
+
)}
|
124 |
+
{...props}
|
125 |
+
>
|
126 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
127 |
+
<ContextMenuPrimitive.ItemIndicator>
|
128 |
+
<Circle className="h-2 w-2 fill-current" />
|
129 |
+
</ContextMenuPrimitive.ItemIndicator>
|
130 |
+
</span>
|
131 |
+
{children}
|
132 |
+
</ContextMenuPrimitive.RadioItem>
|
133 |
+
))
|
134 |
+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
135 |
+
|
136 |
+
const ContextMenuLabel = React.forwardRef<
|
137 |
+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
138 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
139 |
+
inset?: boolean
|
140 |
+
}
|
141 |
+
>(({ className, inset, ...props }, ref) => (
|
142 |
+
<ContextMenuPrimitive.Label
|
143 |
+
ref={ref}
|
144 |
+
className={cn(
|
145 |
+
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
146 |
+
inset && "pl-8",
|
147 |
+
className
|
148 |
+
)}
|
149 |
+
{...props}
|
150 |
+
/>
|
151 |
+
))
|
152 |
+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
153 |
+
|
154 |
+
const ContextMenuSeparator = React.forwardRef<
|
155 |
+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
156 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
157 |
+
>(({ className, ...props }, ref) => (
|
158 |
+
<ContextMenuPrimitive.Separator
|
159 |
+
ref={ref}
|
160 |
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
161 |
+
{...props}
|
162 |
+
/>
|
163 |
+
))
|
164 |
+
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
165 |
+
|
166 |
+
const ContextMenuShortcut = ({
|
167 |
+
className,
|
168 |
+
...props
|
169 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
170 |
+
return (
|
171 |
+
<span
|
172 |
+
className={cn(
|
173 |
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
174 |
+
className
|
175 |
+
)}
|
176 |
+
{...props}
|
177 |
+
/>
|
178 |
+
)
|
179 |
+
}
|
180 |
+
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
181 |
+
|
182 |
+
export {
|
183 |
+
ContextMenu,
|
184 |
+
ContextMenuTrigger,
|
185 |
+
ContextMenuContent,
|
186 |
+
ContextMenuItem,
|
187 |
+
ContextMenuCheckboxItem,
|
188 |
+
ContextMenuRadioItem,
|
189 |
+
ContextMenuLabel,
|
190 |
+
ContextMenuSeparator,
|
191 |
+
ContextMenuShortcut,
|
192 |
+
ContextMenuGroup,
|
193 |
+
ContextMenuPortal,
|
194 |
+
ContextMenuSub,
|
195 |
+
ContextMenuSubContent,
|
196 |
+
ContextMenuSubTrigger,
|
197 |
+
ContextMenuRadioGroup,
|
198 |
+
}
|
client/src/components/ui/dialog.tsx
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
5 |
+
import { X } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
|
9 |
+
const Dialog = DialogPrimitive.Root
|
10 |
+
|
11 |
+
const DialogTrigger = DialogPrimitive.Trigger
|
12 |
+
|
13 |
+
const DialogPortal = DialogPrimitive.Portal
|
14 |
+
|
15 |
+
const DialogClose = DialogPrimitive.Close
|
16 |
+
|
17 |
+
const DialogOverlay = React.forwardRef<
|
18 |
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
19 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
20 |
+
>(({ className, ...props }, ref) => (
|
21 |
+
<DialogPrimitive.Overlay
|
22 |
+
ref={ref}
|
23 |
+
className={cn(
|
24 |
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
25 |
+
className
|
26 |
+
)}
|
27 |
+
{...props}
|
28 |
+
/>
|
29 |
+
))
|
30 |
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
31 |
+
|
32 |
+
const DialogContent = React.forwardRef<
|
33 |
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
34 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
35 |
+
>(({ className, children, ...props }, ref) => (
|
36 |
+
<DialogPortal>
|
37 |
+
<DialogOverlay />
|
38 |
+
<DialogPrimitive.Content
|
39 |
+
ref={ref}
|
40 |
+
className={cn(
|
41 |
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
42 |
+
className
|
43 |
+
)}
|
44 |
+
{...props}
|
45 |
+
>
|
46 |
+
{children}
|
47 |
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
48 |
+
<X className="h-4 w-4" />
|
49 |
+
<span className="sr-only">Close</span>
|
50 |
+
</DialogPrimitive.Close>
|
51 |
+
</DialogPrimitive.Content>
|
52 |
+
</DialogPortal>
|
53 |
+
))
|
54 |
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
55 |
+
|
56 |
+
const DialogHeader = ({
|
57 |
+
className,
|
58 |
+
...props
|
59 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
60 |
+
<div
|
61 |
+
className={cn(
|
62 |
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
63 |
+
className
|
64 |
+
)}
|
65 |
+
{...props}
|
66 |
+
/>
|
67 |
+
)
|
68 |
+
DialogHeader.displayName = "DialogHeader"
|
69 |
+
|
70 |
+
const DialogFooter = ({
|
71 |
+
className,
|
72 |
+
...props
|
73 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
74 |
+
<div
|
75 |
+
className={cn(
|
76 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
77 |
+
className
|
78 |
+
)}
|
79 |
+
{...props}
|
80 |
+
/>
|
81 |
+
)
|
82 |
+
DialogFooter.displayName = "DialogFooter"
|
83 |
+
|
84 |
+
const DialogTitle = React.forwardRef<
|
85 |
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
86 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
87 |
+
>(({ className, ...props }, ref) => (
|
88 |
+
<DialogPrimitive.Title
|
89 |
+
ref={ref}
|
90 |
+
className={cn(
|
91 |
+
"text-lg font-semibold leading-none tracking-tight",
|
92 |
+
className
|
93 |
+
)}
|
94 |
+
{...props}
|
95 |
+
/>
|
96 |
+
))
|
97 |
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
98 |
+
|
99 |
+
const DialogDescription = React.forwardRef<
|
100 |
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
101 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
102 |
+
>(({ className, ...props }, ref) => (
|
103 |
+
<DialogPrimitive.Description
|
104 |
+
ref={ref}
|
105 |
+
className={cn("text-sm text-muted-foreground", className)}
|
106 |
+
{...props}
|
107 |
+
/>
|
108 |
+
))
|
109 |
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
110 |
+
|
111 |
+
export {
|
112 |
+
Dialog,
|
113 |
+
DialogPortal,
|
114 |
+
DialogOverlay,
|
115 |
+
DialogClose,
|
116 |
+
DialogTrigger,
|
117 |
+
DialogContent,
|
118 |
+
DialogHeader,
|
119 |
+
DialogFooter,
|
120 |
+
DialogTitle,
|
121 |
+
DialogDescription,
|
122 |
+
}
|
client/src/components/ui/drawer.tsx
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import { Drawer as DrawerPrimitive } from "vaul"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const Drawer = ({
|
9 |
+
shouldScaleBackground = true,
|
10 |
+
...props
|
11 |
+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
12 |
+
<DrawerPrimitive.Root
|
13 |
+
shouldScaleBackground={shouldScaleBackground}
|
14 |
+
{...props}
|
15 |
+
/>
|
16 |
+
)
|
17 |
+
Drawer.displayName = "Drawer"
|
18 |
+
|
19 |
+
const DrawerTrigger = DrawerPrimitive.Trigger
|
20 |
+
|
21 |
+
const DrawerPortal = DrawerPrimitive.Portal
|
22 |
+
|
23 |
+
const DrawerClose = DrawerPrimitive.Close
|
24 |
+
|
25 |
+
const DrawerOverlay = React.forwardRef<
|
26 |
+
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
27 |
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
28 |
+
>(({ className, ...props }, ref) => (
|
29 |
+
<DrawerPrimitive.Overlay
|
30 |
+
ref={ref}
|
31 |
+
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
32 |
+
{...props}
|
33 |
+
/>
|
34 |
+
))
|
35 |
+
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
36 |
+
|
37 |
+
const DrawerContent = React.forwardRef<
|
38 |
+
React.ElementRef<typeof DrawerPrimitive.Content>,
|
39 |
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
40 |
+
>(({ className, children, ...props }, ref) => (
|
41 |
+
<DrawerPortal>
|
42 |
+
<DrawerOverlay />
|
43 |
+
<DrawerPrimitive.Content
|
44 |
+
ref={ref}
|
45 |
+
className={cn(
|
46 |
+
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
47 |
+
className
|
48 |
+
)}
|
49 |
+
{...props}
|
50 |
+
>
|
51 |
+
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
52 |
+
{children}
|
53 |
+
</DrawerPrimitive.Content>
|
54 |
+
</DrawerPortal>
|
55 |
+
))
|
56 |
+
DrawerContent.displayName = "DrawerContent"
|
57 |
+
|
58 |
+
const DrawerHeader = ({
|
59 |
+
className,
|
60 |
+
...props
|
61 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
62 |
+
<div
|
63 |
+
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
64 |
+
{...props}
|
65 |
+
/>
|
66 |
+
)
|
67 |
+
DrawerHeader.displayName = "DrawerHeader"
|
68 |
+
|
69 |
+
const DrawerFooter = ({
|
70 |
+
className,
|
71 |
+
...props
|
72 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
73 |
+
<div
|
74 |
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
75 |
+
{...props}
|
76 |
+
/>
|
77 |
+
)
|
78 |
+
DrawerFooter.displayName = "DrawerFooter"
|
79 |
+
|
80 |
+
const DrawerTitle = React.forwardRef<
|
81 |
+
React.ElementRef<typeof DrawerPrimitive.Title>,
|
82 |
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
83 |
+
>(({ className, ...props }, ref) => (
|
84 |
+
<DrawerPrimitive.Title
|
85 |
+
ref={ref}
|
86 |
+
className={cn(
|
87 |
+
"text-lg font-semibold leading-none tracking-tight",
|
88 |
+
className
|
89 |
+
)}
|
90 |
+
{...props}
|
91 |
+
/>
|
92 |
+
))
|
93 |
+
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
|
94 |
+
|
95 |
+
const DrawerDescription = React.forwardRef<
|
96 |
+
React.ElementRef<typeof DrawerPrimitive.Description>,
|
97 |
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
98 |
+
>(({ className, ...props }, ref) => (
|
99 |
+
<DrawerPrimitive.Description
|
100 |
+
ref={ref}
|
101 |
+
className={cn("text-sm text-muted-foreground", className)}
|
102 |
+
{...props}
|
103 |
+
/>
|
104 |
+
))
|
105 |
+
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
|
106 |
+
|
107 |
+
export {
|
108 |
+
Drawer,
|
109 |
+
DrawerPortal,
|
110 |
+
DrawerOverlay,
|
111 |
+
DrawerTrigger,
|
112 |
+
DrawerClose,
|
113 |
+
DrawerContent,
|
114 |
+
DrawerHeader,
|
115 |
+
DrawerFooter,
|
116 |
+
DrawerTitle,
|
117 |
+
DrawerDescription,
|
118 |
+
}
|
client/src/components/ui/dropdown-menu.tsx
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
3 |
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const DropdownMenu = DropdownMenuPrimitive.Root
|
8 |
+
|
9 |
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
10 |
+
|
11 |
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
12 |
+
|
13 |
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
14 |
+
|
15 |
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
16 |
+
|
17 |
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
18 |
+
|
19 |
+
const DropdownMenuSubTrigger = React.forwardRef<
|
20 |
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
21 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
22 |
+
inset?: boolean
|
23 |
+
}
|
24 |
+
>(({ className, inset, children, ...props }, ref) => (
|
25 |
+
<DropdownMenuPrimitive.SubTrigger
|
26 |
+
ref={ref}
|
27 |
+
className={cn(
|
28 |
+
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
29 |
+
inset && "pl-8",
|
30 |
+
className
|
31 |
+
)}
|
32 |
+
{...props}
|
33 |
+
>
|
34 |
+
{children}
|
35 |
+
<ChevronRight className="ml-auto" />
|
36 |
+
</DropdownMenuPrimitive.SubTrigger>
|
37 |
+
))
|
38 |
+
DropdownMenuSubTrigger.displayName =
|
39 |
+
DropdownMenuPrimitive.SubTrigger.displayName
|
40 |
+
|
41 |
+
const DropdownMenuSubContent = React.forwardRef<
|
42 |
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
43 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
44 |
+
>(({ className, ...props }, ref) => (
|
45 |
+
<DropdownMenuPrimitive.SubContent
|
46 |
+
ref={ref}
|
47 |
+
className={cn(
|
48 |
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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 origin-[--radix-dropdown-menu-content-transform-origin]",
|
49 |
+
className
|
50 |
+
)}
|
51 |
+
{...props}
|
52 |
+
/>
|
53 |
+
))
|
54 |
+
DropdownMenuSubContent.displayName =
|
55 |
+
DropdownMenuPrimitive.SubContent.displayName
|
56 |
+
|
57 |
+
const DropdownMenuContent = React.forwardRef<
|
58 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
59 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
60 |
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
61 |
+
<DropdownMenuPrimitive.Portal>
|
62 |
+
<DropdownMenuPrimitive.Content
|
63 |
+
ref={ref}
|
64 |
+
sideOffset={sideOffset}
|
65 |
+
className={cn(
|
66 |
+
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 origin-[--radix-dropdown-menu-content-transform-origin]",
|
67 |
+
className
|
68 |
+
)}
|
69 |
+
{...props}
|
70 |
+
/>
|
71 |
+
</DropdownMenuPrimitive.Portal>
|
72 |
+
))
|
73 |
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
74 |
+
|
75 |
+
const DropdownMenuItem = React.forwardRef<
|
76 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
77 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
78 |
+
inset?: boolean
|
79 |
+
}
|
80 |
+
>(({ className, inset, ...props }, ref) => (
|
81 |
+
<DropdownMenuPrimitive.Item
|
82 |
+
ref={ref}
|
83 |
+
className={cn(
|
84 |
+
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
85 |
+
inset && "pl-8",
|
86 |
+
className
|
87 |
+
)}
|
88 |
+
{...props}
|
89 |
+
/>
|
90 |
+
))
|
91 |
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
92 |
+
|
93 |
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
94 |
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
95 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
96 |
+
>(({ className, children, checked, ...props }, ref) => (
|
97 |
+
<DropdownMenuPrimitive.CheckboxItem
|
98 |
+
ref={ref}
|
99 |
+
className={cn(
|
100 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
101 |
+
className
|
102 |
+
)}
|
103 |
+
checked={checked}
|
104 |
+
{...props}
|
105 |
+
>
|
106 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
107 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
108 |
+
<Check className="h-4 w-4" />
|
109 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
110 |
+
</span>
|
111 |
+
{children}
|
112 |
+
</DropdownMenuPrimitive.CheckboxItem>
|
113 |
+
))
|
114 |
+
DropdownMenuCheckboxItem.displayName =
|
115 |
+
DropdownMenuPrimitive.CheckboxItem.displayName
|
116 |
+
|
117 |
+
const DropdownMenuRadioItem = React.forwardRef<
|
118 |
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
119 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
120 |
+
>(({ className, children, ...props }, ref) => (
|
121 |
+
<DropdownMenuPrimitive.RadioItem
|
122 |
+
ref={ref}
|
123 |
+
className={cn(
|
124 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
125 |
+
className
|
126 |
+
)}
|
127 |
+
{...props}
|
128 |
+
>
|
129 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
130 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
131 |
+
<Circle className="h-2 w-2 fill-current" />
|
132 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
133 |
+
</span>
|
134 |
+
{children}
|
135 |
+
</DropdownMenuPrimitive.RadioItem>
|
136 |
+
))
|
137 |
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
138 |
+
|
139 |
+
const DropdownMenuLabel = React.forwardRef<
|
140 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
141 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
142 |
+
inset?: boolean
|
143 |
+
}
|
144 |
+
>(({ className, inset, ...props }, ref) => (
|
145 |
+
<DropdownMenuPrimitive.Label
|
146 |
+
ref={ref}
|
147 |
+
className={cn(
|
148 |
+
"px-2 py-1.5 text-sm font-semibold",
|
149 |
+
inset && "pl-8",
|
150 |
+
className
|
151 |
+
)}
|
152 |
+
{...props}
|
153 |
+
/>
|
154 |
+
))
|
155 |
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
156 |
+
|
157 |
+
const DropdownMenuSeparator = React.forwardRef<
|
158 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
159 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
160 |
+
>(({ className, ...props }, ref) => (
|
161 |
+
<DropdownMenuPrimitive.Separator
|
162 |
+
ref={ref}
|
163 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
164 |
+
{...props}
|
165 |
+
/>
|
166 |
+
))
|
167 |
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
168 |
+
|
169 |
+
const DropdownMenuShortcut = ({
|
170 |
+
className,
|
171 |
+
...props
|
172 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
173 |
+
return (
|
174 |
+
<span
|
175 |
+
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
176 |
+
{...props}
|
177 |
+
/>
|
178 |
+
)
|
179 |
+
}
|
180 |
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
181 |
+
|
182 |
+
export {
|
183 |
+
DropdownMenu,
|
184 |
+
DropdownMenuTrigger,
|
185 |
+
DropdownMenuContent,
|
186 |
+
DropdownMenuItem,
|
187 |
+
DropdownMenuCheckboxItem,
|
188 |
+
DropdownMenuRadioItem,
|
189 |
+
DropdownMenuLabel,
|
190 |
+
DropdownMenuSeparator,
|
191 |
+
DropdownMenuShortcut,
|
192 |
+
DropdownMenuGroup,
|
193 |
+
DropdownMenuPortal,
|
194 |
+
DropdownMenuSub,
|
195 |
+
DropdownMenuSubContent,
|
196 |
+
DropdownMenuSubTrigger,
|
197 |
+
DropdownMenuRadioGroup,
|
198 |
+
}
|
client/src/components/ui/form.tsx
ADDED
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
5 |
+
import { Slot } from "@radix-ui/react-slot"
|
6 |
+
import {
|
7 |
+
Controller,
|
8 |
+
FormProvider,
|
9 |
+
useFormContext,
|
10 |
+
type ControllerProps,
|
11 |
+
type FieldPath,
|
12 |
+
type FieldValues,
|
13 |
+
} from "react-hook-form"
|
14 |
+
|
15 |
+
import { cn } from "@/lib/utils"
|
16 |
+
import { Label } from "@/components/ui/label"
|
17 |
+
|
18 |
+
const Form = FormProvider
|
19 |
+
|
20 |
+
type FormFieldContextValue<
|
21 |
+
TFieldValues extends FieldValues = FieldValues,
|
22 |
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
23 |
+
> = {
|
24 |
+
name: TName
|
25 |
+
}
|
26 |
+
|
27 |
+
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
28 |
+
{} as FormFieldContextValue
|
29 |
+
)
|
30 |
+
|
31 |
+
const FormField = <
|
32 |
+
TFieldValues extends FieldValues = FieldValues,
|
33 |
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
34 |
+
>({
|
35 |
+
...props
|
36 |
+
}: ControllerProps<TFieldValues, TName>) => {
|
37 |
+
return (
|
38 |
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
39 |
+
<Controller {...props} />
|
40 |
+
</FormFieldContext.Provider>
|
41 |
+
)
|
42 |
+
}
|
43 |
+
|
44 |
+
const useFormField = () => {
|
45 |
+
const fieldContext = React.useContext(FormFieldContext)
|
46 |
+
const itemContext = React.useContext(FormItemContext)
|
47 |
+
const { getFieldState, formState } = useFormContext()
|
48 |
+
|
49 |
+
const fieldState = getFieldState(fieldContext.name, formState)
|
50 |
+
|
51 |
+
if (!fieldContext) {
|
52 |
+
throw new Error("useFormField should be used within <FormField>")
|
53 |
+
}
|
54 |
+
|
55 |
+
const { id } = itemContext
|
56 |
+
|
57 |
+
return {
|
58 |
+
id,
|
59 |
+
name: fieldContext.name,
|
60 |
+
formItemId: `${id}-form-item`,
|
61 |
+
formDescriptionId: `${id}-form-item-description`,
|
62 |
+
formMessageId: `${id}-form-item-message`,
|
63 |
+
...fieldState,
|
64 |
+
}
|
65 |
+
}
|
66 |
+
|
67 |
+
type FormItemContextValue = {
|
68 |
+
id: string
|
69 |
+
}
|
70 |
+
|
71 |
+
const FormItemContext = React.createContext<FormItemContextValue>(
|
72 |
+
{} as FormItemContextValue
|
73 |
+
)
|
74 |
+
|
75 |
+
const FormItem = React.forwardRef<
|
76 |
+
HTMLDivElement,
|
77 |
+
React.HTMLAttributes<HTMLDivElement>
|
78 |
+
>(({ className, ...props }, ref) => {
|
79 |
+
const id = React.useId()
|
80 |
+
|
81 |
+
return (
|
82 |
+
<FormItemContext.Provider value={{ id }}>
|
83 |
+
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
84 |
+
</FormItemContext.Provider>
|
85 |
+
)
|
86 |
+
})
|
87 |
+
FormItem.displayName = "FormItem"
|
88 |
+
|
89 |
+
const FormLabel = React.forwardRef<
|
90 |
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
91 |
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
92 |
+
>(({ className, ...props }, ref) => {
|
93 |
+
const { error, formItemId } = useFormField()
|
94 |
+
|
95 |
+
return (
|
96 |
+
<Label
|
97 |
+
ref={ref}
|
98 |
+
className={cn(error && "text-destructive", className)}
|
99 |
+
htmlFor={formItemId}
|
100 |
+
{...props}
|
101 |
+
/>
|
102 |
+
)
|
103 |
+
})
|
104 |
+
FormLabel.displayName = "FormLabel"
|
105 |
+
|
106 |
+
const FormControl = React.forwardRef<
|
107 |
+
React.ElementRef<typeof Slot>,
|
108 |
+
React.ComponentPropsWithoutRef<typeof Slot>
|
109 |
+
>(({ ...props }, ref) => {
|
110 |
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
111 |
+
|
112 |
+
return (
|
113 |
+
<Slot
|
114 |
+
ref={ref}
|
115 |
+
id={formItemId}
|
116 |
+
aria-describedby={
|
117 |
+
!error
|
118 |
+
? `${formDescriptionId}`
|
119 |
+
: `${formDescriptionId} ${formMessageId}`
|
120 |
+
}
|
121 |
+
aria-invalid={!!error}
|
122 |
+
{...props}
|
123 |
+
/>
|
124 |
+
)
|
125 |
+
})
|
126 |
+
FormControl.displayName = "FormControl"
|
127 |
+
|
128 |
+
const FormDescription = React.forwardRef<
|
129 |
+
HTMLParagraphElement,
|
130 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
131 |
+
>(({ className, ...props }, ref) => {
|
132 |
+
const { formDescriptionId } = useFormField()
|
133 |
+
|
134 |
+
return (
|
135 |
+
<p
|
136 |
+
ref={ref}
|
137 |
+
id={formDescriptionId}
|
138 |
+
className={cn("text-sm text-muted-foreground", className)}
|
139 |
+
{...props}
|
140 |
+
/>
|
141 |
+
)
|
142 |
+
})
|
143 |
+
FormDescription.displayName = "FormDescription"
|
144 |
+
|
145 |
+
const FormMessage = React.forwardRef<
|
146 |
+
HTMLParagraphElement,
|
147 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
148 |
+
>(({ className, children, ...props }, ref) => {
|
149 |
+
const { error, formMessageId } = useFormField()
|
150 |
+
const body = error ? String(error?.message ?? "") : children
|
151 |
+
|
152 |
+
if (!body) {
|
153 |
+
return null
|
154 |
+
}
|
155 |
+
|
156 |
+
return (
|
157 |
+
<p
|
158 |
+
ref={ref}
|
159 |
+
id={formMessageId}
|
160 |
+
className={cn("text-sm font-medium text-destructive", className)}
|
161 |
+
{...props}
|
162 |
+
>
|
163 |
+
{body}
|
164 |
+
</p>
|
165 |
+
)
|
166 |
+
})
|
167 |
+
FormMessage.displayName = "FormMessage"
|
168 |
+
|
169 |
+
export {
|
170 |
+
useFormField,
|
171 |
+
Form,
|
172 |
+
FormItem,
|
173 |
+
FormLabel,
|
174 |
+
FormControl,
|
175 |
+
FormDescription,
|
176 |
+
FormMessage,
|
177 |
+
FormField,
|
178 |
+
}
|
client/src/components/ui/hover-card.tsx
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const HoverCard = HoverCardPrimitive.Root
|
9 |
+
|
10 |
+
const HoverCardTrigger = HoverCardPrimitive.Trigger
|
11 |
+
|
12 |
+
const HoverCardContent = React.forwardRef<
|
13 |
+
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
14 |
+
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
15 |
+
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
16 |
+
<HoverCardPrimitive.Content
|
17 |
+
ref={ref}
|
18 |
+
align={align}
|
19 |
+
sideOffset={sideOffset}
|
20 |
+
className={cn(
|
21 |
+
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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 origin-[--radix-hover-card-content-transform-origin]",
|
22 |
+
className
|
23 |
+
)}
|
24 |
+
{...props}
|
25 |
+
/>
|
26 |
+
))
|
27 |
+
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
|
28 |
+
|
29 |
+
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
client/src/components/ui/input-otp.tsx
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { OTPInput, OTPInputContext } from "input-otp"
|
3 |
+
import { Dot } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const InputOTP = React.forwardRef<
|
8 |
+
React.ElementRef<typeof OTPInput>,
|
9 |
+
React.ComponentPropsWithoutRef<typeof OTPInput>
|
10 |
+
>(({ className, containerClassName, ...props }, ref) => (
|
11 |
+
<OTPInput
|
12 |
+
ref={ref}
|
13 |
+
containerClassName={cn(
|
14 |
+
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
15 |
+
containerClassName
|
16 |
+
)}
|
17 |
+
className={cn("disabled:cursor-not-allowed", className)}
|
18 |
+
{...props}
|
19 |
+
/>
|
20 |
+
))
|
21 |
+
InputOTP.displayName = "InputOTP"
|
22 |
+
|
23 |
+
const InputOTPGroup = React.forwardRef<
|
24 |
+
React.ElementRef<"div">,
|
25 |
+
React.ComponentPropsWithoutRef<"div">
|
26 |
+
>(({ className, ...props }, ref) => (
|
27 |
+
<div ref={ref} className={cn("flex items-center", className)} {...props} />
|
28 |
+
))
|
29 |
+
InputOTPGroup.displayName = "InputOTPGroup"
|
30 |
+
|
31 |
+
const InputOTPSlot = React.forwardRef<
|
32 |
+
React.ElementRef<"div">,
|
33 |
+
React.ComponentPropsWithoutRef<"div"> & { index: number }
|
34 |
+
>(({ index, className, ...props }, ref) => {
|
35 |
+
const inputOTPContext = React.useContext(OTPInputContext)
|
36 |
+
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
|
37 |
+
|
38 |
+
return (
|
39 |
+
<div
|
40 |
+
ref={ref}
|
41 |
+
className={cn(
|
42 |
+
"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
|
43 |
+
isActive && "z-10 ring-2 ring-ring ring-offset-background",
|
44 |
+
className
|
45 |
+
)}
|
46 |
+
{...props}
|
47 |
+
>
|
48 |
+
{char}
|
49 |
+
{hasFakeCaret && (
|
50 |
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
51 |
+
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
|
52 |
+
</div>
|
53 |
+
)}
|
54 |
+
</div>
|
55 |
+
)
|
56 |
+
})
|
57 |
+
InputOTPSlot.displayName = "InputOTPSlot"
|
58 |
+
|
59 |
+
const InputOTPSeparator = React.forwardRef<
|
60 |
+
React.ElementRef<"div">,
|
61 |
+
React.ComponentPropsWithoutRef<"div">
|
62 |
+
>(({ ...props }, ref) => (
|
63 |
+
<div ref={ref} role="separator" {...props}>
|
64 |
+
<Dot />
|
65 |
+
</div>
|
66 |
+
))
|
67 |
+
InputOTPSeparator.displayName = "InputOTPSeparator"
|
68 |
+
|
69 |
+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
client/src/components/ui/input.tsx
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
|
3 |
+
import { cn } from "@/lib/utils"
|
4 |
+
|
5 |
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
6 |
+
({ className, type, ...props }, ref) => {
|
7 |
+
return (
|
8 |
+
<input
|
9 |
+
type={type}
|
10 |
+
className={cn(
|
11 |
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
12 |
+
className
|
13 |
+
)}
|
14 |
+
ref={ref}
|
15 |
+
{...props}
|
16 |
+
/>
|
17 |
+
)
|
18 |
+
}
|
19 |
+
)
|
20 |
+
Input.displayName = "Input"
|
21 |
+
|
22 |
+
export { Input }
|
client/src/components/ui/label.tsx
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const labelVariants = cva(
|
8 |
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
9 |
+
)
|
10 |
+
|
11 |
+
const Label = React.forwardRef<
|
12 |
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
13 |
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
14 |
+
VariantProps<typeof labelVariants>
|
15 |
+
>(({ className, ...props }, ref) => (
|
16 |
+
<LabelPrimitive.Root
|
17 |
+
ref={ref}
|
18 |
+
className={cn(labelVariants(), className)}
|
19 |
+
{...props}
|
20 |
+
/>
|
21 |
+
))
|
22 |
+
Label.displayName = LabelPrimitive.Root.displayName
|
23 |
+
|
24 |
+
export { Label }
|
client/src/components/ui/menubar.tsx
ADDED
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
5 |
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
|
9 |
+
function MenubarMenu({
|
10 |
+
...props
|
11 |
+
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
|
12 |
+
return <MenubarPrimitive.Menu {...props} />
|
13 |
+
}
|
14 |
+
|
15 |
+
function MenubarGroup({
|
16 |
+
...props
|
17 |
+
}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
|
18 |
+
return <MenubarPrimitive.Group {...props} />
|
19 |
+
}
|
20 |
+
|
21 |
+
function MenubarPortal({
|
22 |
+
...props
|
23 |
+
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
|
24 |
+
return <MenubarPrimitive.Portal {...props} />
|
25 |
+
}
|
26 |
+
|
27 |
+
function MenubarRadioGroup({
|
28 |
+
...props
|
29 |
+
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
|
30 |
+
return <MenubarPrimitive.RadioGroup {...props} />
|
31 |
+
}
|
32 |
+
|
33 |
+
function MenubarSub({
|
34 |
+
...props
|
35 |
+
}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
|
36 |
+
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
|
37 |
+
}
|
38 |
+
|
39 |
+
const Menubar = React.forwardRef<
|
40 |
+
React.ElementRef<typeof MenubarPrimitive.Root>,
|
41 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
|
42 |
+
>(({ className, ...props }, ref) => (
|
43 |
+
<MenubarPrimitive.Root
|
44 |
+
ref={ref}
|
45 |
+
className={cn(
|
46 |
+
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
|
47 |
+
className
|
48 |
+
)}
|
49 |
+
{...props}
|
50 |
+
/>
|
51 |
+
))
|
52 |
+
Menubar.displayName = MenubarPrimitive.Root.displayName
|
53 |
+
|
54 |
+
const MenubarTrigger = React.forwardRef<
|
55 |
+
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
56 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
|
57 |
+
>(({ className, ...props }, ref) => (
|
58 |
+
<MenubarPrimitive.Trigger
|
59 |
+
ref={ref}
|
60 |
+
className={cn(
|
61 |
+
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
62 |
+
className
|
63 |
+
)}
|
64 |
+
{...props}
|
65 |
+
/>
|
66 |
+
))
|
67 |
+
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
|
68 |
+
|
69 |
+
const MenubarSubTrigger = React.forwardRef<
|
70 |
+
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
|
71 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
|
72 |
+
inset?: boolean
|
73 |
+
}
|
74 |
+
>(({ className, inset, children, ...props }, ref) => (
|
75 |
+
<MenubarPrimitive.SubTrigger
|
76 |
+
ref={ref}
|
77 |
+
className={cn(
|
78 |
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
79 |
+
inset && "pl-8",
|
80 |
+
className
|
81 |
+
)}
|
82 |
+
{...props}
|
83 |
+
>
|
84 |
+
{children}
|
85 |
+
<ChevronRight className="ml-auto h-4 w-4" />
|
86 |
+
</MenubarPrimitive.SubTrigger>
|
87 |
+
))
|
88 |
+
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
|
89 |
+
|
90 |
+
const MenubarSubContent = React.forwardRef<
|
91 |
+
React.ElementRef<typeof MenubarPrimitive.SubContent>,
|
92 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
|
93 |
+
>(({ className, ...props }, ref) => (
|
94 |
+
<MenubarPrimitive.SubContent
|
95 |
+
ref={ref}
|
96 |
+
className={cn(
|
97 |
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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 origin-[--radix-menubar-content-transform-origin]",
|
98 |
+
className
|
99 |
+
)}
|
100 |
+
{...props}
|
101 |
+
/>
|
102 |
+
))
|
103 |
+
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
|
104 |
+
|
105 |
+
const MenubarContent = React.forwardRef<
|
106 |
+
React.ElementRef<typeof MenubarPrimitive.Content>,
|
107 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
|
108 |
+
>(
|
109 |
+
(
|
110 |
+
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
|
111 |
+
ref
|
112 |
+
) => (
|
113 |
+
<MenubarPrimitive.Portal>
|
114 |
+
<MenubarPrimitive.Content
|
115 |
+
ref={ref}
|
116 |
+
align={align}
|
117 |
+
alignOffset={alignOffset}
|
118 |
+
sideOffset={sideOffset}
|
119 |
+
className={cn(
|
120 |
+
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in 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 origin-[--radix-menubar-content-transform-origin]",
|
121 |
+
className
|
122 |
+
)}
|
123 |
+
{...props}
|
124 |
+
/>
|
125 |
+
</MenubarPrimitive.Portal>
|
126 |
+
)
|
127 |
+
)
|
128 |
+
MenubarContent.displayName = MenubarPrimitive.Content.displayName
|
129 |
+
|
130 |
+
const MenubarItem = React.forwardRef<
|
131 |
+
React.ElementRef<typeof MenubarPrimitive.Item>,
|
132 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
|
133 |
+
inset?: boolean
|
134 |
+
}
|
135 |
+
>(({ className, inset, ...props }, ref) => (
|
136 |
+
<MenubarPrimitive.Item
|
137 |
+
ref={ref}
|
138 |
+
className={cn(
|
139 |
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
140 |
+
inset && "pl-8",
|
141 |
+
className
|
142 |
+
)}
|
143 |
+
{...props}
|
144 |
+
/>
|
145 |
+
))
|
146 |
+
MenubarItem.displayName = MenubarPrimitive.Item.displayName
|
147 |
+
|
148 |
+
const MenubarCheckboxItem = React.forwardRef<
|
149 |
+
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
|
150 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
|
151 |
+
>(({ className, children, checked, ...props }, ref) => (
|
152 |
+
<MenubarPrimitive.CheckboxItem
|
153 |
+
ref={ref}
|
154 |
+
className={cn(
|
155 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
156 |
+
className
|
157 |
+
)}
|
158 |
+
checked={checked}
|
159 |
+
{...props}
|
160 |
+
>
|
161 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
162 |
+
<MenubarPrimitive.ItemIndicator>
|
163 |
+
<Check className="h-4 w-4" />
|
164 |
+
</MenubarPrimitive.ItemIndicator>
|
165 |
+
</span>
|
166 |
+
{children}
|
167 |
+
</MenubarPrimitive.CheckboxItem>
|
168 |
+
))
|
169 |
+
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
|
170 |
+
|
171 |
+
const MenubarRadioItem = React.forwardRef<
|
172 |
+
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
|
173 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
|
174 |
+
>(({ className, children, ...props }, ref) => (
|
175 |
+
<MenubarPrimitive.RadioItem
|
176 |
+
ref={ref}
|
177 |
+
className={cn(
|
178 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
179 |
+
className
|
180 |
+
)}
|
181 |
+
{...props}
|
182 |
+
>
|
183 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
184 |
+
<MenubarPrimitive.ItemIndicator>
|
185 |
+
<Circle className="h-2 w-2 fill-current" />
|
186 |
+
</MenubarPrimitive.ItemIndicator>
|
187 |
+
</span>
|
188 |
+
{children}
|
189 |
+
</MenubarPrimitive.RadioItem>
|
190 |
+
))
|
191 |
+
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
|
192 |
+
|
193 |
+
const MenubarLabel = React.forwardRef<
|
194 |
+
React.ElementRef<typeof MenubarPrimitive.Label>,
|
195 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
|
196 |
+
inset?: boolean
|
197 |
+
}
|
198 |
+
>(({ className, inset, ...props }, ref) => (
|
199 |
+
<MenubarPrimitive.Label
|
200 |
+
ref={ref}
|
201 |
+
className={cn(
|
202 |
+
"px-2 py-1.5 text-sm font-semibold",
|
203 |
+
inset && "pl-8",
|
204 |
+
className
|
205 |
+
)}
|
206 |
+
{...props}
|
207 |
+
/>
|
208 |
+
))
|
209 |
+
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
|
210 |
+
|
211 |
+
const MenubarSeparator = React.forwardRef<
|
212 |
+
React.ElementRef<typeof MenubarPrimitive.Separator>,
|
213 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
|
214 |
+
>(({ className, ...props }, ref) => (
|
215 |
+
<MenubarPrimitive.Separator
|
216 |
+
ref={ref}
|
217 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
218 |
+
{...props}
|
219 |
+
/>
|
220 |
+
))
|
221 |
+
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
|
222 |
+
|
223 |
+
const MenubarShortcut = ({
|
224 |
+
className,
|
225 |
+
...props
|
226 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
227 |
+
return (
|
228 |
+
<span
|
229 |
+
className={cn(
|
230 |
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
231 |
+
className
|
232 |
+
)}
|
233 |
+
{...props}
|
234 |
+
/>
|
235 |
+
)
|
236 |
+
}
|
237 |
+
MenubarShortcut.displayname = "MenubarShortcut"
|
238 |
+
|
239 |
+
export {
|
240 |
+
Menubar,
|
241 |
+
MenubarMenu,
|
242 |
+
MenubarTrigger,
|
243 |
+
MenubarContent,
|
244 |
+
MenubarItem,
|
245 |
+
MenubarSeparator,
|
246 |
+
MenubarLabel,
|
247 |
+
MenubarCheckboxItem,
|
248 |
+
MenubarRadioGroup,
|
249 |
+
MenubarRadioItem,
|
250 |
+
MenubarPortal,
|
251 |
+
MenubarSubContent,
|
252 |
+
MenubarSubTrigger,
|
253 |
+
MenubarGroup,
|
254 |
+
MenubarSub,
|
255 |
+
MenubarShortcut,
|
256 |
+
}
|
client/src/components/ui/navigation-menu.tsx
ADDED
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
3 |
+
import { cva } from "class-variance-authority"
|
4 |
+
import { ChevronDown } from "lucide-react"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const NavigationMenu = React.forwardRef<
|
9 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
10 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
11 |
+
>(({ className, children, ...props }, ref) => (
|
12 |
+
<NavigationMenuPrimitive.Root
|
13 |
+
ref={ref}
|
14 |
+
className={cn(
|
15 |
+
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
16 |
+
className
|
17 |
+
)}
|
18 |
+
{...props}
|
19 |
+
>
|
20 |
+
{children}
|
21 |
+
<NavigationMenuViewport />
|
22 |
+
</NavigationMenuPrimitive.Root>
|
23 |
+
))
|
24 |
+
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
25 |
+
|
26 |
+
const NavigationMenuList = React.forwardRef<
|
27 |
+
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
28 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
29 |
+
>(({ className, ...props }, ref) => (
|
30 |
+
<NavigationMenuPrimitive.List
|
31 |
+
ref={ref}
|
32 |
+
className={cn(
|
33 |
+
"group flex flex-1 list-none items-center justify-center space-x-1",
|
34 |
+
className
|
35 |
+
)}
|
36 |
+
{...props}
|
37 |
+
/>
|
38 |
+
))
|
39 |
+
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
40 |
+
|
41 |
+
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
42 |
+
|
43 |
+
const navigationMenuTriggerStyle = cva(
|
44 |
+
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent"
|
45 |
+
)
|
46 |
+
|
47 |
+
const NavigationMenuTrigger = React.forwardRef<
|
48 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
49 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
50 |
+
>(({ className, children, ...props }, ref) => (
|
51 |
+
<NavigationMenuPrimitive.Trigger
|
52 |
+
ref={ref}
|
53 |
+
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
54 |
+
{...props}
|
55 |
+
>
|
56 |
+
{children}{" "}
|
57 |
+
<ChevronDown
|
58 |
+
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
59 |
+
aria-hidden="true"
|
60 |
+
/>
|
61 |
+
</NavigationMenuPrimitive.Trigger>
|
62 |
+
))
|
63 |
+
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
64 |
+
|
65 |
+
const NavigationMenuContent = React.forwardRef<
|
66 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
67 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
68 |
+
>(({ className, ...props }, ref) => (
|
69 |
+
<NavigationMenuPrimitive.Content
|
70 |
+
ref={ref}
|
71 |
+
className={cn(
|
72 |
+
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
73 |
+
className
|
74 |
+
)}
|
75 |
+
{...props}
|
76 |
+
/>
|
77 |
+
))
|
78 |
+
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
79 |
+
|
80 |
+
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
81 |
+
|
82 |
+
const NavigationMenuViewport = React.forwardRef<
|
83 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
84 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
85 |
+
>(({ className, ...props }, ref) => (
|
86 |
+
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
87 |
+
<NavigationMenuPrimitive.Viewport
|
88 |
+
className={cn(
|
89 |
+
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
90 |
+
className
|
91 |
+
)}
|
92 |
+
ref={ref}
|
93 |
+
{...props}
|
94 |
+
/>
|
95 |
+
</div>
|
96 |
+
))
|
97 |
+
NavigationMenuViewport.displayName =
|
98 |
+
NavigationMenuPrimitive.Viewport.displayName
|
99 |
+
|
100 |
+
const NavigationMenuIndicator = React.forwardRef<
|
101 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
102 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
103 |
+
>(({ className, ...props }, ref) => (
|
104 |
+
<NavigationMenuPrimitive.Indicator
|
105 |
+
ref={ref}
|
106 |
+
className={cn(
|
107 |
+
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
108 |
+
className
|
109 |
+
)}
|
110 |
+
{...props}
|
111 |
+
>
|
112 |
+
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
113 |
+
</NavigationMenuPrimitive.Indicator>
|
114 |
+
))
|
115 |
+
NavigationMenuIndicator.displayName =
|
116 |
+
NavigationMenuPrimitive.Indicator.displayName
|
117 |
+
|
118 |
+
export {
|
119 |
+
navigationMenuTriggerStyle,
|
120 |
+
NavigationMenu,
|
121 |
+
NavigationMenuList,
|
122 |
+
NavigationMenuItem,
|
123 |
+
NavigationMenuContent,
|
124 |
+
NavigationMenuTrigger,
|
125 |
+
NavigationMenuLink,
|
126 |
+
NavigationMenuIndicator,
|
127 |
+
NavigationMenuViewport,
|
128 |
+
}
|
client/src/components/ui/navigation.tsx
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Card } from "@/components/ui/card";
|
2 |
+
import { Button } from "@/components/ui/button";
|
3 |
+
import { Bot, Circle, Download } from "lucide-react";
|
4 |
+
import { useState } from "react";
|
5 |
+
|
6 |
+
const navigationItems = [
|
7 |
+
{ id: "overview", label: "Overview", icon: "fas fa-home" },
|
8 |
+
{ id: "modules", label: "Modules", icon: "fas fa-cube" },
|
9 |
+
{ id: "configuration", label: "Configuration", icon: "fas fa-cog" },
|
10 |
+
{ id: "commands", label: "CLI Commands", icon: "fas fa-terminal" },
|
11 |
+
{ id: "deployment", label: "Deployment", icon: "fas fa-rocket" },
|
12 |
+
];
|
13 |
+
|
14 |
+
const quickActions = [
|
15 |
+
{ label: "Initialize Project", icon: "fas fa-play" },
|
16 |
+
{ label: "Download Models", icon: "fas fa-download" },
|
17 |
+
{ label: "Run Tests", icon: "fas fa-test-tube" },
|
18 |
+
];
|
19 |
+
|
20 |
+
interface NavigationProps {
|
21 |
+
activeSection: string;
|
22 |
+
onSectionChange: (section: string) => void;
|
23 |
+
}
|
24 |
+
|
25 |
+
export function Navigation({ activeSection, onSectionChange }: NavigationProps) {
|
26 |
+
return (
|
27 |
+
<aside className="space-y-4 sticky top-24">
|
28 |
+
<Card className="p-4 border-border bg-card">
|
29 |
+
<h3 className="font-semibold mb-4 text-sm uppercase tracking-wide text-muted-foreground">
|
30 |
+
Project Setup
|
31 |
+
</h3>
|
32 |
+
<ul className="space-y-2">
|
33 |
+
{navigationItems.map((item) => (
|
34 |
+
<li key={item.id}>
|
35 |
+
<button
|
36 |
+
onClick={() => onSectionChange(item.id)}
|
37 |
+
className={`flex items-center space-x-3 w-full px-3 py-2 rounded-md text-sm font-medium transition-colors ${
|
38 |
+
activeSection === item.id
|
39 |
+
? "text-primary bg-primary/10"
|
40 |
+
: "text-foreground hover:text-primary"
|
41 |
+
}`}
|
42 |
+
data-testid={`nav-${item.id}`}
|
43 |
+
>
|
44 |
+
<i className={`${item.icon} w-4`}></i>
|
45 |
+
<span>{item.label}</span>
|
46 |
+
</button>
|
47 |
+
</li>
|
48 |
+
))}
|
49 |
+
</ul>
|
50 |
+
</Card>
|
51 |
+
|
52 |
+
<Card className="p-4 border-border bg-card">
|
53 |
+
<h3 className="font-semibold mb-4 text-sm uppercase tracking-wide text-muted-foreground">
|
54 |
+
Quick Actions
|
55 |
+
</h3>
|
56 |
+
<div className="space-y-3">
|
57 |
+
{quickActions.map((action, index) => (
|
58 |
+
<Button
|
59 |
+
key={index}
|
60 |
+
variant="secondary"
|
61 |
+
className="w-full justify-start text-left"
|
62 |
+
data-testid={`action-${action.label.toLowerCase().replace(/\s+/g, '-')}`}
|
63 |
+
>
|
64 |
+
<i className={`${action.icon} mr-2`}></i>
|
65 |
+
{action.label}
|
66 |
+
</Button>
|
67 |
+
))}
|
68 |
+
</div>
|
69 |
+
</Card>
|
70 |
+
</aside>
|
71 |
+
);
|
72 |
+
}
|
client/src/components/ui/pagination.tsx
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
6 |
+
|
7 |
+
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
8 |
+
<nav
|
9 |
+
role="navigation"
|
10 |
+
aria-label="pagination"
|
11 |
+
className={cn("mx-auto flex w-full justify-center", className)}
|
12 |
+
{...props}
|
13 |
+
/>
|
14 |
+
)
|
15 |
+
Pagination.displayName = "Pagination"
|
16 |
+
|
17 |
+
const PaginationContent = React.forwardRef<
|
18 |
+
HTMLUListElement,
|
19 |
+
React.ComponentProps<"ul">
|
20 |
+
>(({ className, ...props }, ref) => (
|
21 |
+
<ul
|
22 |
+
ref={ref}
|
23 |
+
className={cn("flex flex-row items-center gap-1", className)}
|
24 |
+
{...props}
|
25 |
+
/>
|
26 |
+
))
|
27 |
+
PaginationContent.displayName = "PaginationContent"
|
28 |
+
|
29 |
+
const PaginationItem = React.forwardRef<
|
30 |
+
HTMLLIElement,
|
31 |
+
React.ComponentProps<"li">
|
32 |
+
>(({ className, ...props }, ref) => (
|
33 |
+
<li ref={ref} className={cn("", className)} {...props} />
|
34 |
+
))
|
35 |
+
PaginationItem.displayName = "PaginationItem"
|
36 |
+
|
37 |
+
type PaginationLinkProps = {
|
38 |
+
isActive?: boolean
|
39 |
+
} & Pick<ButtonProps, "size"> &
|
40 |
+
React.ComponentProps<"a">
|
41 |
+
|
42 |
+
const PaginationLink = ({
|
43 |
+
className,
|
44 |
+
isActive,
|
45 |
+
size = "icon",
|
46 |
+
...props
|
47 |
+
}: PaginationLinkProps) => (
|
48 |
+
<a
|
49 |
+
aria-current={isActive ? "page" : undefined}
|
50 |
+
className={cn(
|
51 |
+
buttonVariants({
|
52 |
+
variant: isActive ? "outline" : "ghost",
|
53 |
+
size,
|
54 |
+
}),
|
55 |
+
className
|
56 |
+
)}
|
57 |
+
{...props}
|
58 |
+
/>
|
59 |
+
)
|
60 |
+
PaginationLink.displayName = "PaginationLink"
|
61 |
+
|
62 |
+
const PaginationPrevious = ({
|
63 |
+
className,
|
64 |
+
...props
|
65 |
+
}: React.ComponentProps<typeof PaginationLink>) => (
|
66 |
+
<PaginationLink
|
67 |
+
aria-label="Go to previous page"
|
68 |
+
size="default"
|
69 |
+
className={cn("gap-1 pl-2.5", className)}
|
70 |
+
{...props}
|
71 |
+
>
|
72 |
+
<ChevronLeft className="h-4 w-4" />
|
73 |
+
<span>Previous</span>
|
74 |
+
</PaginationLink>
|
75 |
+
)
|
76 |
+
PaginationPrevious.displayName = "PaginationPrevious"
|
77 |
+
|
78 |
+
const PaginationNext = ({
|
79 |
+
className,
|
80 |
+
...props
|
81 |
+
}: React.ComponentProps<typeof PaginationLink>) => (
|
82 |
+
<PaginationLink
|
83 |
+
aria-label="Go to next page"
|
84 |
+
size="default"
|
85 |
+
className={cn("gap-1 pr-2.5", className)}
|
86 |
+
{...props}
|
87 |
+
>
|
88 |
+
<span>Next</span>
|
89 |
+
<ChevronRight className="h-4 w-4" />
|
90 |
+
</PaginationLink>
|
91 |
+
)
|
92 |
+
PaginationNext.displayName = "PaginationNext"
|
93 |
+
|
94 |
+
const PaginationEllipsis = ({
|
95 |
+
className,
|
96 |
+
...props
|
97 |
+
}: React.ComponentProps<"span">) => (
|
98 |
+
<span
|
99 |
+
aria-hidden
|
100 |
+
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
101 |
+
{...props}
|
102 |
+
>
|
103 |
+
<MoreHorizontal className="h-4 w-4" />
|
104 |
+
<span className="sr-only">More pages</span>
|
105 |
+
</span>
|
106 |
+
)
|
107 |
+
PaginationEllipsis.displayName = "PaginationEllipsis"
|
108 |
+
|
109 |
+
export {
|
110 |
+
Pagination,
|
111 |
+
PaginationContent,
|
112 |
+
PaginationEllipsis,
|
113 |
+
PaginationItem,
|
114 |
+
PaginationLink,
|
115 |
+
PaginationNext,
|
116 |
+
PaginationPrevious,
|
117 |
+
}
|
client/src/components/ui/popover.tsx
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const Popover = PopoverPrimitive.Root
|
7 |
+
|
8 |
+
const PopoverTrigger = PopoverPrimitive.Trigger
|
9 |
+
|
10 |
+
const PopoverContent = React.forwardRef<
|
11 |
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
12 |
+
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
13 |
+
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
14 |
+
<PopoverPrimitive.Portal>
|
15 |
+
<PopoverPrimitive.Content
|
16 |
+
ref={ref}
|
17 |
+
align={align}
|
18 |
+
sideOffset={sideOffset}
|
19 |
+
className={cn(
|
20 |
+
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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 origin-[--radix-popover-content-transform-origin]",
|
21 |
+
className
|
22 |
+
)}
|
23 |
+
{...props}
|
24 |
+
/>
|
25 |
+
</PopoverPrimitive.Portal>
|
26 |
+
))
|
27 |
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
28 |
+
|
29 |
+
export { Popover, PopoverTrigger, PopoverContent }
|
client/src/components/ui/progress.tsx
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const Progress = React.forwardRef<
|
9 |
+
React.ElementRef<typeof ProgressPrimitive.Root>,
|
10 |
+
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
11 |
+
>(({ className, value, ...props }, ref) => (
|
12 |
+
<ProgressPrimitive.Root
|
13 |
+
ref={ref}
|
14 |
+
className={cn(
|
15 |
+
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
16 |
+
className
|
17 |
+
)}
|
18 |
+
{...props}
|
19 |
+
>
|
20 |
+
<ProgressPrimitive.Indicator
|
21 |
+
className="h-full w-full flex-1 bg-primary transition-all"
|
22 |
+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
23 |
+
/>
|
24 |
+
</ProgressPrimitive.Root>
|
25 |
+
))
|
26 |
+
Progress.displayName = ProgressPrimitive.Root.displayName
|
27 |
+
|
28 |
+
export { Progress }
|
client/src/components/ui/radio-group.tsx
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
3 |
+
import { Circle } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const RadioGroup = React.forwardRef<
|
8 |
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
9 |
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
10 |
+
>(({ className, ...props }, ref) => {
|
11 |
+
return (
|
12 |
+
<RadioGroupPrimitive.Root
|
13 |
+
className={cn("grid gap-2", className)}
|
14 |
+
{...props}
|
15 |
+
ref={ref}
|
16 |
+
/>
|
17 |
+
)
|
18 |
+
})
|
19 |
+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
20 |
+
|
21 |
+
const RadioGroupItem = React.forwardRef<
|
22 |
+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
23 |
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
24 |
+
>(({ className, ...props }, ref) => {
|
25 |
+
return (
|
26 |
+
<RadioGroupPrimitive.Item
|
27 |
+
ref={ref}
|
28 |
+
className={cn(
|
29 |
+
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
30 |
+
className
|
31 |
+
)}
|
32 |
+
{...props}
|
33 |
+
>
|
34 |
+
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
35 |
+
<Circle className="h-2.5 w-2.5 fill-current text-current" />
|
36 |
+
</RadioGroupPrimitive.Indicator>
|
37 |
+
</RadioGroupPrimitive.Item>
|
38 |
+
)
|
39 |
+
})
|
40 |
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
41 |
+
|
42 |
+
export { RadioGroup, RadioGroupItem }
|
client/src/components/ui/resizable.tsx
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { GripVertical } from "lucide-react"
|
4 |
+
import * as ResizablePrimitive from "react-resizable-panels"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const ResizablePanelGroup = ({
|
9 |
+
className,
|
10 |
+
...props
|
11 |
+
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
|
12 |
+
<ResizablePrimitive.PanelGroup
|
13 |
+
className={cn(
|
14 |
+
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
15 |
+
className
|
16 |
+
)}
|
17 |
+
{...props}
|
18 |
+
/>
|
19 |
+
)
|
20 |
+
|
21 |
+
const ResizablePanel = ResizablePrimitive.Panel
|
22 |
+
|
23 |
+
const ResizableHandle = ({
|
24 |
+
withHandle,
|
25 |
+
className,
|
26 |
+
...props
|
27 |
+
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
28 |
+
withHandle?: boolean
|
29 |
+
}) => (
|
30 |
+
<ResizablePrimitive.PanelResizeHandle
|
31 |
+
className={cn(
|
32 |
+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
33 |
+
className
|
34 |
+
)}
|
35 |
+
{...props}
|
36 |
+
>
|
37 |
+
{withHandle && (
|
38 |
+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
39 |
+
<GripVertical className="h-2.5 w-2.5" />
|
40 |
+
</div>
|
41 |
+
)}
|
42 |
+
</ResizablePrimitive.PanelResizeHandle>
|
43 |
+
)
|
44 |
+
|
45 |
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
client/src/components/ui/scroll-area.tsx
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const ScrollArea = React.forwardRef<
|
7 |
+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
8 |
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
9 |
+
>(({ className, children, ...props }, ref) => (
|
10 |
+
<ScrollAreaPrimitive.Root
|
11 |
+
ref={ref}
|
12 |
+
className={cn("relative overflow-hidden", className)}
|
13 |
+
{...props}
|
14 |
+
>
|
15 |
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
16 |
+
{children}
|
17 |
+
</ScrollAreaPrimitive.Viewport>
|
18 |
+
<ScrollBar />
|
19 |
+
<ScrollAreaPrimitive.Corner />
|
20 |
+
</ScrollAreaPrimitive.Root>
|
21 |
+
))
|
22 |
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
23 |
+
|
24 |
+
const ScrollBar = React.forwardRef<
|
25 |
+
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
26 |
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
27 |
+
>(({ className, orientation = "vertical", ...props }, ref) => (
|
28 |
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
29 |
+
ref={ref}
|
30 |
+
orientation={orientation}
|
31 |
+
className={cn(
|
32 |
+
"flex touch-none select-none transition-colors",
|
33 |
+
orientation === "vertical" &&
|
34 |
+
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
35 |
+
orientation === "horizontal" &&
|
36 |
+
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
37 |
+
className
|
38 |
+
)}
|
39 |
+
{...props}
|
40 |
+
>
|
41 |
+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
42 |
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
43 |
+
))
|
44 |
+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
45 |
+
|
46 |
+
export { ScrollArea, ScrollBar }
|
client/src/components/ui/select.tsx
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
5 |
+
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
|
9 |
+
const Select = SelectPrimitive.Root
|
10 |
+
|
11 |
+
const SelectGroup = SelectPrimitive.Group
|
12 |
+
|
13 |
+
const SelectValue = SelectPrimitive.Value
|
14 |
+
|
15 |
+
const SelectTrigger = React.forwardRef<
|
16 |
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
17 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
18 |
+
>(({ className, children, ...props }, ref) => (
|
19 |
+
<SelectPrimitive.Trigger
|
20 |
+
ref={ref}
|
21 |
+
className={cn(
|
22 |
+
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
23 |
+
className
|
24 |
+
)}
|
25 |
+
{...props}
|
26 |
+
>
|
27 |
+
{children}
|
28 |
+
<SelectPrimitive.Icon asChild>
|
29 |
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
30 |
+
</SelectPrimitive.Icon>
|
31 |
+
</SelectPrimitive.Trigger>
|
32 |
+
))
|
33 |
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
34 |
+
|
35 |
+
const SelectScrollUpButton = React.forwardRef<
|
36 |
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
37 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
38 |
+
>(({ className, ...props }, ref) => (
|
39 |
+
<SelectPrimitive.ScrollUpButton
|
40 |
+
ref={ref}
|
41 |
+
className={cn(
|
42 |
+
"flex cursor-default items-center justify-center py-1",
|
43 |
+
className
|
44 |
+
)}
|
45 |
+
{...props}
|
46 |
+
>
|
47 |
+
<ChevronUp className="h-4 w-4" />
|
48 |
+
</SelectPrimitive.ScrollUpButton>
|
49 |
+
))
|
50 |
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
51 |
+
|
52 |
+
const SelectScrollDownButton = React.forwardRef<
|
53 |
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
54 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
55 |
+
>(({ className, ...props }, ref) => (
|
56 |
+
<SelectPrimitive.ScrollDownButton
|
57 |
+
ref={ref}
|
58 |
+
className={cn(
|
59 |
+
"flex cursor-default items-center justify-center py-1",
|
60 |
+
className
|
61 |
+
)}
|
62 |
+
{...props}
|
63 |
+
>
|
64 |
+
<ChevronDown className="h-4 w-4" />
|
65 |
+
</SelectPrimitive.ScrollDownButton>
|
66 |
+
))
|
67 |
+
SelectScrollDownButton.displayName =
|
68 |
+
SelectPrimitive.ScrollDownButton.displayName
|
69 |
+
|
70 |
+
const SelectContent = React.forwardRef<
|
71 |
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
72 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
73 |
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
74 |
+
<SelectPrimitive.Portal>
|
75 |
+
<SelectPrimitive.Content
|
76 |
+
ref={ref}
|
77 |
+
className={cn(
|
78 |
+
"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md 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 origin-[--radix-select-content-transform-origin]",
|
79 |
+
position === "popper" &&
|
80 |
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
81 |
+
className
|
82 |
+
)}
|
83 |
+
position={position}
|
84 |
+
{...props}
|
85 |
+
>
|
86 |
+
<SelectScrollUpButton />
|
87 |
+
<SelectPrimitive.Viewport
|
88 |
+
className={cn(
|
89 |
+
"p-1",
|
90 |
+
position === "popper" &&
|
91 |
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
92 |
+
)}
|
93 |
+
>
|
94 |
+
{children}
|
95 |
+
</SelectPrimitive.Viewport>
|
96 |
+
<SelectScrollDownButton />
|
97 |
+
</SelectPrimitive.Content>
|
98 |
+
</SelectPrimitive.Portal>
|
99 |
+
))
|
100 |
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
101 |
+
|
102 |
+
const SelectLabel = React.forwardRef<
|
103 |
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
104 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
105 |
+
>(({ className, ...props }, ref) => (
|
106 |
+
<SelectPrimitive.Label
|
107 |
+
ref={ref}
|
108 |
+
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
109 |
+
{...props}
|
110 |
+
/>
|
111 |
+
))
|
112 |
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
113 |
+
|
114 |
+
const SelectItem = React.forwardRef<
|
115 |
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
116 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
117 |
+
>(({ className, children, ...props }, ref) => (
|
118 |
+
<SelectPrimitive.Item
|
119 |
+
ref={ref}
|
120 |
+
className={cn(
|
121 |
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
122 |
+
className
|
123 |
+
)}
|
124 |
+
{...props}
|
125 |
+
>
|
126 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
127 |
+
<SelectPrimitive.ItemIndicator>
|
128 |
+
<Check className="h-4 w-4" />
|
129 |
+
</SelectPrimitive.ItemIndicator>
|
130 |
+
</span>
|
131 |
+
|
132 |
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
133 |
+
</SelectPrimitive.Item>
|
134 |
+
))
|
135 |
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
136 |
+
|
137 |
+
const SelectSeparator = React.forwardRef<
|
138 |
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
139 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
140 |
+
>(({ className, ...props }, ref) => (
|
141 |
+
<SelectPrimitive.Separator
|
142 |
+
ref={ref}
|
143 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
144 |
+
{...props}
|
145 |
+
/>
|
146 |
+
))
|
147 |
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
148 |
+
|
149 |
+
export {
|
150 |
+
Select,
|
151 |
+
SelectGroup,
|
152 |
+
SelectValue,
|
153 |
+
SelectTrigger,
|
154 |
+
SelectContent,
|
155 |
+
SelectLabel,
|
156 |
+
SelectItem,
|
157 |
+
SelectSeparator,
|
158 |
+
SelectScrollUpButton,
|
159 |
+
SelectScrollDownButton,
|
160 |
+
}
|
client/src/components/ui/separator.tsx
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const Separator = React.forwardRef<
|
7 |
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
8 |
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
9 |
+
>(
|
10 |
+
(
|
11 |
+
{ className, orientation = "horizontal", decorative = true, ...props },
|
12 |
+
ref
|
13 |
+
) => (
|
14 |
+
<SeparatorPrimitive.Root
|
15 |
+
ref={ref}
|
16 |
+
decorative={decorative}
|
17 |
+
orientation={orientation}
|
18 |
+
className={cn(
|
19 |
+
"shrink-0 bg-border",
|
20 |
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
21 |
+
className
|
22 |
+
)}
|
23 |
+
{...props}
|
24 |
+
/>
|
25 |
+
)
|
26 |
+
)
|
27 |
+
Separator.displayName = SeparatorPrimitive.Root.displayName
|
28 |
+
|
29 |
+
export { Separator }
|
client/src/components/ui/sheet.tsx
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
5 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
6 |
+
import { X } from "lucide-react"
|
7 |
+
|
8 |
+
import { cn } from "@/lib/utils"
|
9 |
+
|
10 |
+
const Sheet = SheetPrimitive.Root
|
11 |
+
|
12 |
+
const SheetTrigger = SheetPrimitive.Trigger
|
13 |
+
|
14 |
+
const SheetClose = SheetPrimitive.Close
|
15 |
+
|
16 |
+
const SheetPortal = SheetPrimitive.Portal
|
17 |
+
|
18 |
+
const SheetOverlay = React.forwardRef<
|
19 |
+
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
20 |
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
21 |
+
>(({ className, ...props }, ref) => (
|
22 |
+
<SheetPrimitive.Overlay
|
23 |
+
className={cn(
|
24 |
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
25 |
+
className
|
26 |
+
)}
|
27 |
+
{...props}
|
28 |
+
ref={ref}
|
29 |
+
/>
|
30 |
+
))
|
31 |
+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
32 |
+
|
33 |
+
const sheetVariants = cva(
|
34 |
+
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
35 |
+
{
|
36 |
+
variants: {
|
37 |
+
side: {
|
38 |
+
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
39 |
+
bottom:
|
40 |
+
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
41 |
+
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
42 |
+
right:
|
43 |
+
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
44 |
+
},
|
45 |
+
},
|
46 |
+
defaultVariants: {
|
47 |
+
side: "right",
|
48 |
+
},
|
49 |
+
}
|
50 |
+
)
|
51 |
+
|
52 |
+
interface SheetContentProps
|
53 |
+
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
54 |
+
VariantProps<typeof sheetVariants> {}
|
55 |
+
|
56 |
+
const SheetContent = React.forwardRef<
|
57 |
+
React.ElementRef<typeof SheetPrimitive.Content>,
|
58 |
+
SheetContentProps
|
59 |
+
>(({ side = "right", className, children, ...props }, ref) => (
|
60 |
+
<SheetPortal>
|
61 |
+
<SheetOverlay />
|
62 |
+
<SheetPrimitive.Content
|
63 |
+
ref={ref}
|
64 |
+
className={cn(sheetVariants({ side }), className)}
|
65 |
+
{...props}
|
66 |
+
>
|
67 |
+
{children}
|
68 |
+
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
69 |
+
<X className="h-4 w-4" />
|
70 |
+
<span className="sr-only">Close</span>
|
71 |
+
</SheetPrimitive.Close>
|
72 |
+
</SheetPrimitive.Content>
|
73 |
+
</SheetPortal>
|
74 |
+
))
|
75 |
+
SheetContent.displayName = SheetPrimitive.Content.displayName
|
76 |
+
|
77 |
+
const SheetHeader = ({
|
78 |
+
className,
|
79 |
+
...props
|
80 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
81 |
+
<div
|
82 |
+
className={cn(
|
83 |
+
"flex flex-col space-y-2 text-center sm:text-left",
|
84 |
+
className
|
85 |
+
)}
|
86 |
+
{...props}
|
87 |
+
/>
|
88 |
+
)
|
89 |
+
SheetHeader.displayName = "SheetHeader"
|
90 |
+
|
91 |
+
const SheetFooter = ({
|
92 |
+
className,
|
93 |
+
...props
|
94 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
95 |
+
<div
|
96 |
+
className={cn(
|
97 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
98 |
+
className
|
99 |
+
)}
|
100 |
+
{...props}
|
101 |
+
/>
|
102 |
+
)
|
103 |
+
SheetFooter.displayName = "SheetFooter"
|
104 |
+
|
105 |
+
const SheetTitle = React.forwardRef<
|
106 |
+
React.ElementRef<typeof SheetPrimitive.Title>,
|
107 |
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
108 |
+
>(({ className, ...props }, ref) => (
|
109 |
+
<SheetPrimitive.Title
|
110 |
+
ref={ref}
|
111 |
+
className={cn("text-lg font-semibold text-foreground", className)}
|
112 |
+
{...props}
|
113 |
+
/>
|
114 |
+
))
|
115 |
+
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
116 |
+
|
117 |
+
const SheetDescription = React.forwardRef<
|
118 |
+
React.ElementRef<typeof SheetPrimitive.Description>,
|
119 |
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
120 |
+
>(({ className, ...props }, ref) => (
|
121 |
+
<SheetPrimitive.Description
|
122 |
+
ref={ref}
|
123 |
+
className={cn("text-sm text-muted-foreground", className)}
|
124 |
+
{...props}
|
125 |
+
/>
|
126 |
+
))
|
127 |
+
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
128 |
+
|
129 |
+
export {
|
130 |
+
Sheet,
|
131 |
+
SheetPortal,
|
132 |
+
SheetOverlay,
|
133 |
+
SheetTrigger,
|
134 |
+
SheetClose,
|
135 |
+
SheetContent,
|
136 |
+
SheetHeader,
|
137 |
+
SheetFooter,
|
138 |
+
SheetTitle,
|
139 |
+
SheetDescription,
|
140 |
+
}
|
client/src/components/ui/sidebar.tsx
ADDED
@@ -0,0 +1,771 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { VariantProps, cva } from "class-variance-authority"
|
4 |
+
import { PanelLeft } from "lucide-react"
|
5 |
+
|
6 |
+
import { useIsMobile } from "@/hooks/use-mobile"
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
import { Button } from "@/components/ui/button"
|
9 |
+
import { Input } from "@/components/ui/input"
|
10 |
+
import { Separator } from "@/components/ui/separator"
|
11 |
+
import {
|
12 |
+
Sheet,
|
13 |
+
SheetContent,
|
14 |
+
SheetDescription,
|
15 |
+
SheetHeader,
|
16 |
+
SheetTitle,
|
17 |
+
} from "@/components/ui/sheet"
|
18 |
+
import { Skeleton } from "@/components/ui/skeleton"
|
19 |
+
import {
|
20 |
+
Tooltip,
|
21 |
+
TooltipContent,
|
22 |
+
TooltipProvider,
|
23 |
+
TooltipTrigger,
|
24 |
+
} from "@/components/ui/tooltip"
|
25 |
+
|
26 |
+
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
27 |
+
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
28 |
+
const SIDEBAR_WIDTH = "16rem"
|
29 |
+
const SIDEBAR_WIDTH_MOBILE = "18rem"
|
30 |
+
const SIDEBAR_WIDTH_ICON = "3rem"
|
31 |
+
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
32 |
+
|
33 |
+
type SidebarContextProps = {
|
34 |
+
state: "expanded" | "collapsed"
|
35 |
+
open: boolean
|
36 |
+
setOpen: (open: boolean) => void
|
37 |
+
openMobile: boolean
|
38 |
+
setOpenMobile: (open: boolean) => void
|
39 |
+
isMobile: boolean
|
40 |
+
toggleSidebar: () => void
|
41 |
+
}
|
42 |
+
|
43 |
+
const SidebarContext = React.createContext<SidebarContextProps | null>(null)
|
44 |
+
|
45 |
+
function useSidebar() {
|
46 |
+
const context = React.useContext(SidebarContext)
|
47 |
+
if (!context) {
|
48 |
+
throw new Error("useSidebar must be used within a SidebarProvider.")
|
49 |
+
}
|
50 |
+
|
51 |
+
return context
|
52 |
+
}
|
53 |
+
|
54 |
+
const SidebarProvider = React.forwardRef<
|
55 |
+
HTMLDivElement,
|
56 |
+
React.ComponentProps<"div"> & {
|
57 |
+
defaultOpen?: boolean
|
58 |
+
open?: boolean
|
59 |
+
onOpenChange?: (open: boolean) => void
|
60 |
+
}
|
61 |
+
>(
|
62 |
+
(
|
63 |
+
{
|
64 |
+
defaultOpen = true,
|
65 |
+
open: openProp,
|
66 |
+
onOpenChange: setOpenProp,
|
67 |
+
className,
|
68 |
+
style,
|
69 |
+
children,
|
70 |
+
...props
|
71 |
+
},
|
72 |
+
ref
|
73 |
+
) => {
|
74 |
+
const isMobile = useIsMobile()
|
75 |
+
const [openMobile, setOpenMobile] = React.useState(false)
|
76 |
+
|
77 |
+
// This is the internal state of the sidebar.
|
78 |
+
// We use openProp and setOpenProp for control from outside the component.
|
79 |
+
const [_open, _setOpen] = React.useState(defaultOpen)
|
80 |
+
const open = openProp ?? _open
|
81 |
+
const setOpen = React.useCallback(
|
82 |
+
(value: boolean | ((value: boolean) => boolean)) => {
|
83 |
+
const openState = typeof value === "function" ? value(open) : value
|
84 |
+
if (setOpenProp) {
|
85 |
+
setOpenProp(openState)
|
86 |
+
} else {
|
87 |
+
_setOpen(openState)
|
88 |
+
}
|
89 |
+
|
90 |
+
// This sets the cookie to keep the sidebar state.
|
91 |
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
|
92 |
+
},
|
93 |
+
[setOpenProp, open]
|
94 |
+
)
|
95 |
+
|
96 |
+
// Helper to toggle the sidebar.
|
97 |
+
const toggleSidebar = React.useCallback(() => {
|
98 |
+
return isMobile
|
99 |
+
? setOpenMobile((open) => !open)
|
100 |
+
: setOpen((open) => !open)
|
101 |
+
}, [isMobile, setOpen, setOpenMobile])
|
102 |
+
|
103 |
+
// Adds a keyboard shortcut to toggle the sidebar.
|
104 |
+
React.useEffect(() => {
|
105 |
+
const handleKeyDown = (event: KeyboardEvent) => {
|
106 |
+
if (
|
107 |
+
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
|
108 |
+
(event.metaKey || event.ctrlKey)
|
109 |
+
) {
|
110 |
+
event.preventDefault()
|
111 |
+
toggleSidebar()
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
window.addEventListener("keydown", handleKeyDown)
|
116 |
+
return () => window.removeEventListener("keydown", handleKeyDown)
|
117 |
+
}, [toggleSidebar])
|
118 |
+
|
119 |
+
// We add a state so that we can do data-state="expanded" or "collapsed".
|
120 |
+
// This makes it easier to style the sidebar with Tailwind classes.
|
121 |
+
const state = open ? "expanded" : "collapsed"
|
122 |
+
|
123 |
+
const contextValue = React.useMemo<SidebarContextProps>(
|
124 |
+
() => ({
|
125 |
+
state,
|
126 |
+
open,
|
127 |
+
setOpen,
|
128 |
+
isMobile,
|
129 |
+
openMobile,
|
130 |
+
setOpenMobile,
|
131 |
+
toggleSidebar,
|
132 |
+
}),
|
133 |
+
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
|
134 |
+
)
|
135 |
+
|
136 |
+
return (
|
137 |
+
<SidebarContext.Provider value={contextValue}>
|
138 |
+
<TooltipProvider delayDuration={0}>
|
139 |
+
<div
|
140 |
+
style={
|
141 |
+
{
|
142 |
+
"--sidebar-width": SIDEBAR_WIDTH,
|
143 |
+
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
144 |
+
...style,
|
145 |
+
} as React.CSSProperties
|
146 |
+
}
|
147 |
+
className={cn(
|
148 |
+
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
|
149 |
+
className
|
150 |
+
)}
|
151 |
+
ref={ref}
|
152 |
+
{...props}
|
153 |
+
>
|
154 |
+
{children}
|
155 |
+
</div>
|
156 |
+
</TooltipProvider>
|
157 |
+
</SidebarContext.Provider>
|
158 |
+
)
|
159 |
+
}
|
160 |
+
)
|
161 |
+
SidebarProvider.displayName = "SidebarProvider"
|
162 |
+
|
163 |
+
const Sidebar = React.forwardRef<
|
164 |
+
HTMLDivElement,
|
165 |
+
React.ComponentProps<"div"> & {
|
166 |
+
side?: "left" | "right"
|
167 |
+
variant?: "sidebar" | "floating" | "inset"
|
168 |
+
collapsible?: "offcanvas" | "icon" | "none"
|
169 |
+
}
|
170 |
+
>(
|
171 |
+
(
|
172 |
+
{
|
173 |
+
side = "left",
|
174 |
+
variant = "sidebar",
|
175 |
+
collapsible = "offcanvas",
|
176 |
+
className,
|
177 |
+
children,
|
178 |
+
...props
|
179 |
+
},
|
180 |
+
ref
|
181 |
+
) => {
|
182 |
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
183 |
+
|
184 |
+
if (collapsible === "none") {
|
185 |
+
return (
|
186 |
+
<div
|
187 |
+
className={cn(
|
188 |
+
"flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
|
189 |
+
className
|
190 |
+
)}
|
191 |
+
ref={ref}
|
192 |
+
{...props}
|
193 |
+
>
|
194 |
+
{children}
|
195 |
+
</div>
|
196 |
+
)
|
197 |
+
}
|
198 |
+
|
199 |
+
if (isMobile) {
|
200 |
+
return (
|
201 |
+
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
202 |
+
<SheetContent
|
203 |
+
data-sidebar="sidebar"
|
204 |
+
data-mobile="true"
|
205 |
+
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
|
206 |
+
style={
|
207 |
+
{
|
208 |
+
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
209 |
+
} as React.CSSProperties
|
210 |
+
}
|
211 |
+
side={side}
|
212 |
+
>
|
213 |
+
<SheetHeader className="sr-only">
|
214 |
+
<SheetTitle>Sidebar</SheetTitle>
|
215 |
+
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
|
216 |
+
</SheetHeader>
|
217 |
+
<div className="flex h-full w-full flex-col">{children}</div>
|
218 |
+
</SheetContent>
|
219 |
+
</Sheet>
|
220 |
+
)
|
221 |
+
}
|
222 |
+
|
223 |
+
return (
|
224 |
+
<div
|
225 |
+
ref={ref}
|
226 |
+
className="group peer hidden text-sidebar-foreground md:block"
|
227 |
+
data-state={state}
|
228 |
+
data-collapsible={state === "collapsed" ? collapsible : ""}
|
229 |
+
data-variant={variant}
|
230 |
+
data-side={side}
|
231 |
+
>
|
232 |
+
{/* This is what handles the sidebar gap on desktop */}
|
233 |
+
<div
|
234 |
+
className={cn(
|
235 |
+
"relative w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear",
|
236 |
+
"group-data-[collapsible=offcanvas]:w-0",
|
237 |
+
"group-data-[side=right]:rotate-180",
|
238 |
+
variant === "floating" || variant === "inset"
|
239 |
+
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
|
240 |
+
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
|
241 |
+
)}
|
242 |
+
/>
|
243 |
+
<div
|
244 |
+
className={cn(
|
245 |
+
"fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
|
246 |
+
side === "left"
|
247 |
+
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
248 |
+
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
249 |
+
// Adjust the padding for floating and inset variants.
|
250 |
+
variant === "floating" || variant === "inset"
|
251 |
+
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
|
252 |
+
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
253 |
+
className
|
254 |
+
)}
|
255 |
+
{...props}
|
256 |
+
>
|
257 |
+
<div
|
258 |
+
data-sidebar="sidebar"
|
259 |
+
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
|
260 |
+
>
|
261 |
+
{children}
|
262 |
+
</div>
|
263 |
+
</div>
|
264 |
+
</div>
|
265 |
+
)
|
266 |
+
}
|
267 |
+
)
|
268 |
+
Sidebar.displayName = "Sidebar"
|
269 |
+
|
270 |
+
const SidebarTrigger = React.forwardRef<
|
271 |
+
React.ElementRef<typeof Button>,
|
272 |
+
React.ComponentProps<typeof Button>
|
273 |
+
>(({ className, onClick, ...props }, ref) => {
|
274 |
+
const { toggleSidebar } = useSidebar()
|
275 |
+
|
276 |
+
return (
|
277 |
+
<Button
|
278 |
+
ref={ref}
|
279 |
+
data-sidebar="trigger"
|
280 |
+
variant="ghost"
|
281 |
+
size="icon"
|
282 |
+
className={cn("h-7 w-7", className)}
|
283 |
+
onClick={(event) => {
|
284 |
+
onClick?.(event)
|
285 |
+
toggleSidebar()
|
286 |
+
}}
|
287 |
+
{...props}
|
288 |
+
>
|
289 |
+
<PanelLeft />
|
290 |
+
<span className="sr-only">Toggle Sidebar</span>
|
291 |
+
</Button>
|
292 |
+
)
|
293 |
+
})
|
294 |
+
SidebarTrigger.displayName = "SidebarTrigger"
|
295 |
+
|
296 |
+
const SidebarRail = React.forwardRef<
|
297 |
+
HTMLButtonElement,
|
298 |
+
React.ComponentProps<"button">
|
299 |
+
>(({ className, ...props }, ref) => {
|
300 |
+
const { toggleSidebar } = useSidebar()
|
301 |
+
|
302 |
+
return (
|
303 |
+
<button
|
304 |
+
ref={ref}
|
305 |
+
data-sidebar="rail"
|
306 |
+
aria-label="Toggle Sidebar"
|
307 |
+
tabIndex={-1}
|
308 |
+
onClick={toggleSidebar}
|
309 |
+
title="Toggle Sidebar"
|
310 |
+
className={cn(
|
311 |
+
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
|
312 |
+
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
|
313 |
+
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
314 |
+
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
|
315 |
+
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
316 |
+
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
317 |
+
className
|
318 |
+
)}
|
319 |
+
{...props}
|
320 |
+
/>
|
321 |
+
)
|
322 |
+
})
|
323 |
+
SidebarRail.displayName = "SidebarRail"
|
324 |
+
|
325 |
+
const SidebarInset = React.forwardRef<
|
326 |
+
HTMLDivElement,
|
327 |
+
React.ComponentProps<"main">
|
328 |
+
>(({ className, ...props }, ref) => {
|
329 |
+
return (
|
330 |
+
<main
|
331 |
+
ref={ref}
|
332 |
+
className={cn(
|
333 |
+
"relative flex w-full flex-1 flex-col bg-background",
|
334 |
+
"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
|
335 |
+
className
|
336 |
+
)}
|
337 |
+
{...props}
|
338 |
+
/>
|
339 |
+
)
|
340 |
+
})
|
341 |
+
SidebarInset.displayName = "SidebarInset"
|
342 |
+
|
343 |
+
const SidebarInput = React.forwardRef<
|
344 |
+
React.ElementRef<typeof Input>,
|
345 |
+
React.ComponentProps<typeof Input>
|
346 |
+
>(({ className, ...props }, ref) => {
|
347 |
+
return (
|
348 |
+
<Input
|
349 |
+
ref={ref}
|
350 |
+
data-sidebar="input"
|
351 |
+
className={cn(
|
352 |
+
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
|
353 |
+
className
|
354 |
+
)}
|
355 |
+
{...props}
|
356 |
+
/>
|
357 |
+
)
|
358 |
+
})
|
359 |
+
SidebarInput.displayName = "SidebarInput"
|
360 |
+
|
361 |
+
const SidebarHeader = React.forwardRef<
|
362 |
+
HTMLDivElement,
|
363 |
+
React.ComponentProps<"div">
|
364 |
+
>(({ className, ...props }, ref) => {
|
365 |
+
return (
|
366 |
+
<div
|
367 |
+
ref={ref}
|
368 |
+
data-sidebar="header"
|
369 |
+
className={cn("flex flex-col gap-2 p-2", className)}
|
370 |
+
{...props}
|
371 |
+
/>
|
372 |
+
)
|
373 |
+
})
|
374 |
+
SidebarHeader.displayName = "SidebarHeader"
|
375 |
+
|
376 |
+
const SidebarFooter = React.forwardRef<
|
377 |
+
HTMLDivElement,
|
378 |
+
React.ComponentProps<"div">
|
379 |
+
>(({ className, ...props }, ref) => {
|
380 |
+
return (
|
381 |
+
<div
|
382 |
+
ref={ref}
|
383 |
+
data-sidebar="footer"
|
384 |
+
className={cn("flex flex-col gap-2 p-2", className)}
|
385 |
+
{...props}
|
386 |
+
/>
|
387 |
+
)
|
388 |
+
})
|
389 |
+
SidebarFooter.displayName = "SidebarFooter"
|
390 |
+
|
391 |
+
const SidebarSeparator = React.forwardRef<
|
392 |
+
React.ElementRef<typeof Separator>,
|
393 |
+
React.ComponentProps<typeof Separator>
|
394 |
+
>(({ className, ...props }, ref) => {
|
395 |
+
return (
|
396 |
+
<Separator
|
397 |
+
ref={ref}
|
398 |
+
data-sidebar="separator"
|
399 |
+
className={cn("mx-2 w-auto bg-sidebar-border", className)}
|
400 |
+
{...props}
|
401 |
+
/>
|
402 |
+
)
|
403 |
+
})
|
404 |
+
SidebarSeparator.displayName = "SidebarSeparator"
|
405 |
+
|
406 |
+
const SidebarContent = React.forwardRef<
|
407 |
+
HTMLDivElement,
|
408 |
+
React.ComponentProps<"div">
|
409 |
+
>(({ className, ...props }, ref) => {
|
410 |
+
return (
|
411 |
+
<div
|
412 |
+
ref={ref}
|
413 |
+
data-sidebar="content"
|
414 |
+
className={cn(
|
415 |
+
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
416 |
+
className
|
417 |
+
)}
|
418 |
+
{...props}
|
419 |
+
/>
|
420 |
+
)
|
421 |
+
})
|
422 |
+
SidebarContent.displayName = "SidebarContent"
|
423 |
+
|
424 |
+
const SidebarGroup = React.forwardRef<
|
425 |
+
HTMLDivElement,
|
426 |
+
React.ComponentProps<"div">
|
427 |
+
>(({ className, ...props }, ref) => {
|
428 |
+
return (
|
429 |
+
<div
|
430 |
+
ref={ref}
|
431 |
+
data-sidebar="group"
|
432 |
+
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
|
433 |
+
{...props}
|
434 |
+
/>
|
435 |
+
)
|
436 |
+
})
|
437 |
+
SidebarGroup.displayName = "SidebarGroup"
|
438 |
+
|
439 |
+
const SidebarGroupLabel = React.forwardRef<
|
440 |
+
HTMLDivElement,
|
441 |
+
React.ComponentProps<"div"> & { asChild?: boolean }
|
442 |
+
>(({ className, asChild = false, ...props }, ref) => {
|
443 |
+
const Comp = asChild ? Slot : "div"
|
444 |
+
|
445 |
+
return (
|
446 |
+
<Comp
|
447 |
+
ref={ref}
|
448 |
+
data-sidebar="group-label"
|
449 |
+
className={cn(
|
450 |
+
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
451 |
+
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
452 |
+
className
|
453 |
+
)}
|
454 |
+
{...props}
|
455 |
+
/>
|
456 |
+
)
|
457 |
+
})
|
458 |
+
SidebarGroupLabel.displayName = "SidebarGroupLabel"
|
459 |
+
|
460 |
+
const SidebarGroupAction = React.forwardRef<
|
461 |
+
HTMLButtonElement,
|
462 |
+
React.ComponentProps<"button"> & { asChild?: boolean }
|
463 |
+
>(({ className, asChild = false, ...props }, ref) => {
|
464 |
+
const Comp = asChild ? Slot : "button"
|
465 |
+
|
466 |
+
return (
|
467 |
+
<Comp
|
468 |
+
ref={ref}
|
469 |
+
data-sidebar="group-action"
|
470 |
+
className={cn(
|
471 |
+
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
472 |
+
// Increases the hit area of the button on mobile.
|
473 |
+
"after:absolute after:-inset-2 after:md:hidden",
|
474 |
+
"group-data-[collapsible=icon]:hidden",
|
475 |
+
className
|
476 |
+
)}
|
477 |
+
{...props}
|
478 |
+
/>
|
479 |
+
)
|
480 |
+
})
|
481 |
+
SidebarGroupAction.displayName = "SidebarGroupAction"
|
482 |
+
|
483 |
+
const SidebarGroupContent = React.forwardRef<
|
484 |
+
HTMLDivElement,
|
485 |
+
React.ComponentProps<"div">
|
486 |
+
>(({ className, ...props }, ref) => (
|
487 |
+
<div
|
488 |
+
ref={ref}
|
489 |
+
data-sidebar="group-content"
|
490 |
+
className={cn("w-full text-sm", className)}
|
491 |
+
{...props}
|
492 |
+
/>
|
493 |
+
))
|
494 |
+
SidebarGroupContent.displayName = "SidebarGroupContent"
|
495 |
+
|
496 |
+
const SidebarMenu = React.forwardRef<
|
497 |
+
HTMLUListElement,
|
498 |
+
React.ComponentProps<"ul">
|
499 |
+
>(({ className, ...props }, ref) => (
|
500 |
+
<ul
|
501 |
+
ref={ref}
|
502 |
+
data-sidebar="menu"
|
503 |
+
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
504 |
+
{...props}
|
505 |
+
/>
|
506 |
+
))
|
507 |
+
SidebarMenu.displayName = "SidebarMenu"
|
508 |
+
|
509 |
+
const SidebarMenuItem = React.forwardRef<
|
510 |
+
HTMLLIElement,
|
511 |
+
React.ComponentProps<"li">
|
512 |
+
>(({ className, ...props }, ref) => (
|
513 |
+
<li
|
514 |
+
ref={ref}
|
515 |
+
data-sidebar="menu-item"
|
516 |
+
className={cn("group/menu-item relative", className)}
|
517 |
+
{...props}
|
518 |
+
/>
|
519 |
+
))
|
520 |
+
SidebarMenuItem.displayName = "SidebarMenuItem"
|
521 |
+
|
522 |
+
const sidebarMenuButtonVariants = cva(
|
523 |
+
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
524 |
+
{
|
525 |
+
variants: {
|
526 |
+
variant: {
|
527 |
+
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
528 |
+
outline:
|
529 |
+
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
|
530 |
+
},
|
531 |
+
size: {
|
532 |
+
default: "h-8 text-sm",
|
533 |
+
sm: "h-7 text-xs",
|
534 |
+
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
|
535 |
+
},
|
536 |
+
},
|
537 |
+
defaultVariants: {
|
538 |
+
variant: "default",
|
539 |
+
size: "default",
|
540 |
+
},
|
541 |
+
}
|
542 |
+
)
|
543 |
+
|
544 |
+
const SidebarMenuButton = React.forwardRef<
|
545 |
+
HTMLButtonElement,
|
546 |
+
React.ComponentProps<"button"> & {
|
547 |
+
asChild?: boolean
|
548 |
+
isActive?: boolean
|
549 |
+
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
550 |
+
} & VariantProps<typeof sidebarMenuButtonVariants>
|
551 |
+
>(
|
552 |
+
(
|
553 |
+
{
|
554 |
+
asChild = false,
|
555 |
+
isActive = false,
|
556 |
+
variant = "default",
|
557 |
+
size = "default",
|
558 |
+
tooltip,
|
559 |
+
className,
|
560 |
+
...props
|
561 |
+
},
|
562 |
+
ref
|
563 |
+
) => {
|
564 |
+
const Comp = asChild ? Slot : "button"
|
565 |
+
const { isMobile, state } = useSidebar()
|
566 |
+
|
567 |
+
const button = (
|
568 |
+
<Comp
|
569 |
+
ref={ref}
|
570 |
+
data-sidebar="menu-button"
|
571 |
+
data-size={size}
|
572 |
+
data-active={isActive}
|
573 |
+
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
574 |
+
{...props}
|
575 |
+
/>
|
576 |
+
)
|
577 |
+
|
578 |
+
if (!tooltip) {
|
579 |
+
return button
|
580 |
+
}
|
581 |
+
|
582 |
+
if (typeof tooltip === "string") {
|
583 |
+
tooltip = {
|
584 |
+
children: tooltip,
|
585 |
+
}
|
586 |
+
}
|
587 |
+
|
588 |
+
return (
|
589 |
+
<Tooltip>
|
590 |
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
591 |
+
<TooltipContent
|
592 |
+
side="right"
|
593 |
+
align="center"
|
594 |
+
hidden={state !== "collapsed" || isMobile}
|
595 |
+
{...tooltip}
|
596 |
+
/>
|
597 |
+
</Tooltip>
|
598 |
+
)
|
599 |
+
}
|
600 |
+
)
|
601 |
+
SidebarMenuButton.displayName = "SidebarMenuButton"
|
602 |
+
|
603 |
+
const SidebarMenuAction = React.forwardRef<
|
604 |
+
HTMLButtonElement,
|
605 |
+
React.ComponentProps<"button"> & {
|
606 |
+
asChild?: boolean
|
607 |
+
showOnHover?: boolean
|
608 |
+
}
|
609 |
+
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
|
610 |
+
const Comp = asChild ? Slot : "button"
|
611 |
+
|
612 |
+
return (
|
613 |
+
<Comp
|
614 |
+
ref={ref}
|
615 |
+
data-sidebar="menu-action"
|
616 |
+
className={cn(
|
617 |
+
"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
618 |
+
// Increases the hit area of the button on mobile.
|
619 |
+
"after:absolute after:-inset-2 after:md:hidden",
|
620 |
+
"peer-data-[size=sm]/menu-button:top-1",
|
621 |
+
"peer-data-[size=default]/menu-button:top-1.5",
|
622 |
+
"peer-data-[size=lg]/menu-button:top-2.5",
|
623 |
+
"group-data-[collapsible=icon]:hidden",
|
624 |
+
showOnHover &&
|
625 |
+
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
|
626 |
+
className
|
627 |
+
)}
|
628 |
+
{...props}
|
629 |
+
/>
|
630 |
+
)
|
631 |
+
})
|
632 |
+
SidebarMenuAction.displayName = "SidebarMenuAction"
|
633 |
+
|
634 |
+
const SidebarMenuBadge = React.forwardRef<
|
635 |
+
HTMLDivElement,
|
636 |
+
React.ComponentProps<"div">
|
637 |
+
>(({ className, ...props }, ref) => (
|
638 |
+
<div
|
639 |
+
ref={ref}
|
640 |
+
data-sidebar="menu-badge"
|
641 |
+
className={cn(
|
642 |
+
"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground",
|
643 |
+
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
|
644 |
+
"peer-data-[size=sm]/menu-button:top-1",
|
645 |
+
"peer-data-[size=default]/menu-button:top-1.5",
|
646 |
+
"peer-data-[size=lg]/menu-button:top-2.5",
|
647 |
+
"group-data-[collapsible=icon]:hidden",
|
648 |
+
className
|
649 |
+
)}
|
650 |
+
{...props}
|
651 |
+
/>
|
652 |
+
))
|
653 |
+
SidebarMenuBadge.displayName = "SidebarMenuBadge"
|
654 |
+
|
655 |
+
const SidebarMenuSkeleton = React.forwardRef<
|
656 |
+
HTMLDivElement,
|
657 |
+
React.ComponentProps<"div"> & {
|
658 |
+
showIcon?: boolean
|
659 |
+
}
|
660 |
+
>(({ className, showIcon = false, ...props }, ref) => {
|
661 |
+
// Random width between 50 to 90%.
|
662 |
+
const width = React.useMemo(() => {
|
663 |
+
return `${Math.floor(Math.random() * 40) + 50}%`
|
664 |
+
}, [])
|
665 |
+
|
666 |
+
return (
|
667 |
+
<div
|
668 |
+
ref={ref}
|
669 |
+
data-sidebar="menu-skeleton"
|
670 |
+
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
|
671 |
+
{...props}
|
672 |
+
>
|
673 |
+
{showIcon && (
|
674 |
+
<Skeleton
|
675 |
+
className="size-4 rounded-md"
|
676 |
+
data-sidebar="menu-skeleton-icon"
|
677 |
+
/>
|
678 |
+
)}
|
679 |
+
<Skeleton
|
680 |
+
className="h-4 max-w-[--skeleton-width] flex-1"
|
681 |
+
data-sidebar="menu-skeleton-text"
|
682 |
+
style={
|
683 |
+
{
|
684 |
+
"--skeleton-width": width,
|
685 |
+
} as React.CSSProperties
|
686 |
+
}
|
687 |
+
/>
|
688 |
+
</div>
|
689 |
+
)
|
690 |
+
})
|
691 |
+
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
|
692 |
+
|
693 |
+
const SidebarMenuSub = React.forwardRef<
|
694 |
+
HTMLUListElement,
|
695 |
+
React.ComponentProps<"ul">
|
696 |
+
>(({ className, ...props }, ref) => (
|
697 |
+
<ul
|
698 |
+
ref={ref}
|
699 |
+
data-sidebar="menu-sub"
|
700 |
+
className={cn(
|
701 |
+
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
|
702 |
+
"group-data-[collapsible=icon]:hidden",
|
703 |
+
className
|
704 |
+
)}
|
705 |
+
{...props}
|
706 |
+
/>
|
707 |
+
))
|
708 |
+
SidebarMenuSub.displayName = "SidebarMenuSub"
|
709 |
+
|
710 |
+
const SidebarMenuSubItem = React.forwardRef<
|
711 |
+
HTMLLIElement,
|
712 |
+
React.ComponentProps<"li">
|
713 |
+
>(({ ...props }, ref) => <li ref={ref} {...props} />)
|
714 |
+
SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
|
715 |
+
|
716 |
+
const SidebarMenuSubButton = React.forwardRef<
|
717 |
+
HTMLAnchorElement,
|
718 |
+
React.ComponentProps<"a"> & {
|
719 |
+
asChild?: boolean
|
720 |
+
size?: "sm" | "md"
|
721 |
+
isActive?: boolean
|
722 |
+
}
|
723 |
+
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
|
724 |
+
const Comp = asChild ? Slot : "a"
|
725 |
+
|
726 |
+
return (
|
727 |
+
<Comp
|
728 |
+
ref={ref}
|
729 |
+
data-sidebar="menu-sub-button"
|
730 |
+
data-size={size}
|
731 |
+
data-active={isActive}
|
732 |
+
className={cn(
|
733 |
+
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
734 |
+
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
735 |
+
size === "sm" && "text-xs",
|
736 |
+
size === "md" && "text-sm",
|
737 |
+
"group-data-[collapsible=icon]:hidden",
|
738 |
+
className
|
739 |
+
)}
|
740 |
+
{...props}
|
741 |
+
/>
|
742 |
+
)
|
743 |
+
})
|
744 |
+
SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
|
745 |
+
|
746 |
+
export {
|
747 |
+
Sidebar,
|
748 |
+
SidebarContent,
|
749 |
+
SidebarFooter,
|
750 |
+
SidebarGroup,
|
751 |
+
SidebarGroupAction,
|
752 |
+
SidebarGroupContent,
|
753 |
+
SidebarGroupLabel,
|
754 |
+
SidebarHeader,
|
755 |
+
SidebarInput,
|
756 |
+
SidebarInset,
|
757 |
+
SidebarMenu,
|
758 |
+
SidebarMenuAction,
|
759 |
+
SidebarMenuBadge,
|
760 |
+
SidebarMenuButton,
|
761 |
+
SidebarMenuItem,
|
762 |
+
SidebarMenuSkeleton,
|
763 |
+
SidebarMenuSub,
|
764 |
+
SidebarMenuSubButton,
|
765 |
+
SidebarMenuSubItem,
|
766 |
+
SidebarProvider,
|
767 |
+
SidebarRail,
|
768 |
+
SidebarSeparator,
|
769 |
+
SidebarTrigger,
|
770 |
+
useSidebar,
|
771 |
+
}
|