mirror of
https://github.com/JOYCEQL/magic-resume.git
synced 2026-07-03 14:07:11 +02:00
@@ -1,7 +1,15 @@
|
||||
# 魔方简历
|
||||
|
||||
## 前端
|
||||
|
||||
Next技术栈
|
||||
|
||||
### 添加shadcn/ui 组件
|
||||
|
||||
```bash
|
||||
pnpm dlx shadcn-ui@latest add tabs
|
||||
```
|
||||
|
||||
## 后端
|
||||
Nest技术栈
|
||||
|
||||
Nest技术栈
|
||||
|
||||
@@ -9,13 +9,27 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@tiptap/extension-color": "^2.4.0",
|
||||
"@tiptap/extension-list-item": "^2.4.0",
|
||||
"@tiptap/extension-text-align": "^2.4.0",
|
||||
"@tiptap/extension-text-style": "^2.4.0",
|
||||
"@tiptap/pm": "^2.4.0",
|
||||
"@tiptap/react": "^2.4.0",
|
||||
"@tiptap/starter-kit": "^2.4.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"lucide-react": "^0.379.0",
|
||||
"next": "14.2.3",
|
||||
"react": "^18",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18",
|
||||
"react-resizable-panels": "^2.0.20",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
@@ -26,6 +40,7 @@
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
"postcss": "^8",
|
||||
"sass": "^1.77.4",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
||||
@@ -5,10 +5,6 @@ import { CircleArrowRight } from "lucide-react";
|
||||
import EditButton from "@/components/EditButton";
|
||||
import IconLogo from "@/assets/images/logo@2x.svg";
|
||||
export default function Home() {
|
||||
const goWorkbench = () => {
|
||||
redirect("/workbench/index");
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center p-[24px]">
|
||||
<header className="flex justify-between w-[100%]">
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useState } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger
|
||||
} from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const BasicInfo = () => {
|
||||
const [date, setDate] = useState<Date>();
|
||||
const [isDateOpen, setIsDateOpen] = useState<boolean>(false);
|
||||
return (
|
||||
<div className="flex items-center flex-wrap gap-[16px]">
|
||||
<div className="flex items-center flex-[48%] ">
|
||||
<Label htmlFor="name" className="w-[80px]">
|
||||
姓名
|
||||
</Label>
|
||||
<Input id="name" className="w-[200px] flex-1" />
|
||||
</div>
|
||||
<div className="flex items-center flex-[48%]">
|
||||
<Label htmlFor="phone" className="w-[80px]">
|
||||
手机号码
|
||||
</Label>
|
||||
<Input id="phone" className="w-[200px] flex-1" />
|
||||
</div>
|
||||
<div className="flex items-center flex-[48%]">
|
||||
<Label htmlFor="wechat" className="w-[80px]">
|
||||
微信
|
||||
</Label>
|
||||
<Input id="wechat" className="w-[200px] flex-1" />
|
||||
</div>
|
||||
<div className="flex items-center flex-[48%]">
|
||||
<Label htmlFor="email" className="w-[80px]">
|
||||
电子邮件
|
||||
</Label>
|
||||
<Input id="email" className="w-[200px] flex-1" />
|
||||
</div>
|
||||
<div className="flex items-center flex-[48%]">
|
||||
<Label htmlFor="birthday" className="w-[80px]">
|
||||
出生日期
|
||||
</Label>
|
||||
<Popover open={isDateOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="relative flex-1 w-[200px]">
|
||||
<CalendarIcon className="left-[10px] top-[12px] absolute h-4 w-4" />
|
||||
<Input
|
||||
id="birthday"
|
||||
className="flex-1 pl-[36px] "
|
||||
value={date ? format(date, "PPP") : "Pick a date"}
|
||||
onClick={() => setIsDateOpen(true)}
|
||||
></Input>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto p-0"
|
||||
onFocusOutside={() => setIsDateOpen(false)}
|
||||
onPointerDownOutside={() => setIsDateOpen(false)}
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={(val) => {
|
||||
setIsDateOpen(false);
|
||||
setDate(val);
|
||||
}}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicInfo;
|
||||
@@ -0,0 +1,5 @@
|
||||
const Cert = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default Cert;
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useState } from "react";
|
||||
import BasicInfo from "../BasicInfo";
|
||||
import Skills from "../Skills";
|
||||
import Project from "../Project";
|
||||
import Empolyment from "../Empolyment";
|
||||
import Education from "../Education";
|
||||
import Cert from "../Cert";
|
||||
import {
|
||||
UserRound,
|
||||
PencilRuler,
|
||||
FileJson,
|
||||
Network,
|
||||
GraduationCap,
|
||||
BookOpen
|
||||
} from "lucide-react";
|
||||
const Editor = () => {
|
||||
const [activeTab, setActiveTab] = useState("basic");
|
||||
const tabList = [
|
||||
{
|
||||
value: "basic",
|
||||
label: "基本信息",
|
||||
icon: <UserRound size={16} />
|
||||
},
|
||||
{
|
||||
value: "skills",
|
||||
label: "专业技能",
|
||||
icon: <PencilRuler size={16} />
|
||||
},
|
||||
{
|
||||
value: "project",
|
||||
label: "项目经历",
|
||||
icon: <FileJson size={16} />
|
||||
},
|
||||
{
|
||||
value: "empolyment",
|
||||
label: "工作经历",
|
||||
icon: <Network size={16} />
|
||||
},
|
||||
{
|
||||
value: "education",
|
||||
label: "教育经历",
|
||||
icon: <GraduationCap size={16} />
|
||||
},
|
||||
{
|
||||
value: "cert",
|
||||
label: "技能证书",
|
||||
icon: <BookOpen size={16} />
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div className="flex flex-1 p-[12px] h-[100vh]">
|
||||
<div className="w-[72px] shrink-0 mr-[10px] bg-[#ecedee]">
|
||||
{tabList.map((item, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center text-[12px] p-[12px] cursor-pointer hover:bg-[#e3e3e5] hover:rounded-[4px]"
|
||||
>
|
||||
<div>{item.icon}</div>
|
||||
<div>{item.label}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className="bg-[#fff] p-[12px] rounded-[6px] overflow-auto "
|
||||
style={{ scrollbarWidth: "none" }}
|
||||
>
|
||||
<div className="text-[24px] mb-[10px]">前端-xx-x年</div>
|
||||
|
||||
<div className="mt-[12px]">
|
||||
<div className="text-[20px] mb-[12px]">基本信息</div>
|
||||
<BasicInfo></BasicInfo>
|
||||
<div className="text-[20px] mb-[12px]">专业技能</div>
|
||||
<Skills></Skills>
|
||||
<div className="text-[20px] mb-[12px]">项目经历</div>
|
||||
<Project></Project>
|
||||
<div className="text-[20px] mb-[12px]">工作经历</div>
|
||||
<Empolyment></Empolyment>
|
||||
<div className="text-[20px] mb-[12px]">教育经历</div>
|
||||
<Education></Education>
|
||||
<div className="text-[20px]">技能证书</div>
|
||||
<Cert></Cert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
@@ -0,0 +1,5 @@
|
||||
const Education = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default Education;
|
||||
@@ -0,0 +1,5 @@
|
||||
const Empolyment = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default Empolyment;
|
||||
@@ -0,0 +1,10 @@
|
||||
const Preview = () => {
|
||||
return (
|
||||
<div className="flex-1 p-[12px]">
|
||||
{/* 简历编辑表单 */}
|
||||
预览区域
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Preview;
|
||||
@@ -0,0 +1,11 @@
|
||||
import Tiptap from "@/components/Tiptap";
|
||||
|
||||
const Project = () => {
|
||||
return (
|
||||
<div>
|
||||
<Tiptap></Tiptap>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Project;
|
||||
@@ -0,0 +1,10 @@
|
||||
import Tiptap from "@/components/Tiptap";
|
||||
const Skills = () => {
|
||||
return (
|
||||
<div>
|
||||
<Tiptap></Tiptap>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Skills;
|
||||
@@ -0,0 +1,5 @@
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background: #ecedee;
|
||||
}
|
||||
@@ -1,6 +1,29 @@
|
||||
"use client";
|
||||
import Editor from "./compoents/Editor";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup
|
||||
} from "@/components/ui/resizable";
|
||||
import Preview from "./compoents/Preview";
|
||||
import styles from "./index.module.scss";
|
||||
const WorkBench = () => {
|
||||
return <div>WorkBench</div>;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="h-[100vh] rounded-lg border"
|
||||
>
|
||||
<ResizablePanel>
|
||||
<Editor></Editor>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel>
|
||||
<Preview></Preview>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkBench;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
const colorList = [
|
||||
// 富文本文字顏色值,字符串格式
|
||||
"#000000",
|
||||
"#ffffff",
|
||||
"#ff0000",
|
||||
"#00ff00",
|
||||
"#0000ff",
|
||||
"#ffff00",
|
||||
"#00ffff",
|
||||
"#ff00ff",
|
||||
"#c0c0c0",
|
||||
"#808080",
|
||||
"#800000",
|
||||
"#008000",
|
||||
"#000080",
|
||||
"#808000",
|
||||
"#008080"
|
||||
];
|
||||
|
||||
interface IProps {
|
||||
setCurrentColor: (color: string) => () => void;
|
||||
}
|
||||
|
||||
const ColorBar = ({ setCurrentColor }: IProps) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-wrap">
|
||||
{colorList.map((color, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="shrink-0 cursor-pointer hover:scale-[1.3] transition-all"
|
||||
onClick={setCurrentColor(color)}
|
||||
style={{
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
backgroundColor: color,
|
||||
margin: "5px",
|
||||
borderRadius: "50%"
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorBar;
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
}
|
||||
|
||||
function CustomTooltip({ children, title }: IProps) {
|
||||
return (
|
||||
<TooltipProvider delayDuration={200}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent>{title}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTooltip;
|
||||
@@ -0,0 +1,247 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AlignCenter,
|
||||
AlignLeft,
|
||||
AlignRight,
|
||||
Baseline,
|
||||
Bold,
|
||||
CodeXml,
|
||||
CornerDownLeft,
|
||||
Eraser,
|
||||
FileCode2,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
Heading4,
|
||||
Heading5,
|
||||
Heading6,
|
||||
Italic,
|
||||
List,
|
||||
ListOrdered,
|
||||
MessageSquareQuote,
|
||||
Palette,
|
||||
Pilcrow,
|
||||
Redo,
|
||||
Strikethrough,
|
||||
Undo
|
||||
} from "lucide-react";
|
||||
|
||||
import { Color } from "@tiptap/extension-color";
|
||||
import ListItem from "@tiptap/extension-list-item";
|
||||
import TextStyle from "@tiptap/extension-text-style";
|
||||
import { EditorProvider, useCurrentEditor } from "@tiptap/react";
|
||||
import TextAlign from "@tiptap/extension-text-align";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger
|
||||
} from "@/components/ui/popover";
|
||||
|
||||
import ColorBar from "./ColorBar";
|
||||
import CustomTooltip from "./CustomTooltip";
|
||||
|
||||
import "../styles/tiptap.scss";
|
||||
|
||||
const MenuBar = () => {
|
||||
const { editor } = useCurrentEditor();
|
||||
const setCurrentColor = (color: string) => () => {
|
||||
editor?.chain().focus().setColor(color).run();
|
||||
};
|
||||
if (!editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="control-group">
|
||||
<div className="button-group">
|
||||
<CustomTooltip title="加粗">
|
||||
<Bold
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
className={editor.isActive("bold") ? "is-active" : ""}
|
||||
></Bold>
|
||||
</CustomTooltip>
|
||||
|
||||
<CustomTooltip title="斜体">
|
||||
<Italic
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
className={editor.isActive("italic") ? "is-active" : ""}
|
||||
></Italic>
|
||||
</CustomTooltip>
|
||||
|
||||
<CustomTooltip title="中划线">
|
||||
<Strikethrough
|
||||
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||
className={editor.isActive("strike") ? "is-active" : ""}
|
||||
></Strikethrough>
|
||||
</CustomTooltip>
|
||||
|
||||
<CustomTooltip title="行内代码">
|
||||
<CodeXml
|
||||
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||
className={editor.isActive("code") ? "is-active" : ""}
|
||||
></CodeXml>
|
||||
</CustomTooltip>
|
||||
|
||||
<Heading1
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
||||
}
|
||||
className={
|
||||
editor.isActive("heading", { level: 1 }) ? "is-active" : ""
|
||||
}
|
||||
></Heading1>
|
||||
|
||||
<Heading2
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||
}
|
||||
className={
|
||||
editor.isActive("heading", { level: 2 }) ? "is-active" : ""
|
||||
}
|
||||
></Heading2>
|
||||
<Heading3
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||
}
|
||||
className={
|
||||
editor.isActive("heading", { level: 3 }) ? "is-active" : ""
|
||||
}
|
||||
></Heading3>
|
||||
<Heading4
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 4 }).run()
|
||||
}
|
||||
className={
|
||||
editor.isActive("heading", { level: 4 }) ? "is-active" : ""
|
||||
}
|
||||
></Heading4>
|
||||
|
||||
<Heading5
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 5 }).run()
|
||||
}
|
||||
className={
|
||||
editor.isActive("heading", { level: 6 }) ? "is-active" : ""
|
||||
}
|
||||
></Heading5>
|
||||
<Heading6
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 5 }).run()
|
||||
}
|
||||
className={
|
||||
editor.isActive("heading", { level: 6 }) ? "is-active" : ""
|
||||
}
|
||||
></Heading6>
|
||||
|
||||
<List
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
className={editor.isActive("bulletList") ? "is-active" : ""}
|
||||
></List>
|
||||
<ListOrdered
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
className={editor.isActive("orderedList") ? "is-active" : ""}
|
||||
></ListOrdered>
|
||||
<CustomTooltip title="代码块">
|
||||
<FileCode2
|
||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
className={editor.isActive("codeBlock") ? "is-active" : ""}
|
||||
></FileCode2>
|
||||
</CustomTooltip>
|
||||
|
||||
<MessageSquareQuote
|
||||
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
className={editor.isActive("blockquote") ? "is-active" : ""}
|
||||
></MessageSquareQuote>
|
||||
|
||||
<CornerDownLeft
|
||||
onClick={() => editor.chain().focus().setHardBreak().run()}
|
||||
></CornerDownLeft>
|
||||
|
||||
{/* <button
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
disabled={!editor.can().chain().focus().undo().run()}
|
||||
>
|
||||
Undo
|
||||
</button> */}
|
||||
<Undo onClick={() => editor.chain().focus().undo().run()}></Undo>
|
||||
{/* <button
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
disabled={!editor.can().chain().focus().redo().run()}
|
||||
>
|
||||
Redo
|
||||
</button> */}
|
||||
<Redo onClick={() => editor.chain().focus().undo().run()}></Redo>
|
||||
<CustomTooltip title="居左">
|
||||
<AlignLeft
|
||||
onClick={() => editor.chain().focus().setTextAlign("left").run()}
|
||||
className={
|
||||
editor.isActive({ textAlign: "left" }) ? "is-active" : ""
|
||||
}
|
||||
></AlignLeft>
|
||||
</CustomTooltip>
|
||||
|
||||
<CustomTooltip title="居中">
|
||||
<AlignCenter
|
||||
onClick={() => editor.chain().focus().setTextAlign("center").run()}
|
||||
className={
|
||||
editor.isActive({ textAlign: "center" }) ? "is-active" : ""
|
||||
}
|
||||
></AlignCenter>
|
||||
</CustomTooltip>
|
||||
|
||||
<CustomTooltip title="居右">
|
||||
<AlignRight
|
||||
onClick={() => editor.chain().focus().setTextAlign("right").run()}
|
||||
className={
|
||||
editor.isActive({ textAlign: "right" }) ? "is-active" : ""
|
||||
}
|
||||
></AlignRight>
|
||||
</CustomTooltip>
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<CustomTooltip title="文本颜色">
|
||||
<Palette></Palette>
|
||||
</CustomTooltip>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 bg-[#21242a]">
|
||||
<ColorBar setCurrentColor={setCurrentColor}></ColorBar>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const extensions = [
|
||||
Color.configure({ types: [TextStyle.name, ListItem.name] }),
|
||||
TextStyle,
|
||||
TextAlign.configure({
|
||||
types: ["heading", "paragraph"]
|
||||
}),
|
||||
StarterKit.configure({
|
||||
bulletList: {
|
||||
keepMarks: true,
|
||||
keepAttributes: false // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
||||
},
|
||||
orderedList: {
|
||||
keepMarks: true,
|
||||
keepAttributes: false // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
const content = ``;
|
||||
|
||||
const Tiptap = () => {
|
||||
return (
|
||||
<EditorProvider
|
||||
slotBefore={<MenuBar />}
|
||||
extensions={extensions}
|
||||
content={content}
|
||||
></EditorProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tiptap;
|
||||
@@ -0,0 +1,66 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { DayPicker } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
month: "space-y-4",
|
||||
caption: "flex justify-center pt-1 relative items-center",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
||||
),
|
||||
day_range_end: "day-range-end",
|
||||
day_selected:
|
||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||
day_today: "bg-accent text-accent-foreground",
|
||||
day_outside:
|
||||
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
|
||||
day_disabled: "text-muted-foreground opacity-50",
|
||||
day_range_middle:
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||
day_hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
|
||||
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Calendar.displayName = "Calendar"
|
||||
|
||||
export { Calendar }
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
@@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client"
|
||||
|
||||
import { GripVertical } from "lucide-react"
|
||||
import * as ResizablePrimitive from "react-resizable-panels"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ResizablePanelGroup = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
|
||||
<ResizablePrimitive.PanelGroup
|
||||
className={cn(
|
||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
const ResizablePanel = ResizablePrimitive.Panel
|
||||
|
||||
const ResizableHandle = ({
|
||||
withHandle,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
|
||||
withHandle?: boolean
|
||||
}) => (
|
||||
<ResizablePrimitive.PanelResizeHandle
|
||||
className={cn(
|
||||
"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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{withHandle && (
|
||||
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
||||
<GripVertical className="h-2.5 w-2.5" />
|
||||
</div>
|
||||
)}
|
||||
</ResizablePrimitive.PanelResizeHandle>
|
||||
)
|
||||
|
||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
||||
@@ -0,0 +1,55 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 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-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
@@ -0,0 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md 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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
@@ -0,0 +1,138 @@
|
||||
/* Basic editor styles */
|
||||
.tiptap {
|
||||
min-height: 300px;
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 2px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* List styles */
|
||||
ul,
|
||||
ol {
|
||||
list-style-type: disc;
|
||||
padding: 0 1rem;
|
||||
margin: 1.25rem 1rem 1.25rem 0.4rem;
|
||||
|
||||
li p {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
}
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
padding: 0 1rem;
|
||||
margin: 1.25rem 1rem 1.25rem 0.4rem;
|
||||
|
||||
li p {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
}
|
||||
/* Heading styles */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
margin-top: 2.5rem;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin-top: 3.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Code and preformatted text styles */
|
||||
code {
|
||||
background-color: rgba(27, 31, 35, 0.05);
|
||||
border-radius: 0.4rem;
|
||||
color: black;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.25em 0.3em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: black;
|
||||
border-radius: 0.5rem;
|
||||
color: white;
|
||||
font-family: "JetBrainsMono", monospace;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid gray;
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid gray;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.control-group {
|
||||
border: 1px solid #d1d5db;
|
||||
border-bottom: none;
|
||||
background-color: #403d39;
|
||||
border-radius: 4px 4px 0 0;
|
||||
overflow: hidden;
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
padding: 4px;
|
||||
|
||||
.lucide {
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
&.is-active {
|
||||
font-weight: 700;
|
||||
color: #2ec4b6;
|
||||
}
|
||||
&:hover {
|
||||
border-radius: 4px;
|
||||
background-color: #e26d5c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[contenteditable]:focus {
|
||||
outline: none;
|
||||
}
|
||||
Generated
+1336
-5
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user