diff --git a/package.json b/package.json
index a7d05b4..5067a3f 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.3",
@@ -36,7 +37,8 @@
"react-hook-form": "^7.53.0",
"react-redux": "^9.1.2",
"tailwind-merge": "^2.5.3",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "useismobile": "^1.0.4"
},
"devDependencies": {
"@codedependant/semantic-release-docker": "^5.0.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 184e030..04f6b2f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
'@radix-ui/react-label':
specifier: ^2.1.0
version: 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-separator':
+ specifier: ^1.1.0
+ version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot':
specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.11)(react@18.3.1)
@@ -89,6 +92,9 @@ importers:
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.13)
+ useismobile:
+ specifier: ^1.0.4
+ version: 1.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
devDependencies:
'@codedependant/semantic-release-docker':
specifier: ^5.0.3
@@ -571,6 +577,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-separator@1.1.0':
+ resolution: {integrity: sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-slot@1.1.0':
resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==}
peerDependencies:
@@ -2431,6 +2450,12 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ useismobile@1.0.4:
+ resolution: {integrity: sha512-EUjKD4IfJb+CHCP7Xaw++vUTl65aIE5LCQOAzyPonOMlRrsfX1Ur5k0GBaEyxTKpSuj3SvvcqK/CdfA+K1gN0Q==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.1 || ^18.2.0
+ react-dom: ^16.8.0 || ^17.0.1 || ^18.2.0
+
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -2937,6 +2962,15 @@ snapshots:
'@types/react': 18.3.11
'@types/react-dom': 18.3.0
+ '@radix-ui/react-separator@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.11
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-slot@1.1.0(@types/react@18.3.11)(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.11)(react@18.3.1)
@@ -5120,6 +5154,11 @@ snapshots:
dependencies:
react: 18.3.1
+ useismobile@1.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
util-deprecate@1.0.2: {}
vfile-location@4.1.0:
diff --git a/src/app/(gistLayout)/layout-ui.tsx b/src/app/(gistLayout)/layout-ui.tsx
index 931e1a7..7af4fea 100644
--- a/src/app/(gistLayout)/layout-ui.tsx
+++ b/src/app/(gistLayout)/layout-ui.tsx
@@ -1,174 +1,231 @@
-import { OrgListFeature } from "@/components/logic/org-list-logic";
-import {
- Avatar,
- AvatarFallback,
- AvatarImage,
-} from "@/components/shadcn/avatar";
-import { Button } from "@/components/shadcn/button";
-import { Codearea } from "@/components/shadcn/codearea";
-import { Input } from "@/components/shadcn/input";
-import MenuButton from "@/components/ui/menu-button";
-import { Modal } from "@/components/ui/modal";
-import { ProfileDropdown } from "@/components/ui/profile-dropdown";
-import Shortcut from "@/components/ui/shortcut";
-import TooltipShortcut, {
- TooltipShortcutTrigger,
-} from "@/components/ui/tooltip-shortcut";
-import { getLanguage } from "@/lib/language";
-import { FileCodeIcon, LucidePencil, Menu, PlusIcon } from "lucide-react";
-import { useState } from "react";
+import { OrgListFeature } from '@/components/logic/org-list-logic'
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/shadcn/avatar'
+import { Button } from '@/components/shadcn/button'
+import { Codearea } from '@/components/shadcn/codearea'
+import { Input } from '@/components/shadcn/input'
+import { Sidebar, SidebarContent, SidebarHeader, SidebarProvider } from '@/components/shadcn/sidebar'
+import MenuButton from '@/components/ui/menu-button'
+import { Modal } from '@/components/ui/modal'
+import { ProfileDropdown } from '@/components/ui/profile-dropdown'
+import TooltipShortcut, { TooltipShortcutTrigger } from '@/components/ui/tooltip-shortcut'
+import { getLanguage } from '@/lib/language'
+import { FileCodeIcon, LucidePencil, PlusIcon } from 'lucide-react'
+import { useState } from 'react'
interface GistLayoutProps {
- username: string;
- avatar: string;
- children: React.ReactNode;
- onMyGists: () => void;
- onCreateOrg: (name: string) => void;
- onCreateGist: (name: string, content: string) => void;
- onLogout: () => void;
+ username: string
+ avatar: string
+ children: React.ReactNode
+ onMyGists: () => void
+ onCreateOrg: (name: string) => void
+ onCreateGist: (name: string, content: string) => void
+ onLogout: () => void
}
-export default function GistLayout({
+export default function GistLayout({ avatar, children, username, onMyGists, onCreateOrg, onCreateGist, onLogout }: GistLayoutProps) {
+ const [gistName, setGistName] = useState('')
+ const [gistContent, setGistContent] = useState('')
+ const [isGistModalOpen, setIsGistModalOpen] = useState(false)
+ const [orgName, setOrgName] = useState('')
+ const [isOrgModalOpen, setIsOrgModalOpen] = useState(false)
+
+ const language = getLanguage(gistName)
+
+ return (
+
+
+
+ )
+}
+
+interface AppSidebarProps {
+ avatar: string
+ username: string
+ gistName: string
+ setGistName: (name: string) => void
+ isGistModalOpen: boolean
+ setIsGistModalOpen: (open: boolean) => void
+ onCreateGist: (name: string, content: string) => void
+ onLogout: () => void
+ orgName: string
+ setOrgName: (name: string) => void
+ isOrgModalOpen: boolean
+ setIsOrgModalOpen: (open: boolean) => void
+ onCreateOrg: (name: string) => void
+ onMyGists: () => void
+ gistContent: string
+ setGistContent: (content: string) => void
+ language: string
+}
+
+function AppSidebar({
avatar,
- children,
username,
- onMyGists,
- onCreateOrg,
+ gistName,
+ setGistName,
+ isGistModalOpen,
+ setIsGistModalOpen,
onCreateGist,
onLogout,
-}: GistLayoutProps) {
- const [gistName, setGistName] = useState("");
- const [gistContent, setGistContent] = useState("");
- const [orgName, setOrgName] = useState("");
- const [isOrgModalOpen, setIsOrgModalOpen] = useState(false);
-
- const language = getLanguage(gistName);
-
- const handleCreateGistClick = () => {
- onCreateGist(gistName, gistContent);
- setGistName("");
- setGistContent("");
- };
-
+ orgName,
+ setOrgName,
+ isOrgModalOpen,
+ setIsOrgModalOpen,
+ onCreateOrg,
+ onMyGists,
+ gistContent,
+ setGistContent,
+ language,
+}: AppSidebarProps) {
return (
-
-
-
-
+
+
+
+
-
- {username.charAt(0).toUpperCase()}
-
+ {username.charAt(0).toUpperCase()}
-
-
-
-
-
-
-
-
- }
- content={
-
- setGistName(e.target.value)}
- />
- setGistContent(e.target.value)}
- />
-
- }
- footer={
-
- Create
-
- }
- >
+
+
+
-
}
- variant="menu"
- size="menu"
- letter="M"
- onClick={onMyGists}
- href="/mygist"
- className="w-full"
- >
+
} variant="menu" size="menu" letter="M" onClick={onMyGists} href="/mygist" className="w-full">
My Gists
-
}
- variant="menu"
- size="menu"
- letter="T"
- className="w-full"
- >
- Create org
-
- }
- title="Create Org"
- content={
-
- setOrgName(e.target.value)}
- />
-
- }
- footer={
-
{
- onCreateOrg(orgName);
- setOrgName("");
- setIsOrgModalOpen(false);
- }}
- >
- Create
-
- }
- />
+
-
+
- {children}
-
- );
+
+ )
+}
+
+interface CreateGistModalProps {
+ gistName: string
+ setGistName: (name: string) => void
+ isGistModalOpen: boolean
+ setIsGistModalOpen: (open: boolean) => void
+ onCreateGist: (name: string, content: string) => void
+ gistContent: string
+ setGistContent: (content: string) => void
+ language: string
+}
+
+function CreateGistModal({ gistName, setGistName, isGistModalOpen, setIsGistModalOpen, onCreateGist, gistContent, setGistContent, language }: CreateGistModalProps) {
+ return (
+
+
+
+
+
+
+
+ }
+ content={
+
+
setGistName(e.target.value)} />
+
+ setGistContent(e.target.value)} />
+
+
+ }
+ footer={
+ {
+ onCreateGist(gistName, gistContent)
+ setGistName('')
+ setIsGistModalOpen(false)
+ }}
+ >
+ Create
+
+ }
+ >
+ )
+}
+
+interface CreateOrgModalProps {
+ orgName: string
+ setOrgName: (name: string) => void
+ isOrgModalOpen: boolean
+ setIsOrgModalOpen: (open: boolean) => void
+ onCreateOrg: (name: string) => void
+}
+
+function CreateOrgModal({ orgName, setOrgName, setIsOrgModalOpen, onCreateOrg, isOrgModalOpen }: CreateOrgModalProps) {
+ return (
+ } variant="menu" size="menu" letter="T" className="w-full">
+ Create org
+
+ }
+ title="Create Org"
+ content={
+
+ setOrgName(e.target.value)} />
+
+ }
+ footer={
+ {
+ onCreateOrg(orgName)
+ setOrgName('')
+ setIsOrgModalOpen(false)
+ }}
+ >
+ Create
+
+ }
+ />
+ )
}
diff --git a/src/app/(gistLayout)/layout.tsx b/src/app/(gistLayout)/layout.tsx
index f8ad22b..043bc6f 100644
--- a/src/app/(gistLayout)/layout.tsx
+++ b/src/app/(gistLayout)/layout.tsx
@@ -1,80 +1,66 @@
-"use client";
+'use client'
-import { ReactNode, useCallback } from "react";
-import GistLayout from "./layout-ui";
-import { useMe } from "@/lib/queries/user.queries";
-import { useToast } from "@/components/shadcn/use-toast";
-import { useCreateGist } from "@/lib/queries/gists.queries";
-import { useCreateOrg } from "@/lib/queries/orgs.queries";
-import { useLogout } from "@/lib/queries/auth.queries";
-import { redirect } from "next/navigation";
-import { useRouter } from "next/router";
+import { ReactNode, useCallback } from 'react'
+import GistLayout from './layout-ui'
+import { useMe } from '@/lib/queries/user.queries'
+import { useToast } from '@/components/shadcn/use-toast'
+import { useCreateGist } from '@/lib/queries/gists.queries'
+import { useCreateOrg } from '@/lib/queries/orgs.queries'
+import { useLogout } from '@/lib/queries/auth.queries'
-export default function GistLayoutFeature({
- children,
-}: {
- children: ReactNode;
-}) {
- const { data, error } = useMe();
- const { toast } = useToast();
+export default function GistLayoutFeature({ children }: { children: ReactNode }) {
+ const { data, error } = useMe()
+ const { toast } = useToast()
const { mutate: createGist } = useCreateGist({
onSuccess: () => {
toast({
- title: "Gist Created",
- description: "Your gist has been created successfully",
- });
+ title: 'Gist Created',
+ description: 'Your gist has been created successfully',
+ })
},
- });
-
+ })
const { mutate: createOrg } = useCreateOrg({
onSuccess: () => {
toast({
- title: "Organization Created",
- description: "Your org has been created successfully",
- });
+ title: 'Organization Created',
+ description: 'Your org has been created successfully',
+ })
},
- });
+ })
const { mutate: logout } = useLogout({
onSuccess: () => {
toast({
- title: "Logged Out",
- description: "You have been logged out successfully",
- });
- window.location.href = "/"; //sorry but couldn't find a way to redirect to the login page
+ title: 'Logged Out',
+ description: 'You have been logged out successfully',
+ })
+ window.location.href = '/' //sorry but couldn't find a way to redirect to the login page
},
- });
+ })
- const onMyGists = () => {};
+ const onMyGists = () => {}
const onCreateOrg = useCallback(
(name: string) => {
- createOrg(name);
+ createOrg(name)
},
- [createOrg],
- );
+ [createOrg]
+ )
const onLogout = () => {
- logout();
- };
+ logout()
+ }
const onCreateGist = (name: string, content: string) => {
createGist({
content,
name,
- });
- };
+ })
+ }
return (
-
+
{children}
- );
+ )
}
diff --git a/src/app/(gistLayout)/mygist/page-ui.tsx b/src/app/(gistLayout)/mygist/page-ui.tsx
index aa8387d..7dbf497 100644
--- a/src/app/(gistLayout)/mygist/page-ui.tsx
+++ b/src/app/(gistLayout)/mygist/page-ui.tsx
@@ -1,4 +1,5 @@
import { MyGistListFeature } from '@/components/logic/mygist-list-logic'
+import { SidebarTrigger } from '@/components/shadcn/sidebar'
import MenuButton from '@/components/ui/menu-button'
import { PaginationComponent } from '@/components/ui/pagination'
import TooltipShortcut, { TooltipShortcutTrigger } from '@/components/ui/tooltip-shortcut'
@@ -8,9 +9,13 @@ interface MyGistPageProps {}
export default function MyGistsPage({}: MyGistPageProps) {
return (
-
-
-
My Gists
+
+
+
} variant={'menu'}>
@@ -22,7 +27,7 @@ export default function MyGistsPage({}: MyGistPageProps) {
-
diff --git a/src/app/globals.css b/src/app/globals.css
index 1f66bd0..604be02 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -30,6 +30,14 @@
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
+ --sidebar-background: 0 0% 98%;
+ --sidebar-foreground: 240 5.3% 26.1%;
+ --sidebar-primary: 240 5.9% 10%;
+ --sidebar-primary-foreground: 0 0% 98%;
+ --sidebar-accent: 240 4.8% 95.9%;
+ --sidebar-accent-foreground: 240 5.9% 10%;
+ --sidebar-border: 220 13% 91%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
} */
.dark {
@@ -60,6 +68,14 @@
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
+ --sidebar-background: 210 7% 5%;
+ --sidebar-foreground: 240 4.8% 95.9%;
+ --sidebar-primary: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 240 3.7% 15.9%;
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --sidebar-border: 217 33% 17%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
}
.light {
@@ -90,6 +106,14 @@
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
+ --sidebar-background: 240 5.9% 10%;
+ --sidebar-foreground: 240 4.8% 95.9%;
+ --sidebar-primary: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 240 3.7% 15.9%;
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --sidebar-border: 240 3.7% 15.9%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
}
h1 {
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 82cc5be..3392d4c 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -7,9 +7,52 @@ import { Toaster } from '@/components/shadcn/toaster'
import { Providers } from '@/components/theme/theme-provider'
import QueryProvider from '@/components/api/api-provider'
import Script from 'next/script'
+import { Metadata } from 'next'
const fontSans = FontSans({ subsets: ['latin'] })
+export const metadata: Metadata = {
+ title: 'Create and share secure code snippets - Gists',
+ description: 'Gists lets developers create, share, and collaborate on secure code snippets.',
+ metadataBase: new URL('https://gists.app'),
+ icons: {
+ icon: '/favicon.png',
+ },
+ keywords: [
+ 'gists',
+ 'app',
+ 'code snippets',
+ 'code sharing',
+ 'developer tools',
+ 'programming',
+ 'collaboration',
+ 'open source',
+ 'project management',
+ 'code editor',
+ 'gist platform',
+ 'coding platform',
+ 'software development',
+ 'team collaboration',
+ 'version control',
+ 'code storage',
+ ],
+ openGraph: {
+ title: 'Create and share secure code snippets - Gists',
+ description: 'Gists lets developers create, share, and collaborate on secure code snippets.',
+ type: 'website',
+ url: 'https://gists.app',
+ siteName: 'Gists',
+ images: [
+ {
+ url: 'https://gists.app/og-card.png',
+ width: 1200,
+ height: 630,
+ alt: 'Preview image for Gists.app',
+ },
+ ],
+ },
+}
+
export default function RootLayout({
children,
}: Readonly<{
diff --git a/src/app/page.tsx b/src/app/page.tsx
index eed525e..c81b07a 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,51 +1,8 @@
-import { Metadata } from 'next'
import GistsLandingLogic from '@/components/logic/gists-landing-logic'
-export const metadata: Metadata = {
- title: 'Create and share secure code snippets - Gists',
- description: 'Gists lets developers create, share, and collaborate on secure code snippets.',
- metadataBase: new URL('https://gists.app'),
- icons: {
- icon: '/favicon.png',
- },
- keywords: [
- 'gists',
- 'app',
- 'code snippets',
- 'code sharing',
- 'developer tools',
- 'programming',
- 'collaboration',
- 'open source',
- 'project management',
- 'code editor',
- 'gist platform',
- 'coding platform',
- 'software development',
- 'team collaboration',
- 'version control',
- 'code storage',
- ],
- openGraph: {
- title: 'Create and share secure code snippets - Gists',
- description: 'Gists lets developers create, share, and collaborate on secure code snippets.',
- type: 'website',
- url: 'https://gists.app',
- siteName: 'Gists',
- images: [
- {
- url: 'https://gists.app/og-card.png',
- width: 1200,
- height: 630,
- alt: 'Preview image for Gists.app',
- },
- ],
- },
-}
-
export default function HomePage() {
return (
-
+
diff --git a/src/components/shadcn/separator.tsx b/src/components/shadcn/separator.tsx
new file mode 100644
index 0000000..b6fe39b
--- /dev/null
+++ b/src/components/shadcn/separator.tsx
@@ -0,0 +1,21 @@
+'use client'
+
+import * as React from 'react'
+import * as SeparatorPrimitive from '@radix-ui/react-separator'
+
+import { cn } from '@/lib/utils'
+
+const Separator = React.forwardRef
, React.ComponentPropsWithoutRef>(
+ ({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
+
+ )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/src/components/shadcn/sheet.tsx b/src/components/shadcn/sheet.tsx
new file mode 100644
index 0000000..4eb2719
--- /dev/null
+++ b/src/components/shadcn/sheet.tsx
@@ -0,0 +1,76 @@
+'use client'
+
+import * as React from 'react'
+import * as SheetPrimitive from '@radix-ui/react-dialog'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { X } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
+
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+ 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
+ {
+ variants: {
+ side: {
+ top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
+ bottom: 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
+ right: 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
+ },
+ },
+ defaultVariants: {
+ side: 'right',
+ },
+ }
+)
+
+interface SheetContentProps extends React.ComponentPropsWithoutRef, VariantProps {}
+
+const SheetContent = React.forwardRef, SheetContentProps>(({ side = 'right', className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({ className, ...props }: React.HTMLAttributes) =>
+SheetHeader.displayName = 'SheetHeader'
+
+const SheetFooter = ({ className, ...props }: React.HTMLAttributes) =>
+SheetFooter.displayName = 'SheetFooter'
+
+const SheetTitle = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
+
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef, React.ComponentPropsWithoutRef>(({ className, ...props }, ref) => (
+
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription }
diff --git a/src/components/shadcn/sidebar.tsx b/src/components/shadcn/sidebar.tsx
new file mode 100644
index 0000000..e841fba
--- /dev/null
+++ b/src/components/shadcn/sidebar.tsx
@@ -0,0 +1,557 @@
+'use client'
+
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { VariantProps, cva } from 'class-variance-authority'
+import { PanelLeft } from 'lucide-react'
+
+import { useIsMobile } from '@/lib/hook/use-is-mobile'
+import { cn } from '@/lib/utils'
+import { Button } from './button'
+import { Input } from './input'
+import { Separator } from './separator'
+import { Sheet, SheetContent } from './sheet'
+import { Skeleton } from './skeleton'
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip'
+
+const SIDEBAR_COOKIE_NAME = 'sidebar:state'
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = '18rem'
+const SIDEBAR_WIDTH_MOBILE = '18rem'
+const SIDEBAR_WIDTH_ICON = '3rem'
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
+
+type SidebarContext = {
+ state: 'expanded' | 'collapsed'
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext(null)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error('useSidebar must be used within a SidebarProvider.')
+ }
+
+ return context
+}
+
+const SidebarProvider = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<'div'> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+ }
+>(({ defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props }, ref) => {
+ const isMobile = useIsMobile()
+ const [openMobile, setOpenMobile] = React.useState(false)
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen)
+ const open = openProp ?? _open
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === 'function' ? value(open) : value
+ if (setOpenProp) {
+ setOpenProp(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+ },
+ [setOpenProp, open]
+ )
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
+ }, [isMobile, setOpen, setOpenMobile])
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
+ event.preventDefault()
+ toggleSidebar()
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown)
+ return () => window.removeEventListener('keydown', handleKeyDown)
+ }, [toggleSidebar])
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? 'expanded' : 'collapsed'
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+
+
+
+ {children}
+
+
+
+ )
+})
+SidebarProvider.displayName = 'SidebarProvider'
+
+const Sidebar = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<'div'> & {
+ side?: 'left' | 'right'
+ variant?: 'sidebar' | 'floating' | 'inset'
+ collapsible?: 'offcanvas' | 'icon' | 'none'
+ }
+>(({ side = 'left', variant = 'sidebar', collapsible = 'offcanvas', className, children, ...props }, ref) => {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+ if (collapsible === 'none') {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (isMobile) {
+ return (
+
+
+ {children}
+
+
+ )
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ )
+})
+Sidebar.displayName = 'Sidebar'
+
+const SidebarTrigger = React.forwardRef, React.ComponentProps>(({ className, onClick, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+})
+SidebarTrigger.displayName = 'SidebarTrigger'
+
+const SidebarRail = React.forwardRef>(({ className, ...props }, ref) => {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+})
+SidebarRail.displayName = 'SidebarRail'
+
+const SidebarInset = React.forwardRef>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+SidebarInset.displayName = 'SidebarInset'
+
+const SidebarInput = React.forwardRef, React.ComponentProps>(({ className, ...props }, ref) => {
+ return
+})
+SidebarInput.displayName = 'SidebarInput'
+
+const SidebarHeader = React.forwardRef>(({ className, ...props }, ref) => {
+ return
+})
+SidebarHeader.displayName = 'SidebarHeader'
+
+const SidebarFooter = React.forwardRef>(({ className, ...props }, ref) => {
+ return
+})
+SidebarFooter.displayName = 'SidebarFooter'
+
+const SidebarSeparator = React.forwardRef, React.ComponentProps>(({ className, ...props }, ref) => {
+ return
+})
+SidebarSeparator.displayName = 'SidebarSeparator'
+
+const SidebarContent = React.forwardRef>(({ className, ...props }, ref) => {
+ return
+})
+SidebarContent.displayName = 'SidebarContent'
+
+const SidebarGroup = React.forwardRef>(({ className, ...props }, ref) => {
+ return
+})
+SidebarGroup.displayName = 'SidebarGroup'
+
+const SidebarGroupLabel = React.forwardRef & { asChild?: boolean }>(({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'div'
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarGroupLabel.displayName = 'SidebarGroupLabel'
+
+const SidebarGroupAction = React.forwardRef & { asChild?: boolean }>(({ className, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button'
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 after:md:hidden',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarGroupAction.displayName = 'SidebarGroupAction'
+
+const SidebarGroupContent = React.forwardRef>(({ className, ...props }, ref) => (
+
+))
+SidebarGroupContent.displayName = 'SidebarGroupContent'
+
+const SidebarMenu = React.forwardRef>(({ className, ...props }, ref) => (
+
+))
+SidebarMenu.displayName = 'SidebarMenu'
+
+const SidebarMenuItem = React.forwardRef>(({ className, ...props }, ref) => (
+
+))
+SidebarMenuItem.displayName = 'SidebarMenuItem'
+
+const sidebarMenuButtonVariants = cva(
+ 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
+ {
+ variants: {
+ variant: {
+ default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
+ outline: 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
+ },
+ size: {
+ default: 'h-8 text-sm',
+ sm: 'h-7 text-xs',
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ }
+)
+
+const SidebarMenuButton = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<'button'> & {
+ asChild?: boolean
+ isActive?: boolean
+ tooltip?: string | React.ComponentProps
+ } & VariantProps
+>(({ asChild = false, isActive = false, variant = 'default', size = 'default', tooltip, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button'
+ const { isMobile, state } = useSidebar()
+
+ const button =
+
+ if (!tooltip) {
+ return button
+ }
+
+ if (typeof tooltip === 'string') {
+ tooltip = {
+ children: tooltip,
+ }
+ }
+
+ return (
+
+ {button}
+
+
+ )
+})
+SidebarMenuButton.displayName = 'SidebarMenuButton'
+
+const SidebarMenuAction = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps<'button'> & {
+ asChild?: boolean
+ showOnHover?: boolean
+ }
+>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button'
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 after:md:hidden',
+ 'peer-data-[size=sm]/menu-button:top-1',
+ 'peer-data-[size=default]/menu-button:top-1.5',
+ 'peer-data-[size=lg]/menu-button:top-2.5',
+ 'group-data-[collapsible=icon]:hidden',
+ showOnHover &&
+ 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarMenuAction.displayName = 'SidebarMenuAction'
+
+const SidebarMenuBadge = React.forwardRef>(({ className, ...props }, ref) => (
+
+))
+SidebarMenuBadge.displayName = 'SidebarMenuBadge'
+
+const SidebarMenuSkeleton = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<'div'> & {
+ showIcon?: boolean
+ }
+>(({ className, showIcon = false, ...props }, ref) => {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`
+ }, [])
+
+ return (
+
+ {showIcon && }
+
+
+ )
+})
+SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton'
+
+const SidebarMenuSub = React.forwardRef>(({ className, ...props }, ref) => (
+
+))
+SidebarMenuSub.displayName = 'SidebarMenuSub'
+
+const SidebarMenuSubItem = React.forwardRef>(({ ...props }, ref) => )
+SidebarMenuSubItem.displayName = 'SidebarMenuSubItem'
+
+const SidebarMenuSubButton = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentProps<'a'> & {
+ asChild?: boolean
+ size?: 'sm' | 'md'
+ isActive?: boolean
+ }
+>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'a'
+
+ return (
+ span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
+ size === 'sm' && 'text-xs',
+ size === 'md' && 'text-sm',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ )}
+ {...props}
+ />
+ )
+})
+SidebarMenuSubButton.displayName = 'SidebarMenuSubButton'
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ useSidebar,
+}
diff --git a/src/components/shadcn/skeleton.tsx b/src/components/shadcn/skeleton.tsx
new file mode 100644
index 0000000..7347fdf
--- /dev/null
+++ b/src/components/shadcn/skeleton.tsx
@@ -0,0 +1,7 @@
+import { cn } from '@/lib/utils'
+
+function Skeleton({ className, ...props }: React.HTMLAttributes) {
+ return
+}
+
+export { Skeleton }
diff --git a/src/components/theme/theme-provider.tsx b/src/components/theme/theme-provider.tsx
index 1107645..200f16d 100644
--- a/src/components/theme/theme-provider.tsx
+++ b/src/components/theme/theme-provider.tsx
@@ -1,10 +1,10 @@
-"use client";
-import { ThemeProvider } from "next-themes";
+'use client'
+import { ThemeProvider } from 'next-themes'
export function Providers({ children }: { children: React.ReactNode }) {
return (
- {children}{" "}
+ {children}
- );
+ )
}
diff --git a/src/components/ui/gist-details.tsx b/src/components/ui/gist-details.tsx
index 9f031c7..d07f644 100644
--- a/src/components/ui/gist-details.tsx
+++ b/src/components/ui/gist-details.tsx
@@ -1,131 +1,94 @@
-import { Badge } from "@/components/shadcn/badge";
-import { Input } from "@/components/shadcn/input";
-import MenuButton from "@/components/ui/menu-button";
-import { Gist } from "@/types";
-import {
- ChevronRightIcon,
- DownloadIcon,
- ExternalLinkIcon,
- ShareIcon,
- Trash2Icon,
-} from "lucide-react";
-import Link from "next/link";
-import { useState } from "react";
-import { Codearea } from "../shadcn/codearea";
-import { getLanguage } from "@/lib/language";
-import TooltipShortcut, { TooltipShortcutTrigger } from "./tooltip-shortcut";
-import { getBackendURL } from "@/lib/utils";
+import { Badge } from '@/components/shadcn/badge'
+import { Input } from '@/components/shadcn/input'
+import MenuButton from '@/components/ui/menu-button'
+import { Gist } from '@/types'
+import { ChevronRightIcon, DownloadIcon, ExternalLinkIcon, ShareIcon, Trash2Icon } from 'lucide-react'
+import Link from 'next/link'
+import { useState } from 'react'
+import { Codearea } from '../shadcn/codearea'
+import { getLanguage } from '@/lib/language'
+import TooltipShortcut, { TooltipShortcutTrigger } from './tooltip-shortcut'
+import { getBackendURL } from '@/lib/utils'
+import { SidebarTrigger } from '../shadcn/sidebar'
interface GistDetailsProps {
- gist: Gist;
- orgName: string;
- redirect?: boolean;
- onDownload: (name: string, code: string) => void;
- onShare: () => void;
- onDelete: (id: string) => void;
- onSave: (name: string, code: string) => void;
+ gist: Gist
+ orgName: string
+ redirect?: boolean
+ onDownload: (name: string, code: string) => void
+ onShare: () => void
+ onDelete: (id: string) => void
+ onSave: (name: string, code: string) => void
}
-export default function GistDetails({
- gist,
- orgName,
- redirect,
- onDownload,
- onShare,
- onDelete,
- onSave,
-}: GistDetailsProps) {
- const [gistId] = useState(gist.id);
- const [gistName, setGistName] = useState(gist.name);
- const [gistCode, setGistCode] = useState(gist.code);
+export default function GistDetails({ gist, orgName, redirect, onDownload, onShare, onDelete, onSave }: GistDetailsProps) {
+ const [gistId] = useState(gist.id)
+ const [gistName, setGistName] = useState(gist.name)
+ const [gistCode, setGistCode] = useState(gist.code)
const gistState: Gist = {
id: gistId,
name: gistName,
code: gistCode,
- };
+ }
const onOpenPlainText = (gistID: string) => {
// if production go to https://raw.gists.app/{gistID}
- console.log(process.env.NODE_ENV);
- if (process.env.NODE_ENV === "production") {
- const raw_url = getBackendURL().replace("api", "raw") + "/" + gistID;
- window.open(raw_url, "_blank");
- return;
+ console.log(process.env.NODE_ENV)
+ if (process.env.NODE_ENV === 'production') {
+ const raw_url = getBackendURL().replace('api', 'raw') + '/' + gistID
+ window.open(raw_url, '_blank')
+ return
}
- window.open(getBackendURL() + "/gists/raw/" + gistID, "_blank");
- };
+ window.open(getBackendURL() + '/gists/raw/' + gistID, '_blank')
+ }
return (
-
-
onDownload(gistName, gistCode)}
- onOpenPlainText={onOpenPlainText}
- />
+
+
onDownload(gistName, gistCode)} onOpenPlainText={onOpenPlainText} />
-
+
File name
- setGistName(e.target.value)}
- className="rounded-none"
- />
+ setGistName(e.target.value)} className="rounded-none" />
Code
-
- {/*
1
*/}
+
setGistCode(e.target.value)}
- className="rounded-none h-full"
+ className="rounded-none h-full overflow-y-auto w-full"
language={getLanguage(gistName)}
/>
-
-
+
+
- onDelete(gistId)}
- variant={"menu"}
- icon={}
- >
+ onDelete(gistId)} variant={'menu'} icon={}>
Delete
-
+
- }
- variant={"menu"}
- >
+ } variant={'menu'}>
Share
-
+
- onSave(gistName, gistCode)}
- variant={"menu"}
- >
+ onSave(gistName, gistCode)} variant={'menu'}>
Save
@@ -133,69 +96,54 @@ export default function GistDetails({
- );
+ )
}
interface HeaderProps {
- gist: Gist;
- orgName: string;
- redirect?: boolean;
- onDownload: (name: string, code: string) => void;
- onOpenPlainText?: (gistID: string) => void;
+ gist: Gist
+ orgName: string
+ redirect?: boolean
+ onDownload: (name: string, code: string) => void
+ onOpenPlainText?: (gistID: string) => void
}
-function Header({
- gist,
- orgName,
- redirect,
- onDownload,
- onOpenPlainText,
-}: HeaderProps) {
+function Header({ gist, orgName, redirect, onDownload, onOpenPlainText }: HeaderProps) {
return (
-
-
- {redirect ? (
-
- {orgName}
-
- ) : (
-
{orgName}
- )}
-
-
{gist.name}
+
+
+
+
+
+ {redirect ? (
+
+ {orgName}
+
+ ) : (
+ {orgName}
+ )}
+
+ {gist.name}
+
{onOpenPlainText && (
-
+
- onOpenPlainText(gist.id)}
- icon={}
- variant={"header"}
- >
+ onOpenPlainText(gist.id)} icon={} variant={'header'}>
Raw
)}
-
+
- onDownload(gist.name, gist.code)}
- icon={}
- variant={"header"}
- >
+ onDownload(gist.name, gist.code)} icon={} variant={'header'}>
Download
- );
+ )
}
diff --git a/src/components/ui/gist-landing.tsx b/src/components/ui/gist-landing.tsx
index 1c02ffb..230f31c 100644
--- a/src/components/ui/gist-landing.tsx
+++ b/src/components/ui/gist-landing.tsx
@@ -21,7 +21,7 @@ interface GistLandingProps {
export default function GistLanding({ gist, onDownload, onShare, onShareDialog, onGistNameChange, onGistCodeChange, onOpenFile, isShareDialogOpen, setIsShareDialogOpen }: GistLandingProps) {
return (
-
+
diff --git a/src/components/ui/modal.tsx b/src/components/ui/modal.tsx
index cc356a7..8a3f07c 100644
--- a/src/components/ui/modal.tsx
+++ b/src/components/ui/modal.tsx
@@ -13,7 +13,7 @@ export function Modal({ trigger, title, content, footer, open, onOpenChange }: M
return (