Spaces:
Paused
Paused
Commit
Β·
7948ff4
0
Parent(s):
initial commit π¬
Browse files- .eslintrc.json +3 -0
- .gitignore +35 -0
- .nvmrc +1 -0
- Dockerfile +37 -0
- README.md +25 -0
- components.json +16 -0
- next.config.js +4 -0
- package-lock.json +0 -0
- package.json +52 -0
- postcss.config.js +6 -0
- public/next.svg +1 -0
- public/vercel.svg +1 -0
- scripts/test.js +23 -0
- src/app/favicon.ico +0 -0
- src/app/globals.css +27 -0
- src/app/layout.tsx +23 -0
- src/app/page.tsx +18 -0
- src/app/types.ts +63 -0
- src/components/business/menu.tsx +200 -0
- src/components/business/timeline/index.tsx +71 -0
- src/components/ui/accordion.tsx +60 -0
- src/components/ui/alert.tsx +59 -0
- src/components/ui/badge.tsx +36 -0
- src/components/ui/button.tsx +56 -0
- src/components/ui/calendar.tsx +64 -0
- src/components/ui/card.tsx +79 -0
- src/components/ui/checkbox.tsx +30 -0
- src/components/ui/collapsible.tsx +11 -0
- src/components/ui/command.tsx +155 -0
- src/components/ui/dialog.tsx +123 -0
- src/components/ui/menubar.tsx +236 -0
- src/core/goodWords.ts +39 -0
- src/core/hasBadWords.ts +10 -0
- src/core/interpolateVideo.ts +40 -0
- src/core/isGoodWord.ts +7 -0
- src/lib/utils.ts +6 -0
- tailwind.config.js +36 -0
- tsconfig.json +28 -0
.eslintrc.json
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"extends": "next/core-web-vitals"
|
3 |
+
}
|
.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
/node_modules
|
5 |
+
/.pnp
|
6 |
+
.pnp.js
|
7 |
+
|
8 |
+
# testing
|
9 |
+
/coverage
|
10 |
+
|
11 |
+
# next.js
|
12 |
+
/.next/
|
13 |
+
/out/
|
14 |
+
|
15 |
+
# production
|
16 |
+
/build
|
17 |
+
|
18 |
+
# misc
|
19 |
+
.DS_Store
|
20 |
+
*.pem
|
21 |
+
|
22 |
+
# debug
|
23 |
+
npm-debug.log*
|
24 |
+
yarn-debug.log*
|
25 |
+
yarn-error.log*
|
26 |
+
|
27 |
+
# local env files
|
28 |
+
.env*.local
|
29 |
+
|
30 |
+
# vercel
|
31 |
+
.vercel
|
32 |
+
|
33 |
+
# typescript
|
34 |
+
*.tsbuildinfo
|
35 |
+
next-env.d.ts
|
.nvmrc
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
v18.16.0
|
Dockerfile
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM node:18
|
2 |
+
|
3 |
+
|
4 |
+
ARG DEBIAN_FRONTEND=noninteractive
|
5 |
+
|
6 |
+
RUN apt update
|
7 |
+
|
8 |
+
RUN apt --yes install ffmpeg
|
9 |
+
|
10 |
+
# Set up a new user named "user" with user ID 1000
|
11 |
+
RUN useradd -o -u 1000 user
|
12 |
+
|
13 |
+
# Switch to the "user" user
|
14 |
+
USER user
|
15 |
+
|
16 |
+
# Set home to the user's home directory
|
17 |
+
ENV HOME=/home/user \
|
18 |
+
PATH=/home/user/.local/bin:$PATH
|
19 |
+
|
20 |
+
# Set the working directory to the user's home directory
|
21 |
+
WORKDIR $HOME/app
|
22 |
+
|
23 |
+
# Install app dependencies
|
24 |
+
# A wildcard is used to ensure both package.json AND package-lock.json are copied
|
25 |
+
# where available (npm@5+)
|
26 |
+
COPY --chown=user package*.json $HOME/app
|
27 |
+
|
28 |
+
RUN npm install
|
29 |
+
|
30 |
+
RUN npm build
|
31 |
+
|
32 |
+
# Copy the current directory contents into the container at $HOME/app setting the owner to the user
|
33 |
+
COPY --chown=user . $HOME/app
|
34 |
+
|
35 |
+
EXPOSE 3000
|
36 |
+
|
37 |
+
CMD [ "npm", "run", "start" ]
|
README.md
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: VideoChain-UI
|
3 |
+
emoji: π¬
|
4 |
+
colorFrom: "#1a1b1c"
|
5 |
+
colorTo: "#acb1b5"
|
6 |
+
sdk: docker
|
7 |
+
pinned: false
|
8 |
+
app_port: 3000
|
9 |
+
---
|
10 |
+
|
11 |
+
This is the frontend interface to VideoChain-API, a server to generate videos using AI.
|
12 |
+
|
13 |
+
## Getting Started
|
14 |
+
|
15 |
+
First, run the development server:
|
16 |
+
|
17 |
+
```bash
|
18 |
+
npm run dev
|
19 |
+
```
|
20 |
+
|
21 |
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
22 |
+
|
23 |
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
24 |
+
|
25 |
+
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
components.json
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
3 |
+
"style": "default",
|
4 |
+
"rsc": true,
|
5 |
+
"tsx": true,
|
6 |
+
"tailwind": {
|
7 |
+
"config": "tailwind.config.js",
|
8 |
+
"css": "app/globals.css",
|
9 |
+
"baseColor": "zinc",
|
10 |
+
"cssVariables": false
|
11 |
+
},
|
12 |
+
"aliases": {
|
13 |
+
"components": "@/components",
|
14 |
+
"utils": "@/lib/utils"
|
15 |
+
}
|
16 |
+
}
|
next.config.js
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** @type {import('next').NextConfig} */
|
2 |
+
const nextConfig = {}
|
3 |
+
|
4 |
+
module.exports = nextConfig
|
package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
package.json
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "videochain-ui",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"scripts": {
|
6 |
+
"dev": "next dev",
|
7 |
+
"build": "next build",
|
8 |
+
"start": "next start",
|
9 |
+
"lint": "next lint"
|
10 |
+
},
|
11 |
+
"dependencies": {
|
12 |
+
"@gradio/client": "^0.1.4",
|
13 |
+
"@huggingface/inference": "^2.6.1",
|
14 |
+
"@radix-ui/react-accordion": "^1.1.2",
|
15 |
+
"@radix-ui/react-checkbox": "^1.0.4",
|
16 |
+
"@radix-ui/react-collapsible": "^1.0.3",
|
17 |
+
"@radix-ui/react-dialog": "^1.0.4",
|
18 |
+
"@radix-ui/react-icons": "^1.3.0",
|
19 |
+
"@radix-ui/react-menubar": "^1.0.3",
|
20 |
+
"@radix-ui/react-slot": "^1.0.2",
|
21 |
+
"@silevis/reactgrid": "^4.0.5",
|
22 |
+
"@types/fluent-ffmpeg": "^2.1.21",
|
23 |
+
"@types/node": "20.4.2",
|
24 |
+
"@types/react": "18.2.15",
|
25 |
+
"@types/react-dom": "18.2.7",
|
26 |
+
"@types/uuid": "^9.0.2",
|
27 |
+
"autoprefixer": "10.4.14",
|
28 |
+
"class-variance-authority": "^0.6.1",
|
29 |
+
"clsx": "^2.0.0",
|
30 |
+
"cmdk": "^0.2.0",
|
31 |
+
"date-fns": "^2.30.0",
|
32 |
+
"eslint": "8.45.0",
|
33 |
+
"eslint-config-next": "13.4.10",
|
34 |
+
"fluent-ffmpeg": "^2.1.2",
|
35 |
+
"fs-extra": "^11.1.1",
|
36 |
+
"lucide-react": "^0.260.0",
|
37 |
+
"next": "13.4.10",
|
38 |
+
"postcss": "8.4.26",
|
39 |
+
"puppeteer": "^20.8.2",
|
40 |
+
"react": "18.2.0",
|
41 |
+
"react-day-picker": "^8.8.0",
|
42 |
+
"react-dom": "18.2.0",
|
43 |
+
"tailwind-merge": "^1.13.2",
|
44 |
+
"tailwindcss": "3.3.3",
|
45 |
+
"tailwindcss-animate": "^1.0.6",
|
46 |
+
"temp-dir": "^3.0.0",
|
47 |
+
"ts-node": "^10.9.1",
|
48 |
+
"typescript": "5.1.6",
|
49 |
+
"uuid": "^9.0.0",
|
50 |
+
"zustand": "^4.3.9"
|
51 |
+
}
|
52 |
+
}
|
postcss.config.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
plugins: {
|
3 |
+
tailwindcss: {},
|
4 |
+
autoprefixer: {},
|
5 |
+
},
|
6 |
+
}
|
public/next.svg
ADDED
|
public/vercel.svg
ADDED
|
scripts/test.js
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const { promises: fs } = require("node:fs")
|
2 |
+
|
3 |
+
const main = async () => {
|
4 |
+
console.log('generating shot..')
|
5 |
+
const response = await fetch("http://localhost:3000/api/shot", {
|
6 |
+
method: "POST",
|
7 |
+
headers: {
|
8 |
+
"Accept": "application/json",
|
9 |
+
"Content-Type": "application/json"
|
10 |
+
},
|
11 |
+
body: JSON.stringify({
|
12 |
+
token: process.env.VS_SECRET_ACCESS_TOKEN,
|
13 |
+
shotPrompt: "video of a dancing cat"
|
14 |
+
})
|
15 |
+
});
|
16 |
+
|
17 |
+
console.log('response:', response)
|
18 |
+
const buffer = await response.buffer()
|
19 |
+
|
20 |
+
fs.writeFile(`./test-juju.mp4`, buffer)
|
21 |
+
}
|
22 |
+
|
23 |
+
main()
|
src/app/favicon.ico
ADDED
|
src/app/globals.css
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
:root {
|
6 |
+
--foreground-rgb: 0, 0, 0;
|
7 |
+
--background-start-rgb: 214, 219, 220;
|
8 |
+
--background-end-rgb: 255, 255, 255;
|
9 |
+
}
|
10 |
+
|
11 |
+
@media (prefers-color-scheme: dark) {
|
12 |
+
:root {
|
13 |
+
--foreground-rgb: 255, 255, 255;
|
14 |
+
--background-start-rgb: 0, 0, 0;
|
15 |
+
--background-end-rgb: 0, 0, 0;
|
16 |
+
}
|
17 |
+
}
|
18 |
+
|
19 |
+
body {
|
20 |
+
color: rgb(var(--foreground-rgb));
|
21 |
+
background: linear-gradient(
|
22 |
+
to bottom,
|
23 |
+
transparent,
|
24 |
+
rgb(var(--background-end-rgb))
|
25 |
+
)
|
26 |
+
rgb(var(--background-start-rgb));
|
27 |
+
}
|
src/app/layout.tsx
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import './globals.css'
|
2 |
+
import type { Metadata } from 'next'
|
3 |
+
import { Inter } from 'next/font/google'
|
4 |
+
import Head from 'next/head'
|
5 |
+
|
6 |
+
const inter = Inter({ subsets: ['latin'] })
|
7 |
+
|
8 |
+
export const metadata: Metadata = {
|
9 |
+
title: 'Create Next App',
|
10 |
+
description: 'Generated by create next app',
|
11 |
+
}
|
12 |
+
|
13 |
+
export default function RootLayout({
|
14 |
+
children,
|
15 |
+
}: {
|
16 |
+
children: React.ReactNode
|
17 |
+
}) {
|
18 |
+
return (
|
19 |
+
<html lang="en">
|
20 |
+
<body className={inter.className}>{children}</body>
|
21 |
+
</html>
|
22 |
+
)
|
23 |
+
}
|
src/app/page.tsx
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Head from "next/head"
|
2 |
+
|
3 |
+
import { Timeline } from "@/components/business/timeline"
|
4 |
+
|
5 |
+
export default function Index() {
|
6 |
+
return (
|
7 |
+
<div>
|
8 |
+
<Head>
|
9 |
+
<meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
|
10 |
+
</Head>
|
11 |
+
<main className="h-screen w-full flex bg-gray-700 text-gray-200">
|
12 |
+
<div className="flex flex-col">
|
13 |
+
<Timeline />
|
14 |
+
</div>
|
15 |
+
</main>
|
16 |
+
</div>
|
17 |
+
)
|
18 |
+
}
|
src/app/types.ts
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface Shot {
|
2 |
+
shotId: string
|
3 |
+
index: number
|
4 |
+
lastGenerationAt: string
|
5 |
+
videoPrompt: string
|
6 |
+
audioPrompt: string
|
7 |
+
duration: number // no more than 3 (we don't have the ressources for it)
|
8 |
+
fps: number // typically 8, 12, 24
|
9 |
+
}
|
10 |
+
|
11 |
+
export interface Sequence {
|
12 |
+
sequenceId: string
|
13 |
+
skip: boolean
|
14 |
+
lastGenerationAt: string
|
15 |
+
videoPrompt: string
|
16 |
+
audioPrompt: string
|
17 |
+
channel: string
|
18 |
+
tags: string[]
|
19 |
+
shots: Shot[]
|
20 |
+
}
|
21 |
+
|
22 |
+
export interface Database {
|
23 |
+
version: number
|
24 |
+
startAtShotId: string
|
25 |
+
sequences: Sequence[]
|
26 |
+
}
|
27 |
+
|
28 |
+
|
29 |
+
export interface ShotQuery {
|
30 |
+
token: string
|
31 |
+
shotPrompt: string
|
32 |
+
// inputVideo?: string
|
33 |
+
|
34 |
+
// describe the background audio (crowd, birds, wind, sea etc..)
|
35 |
+
backgroundAudioPrompt?: string
|
36 |
+
|
37 |
+
// describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
|
38 |
+
foregroundAudioPrompt?: string
|
39 |
+
|
40 |
+
// describe the main actor visible in the shot (optional)
|
41 |
+
actorPrompt?: string
|
42 |
+
|
43 |
+
// describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
|
44 |
+
actorVoicePrompt?: string
|
45 |
+
|
46 |
+
// describe the main actor dialogue line
|
47 |
+
actorDialoguePrompt?: string
|
48 |
+
|
49 |
+
seed?: number
|
50 |
+
upscale?: boolean
|
51 |
+
|
52 |
+
duration?: number
|
53 |
+
steps?: number
|
54 |
+
|
55 |
+
fps?: number // 8, 12, 24, 30, 60
|
56 |
+
|
57 |
+
resolution?: number // 256, 512, 576, 720, 1080
|
58 |
+
}
|
59 |
+
|
60 |
+
export interface Job {
|
61 |
+
startedAt: string
|
62 |
+
query: ShotQuery
|
63 |
+
}
|
src/components/business/menu.tsx
ADDED
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Menubar,
|
3 |
+
MenubarCheckboxItem,
|
4 |
+
MenubarContent,
|
5 |
+
MenubarItem,
|
6 |
+
MenubarLabel,
|
7 |
+
MenubarMenu,
|
8 |
+
MenubarRadioGroup,
|
9 |
+
MenubarRadioItem,
|
10 |
+
MenubarSeparator,
|
11 |
+
MenubarShortcut,
|
12 |
+
MenubarSub,
|
13 |
+
MenubarSubContent,
|
14 |
+
MenubarSubTrigger,
|
15 |
+
MenubarTrigger,
|
16 |
+
} from "@/components/ui/menubar"
|
17 |
+
|
18 |
+
export function Menu() {
|
19 |
+
return (
|
20 |
+
<Menubar className="rounded-none border-b border-none px-2 lg:px-4">
|
21 |
+
<MenubarMenu>
|
22 |
+
<MenubarTrigger className="font-bold">Music</MenubarTrigger>
|
23 |
+
<MenubarContent>
|
24 |
+
<MenubarItem>About Music</MenubarItem>
|
25 |
+
<MenubarSeparator />
|
26 |
+
<MenubarItem>
|
27 |
+
Preferences... <MenubarShortcut>β,</MenubarShortcut>
|
28 |
+
</MenubarItem>
|
29 |
+
<MenubarSeparator />
|
30 |
+
<MenubarItem>
|
31 |
+
Hide Music... <MenubarShortcut>βH</MenubarShortcut>
|
32 |
+
</MenubarItem>
|
33 |
+
<MenubarItem>
|
34 |
+
Hide Others... <MenubarShortcut>β§βH</MenubarShortcut>
|
35 |
+
</MenubarItem>
|
36 |
+
<MenubarShortcut />
|
37 |
+
<MenubarItem>
|
38 |
+
Quit Music <MenubarShortcut>βQ</MenubarShortcut>
|
39 |
+
</MenubarItem>
|
40 |
+
</MenubarContent>
|
41 |
+
</MenubarMenu>
|
42 |
+
<MenubarMenu>
|
43 |
+
<MenubarTrigger className="relative">File</MenubarTrigger>
|
44 |
+
<MenubarContent>
|
45 |
+
<MenubarSub>
|
46 |
+
<MenubarSubTrigger>New</MenubarSubTrigger>
|
47 |
+
<MenubarSubContent className="w-[230px]">
|
48 |
+
<MenubarItem>
|
49 |
+
Playlist <MenubarShortcut>βN</MenubarShortcut>
|
50 |
+
</MenubarItem>
|
51 |
+
<MenubarItem disabled>
|
52 |
+
Playlist from Selection <MenubarShortcut>β§βN</MenubarShortcut>
|
53 |
+
</MenubarItem>
|
54 |
+
<MenubarItem>
|
55 |
+
Smart Playlist... <MenubarShortcut>β₯βN</MenubarShortcut>
|
56 |
+
</MenubarItem>
|
57 |
+
<MenubarItem>Playlist Folder</MenubarItem>
|
58 |
+
<MenubarItem disabled>Genius Playlist</MenubarItem>
|
59 |
+
</MenubarSubContent>
|
60 |
+
</MenubarSub>
|
61 |
+
<MenubarItem>
|
62 |
+
Open Stream URL... <MenubarShortcut>βU</MenubarShortcut>
|
63 |
+
</MenubarItem>
|
64 |
+
<MenubarItem>
|
65 |
+
Close Window <MenubarShortcut>βW</MenubarShortcut>
|
66 |
+
</MenubarItem>
|
67 |
+
<MenubarSeparator />
|
68 |
+
<MenubarSub>
|
69 |
+
<MenubarSubTrigger>Library</MenubarSubTrigger>
|
70 |
+
<MenubarSubContent>
|
71 |
+
<MenubarItem>Update Cloud Library</MenubarItem>
|
72 |
+
<MenubarItem>Update Genius</MenubarItem>
|
73 |
+
<MenubarSeparator />
|
74 |
+
<MenubarItem>Organize Library...</MenubarItem>
|
75 |
+
<MenubarItem>Export Library...</MenubarItem>
|
76 |
+
<MenubarSeparator />
|
77 |
+
<MenubarItem>Import Playlist...</MenubarItem>
|
78 |
+
<MenubarItem disabled>Export Playlist...</MenubarItem>
|
79 |
+
<MenubarItem>Show Duplicate Items</MenubarItem>
|
80 |
+
<MenubarSeparator />
|
81 |
+
<MenubarItem>Get Album Artwork</MenubarItem>
|
82 |
+
<MenubarItem disabled>Get Track Names</MenubarItem>
|
83 |
+
</MenubarSubContent>
|
84 |
+
</MenubarSub>
|
85 |
+
<MenubarItem>
|
86 |
+
Import... <MenubarShortcut>βO</MenubarShortcut>
|
87 |
+
</MenubarItem>
|
88 |
+
<MenubarItem disabled>Burn Playlist to Disc...</MenubarItem>
|
89 |
+
<MenubarSeparator />
|
90 |
+
<MenubarItem>
|
91 |
+
Show in Finder <MenubarShortcut>β§βR</MenubarShortcut>{" "}
|
92 |
+
</MenubarItem>
|
93 |
+
<MenubarItem>Convert</MenubarItem>
|
94 |
+
<MenubarSeparator />
|
95 |
+
<MenubarItem>Page Setup...</MenubarItem>
|
96 |
+
<MenubarItem disabled>
|
97 |
+
Print... <MenubarShortcut>βP</MenubarShortcut>
|
98 |
+
</MenubarItem>
|
99 |
+
</MenubarContent>
|
100 |
+
</MenubarMenu>
|
101 |
+
<MenubarMenu>
|
102 |
+
<MenubarTrigger>Edit</MenubarTrigger>
|
103 |
+
<MenubarContent>
|
104 |
+
<MenubarItem disabled>
|
105 |
+
Undo <MenubarShortcut>βZ</MenubarShortcut>
|
106 |
+
</MenubarItem>
|
107 |
+
<MenubarItem disabled>
|
108 |
+
Redo <MenubarShortcut>β§βZ</MenubarShortcut>
|
109 |
+
</MenubarItem>
|
110 |
+
<MenubarSeparator />
|
111 |
+
<MenubarItem disabled>
|
112 |
+
Cut <MenubarShortcut>βX</MenubarShortcut>
|
113 |
+
</MenubarItem>
|
114 |
+
<MenubarItem disabled>
|
115 |
+
Copy <MenubarShortcut>βC</MenubarShortcut>
|
116 |
+
</MenubarItem>
|
117 |
+
<MenubarItem disabled>
|
118 |
+
Paste <MenubarShortcut>βV</MenubarShortcut>
|
119 |
+
</MenubarItem>
|
120 |
+
<MenubarSeparator />
|
121 |
+
<MenubarItem>
|
122 |
+
Select All <MenubarShortcut>βA</MenubarShortcut>
|
123 |
+
</MenubarItem>
|
124 |
+
<MenubarItem disabled>
|
125 |
+
Deselect All <MenubarShortcut>β§βA</MenubarShortcut>
|
126 |
+
</MenubarItem>
|
127 |
+
<MenubarSeparator />
|
128 |
+
<MenubarItem>
|
129 |
+
Smart Dictation...{" "}
|
130 |
+
<MenubarShortcut>
|
131 |
+
<svg
|
132 |
+
xmlns="http://www.w3.org/2000/svg"
|
133 |
+
fill="none"
|
134 |
+
stroke="currentColor"
|
135 |
+
strokeLinecap="round"
|
136 |
+
strokeLinejoin="round"
|
137 |
+
strokeWidth="2"
|
138 |
+
className="h-4 w-4"
|
139 |
+
viewBox="0 0 24 24"
|
140 |
+
>
|
141 |
+
<path d="m12 8-9.04 9.06a2.82 2.82 0 1 0 3.98 3.98L16 12" />
|
142 |
+
<circle cx="17" cy="7" r="5" />
|
143 |
+
</svg>
|
144 |
+
</MenubarShortcut>
|
145 |
+
</MenubarItem>
|
146 |
+
<MenubarItem>
|
147 |
+
Emoji & Symbols{" "}
|
148 |
+
<MenubarShortcut>
|
149 |
+
<svg
|
150 |
+
xmlns="http://www.w3.org/2000/svg"
|
151 |
+
fill="none"
|
152 |
+
stroke="currentColor"
|
153 |
+
strokeLinecap="round"
|
154 |
+
strokeLinejoin="round"
|
155 |
+
strokeWidth="2"
|
156 |
+
className="h-4 w-4"
|
157 |
+
viewBox="0 0 24 24"
|
158 |
+
>
|
159 |
+
<circle cx="12" cy="12" r="10" />
|
160 |
+
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
161 |
+
</svg>
|
162 |
+
</MenubarShortcut>
|
163 |
+
</MenubarItem>
|
164 |
+
</MenubarContent>
|
165 |
+
</MenubarMenu>
|
166 |
+
<MenubarMenu>
|
167 |
+
<MenubarTrigger>View</MenubarTrigger>
|
168 |
+
<MenubarContent>
|
169 |
+
<MenubarCheckboxItem>Show Playing Next</MenubarCheckboxItem>
|
170 |
+
<MenubarCheckboxItem checked>Show Lyrics</MenubarCheckboxItem>
|
171 |
+
<MenubarSeparator />
|
172 |
+
<MenubarItem inset disabled>
|
173 |
+
Show Status Bar
|
174 |
+
</MenubarItem>
|
175 |
+
<MenubarSeparator />
|
176 |
+
<MenubarItem inset>Hide Sidebar</MenubarItem>
|
177 |
+
<MenubarItem disabled inset>
|
178 |
+
Enter Full Screen
|
179 |
+
</MenubarItem>
|
180 |
+
</MenubarContent>
|
181 |
+
</MenubarMenu>
|
182 |
+
<MenubarMenu>
|
183 |
+
<MenubarTrigger className="hidden md:block">Account</MenubarTrigger>
|
184 |
+
<MenubarContent forceMount>
|
185 |
+
<MenubarLabel inset>Switch Account</MenubarLabel>
|
186 |
+
<MenubarSeparator />
|
187 |
+
<MenubarRadioGroup value="benoit">
|
188 |
+
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
|
189 |
+
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
|
190 |
+
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
|
191 |
+
</MenubarRadioGroup>
|
192 |
+
<MenubarSeparator />
|
193 |
+
<MenubarItem inset>Manage Famliy...</MenubarItem>
|
194 |
+
<MenubarSeparator />
|
195 |
+
<MenubarItem inset>Add Account...</MenubarItem>
|
196 |
+
</MenubarContent>
|
197 |
+
</MenubarMenu>
|
198 |
+
</Menubar>
|
199 |
+
)
|
200 |
+
}
|
src/components/business/timeline/index.tsx
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import { ReactGrid, Column, Row } from "@silevis/reactgrid"
|
4 |
+
import "@silevis/reactgrid/styles.css"
|
5 |
+
import { useState } from "react"
|
6 |
+
|
7 |
+
type RowData = Record<string, string>
|
8 |
+
|
9 |
+
const nbColumns = 20
|
10 |
+
|
11 |
+
const getRowsData = (nbLayers: number, nbShots: number): RowData[] => [
|
12 |
+
{ name: "Thomas", surname: "Goldman" },
|
13 |
+
{ name: "Susie", surname: "Quattro" },
|
14 |
+
{ name: "", surname: "" }
|
15 |
+
];
|
16 |
+
|
17 |
+
const getColumns = (nbColumns: number): Column[] => {
|
18 |
+
|
19 |
+
const columns: Column[] = []
|
20 |
+
for (let i = 0; i < nbColumns; i++) {
|
21 |
+
columns.push({
|
22 |
+
columnId: `Shot ${i}`,
|
23 |
+
width: 150,
|
24 |
+
})
|
25 |
+
}
|
26 |
+
|
27 |
+
return columns
|
28 |
+
}
|
29 |
+
|
30 |
+
|
31 |
+
|
32 |
+
const getRows = (nbShots: number, rows: RowData[]): Row[] => [
|
33 |
+
{
|
34 |
+
rowId: 'header',
|
35 |
+
cells: [...Array(nbShots)].map((_, i) => ({
|
36 |
+
type: "text",
|
37 |
+
text: `Shot ${i}`,
|
38 |
+
})),
|
39 |
+
},
|
40 |
+
...rows.map<Row>((row, idx) => ({
|
41 |
+
rowId: idx,
|
42 |
+
cells: Object.entries(row).map(([_, value]) => ({
|
43 |
+
type: "text",
|
44 |
+
text: value
|
45 |
+
}))
|
46 |
+
}))
|
47 |
+
]
|
48 |
+
|
49 |
+
export function Timeline() {
|
50 |
+
|
51 |
+
const nbLayers = 8
|
52 |
+
const nbShots = 30
|
53 |
+
|
54 |
+
const [rowsData] = useState<RowData[]>(getRowsData(nbLayers, nbShots))
|
55 |
+
|
56 |
+
const rows = getRows(nbShots, rowsData)
|
57 |
+
const columns = getColumns(nbShots)
|
58 |
+
|
59 |
+
return (
|
60 |
+
<ReactGrid
|
61 |
+
rows={rows}
|
62 |
+
columns={columns}
|
63 |
+
onCellsChanged={(changes) => {
|
64 |
+
const change = changes[0]
|
65 |
+
const { columnId, newCell, previousCell, rowId, type } = change
|
66 |
+
|
67 |
+
console.log('change:', { columnId, newCell, previousCell, rowId, type })
|
68 |
+
}}
|
69 |
+
/>
|
70 |
+
)
|
71 |
+
}
|
src/components/ui/accordion.tsx
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
5 |
+
import { ChevronDown } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
|
9 |
+
const Accordion = AccordionPrimitive.Root
|
10 |
+
|
11 |
+
const AccordionItem = React.forwardRef<
|
12 |
+
React.ElementRef<typeof AccordionPrimitive.Item>,
|
13 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
14 |
+
>(({ className, ...props }, ref) => (
|
15 |
+
<AccordionPrimitive.Item
|
16 |
+
ref={ref}
|
17 |
+
className={cn("border-b", className)}
|
18 |
+
{...props}
|
19 |
+
/>
|
20 |
+
))
|
21 |
+
AccordionItem.displayName = "AccordionItem"
|
22 |
+
|
23 |
+
const AccordionTrigger = React.forwardRef<
|
24 |
+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
25 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
26 |
+
>(({ className, children, ...props }, ref) => (
|
27 |
+
<AccordionPrimitive.Header className="flex">
|
28 |
+
<AccordionPrimitive.Trigger
|
29 |
+
ref={ref}
|
30 |
+
className={cn(
|
31 |
+
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
32 |
+
className
|
33 |
+
)}
|
34 |
+
{...props}
|
35 |
+
>
|
36 |
+
{children}
|
37 |
+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
38 |
+
</AccordionPrimitive.Trigger>
|
39 |
+
</AccordionPrimitive.Header>
|
40 |
+
))
|
41 |
+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
42 |
+
|
43 |
+
const AccordionContent = React.forwardRef<
|
44 |
+
React.ElementRef<typeof AccordionPrimitive.Content>,
|
45 |
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
46 |
+
>(({ className, children, ...props }, ref) => (
|
47 |
+
<AccordionPrimitive.Content
|
48 |
+
ref={ref}
|
49 |
+
className={cn(
|
50 |
+
"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
51 |
+
className
|
52 |
+
)}
|
53 |
+
{...props}
|
54 |
+
>
|
55 |
+
<div className="pb-4 pt-0">{children}</div>
|
56 |
+
</AccordionPrimitive.Content>
|
57 |
+
))
|
58 |
+
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
59 |
+
|
60 |
+
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 border-zinc-200 p-4 [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-zinc-950 dark:border-zinc-800 dark:[&>svg]:text-zinc-50",
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default: "bg-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50",
|
12 |
+
destructive:
|
13 |
+
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
|
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/badge.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
3 |
+
|
4 |
+
import { cn } from "@/lib/utils"
|
5 |
+
|
6 |
+
const badgeVariants = cva(
|
7 |
+
"inline-flex items-center rounded-full border border-zinc-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:border-zinc-800 dark:focus:ring-zinc-800",
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default:
|
12 |
+
"border-transparent bg-zinc-900 text-zinc-50 hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/80",
|
13 |
+
secondary:
|
14 |
+
"border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
|
15 |
+
destructive:
|
16 |
+
"border-transparent bg-red-500 text-zinc-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/80",
|
17 |
+
outline: "text-zinc-950 dark:text-zinc-50",
|
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/button.tsx
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const buttonVariants = cva(
|
8 |
+
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-800",
|
9 |
+
{
|
10 |
+
variants: {
|
11 |
+
variant: {
|
12 |
+
default: "bg-zinc-900 text-zinc-50 hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90",
|
13 |
+
destructive:
|
14 |
+
"bg-red-500 text-zinc-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/90",
|
15 |
+
outline:
|
16 |
+
"border border-zinc-200 bg-white hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
|
17 |
+
secondary:
|
18 |
+
"bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
|
19 |
+
ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
|
20 |
+
link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50",
|
21 |
+
},
|
22 |
+
size: {
|
23 |
+
default: "h-10 px-4 py-2",
|
24 |
+
sm: "h-9 rounded-md px-3",
|
25 |
+
lg: "h-11 rounded-md px-8",
|
26 |
+
icon: "h-10 w-10",
|
27 |
+
},
|
28 |
+
},
|
29 |
+
defaultVariants: {
|
30 |
+
variant: "default",
|
31 |
+
size: "default",
|
32 |
+
},
|
33 |
+
}
|
34 |
+
)
|
35 |
+
|
36 |
+
export interface ButtonProps
|
37 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
38 |
+
VariantProps<typeof buttonVariants> {
|
39 |
+
asChild?: boolean
|
40 |
+
}
|
41 |
+
|
42 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
43 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
44 |
+
const Comp = asChild ? Slot : "button"
|
45 |
+
return (
|
46 |
+
<Comp
|
47 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
48 |
+
ref={ref}
|
49 |
+
{...props}
|
50 |
+
/>
|
51 |
+
)
|
52 |
+
}
|
53 |
+
)
|
54 |
+
Button.displayName = "Button"
|
55 |
+
|
56 |
+
export { Button, buttonVariants }
|
src/components/ui/calendar.tsx
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import { ChevronLeft, ChevronRight } from "lucide-react"
|
5 |
+
import { DayPicker } from "react-day-picker"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
import { buttonVariants } from "@/components/ui/button"
|
9 |
+
|
10 |
+
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
11 |
+
|
12 |
+
function Calendar({
|
13 |
+
className,
|
14 |
+
classNames,
|
15 |
+
showOutsideDays = true,
|
16 |
+
...props
|
17 |
+
}: CalendarProps) {
|
18 |
+
return (
|
19 |
+
<DayPicker
|
20 |
+
showOutsideDays={showOutsideDays}
|
21 |
+
className={cn("p-3", className)}
|
22 |
+
classNames={{
|
23 |
+
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
24 |
+
month: "space-y-4",
|
25 |
+
caption: "flex justify-center pt-1 relative items-center",
|
26 |
+
caption_label: "text-sm font-medium",
|
27 |
+
nav: "space-x-1 flex items-center",
|
28 |
+
nav_button: cn(
|
29 |
+
buttonVariants({ variant: "outline" }),
|
30 |
+
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
31 |
+
),
|
32 |
+
nav_button_previous: "absolute left-1",
|
33 |
+
nav_button_next: "absolute right-1",
|
34 |
+
table: "w-full border-collapse space-y-1",
|
35 |
+
head_row: "flex",
|
36 |
+
head_cell:
|
37 |
+
"text-zinc-500 rounded-md w-9 font-normal text-[0.8rem] dark:text-zinc-400",
|
38 |
+
row: "flex w-full mt-2",
|
39 |
+
cell: "text-center text-sm p-0 relative [&:has([aria-selected])]:bg-zinc-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20 dark:[&:has([aria-selected])]:bg-zinc-800",
|
40 |
+
day: cn(
|
41 |
+
buttonVariants({ variant: "ghost" }),
|
42 |
+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
43 |
+
),
|
44 |
+
day_selected:
|
45 |
+
"bg-zinc-900 text-zinc-50 hover:bg-zinc-900 hover:text-zinc-50 focus:bg-zinc-900 focus:text-zinc-50 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50 dark:hover:text-zinc-900 dark:focus:bg-zinc-50 dark:focus:text-zinc-900",
|
46 |
+
day_today: "bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-zinc-50",
|
47 |
+
day_outside: "text-zinc-500 opacity-50 dark:text-zinc-400",
|
48 |
+
day_disabled: "text-zinc-500 opacity-50 dark:text-zinc-400",
|
49 |
+
day_range_middle:
|
50 |
+
"aria-selected:bg-zinc-100 aria-selected:text-zinc-900 dark:aria-selected:bg-zinc-800 dark:aria-selected:text-zinc-50",
|
51 |
+
day_hidden: "invisible",
|
52 |
+
...classNames,
|
53 |
+
}}
|
54 |
+
components={{
|
55 |
+
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
|
56 |
+
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
|
57 |
+
}}
|
58 |
+
{...props}
|
59 |
+
/>
|
60 |
+
)
|
61 |
+
}
|
62 |
+
Calendar.displayName = "Calendar"
|
63 |
+
|
64 |
+
export { Calendar }
|
src/components/ui/card.tsx
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
|
3 |
+
import { cn } from "@/lib/utils"
|
4 |
+
|
5 |
+
const Card = React.forwardRef<
|
6 |
+
HTMLDivElement,
|
7 |
+
React.HTMLAttributes<HTMLDivElement>
|
8 |
+
>(({ className, ...props }, ref) => (
|
9 |
+
<div
|
10 |
+
ref={ref}
|
11 |
+
className={cn(
|
12 |
+
"rounded-lg border border-zinc-200 bg-white text-zinc-950 shadow-sm dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
|
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 |
+
HTMLParagraphElement,
|
34 |
+
React.HTMLAttributes<HTMLHeadingElement>
|
35 |
+
>(({ className, ...props }, ref) => (
|
36 |
+
<h3
|
37 |
+
ref={ref}
|
38 |
+
className={cn(
|
39 |
+
"text-2xl font-semibold leading-none tracking-tight",
|
40 |
+
className
|
41 |
+
)}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
))
|
45 |
+
CardTitle.displayName = "CardTitle"
|
46 |
+
|
47 |
+
const CardDescription = React.forwardRef<
|
48 |
+
HTMLParagraphElement,
|
49 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
50 |
+
>(({ className, ...props }, ref) => (
|
51 |
+
<p
|
52 |
+
ref={ref}
|
53 |
+
className={cn("text-sm text-zinc-500 dark:text-zinc-400", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
))
|
57 |
+
CardDescription.displayName = "CardDescription"
|
58 |
+
|
59 |
+
const CardContent = React.forwardRef<
|
60 |
+
HTMLDivElement,
|
61 |
+
React.HTMLAttributes<HTMLDivElement>
|
62 |
+
>(({ className, ...props }, ref) => (
|
63 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
64 |
+
))
|
65 |
+
CardContent.displayName = "CardContent"
|
66 |
+
|
67 |
+
const CardFooter = React.forwardRef<
|
68 |
+
HTMLDivElement,
|
69 |
+
React.HTMLAttributes<HTMLDivElement>
|
70 |
+
>(({ className, ...props }, ref) => (
|
71 |
+
<div
|
72 |
+
ref={ref}
|
73 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
74 |
+
{...props}
|
75 |
+
/>
|
76 |
+
))
|
77 |
+
CardFooter.displayName = "CardFooter"
|
78 |
+
|
79 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
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-zinc-200 border-zinc-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-zinc-900 data-[state=checked]:text-zinc-50 dark:border-zinc-800 dark:border-zinc-50 dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-800 dark:data-[state=checked]:bg-zinc-50 dark:data-[state=checked]:text-zinc-900",
|
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,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
4 |
+
|
5 |
+
const Collapsible = CollapsiblePrimitive.Root
|
6 |
+
|
7 |
+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
8 |
+
|
9 |
+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
10 |
+
|
11 |
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
src/components/ui/command.tsx
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import { 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-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50",
|
19 |
+
className
|
20 |
+
)}
|
21 |
+
{...props}
|
22 |
+
/>
|
23 |
+
))
|
24 |
+
Command.displayName = CommandPrimitive.displayName
|
25 |
+
|
26 |
+
interface CommandDialogProps extends DialogProps {}
|
27 |
+
|
28 |
+
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
29 |
+
return (
|
30 |
+
<Dialog {...props}>
|
31 |
+
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
32 |
+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-zinc-500 [&_[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 dark:[&_[cmdk-group-heading]]:text-zinc-400">
|
33 |
+
{children}
|
34 |
+
</Command>
|
35 |
+
</DialogContent>
|
36 |
+
</Dialog>
|
37 |
+
)
|
38 |
+
}
|
39 |
+
|
40 |
+
const CommandInput = React.forwardRef<
|
41 |
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
42 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
43 |
+
>(({ className, ...props }, ref) => (
|
44 |
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
45 |
+
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
46 |
+
<CommandPrimitive.Input
|
47 |
+
ref={ref}
|
48 |
+
className={cn(
|
49 |
+
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-zinc-500 disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-zinc-400",
|
50 |
+
className
|
51 |
+
)}
|
52 |
+
{...props}
|
53 |
+
/>
|
54 |
+
</div>
|
55 |
+
))
|
56 |
+
|
57 |
+
CommandInput.displayName = CommandPrimitive.Input.displayName
|
58 |
+
|
59 |
+
const CommandList = React.forwardRef<
|
60 |
+
React.ElementRef<typeof CommandPrimitive.List>,
|
61 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
62 |
+
>(({ className, ...props }, ref) => (
|
63 |
+
<CommandPrimitive.List
|
64 |
+
ref={ref}
|
65 |
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
66 |
+
{...props}
|
67 |
+
/>
|
68 |
+
))
|
69 |
+
|
70 |
+
CommandList.displayName = CommandPrimitive.List.displayName
|
71 |
+
|
72 |
+
const CommandEmpty = React.forwardRef<
|
73 |
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
74 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
75 |
+
>((props, ref) => (
|
76 |
+
<CommandPrimitive.Empty
|
77 |
+
ref={ref}
|
78 |
+
className="py-6 text-center text-sm"
|
79 |
+
{...props}
|
80 |
+
/>
|
81 |
+
))
|
82 |
+
|
83 |
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
84 |
+
|
85 |
+
const CommandGroup = React.forwardRef<
|
86 |
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
87 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
88 |
+
>(({ className, ...props }, ref) => (
|
89 |
+
<CommandPrimitive.Group
|
90 |
+
ref={ref}
|
91 |
+
className={cn(
|
92 |
+
"overflow-hidden p-1 text-zinc-950 [&_[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-zinc-500 dark:text-zinc-50 dark:[&_[cmdk-group-heading]]:text-zinc-400",
|
93 |
+
className
|
94 |
+
)}
|
95 |
+
{...props}
|
96 |
+
/>
|
97 |
+
))
|
98 |
+
|
99 |
+
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
100 |
+
|
101 |
+
const CommandSeparator = React.forwardRef<
|
102 |
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
103 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
104 |
+
>(({ className, ...props }, ref) => (
|
105 |
+
<CommandPrimitive.Separator
|
106 |
+
ref={ref}
|
107 |
+
className={cn("-mx-1 h-px bg-zinc-200 dark:bg-zinc-800", className)}
|
108 |
+
{...props}
|
109 |
+
/>
|
110 |
+
))
|
111 |
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
112 |
+
|
113 |
+
const CommandItem = React.forwardRef<
|
114 |
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
115 |
+
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
116 |
+
>(({ className, ...props }, ref) => (
|
117 |
+
<CommandPrimitive.Item
|
118 |
+
ref={ref}
|
119 |
+
className={cn(
|
120 |
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-zinc-100 aria-selected:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:aria-selected:bg-zinc-800 dark:aria-selected:text-zinc-50",
|
121 |
+
className
|
122 |
+
)}
|
123 |
+
{...props}
|
124 |
+
/>
|
125 |
+
))
|
126 |
+
|
127 |
+
CommandItem.displayName = CommandPrimitive.Item.displayName
|
128 |
+
|
129 |
+
const CommandShortcut = ({
|
130 |
+
className,
|
131 |
+
...props
|
132 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
133 |
+
return (
|
134 |
+
<span
|
135 |
+
className={cn(
|
136 |
+
"ml-auto text-xs tracking-widest text-zinc-500 dark:text-zinc-400",
|
137 |
+
className
|
138 |
+
)}
|
139 |
+
{...props}
|
140 |
+
/>
|
141 |
+
)
|
142 |
+
}
|
143 |
+
CommandShortcut.displayName = "CommandShortcut"
|
144 |
+
|
145 |
+
export {
|
146 |
+
Command,
|
147 |
+
CommandDialog,
|
148 |
+
CommandInput,
|
149 |
+
CommandList,
|
150 |
+
CommandEmpty,
|
151 |
+
CommandGroup,
|
152 |
+
CommandItem,
|
153 |
+
CommandShortcut,
|
154 |
+
CommandSeparator,
|
155 |
+
}
|
src/components/ui/dialog.tsx
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
5 |
+
import { X } from "lucide-react"
|
6 |
+
|
7 |
+
import { cn } from "@/lib/utils"
|
8 |
+
|
9 |
+
const Dialog = DialogPrimitive.Root
|
10 |
+
|
11 |
+
const DialogTrigger = DialogPrimitive.Trigger
|
12 |
+
|
13 |
+
const DialogPortal = ({
|
14 |
+
className,
|
15 |
+
...props
|
16 |
+
}: DialogPrimitive.DialogPortalProps) => (
|
17 |
+
<DialogPrimitive.Portal className={cn(className)} {...props} />
|
18 |
+
)
|
19 |
+
DialogPortal.displayName = DialogPrimitive.Portal.displayName
|
20 |
+
|
21 |
+
const DialogOverlay = React.forwardRef<
|
22 |
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
23 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
24 |
+
>(({ className, ...props }, ref) => (
|
25 |
+
<DialogPrimitive.Overlay
|
26 |
+
ref={ref}
|
27 |
+
className={cn(
|
28 |
+
"fixed inset-0 z-50 bg-white/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 dark:bg-zinc-950/80",
|
29 |
+
className
|
30 |
+
)}
|
31 |
+
{...props}
|
32 |
+
/>
|
33 |
+
))
|
34 |
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
35 |
+
|
36 |
+
const DialogContent = React.forwardRef<
|
37 |
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
38 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
39 |
+
>(({ className, children, ...props }, ref) => (
|
40 |
+
<DialogPortal>
|
41 |
+
<DialogOverlay />
|
42 |
+
<DialogPrimitive.Content
|
43 |
+
ref={ref}
|
44 |
+
className={cn(
|
45 |
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-zinc-200 bg-white 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 md:w-full dark:border-zinc-800 dark:bg-zinc-950",
|
46 |
+
className
|
47 |
+
)}
|
48 |
+
{...props}
|
49 |
+
>
|
50 |
+
{children}
|
51 |
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-zinc-100 data-[state=open]:text-zinc-500 dark:ring-offset-zinc-950 dark:focus:ring-zinc-800 dark:data-[state=open]:bg-zinc-800 dark:data-[state=open]:text-zinc-400">
|
52 |
+
<X className="h-4 w-4" />
|
53 |
+
<span className="sr-only">Close</span>
|
54 |
+
</DialogPrimitive.Close>
|
55 |
+
</DialogPrimitive.Content>
|
56 |
+
</DialogPortal>
|
57 |
+
))
|
58 |
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
59 |
+
|
60 |
+
const DialogHeader = ({
|
61 |
+
className,
|
62 |
+
...props
|
63 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
64 |
+
<div
|
65 |
+
className={cn(
|
66 |
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
67 |
+
className
|
68 |
+
)}
|
69 |
+
{...props}
|
70 |
+
/>
|
71 |
+
)
|
72 |
+
DialogHeader.displayName = "DialogHeader"
|
73 |
+
|
74 |
+
const DialogFooter = ({
|
75 |
+
className,
|
76 |
+
...props
|
77 |
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
78 |
+
<div
|
79 |
+
className={cn(
|
80 |
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
81 |
+
className
|
82 |
+
)}
|
83 |
+
{...props}
|
84 |
+
/>
|
85 |
+
)
|
86 |
+
DialogFooter.displayName = "DialogFooter"
|
87 |
+
|
88 |
+
const DialogTitle = React.forwardRef<
|
89 |
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
90 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
91 |
+
>(({ className, ...props }, ref) => (
|
92 |
+
<DialogPrimitive.Title
|
93 |
+
ref={ref}
|
94 |
+
className={cn(
|
95 |
+
"text-lg font-semibold leading-none tracking-tight",
|
96 |
+
className
|
97 |
+
)}
|
98 |
+
{...props}
|
99 |
+
/>
|
100 |
+
))
|
101 |
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
102 |
+
|
103 |
+
const DialogDescription = React.forwardRef<
|
104 |
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
105 |
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
106 |
+
>(({ className, ...props }, ref) => (
|
107 |
+
<DialogPrimitive.Description
|
108 |
+
ref={ref}
|
109 |
+
className={cn("text-sm text-zinc-500 dark:text-zinc-400", className)}
|
110 |
+
{...props}
|
111 |
+
/>
|
112 |
+
))
|
113 |
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
114 |
+
|
115 |
+
export {
|
116 |
+
Dialog,
|
117 |
+
DialogTrigger,
|
118 |
+
DialogContent,
|
119 |
+
DialogHeader,
|
120 |
+
DialogFooter,
|
121 |
+
DialogTitle,
|
122 |
+
DialogDescription,
|
123 |
+
}
|
src/components/ui/menubar.tsx
ADDED
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
const MenubarMenu = MenubarPrimitive.Menu
|
10 |
+
|
11 |
+
const MenubarGroup = MenubarPrimitive.Group
|
12 |
+
|
13 |
+
const MenubarPortal = MenubarPrimitive.Portal
|
14 |
+
|
15 |
+
const MenubarSub = MenubarPrimitive.Sub
|
16 |
+
|
17 |
+
const MenubarRadioGroup = MenubarPrimitive.RadioGroup
|
18 |
+
|
19 |
+
const Menubar = React.forwardRef<
|
20 |
+
React.ElementRef<typeof MenubarPrimitive.Root>,
|
21 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
|
22 |
+
>(({ className, ...props }, ref) => (
|
23 |
+
<MenubarPrimitive.Root
|
24 |
+
ref={ref}
|
25 |
+
className={cn(
|
26 |
+
"flex h-10 items-center space-x-1 rounded-md border border-zinc-200 bg-white p-1 dark:border-zinc-800 dark:bg-zinc-950",
|
27 |
+
className
|
28 |
+
)}
|
29 |
+
{...props}
|
30 |
+
/>
|
31 |
+
))
|
32 |
+
Menubar.displayName = MenubarPrimitive.Root.displayName
|
33 |
+
|
34 |
+
const MenubarTrigger = React.forwardRef<
|
35 |
+
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
36 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
|
37 |
+
>(({ className, ...props }, ref) => (
|
38 |
+
<MenubarPrimitive.Trigger
|
39 |
+
ref={ref}
|
40 |
+
className={cn(
|
41 |
+
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-zinc-100 focus:text-zinc-900 data-[state=open]:bg-zinc-100 data-[state=open]:text-zinc-900 dark:focus:bg-zinc-800 dark:focus:text-zinc-50 dark:data-[state=open]:bg-zinc-800 dark:data-[state=open]:text-zinc-50",
|
42 |
+
className
|
43 |
+
)}
|
44 |
+
{...props}
|
45 |
+
/>
|
46 |
+
))
|
47 |
+
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
|
48 |
+
|
49 |
+
const MenubarSubTrigger = React.forwardRef<
|
50 |
+
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
|
51 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
|
52 |
+
inset?: boolean
|
53 |
+
}
|
54 |
+
>(({ className, inset, children, ...props }, ref) => (
|
55 |
+
<MenubarPrimitive.SubTrigger
|
56 |
+
ref={ref}
|
57 |
+
className={cn(
|
58 |
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-zinc-100 focus:text-zinc-900 data-[state=open]:bg-zinc-100 data-[state=open]:text-zinc-900 dark:focus:bg-zinc-800 dark:focus:text-zinc-50 dark:data-[state=open]:bg-zinc-800 dark:data-[state=open]:text-zinc-50",
|
59 |
+
inset && "pl-8",
|
60 |
+
className
|
61 |
+
)}
|
62 |
+
{...props}
|
63 |
+
>
|
64 |
+
{children}
|
65 |
+
<ChevronRight className="ml-auto h-4 w-4" />
|
66 |
+
</MenubarPrimitive.SubTrigger>
|
67 |
+
))
|
68 |
+
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
|
69 |
+
|
70 |
+
const MenubarSubContent = React.forwardRef<
|
71 |
+
React.ElementRef<typeof MenubarPrimitive.SubContent>,
|
72 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
|
73 |
+
>(({ className, ...props }, ref) => (
|
74 |
+
<MenubarPrimitive.SubContent
|
75 |
+
ref={ref}
|
76 |
+
className={cn(
|
77 |
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-zinc-200 bg-white p-1 text-zinc-950 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 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
|
78 |
+
className
|
79 |
+
)}
|
80 |
+
{...props}
|
81 |
+
/>
|
82 |
+
))
|
83 |
+
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
|
84 |
+
|
85 |
+
const MenubarContent = React.forwardRef<
|
86 |
+
React.ElementRef<typeof MenubarPrimitive.Content>,
|
87 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
|
88 |
+
>(
|
89 |
+
(
|
90 |
+
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
|
91 |
+
ref
|
92 |
+
) => (
|
93 |
+
<MenubarPrimitive.Portal>
|
94 |
+
<MenubarPrimitive.Content
|
95 |
+
ref={ref}
|
96 |
+
align={align}
|
97 |
+
alignOffset={alignOffset}
|
98 |
+
sideOffset={sideOffset}
|
99 |
+
className={cn(
|
100 |
+
"z-50 min-w-[12rem] overflow-hidden rounded-md border border-zinc-200 bg-white p-1 text-zinc-950 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 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
|
101 |
+
className
|
102 |
+
)}
|
103 |
+
{...props}
|
104 |
+
/>
|
105 |
+
</MenubarPrimitive.Portal>
|
106 |
+
)
|
107 |
+
)
|
108 |
+
MenubarContent.displayName = MenubarPrimitive.Content.displayName
|
109 |
+
|
110 |
+
const MenubarItem = React.forwardRef<
|
111 |
+
React.ElementRef<typeof MenubarPrimitive.Item>,
|
112 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
|
113 |
+
inset?: boolean
|
114 |
+
}
|
115 |
+
>(({ className, inset, ...props }, ref) => (
|
116 |
+
<MenubarPrimitive.Item
|
117 |
+
ref={ref}
|
118 |
+
className={cn(
|
119 |
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-50",
|
120 |
+
inset && "pl-8",
|
121 |
+
className
|
122 |
+
)}
|
123 |
+
{...props}
|
124 |
+
/>
|
125 |
+
))
|
126 |
+
MenubarItem.displayName = MenubarPrimitive.Item.displayName
|
127 |
+
|
128 |
+
const MenubarCheckboxItem = React.forwardRef<
|
129 |
+
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
|
130 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
|
131 |
+
>(({ className, children, checked, ...props }, ref) => (
|
132 |
+
<MenubarPrimitive.CheckboxItem
|
133 |
+
ref={ref}
|
134 |
+
className={cn(
|
135 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-50",
|
136 |
+
className
|
137 |
+
)}
|
138 |
+
checked={checked}
|
139 |
+
{...props}
|
140 |
+
>
|
141 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
142 |
+
<MenubarPrimitive.ItemIndicator>
|
143 |
+
<Check className="h-4 w-4" />
|
144 |
+
</MenubarPrimitive.ItemIndicator>
|
145 |
+
</span>
|
146 |
+
{children}
|
147 |
+
</MenubarPrimitive.CheckboxItem>
|
148 |
+
))
|
149 |
+
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
|
150 |
+
|
151 |
+
const MenubarRadioItem = React.forwardRef<
|
152 |
+
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
|
153 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
|
154 |
+
>(({ className, children, ...props }, ref) => (
|
155 |
+
<MenubarPrimitive.RadioItem
|
156 |
+
ref={ref}
|
157 |
+
className={cn(
|
158 |
+
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-50",
|
159 |
+
className
|
160 |
+
)}
|
161 |
+
{...props}
|
162 |
+
>
|
163 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
164 |
+
<MenubarPrimitive.ItemIndicator>
|
165 |
+
<Circle className="h-2 w-2 fill-current" />
|
166 |
+
</MenubarPrimitive.ItemIndicator>
|
167 |
+
</span>
|
168 |
+
{children}
|
169 |
+
</MenubarPrimitive.RadioItem>
|
170 |
+
))
|
171 |
+
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
|
172 |
+
|
173 |
+
const MenubarLabel = React.forwardRef<
|
174 |
+
React.ElementRef<typeof MenubarPrimitive.Label>,
|
175 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
|
176 |
+
inset?: boolean
|
177 |
+
}
|
178 |
+
>(({ className, inset, ...props }, ref) => (
|
179 |
+
<MenubarPrimitive.Label
|
180 |
+
ref={ref}
|
181 |
+
className={cn(
|
182 |
+
"px-2 py-1.5 text-sm font-semibold",
|
183 |
+
inset && "pl-8",
|
184 |
+
className
|
185 |
+
)}
|
186 |
+
{...props}
|
187 |
+
/>
|
188 |
+
))
|
189 |
+
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
|
190 |
+
|
191 |
+
const MenubarSeparator = React.forwardRef<
|
192 |
+
React.ElementRef<typeof MenubarPrimitive.Separator>,
|
193 |
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
|
194 |
+
>(({ className, ...props }, ref) => (
|
195 |
+
<MenubarPrimitive.Separator
|
196 |
+
ref={ref}
|
197 |
+
className={cn("-mx-1 my-1 h-px bg-zinc-100 dark:bg-zinc-800", className)}
|
198 |
+
{...props}
|
199 |
+
/>
|
200 |
+
))
|
201 |
+
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
|
202 |
+
|
203 |
+
const MenubarShortcut = ({
|
204 |
+
className,
|
205 |
+
...props
|
206 |
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
207 |
+
return (
|
208 |
+
<span
|
209 |
+
className={cn(
|
210 |
+
"ml-auto text-xs tracking-widest text-zinc-500 dark:text-zinc-400",
|
211 |
+
className
|
212 |
+
)}
|
213 |
+
{...props}
|
214 |
+
/>
|
215 |
+
)
|
216 |
+
}
|
217 |
+
MenubarShortcut.displayname = "MenubarShortcut"
|
218 |
+
|
219 |
+
export {
|
220 |
+
Menubar,
|
221 |
+
MenubarMenu,
|
222 |
+
MenubarTrigger,
|
223 |
+
MenubarContent,
|
224 |
+
MenubarItem,
|
225 |
+
MenubarSeparator,
|
226 |
+
MenubarLabel,
|
227 |
+
MenubarCheckboxItem,
|
228 |
+
MenubarRadioGroup,
|
229 |
+
MenubarRadioItem,
|
230 |
+
MenubarPortal,
|
231 |
+
MenubarSubContent,
|
232 |
+
MenubarSubTrigger,
|
233 |
+
MenubarGroup,
|
234 |
+
MenubarSub,
|
235 |
+
MenubarShortcut,
|
236 |
+
}
|
src/core/goodWords.ts
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { promises as fs } from "node:fs"
|
2 |
+
|
3 |
+
type GoodWordsDB = Record<string, Record<string, string[]>>
|
4 |
+
|
5 |
+
// import ngrams from "talisman/tokenizers/ngrams"
|
6 |
+
|
7 |
+
let db: GoodWordsDB = {}
|
8 |
+
|
9 |
+
export let goodWords: Set<string> = new Set()
|
10 |
+
export let goodWordsList: string[] = []
|
11 |
+
|
12 |
+
export const getGoodWords = async () =>Β {
|
13 |
+
|
14 |
+
if (!Object.entries(db)) {
|
15 |
+
const dbFileContent = await fs.readFile("./data/good_words.json", "utf8")
|
16 |
+
|
17 |
+
db = JSON.parse(dbFileContent) as GoodWordsDB
|
18 |
+
|
19 |
+
|
20 |
+
// we don't want those categories to be part of the acceptable, fair use words
|
21 |
+
const unwantedCategories = {
|
22 |
+
celeb: true
|
23 |
+
} as Record<string, boolean>
|
24 |
+
|
25 |
+
for (const [category, words] of Object.entries(db)) {
|
26 |
+
if (unwantedCategories[category]) {
|
27 |
+
continue
|
28 |
+
}
|
29 |
+
|
30 |
+
for (const word in words) {
|
31 |
+
const normalizedWord = word.trim().toLowerCase()
|
32 |
+
goodWords.add(normalizedWord)
|
33 |
+
goodWordsList.push(normalizedWord)
|
34 |
+
}
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
return { db, goodWords, goodWordsList }
|
39 |
+
}
|
src/core/hasBadWords.ts
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
// as the tool will be public, we need some level of moderation to bred unfair use
|
3 |
+
// note: another possibility could be to use the moderation api from openai,
|
4 |
+
// but it is paying
|
5 |
+
const config = JSON.parse(`${process.env.VS_MODERATION_FILTER_PATTERN}`)
|
6 |
+
|
7 |
+
export const hasBadWords = (userPrompt: string) => {
|
8 |
+
const normalized = userPrompt.trim().toLowerCase()
|
9 |
+
return !!config[normalized]
|
10 |
+
}
|
src/core/interpolateVideo.ts
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { promises as fs } from "node:fs"
|
2 |
+
import path from "node:path"
|
3 |
+
import { Blob } from "buffer"
|
4 |
+
// import { blobFrom } from "fetch-blob"
|
5 |
+
|
6 |
+
import { client } from "@gradio/client"
|
7 |
+
import tmpDir from "temp-dir"
|
8 |
+
|
9 |
+
import { downloadVideo } from "./downloadVideo"
|
10 |
+
|
11 |
+
const instances: string[] = [
|
12 |
+
`${process.env.VS_VIDEO_INTERPOLATION_SPACE_API_URL}`
|
13 |
+
]
|
14 |
+
|
15 |
+
export const interpolateVideo = async (fileName: string) => {
|
16 |
+
|
17 |
+
const inputFilePath = path.join(tmpDir, fileName)
|
18 |
+
|
19 |
+
const instance = instances.shift()!
|
20 |
+
instances.push(instance)
|
21 |
+
|
22 |
+
const api = await client(instance)
|
23 |
+
|
24 |
+
const video = await fs.readFile(inputFilePath)
|
25 |
+
|
26 |
+
const blob = new Blob([video], { type: 'video/mp4' })
|
27 |
+
// const blob = blobFrom(filePath)
|
28 |
+
const result = await api.predict(1, [
|
29 |
+
blob, // blob in 'parameter_5' Video component
|
30 |
+
1, // number (numeric value between 1 and 4) in 'Interpolation Steps' Slider component
|
31 |
+
24, // string in 'FPS output' Radio component
|
32 |
+
])
|
33 |
+
|
34 |
+
const data = (result as any).data[0]
|
35 |
+
console.log('raw data:', data)
|
36 |
+
const { orig_name, data: remoteFilePath } = data
|
37 |
+
const remoteUrl = `${instance}/file=${remoteFilePath}`
|
38 |
+
console.log("remoteUrl:", remoteUrl)
|
39 |
+
await downloadVideo(remoteUrl, fileName)
|
40 |
+
}
|
src/core/isGoodWord.ts
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { getGoodWords } from "./goodWords"
|
2 |
+
|
3 |
+
//
|
4 |
+
export const isGoodWord = async (word: string) => {
|
5 |
+
const { goodWords } = await getGoodWords()
|
6 |
+
return goodWords.has(word.trim().toLowerCase())
|
7 |
+
}
|
src/lib/utils.ts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { type ClassValue, clsx } from "clsx"
|
2 |
+
import { twMerge } from "tailwind-merge"
|
3 |
+
|
4 |
+
export function cn(...inputs: ClassValue[]) {
|
5 |
+
return twMerge(clsx(inputs))
|
6 |
+
}
|
tailwind.config.js
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** @type {import('tailwindcss').Config} */
|
2 |
+
module.exports = {
|
3 |
+
darkMode: ["class"],
|
4 |
+
content: [
|
5 |
+
'./pages/**/*.{ts,tsx}',
|
6 |
+
'./components/**/*.{ts,tsx}',
|
7 |
+
'./app/**/*.{ts,tsx}',
|
8 |
+
'./src/**/*.{ts,tsx}',
|
9 |
+
],
|
10 |
+
theme: {
|
11 |
+
container: {
|
12 |
+
center: true,
|
13 |
+
padding: "2rem",
|
14 |
+
screens: {
|
15 |
+
"2xl": "1400px",
|
16 |
+
},
|
17 |
+
},
|
18 |
+
extend: {
|
19 |
+
keyframes: {
|
20 |
+
"accordion-down": {
|
21 |
+
from: { height: 0 },
|
22 |
+
to: { height: "var(--radix-accordion-content-height)" },
|
23 |
+
},
|
24 |
+
"accordion-up": {
|
25 |
+
from: { height: "var(--radix-accordion-content-height)" },
|
26 |
+
to: { height: 0 },
|
27 |
+
},
|
28 |
+
},
|
29 |
+
animation: {
|
30 |
+
"accordion-down": "accordion-down 0.2s ease-out",
|
31 |
+
"accordion-up": "accordion-up 0.2s ease-out",
|
32 |
+
},
|
33 |
+
},
|
34 |
+
},
|
35 |
+
plugins: [require("tailwindcss-animate")],
|
36 |
+
}
|
tsconfig.json
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "es5",
|
4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
5 |
+
"allowJs": true,
|
6 |
+
"skipLibCheck": true,
|
7 |
+
"strict": true,
|
8 |
+
"forceConsistentCasingInFileNames": true,
|
9 |
+
"noEmit": true,
|
10 |
+
"esModuleInterop": true,
|
11 |
+
"module": "esnext",
|
12 |
+
"moduleResolution": "node",
|
13 |
+
"resolveJsonModule": true,
|
14 |
+
"isolatedModules": true,
|
15 |
+
"jsx": "preserve",
|
16 |
+
"incremental": true,
|
17 |
+
"plugins": [
|
18 |
+
{
|
19 |
+
"name": "next"
|
20 |
+
}
|
21 |
+
],
|
22 |
+
"paths": {
|
23 |
+
"@/*": ["./src/*"]
|
24 |
+
}
|
25 |
+
},
|
26 |
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
27 |
+
"exclude": ["node_modules"]
|
28 |
+
}
|