feature/studio-layout #6

Merged
Yussif merged 8 commits from feature/studio-layout into staging 2025-04-27 14:02:15 +00:00
9 changed files with 319 additions and 0 deletions
Showing only changes of commit 65beb0987b - Show all commits

View File

@ -0,0 +1,26 @@
'use client'
import { Button } from '@/components/ui/button'
import React from 'react'
export interface ActionButtonsProps {
icon: React.ReactNode,
label: string,
isGhost?: boolean,
onClick?: () => void
}
function ActionButtons({ icon, label, onClick ,isGhost=false}: ActionButtonsProps) {
return (
<div className='flex flex-col gap-1 items-center align-middle cursor-pointer'>
<Button onClick={onClick} className='flex gap-2 items-center align-middle hover:bg-transparent' variant={isGhost ? 'ghost' : 'default'}>
{icon}
</Button>
<p className='text-xs text-white text-center'>{label}</p>
</div>
)
}
export default ActionButtons

View File

@ -0,0 +1,59 @@
import React from 'react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import {
Eye, Code, Tag, UploadCloud, Layout, Image, Film,
FileCode, MousePointerClick, ChevronLeft, ChevronRight, ChevronsRight,
ChevronsLeft, ChevronLast, Bell, Facebook, Linkedin, Twitter,
Youtube, MessageCircle, Settings,
Upload,
TextSelect
} from "lucide-react";
import ActionButtons from "./ActionButtons";
import { DocumentUpload, Element2, Magicpen, Setting2, Shapes, Text } from 'iconsax-react';
const CategoryHeading = ({ title }: { title: string }) => (
<div className="px-4 py-2 text-xs font-semibold text-gray-100 uppercase">
{title}
</div>
);
const Sidebar = () => {
return (
<div className='w-[376px] h-full bg-[#181923] flex sticky'>
<Tabs defaultValue="text" className=" h-full flex flex-row gap-2">
<TabsList className="w-[89px] bg-[#010313] h-full flex flex-col justify-between rounded-none overflow-y-auto">
<div className='flex flex-col gap-2'>
<TabsTrigger value="text" className="w-[50px] h-[50px] border-b-2 border-transparent data-[state=active]:border-b-white rounded-none data-[state=active]:shadow-none data-[state=active]:bg-transparent"><ActionButtons icon={<Text size="20" color="#ffffff" />} label="Text" isGhost={true} /></TabsTrigger>
<TabsTrigger value="upload" className="w-[50px] h-[50px] border-b-2 border-transparent data-[state=active]:border-b-white rounded-none data-[state=active]:shadow-none data-[state=active]:bg-transparent"><ActionButtons icon={<Upload size="20" color="#ffffff" />} label="Upload" isGhost={true} /></TabsTrigger>
<TabsTrigger value="elements" className="w-[50px] h-[50px] border-b-2 border-transparent data-[state=active]:border-b-white rounded-none data-[state=active]:shadow-none data-[state=active]:bg-transparent"><ActionButtons icon={<Shapes size="20" color="#ffffff" />} label="Elements" isGhost={true} /></TabsTrigger>
<TabsTrigger value="interactions" className="w-[50px] h-[50px] border-b-2 border-transparent data-[state=active]:border-b-white rounded-none data-[state=active]:shadow-none data-[state=active]:bg-transparent"><ActionButtons icon={<TextSelect size="20" color="#ffffff" />} label="Interactions" isGhost={true} /></TabsTrigger>
<TabsTrigger value="templates" className="w-[50px] h-[50px] border-b-2 border-transparent data-[state=active]:border-b-white rounded-none data-[state=active]:shadow-none data-[state=active]:bg-transparent"><ActionButtons icon={<Element2 size="20" color="#ffffff" />} label="Templates" isGhost={true} /></TabsTrigger>
<TabsTrigger value="aiwrite" className="w-[50px] h-[50px] border-b-2 border-transparent data-[state=active]:border-b-white rounded-none data-[state=active]:shadow-none data-[state=active]:bg-transparent"><ActionButtons icon={<Magicpen size="20" color="#ffffff" />} label="Ai Write" isGhost={true} /></TabsTrigger>
</div>
<TabsTrigger value="settings" className="w-[50px] h-[50px] border-b-2 border-transparent data-[state=active]:border-b-white rounded-none data-[state=active]:shadow-none data-[state=active]:bg-transparent"><ActionButtons icon={<Setting2 size="20" color="#ffffff" />} label="Preview" isGhost={true} /></TabsTrigger>
</TabsList>
<TabsContent value="text">
<div className='flex w-full flex-col flex-wrap'>
<CategoryHeading title="Links & Tags" />
<ActionButtons icon={<Text size="20" color="#ffffff" />} label="Link Area" isGhost={false} />
<ActionButtons icon={<Text size="20" color="#ffffff" />} label="Link Area" isGhost={false} />
<ActionButtons icon={<Text size="20" color="#ffffff" />} label="Link Area" isGhost={false} />
<ActionButtons icon={<Text size="20" color="#ffffff" />} label="Link Area" isGhost={false} />
</div>
</TabsContent>
<TabsContent value="upload">DFHDSFGSDFGHSFHWd.</TabsContent>
<TabsContent value="elements"> odit mibus quas porro, modi corrupti repudiandae an</TabsContent>
<TabsContent value="interactions"> odit mibus quas porro, modi corrupti repudiandae an</TabsContent>
<TabsContent value="templates"> odit mibus quas porro, modi corrupti repudiandae an</TabsContent>
<TabsContent value="aiwrite"> odit mibus quas porro, modi corrupti repudiandae an</TabsContent>
<TabsContent value="settings"> odit mibus quas porro, modi corrupti repudiandae an</TabsContent>
</Tabs>
</div>
);
};
export default Sidebar;

90
app/studio/layout.tsx Normal file
View File

@ -0,0 +1,90 @@
import { Button } from '@/components/ui/button';
import { Add, ArrowDown2, Eye, Pointer } from 'iconsax-react';
import { Download, PointerIcon, Redo2, Share2, Undo2 } from 'lucide-react';
import type { Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';
import React from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import ActionButtons from '../components/common/ActionButtons';
import Sidebar from '../components/common/SideBar';
export const metadata: Metadata = {
title: 'Studio',
}
export interface Props {
children: React.ReactNode
}
function layout({ children }: Props) {
return (
<div className="flex flex-col h-screen w-full">
{/* NavBar */}
<div className='w-full h-[80px] flex items-center align-middle bg-white shadow-md justify-between pl-5 pr-5'>
<div className='w-[60%] flex items-center align-middle justify-between gap-2 '>
<Image src="/images/logo.png" alt="logo" width={100} height={100} className='w-[116px] h-[53px]' />
<div className='flex items-center gap-4'>
<Link href="#" className='text-black '>Home</Link>
<Link href="#" className='text-black '>View</Link>
<Link href="#" className='text-black '>Help</Link>
<p className='text-gray-700 text-sm'>Last saved 5 seconds ago</p>
</div>
<div className='flex items-center gap-4'>
<Pointer size="20" color="#555555" className='cursor-pointer' />
<PointerIcon size="20" color="#555555" className=' cursor-pointer' />
<div className="flex items-center gap-1 cursor-pointer">
<p>100%</p>
<ArrowDown2 size="20" color="#555555" />
</div>
<div className="flex items-center gap-1">
<Undo2 size="20" color="#555555" className=' cursor-pointer' />
<Redo2 size="20" color="#555555" className=' cursor-pointer' />
</div>
</div>
</div>
<div className='flex w-[30%] gap-3 items-center align-middle'>
<Button variant='outline' className='h-10 w-10 p-2'>
<div className="h-5 w-5 bg-black rounded-full">.</div>
</Button>
<Button variant='outline' className='h-10 w-10 p-2'>
<Download size="20" color="#555555" />
</Button>
<Button variant='outline' className='h-10 p-2 flex gap-2'>
<Eye size="20" color="#555555" />
<p>Preview</p>
</Button>
<Button className='h-10 p-2 flex gap-2 bg-[#1A237E]'>
<Share2 size="20" color="#ffffff" />
<p>Share</p>
</Button>
<div className='flex relative'>
<Image src="/images/profile.jpeg" alt="profile" width={40} height={40} className='w-[40px] h-[40px] rounded-full z-10' />
<Button variant='outline' className='absolute right-0 left-7 h-10 w-10 p-2 rounded-full'>
<Add size="20" color="#555555" />
</Button>
</div>
</div>
</div>
{/* End of NavBar */}
{/* Main Content with Sidebar */}
<div className='flex w-full h-[calc(100vh-80px)]'>
{/* Sidebar */}
<Sidebar/>
<div className='w-[80%] h-full bg-white'>{children}</div>
</div>
</div>
);
}
export default layout

22
app/studio/page.tsx Normal file
View File

@ -0,0 +1,22 @@
import React from 'react'
export default function Studio() {
return (
<div>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod nam autem
ratione et perspiciatis iure ab ducimus, maxime distinctio in amet fugit de
lectus optio asperiores neque unde possimus cumque repudiandae nulla laudantium. E
rror iure laborum fuga quia, at ut dolor consectetur culpa provident mollitia inven
tore cumque obcaecati ab esse similique recusandae, tempore architecto nulla molestias i
mpedit optio animi. Provident sed quisquam reprehenderit possimus voluptatum voluptates p
erferendis esse qui excepturi doloremque quos similique eius vel fuga impedit neque nulla d
ebitis, rem praesentium accusamus! Commodi, temporibus maxime ex amet facilis quasi reprehenderit
cumque quidem repudiandae, earum odio ab cum qui rerum eum culpa. Aliquid corrupti et fuga evenie
t ipsa, consequatur a suscipit excepturi laudantium! Unde quasi, incidunt doloribus optio cupiditate eius.
Incidunt eaque natus et magnam in maiores laudantium necessitatibus possimus rem porro, placeat officiis
eligendi, nisi ducimus! Corrupti, aliquam vitae. Ipsum commodi perspiciatis laudantium eum eligendi repellat amet, su
nt enim obcaecati non architecto, delectus nisi iusto quas voluptates cum doloribus quod sapiente sint beatae qui assumenda. Hic, corr
upti magni quas quaerat odit harum modi ullam accusamus fugiat accusantium veniam quis voluptate iusto optio porro, tempore cumque ratione ut quam
volupta
tum perferendis repudiandae eius amet! Ratione aliquid quod laudantium suscipit explicabo inventore.</div>
)
}

53
components/ui/avatar.tsx Normal file
View File

@ -0,0 +1,53 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
function Avatar({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
className
)}
{...props}
/>
)
}
export { Avatar, AvatarImage, AvatarFallback }

View File

@ -9,10 +9,12 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.7",
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-tabs": "^1.1.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"iconsax-react": "^0.0.8",
"lucide-react": "^0.503.0",
"next": "15.3.1",
"react": "^19.0.0",

View File

@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@radix-ui/react-avatar':
specifier: ^1.1.7
version: 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot':
specifier: ^1.2.0
version: 1.2.0(@types/react@19.1.2)(react@19.1.0)
@ -20,6 +23,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
iconsax-react:
specifier: ^0.0.8
version: 0.0.8(react@19.1.0)
lucide-react:
specifier: ^0.503.0
version: 0.503.0(react@19.1.0)
@ -326,6 +332,19 @@ packages:
'@radix-ui/primitive@1.1.2':
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
'@radix-ui/react-avatar@1.1.7':
resolution: {integrity: sha512-V7ODUt4mUoJTe3VUxZw6nfURxaPALVqmDQh501YmaQsk3D8AZQrOPRnfKn4H7JGDLBc0KqLhT94H79nV88ppNg==}
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-collection@1.1.4':
resolution: {integrity: sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==}
peerDependencies:
@ -463,6 +482,15 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-is-hydrated@0.1.0':
resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.1':
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
peerDependencies:
@ -1219,6 +1247,11 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
iconsax-react@0.0.8:
resolution: {integrity: sha512-l3dVk4zGtkkJHgvNYqAf0wDKqnKxXykee5/DoESGo2JvSYwaxajJUHSX2YrPRXSov8Hd8ClGFwJxCEaEjrFD1Q==}
peerDependencies:
react: '*'
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@ -1872,6 +1905,11 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
use-sync-external-store@1.5.0:
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@ -2109,6 +2147,19 @@ snapshots:
'@radix-ui/primitive@1.1.2': {}
'@radix-ui/react-avatar@1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
'@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
'@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.2)(react@19.1.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
'@radix-ui/react-collection@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
@ -2226,6 +2277,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
'@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.2)(react@19.1.0)':
dependencies:
react: 19.1.0
use-sync-external-store: 1.5.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.2
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.2)(react@19.1.0)':
dependencies:
react: 19.1.0
@ -3136,6 +3194,11 @@ snapshots:
dependencies:
function-bind: 1.1.2
iconsax-react@0.0.8(react@19.1.0):
dependencies:
prop-types: 15.8.1
react: 19.1.0
ignore@5.3.2: {}
import-fresh@3.3.1:
@ -3874,6 +3937,10 @@ snapshots:
dependencies:
punycode: 2.3.1
use-sync-external-store@1.5.0(react@19.1.0):
dependencies:
react: 19.1.0
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
public/images/profile.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB