mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-07 02:58:11 +00:00
uptodate with main
This commit is contained in:
commit
20febf888b
24 changed files with 7576 additions and 63 deletions
1
apps/cf-ai-backend/.gitignore
vendored
1
apps/cf-ai-backend/.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
# Logs
|
||||
wrangler.toml
|
||||
|
||||
logs
|
||||
_.log
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"cmdk": "^1.0.0",
|
||||
"framer-motion": "^11.0.24",
|
||||
"lucide-react": "^0.338.0",
|
||||
"next": "14.1.0",
|
||||
"react": "^18",
|
||||
|
|
|
|||
5805
apps/web/pnpm-lock.yaml
generated
Normal file
5805
apps/web/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
@apply bg-rgray-2 text-rgray-11;
|
||||
@apply bg-rgray-2 text-rgray-11 max-h-screen overflow-y-hidden;
|
||||
/* color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
|
|
@ -31,7 +31,7 @@ body {
|
|||
}
|
||||
|
||||
[vaul-drawer-wrapper] {
|
||||
@apply bg-rgray-1;
|
||||
@apply bg-rgray-2;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Roboto } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import type { Metadata } from 'next';
|
||||
import { Roboto } from 'next/font/google';
|
||||
import './globals.css';
|
||||
|
||||
const roboto = Roboto({ weight: ["300", "400", "500"], subsets: ["latin"] });
|
||||
const roboto = Roboto({ weight: ['300', '400', '500'], subsets: ['latin'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
|
@ -17,6 +17,7 @@ export default function RootLayout({
|
|||
return (
|
||||
<html lang="en">
|
||||
<body className={roboto.className}>
|
||||
<div id="d" className="fixed left-0 top-0 h-[5px] w-screen"></div>
|
||||
<div vaul-drawer-wrapper="" className="min-w-screen overflow-x-hidden">
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
"use client";
|
||||
import Main from "@/components/Main";
|
||||
import Sidebar from "@/components/Sidebar/index";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Home() {
|
||||
const [selectedItem, setSelectedItem] = useState<string | null>(null);
|
||||
|
||||
export default async function Home() {
|
||||
return (
|
||||
<>
|
||||
<Sidebar />
|
||||
</>
|
||||
<div className="flex w-screen">
|
||||
<Sidebar onSelectChange={setSelectedItem} />
|
||||
<Main sidebarOpen={selectedItem !== null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
71
apps/web/src/assets/Memories.tsx
Normal file
71
apps/web/src/assets/Memories.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { svgId } from "@/lib/utils";
|
||||
|
||||
export const MemoryIcon: React.FC<React.SVGAttributes<SVGElement>> = (
|
||||
props,
|
||||
) => (
|
||||
<svg
|
||||
viewBox="0 0 89 53"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
x="0.40697"
|
||||
y="8.52821"
|
||||
width="43.0286"
|
||||
height="43.0286"
|
||||
rx="5.5"
|
||||
transform="rotate(-12 0.40697 8.52821)"
|
||||
fill="var(--gray-5)"
|
||||
stroke="var(--gray-10)"
|
||||
/>
|
||||
<rect
|
||||
x="20.8257"
|
||||
y="9.19775"
|
||||
width="43"
|
||||
height="43"
|
||||
rx="5.5"
|
||||
fill="var(--gray-5)"
|
||||
stroke="var(--gray-10)"
|
||||
/>
|
||||
<rect
|
||||
x="47.6965"
|
||||
y="-0.612372"
|
||||
width="43.0286"
|
||||
height="43.0286"
|
||||
rx="5.5"
|
||||
transform="rotate(15 47.6965 -0.612372)"
|
||||
fill="var(--gray-5)"
|
||||
stroke="var(--gray-10)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SpaceIcon: React.FC<React.SVGAttributes<SVGElement>> = (props) => (
|
||||
<svg
|
||||
viewBox="0 0 34 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
x="1.39502"
|
||||
y="5.2229"
|
||||
width="24"
|
||||
height="24"
|
||||
rx="5.5"
|
||||
fill="var(--gray-5)"
|
||||
stroke="var(--gray-10)"
|
||||
/>
|
||||
<rect
|
||||
x="11.2231"
|
||||
y="-0.157702"
|
||||
width="24"
|
||||
height="24"
|
||||
rx="5.5"
|
||||
transform="rotate(20 11.2231 -0.157702)"
|
||||
fill="var(--gray-5)"
|
||||
stroke="var(--gray-10)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
528
apps/web/src/assets/MemoryWithImages.tsx
Normal file
528
apps/web/src/assets/MemoryWithImages.tsx
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
import { svgId } from "@/lib/utils";
|
||||
|
||||
export const MemoryWithImage: React.FC<
|
||||
{ image: string; id: string } & React.SVGAttributes<SVGElement>
|
||||
> = ({ image, id: _id, ...props }) => {
|
||||
const id = "space-1-" + _id;
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 137 137"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
{...props}
|
||||
>
|
||||
<g filter={`url(#${svgId(id, "filter0_d_83_72")})`}>
|
||||
<rect
|
||||
x="33"
|
||||
y="33"
|
||||
width="72.207"
|
||||
height="72.207"
|
||||
rx="10"
|
||||
fill="var(--gray-4)"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="47.0637"
|
||||
y="46.9976"
|
||||
width="45"
|
||||
height="45"
|
||||
fill={`url(#${svgId(id, "pattern0")})`}
|
||||
/>
|
||||
<defs>
|
||||
<filter
|
||||
id={svgId(id, "filter0_d_83_72")}
|
||||
x="21"
|
||||
y="21"
|
||||
width="96.207"
|
||||
height="96.207"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="2"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_83_72"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_83_72"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_83_72"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<pattern
|
||||
id={svgId(id, "pattern0")}
|
||||
patternContentUnits="objectBoundingBox"
|
||||
width="1"
|
||||
height="1"
|
||||
>
|
||||
<use
|
||||
xlinkHref={`#${svgId(id, "image0_83_72")}`}
|
||||
transform="scale(0.00520833)"
|
||||
/>
|
||||
</pattern>
|
||||
<image
|
||||
id={svgId(id, "image0_83_72")}
|
||||
width="192"
|
||||
height="192"
|
||||
xlinkHref={image}
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const MemoryWithImages2: React.FC<
|
||||
{ images: string[]; id: string } & React.SVGAttributes<SVGElement>
|
||||
> = ({ images, id: _id, ...props }) => {
|
||||
const id = "space-2-" + _id;
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 137 137"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath={`url(#${svgId(id, "clip0_80_62")})`}>
|
||||
<g filter={`url(#${svgId(id, "filter0_d_80_62")})`}>
|
||||
<rect
|
||||
x="7"
|
||||
y="51.5427"
|
||||
width="72.207"
|
||||
height="72.207"
|
||||
rx="10"
|
||||
transform="rotate(-24.1922 7 51.5427)"
|
||||
fill="var(--gray-4)"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="26.2664"
|
||||
y="58.9253"
|
||||
width="45"
|
||||
height="45"
|
||||
transform="rotate(-24.2 26.2664 58.9253)"
|
||||
fill={`url(#${svgId(id, "pattern0")})`}
|
||||
/>
|
||||
<g filter={`url(#${svgId(id, "filter1_d_80_62")})`}>
|
||||
<rect
|
||||
x="59.9409"
|
||||
y="42.2124"
|
||||
width="72.207"
|
||||
height="72.207"
|
||||
rx="10"
|
||||
transform="rotate(10.2301 59.9409 42.2124)"
|
||||
fill="var(--gray-4)"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="71.2952"
|
||||
y="58.4851"
|
||||
width="45"
|
||||
height="45"
|
||||
transform="rotate(10.23 71.2952 58.4851)"
|
||||
fill={`url(#${svgId(id, "pattern1")})`}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id={svgId(id, "filter0_d_80_62")}
|
||||
x="-1.78271"
|
||||
y="13.1697"
|
||||
width="113.021"
|
||||
height="113.021"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="2"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_80_62"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_80_62"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_80_62"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<pattern
|
||||
id={svgId(id, "pattern0")}
|
||||
patternContentUnits="objectBoundingBox"
|
||||
width="1"
|
||||
height="1"
|
||||
>
|
||||
<use
|
||||
xlinkHref={`#${svgId(id, "image0_80_62")}`}
|
||||
transform="scale(0.0111111)"
|
||||
/>
|
||||
</pattern>
|
||||
<filter
|
||||
id={svgId(id, "filter1_d_80_62")}
|
||||
x="36.7322"
|
||||
y="31.8276"
|
||||
width="104.652"
|
||||
height="104.653"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="2"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_80_62"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_80_62"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_80_62"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<pattern
|
||||
id={svgId(id, "pattern1")}
|
||||
patternContentUnits="objectBoundingBox"
|
||||
width="1"
|
||||
height="1"
|
||||
>
|
||||
<use
|
||||
xlinkHref={`#${svgId(id, "image1_80_62")}`}
|
||||
transform="scale(0.00520833)"
|
||||
/>
|
||||
</pattern>
|
||||
<clipPath id={svgId(id, "clip0_80_62")}>
|
||||
<rect width="137" height="137" fill="white" />
|
||||
</clipPath>
|
||||
<image
|
||||
id={svgId(id, "image0_80_62")}
|
||||
width="90"
|
||||
height="90"
|
||||
xlinkHref={images[0]}
|
||||
/>
|
||||
<image
|
||||
id={svgId(id, "image1_80_62")}
|
||||
width="192"
|
||||
height="192"
|
||||
xlinkHref={images[1]}
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const MemoryWithImages3: React.FC<
|
||||
{ images: string[]; id: string } & React.SVGAttributes<SVGElement>
|
||||
> = ({ images, id: _id, ...props }) => {
|
||||
const id = "space-3-" + _id;
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 137 137"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
{...props}
|
||||
>
|
||||
<g clipPath={`url(#${svgId(id, "clip0_79_36")})`}>
|
||||
<g filter={`url(#${svgId(id, "filter0_d_79_36")})`}>
|
||||
<rect
|
||||
x="53.5242"
|
||||
y="12"
|
||||
width="72.207"
|
||||
height="72.207"
|
||||
rx="10"
|
||||
transform="rotate(14.9009 53.5242 12)"
|
||||
fill="var(--gray-4)"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="63.3663"
|
||||
y="27.3052"
|
||||
width="45"
|
||||
height="45"
|
||||
transform="rotate(14.9 63.3663 27.3052)"
|
||||
fill={`url(#${svgId(id, "pattern0")})`}
|
||||
/>
|
||||
<g filter={`url(#${svgId(id, "filter1_d_79_36")})`}>
|
||||
<rect
|
||||
x="7"
|
||||
y="51.5427"
|
||||
width="72.207"
|
||||
height="72.207"
|
||||
rx="10"
|
||||
transform="rotate(-24.1922 7 51.5427)"
|
||||
fill="var(--gray-4)"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="26.2664"
|
||||
y="58.9253"
|
||||
width="45"
|
||||
height="45"
|
||||
transform="rotate(-24.2 26.2664 58.9253)"
|
||||
fill={`url(#${svgId(id, "pattern1")})`}
|
||||
/>
|
||||
<g filter={`url(#${svgId(id, "filter2_d_79_36")})`}>
|
||||
<rect
|
||||
x="59.9409"
|
||||
y="42.2124"
|
||||
width="72.207"
|
||||
height="72.207"
|
||||
rx="10"
|
||||
transform="rotate(10.2301 59.9409 42.2124)"
|
||||
fill="var(--gray-4)"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="71.2952"
|
||||
y="58.4851"
|
||||
width="45"
|
||||
height="45"
|
||||
transform="rotate(10.23 71.2952 58.4851)"
|
||||
fill={`url(#${svgId(id, "pattern2")})`}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id={svgId(id, "filter0_d_79_36")}
|
||||
x="25.189"
|
||||
y="2.23267"
|
||||
width="107.881"
|
||||
height="107.881"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="2"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_79_36"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_79_36"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_79_36"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<pattern
|
||||
id={svgId(id, "pattern0")}
|
||||
patternContentUnits="objectBoundingBox"
|
||||
width="1"
|
||||
height="1"
|
||||
>
|
||||
<use
|
||||
xlinkHref={`#${svgId(id, "image0_79_36")}`}
|
||||
transform="scale(0.0111111)"
|
||||
/>
|
||||
</pattern>
|
||||
<filter
|
||||
id={svgId(id, "filter1_d_79_36")}
|
||||
x="-1.78271"
|
||||
y="13.1697"
|
||||
width="113.021"
|
||||
height="113.021"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="2"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_79_36"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_79_36"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_79_36"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<pattern
|
||||
id={svgId(id, "pattern1")}
|
||||
patternContentUnits="objectBoundingBox"
|
||||
width="1"
|
||||
height="1"
|
||||
>
|
||||
<use
|
||||
xlinkHref={`#${svgId(id, "image1_79_36")}`}
|
||||
transform="scale(0.0111111)"
|
||||
/>
|
||||
</pattern>
|
||||
<filter
|
||||
id={svgId(id, "filter2_d_79_36")}
|
||||
x="36.7322"
|
||||
y="31.8276"
|
||||
width="104.652"
|
||||
height="104.653"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="2"
|
||||
operator="dilate"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow_79_36"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_79_36"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_79_36"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<pattern
|
||||
id={svgId(id, "pattern2")}
|
||||
patternContentUnits="objectBoundingBox"
|
||||
width="1"
|
||||
height="1"
|
||||
>
|
||||
<use
|
||||
xlinkHref={`#${svgId(id, "image2_79_36")}`}
|
||||
transform="scale(0.00520833)"
|
||||
/>
|
||||
</pattern>
|
||||
<clipPath id={svgId(id, "clip0_79_36")}>
|
||||
<rect width="137" height="137" fill="white" />
|
||||
</clipPath>
|
||||
<image
|
||||
id={svgId(id, "image0_79_36")}
|
||||
width="90"
|
||||
height="90"
|
||||
xlinkHref={images[0]}
|
||||
/>
|
||||
<image
|
||||
id={svgId(id, "image1_79_36")}
|
||||
width="90"
|
||||
height="90"
|
||||
xlinkHref={images[1]}
|
||||
/>
|
||||
<image
|
||||
id={svgId(id, "image2_79_36")}
|
||||
width="192"
|
||||
height="192"
|
||||
xlinkHref={images[2]}
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
56
apps/web/src/components/Main.tsx
Normal file
56
apps/web/src/components/Main.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"use client";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FilterCombobox } from "./Sidebar/FilterCombobox";
|
||||
import { Textarea2 } from "./ui/textarea";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { MemoryDrawer } from "./MemoryDrawer";
|
||||
import useViewport from "@/hooks/useViewport";
|
||||
|
||||
export default function Main({ sidebarOpen }: { sidebarOpen: boolean }) {
|
||||
const [value, setValue] = useState("");
|
||||
const { width } = useViewport();
|
||||
|
||||
const textArea = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function onResize() {
|
||||
if (!textArea.current || !window.visualViewport) return;
|
||||
|
||||
const visualViewportHeight = window.visualViewport.height;
|
||||
}
|
||||
|
||||
window.visualViewport?.addEventListener("resize", onResize);
|
||||
return () => window.visualViewport?.removeEventListener("resize", onResize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main
|
||||
data-sidebar-open={sidebarOpen}
|
||||
className="flex h-screen max-h-screen w-full items-end justify-center px-5 pb-[20vh] pt-5 md:items-center md:px-60 md:[&[data-sidebar-open='true']]:px-20"
|
||||
>
|
||||
<Textarea2
|
||||
ref={textArea}
|
||||
className="h-max max-h-[30em] min-h-[3em] resize-y flex-row items-start justify-center overflow-auto py-5 md:h-[20vh] md:resize-none md:flex-col md:items-center md:justify-center md:p-2 md:pb-2 md:pt-2"
|
||||
textAreaProps={{
|
||||
placeholder: "Ask your SuperMemory...",
|
||||
className:
|
||||
"h-auto overflow-auto md:h-full md:resize-none text-lg py-0 px-2 md:py-0 md:p-5 resize-y text-rgray-11 w-full min-h-[1em]",
|
||||
value,
|
||||
autoFocus: true,
|
||||
onChange: (e) => setValue(e.target.value),
|
||||
}}
|
||||
>
|
||||
<div className="text-rgray-11/70 flex h-full w-fit items-center justify-center pl-0 md:w-full md:p-2">
|
||||
<FilterCombobox className="hidden md:flex" />
|
||||
<button
|
||||
disabled={value.trim().length < 1}
|
||||
className="text-rgray-11/70 bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-4 mt-auto flex items-center justify-center rounded-full p-2 ring-2 ring-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:ml-auto md:mt-0"
|
||||
>
|
||||
<ArrowRight className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</Textarea2>
|
||||
{width <= 768 && <MemoryDrawer />}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
36
apps/web/src/components/MemoryDrawer.tsx
Normal file
36
apps/web/src/components/MemoryDrawer.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { useState } from "react";
|
||||
import { Drawer, DrawerContent, DrawerOverlay } from "./ui/drawer";
|
||||
import { MemoryIcon } from "@/assets/Memories";
|
||||
|
||||
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
|
||||
export function MemoryDrawer({ className, ...props }: Props) {
|
||||
const [activeSnapPoint, setActiveSnapPoint] = useState<
|
||||
number | null | string
|
||||
>(0.1);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
snapPoints={[0.1, 0.9]}
|
||||
activeSnapPoint={activeSnapPoint}
|
||||
shouldScaleBackground={false}
|
||||
setActiveSnapPoint={setActiveSnapPoint}
|
||||
open={true}
|
||||
dismissible={false}
|
||||
modal={false}
|
||||
>
|
||||
<DrawerContent
|
||||
overlay={false}
|
||||
className="border-rgray-6 h-full w-screen border-2 pt-4 focus-visible:outline-none"
|
||||
handle={false}
|
||||
>
|
||||
<div className="bg-rgray-4 border-rgray-6 text-rgray-11 absolute left-1/2 top-0 flex w-fit -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-2 rounded-md border-2 px-3 py-2">
|
||||
<MemoryIcon className="h-7 w-7" />
|
||||
Memories
|
||||
</div>
|
||||
Hello
|
||||
</DrawerContent>
|
||||
<DrawerOverlay className="relative bg-transparent" />
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
307
apps/web/src/components/Sidebar/CategoryItem.tsx
Normal file
307
apps/web/src/components/Sidebar/CategoryItem.tsx
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
"use client";
|
||||
import { cleanUrl } from "@/lib/utils";
|
||||
import { StoredContent } from "@/server/db/schema";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
} from "../ui/dropdown-menu";
|
||||
import { Label } from "../ui/label";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
MoreHorizontal,
|
||||
Tags,
|
||||
ChevronDown,
|
||||
Edit3,
|
||||
Trash2,
|
||||
Save,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
Minus,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerClose,
|
||||
} from "../ui/drawer";
|
||||
import { Input } from "../ui/input";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||
import {
|
||||
AnimatePresence,
|
||||
motion,
|
||||
Reorder,
|
||||
useMotionValue,
|
||||
} from "framer-motion";
|
||||
|
||||
const pages: StoredContent[] = [
|
||||
{
|
||||
id: 1,
|
||||
content: "",
|
||||
title: "Visual Studio Code",
|
||||
url: "https://code.visualstudio.com",
|
||||
description: "",
|
||||
image: "https://code.visualstudio.com/favicon.ico",
|
||||
baseUrl: "https://code.visualstudio.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
space: ""
|
||||
},
|
||||
];
|
||||
export const CategoryItem: React.FC<{ item: StoredContent }> = ({ item }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false);
|
||||
|
||||
const [items, setItems] = useState<StoredContent[]>(pages);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hover:bg-rgray-5 has-[button:focus]:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>button>div>[data-down-icon]]:scale-125 [&:hover>button>div>[data-down-icon]]:opacity-100 [&:hover>button>div>[data-down-icon]]:delay-150 [&:hover>button>div>[data-tags-icon]]:scale-75 [&:hover>button>div>[data-tags-icon]]:opacity-0 [&:hover>button>div>[data-tags-icon]]:delay-0 [&:hover>button]:opacity-100">
|
||||
<button
|
||||
onClick={() => setIsExpanded((prev) => !prev)}
|
||||
className="flex w-full items-center gap-2 focus-visible:outline-none"
|
||||
>
|
||||
<div className="relative h-5 min-w-5">
|
||||
<Tags
|
||||
data-tags-icon
|
||||
className="z-1 h-5 w-5 transition-[transform,opacity] delay-150 duration-150"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<ChevronDown
|
||||
data-down-icon
|
||||
className={`absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150 ${isExpanded ? "rotate-180" : "rotate-0"}`}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="w-full truncate text-nowrap text-left">
|
||||
{item.title ?? "Untitled website"}
|
||||
</span>
|
||||
</button>
|
||||
<Drawer
|
||||
shouldScaleBackground
|
||||
open={isEditDrawerOpen}
|
||||
onOpenChange={setIsEditDrawerOpen}
|
||||
>
|
||||
<DrawerContent className="pb-10 lg:px-[25vw]">
|
||||
<DrawerHeader className="relative mt-10 px-0">
|
||||
<DrawerTitle className=" flex w-full justify-between">
|
||||
Edit Page Details
|
||||
</DrawerTitle>
|
||||
<DrawerDescription>Change the page details</DrawerDescription>
|
||||
<a
|
||||
target="_blank"
|
||||
href={item.url}
|
||||
className="text-rgray-11/90 bg-rgray-3 text-md absolute right-0 top-0 flex w-min translate-y-1/2 items-center justify-center gap-1 rounded-full px-5 py-1"
|
||||
>
|
||||
<img src={item.image ?? "/brain.png"} className="h-4 w-4" />
|
||||
{cleanUrl(item.url)}
|
||||
</a>
|
||||
</DrawerHeader>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label>Title</Label>
|
||||
<Input
|
||||
className=""
|
||||
required
|
||||
value={item.title ?? ""}
|
||||
placeholder={item.title ?? "Enter the title for the page"}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Additional Context</Label>
|
||||
<Textarea
|
||||
className=""
|
||||
value={item.content ?? ""}
|
||||
placeholder={"Enter additional context for this page"}
|
||||
/>
|
||||
</div>
|
||||
<DrawerFooter className="flex flex-row-reverse items-center justify-end px-0 pt-5">
|
||||
<DrawerClose className="flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition hover:bg-blue-100 hover:text-blue-400 focus-visible:bg-blue-100 focus-visible:text-blue-400 focus-visible:outline-none focus-visible:ring-blue-200 dark:hover:bg-blue-100/10 dark:focus-visible:bg-blue-100/10 dark:focus-visible:ring-blue-200/30">
|
||||
<Save className="mr-2 h-4 w-4 " strokeWidth={1.5} />
|
||||
Save
|
||||
</DrawerClose>
|
||||
<DrawerClose className="hover:bg-rgray-3 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 flex items-center justify-center rounded-md px-3 py-2 ring-2 ring-transparent transition focus-visible:outline-none">
|
||||
Cancel
|
||||
</DrawerClose>
|
||||
<DrawerClose className="mr-auto flex items-center justify-center rounded-md bg-red-100 px-3 py-2 text-red-400 ring-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-red-200 dark:bg-red-100/10 dark:focus-visible:ring-red-200/30">
|
||||
<Trash2 className="mr-2 h-4 w-4 " strokeWidth={1.5} />
|
||||
Delete
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{isExpanded && (
|
||||
<Reorder.Group
|
||||
axis="y"
|
||||
values={items}
|
||||
onReorder={setItems}
|
||||
as="div"
|
||||
initial={{ height: 0 }}
|
||||
animate={{ height: "auto" }}
|
||||
exit={{
|
||||
height: 0,
|
||||
transition: {},
|
||||
}}
|
||||
layoutScroll
|
||||
className="flex max-h-32 w-full flex-col items-center overflow-y-auto pl-7"
|
||||
>
|
||||
<AnimatePresence>
|
||||
{items.map((item, i) => (
|
||||
<CategoryPage
|
||||
key={item.id}
|
||||
index={i}
|
||||
item={item}
|
||||
onRemove={() =>
|
||||
setItems((prev) => prev.filter((_, index) => i !== index))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</Reorder.Group>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const CategoryPage: React.FC<{
|
||||
item: StoredContent;
|
||||
index: number;
|
||||
onRemove?: () => void;
|
||||
}> = ({ item, onRemove, index }) => {
|
||||
return (
|
||||
<Reorder.Item
|
||||
value={item}
|
||||
as="div"
|
||||
key={index}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
dragListener={false}
|
||||
className="hover:bg-rgray-5 has-[a:focus]:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-icon]]:scale-125 [&:hover>a>div>[data-icon]]:opacity-100 [&:hover>a>div>[data-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100"
|
||||
>
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
className="flex w-[90%] items-center gap-2 focus-visible:outline-none"
|
||||
>
|
||||
<div className="relative h-4 min-w-4">
|
||||
<img
|
||||
src={item.image ?? "/brain.png"}
|
||||
alt={item.title ?? "Untitiled website"}
|
||||
className="z-1 h-4 w-4 transition-[transform,opacity] delay-150 duration-150"
|
||||
/>
|
||||
<ArrowUpRight
|
||||
data-icon
|
||||
className="absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="w-full truncate text-nowrap">
|
||||
{item.title ?? "Untitled website"}
|
||||
</span>
|
||||
</a>
|
||||
<button
|
||||
onClick={() => onRemove?.()}
|
||||
className="ml-auto w-4 min-w-4 rounded-[0.15rem] opacity-0 focus-visible:opacity-100 focus-visible:outline-none"
|
||||
>
|
||||
<Minus className="h-4 w-4 min-w-4" />
|
||||
</button>
|
||||
</Reorder.Item>
|
||||
);
|
||||
};
|
||||
109
apps/web/src/components/Sidebar/FilterCombobox.tsx
Normal file
109
apps/web/src/components/Sidebar/FilterCombobox.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { SpaceIcon } from "@/assets/Memories";
|
||||
|
||||
const spaces = [
|
||||
{
|
||||
value: "1",
|
||||
label: "Cool Tech",
|
||||
},
|
||||
{
|
||||
value: "2",
|
||||
label: "Cool Courses",
|
||||
},
|
||||
{
|
||||
value: "3",
|
||||
label: "Cool Libraries",
|
||||
},
|
||||
];
|
||||
|
||||
export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||
|
||||
export function FilterCombobox({ className, ...props }: Props) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [values, setValues] = React.useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
data-state-on={open}
|
||||
className={cn(
|
||||
"text-rgray-11/70 on:bg-rgray-3 focus-visible:ring-rgray-8 hover:bg-rgray-3 relative flex items-center justify-center gap-1 rounded-md px-3 py-1.5 ring-2 ring-transparent focus-visible:outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SpaceIcon className="mr-1 h-5 w-5" />
|
||||
Filter
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<div
|
||||
data-state-on={values.length > 0}
|
||||
className="on:flex text-rgray-11 border-rgray-6 bg-rgray-2 absolute left-0 top-0 hidden aspect-[1] h-4 w-4 -translate-x-1/3 -translate-y-1/3 items-center justify-center rounded-full border text-center text-[9px]"
|
||||
>
|
||||
{values.length}
|
||||
</div>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command
|
||||
filter={(val, search) =>
|
||||
spaces
|
||||
.find((s) => s.value === val)
|
||||
?.label.toLowerCase()
|
||||
.includes(search.toLowerCase().trim())
|
||||
? 1
|
||||
: 0
|
||||
}
|
||||
>
|
||||
<CommandInput placeholder="Filter spaces..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>Nothing found</CommandEmpty>
|
||||
{/* bug: doesn't work on clicking with mouse only keyboard, weird */}
|
||||
<CommandGroup>
|
||||
{spaces.map((space) => (
|
||||
<CommandItem
|
||||
key={space.value}
|
||||
value={space.value}
|
||||
onSelect={(val) => {
|
||||
setValues((prev) =>
|
||||
prev.includes(val)
|
||||
? prev.filter((v) => v !== val)
|
||||
: [...prev, val],
|
||||
);
|
||||
}}
|
||||
>
|
||||
<SpaceIcon className="mr-2 h-4 w-4" />
|
||||
{space.label}
|
||||
{values.includes(space.value)}
|
||||
<Check
|
||||
data-state-on={values.includes(space.value)}
|
||||
className={cn("on:opacity-100 ml-auto h-4 w-4 opacity-0")}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
147
apps/web/src/components/Sidebar/MemoriesBar.tsx
Normal file
147
apps/web/src/components/Sidebar/MemoriesBar.tsx
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import {
|
||||
MemoryWithImage,
|
||||
MemoryWithImages3,
|
||||
MemoryWithImages2,
|
||||
} from '@/assets/MemoryWithImages';
|
||||
import { type Space } from '../../../types/memory';
|
||||
import { InputWithIcon } from '../ui/input';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
export function MemoriesBar() {
|
||||
const spaces: Space[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Cool Tech',
|
||||
description: 'Really cool mind blowing tech',
|
||||
content: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Perplexity',
|
||||
description: 'A good ui',
|
||||
content: '',
|
||||
image: 'https://perplexity.ai/favicon.ico',
|
||||
url: 'https://perplexity.ai',
|
||||
savedAt: new Date(),
|
||||
baseUrl: 'https://perplexity.ai',
|
||||
space: 'Cool tech',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Pi.ai',
|
||||
description: 'A good ui',
|
||||
content: '',
|
||||
image: 'https://pi.ai/pi-logo-192.png?v=2',
|
||||
url: 'https://pi.ai',
|
||||
savedAt: new Date(),
|
||||
baseUrl: 'https://pi.ai',
|
||||
space: 'Cool tech',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Visual Studio Code',
|
||||
description: 'A good ui',
|
||||
content: '',
|
||||
image: 'https://code.visualstudio.com/favicon.ico',
|
||||
url: 'https://code.visualstudio.com',
|
||||
savedAt: new Date(),
|
||||
baseUrl: 'https://code.visualstudio.com',
|
||||
space: 'Cool tech',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Cool Courses',
|
||||
description: 'Amazng',
|
||||
content: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Animation on the web',
|
||||
description: 'A good ui',
|
||||
content: '',
|
||||
image: 'https://animations.dev/favicon.ico',
|
||||
url: 'https://animations.dev',
|
||||
savedAt: new Date(),
|
||||
baseUrl: 'https://animations.dev',
|
||||
space: 'Cool courses',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Tailwind Course',
|
||||
description: 'A good ui',
|
||||
content: '',
|
||||
image:
|
||||
'https://tailwindcss.com/_next/static/media/tailwindcss-mark.3c5441fc7a190fb1800d4a5c7f07ba4b1345a9c8.svg',
|
||||
url: 'https://tailwindcss.com',
|
||||
savedAt: new Date(),
|
||||
baseUrl: 'https://tailwindcss.com',
|
||||
space: 'Cool courses',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Cool Libraries',
|
||||
description: 'Really cool mind blowing tech',
|
||||
content: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Perplexity',
|
||||
description: 'A good ui',
|
||||
content: '',
|
||||
image: 'https://yashverma.me/logo.jpg',
|
||||
url: 'https://perplexity.ai',
|
||||
savedAt: new Date(),
|
||||
baseUrl: 'https://perplexity.ai',
|
||||
space: 'Cool libraries',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
|
||||
<div className="w-full px-8">
|
||||
<h1 className="w-full text-2xl">Your Memories</h1>
|
||||
<InputWithIcon
|
||||
placeholder="Search"
|
||||
icon={<Search className="text-rgray-11 h-5 w-5 opacity-50" />}
|
||||
className="bg-rgray-4 mt-2 w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5">
|
||||
{spaces.map((space) => (
|
||||
<Space key={space.id} {...space} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Space({ title, description, content, id }: Space) {
|
||||
console.log(title, content.map((c) => c.image).reverse());
|
||||
return (
|
||||
<button className="hover:bg-rgray-2 focus-visible:bg-rgray-2 focus-visible:ring-rgray-7 flex flex-col items-center justify-center rounded-md p-2 text-center ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
|
||||
{content.length > 2 ? (
|
||||
<MemoryWithImages3
|
||||
className="h-24 w-24"
|
||||
id={id.toString()}
|
||||
images={content.map((c) => c.image).reverse() as string[]}
|
||||
/>
|
||||
) : content.length === 1 ? (
|
||||
<MemoryWithImage
|
||||
className="h-24 w-24"
|
||||
id={id.toString()}
|
||||
image={content[0].image!}
|
||||
/>
|
||||
) : (
|
||||
<MemoryWithImages2
|
||||
className="h-24 w-24"
|
||||
id={id.toString()}
|
||||
images={content.map((c) => c.image).reverse() as string[]}
|
||||
/>
|
||||
)}
|
||||
<span>{title}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ export const PageItem: React.FC<{ item: StoredContent }> = ({ item }) => {
|
|||
const [isEditDrawerOpen, setIsEditDrawerOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="hover:bg-rgray-5 focus-within:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-upright-icon]]:scale-125 [&:hover>a>div>[data-upright-icon]]:opacity-100 [&:hover>a>div>[data-upright-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100">
|
||||
<div className="hover:bg-rgray-5 has-[a:focus]:bg-rgray-5 flex w-full items-center rounded-full py-1 pl-3 pr-2 transition [&:hover>a>div>[data-icon]]:scale-125 [&:hover>a>div>[data-icon]]:opacity-100 [&:hover>a>div>[data-icon]]:delay-150 [&:hover>a>div>img]:scale-75 [&:hover>a>div>img]:opacity-0 [&:hover>a>div>img]:delay-0 [&:hover>button]:opacity-100">
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
|
|
@ -49,7 +49,7 @@ export const PageItem: React.FC<{ item: StoredContent }> = ({ item }) => {
|
|||
className="z-1 h-4 w-4 transition-[transform,opacity] delay-150 duration-150"
|
||||
/>
|
||||
<ArrowUpRight
|
||||
data-upright-icon
|
||||
data-icon
|
||||
className="absolute left-1/2 top-1/2 z-[2] h-4 w-4 min-w-4 -translate-x-1/2 -translate-y-1/2 scale-75 opacity-0 transition-[transform,opacity] duration-150"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,46 +1,103 @@
|
|||
"use server";
|
||||
"use client";
|
||||
import { StoredContent } from "@/server/db/schema";
|
||||
import { AddNewPagePopover, PageItem } from "./PagesItem";
|
||||
import { MemoryIcon } from "../../assets/Memories";
|
||||
import { Trash2, User2 } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { MemoriesBar } from "./MemoriesBar";
|
||||
|
||||
export default async function Sidebar() {
|
||||
const pages: StoredContent[] = [
|
||||
{
|
||||
id: 1,
|
||||
content: "",
|
||||
title: "Visual Studio Code",
|
||||
url: "https://code.visualstudio.com",
|
||||
description: "",
|
||||
image: "https://code.visualstudio.com/favicon.ico",
|
||||
baseUrl: "https://code.visualstudio.com",
|
||||
savedAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: "",
|
||||
title: "yxshv/vscode: An unofficial remake of vscode's landing page",
|
||||
url: "https://github.com/yxshv/vscode",
|
||||
description: "",
|
||||
image: "https://github.com/favicon.ico",
|
||||
baseUrl: "https://github.com",
|
||||
savedAt: new Date(),
|
||||
},
|
||||
];
|
||||
export type MenuItem = {
|
||||
icon: React.ReactNode | React.ReactNode[];
|
||||
label: string;
|
||||
content?: React.FC;
|
||||
};
|
||||
|
||||
const menuItemsTop: Array<MenuItem> = [
|
||||
{
|
||||
icon: <MemoryIcon className="h-10 w-10" />,
|
||||
label: "Memories",
|
||||
content: MemoriesBar,
|
||||
},
|
||||
];
|
||||
|
||||
const menuItemsBottom: Array<MenuItem> = [
|
||||
{
|
||||
icon: <Trash2 strokeWidth={1.3} className="h-6 w-6" />,
|
||||
label: "Trash",
|
||||
},
|
||||
{
|
||||
icon: <User2 strokeWidth={1.3} className="h-6 w-6" />,
|
||||
label: "Profile",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Sidebar({
|
||||
onSelectChange,
|
||||
}: {
|
||||
onSelectChange?: (selectedItem: string | null) => void;
|
||||
}) {
|
||||
const menuItems = [...menuItemsTop, ...menuItemsBottom];
|
||||
const [selectedItem, setSelectedItem] = useState<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
onSelectChange?.(selectedItem);
|
||||
}, [selectedItem]);
|
||||
|
||||
const Subbar =
|
||||
menuItems.find((i) => i.label === selectedItem)?.content ?? (() => <></>);
|
||||
|
||||
return (
|
||||
<aside className="bg-rgray-3 flex h-screen w-[25%] flex-col items-start justify-between py-5 pb-[50vh] font-light">
|
||||
<div className="flex items-center justify-center gap-1 px-5 text-xl font-normal">
|
||||
<img src="/brain.png" alt="logo" className="h-10 w-10" />
|
||||
SuperMemory
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start justify-center p-2">
|
||||
<h1 className="mb-1 flex w-full items-center justify-center px-3 font-normal">
|
||||
Pages
|
||||
<AddNewPagePopover />
|
||||
</h1>
|
||||
{pages.map((item) => (
|
||||
<PageItem key={item.id} item={item} />
|
||||
<>
|
||||
<div className="bg-rgray-2 border-r-rgray-6 hidden h-screen max-h-screen w-max flex-col items-center border-r px-2 py-5 text-sm font-light md:flex">
|
||||
{menuItemsTop.map((item, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
item={item}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
))}
|
||||
<div className="mt-auto" />
|
||||
{menuItemsBottom.map((item, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
item={item}
|
||||
selectedItem={selectedItem}
|
||||
setSelectedItem={setSelectedItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
{selectedItem && (
|
||||
<SubSidebar>
|
||||
<Subbar />
|
||||
</SubSidebar>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const MenuItem = ({
|
||||
item: { icon, label },
|
||||
selectedItem,
|
||||
setSelectedItem,
|
||||
}: {
|
||||
item: MenuItem;
|
||||
selectedItem: string | null;
|
||||
setSelectedItem: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
}) => (
|
||||
<button
|
||||
data-state-on={selectedItem === label}
|
||||
onClick={() => setSelectedItem((prev) => (prev === label ? null : label))}
|
||||
className="on:opacity-100 on:bg-rgray-4 focus-visible:ring-rgray-7 flex w-full flex-col items-center justify-center rounded-md px-3 py-3 opacity-80 ring-2 ring-transparent transition hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none"
|
||||
>
|
||||
{icon}
|
||||
<span className="">{label}</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
export function SubSidebar({ children }: { children?: React.ReactNode }) {
|
||||
return (
|
||||
<div className="bg-rgray-3 border-r-rgray-6 hidden h-screen w-[50vw] flex-col items-center border-r font-light md:flex">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
155
apps/web/src/components/ui/command.tsx
Normal file
155
apps/web/src/components/ui/command.tsx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { type DialogProps } from "@radix-ui/react-dialog";
|
||||
import { Command as CommandPrimitive } from "cmdk";
|
||||
import { Search } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-rgray-3 text-rgray-11 flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Command.displayName = CommandPrimitive.displayName;
|
||||
|
||||
interface CommandDialogProps extends DialogProps {}
|
||||
|
||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
||||
<Command className="[&_[cmdk-group-heading]]:text-rgray-11 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[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">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandInput = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
className="border-rgray-6 flex items-center border-b px-3"
|
||||
cmdk-input-wrapper=""
|
||||
>
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"placeholder:text-rgray-11/50 flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
||||
|
||||
const CommandList = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.List
|
||||
ref={ref}
|
||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandList.displayName = CommandPrimitive.List.displayName;
|
||||
|
||||
const CommandEmpty = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||
>((props, ref) => (
|
||||
<CommandPrimitive.Empty
|
||||
ref={ref}
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||
|
||||
const CommandGroup = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-rgray-12 [&_[cmdk-group-heading]]:text-rgray-11 overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
||||
|
||||
const CommandSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("bg-rgray-3 -mx-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
||||
|
||||
const CommandItem = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"aria-selected:bg-rgray-5 aria-selected:text-rgray-12 relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
||||
|
||||
const CommandShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("text-gray-11 ml-auto text-xs tracking-widest", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
CommandShortcut.displayName = "CommandShortcut";
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
};
|
||||
122
apps/web/src/components/ui/dialog.tsx
Normal file
122
apps/web/src/components/ui/dialog.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"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%] border-rgray-6 bg-rgray-3 fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-rgray-2 focus:ring-rgray-7 data-[state=open]:bg-rgray-3 data-[state=open]:text-rgray-11 absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogHeader.displayName = "DialogHeader";
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogFooter.displayName = "DialogFooter";
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-rgray-11 text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
|
|
@ -28,6 +28,7 @@ const DrawerOverlay = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Overlay
|
||||
ref={ref}
|
||||
data-drawer-overlay
|
||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -36,10 +37,13 @@ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
|||
|
||||
const DrawerContent = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
|
||||
overlay?: boolean;
|
||||
handle?: boolean;
|
||||
}
|
||||
>(({ className, children, overlay = true, handle = true, ...props }, ref) => (
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
{overlay && <DrawerOverlay />}
|
||||
<DrawerPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
|
|
@ -48,7 +52,9 @@ const DrawerContent = React.forwardRef<
|
|||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="bg-rgray-4 mx-auto mt-4 h-2 w-[100px] rounded-full " />
|
||||
{handle && (
|
||||
<div className="bg-rgray-4 mx-auto mt-4 h-2 w-[100px] rounded-full " />
|
||||
)}
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"border-rgray-6 text-rgray-12 ring-offset-rgray-2 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex h-10 w-full rounded-md border bg-transparent px-3 py-2 text-sm transition file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ",
|
||||
"border-rgray-6 text-rgray-11 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex h-10 w-full rounded-md border bg-transparent px-3 py-2 text-sm transition file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 ",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
|
@ -20,6 +20,34 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
export interface InputWithIconProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const InputWithIcon = React.forwardRef<HTMLInputElement, InputWithIconProps>(
|
||||
({ className, type, icon, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-rgray-6 text-rgray-11 focus-within:ring-rgray-7 flex h-10 w-full items-center justify-center gap-2 rounded-md border bg-transparent px-3 py-2 text-sm transition focus-within:outline-none focus-within:ring-2 ",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
<input
|
||||
type={type}
|
||||
className={
|
||||
"placeholder:text-rgray-11/50 w-full bg-transparent file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
InputWithIcon.displayName = "Input";
|
||||
|
||||
export { Input, InputWithIcon };
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"border-rgray-6 ring-offset-rgray-2 placeholder:text-rgray-11 focus-visible:ring-rgray-7 flex min-h-[80px] w-full rounded-md border bg-transparent px-3 py-2 text-sm transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"border-rgray-6 text-rgray-11 placeholder:text-rgray-11/70 focus-within:ring-rgray-7 flex min-h-[80px] w-full rounded-md border bg-transparent px-3 py-2 text-sm font-normal transition focus-within:outline-none focus-within:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
|
@ -21,4 +21,35 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||
);
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea };
|
||||
export interface Textarea2Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
textAreaProps?: TextareaProps;
|
||||
}
|
||||
|
||||
const Textarea2 = React.forwardRef<HTMLTextAreaElement, Textarea2Props>(
|
||||
({ className, children, textAreaProps: _textAreaProps, ...props }, ref) => {
|
||||
const { className: textAreaClassName, ...textAreaProps } =
|
||||
_textAreaProps || {};
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-rgray-6 text-rgray-11 focus-within:ring-rgray-7 flex h-auto min-h-[80px] w-full flex-col items-start justify-center rounded-md border bg-transparent px-3 py-2 text-sm transition focus-within:outline-none focus-within:ring-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<textarea
|
||||
className={cn(
|
||||
"placeholder:text-rgray-11/70 h-full w-full resize-none bg-transparent focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
textAreaClassName,
|
||||
)}
|
||||
ref={ref}
|
||||
{...textAreaProps}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
Textarea2.displayName = "Textarea2";
|
||||
|
||||
export { Textarea, Textarea2 };
|
||||
|
|
|
|||
24
apps/web/src/hooks/useViewport.ts
Normal file
24
apps/web/src/hooks/useViewport.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { useState, useEffect } from "react";
|
||||
|
||||
function getViewport() {
|
||||
const { innerWidth: width, innerHeight: height } = window;
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
export default function useViewport() {
|
||||
const [viewport, setViewport] = useState(getViewport());
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setViewport(getViewport());
|
||||
}
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
return viewport;
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
"use client";
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
|
|
@ -17,3 +16,11 @@ export function cleanUrl(url: string) {
|
|||
? url.slice(7)
|
||||
: url;
|
||||
}
|
||||
|
||||
export function generateId() {
|
||||
return Math.random().toString(36).slice(2, 9);
|
||||
}
|
||||
|
||||
export function svgId(prefix: string, id: string) {
|
||||
return `${prefix}-${id}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { Config } from "tailwindcss";
|
||||
import plugin from "tailwindcss/plugin";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
|
|
@ -58,6 +59,11 @@ const config: Config = {
|
|||
},
|
||||
},
|
||||
darkMode: "class",
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
plugins: [
|
||||
require("tailwindcss-animate"),
|
||||
plugin(function ({ addVariant }) {
|
||||
addVariant("on", "&[data-state-on='true']");
|
||||
}),
|
||||
],
|
||||
};
|
||||
export default config;
|
||||
|
|
|
|||
8
apps/web/types/memory.tsx
Normal file
8
apps/web/types/memory.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { StoredContent } from "@/server/db/schema";
|
||||
|
||||
export type Space = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
content: StoredContent[];
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue