Spaces:
Running
Running
Step 1: 项目计划与分镜大纲 README
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +79 -0
- index.html +1 -0
- scripts/placeholder.html +1 -0
- scripts/push_hf.py +36 -0
- src/App.tsx +24 -0
- src/ErrorBoundary.tsx +106 -0
- src/components/ui/accordion.tsx +55 -0
- src/components/ui/alert.tsx +59 -0
- src/components/ui/aspect-ratio.tsx +5 -0
- src/components/ui/avatar.tsx +48 -0
- src/components/ui/badge.tsx +36 -0
- src/components/ui/breadcrumb.tsx +115 -0
- src/components/ui/button.tsx +57 -0
- src/components/ui/calendar.tsx +211 -0
- src/components/ui/card.tsx +76 -0
- src/components/ui/carousel.tsx +262 -0
- src/components/ui/checkbox.tsx +30 -0
- src/components/ui/collapsible.tsx +9 -0
- src/components/ui/command.tsx +153 -0
- src/components/ui/context-menu.tsx +200 -0
- src/components/ui/dialog.tsx +120 -0
- src/components/ui/drawer.tsx +118 -0
- src/components/ui/dropdown-menu.tsx +201 -0
- src/components/ui/form.tsx +176 -0
- src/components/ui/hover-card.tsx +29 -0
- src/components/ui/input.tsx +22 -0
- src/components/ui/label.tsx +26 -0
- src/components/ui/menubar.tsx +256 -0
- src/components/ui/navigation-menu.tsx +128 -0
- src/components/ui/popover.tsx +33 -0
- src/components/ui/progress.tsx +26 -0
- src/components/ui/radio-group.tsx +42 -0
- src/components/ui/resizable.tsx +43 -0
- src/components/ui/scroll-area.tsx +46 -0
- src/components/ui/select.tsx +157 -0
- src/components/ui/separator.tsx +31 -0
- src/components/ui/sheet.tsx +140 -0
- src/components/ui/skeleton.tsx +15 -0
- src/components/ui/slider.tsx +26 -0
- src/components/ui/sonner.tsx +29 -0
- src/components/ui/switch.tsx +29 -0
- src/components/ui/table.tsx +120 -0
- src/components/ui/tabs.tsx +53 -0
- src/components/ui/textarea.tsx +22 -0
- src/components/ui/toast.tsx +129 -0
- src/components/ui/toaster.tsx +35 -0
- src/components/ui/toggle-group.tsx +59 -0
- src/components/ui/toggle.tsx +43 -0
- src/components/ui/tooltip.tsx +30 -0
- src/entry-client.tsx +112 -0
README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: 腱鞘炎 3D 医学科普动画
|
| 3 |
+
emoji: 🖐️
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# 腱鞘炎 · 3D 人体解剖科普动画(网页实时渲染版)
|
| 12 |
+
|
| 13 |
+
> 类型:医学科普 + 3D 人体解剖动画 | 时长:约 80–90 秒 | 画幅:9:16 竖屏 | 风格:真实医学 3D、清晰、干净、专业、易懂
|
| 14 |
+
> 所有解剖结构、人物手部、器械均为**原创程序化 3D 医学示意模型**,不使用任何医院、品牌、教材或受版权保护的现成素材。
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## 一、项目目标
|
| 19 |
+
|
| 20 |
+
1. 在网页中直接播放一部完整的 3D 动画视频(不是预渲染 mp4),含 **旁白配音 + 背景音乐 + 音效 + 字幕**。
|
| 21 |
+
2. 网页内 **浏览器本地渲染导出 MP4**(WebCodecs 编码,H.264 + AAC/Opus),可选 **24 / 30 / 60 fps**,输出 1080×1920。
|
| 22 |
+
3. **适配手机竖屏观看**:播放器自动铺满竖屏,支持全屏、点击暂停、拖动进度。
|
| 23 |
+
4. 提供四大平台(小红书 / 抖音 / 哔哩哔哩 / 微信视频号)可一键复制的 **标题、描述、话题标签**,并按各平台规格生成 **封面图**(PNG 下载)。
|
| 24 |
+
|
| 25 |
+
## 二、技术方案
|
| 26 |
+
|
| 27 |
+
| 模块 | 技术 |
|
| 28 |
+
|---|---|
|
| 29 |
+
| 3D 渲染 | Three.js(WebGL),程序化建模:手部皮肤、骨骼、肌腱、腱鞘、器械、生活道具 |
|
| 30 |
+
| 分层透视 | 皮肤「深度预写 + 半透明」双通道渲染,实现透明皮肤无接缝;骨骼 / 肌腱 / 腱鞘三层独立淡入淡出 |
|
| 31 |
+
| 疼痛表达 | 统一红色发光(加色混合 Sprite + 肌腱着色器中的摩擦热区 黄→红) |
|
| 32 |
+
| 肌腱滑动 | 每帧按关节世界坐标重建 Tube 曲线,纹理 UV 随拇指屈伸偏移,表现肌腱在腱鞘内滑动 |
|
| 33 |
+
| 时间轴 | 纯函数 `timeline(t) → 场景状态`,同一时刻永远得到同一画面(保证导出逐帧一致) |
|
| 34 |
+
| 2D 叠加层 | Canvas 2D 绘制字幕、关键词、标注、超声画面、四宫格、分屏标签 |
|
| 35 |
+
| 旁白 | 中文神经网络 TTS 预生成逐句音频(构建期生成,存于 `public/audio`),句级精确字幕对齐 |
|
| 36 |
+
| 音乐/音效 | Web Audio `OfflineAudioContext` 程序化合成:氛围铺底和弦、低频脉冲、转场 whoosh、疼痛刺痛音、扫描提示音、松解释放音;旁白时自动压低音乐(ducking) |
|
| 37 |
+
| 导出 | mediabunny(MP4 封装)+ WebCodecs `VideoEncoder/AudioEncoder`,逐帧离线渲染,不依赖实时速度 |
|
| 38 |
+
| 封面 | 同一 3D 场景按 4:3 / 3:4 / 16:9 重新取景渲染 + 排版标题 |
|
| 39 |
+
|
| 40 |
+
## 三、分镜大纲(11 个镜头 + 结尾互动)
|
| 41 |
+
|
| 42 |
+
| # | 镜头 | 关键画面 |
|
| 43 |
+
|---|---|---|
|
| 44 |
+
| 1 | 疼痛开始 | 手部特写,拇指活动、抓握 → 疼痛抖动 → 推镜至腕部拇指侧 → 红色发光 → 皮肤半透明 → 骨骼 / 肌腱 / 腱鞘逐层出现 |
|
| 45 |
+
| 2 | 腱鞘炎发生了什么 | 肌腱在腱鞘中顺畅滑动 → 腱鞘增厚、空间变窄 → 摩擦区 黄→红;横截面插图;关键词「腱鞘增厚 / 空间变窄 / 活动疼痛」 |
|
| 46 |
+
| 3 | 为什么一动就疼 | 抓、捏、拧瓶盖三个动作,内部肌腱同步运动,红区随动作增强,镜头跟随疼痛点 |
|
| 47 |
+
| 4 | 哪些人更容易出现 | 键盘鼠标 / 工具抓握 / 家务拧提 / 抱孩子 四场景快切,红点闪现 + 关键词 |
|
| 48 |
+
| 5 | 不要一上来就治疗 | 3D 手旋转 → 锁定疼痛区 → 肌腱轨迹 → 超声画面(肌腱 / 腱鞘 / 狭窄位置) |
|
| 49 |
+
| 6 | 常见治疗方法 | 四宫格:减少抓握 / 拇指腕部支具 / 局部治疗示意 / 手术松解示意 |
|
| 50 |
+
| 7 | 进入小针刀 | 腱鞘放大,红区标记狭窄,原创器械从皮肤外缓慢进入 |
|
| 51 |
+
| 8 | 治疗过程 | 超声定位白点 → 消毒 → 局部麻醉 → 器械到达目标区域松解(3D 剖面,无血腥) |
|
| 52 |
+
| 9 | 松解前后对比 | 左右分屏:治疗前(狭窄、受限、红)vs 治疗后示意(变宽、顺滑、红区减弱) |
|
| 53 |
+
| 10 | 从疼痛到恢复 | 拇指困难 → 轻微活动 → 抓握 → 拿杯子 → 用手机 → 自然活动,红区渐淡 |
|
| 54 |
+
| 11 | 记忆点 | 再次进入 3D → 拉远恢复完整手部 → 红区消失 → 金句「手越疼,越不能只靠硬扛。」 |
|
| 55 |
+
| 尾 | 评论区互动 | 「评论区告诉我,是抓、捏、拧,还是抱孩子?」 |
|
| 56 |
+
|
| 57 |
+
## 四、实施步骤(每完成一大步推送一次到本 Space)
|
| 58 |
+
|
| 59 |
+
1. ✅ 编写本计划 README,初始化 Space(static)。
|
| 60 |
+
2. ⏳ 生成中文旁白逐句音频 + 时间轴清单(`timeline.json`)。
|
| 61 |
+
3. ⏳ 程序化 3D 手部解剖模型:皮肤 / 骨骼 / 肌腱 / 腱鞘 / 关节骨架绑定 / 疼痛发光 / 器械与道具。
|
| 62 |
+
4. ⏳ 分镜时间轴:11 镜头 + 结尾的相机运动、姿态、图层、字幕、HUD。
|
| 63 |
+
5. ⏳ 程序化音乐与音效、旁白混音、ducking。
|
| 64 |
+
6. ⏳ 播放器 UI(手机竖屏适配、全屏、进度条)+ MP4 本地导出(24/30/60fps)。
|
| 65 |
+
7. ⏳ 四平台文案(标题 / 话题 / 正文,严格字数校验)+ 封面生成与下载。
|
| 66 |
+
8. ⏳ 生产构建、检查,推送最终静态站点。
|
| 67 |
+
|
| 68 |
+
## 五、目录结构(推送到 Space 的内容)
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
index.html, assets/ ← 构建产物,Space 直接可运行
|
| 72 |
+
audio/ ← 旁白音频 + timeline.json
|
| 73 |
+
src/ ← 源码(engine / scenes / audio / export / covers / copy)
|
| 74 |
+
README.md ← 本文件(计划 + 进度)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## 六、进度日志
|
| 78 |
+
|
| 79 |
+
- [x] Step 1:计划与大纲完成
|
index.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<!doctype html><html lang="zh"><meta charset="utf-8"><title>腱鞘炎 3D 科普动画 · 制作中</title><body style="background:#06101f;color:#cfe;font-family:sans-serif;display:grid;place-items:center;height:100vh"><div><h2>腱鞘炎 3D 医学科普动画</h2><p>项目制作中,进度见 README。</p></div></body></html>
|
scripts/placeholder.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<!doctype html><html lang="zh"><meta charset="utf-8"><title>腱鞘炎 3D 科普动画 · 制作中</title><body style="background:#06101f;color:#cfe;font-family:sans-serif;display:grid;place-items:center;height:100vh"><div><h2>腱鞘炎 3D 医学科普动画</h2><p>项目制作中,进度见 README。</p></div></body></html>
|
scripts/push_hf.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Push project progress to HF Space Ethscriptions/mx.
|
| 2 |
+
Usage: HF_TOKEN=... python3 scripts/push_hf.py "commit message"
|
| 3 |
+
Uploads README.md, frontend/src -> src/, frontend/public/audio -> audio/, and built static site (dist_static) at root.
|
| 4 |
+
"""
|
| 5 |
+
import os, sys
|
| 6 |
+
from huggingface_hub import HfApi, CommitOperationAdd
|
| 7 |
+
|
| 8 |
+
REPO = "Ethscriptions/mx"
|
| 9 |
+
api = HfApi(token=os.environ["HF_TOKEN"])
|
| 10 |
+
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 11 |
+
msg = sys.argv[1] if len(sys.argv) > 1 else "update"
|
| 12 |
+
ops = []
|
| 13 |
+
|
| 14 |
+
def add_dir(local, remote_prefix):
|
| 15 |
+
if not os.path.isdir(local):
|
| 16 |
+
return
|
| 17 |
+
for dp, _, files in os.walk(local):
|
| 18 |
+
for f in files:
|
| 19 |
+
lp = os.path.join(dp, f)
|
| 20 |
+
rel = os.path.relpath(lp, local).replace(os.sep, "/")
|
| 21 |
+
ops.append(CommitOperationAdd(path_in_repo=f"{remote_prefix}{rel}", path_or_fileobj=lp))
|
| 22 |
+
|
| 23 |
+
ops.append(CommitOperationAdd(path_in_repo="README.md", path_or_fileobj=os.path.join(root, "README.md")))
|
| 24 |
+
add_dir(os.path.join(root, "frontend", "src"), "src/")
|
| 25 |
+
add_dir(os.path.join(root, "scripts"), "scripts/")
|
| 26 |
+
static = os.path.join(root, "dist_static")
|
| 27 |
+
if os.path.isdir(static):
|
| 28 |
+
add_dir(static, "") # includes index.html, assets/, audio/
|
| 29 |
+
else:
|
| 30 |
+
add_dir(os.path.join(root, "frontend", "public", "audio"), "audio/")
|
| 31 |
+
placeholder = os.path.join(root, "scripts", "placeholder.html")
|
| 32 |
+
if os.path.exists(placeholder):
|
| 33 |
+
ops.append(CommitOperationAdd(path_in_repo="index.html", path_or_fileobj=placeholder))
|
| 34 |
+
|
| 35 |
+
api.create_commit(repo_id=REPO, repo_type="space", operations=ops, commit_message=msg)
|
| 36 |
+
print("pushed", len(ops), "files:", msg)
|
src/App.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const SURF_LOGO_PATH = "M14.6875 13.333C15.0977 13.333 15.4859 13.5196 15.7422 13.8398L17.8721 16.502C17.9009 16.538 17.9308 16.5721 17.9619 16.6035C18.3091 16.9542 18.7529 17.3405 18.7529 17.834C18.7528 18.3399 18.3428 18.75 17.8369 18.75H17.5713C17.2123 18.75 16.8727 18.5869 16.6484 18.3066L15.4678 16.8311C14.761 15.9479 13.3369 16.4478 13.3369 17.5791V18.6494C13.3369 18.7062 13.2882 18.75 13.2314 18.75H11.3584C10.7127 18.75 10.1016 18.4564 9.69824 17.9521L8.80176 16.8311C8.09497 15.9477 6.66993 16.4478 6.66992 17.5791V18.6494C6.66991 18.7062 6.62124 18.75 6.56445 18.75H4.69141C4.04589 18.75 3.43548 18.4562 3.03223 17.9521L2.13477 16.8311C1.77885 16.3862 1.25293 15.931 1.25293 15.3613V13.4395C1.253 13.3822 1.29729 13.3331 1.35449 13.333C1.7646 13.3332 2.15295 13.5196 2.40918 13.8398L4.53809 16.502C5.24478 17.3853 6.66962 16.886 6.66992 15.7549V14.6836C6.67018 13.9379 7.27483 13.3332 8.02051 13.333C8.43065 13.333 8.81884 13.5197 9.0752 13.8398L11.2051 16.502C11.9097 17.3827 13.3282 16.8887 13.3369 15.7646V14.6836C13.3372 13.9378 13.9417 13.3331 14.6875 13.333ZM14.6875 6.66699C15.0978 6.66702 15.4859 6.85347 15.7422 7.17383L17.8721 9.83594C18.2276 10.2801 18.7526 10.7339 18.7529 11.3027V13.2266C18.7528 13.2833 18.7091 13.333 18.6523 13.333C18.2421 13.3329 17.8539 13.1465 17.5977 12.8262L15.4678 10.1641C14.7609 9.28113 13.3369 9.78089 13.3369 10.9121V11.9824C13.3369 12.7282 12.7321 13.3328 11.9863 13.333C11.5761 13.3329 11.187 13.1465 10.9307 12.8262L8.80176 10.1641C8.09497 9.28072 6.66993 9.78076 6.66992 10.9121V11.9824C6.66992 12.7284 6.06525 13.3329 5.31934 13.333C4.90908 13.3329 4.52094 13.1465 4.26465 12.8262L2.13477 10.1641C1.7788 9.71925 1.25293 9.26403 1.25293 8.69434V6.77344C1.253 6.71621 1.29728 6.6671 1.35449 6.66699C1.76461 6.66714 2.15295 6.85359 2.40918 7.17383L4.53809 9.83594C5.24487 10.7193 6.66989 10.2192 6.66992 9.08789V8.01758C6.66992 7.27168 7.27467 6.66722 8.02051 6.66699C8.43082 6.66699 8.81885 6.85345 9.0752 7.17383L11.2051 9.83594C11.9097 10.7163 13.3279 10.2223 13.3369 9.09863V8.01758C13.3369 7.27161 13.9416 6.6671 14.6875 6.66699ZM2.43555 1.25C2.79488 1.25 3.13499 1.41367 3.35938 1.69434L4.53809 3.16895C5.24485 4.05241 6.66991 3.55228 6.66992 2.4209V1.35059C6.66993 1.29391 6.71874 1.25017 6.77539 1.25H8.64844C9.29394 1.25 9.90438 1.5438 10.3076 2.04785L11.2051 3.16895C11.9097 4.04967 13.3282 3.55564 13.3369 2.43164V1.35059C13.3369 1.29391 13.3857 1.25017 13.4424 1.25H15.3154C15.9609 1.25 16.5714 1.5438 16.9746 2.04785L17.8721 3.16895C18.2275 3.61314 18.7526 4.06689 18.7529 4.63574V6.56055C18.7528 6.61728 18.7091 6.66699 18.6523 6.66699C18.2421 6.66688 17.8539 6.4795 17.5977 6.15918L15.4678 3.49805C14.761 2.61474 13.3371 3.11388 13.3369 4.24512V5.31543C13.3369 6.06126 12.7321 6.66673 11.9863 6.66699C11.5761 6.66693 11.187 6.47955 10.9307 6.15918L8.80176 3.49805C8.095 2.61474 6.67006 3.11388 6.66992 4.24512V5.31543C6.66992 6.06137 6.06525 6.66691 5.31934 6.66699C4.90908 6.66693 4.52094 6.47955 4.26465 6.15918L2.13477 3.49805C2.10558 3.46158 2.07543 3.42725 2.04395 3.39551C1.6966 3.04528 1.25293 2.6583 1.25293 2.16504C1.25317 1.65963 1.66348 1.25 2.16895 1.25H2.43555Z"
|
| 2 |
+
|
| 3 |
+
function SurfLogo() {
|
| 4 |
+
return (
|
| 5 |
+
<svg width="40" height="40" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 6 |
+
<path d={SURF_LOGO_PATH} fill="var(--fg-base, #212121)" />
|
| 7 |
+
</svg>
|
| 8 |
+
)
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
// Frontend API calls should use `src/lib/api.ts`, not absolute `/api/...` URLs.
|
| 12 |
+
export default function App() {
|
| 13 |
+
return (
|
| 14 |
+
<div data-surf-placeholder className="min-h-screen flex flex-col items-center justify-center gap-4 rounded-[14px]" style={{ background: 'var(--bg-chat-nav, #f4f4f4)', border: '1px solid var(--border-strong, rgba(42,42,42,0.08))' }}>
|
| 15 |
+
<SurfLogo />
|
| 16 |
+
<h1 className="font-black text-[36px] leading-[44px] text-[var(--fg-base,#212121)]">
|
| 17 |
+
GM, Builder<span style={{ color: 'var(--brand-100, #ff2882)' }}>.</span>
|
| 18 |
+
</h1>
|
| 19 |
+
<p className="text-xl leading-7 text-[var(--fg-subtle,#7a7a7a)]">
|
| 20 |
+
Start your crypto project here!
|
| 21 |
+
</p>
|
| 22 |
+
</div>
|
| 23 |
+
)
|
| 24 |
+
}
|
src/ErrorBoundary.tsx
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, type ReactNode } from 'react'
|
| 2 |
+
|
| 3 |
+
interface Props { children: ReactNode; fallback?: ReactNode }
|
| 4 |
+
interface State { error: Error | null }
|
| 5 |
+
|
| 6 |
+
// Auto-reload config for transient dep-optimization errors.
|
| 7 |
+
// Shares the same key as entry-client's cold-start guard so reload
|
| 8 |
+
// attempts are counted together, preventing double reload loops.
|
| 9 |
+
const RELOAD_KEY = '__dep_reload'
|
| 10 |
+
const MAX_RELOADS = 6
|
| 11 |
+
// If the last reload was more than this many ms ago, reset the counter.
|
| 12 |
+
// This prevents stale counters from blocking legitimate retries on a
|
| 13 |
+
// later visit, while still capping rapid reload loops.
|
| 14 |
+
const RELOAD_WINDOW_MS = 60_000
|
| 15 |
+
|
| 16 |
+
// Patterns that indicate React modules loaded as stubs (dep optimization in progress)
|
| 17 |
+
const DEP_OPT_PATTERNS = [
|
| 18 |
+
"reading 'useState'",
|
| 19 |
+
"reading 'useEffect'",
|
| 20 |
+
"reading 'useRef'",
|
| 21 |
+
"reading 'useCallback'",
|
| 22 |
+
"reading 'useMemo'",
|
| 23 |
+
"reading 'useContext'",
|
| 24 |
+
"reading 'useReducer'",
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
function isDepOptError(msg: string): boolean {
|
| 28 |
+
return DEP_OPT_PATTERNS.some((p) => msg.includes(p))
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
// Shared format with entry-client: { c: count, t: timestamp }
|
| 32 |
+
function getReloadState(): { c: number; t: number } {
|
| 33 |
+
try {
|
| 34 |
+
const raw = sessionStorage.getItem(RELOAD_KEY)
|
| 35 |
+
if (raw) return JSON.parse(raw)
|
| 36 |
+
} catch { /* ignore parse errors */ }
|
| 37 |
+
return { c: 0, t: 0 }
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
export default class ErrorBoundary extends Component<Props, State> {
|
| 41 |
+
state: State = { error: null }
|
| 42 |
+
|
| 43 |
+
static getDerivedStateFromError(error: Error) {
|
| 44 |
+
return { error }
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
componentDidCatch(error: Error) {
|
| 48 |
+
if (!isDepOptError(error.message)) return
|
| 49 |
+
// Vite dep optimization may serve React stubs during cold start.
|
| 50 |
+
// Auto-reload so the browser fetches the real modules once ready.
|
| 51 |
+
const prev = getReloadState()
|
| 52 |
+
// Reset counter if outside the rapid-reload window (stale from earlier visit)
|
| 53 |
+
const count = (Date.now() - prev.t > RELOAD_WINDOW_MS) ? 0 : prev.c
|
| 54 |
+
if (count < MAX_RELOADS) {
|
| 55 |
+
sessionStorage.setItem(RELOAD_KEY, JSON.stringify({ c: count + 1, t: Date.now() }))
|
| 56 |
+
setTimeout(() => location.reload(), 3000)
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
render() {
|
| 61 |
+
if (this.state.error) {
|
| 62 |
+
if (this.props.fallback) return this.props.fallback
|
| 63 |
+
|
| 64 |
+
// Show a friendlier message for dep optimization errors that will auto-reload
|
| 65 |
+
if (isDepOptError(this.state.error.message)) {
|
| 66 |
+
const { c, t } = getReloadState()
|
| 67 |
+
const fresh = (Date.now() - t <= RELOAD_WINDOW_MS)
|
| 68 |
+
if (!fresh || c < MAX_RELOADS) {
|
| 69 |
+
return (
|
| 70 |
+
<div style={{
|
| 71 |
+
padding: '24px',
|
| 72 |
+
margin: '16px',
|
| 73 |
+
borderRadius: '12px',
|
| 74 |
+
background: 'rgba(59,130,246,0.06)',
|
| 75 |
+
border: '1px solid rgba(59,130,246,0.15)',
|
| 76 |
+
color: '#3b82f6',
|
| 77 |
+
fontSize: '13px',
|
| 78 |
+
fontFamily: 'system-ui, sans-serif',
|
| 79 |
+
textAlign: 'center',
|
| 80 |
+
}}>
|
| 81 |
+
<p style={{ fontWeight: 600, marginBottom: '4px' }}>Loading dependencies...</p>
|
| 82 |
+
<p style={{ opacity: 0.7, fontSize: '12px' }}>Reloading automatically</p>
|
| 83 |
+
</div>
|
| 84 |
+
)
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
return (
|
| 89 |
+
<div style={{
|
| 90 |
+
padding: '24px',
|
| 91 |
+
margin: '16px',
|
| 92 |
+
borderRadius: '12px',
|
| 93 |
+
background: 'rgba(245,34,45,0.06)',
|
| 94 |
+
border: '1px solid rgba(245,34,45,0.15)',
|
| 95 |
+
color: '#c0392b',
|
| 96 |
+
fontSize: '13px',
|
| 97 |
+
fontFamily: 'monospace',
|
| 98 |
+
}}>
|
| 99 |
+
<p style={{ fontWeight: 600, marginBottom: '8px' }}>Component Error</p>
|
| 100 |
+
<p style={{ opacity: 0.8 }}>{this.state.error.message}</p>
|
| 101 |
+
</div>
|
| 102 |
+
)
|
| 103 |
+
}
|
| 104 |
+
return this.props.children
|
| 105 |
+
}
|
| 106 |
+
}
|
src/components/ui/accordion.tsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
|
| 30 |
+
className
|
| 31 |
+
)}
|
| 32 |
+
{...props}
|
| 33 |
+
>
|
| 34 |
+
{children}
|
| 35 |
+
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground 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 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 |
+
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
| 54 |
+
|
| 55 |
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
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 px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
| 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 }
|
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 }
|
src/components/ui/avatar.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const Avatar = React.forwardRef<
|
| 7 |
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
| 8 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
| 9 |
+
>(({ className, ...props }, ref) => (
|
| 10 |
+
<AvatarPrimitive.Root
|
| 11 |
+
ref={ref}
|
| 12 |
+
className={cn(
|
| 13 |
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
| 14 |
+
className
|
| 15 |
+
)}
|
| 16 |
+
{...props}
|
| 17 |
+
/>
|
| 18 |
+
))
|
| 19 |
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
| 20 |
+
|
| 21 |
+
const AvatarImage = React.forwardRef<
|
| 22 |
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
| 23 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
| 24 |
+
>(({ className, ...props }, ref) => (
|
| 25 |
+
<AvatarPrimitive.Image
|
| 26 |
+
ref={ref}
|
| 27 |
+
className={cn("aspect-square h-full w-full", className)}
|
| 28 |
+
{...props}
|
| 29 |
+
/>
|
| 30 |
+
))
|
| 31 |
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
| 32 |
+
|
| 33 |
+
const AvatarFallback = React.forwardRef<
|
| 34 |
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
| 35 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
| 36 |
+
>(({ className, ...props }, ref) => (
|
| 37 |
+
<AvatarPrimitive.Fallback
|
| 38 |
+
ref={ref}
|
| 39 |
+
className={cn(
|
| 40 |
+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
| 41 |
+
className
|
| 42 |
+
)}
|
| 43 |
+
{...props}
|
| 44 |
+
/>
|
| 45 |
+
))
|
| 46 |
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
| 47 |
+
|
| 48 |
+
export { Avatar, AvatarImage, AvatarFallback }
|
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-md 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 shadow 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 shadow 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 }
|
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 |
+
}
|
src/components/ui/button.tsx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default:
|
| 13 |
+
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
| 14 |
+
destructive:
|
| 15 |
+
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
| 16 |
+
outline:
|
| 17 |
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
| 18 |
+
secondary:
|
| 19 |
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
| 20 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
| 21 |
+
link: "text-primary underline-offset-4 hover:underline",
|
| 22 |
+
},
|
| 23 |
+
size: {
|
| 24 |
+
default: "h-9 px-4 py-2",
|
| 25 |
+
sm: "h-8 rounded-md px-3 text-xs",
|
| 26 |
+
lg: "h-10 rounded-md px-8",
|
| 27 |
+
icon: "h-9 w-9",
|
| 28 |
+
},
|
| 29 |
+
},
|
| 30 |
+
defaultVariants: {
|
| 31 |
+
variant: "default",
|
| 32 |
+
size: "default",
|
| 33 |
+
},
|
| 34 |
+
}
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
export interface ButtonProps
|
| 38 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
| 39 |
+
VariantProps<typeof buttonVariants> {
|
| 40 |
+
asChild?: boolean
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
| 44 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
| 45 |
+
const Comp = asChild ? Slot : "button"
|
| 46 |
+
return (
|
| 47 |
+
<Comp
|
| 48 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
| 49 |
+
ref={ref}
|
| 50 |
+
{...props}
|
| 51 |
+
/>
|
| 52 |
+
)
|
| 53 |
+
}
|
| 54 |
+
)
|
| 55 |
+
Button.displayName = "Button"
|
| 56 |
+
|
| 57 |
+
export { Button, buttonVariants }
|
src/components/ui/calendar.tsx
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import {
|
| 3 |
+
ChevronDownIcon,
|
| 4 |
+
ChevronLeftIcon,
|
| 5 |
+
ChevronRightIcon,
|
| 6 |
+
} from "lucide-react"
|
| 7 |
+
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
| 8 |
+
|
| 9 |
+
import { cn } from "@/lib/utils"
|
| 10 |
+
import { Button, buttonVariants } from "@/components/ui/button"
|
| 11 |
+
|
| 12 |
+
function Calendar({
|
| 13 |
+
className,
|
| 14 |
+
classNames,
|
| 15 |
+
showOutsideDays = true,
|
| 16 |
+
captionLayout = "label",
|
| 17 |
+
buttonVariant = "ghost",
|
| 18 |
+
formatters,
|
| 19 |
+
components,
|
| 20 |
+
...props
|
| 21 |
+
}: React.ComponentProps<typeof DayPicker> & {
|
| 22 |
+
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
| 23 |
+
}) {
|
| 24 |
+
const defaultClassNames = getDefaultClassNames()
|
| 25 |
+
|
| 26 |
+
return (
|
| 27 |
+
<DayPicker
|
| 28 |
+
showOutsideDays={showOutsideDays}
|
| 29 |
+
className={cn(
|
| 30 |
+
"bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
| 31 |
+
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
| 32 |
+
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
| 33 |
+
className
|
| 34 |
+
)}
|
| 35 |
+
captionLayout={captionLayout}
|
| 36 |
+
formatters={{
|
| 37 |
+
formatMonthDropdown: (date) =>
|
| 38 |
+
date.toLocaleString("default", { month: "short" }),
|
| 39 |
+
...formatters,
|
| 40 |
+
}}
|
| 41 |
+
classNames={{
|
| 42 |
+
root: cn("w-fit", defaultClassNames.root),
|
| 43 |
+
months: cn(
|
| 44 |
+
"relative flex flex-col gap-4 md:flex-row",
|
| 45 |
+
defaultClassNames.months
|
| 46 |
+
),
|
| 47 |
+
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
| 48 |
+
nav: cn(
|
| 49 |
+
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
| 50 |
+
defaultClassNames.nav
|
| 51 |
+
),
|
| 52 |
+
button_previous: cn(
|
| 53 |
+
buttonVariants({ variant: buttonVariant }),
|
| 54 |
+
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
| 55 |
+
defaultClassNames.button_previous
|
| 56 |
+
),
|
| 57 |
+
button_next: cn(
|
| 58 |
+
buttonVariants({ variant: buttonVariant }),
|
| 59 |
+
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
| 60 |
+
defaultClassNames.button_next
|
| 61 |
+
),
|
| 62 |
+
month_caption: cn(
|
| 63 |
+
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
|
| 64 |
+
defaultClassNames.month_caption
|
| 65 |
+
),
|
| 66 |
+
dropdowns: cn(
|
| 67 |
+
"flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
|
| 68 |
+
defaultClassNames.dropdowns
|
| 69 |
+
),
|
| 70 |
+
dropdown_root: cn(
|
| 71 |
+
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
|
| 72 |
+
defaultClassNames.dropdown_root
|
| 73 |
+
),
|
| 74 |
+
dropdown: cn(
|
| 75 |
+
"bg-popover absolute inset-0 opacity-0",
|
| 76 |
+
defaultClassNames.dropdown
|
| 77 |
+
),
|
| 78 |
+
caption_label: cn(
|
| 79 |
+
"select-none font-medium",
|
| 80 |
+
captionLayout === "label"
|
| 81 |
+
? "text-sm"
|
| 82 |
+
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
| 83 |
+
defaultClassNames.caption_label
|
| 84 |
+
),
|
| 85 |
+
table: "w-full border-collapse",
|
| 86 |
+
weekdays: cn("flex", defaultClassNames.weekdays),
|
| 87 |
+
weekday: cn(
|
| 88 |
+
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
| 89 |
+
defaultClassNames.weekday
|
| 90 |
+
),
|
| 91 |
+
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
| 92 |
+
week_number_header: cn(
|
| 93 |
+
"w-[--cell-size] select-none",
|
| 94 |
+
defaultClassNames.week_number_header
|
| 95 |
+
),
|
| 96 |
+
week_number: cn(
|
| 97 |
+
"text-muted-foreground select-none text-[0.8rem]",
|
| 98 |
+
defaultClassNames.week_number
|
| 99 |
+
),
|
| 100 |
+
day: cn(
|
| 101 |
+
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
|
| 102 |
+
defaultClassNames.day
|
| 103 |
+
),
|
| 104 |
+
range_start: cn(
|
| 105 |
+
"bg-accent rounded-l-md",
|
| 106 |
+
defaultClassNames.range_start
|
| 107 |
+
),
|
| 108 |
+
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
| 109 |
+
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
|
| 110 |
+
today: cn(
|
| 111 |
+
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
| 112 |
+
defaultClassNames.today
|
| 113 |
+
),
|
| 114 |
+
outside: cn(
|
| 115 |
+
"text-muted-foreground aria-selected:text-muted-foreground",
|
| 116 |
+
defaultClassNames.outside
|
| 117 |
+
),
|
| 118 |
+
disabled: cn(
|
| 119 |
+
"text-muted-foreground opacity-50",
|
| 120 |
+
defaultClassNames.disabled
|
| 121 |
+
),
|
| 122 |
+
hidden: cn("invisible", defaultClassNames.hidden),
|
| 123 |
+
...classNames,
|
| 124 |
+
}}
|
| 125 |
+
components={{
|
| 126 |
+
Root: ({ className, rootRef, ...props }) => {
|
| 127 |
+
return (
|
| 128 |
+
<div
|
| 129 |
+
data-slot="calendar"
|
| 130 |
+
ref={rootRef}
|
| 131 |
+
className={cn(className)}
|
| 132 |
+
{...props}
|
| 133 |
+
/>
|
| 134 |
+
)
|
| 135 |
+
},
|
| 136 |
+
Chevron: ({ className, orientation, ...props }) => {
|
| 137 |
+
if (orientation === "left") {
|
| 138 |
+
return (
|
| 139 |
+
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
| 140 |
+
)
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if (orientation === "right") {
|
| 144 |
+
return (
|
| 145 |
+
<ChevronRightIcon
|
| 146 |
+
className={cn("size-4", className)}
|
| 147 |
+
{...props}
|
| 148 |
+
/>
|
| 149 |
+
)
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
return (
|
| 153 |
+
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
| 154 |
+
)
|
| 155 |
+
},
|
| 156 |
+
DayButton: CalendarDayButton,
|
| 157 |
+
WeekNumber: ({ children, ...props }) => {
|
| 158 |
+
return (
|
| 159 |
+
<td {...props}>
|
| 160 |
+
<div className="flex size-[--cell-size] items-center justify-center text-center">
|
| 161 |
+
{children}
|
| 162 |
+
</div>
|
| 163 |
+
</td>
|
| 164 |
+
)
|
| 165 |
+
},
|
| 166 |
+
...components,
|
| 167 |
+
}}
|
| 168 |
+
{...props}
|
| 169 |
+
/>
|
| 170 |
+
)
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
function CalendarDayButton({
|
| 174 |
+
className,
|
| 175 |
+
day,
|
| 176 |
+
modifiers,
|
| 177 |
+
...props
|
| 178 |
+
}: React.ComponentProps<typeof DayButton>) {
|
| 179 |
+
const defaultClassNames = getDefaultClassNames()
|
| 180 |
+
|
| 181 |
+
const ref = React.useRef<HTMLButtonElement>(null)
|
| 182 |
+
React.useEffect(() => {
|
| 183 |
+
if (modifiers.focused) ref.current?.focus()
|
| 184 |
+
}, [modifiers.focused])
|
| 185 |
+
|
| 186 |
+
return (
|
| 187 |
+
<Button
|
| 188 |
+
ref={ref}
|
| 189 |
+
variant="ghost"
|
| 190 |
+
size="icon"
|
| 191 |
+
data-day={day.date.toLocaleDateString()}
|
| 192 |
+
data-selected-single={
|
| 193 |
+
modifiers.selected &&
|
| 194 |
+
!modifiers.range_start &&
|
| 195 |
+
!modifiers.range_end &&
|
| 196 |
+
!modifiers.range_middle
|
| 197 |
+
}
|
| 198 |
+
data-range-start={modifiers.range_start}
|
| 199 |
+
data-range-end={modifiers.range_end}
|
| 200 |
+
data-range-middle={modifiers.range_middle}
|
| 201 |
+
className={cn(
|
| 202 |
+
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
|
| 203 |
+
defaultClassNames.day,
|
| 204 |
+
className
|
| 205 |
+
)}
|
| 206 |
+
{...props}
|
| 207 |
+
/>
|
| 208 |
+
)
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
export { Calendar, CalendarDayButton }
|
src/components/ui/card.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-xl border bg-card text-card-foreground shadow",
|
| 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("font-semibold leading-none tracking-tight", className)}
|
| 39 |
+
{...props}
|
| 40 |
+
/>
|
| 41 |
+
))
|
| 42 |
+
CardTitle.displayName = "CardTitle"
|
| 43 |
+
|
| 44 |
+
const CardDescription = React.forwardRef<
|
| 45 |
+
HTMLDivElement,
|
| 46 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 47 |
+
>(({ className, ...props }, ref) => (
|
| 48 |
+
<div
|
| 49 |
+
ref={ref}
|
| 50 |
+
className={cn("text-sm text-muted-foreground", className)}
|
| 51 |
+
{...props}
|
| 52 |
+
/>
|
| 53 |
+
))
|
| 54 |
+
CardDescription.displayName = "CardDescription"
|
| 55 |
+
|
| 56 |
+
const CardContent = React.forwardRef<
|
| 57 |
+
HTMLDivElement,
|
| 58 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 59 |
+
>(({ className, ...props }, ref) => (
|
| 60 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
| 61 |
+
))
|
| 62 |
+
CardContent.displayName = "CardContent"
|
| 63 |
+
|
| 64 |
+
const CardFooter = React.forwardRef<
|
| 65 |
+
HTMLDivElement,
|
| 66 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 67 |
+
>(({ className, ...props }, ref) => (
|
| 68 |
+
<div
|
| 69 |
+
ref={ref}
|
| 70 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
| 71 |
+
{...props}
|
| 72 |
+
/>
|
| 73 |
+
))
|
| 74 |
+
CardFooter.displayName = "CardFooter"
|
| 75 |
+
|
| 76 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
src/components/ui/carousel.tsx
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import useEmblaCarousel, {
|
| 5 |
+
type UseEmblaCarouselType,
|
| 6 |
+
} from "embla-carousel-react"
|
| 7 |
+
import { ArrowLeft, ArrowRight } from "lucide-react"
|
| 8 |
+
|
| 9 |
+
import { cn } from "@/lib/utils"
|
| 10 |
+
import { Button } from "@/components/ui/button"
|
| 11 |
+
|
| 12 |
+
type CarouselApi = UseEmblaCarouselType[1]
|
| 13 |
+
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
| 14 |
+
type CarouselOptions = UseCarouselParameters[0]
|
| 15 |
+
type CarouselPlugin = UseCarouselParameters[1]
|
| 16 |
+
|
| 17 |
+
type CarouselProps = {
|
| 18 |
+
opts?: CarouselOptions
|
| 19 |
+
plugins?: CarouselPlugin
|
| 20 |
+
orientation?: "horizontal" | "vertical"
|
| 21 |
+
setApi?: (api: CarouselApi) => void
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
type CarouselContextProps = {
|
| 25 |
+
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
| 26 |
+
api: ReturnType<typeof useEmblaCarousel>[1]
|
| 27 |
+
scrollPrev: () => void
|
| 28 |
+
scrollNext: () => void
|
| 29 |
+
canScrollPrev: boolean
|
| 30 |
+
canScrollNext: boolean
|
| 31 |
+
} & CarouselProps
|
| 32 |
+
|
| 33 |
+
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
| 34 |
+
|
| 35 |
+
function useCarousel() {
|
| 36 |
+
const context = React.useContext(CarouselContext)
|
| 37 |
+
|
| 38 |
+
if (!context) {
|
| 39 |
+
throw new Error("useCarousel must be used within a <Carousel />")
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
return context
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
const Carousel = React.forwardRef<
|
| 46 |
+
HTMLDivElement,
|
| 47 |
+
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
| 48 |
+
>(
|
| 49 |
+
(
|
| 50 |
+
{
|
| 51 |
+
orientation = "horizontal",
|
| 52 |
+
opts,
|
| 53 |
+
setApi,
|
| 54 |
+
plugins,
|
| 55 |
+
className,
|
| 56 |
+
children,
|
| 57 |
+
...props
|
| 58 |
+
},
|
| 59 |
+
ref
|
| 60 |
+
) => {
|
| 61 |
+
const [carouselRef, api] = useEmblaCarousel(
|
| 62 |
+
{
|
| 63 |
+
...opts,
|
| 64 |
+
axis: orientation === "horizontal" ? "x" : "y",
|
| 65 |
+
},
|
| 66 |
+
plugins
|
| 67 |
+
)
|
| 68 |
+
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
| 69 |
+
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
| 70 |
+
|
| 71 |
+
const onSelect = React.useCallback((api: CarouselApi) => {
|
| 72 |
+
if (!api) {
|
| 73 |
+
return
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
setCanScrollPrev(api.canScrollPrev())
|
| 77 |
+
setCanScrollNext(api.canScrollNext())
|
| 78 |
+
}, [])
|
| 79 |
+
|
| 80 |
+
const scrollPrev = React.useCallback(() => {
|
| 81 |
+
api?.scrollPrev()
|
| 82 |
+
}, [api])
|
| 83 |
+
|
| 84 |
+
const scrollNext = React.useCallback(() => {
|
| 85 |
+
api?.scrollNext()
|
| 86 |
+
}, [api])
|
| 87 |
+
|
| 88 |
+
const handleKeyDown = React.useCallback(
|
| 89 |
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
| 90 |
+
if (event.key === "ArrowLeft") {
|
| 91 |
+
event.preventDefault()
|
| 92 |
+
scrollPrev()
|
| 93 |
+
} else if (event.key === "ArrowRight") {
|
| 94 |
+
event.preventDefault()
|
| 95 |
+
scrollNext()
|
| 96 |
+
}
|
| 97 |
+
},
|
| 98 |
+
[scrollPrev, scrollNext]
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
React.useEffect(() => {
|
| 102 |
+
if (!api || !setApi) {
|
| 103 |
+
return
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
setApi(api)
|
| 107 |
+
}, [api, setApi])
|
| 108 |
+
|
| 109 |
+
React.useEffect(() => {
|
| 110 |
+
if (!api) {
|
| 111 |
+
return
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
onSelect(api)
|
| 115 |
+
api.on("reInit", onSelect)
|
| 116 |
+
api.on("select", onSelect)
|
| 117 |
+
|
| 118 |
+
return () => {
|
| 119 |
+
api?.off("select", onSelect)
|
| 120 |
+
}
|
| 121 |
+
}, [api, onSelect])
|
| 122 |
+
|
| 123 |
+
return (
|
| 124 |
+
<CarouselContext.Provider
|
| 125 |
+
value={{
|
| 126 |
+
carouselRef,
|
| 127 |
+
api: api,
|
| 128 |
+
opts,
|
| 129 |
+
orientation:
|
| 130 |
+
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
| 131 |
+
scrollPrev,
|
| 132 |
+
scrollNext,
|
| 133 |
+
canScrollPrev,
|
| 134 |
+
canScrollNext,
|
| 135 |
+
}}
|
| 136 |
+
>
|
| 137 |
+
<div
|
| 138 |
+
ref={ref}
|
| 139 |
+
onKeyDownCapture={handleKeyDown}
|
| 140 |
+
className={cn("relative", className)}
|
| 141 |
+
role="region"
|
| 142 |
+
aria-roledescription="carousel"
|
| 143 |
+
{...props}
|
| 144 |
+
>
|
| 145 |
+
{children}
|
| 146 |
+
</div>
|
| 147 |
+
</CarouselContext.Provider>
|
| 148 |
+
)
|
| 149 |
+
}
|
| 150 |
+
)
|
| 151 |
+
Carousel.displayName = "Carousel"
|
| 152 |
+
|
| 153 |
+
const CarouselContent = React.forwardRef<
|
| 154 |
+
HTMLDivElement,
|
| 155 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 156 |
+
>(({ className, ...props }, ref) => {
|
| 157 |
+
const { carouselRef, orientation } = useCarousel()
|
| 158 |
+
|
| 159 |
+
return (
|
| 160 |
+
<div ref={carouselRef} className="overflow-hidden">
|
| 161 |
+
<div
|
| 162 |
+
ref={ref}
|
| 163 |
+
className={cn(
|
| 164 |
+
"flex",
|
| 165 |
+
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
| 166 |
+
className
|
| 167 |
+
)}
|
| 168 |
+
{...props}
|
| 169 |
+
/>
|
| 170 |
+
</div>
|
| 171 |
+
)
|
| 172 |
+
})
|
| 173 |
+
CarouselContent.displayName = "CarouselContent"
|
| 174 |
+
|
| 175 |
+
const CarouselItem = React.forwardRef<
|
| 176 |
+
HTMLDivElement,
|
| 177 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 178 |
+
>(({ className, ...props }, ref) => {
|
| 179 |
+
const { orientation } = useCarousel()
|
| 180 |
+
|
| 181 |
+
return (
|
| 182 |
+
<div
|
| 183 |
+
ref={ref}
|
| 184 |
+
role="group"
|
| 185 |
+
aria-roledescription="slide"
|
| 186 |
+
className={cn(
|
| 187 |
+
"min-w-0 shrink-0 grow-0 basis-full",
|
| 188 |
+
orientation === "horizontal" ? "pl-4" : "pt-4",
|
| 189 |
+
className
|
| 190 |
+
)}
|
| 191 |
+
{...props}
|
| 192 |
+
/>
|
| 193 |
+
)
|
| 194 |
+
})
|
| 195 |
+
CarouselItem.displayName = "CarouselItem"
|
| 196 |
+
|
| 197 |
+
const CarouselPrevious = React.forwardRef<
|
| 198 |
+
HTMLButtonElement,
|
| 199 |
+
React.ComponentProps<typeof Button>
|
| 200 |
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
| 201 |
+
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
| 202 |
+
|
| 203 |
+
return (
|
| 204 |
+
<Button
|
| 205 |
+
ref={ref}
|
| 206 |
+
variant={variant}
|
| 207 |
+
size={size}
|
| 208 |
+
className={cn(
|
| 209 |
+
"absolute h-8 w-8 rounded-full",
|
| 210 |
+
orientation === "horizontal"
|
| 211 |
+
? "-left-12 top-1/2 -translate-y-1/2"
|
| 212 |
+
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
| 213 |
+
className
|
| 214 |
+
)}
|
| 215 |
+
disabled={!canScrollPrev}
|
| 216 |
+
onClick={scrollPrev}
|
| 217 |
+
{...props}
|
| 218 |
+
>
|
| 219 |
+
<ArrowLeft className="h-4 w-4" />
|
| 220 |
+
<span className="sr-only">Previous slide</span>
|
| 221 |
+
</Button>
|
| 222 |
+
)
|
| 223 |
+
})
|
| 224 |
+
CarouselPrevious.displayName = "CarouselPrevious"
|
| 225 |
+
|
| 226 |
+
const CarouselNext = React.forwardRef<
|
| 227 |
+
HTMLButtonElement,
|
| 228 |
+
React.ComponentProps<typeof Button>
|
| 229 |
+
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
| 230 |
+
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
| 231 |
+
|
| 232 |
+
return (
|
| 233 |
+
<Button
|
| 234 |
+
ref={ref}
|
| 235 |
+
variant={variant}
|
| 236 |
+
size={size}
|
| 237 |
+
className={cn(
|
| 238 |
+
"absolute h-8 w-8 rounded-full",
|
| 239 |
+
orientation === "horizontal"
|
| 240 |
+
? "-right-12 top-1/2 -translate-y-1/2"
|
| 241 |
+
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
| 242 |
+
className
|
| 243 |
+
)}
|
| 244 |
+
disabled={!canScrollNext}
|
| 245 |
+
onClick={scrollNext}
|
| 246 |
+
{...props}
|
| 247 |
+
>
|
| 248 |
+
<ArrowRight className="h-4 w-4" />
|
| 249 |
+
<span className="sr-only">Next slide</span>
|
| 250 |
+
</Button>
|
| 251 |
+
)
|
| 252 |
+
})
|
| 253 |
+
CarouselNext.displayName = "CarouselNext"
|
| 254 |
+
|
| 255 |
+
export {
|
| 256 |
+
type CarouselApi,
|
| 257 |
+
Carousel,
|
| 258 |
+
CarouselContent,
|
| 259 |
+
CarouselItem,
|
| 260 |
+
CarouselPrevious,
|
| 261 |
+
CarouselNext,
|
| 262 |
+
}
|
src/components/ui/checkbox.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
| 5 |
+
import { Check } from "lucide-react"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const Checkbox = React.forwardRef<
|
| 10 |
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
| 11 |
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
| 12 |
+
>(({ className, ...props }, ref) => (
|
| 13 |
+
<CheckboxPrimitive.Root
|
| 14 |
+
ref={ref}
|
| 15 |
+
className={cn(
|
| 16 |
+
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
| 17 |
+
className
|
| 18 |
+
)}
|
| 19 |
+
{...props}
|
| 20 |
+
>
|
| 21 |
+
<CheckboxPrimitive.Indicator
|
| 22 |
+
className={cn("flex items-center justify-center text-current")}
|
| 23 |
+
>
|
| 24 |
+
<Check className="h-4 w-4" />
|
| 25 |
+
</CheckboxPrimitive.Indicator>
|
| 26 |
+
</CheckboxPrimitive.Root>
|
| 27 |
+
))
|
| 28 |
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
| 29 |
+
|
| 30 |
+
export { Checkbox }
|
src/components/ui/collapsible.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
| 2 |
+
|
| 3 |
+
const Collapsible = CollapsiblePrimitive.Root
|
| 4 |
+
|
| 5 |
+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
| 6 |
+
|
| 7 |
+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
| 8 |
+
|
| 9 |
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
src/components/ui/command.tsx
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import { type DialogProps } from "@radix-ui/react-dialog"
|
| 5 |
+
import { Command as CommandPrimitive } from "cmdk"
|
| 6 |
+
import { Search } from "lucide-react"
|
| 7 |
+
|
| 8 |
+
import { cn } from "@/lib/utils"
|
| 9 |
+
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
| 10 |
+
|
| 11 |
+
const Command = React.forwardRef<
|
| 12 |
+
React.ElementRef<typeof CommandPrimitive>,
|
| 13 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
| 14 |
+
>(({ className, ...props }, ref) => (
|
| 15 |
+
<CommandPrimitive
|
| 16 |
+
ref={ref}
|
| 17 |
+
className={cn(
|
| 18 |
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
| 19 |
+
className
|
| 20 |
+
)}
|
| 21 |
+
{...props}
|
| 22 |
+
/>
|
| 23 |
+
))
|
| 24 |
+
Command.displayName = CommandPrimitive.displayName
|
| 25 |
+
|
| 26 |
+
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
| 27 |
+
return (
|
| 28 |
+
<Dialog {...props}>
|
| 29 |
+
<DialogContent className="overflow-hidden p-0">
|
| 30 |
+
<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">
|
| 31 |
+
{children}
|
| 32 |
+
</Command>
|
| 33 |
+
</DialogContent>
|
| 34 |
+
</Dialog>
|
| 35 |
+
)
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
const CommandInput = React.forwardRef<
|
| 39 |
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
| 40 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
| 41 |
+
>(({ className, ...props }, ref) => (
|
| 42 |
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
| 43 |
+
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
| 44 |
+
<CommandPrimitive.Input
|
| 45 |
+
ref={ref}
|
| 46 |
+
className={cn(
|
| 47 |
+
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
| 48 |
+
className
|
| 49 |
+
)}
|
| 50 |
+
{...props}
|
| 51 |
+
/>
|
| 52 |
+
</div>
|
| 53 |
+
))
|
| 54 |
+
|
| 55 |
+
CommandInput.displayName = CommandPrimitive.Input.displayName
|
| 56 |
+
|
| 57 |
+
const CommandList = React.forwardRef<
|
| 58 |
+
React.ElementRef<typeof CommandPrimitive.List>,
|
| 59 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
| 60 |
+
>(({ className, ...props }, ref) => (
|
| 61 |
+
<CommandPrimitive.List
|
| 62 |
+
ref={ref}
|
| 63 |
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
| 64 |
+
{...props}
|
| 65 |
+
/>
|
| 66 |
+
))
|
| 67 |
+
|
| 68 |
+
CommandList.displayName = CommandPrimitive.List.displayName
|
| 69 |
+
|
| 70 |
+
const CommandEmpty = React.forwardRef<
|
| 71 |
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
| 72 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
| 73 |
+
>((props, ref) => (
|
| 74 |
+
<CommandPrimitive.Empty
|
| 75 |
+
ref={ref}
|
| 76 |
+
className="py-6 text-center text-sm"
|
| 77 |
+
{...props}
|
| 78 |
+
/>
|
| 79 |
+
))
|
| 80 |
+
|
| 81 |
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
| 82 |
+
|
| 83 |
+
const CommandGroup = React.forwardRef<
|
| 84 |
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
| 85 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
| 86 |
+
>(({ className, ...props }, ref) => (
|
| 87 |
+
<CommandPrimitive.Group
|
| 88 |
+
ref={ref}
|
| 89 |
+
className={cn(
|
| 90 |
+
"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",
|
| 91 |
+
className
|
| 92 |
+
)}
|
| 93 |
+
{...props}
|
| 94 |
+
/>
|
| 95 |
+
))
|
| 96 |
+
|
| 97 |
+
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
| 98 |
+
|
| 99 |
+
const CommandSeparator = React.forwardRef<
|
| 100 |
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
| 101 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
| 102 |
+
>(({ className, ...props }, ref) => (
|
| 103 |
+
<CommandPrimitive.Separator
|
| 104 |
+
ref={ref}
|
| 105 |
+
className={cn("-mx-1 h-px bg-border", className)}
|
| 106 |
+
{...props}
|
| 107 |
+
/>
|
| 108 |
+
))
|
| 109 |
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
| 110 |
+
|
| 111 |
+
const CommandItem = React.forwardRef<
|
| 112 |
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
| 113 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
| 114 |
+
>(({ className, ...props }, ref) => (
|
| 115 |
+
<CommandPrimitive.Item
|
| 116 |
+
ref={ref}
|
| 117 |
+
className={cn(
|
| 118 |
+
"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",
|
| 119 |
+
className
|
| 120 |
+
)}
|
| 121 |
+
{...props}
|
| 122 |
+
/>
|
| 123 |
+
))
|
| 124 |
+
|
| 125 |
+
CommandItem.displayName = CommandPrimitive.Item.displayName
|
| 126 |
+
|
| 127 |
+
const CommandShortcut = ({
|
| 128 |
+
className,
|
| 129 |
+
...props
|
| 130 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
| 131 |
+
return (
|
| 132 |
+
<span
|
| 133 |
+
className={cn(
|
| 134 |
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
| 135 |
+
className
|
| 136 |
+
)}
|
| 137 |
+
{...props}
|
| 138 |
+
/>
|
| 139 |
+
)
|
| 140 |
+
}
|
| 141 |
+
CommandShortcut.displayName = "CommandShortcut"
|
| 142 |
+
|
| 143 |
+
export {
|
| 144 |
+
Command,
|
| 145 |
+
CommandDialog,
|
| 146 |
+
CommandInput,
|
| 147 |
+
CommandList,
|
| 148 |
+
CommandEmpty,
|
| 149 |
+
CommandGroup,
|
| 150 |
+
CommandItem,
|
| 151 |
+
CommandShortcut,
|
| 152 |
+
CommandSeparator,
|
| 153 |
+
}
|
src/components/ui/context-menu.tsx
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
| 5 |
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const ContextMenu = ContextMenuPrimitive.Root
|
| 10 |
+
|
| 11 |
+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
| 12 |
+
|
| 13 |
+
const ContextMenuGroup = ContextMenuPrimitive.Group
|
| 14 |
+
|
| 15 |
+
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
| 16 |
+
|
| 17 |
+
const ContextMenuSub = ContextMenuPrimitive.Sub
|
| 18 |
+
|
| 19 |
+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
| 20 |
+
|
| 21 |
+
const ContextMenuSubTrigger = React.forwardRef<
|
| 22 |
+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
| 23 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
| 24 |
+
inset?: boolean
|
| 25 |
+
}
|
| 26 |
+
>(({ className, inset, children, ...props }, ref) => (
|
| 27 |
+
<ContextMenuPrimitive.SubTrigger
|
| 28 |
+
ref={ref}
|
| 29 |
+
className={cn(
|
| 30 |
+
"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",
|
| 31 |
+
inset && "pl-8",
|
| 32 |
+
className
|
| 33 |
+
)}
|
| 34 |
+
{...props}
|
| 35 |
+
>
|
| 36 |
+
{children}
|
| 37 |
+
<ChevronRight className="ml-auto h-4 w-4" />
|
| 38 |
+
</ContextMenuPrimitive.SubTrigger>
|
| 39 |
+
))
|
| 40 |
+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
| 41 |
+
|
| 42 |
+
const ContextMenuSubContent = React.forwardRef<
|
| 43 |
+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
| 44 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
| 45 |
+
>(({ className, ...props }, ref) => (
|
| 46 |
+
<ContextMenuPrimitive.SubContent
|
| 47 |
+
ref={ref}
|
| 48 |
+
className={cn(
|
| 49 |
+
"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-context-menu-content-transform-origin]",
|
| 50 |
+
className
|
| 51 |
+
)}
|
| 52 |
+
{...props}
|
| 53 |
+
/>
|
| 54 |
+
))
|
| 55 |
+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
| 56 |
+
|
| 57 |
+
const ContextMenuContent = React.forwardRef<
|
| 58 |
+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
| 59 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
| 60 |
+
>(({ className, ...props }, ref) => (
|
| 61 |
+
<ContextMenuPrimitive.Portal>
|
| 62 |
+
<ContextMenuPrimitive.Content
|
| 63 |
+
ref={ref}
|
| 64 |
+
className={cn(
|
| 65 |
+
"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 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]",
|
| 66 |
+
className
|
| 67 |
+
)}
|
| 68 |
+
{...props}
|
| 69 |
+
/>
|
| 70 |
+
</ContextMenuPrimitive.Portal>
|
| 71 |
+
))
|
| 72 |
+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
| 73 |
+
|
| 74 |
+
const ContextMenuItem = React.forwardRef<
|
| 75 |
+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
| 76 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
| 77 |
+
inset?: boolean
|
| 78 |
+
}
|
| 79 |
+
>(({ className, inset, ...props }, ref) => (
|
| 80 |
+
<ContextMenuPrimitive.Item
|
| 81 |
+
ref={ref}
|
| 82 |
+
className={cn(
|
| 83 |
+
"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",
|
| 84 |
+
inset && "pl-8",
|
| 85 |
+
className
|
| 86 |
+
)}
|
| 87 |
+
{...props}
|
| 88 |
+
/>
|
| 89 |
+
))
|
| 90 |
+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
| 91 |
+
|
| 92 |
+
const ContextMenuCheckboxItem = React.forwardRef<
|
| 93 |
+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
| 94 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
| 95 |
+
>(({ className, children, checked, ...props }, ref) => (
|
| 96 |
+
<ContextMenuPrimitive.CheckboxItem
|
| 97 |
+
ref={ref}
|
| 98 |
+
className={cn(
|
| 99 |
+
"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",
|
| 100 |
+
className
|
| 101 |
+
)}
|
| 102 |
+
checked={checked}
|
| 103 |
+
{...props}
|
| 104 |
+
>
|
| 105 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 106 |
+
<ContextMenuPrimitive.ItemIndicator>
|
| 107 |
+
<Check className="h-4 w-4" />
|
| 108 |
+
</ContextMenuPrimitive.ItemIndicator>
|
| 109 |
+
</span>
|
| 110 |
+
{children}
|
| 111 |
+
</ContextMenuPrimitive.CheckboxItem>
|
| 112 |
+
))
|
| 113 |
+
ContextMenuCheckboxItem.displayName =
|
| 114 |
+
ContextMenuPrimitive.CheckboxItem.displayName
|
| 115 |
+
|
| 116 |
+
const ContextMenuRadioItem = React.forwardRef<
|
| 117 |
+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
| 118 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
| 119 |
+
>(({ className, children, ...props }, ref) => (
|
| 120 |
+
<ContextMenuPrimitive.RadioItem
|
| 121 |
+
ref={ref}
|
| 122 |
+
className={cn(
|
| 123 |
+
"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",
|
| 124 |
+
className
|
| 125 |
+
)}
|
| 126 |
+
{...props}
|
| 127 |
+
>
|
| 128 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 129 |
+
<ContextMenuPrimitive.ItemIndicator>
|
| 130 |
+
<Circle className="h-4 w-4 fill-current" />
|
| 131 |
+
</ContextMenuPrimitive.ItemIndicator>
|
| 132 |
+
</span>
|
| 133 |
+
{children}
|
| 134 |
+
</ContextMenuPrimitive.RadioItem>
|
| 135 |
+
))
|
| 136 |
+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
| 137 |
+
|
| 138 |
+
const ContextMenuLabel = React.forwardRef<
|
| 139 |
+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
| 140 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
| 141 |
+
inset?: boolean
|
| 142 |
+
}
|
| 143 |
+
>(({ className, inset, ...props }, ref) => (
|
| 144 |
+
<ContextMenuPrimitive.Label
|
| 145 |
+
ref={ref}
|
| 146 |
+
className={cn(
|
| 147 |
+
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
| 148 |
+
inset && "pl-8",
|
| 149 |
+
className
|
| 150 |
+
)}
|
| 151 |
+
{...props}
|
| 152 |
+
/>
|
| 153 |
+
))
|
| 154 |
+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
| 155 |
+
|
| 156 |
+
const ContextMenuSeparator = React.forwardRef<
|
| 157 |
+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
| 158 |
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
| 159 |
+
>(({ className, ...props }, ref) => (
|
| 160 |
+
<ContextMenuPrimitive.Separator
|
| 161 |
+
ref={ref}
|
| 162 |
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
| 163 |
+
{...props}
|
| 164 |
+
/>
|
| 165 |
+
))
|
| 166 |
+
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
| 167 |
+
|
| 168 |
+
const ContextMenuShortcut = ({
|
| 169 |
+
className,
|
| 170 |
+
...props
|
| 171 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
| 172 |
+
return (
|
| 173 |
+
<span
|
| 174 |
+
className={cn(
|
| 175 |
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
| 176 |
+
className
|
| 177 |
+
)}
|
| 178 |
+
{...props}
|
| 179 |
+
/>
|
| 180 |
+
)
|
| 181 |
+
}
|
| 182 |
+
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
| 183 |
+
|
| 184 |
+
export {
|
| 185 |
+
ContextMenu,
|
| 186 |
+
ContextMenuTrigger,
|
| 187 |
+
ContextMenuContent,
|
| 188 |
+
ContextMenuItem,
|
| 189 |
+
ContextMenuCheckboxItem,
|
| 190 |
+
ContextMenuRadioItem,
|
| 191 |
+
ContextMenuLabel,
|
| 192 |
+
ContextMenuSeparator,
|
| 193 |
+
ContextMenuShortcut,
|
| 194 |
+
ContextMenuGroup,
|
| 195 |
+
ContextMenuPortal,
|
| 196 |
+
ContextMenuSub,
|
| 197 |
+
ContextMenuSubContent,
|
| 198 |
+
ContextMenuSubTrigger,
|
| 199 |
+
ContextMenuRadioGroup,
|
| 200 |
+
}
|
src/components/ui/dialog.tsx
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
| 3 |
+
import { X } from "lucide-react"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const Dialog = DialogPrimitive.Root
|
| 8 |
+
|
| 9 |
+
const DialogTrigger = DialogPrimitive.Trigger
|
| 10 |
+
|
| 11 |
+
const DialogPortal = DialogPrimitive.Portal
|
| 12 |
+
|
| 13 |
+
const DialogClose = DialogPrimitive.Close
|
| 14 |
+
|
| 15 |
+
const DialogOverlay = React.forwardRef<
|
| 16 |
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
| 17 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
| 18 |
+
>(({ className, ...props }, ref) => (
|
| 19 |
+
<DialogPrimitive.Overlay
|
| 20 |
+
ref={ref}
|
| 21 |
+
className={cn(
|
| 22 |
+
"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",
|
| 23 |
+
className
|
| 24 |
+
)}
|
| 25 |
+
{...props}
|
| 26 |
+
/>
|
| 27 |
+
))
|
| 28 |
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
| 29 |
+
|
| 30 |
+
const DialogContent = React.forwardRef<
|
| 31 |
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
| 32 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
| 33 |
+
>(({ className, children, ...props }, ref) => (
|
| 34 |
+
<DialogPortal>
|
| 35 |
+
<DialogOverlay />
|
| 36 |
+
<DialogPrimitive.Content
|
| 37 |
+
ref={ref}
|
| 38 |
+
className={cn(
|
| 39 |
+
"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",
|
| 40 |
+
className
|
| 41 |
+
)}
|
| 42 |
+
{...props}
|
| 43 |
+
>
|
| 44 |
+
{children}
|
| 45 |
+
<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">
|
| 46 |
+
<X className="h-4 w-4" />
|
| 47 |
+
<span className="sr-only">Close</span>
|
| 48 |
+
</DialogPrimitive.Close>
|
| 49 |
+
</DialogPrimitive.Content>
|
| 50 |
+
</DialogPortal>
|
| 51 |
+
))
|
| 52 |
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
| 53 |
+
|
| 54 |
+
const DialogHeader = ({
|
| 55 |
+
className,
|
| 56 |
+
...props
|
| 57 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
| 58 |
+
<div
|
| 59 |
+
className={cn(
|
| 60 |
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
| 61 |
+
className
|
| 62 |
+
)}
|
| 63 |
+
{...props}
|
| 64 |
+
/>
|
| 65 |
+
)
|
| 66 |
+
DialogHeader.displayName = "DialogHeader"
|
| 67 |
+
|
| 68 |
+
const DialogFooter = ({
|
| 69 |
+
className,
|
| 70 |
+
...props
|
| 71 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
| 72 |
+
<div
|
| 73 |
+
className={cn(
|
| 74 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
| 75 |
+
className
|
| 76 |
+
)}
|
| 77 |
+
{...props}
|
| 78 |
+
/>
|
| 79 |
+
)
|
| 80 |
+
DialogFooter.displayName = "DialogFooter"
|
| 81 |
+
|
| 82 |
+
const DialogTitle = React.forwardRef<
|
| 83 |
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
| 84 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
| 85 |
+
>(({ className, ...props }, ref) => (
|
| 86 |
+
<DialogPrimitive.Title
|
| 87 |
+
ref={ref}
|
| 88 |
+
className={cn(
|
| 89 |
+
"text-lg font-semibold leading-none tracking-tight",
|
| 90 |
+
className
|
| 91 |
+
)}
|
| 92 |
+
{...props}
|
| 93 |
+
/>
|
| 94 |
+
))
|
| 95 |
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
| 96 |
+
|
| 97 |
+
const DialogDescription = React.forwardRef<
|
| 98 |
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
| 99 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
| 100 |
+
>(({ className, ...props }, ref) => (
|
| 101 |
+
<DialogPrimitive.Description
|
| 102 |
+
ref={ref}
|
| 103 |
+
className={cn("text-sm text-muted-foreground", className)}
|
| 104 |
+
{...props}
|
| 105 |
+
/>
|
| 106 |
+
))
|
| 107 |
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
| 108 |
+
|
| 109 |
+
export {
|
| 110 |
+
Dialog,
|
| 111 |
+
DialogPortal,
|
| 112 |
+
DialogOverlay,
|
| 113 |
+
DialogTrigger,
|
| 114 |
+
DialogClose,
|
| 115 |
+
DialogContent,
|
| 116 |
+
DialogHeader,
|
| 117 |
+
DialogFooter,
|
| 118 |
+
DialogTitle,
|
| 119 |
+
DialogDescription,
|
| 120 |
+
}
|
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 |
+
}
|
src/components/ui/dropdown-menu.tsx
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
| 5 |
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const DropdownMenu = DropdownMenuPrimitive.Root
|
| 10 |
+
|
| 11 |
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
| 12 |
+
|
| 13 |
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
| 14 |
+
|
| 15 |
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
| 16 |
+
|
| 17 |
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
| 18 |
+
|
| 19 |
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
| 20 |
+
|
| 21 |
+
const DropdownMenuSubTrigger = React.forwardRef<
|
| 22 |
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
| 23 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
| 24 |
+
inset?: boolean
|
| 25 |
+
}
|
| 26 |
+
>(({ className, inset, children, ...props }, ref) => (
|
| 27 |
+
<DropdownMenuPrimitive.SubTrigger
|
| 28 |
+
ref={ref}
|
| 29 |
+
className={cn(
|
| 30 |
+
"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",
|
| 31 |
+
inset && "pl-8",
|
| 32 |
+
className
|
| 33 |
+
)}
|
| 34 |
+
{...props}
|
| 35 |
+
>
|
| 36 |
+
{children}
|
| 37 |
+
<ChevronRight className="ml-auto" />
|
| 38 |
+
</DropdownMenuPrimitive.SubTrigger>
|
| 39 |
+
))
|
| 40 |
+
DropdownMenuSubTrigger.displayName =
|
| 41 |
+
DropdownMenuPrimitive.SubTrigger.displayName
|
| 42 |
+
|
| 43 |
+
const DropdownMenuSubContent = React.forwardRef<
|
| 44 |
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
| 45 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
| 46 |
+
>(({ className, ...props }, ref) => (
|
| 47 |
+
<DropdownMenuPrimitive.SubContent
|
| 48 |
+
ref={ref}
|
| 49 |
+
className={cn(
|
| 50 |
+
"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]",
|
| 51 |
+
className
|
| 52 |
+
)}
|
| 53 |
+
{...props}
|
| 54 |
+
/>
|
| 55 |
+
))
|
| 56 |
+
DropdownMenuSubContent.displayName =
|
| 57 |
+
DropdownMenuPrimitive.SubContent.displayName
|
| 58 |
+
|
| 59 |
+
const DropdownMenuContent = React.forwardRef<
|
| 60 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
| 61 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
| 62 |
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
| 63 |
+
<DropdownMenuPrimitive.Portal>
|
| 64 |
+
<DropdownMenuPrimitive.Content
|
| 65 |
+
ref={ref}
|
| 66 |
+
sideOffset={sideOffset}
|
| 67 |
+
className={cn(
|
| 68 |
+
"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",
|
| 69 |
+
"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]",
|
| 70 |
+
className
|
| 71 |
+
)}
|
| 72 |
+
{...props}
|
| 73 |
+
/>
|
| 74 |
+
</DropdownMenuPrimitive.Portal>
|
| 75 |
+
))
|
| 76 |
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
| 77 |
+
|
| 78 |
+
const DropdownMenuItem = React.forwardRef<
|
| 79 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
| 80 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
| 81 |
+
inset?: boolean
|
| 82 |
+
}
|
| 83 |
+
>(({ className, inset, ...props }, ref) => (
|
| 84 |
+
<DropdownMenuPrimitive.Item
|
| 85 |
+
ref={ref}
|
| 86 |
+
className={cn(
|
| 87 |
+
"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]:size-4 [&>svg]:shrink-0",
|
| 88 |
+
inset && "pl-8",
|
| 89 |
+
className
|
| 90 |
+
)}
|
| 91 |
+
{...props}
|
| 92 |
+
/>
|
| 93 |
+
))
|
| 94 |
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
| 95 |
+
|
| 96 |
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
| 97 |
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
| 98 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
| 99 |
+
>(({ className, children, checked, ...props }, ref) => (
|
| 100 |
+
<DropdownMenuPrimitive.CheckboxItem
|
| 101 |
+
ref={ref}
|
| 102 |
+
className={cn(
|
| 103 |
+
"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",
|
| 104 |
+
className
|
| 105 |
+
)}
|
| 106 |
+
checked={checked}
|
| 107 |
+
{...props}
|
| 108 |
+
>
|
| 109 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 110 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
| 111 |
+
<Check className="h-4 w-4" />
|
| 112 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
| 113 |
+
</span>
|
| 114 |
+
{children}
|
| 115 |
+
</DropdownMenuPrimitive.CheckboxItem>
|
| 116 |
+
))
|
| 117 |
+
DropdownMenuCheckboxItem.displayName =
|
| 118 |
+
DropdownMenuPrimitive.CheckboxItem.displayName
|
| 119 |
+
|
| 120 |
+
const DropdownMenuRadioItem = React.forwardRef<
|
| 121 |
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
| 122 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
| 123 |
+
>(({ className, children, ...props }, ref) => (
|
| 124 |
+
<DropdownMenuPrimitive.RadioItem
|
| 125 |
+
ref={ref}
|
| 126 |
+
className={cn(
|
| 127 |
+
"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",
|
| 128 |
+
className
|
| 129 |
+
)}
|
| 130 |
+
{...props}
|
| 131 |
+
>
|
| 132 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 133 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
| 134 |
+
<Circle className="h-2 w-2 fill-current" />
|
| 135 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
| 136 |
+
</span>
|
| 137 |
+
{children}
|
| 138 |
+
</DropdownMenuPrimitive.RadioItem>
|
| 139 |
+
))
|
| 140 |
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
| 141 |
+
|
| 142 |
+
const DropdownMenuLabel = React.forwardRef<
|
| 143 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
| 144 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
| 145 |
+
inset?: boolean
|
| 146 |
+
}
|
| 147 |
+
>(({ className, inset, ...props }, ref) => (
|
| 148 |
+
<DropdownMenuPrimitive.Label
|
| 149 |
+
ref={ref}
|
| 150 |
+
className={cn(
|
| 151 |
+
"px-2 py-1.5 text-sm font-semibold",
|
| 152 |
+
inset && "pl-8",
|
| 153 |
+
className
|
| 154 |
+
)}
|
| 155 |
+
{...props}
|
| 156 |
+
/>
|
| 157 |
+
))
|
| 158 |
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
| 159 |
+
|
| 160 |
+
const DropdownMenuSeparator = React.forwardRef<
|
| 161 |
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
| 162 |
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
| 163 |
+
>(({ className, ...props }, ref) => (
|
| 164 |
+
<DropdownMenuPrimitive.Separator
|
| 165 |
+
ref={ref}
|
| 166 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
| 167 |
+
{...props}
|
| 168 |
+
/>
|
| 169 |
+
))
|
| 170 |
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
| 171 |
+
|
| 172 |
+
const DropdownMenuShortcut = ({
|
| 173 |
+
className,
|
| 174 |
+
...props
|
| 175 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
| 176 |
+
return (
|
| 177 |
+
<span
|
| 178 |
+
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
| 179 |
+
{...props}
|
| 180 |
+
/>
|
| 181 |
+
)
|
| 182 |
+
}
|
| 183 |
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
| 184 |
+
|
| 185 |
+
export {
|
| 186 |
+
DropdownMenu,
|
| 187 |
+
DropdownMenuTrigger,
|
| 188 |
+
DropdownMenuContent,
|
| 189 |
+
DropdownMenuItem,
|
| 190 |
+
DropdownMenuCheckboxItem,
|
| 191 |
+
DropdownMenuRadioItem,
|
| 192 |
+
DropdownMenuLabel,
|
| 193 |
+
DropdownMenuSeparator,
|
| 194 |
+
DropdownMenuShortcut,
|
| 195 |
+
DropdownMenuGroup,
|
| 196 |
+
DropdownMenuPortal,
|
| 197 |
+
DropdownMenuSub,
|
| 198 |
+
DropdownMenuSubContent,
|
| 199 |
+
DropdownMenuSubTrigger,
|
| 200 |
+
DropdownMenuRadioGroup,
|
| 201 |
+
}
|
src/components/ui/form.tsx
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
| 3 |
+
import { Slot } from "@radix-ui/react-slot"
|
| 4 |
+
import {
|
| 5 |
+
Controller,
|
| 6 |
+
FormProvider,
|
| 7 |
+
useFormContext,
|
| 8 |
+
type ControllerProps,
|
| 9 |
+
type FieldPath,
|
| 10 |
+
type FieldValues,
|
| 11 |
+
} from "react-hook-form"
|
| 12 |
+
|
| 13 |
+
import { cn } from "@/lib/utils"
|
| 14 |
+
import { Label } from "@/components/ui/label"
|
| 15 |
+
|
| 16 |
+
const Form = FormProvider
|
| 17 |
+
|
| 18 |
+
type FormFieldContextValue<
|
| 19 |
+
TFieldValues extends FieldValues = FieldValues,
|
| 20 |
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
| 21 |
+
> = {
|
| 22 |
+
name: TName
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
| 26 |
+
{} as FormFieldContextValue
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
const FormField = <
|
| 30 |
+
TFieldValues extends FieldValues = FieldValues,
|
| 31 |
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
| 32 |
+
>({
|
| 33 |
+
...props
|
| 34 |
+
}: ControllerProps<TFieldValues, TName>) => {
|
| 35 |
+
return (
|
| 36 |
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
| 37 |
+
<Controller {...props} />
|
| 38 |
+
</FormFieldContext.Provider>
|
| 39 |
+
)
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
const useFormField = () => {
|
| 43 |
+
const fieldContext = React.useContext(FormFieldContext)
|
| 44 |
+
const itemContext = React.useContext(FormItemContext)
|
| 45 |
+
const { getFieldState, formState } = useFormContext()
|
| 46 |
+
|
| 47 |
+
const fieldState = getFieldState(fieldContext.name, formState)
|
| 48 |
+
|
| 49 |
+
if (!fieldContext) {
|
| 50 |
+
throw new Error("useFormField should be used within <FormField>")
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
const { id } = itemContext
|
| 54 |
+
|
| 55 |
+
return {
|
| 56 |
+
id,
|
| 57 |
+
name: fieldContext.name,
|
| 58 |
+
formItemId: `${id}-form-item`,
|
| 59 |
+
formDescriptionId: `${id}-form-item-description`,
|
| 60 |
+
formMessageId: `${id}-form-item-message`,
|
| 61 |
+
...fieldState,
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
type FormItemContextValue = {
|
| 66 |
+
id: string
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
const FormItemContext = React.createContext<FormItemContextValue>(
|
| 70 |
+
{} as FormItemContextValue
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
const FormItem = React.forwardRef<
|
| 74 |
+
HTMLDivElement,
|
| 75 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 76 |
+
>(({ className, ...props }, ref) => {
|
| 77 |
+
const id = React.useId()
|
| 78 |
+
|
| 79 |
+
return (
|
| 80 |
+
<FormItemContext.Provider value={{ id }}>
|
| 81 |
+
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
| 82 |
+
</FormItemContext.Provider>
|
| 83 |
+
)
|
| 84 |
+
})
|
| 85 |
+
FormItem.displayName = "FormItem"
|
| 86 |
+
|
| 87 |
+
const FormLabel = React.forwardRef<
|
| 88 |
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
| 89 |
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
| 90 |
+
>(({ className, ...props }, ref) => {
|
| 91 |
+
const { error, formItemId } = useFormField()
|
| 92 |
+
|
| 93 |
+
return (
|
| 94 |
+
<Label
|
| 95 |
+
ref={ref}
|
| 96 |
+
className={cn(error && "text-destructive", className)}
|
| 97 |
+
htmlFor={formItemId}
|
| 98 |
+
{...props}
|
| 99 |
+
/>
|
| 100 |
+
)
|
| 101 |
+
})
|
| 102 |
+
FormLabel.displayName = "FormLabel"
|
| 103 |
+
|
| 104 |
+
const FormControl = React.forwardRef<
|
| 105 |
+
React.ElementRef<typeof Slot>,
|
| 106 |
+
React.ComponentPropsWithoutRef<typeof Slot>
|
| 107 |
+
>(({ ...props }, ref) => {
|
| 108 |
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
| 109 |
+
|
| 110 |
+
return (
|
| 111 |
+
<Slot
|
| 112 |
+
ref={ref}
|
| 113 |
+
id={formItemId}
|
| 114 |
+
aria-describedby={
|
| 115 |
+
!error
|
| 116 |
+
? `${formDescriptionId}`
|
| 117 |
+
: `${formDescriptionId} ${formMessageId}`
|
| 118 |
+
}
|
| 119 |
+
aria-invalid={!!error}
|
| 120 |
+
{...props}
|
| 121 |
+
/>
|
| 122 |
+
)
|
| 123 |
+
})
|
| 124 |
+
FormControl.displayName = "FormControl"
|
| 125 |
+
|
| 126 |
+
const FormDescription = React.forwardRef<
|
| 127 |
+
HTMLParagraphElement,
|
| 128 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
| 129 |
+
>(({ className, ...props }, ref) => {
|
| 130 |
+
const { formDescriptionId } = useFormField()
|
| 131 |
+
|
| 132 |
+
return (
|
| 133 |
+
<p
|
| 134 |
+
ref={ref}
|
| 135 |
+
id={formDescriptionId}
|
| 136 |
+
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
| 137 |
+
{...props}
|
| 138 |
+
/>
|
| 139 |
+
)
|
| 140 |
+
})
|
| 141 |
+
FormDescription.displayName = "FormDescription"
|
| 142 |
+
|
| 143 |
+
const FormMessage = React.forwardRef<
|
| 144 |
+
HTMLParagraphElement,
|
| 145 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
| 146 |
+
>(({ className, children, ...props }, ref) => {
|
| 147 |
+
const { error, formMessageId } = useFormField()
|
| 148 |
+
const body = error ? String(error?.message ?? "") : children
|
| 149 |
+
|
| 150 |
+
if (!body) {
|
| 151 |
+
return null
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
return (
|
| 155 |
+
<p
|
| 156 |
+
ref={ref}
|
| 157 |
+
id={formMessageId}
|
| 158 |
+
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
| 159 |
+
{...props}
|
| 160 |
+
>
|
| 161 |
+
{body}
|
| 162 |
+
</p>
|
| 163 |
+
)
|
| 164 |
+
})
|
| 165 |
+
FormMessage.displayName = "FormMessage"
|
| 166 |
+
|
| 167 |
+
export {
|
| 168 |
+
useFormField,
|
| 169 |
+
Form,
|
| 170 |
+
FormItem,
|
| 171 |
+
FormLabel,
|
| 172 |
+
FormControl,
|
| 173 |
+
FormDescription,
|
| 174 |
+
FormMessage,
|
| 175 |
+
FormField,
|
| 176 |
+
}
|
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 }
|
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-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-[color,box-shadow] 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:border-[var(--fg-base)] 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 }
|
src/components/ui/label.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
| 5 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 6 |
+
|
| 7 |
+
import { cn } from "@/lib/utils"
|
| 8 |
+
|
| 9 |
+
const labelVariants = cva(
|
| 10 |
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
const Label = React.forwardRef<
|
| 14 |
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
| 15 |
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
| 16 |
+
VariantProps<typeof labelVariants>
|
| 17 |
+
>(({ className, ...props }, ref) => (
|
| 18 |
+
<LabelPrimitive.Root
|
| 19 |
+
ref={ref}
|
| 20 |
+
className={cn(labelVariants(), className)}
|
| 21 |
+
{...props}
|
| 22 |
+
/>
|
| 23 |
+
))
|
| 24 |
+
Label.displayName = LabelPrimitive.Root.displayName
|
| 25 |
+
|
| 26 |
+
export { Label }
|
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-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
|
| 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 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 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-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-4 w-4 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 |
+
}
|
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-9 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-300 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 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 |
+
}
|
src/components/ui/popover.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const Popover = PopoverPrimitive.Root
|
| 9 |
+
|
| 10 |
+
const PopoverTrigger = PopoverPrimitive.Trigger
|
| 11 |
+
|
| 12 |
+
const PopoverAnchor = PopoverPrimitive.Anchor
|
| 13 |
+
|
| 14 |
+
const PopoverContent = React.forwardRef<
|
| 15 |
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
| 16 |
+
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
| 17 |
+
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
| 18 |
+
<PopoverPrimitive.Portal>
|
| 19 |
+
<PopoverPrimitive.Content
|
| 20 |
+
ref={ref}
|
| 21 |
+
align={align}
|
| 22 |
+
sideOffset={sideOffset}
|
| 23 |
+
className={cn(
|
| 24 |
+
"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]",
|
| 25 |
+
className
|
| 26 |
+
)}
|
| 27 |
+
{...props}
|
| 28 |
+
/>
|
| 29 |
+
</PopoverPrimitive.Portal>
|
| 30 |
+
))
|
| 31 |
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
| 32 |
+
|
| 33 |
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
src/components/ui/progress.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const Progress = React.forwardRef<
|
| 7 |
+
React.ElementRef<typeof ProgressPrimitive.Root>,
|
| 8 |
+
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
| 9 |
+
>(({ className, value, ...props }, ref) => (
|
| 10 |
+
<ProgressPrimitive.Root
|
| 11 |
+
ref={ref}
|
| 12 |
+
className={cn(
|
| 13 |
+
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
| 14 |
+
className
|
| 15 |
+
)}
|
| 16 |
+
{...props}
|
| 17 |
+
>
|
| 18 |
+
<ProgressPrimitive.Indicator
|
| 19 |
+
className="h-full w-full flex-1 bg-primary transition-all"
|
| 20 |
+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
| 21 |
+
/>
|
| 22 |
+
</ProgressPrimitive.Root>
|
| 23 |
+
))
|
| 24 |
+
Progress.displayName = ProgressPrimitive.Root.displayName
|
| 25 |
+
|
| 26 |
+
export { Progress }
|
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 shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring 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-3.5 w-3.5 fill-primary" />
|
| 36 |
+
</RadioGroupPrimitive.Indicator>
|
| 37 |
+
</RadioGroupPrimitive.Item>
|
| 38 |
+
)
|
| 39 |
+
})
|
| 40 |
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
| 41 |
+
|
| 42 |
+
export { RadioGroup, RadioGroupItem }
|
src/components/ui/resizable.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { GripVertical } from "lucide-react"
|
| 2 |
+
import { Group, Panel, Separator } from "react-resizable-panels"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const ResizablePanelGroup = ({
|
| 7 |
+
className,
|
| 8 |
+
...props
|
| 9 |
+
}: React.ComponentProps<typeof Group>) => (
|
| 10 |
+
<Group
|
| 11 |
+
className={cn(
|
| 12 |
+
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
| 13 |
+
className
|
| 14 |
+
)}
|
| 15 |
+
{...props}
|
| 16 |
+
/>
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
const ResizablePanel = Panel
|
| 20 |
+
|
| 21 |
+
const ResizableHandle = ({
|
| 22 |
+
withHandle,
|
| 23 |
+
className,
|
| 24 |
+
...props
|
| 25 |
+
}: React.ComponentProps<typeof Separator> & {
|
| 26 |
+
withHandle?: boolean
|
| 27 |
+
}) => (
|
| 28 |
+
<Separator
|
| 29 |
+
className={cn(
|
| 30 |
+
"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",
|
| 31 |
+
className
|
| 32 |
+
)}
|
| 33 |
+
{...props}
|
| 34 |
+
>
|
| 35 |
+
{withHandle && (
|
| 36 |
+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
| 37 |
+
<GripVertical className="h-2.5 w-2.5" />
|
| 38 |
+
</div>
|
| 39 |
+
)}
|
| 40 |
+
</Separator>
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
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 }
|
src/components/ui/select.tsx
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
| 3 |
+
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const Select = SelectPrimitive.Root
|
| 8 |
+
|
| 9 |
+
const SelectGroup = SelectPrimitive.Group
|
| 10 |
+
|
| 11 |
+
const SelectValue = SelectPrimitive.Value
|
| 12 |
+
|
| 13 |
+
const SelectTrigger = React.forwardRef<
|
| 14 |
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
| 15 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
| 16 |
+
>(({ className, children, ...props }, ref) => (
|
| 17 |
+
<SelectPrimitive.Trigger
|
| 18 |
+
ref={ref}
|
| 19 |
+
className={cn(
|
| 20 |
+
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
| 21 |
+
className
|
| 22 |
+
)}
|
| 23 |
+
{...props}
|
| 24 |
+
>
|
| 25 |
+
{children}
|
| 26 |
+
<SelectPrimitive.Icon asChild>
|
| 27 |
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
| 28 |
+
</SelectPrimitive.Icon>
|
| 29 |
+
</SelectPrimitive.Trigger>
|
| 30 |
+
))
|
| 31 |
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
| 32 |
+
|
| 33 |
+
const SelectScrollUpButton = React.forwardRef<
|
| 34 |
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
| 35 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
| 36 |
+
>(({ className, ...props }, ref) => (
|
| 37 |
+
<SelectPrimitive.ScrollUpButton
|
| 38 |
+
ref={ref}
|
| 39 |
+
className={cn(
|
| 40 |
+
"flex cursor-default items-center justify-center py-1",
|
| 41 |
+
className
|
| 42 |
+
)}
|
| 43 |
+
{...props}
|
| 44 |
+
>
|
| 45 |
+
<ChevronUp className="h-4 w-4" />
|
| 46 |
+
</SelectPrimitive.ScrollUpButton>
|
| 47 |
+
))
|
| 48 |
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
| 49 |
+
|
| 50 |
+
const SelectScrollDownButton = React.forwardRef<
|
| 51 |
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
| 52 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
| 53 |
+
>(({ className, ...props }, ref) => (
|
| 54 |
+
<SelectPrimitive.ScrollDownButton
|
| 55 |
+
ref={ref}
|
| 56 |
+
className={cn(
|
| 57 |
+
"flex cursor-default items-center justify-center py-1",
|
| 58 |
+
className
|
| 59 |
+
)}
|
| 60 |
+
{...props}
|
| 61 |
+
>
|
| 62 |
+
<ChevronDown className="h-4 w-4" />
|
| 63 |
+
</SelectPrimitive.ScrollDownButton>
|
| 64 |
+
))
|
| 65 |
+
SelectScrollDownButton.displayName =
|
| 66 |
+
SelectPrimitive.ScrollDownButton.displayName
|
| 67 |
+
|
| 68 |
+
const SelectContent = React.forwardRef<
|
| 69 |
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
| 70 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
| 71 |
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
| 72 |
+
<SelectPrimitive.Portal>
|
| 73 |
+
<SelectPrimitive.Content
|
| 74 |
+
ref={ref}
|
| 75 |
+
className={cn(
|
| 76 |
+
"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]",
|
| 77 |
+
position === "popper" &&
|
| 78 |
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
| 79 |
+
className
|
| 80 |
+
)}
|
| 81 |
+
position={position}
|
| 82 |
+
{...props}
|
| 83 |
+
>
|
| 84 |
+
<SelectScrollUpButton />
|
| 85 |
+
<SelectPrimitive.Viewport
|
| 86 |
+
className={cn(
|
| 87 |
+
"p-1",
|
| 88 |
+
position === "popper" &&
|
| 89 |
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
| 90 |
+
)}
|
| 91 |
+
>
|
| 92 |
+
{children}
|
| 93 |
+
</SelectPrimitive.Viewport>
|
| 94 |
+
<SelectScrollDownButton />
|
| 95 |
+
</SelectPrimitive.Content>
|
| 96 |
+
</SelectPrimitive.Portal>
|
| 97 |
+
))
|
| 98 |
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
| 99 |
+
|
| 100 |
+
const SelectLabel = React.forwardRef<
|
| 101 |
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
| 102 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
| 103 |
+
>(({ className, ...props }, ref) => (
|
| 104 |
+
<SelectPrimitive.Label
|
| 105 |
+
ref={ref}
|
| 106 |
+
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
| 107 |
+
{...props}
|
| 108 |
+
/>
|
| 109 |
+
))
|
| 110 |
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
| 111 |
+
|
| 112 |
+
const SelectItem = React.forwardRef<
|
| 113 |
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
| 114 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
| 115 |
+
>(({ className, children, ...props }, ref) => (
|
| 116 |
+
<SelectPrimitive.Item
|
| 117 |
+
ref={ref}
|
| 118 |
+
className={cn(
|
| 119 |
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
| 120 |
+
className
|
| 121 |
+
)}
|
| 122 |
+
{...props}
|
| 123 |
+
>
|
| 124 |
+
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
| 125 |
+
<SelectPrimitive.ItemIndicator>
|
| 126 |
+
<Check className="h-4 w-4" />
|
| 127 |
+
</SelectPrimitive.ItemIndicator>
|
| 128 |
+
</span>
|
| 129 |
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
| 130 |
+
</SelectPrimitive.Item>
|
| 131 |
+
))
|
| 132 |
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
| 133 |
+
|
| 134 |
+
const SelectSeparator = React.forwardRef<
|
| 135 |
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
| 136 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
| 137 |
+
>(({ className, ...props }, ref) => (
|
| 138 |
+
<SelectPrimitive.Separator
|
| 139 |
+
ref={ref}
|
| 140 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
| 141 |
+
{...props}
|
| 142 |
+
/>
|
| 143 |
+
))
|
| 144 |
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
| 145 |
+
|
| 146 |
+
export {
|
| 147 |
+
Select,
|
| 148 |
+
SelectGroup,
|
| 149 |
+
SelectValue,
|
| 150 |
+
SelectTrigger,
|
| 151 |
+
SelectContent,
|
| 152 |
+
SelectLabel,
|
| 153 |
+
SelectItem,
|
| 154 |
+
SelectSeparator,
|
| 155 |
+
SelectScrollUpButton,
|
| 156 |
+
SelectScrollDownButton,
|
| 157 |
+
}
|
src/components/ui/separator.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const Separator = React.forwardRef<
|
| 9 |
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
| 10 |
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
| 11 |
+
>(
|
| 12 |
+
(
|
| 13 |
+
{ className, orientation = "horizontal", decorative = true, ...props },
|
| 14 |
+
ref
|
| 15 |
+
) => (
|
| 16 |
+
<SeparatorPrimitive.Root
|
| 17 |
+
ref={ref}
|
| 18 |
+
decorative={decorative}
|
| 19 |
+
orientation={orientation}
|
| 20 |
+
className={cn(
|
| 21 |
+
"shrink-0 bg-border",
|
| 22 |
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
| 23 |
+
className
|
| 24 |
+
)}
|
| 25 |
+
{...props}
|
| 26 |
+
/>
|
| 27 |
+
)
|
| 28 |
+
)
|
| 29 |
+
Separator.displayName = SeparatorPrimitive.Root.displayName
|
| 30 |
+
|
| 31 |
+
export { Separator }
|
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=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
|
| 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 |
+
<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">
|
| 68 |
+
<X className="h-4 w-4" />
|
| 69 |
+
<span className="sr-only">Close</span>
|
| 70 |
+
</SheetPrimitive.Close>
|
| 71 |
+
{children}
|
| 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 |
+
}
|
src/components/ui/skeleton.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { cn } from "@/lib/utils"
|
| 2 |
+
|
| 3 |
+
function Skeleton({
|
| 4 |
+
className,
|
| 5 |
+
...props
|
| 6 |
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
| 7 |
+
return (
|
| 8 |
+
<div
|
| 9 |
+
className={cn("animate-pulse rounded-md bg-[var(--bg-subtle)]", className)}
|
| 10 |
+
{...props}
|
| 11 |
+
/>
|
| 12 |
+
)
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export { Skeleton }
|
src/components/ui/slider.tsx
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as SliderPrimitive from "@radix-ui/react-slider"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const Slider = React.forwardRef<
|
| 7 |
+
React.ElementRef<typeof SliderPrimitive.Root>,
|
| 8 |
+
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
| 9 |
+
>(({ className, ...props }, ref) => (
|
| 10 |
+
<SliderPrimitive.Root
|
| 11 |
+
ref={ref}
|
| 12 |
+
className={cn(
|
| 13 |
+
"relative flex w-full touch-none select-none items-center",
|
| 14 |
+
className
|
| 15 |
+
)}
|
| 16 |
+
{...props}
|
| 17 |
+
>
|
| 18 |
+
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
| 19 |
+
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
| 20 |
+
</SliderPrimitive.Track>
|
| 21 |
+
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
| 22 |
+
</SliderPrimitive.Root>
|
| 23 |
+
))
|
| 24 |
+
Slider.displayName = SliderPrimitive.Root.displayName
|
| 25 |
+
|
| 26 |
+
export { Slider }
|
src/components/ui/sonner.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useTheme } from "next-themes"
|
| 2 |
+
import { Toaster as Sonner } from "sonner"
|
| 3 |
+
|
| 4 |
+
type ToasterProps = React.ComponentProps<typeof Sonner>
|
| 5 |
+
|
| 6 |
+
const Toaster = ({ ...props }: ToasterProps) => {
|
| 7 |
+
const { theme = "system" } = useTheme()
|
| 8 |
+
|
| 9 |
+
return (
|
| 10 |
+
<Sonner
|
| 11 |
+
theme={theme as ToasterProps["theme"]}
|
| 12 |
+
className="toaster group"
|
| 13 |
+
toastOptions={{
|
| 14 |
+
classNames: {
|
| 15 |
+
toast:
|
| 16 |
+
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
| 17 |
+
description: "group-[.toast]:text-muted-foreground",
|
| 18 |
+
actionButton:
|
| 19 |
+
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
| 20 |
+
cancelButton:
|
| 21 |
+
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
| 22 |
+
},
|
| 23 |
+
}}
|
| 24 |
+
{...props}
|
| 25 |
+
/>
|
| 26 |
+
)
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
export { Toaster }
|
src/components/ui/switch.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const Switch = React.forwardRef<
|
| 9 |
+
React.ElementRef<typeof SwitchPrimitives.Root>,
|
| 10 |
+
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
| 11 |
+
>(({ className, ...props }, ref) => (
|
| 12 |
+
<SwitchPrimitives.Root
|
| 13 |
+
className={cn(
|
| 14 |
+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
| 15 |
+
className
|
| 16 |
+
)}
|
| 17 |
+
{...props}
|
| 18 |
+
ref={ref}
|
| 19 |
+
>
|
| 20 |
+
<SwitchPrimitives.Thumb
|
| 21 |
+
className={cn(
|
| 22 |
+
"pointer-events-none block h-4 w-4 rounded-full bg-[var(--neutral-0)] shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
| 23 |
+
)}
|
| 24 |
+
/>
|
| 25 |
+
</SwitchPrimitives.Root>
|
| 26 |
+
))
|
| 27 |
+
Switch.displayName = SwitchPrimitives.Root.displayName
|
| 28 |
+
|
| 29 |
+
export { Switch }
|
src/components/ui/table.tsx
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Table = React.forwardRef<
|
| 6 |
+
HTMLTableElement,
|
| 7 |
+
React.HTMLAttributes<HTMLTableElement>
|
| 8 |
+
>(({ className, ...props }, ref) => (
|
| 9 |
+
<div className="relative w-full overflow-auto">
|
| 10 |
+
<table
|
| 11 |
+
ref={ref}
|
| 12 |
+
className={cn("w-full caption-bottom text-sm", className)}
|
| 13 |
+
{...props}
|
| 14 |
+
/>
|
| 15 |
+
</div>
|
| 16 |
+
))
|
| 17 |
+
Table.displayName = "Table"
|
| 18 |
+
|
| 19 |
+
const TableHeader = React.forwardRef<
|
| 20 |
+
HTMLTableSectionElement,
|
| 21 |
+
React.HTMLAttributes<HTMLTableSectionElement>
|
| 22 |
+
>(({ className, ...props }, ref) => (
|
| 23 |
+
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
| 24 |
+
))
|
| 25 |
+
TableHeader.displayName = "TableHeader"
|
| 26 |
+
|
| 27 |
+
const TableBody = React.forwardRef<
|
| 28 |
+
HTMLTableSectionElement,
|
| 29 |
+
React.HTMLAttributes<HTMLTableSectionElement>
|
| 30 |
+
>(({ className, ...props }, ref) => (
|
| 31 |
+
<tbody
|
| 32 |
+
ref={ref}
|
| 33 |
+
className={cn("[&_tr:last-child]:border-0", className)}
|
| 34 |
+
{...props}
|
| 35 |
+
/>
|
| 36 |
+
))
|
| 37 |
+
TableBody.displayName = "TableBody"
|
| 38 |
+
|
| 39 |
+
const TableFooter = React.forwardRef<
|
| 40 |
+
HTMLTableSectionElement,
|
| 41 |
+
React.HTMLAttributes<HTMLTableSectionElement>
|
| 42 |
+
>(({ className, ...props }, ref) => (
|
| 43 |
+
<tfoot
|
| 44 |
+
ref={ref}
|
| 45 |
+
className={cn(
|
| 46 |
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
| 47 |
+
className
|
| 48 |
+
)}
|
| 49 |
+
{...props}
|
| 50 |
+
/>
|
| 51 |
+
))
|
| 52 |
+
TableFooter.displayName = "TableFooter"
|
| 53 |
+
|
| 54 |
+
const TableRow = React.forwardRef<
|
| 55 |
+
HTMLTableRowElement,
|
| 56 |
+
React.HTMLAttributes<HTMLTableRowElement>
|
| 57 |
+
>(({ className, ...props }, ref) => (
|
| 58 |
+
<tr
|
| 59 |
+
ref={ref}
|
| 60 |
+
className={cn(
|
| 61 |
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
| 62 |
+
className
|
| 63 |
+
)}
|
| 64 |
+
{...props}
|
| 65 |
+
/>
|
| 66 |
+
))
|
| 67 |
+
TableRow.displayName = "TableRow"
|
| 68 |
+
|
| 69 |
+
const TableHead = React.forwardRef<
|
| 70 |
+
HTMLTableCellElement,
|
| 71 |
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
| 72 |
+
>(({ className, ...props }, ref) => (
|
| 73 |
+
<th
|
| 74 |
+
ref={ref}
|
| 75 |
+
className={cn(
|
| 76 |
+
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
| 77 |
+
className
|
| 78 |
+
)}
|
| 79 |
+
{...props}
|
| 80 |
+
/>
|
| 81 |
+
))
|
| 82 |
+
TableHead.displayName = "TableHead"
|
| 83 |
+
|
| 84 |
+
const TableCell = React.forwardRef<
|
| 85 |
+
HTMLTableCellElement,
|
| 86 |
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
| 87 |
+
>(({ className, ...props }, ref) => (
|
| 88 |
+
<td
|
| 89 |
+
ref={ref}
|
| 90 |
+
className={cn(
|
| 91 |
+
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
| 92 |
+
className
|
| 93 |
+
)}
|
| 94 |
+
{...props}
|
| 95 |
+
/>
|
| 96 |
+
))
|
| 97 |
+
TableCell.displayName = "TableCell"
|
| 98 |
+
|
| 99 |
+
const TableCaption = React.forwardRef<
|
| 100 |
+
HTMLTableCaptionElement,
|
| 101 |
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
| 102 |
+
>(({ className, ...props }, ref) => (
|
| 103 |
+
<caption
|
| 104 |
+
ref={ref}
|
| 105 |
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
| 106 |
+
{...props}
|
| 107 |
+
/>
|
| 108 |
+
))
|
| 109 |
+
TableCaption.displayName = "TableCaption"
|
| 110 |
+
|
| 111 |
+
export {
|
| 112 |
+
Table,
|
| 113 |
+
TableHeader,
|
| 114 |
+
TableBody,
|
| 115 |
+
TableFooter,
|
| 116 |
+
TableHead,
|
| 117 |
+
TableRow,
|
| 118 |
+
TableCell,
|
| 119 |
+
TableCaption,
|
| 120 |
+
}
|
src/components/ui/tabs.tsx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const Tabs = TabsPrimitive.Root
|
| 7 |
+
|
| 8 |
+
const TabsList = React.forwardRef<
|
| 9 |
+
React.ElementRef<typeof TabsPrimitive.List>,
|
| 10 |
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
| 11 |
+
>(({ className, ...props }, ref) => (
|
| 12 |
+
<TabsPrimitive.List
|
| 13 |
+
ref={ref}
|
| 14 |
+
className={cn(
|
| 15 |
+
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
| 16 |
+
className
|
| 17 |
+
)}
|
| 18 |
+
{...props}
|
| 19 |
+
/>
|
| 20 |
+
))
|
| 21 |
+
TabsList.displayName = TabsPrimitive.List.displayName
|
| 22 |
+
|
| 23 |
+
const TabsTrigger = React.forwardRef<
|
| 24 |
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
| 25 |
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
| 26 |
+
>(({ className, ...props }, ref) => (
|
| 27 |
+
<TabsPrimitive.Trigger
|
| 28 |
+
ref={ref}
|
| 29 |
+
className={cn(
|
| 30 |
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
| 31 |
+
className
|
| 32 |
+
)}
|
| 33 |
+
{...props}
|
| 34 |
+
/>
|
| 35 |
+
))
|
| 36 |
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
| 37 |
+
|
| 38 |
+
const TabsContent = React.forwardRef<
|
| 39 |
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
| 40 |
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
| 41 |
+
>(({ className, ...props }, ref) => (
|
| 42 |
+
<TabsPrimitive.Content
|
| 43 |
+
ref={ref}
|
| 44 |
+
className={cn(
|
| 45 |
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
| 46 |
+
className
|
| 47 |
+
)}
|
| 48 |
+
{...props}
|
| 49 |
+
/>
|
| 50 |
+
))
|
| 51 |
+
TabsContent.displayName = TabsPrimitive.Content.displayName
|
| 52 |
+
|
| 53 |
+
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
src/components/ui/textarea.tsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Textarea = React.forwardRef<
|
| 6 |
+
HTMLTextAreaElement,
|
| 7 |
+
React.ComponentProps<"textarea">
|
| 8 |
+
>(({ className, ...props }, ref) => {
|
| 9 |
+
return (
|
| 10 |
+
<textarea
|
| 11 |
+
className={cn(
|
| 12 |
+
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
| 13 |
+
className
|
| 14 |
+
)}
|
| 15 |
+
ref={ref}
|
| 16 |
+
{...props}
|
| 17 |
+
/>
|
| 18 |
+
)
|
| 19 |
+
})
|
| 20 |
+
Textarea.displayName = "Textarea"
|
| 21 |
+
|
| 22 |
+
export { Textarea }
|
src/components/ui/toast.tsx
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as ToastPrimitives from "@radix-ui/react-toast"
|
| 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 ToastProvider = ToastPrimitives.Provider
|
| 11 |
+
|
| 12 |
+
const ToastViewport = React.forwardRef<
|
| 13 |
+
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
| 14 |
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
| 15 |
+
>(({ className, ...props }, ref) => (
|
| 16 |
+
<ToastPrimitives.Viewport
|
| 17 |
+
ref={ref}
|
| 18 |
+
className={cn(
|
| 19 |
+
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
| 20 |
+
className
|
| 21 |
+
)}
|
| 22 |
+
{...props}
|
| 23 |
+
/>
|
| 24 |
+
))
|
| 25 |
+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
| 26 |
+
|
| 27 |
+
const toastVariants = cva(
|
| 28 |
+
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
| 29 |
+
{
|
| 30 |
+
variants: {
|
| 31 |
+
variant: {
|
| 32 |
+
default: "border bg-background text-foreground",
|
| 33 |
+
destructive:
|
| 34 |
+
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
| 35 |
+
},
|
| 36 |
+
},
|
| 37 |
+
defaultVariants: {
|
| 38 |
+
variant: "default",
|
| 39 |
+
},
|
| 40 |
+
}
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
const Toast = React.forwardRef<
|
| 44 |
+
React.ElementRef<typeof ToastPrimitives.Root>,
|
| 45 |
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
| 46 |
+
VariantProps<typeof toastVariants>
|
| 47 |
+
>(({ className, variant, ...props }, ref) => {
|
| 48 |
+
return (
|
| 49 |
+
<ToastPrimitives.Root
|
| 50 |
+
ref={ref}
|
| 51 |
+
className={cn(toastVariants({ variant }), className)}
|
| 52 |
+
{...props}
|
| 53 |
+
/>
|
| 54 |
+
)
|
| 55 |
+
})
|
| 56 |
+
Toast.displayName = ToastPrimitives.Root.displayName
|
| 57 |
+
|
| 58 |
+
const ToastAction = React.forwardRef<
|
| 59 |
+
React.ElementRef<typeof ToastPrimitives.Action>,
|
| 60 |
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
| 61 |
+
>(({ className, ...props }, ref) => (
|
| 62 |
+
<ToastPrimitives.Action
|
| 63 |
+
ref={ref}
|
| 64 |
+
className={cn(
|
| 65 |
+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
| 66 |
+
className
|
| 67 |
+
)}
|
| 68 |
+
{...props}
|
| 69 |
+
/>
|
| 70 |
+
))
|
| 71 |
+
ToastAction.displayName = ToastPrimitives.Action.displayName
|
| 72 |
+
|
| 73 |
+
const ToastClose = React.forwardRef<
|
| 74 |
+
React.ElementRef<typeof ToastPrimitives.Close>,
|
| 75 |
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
| 76 |
+
>(({ className, ...props }, ref) => (
|
| 77 |
+
<ToastPrimitives.Close
|
| 78 |
+
ref={ref}
|
| 79 |
+
className={cn(
|
| 80 |
+
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
| 81 |
+
className
|
| 82 |
+
)}
|
| 83 |
+
toast-close=""
|
| 84 |
+
{...props}
|
| 85 |
+
>
|
| 86 |
+
<X className="h-4 w-4" />
|
| 87 |
+
</ToastPrimitives.Close>
|
| 88 |
+
))
|
| 89 |
+
ToastClose.displayName = ToastPrimitives.Close.displayName
|
| 90 |
+
|
| 91 |
+
const ToastTitle = React.forwardRef<
|
| 92 |
+
React.ElementRef<typeof ToastPrimitives.Title>,
|
| 93 |
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
| 94 |
+
>(({ className, ...props }, ref) => (
|
| 95 |
+
<ToastPrimitives.Title
|
| 96 |
+
ref={ref}
|
| 97 |
+
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
|
| 98 |
+
{...props}
|
| 99 |
+
/>
|
| 100 |
+
))
|
| 101 |
+
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
| 102 |
+
|
| 103 |
+
const ToastDescription = React.forwardRef<
|
| 104 |
+
React.ElementRef<typeof ToastPrimitives.Description>,
|
| 105 |
+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
| 106 |
+
>(({ className, ...props }, ref) => (
|
| 107 |
+
<ToastPrimitives.Description
|
| 108 |
+
ref={ref}
|
| 109 |
+
className={cn("text-sm opacity-90", className)}
|
| 110 |
+
{...props}
|
| 111 |
+
/>
|
| 112 |
+
))
|
| 113 |
+
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
| 114 |
+
|
| 115 |
+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
| 116 |
+
|
| 117 |
+
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
| 118 |
+
|
| 119 |
+
export {
|
| 120 |
+
type ToastProps,
|
| 121 |
+
type ToastActionElement,
|
| 122 |
+
ToastProvider,
|
| 123 |
+
ToastViewport,
|
| 124 |
+
Toast,
|
| 125 |
+
ToastTitle,
|
| 126 |
+
ToastDescription,
|
| 127 |
+
ToastClose,
|
| 128 |
+
ToastAction,
|
| 129 |
+
}
|
src/components/ui/toaster.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { useToast } from "@/hooks/use-toast"
|
| 4 |
+
import {
|
| 5 |
+
Toast,
|
| 6 |
+
ToastClose,
|
| 7 |
+
ToastDescription,
|
| 8 |
+
ToastProvider,
|
| 9 |
+
ToastTitle,
|
| 10 |
+
ToastViewport,
|
| 11 |
+
} from "@/components/ui/toast"
|
| 12 |
+
|
| 13 |
+
export function Toaster() {
|
| 14 |
+
const { toasts } = useToast()
|
| 15 |
+
|
| 16 |
+
return (
|
| 17 |
+
<ToastProvider>
|
| 18 |
+
{toasts.map(function ({ id, title, description, action, ...props }) {
|
| 19 |
+
return (
|
| 20 |
+
<Toast key={id} {...props}>
|
| 21 |
+
<div className="grid gap-1">
|
| 22 |
+
{title && <ToastTitle>{title}</ToastTitle>}
|
| 23 |
+
{description && (
|
| 24 |
+
<ToastDescription>{description}</ToastDescription>
|
| 25 |
+
)}
|
| 26 |
+
</div>
|
| 27 |
+
{action}
|
| 28 |
+
<ToastClose />
|
| 29 |
+
</Toast>
|
| 30 |
+
)
|
| 31 |
+
})}
|
| 32 |
+
<ToastViewport />
|
| 33 |
+
</ToastProvider>
|
| 34 |
+
)
|
| 35 |
+
}
|
src/components/ui/toggle-group.tsx
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
| 3 |
+
import { type VariantProps } from "class-variance-authority"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
import { toggleVariants } from "@/components/ui/toggle"
|
| 7 |
+
|
| 8 |
+
const ToggleGroupContext = React.createContext<
|
| 9 |
+
VariantProps<typeof toggleVariants>
|
| 10 |
+
>({
|
| 11 |
+
size: "default",
|
| 12 |
+
variant: "default",
|
| 13 |
+
})
|
| 14 |
+
|
| 15 |
+
const ToggleGroup = React.forwardRef<
|
| 16 |
+
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
| 17 |
+
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
| 18 |
+
VariantProps<typeof toggleVariants>
|
| 19 |
+
>(({ className, variant, size, children, ...props }, ref) => (
|
| 20 |
+
<ToggleGroupPrimitive.Root
|
| 21 |
+
ref={ref}
|
| 22 |
+
className={cn("flex items-center justify-center gap-1", className)}
|
| 23 |
+
{...props}
|
| 24 |
+
>
|
| 25 |
+
<ToggleGroupContext.Provider value={{ variant, size }}>
|
| 26 |
+
{children}
|
| 27 |
+
</ToggleGroupContext.Provider>
|
| 28 |
+
</ToggleGroupPrimitive.Root>
|
| 29 |
+
))
|
| 30 |
+
|
| 31 |
+
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
| 32 |
+
|
| 33 |
+
const ToggleGroupItem = React.forwardRef<
|
| 34 |
+
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
| 35 |
+
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
| 36 |
+
VariantProps<typeof toggleVariants>
|
| 37 |
+
>(({ className, children, variant, size, ...props }, ref) => {
|
| 38 |
+
const context = React.useContext(ToggleGroupContext)
|
| 39 |
+
|
| 40 |
+
return (
|
| 41 |
+
<ToggleGroupPrimitive.Item
|
| 42 |
+
ref={ref}
|
| 43 |
+
className={cn(
|
| 44 |
+
toggleVariants({
|
| 45 |
+
variant: context.variant || variant,
|
| 46 |
+
size: context.size || size,
|
| 47 |
+
}),
|
| 48 |
+
className
|
| 49 |
+
)}
|
| 50 |
+
{...props}
|
| 51 |
+
>
|
| 52 |
+
{children}
|
| 53 |
+
</ToggleGroupPrimitive.Item>
|
| 54 |
+
)
|
| 55 |
+
})
|
| 56 |
+
|
| 57 |
+
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
| 58 |
+
|
| 59 |
+
export { ToggleGroup, ToggleGroupItem }
|
src/components/ui/toggle.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
| 3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const toggleVariants = cva(
|
| 8 |
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default: "bg-transparent",
|
| 13 |
+
outline:
|
| 14 |
+
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
|
| 15 |
+
},
|
| 16 |
+
size: {
|
| 17 |
+
default: "h-9 px-2 min-w-9",
|
| 18 |
+
sm: "h-8 px-1.5 min-w-8",
|
| 19 |
+
lg: "h-10 px-2.5 min-w-10",
|
| 20 |
+
},
|
| 21 |
+
},
|
| 22 |
+
defaultVariants: {
|
| 23 |
+
variant: "default",
|
| 24 |
+
size: "default",
|
| 25 |
+
},
|
| 26 |
+
}
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
const Toggle = React.forwardRef<
|
| 30 |
+
React.ElementRef<typeof TogglePrimitive.Root>,
|
| 31 |
+
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
| 32 |
+
VariantProps<typeof toggleVariants>
|
| 33 |
+
>(({ className, variant, size, ...props }, ref) => (
|
| 34 |
+
<TogglePrimitive.Root
|
| 35 |
+
ref={ref}
|
| 36 |
+
className={cn(toggleVariants({ variant, size, className }))}
|
| 37 |
+
{...props}
|
| 38 |
+
/>
|
| 39 |
+
))
|
| 40 |
+
|
| 41 |
+
Toggle.displayName = TogglePrimitive.Root.displayName
|
| 42 |
+
|
| 43 |
+
export { Toggle, toggleVariants }
|
src/components/ui/tooltip.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const TooltipProvider = TooltipPrimitive.Provider
|
| 7 |
+
|
| 8 |
+
const Tooltip = TooltipPrimitive.Root
|
| 9 |
+
|
| 10 |
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
| 11 |
+
|
| 12 |
+
const TooltipContent = React.forwardRef<
|
| 13 |
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
| 14 |
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
| 15 |
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
| 16 |
+
<TooltipPrimitive.Portal>
|
| 17 |
+
<TooltipPrimitive.Content
|
| 18 |
+
ref={ref}
|
| 19 |
+
sideOffset={sideOffset}
|
| 20 |
+
className={cn(
|
| 21 |
+
"z-50 w-fit text-balance rounded-[10px] border border-[var(--border-base)] bg-foreground/5 px-3 py-2 text-xs text-[var(--fg-subtle)] backdrop-blur-2xl animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
|
| 22 |
+
className
|
| 23 |
+
)}
|
| 24 |
+
{...props}
|
| 25 |
+
/>
|
| 26 |
+
</TooltipPrimitive.Portal>
|
| 27 |
+
))
|
| 28 |
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
| 29 |
+
|
| 30 |
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
src/entry-client.tsx
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import './index.css'
|
| 2 |
+
|
| 3 |
+
// ---------------------------------------------------------------------------
|
| 4 |
+
// Cold-start guard: verify React is real BEFORE rendering.
|
| 5 |
+
//
|
| 6 |
+
// During Vite dep-optimization (cold start or after `npm install <pkg>`),
|
| 7 |
+
// Vite may serve placeholder "stub" modules where all React exports are
|
| 8 |
+
// undefined. Rendering with stub hooks causes "Cannot read properties of
|
| 9 |
+
// null (reading 'useState')".
|
| 10 |
+
//
|
| 11 |
+
// Strategy:
|
| 12 |
+
// 1. Dynamic-import React and check if useState is a function.
|
| 13 |
+
// 2. If stub → show a loading banner, schedule a reload with increasing
|
| 14 |
+
// delay, and do NOT mount any React tree (avoids the error entirely).
|
| 15 |
+
// 3. If real → dynamic-import App/ErrorBoundary and render normally.
|
| 16 |
+
// 4. Use createElement (not JSX) in this file so Vite doesn't inject a
|
| 17 |
+
// static `import { jsx } from 'react/jsx-dev-runtime'` which would
|
| 18 |
+
// itself be a stub and crash before our guard runs.
|
| 19 |
+
// ---------------------------------------------------------------------------
|
| 20 |
+
|
| 21 |
+
const RELOAD_KEY = '__dep_reload'
|
| 22 |
+
const MAX_RELOADS = 6
|
| 23 |
+
const RELOAD_WINDOW = 60_000 // 1 minute
|
| 24 |
+
|
| 25 |
+
function getReloads(): { c: number; t: number } {
|
| 26 |
+
try {
|
| 27 |
+
return JSON.parse(sessionStorage.getItem(RELOAD_KEY) || '{}') as { c: number; t: number }
|
| 28 |
+
} catch { return { c: 0, t: 0 } }
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
async function boot() {
|
| 32 |
+
const root = document.getElementById('root')!
|
| 33 |
+
|
| 34 |
+
try {
|
| 35 |
+
const React = await import('react')
|
| 36 |
+
|
| 37 |
+
// If useState is not a function, React is a dep-optimization stub
|
| 38 |
+
if (typeof React.useState !== 'function') throw new Error('dep-stub')
|
| 39 |
+
|
| 40 |
+
const { createElement } = React
|
| 41 |
+
const { createRoot, hydrateRoot } = await import('react-dom/client')
|
| 42 |
+
const { QueryClient, QueryClientProvider } = await import('@tanstack/react-query')
|
| 43 |
+
const { default: App } = await import('./App')
|
| 44 |
+
const { default: ErrorBoundary } = await import('./ErrorBoundary')
|
| 45 |
+
|
| 46 |
+
const queryClient = new QueryClient({
|
| 47 |
+
defaultOptions: { queries: {
|
| 48 |
+
refetchOnWindowFocus: false,
|
| 49 |
+
retry: 3,
|
| 50 |
+
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 10000),
|
| 51 |
+
staleTime: 30_000,
|
| 52 |
+
} },
|
| 53 |
+
})
|
| 54 |
+
|
| 55 |
+
const children = createElement(ErrorBoundary, null,
|
| 56 |
+
createElement(QueryClientProvider, { client: queryClient },
|
| 57 |
+
createElement(App)
|
| 58 |
+
)
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
// Use hydrateRoot only when SSR rendered real app content. The scaffold's
|
| 62 |
+
// server entry intentionally returns a lightweight placeholder shell so
|
| 63 |
+
// SSR-incompatible libraries (for example echarts-for-react) cannot crash
|
| 64 |
+
// deploy-time render. Placeholder markup must be client-rendered, not hydrated.
|
| 65 |
+
const hasPlaceholder = !!root.querySelector('[data-surf-placeholder]')
|
| 66 |
+
if (root.childNodes.length > 0 && root.innerHTML !== '<!--ssr-outlet-->' && !hasPlaceholder) {
|
| 67 |
+
hydrateRoot(root, children)
|
| 68 |
+
} else {
|
| 69 |
+
root.innerHTML = ''
|
| 70 |
+
createRoot(root).render(children)
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// React rendered successfully — signal to index.html fallback & reset counter
|
| 74 |
+
;(window as any).__reactOk = true
|
| 75 |
+
sessionStorage.removeItem(RELOAD_KEY)
|
| 76 |
+
|
| 77 |
+
// Notify parent frame when real app content renders (not the placeholder).
|
| 78 |
+
// DO NOT REMOVE — the hosting app uses this to dismiss the loading overlay.
|
| 79 |
+
function notifyParentReady() {
|
| 80 |
+
if (!document.querySelector('[data-surf-placeholder]')) {
|
| 81 |
+
try { window.parent.postMessage({ type: 'surf-app-ready' }, '*') } catch { /* cross-origin — ignore */ }
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
notifyParentReady()
|
| 85 |
+
new MutationObserver(notifyParentReady).observe(root, { childList: true, subtree: true })
|
| 86 |
+
} catch {
|
| 87 |
+
// React is not ready — show loading banner and schedule reload
|
| 88 |
+
const prev = getReloads()
|
| 89 |
+
const count = (Date.now() - prev.t > RELOAD_WINDOW) ? 0 : prev.c
|
| 90 |
+
|
| 91 |
+
if (count < MAX_RELOADS) {
|
| 92 |
+
root.innerHTML = [
|
| 93 |
+
'<div style="padding:24px;text-align:center;font-family:system-ui,sans-serif">',
|
| 94 |
+
'<p style="color:#3b82f6;font-weight:600;margin:0 0 4px">Loading dependencies...</p>',
|
| 95 |
+
'<p style="color:#3b82f6;opacity:0.7;font-size:12px;margin:0">Reloading automatically</p>',
|
| 96 |
+
'</div>',
|
| 97 |
+
].join('')
|
| 98 |
+
sessionStorage.setItem(RELOAD_KEY, JSON.stringify({ c: count + 1, t: Date.now() }))
|
| 99 |
+
// Increasing delay: 3s, 4s, 5s, ... gives Vite more time to finish
|
| 100 |
+
setTimeout(() => location.reload(), 3000 + count * 1000)
|
| 101 |
+
} else {
|
| 102 |
+
root.innerHTML = [
|
| 103 |
+
'<div style="padding:24px;text-align:center;font-family:system-ui,sans-serif">',
|
| 104 |
+
'<p style="color:#c0392b;font-weight:600;margin:0 0 4px">Failed to load dependencies</p>',
|
| 105 |
+
'<p style="color:#c0392b;opacity:0.8;font-size:12px;margin:0">Please refresh the page</p>',
|
| 106 |
+
'</div>',
|
| 107 |
+
].join('')
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
boot()
|