added sidebar

This commit is contained in:
paul
2024-08-04 22:47:43 +05:30
parent 12431e0c2b
commit 940fe815c6
14 changed files with 1351 additions and 29 deletions

1000
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@dnd-kit/core": "^6.1.0",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"antd": "^5.20.0",
"autoprefixer": "^10.4.20",
"postcss-cli": "^11.0.0",
"react": "^18.3.1",
@@ -20,7 +23,7 @@
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"build": "GENERATE_SOURCEMAP=false env-cmd -e production react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},

View File

@@ -1,23 +1,34 @@
import './styles/tailwind.css'
import logo from './assets/logo/logo.svg';
import Sidebar from './sidebar/sidebar';
import { LayoutFilled, ProductFilled, CloudUploadOutlined } from "@ant-design/icons";
import WidgetsContainer from './sidebar/widgetsContainer';
function App() {
const tabs = [
{
name: "Widgets",
icon: <LayoutFilled />,
content: <WidgetsContainer />
},
{
name: "Extensions",
icon: <ProductFilled />,
content: <></>
},
{
name: "Uploads",
icon: <CloudUploadOutlined />,
content: <></>
}
]
return (
<div className="tw-w-full tw-bg-black">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<div className="tw-w-full tw-h-[100vh] tw-flex tw-bg-primaryBg">
<Sidebar tabs={tabs}/>
</div>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

0
src/canvas/main.js Normal file
View File

View File

@@ -0,0 +1,22 @@
import React from "react"
import {useDraggable} from "@dnd-kit/core"
function Draggable(props) {
const {attributes, listeners, setNodeRef, transform} = useDraggable({
id: 'draggable',
})
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
} : undefined
return (
<button className={`tw-bg-transparent tw-outline-none tw-border-none ${props.className}`} ref={setNodeRef} style={style} {...listeners} {...attributes}>
{props.children}
</button>
)
}
export default Draggable

View File

@@ -0,0 +1,49 @@
import { useEffect, useMemo } from "react"
import Draggable from "./draggable"
import { GithubOutlined, GitlabOutlined, LinkOutlined } from "@ant-design/icons"
function DraggableWidgetCard({name, img, url}){
const urlIcon = useMemo(() => {
if (url){
const host = new URL(url).hostname.toLowerCase()
if (host === "github.com"){
return <GithubOutlined />
}else if(host === "gitlab.com"){
return <GitlabOutlined />
}else{
return <LinkOutlined />
}
}
}, [url])
useEffect(() => {
}, [])
return (
<Draggable className="tw-cursor-pointer">
<div className="tw-w-full tw-h-[240px] tw-flex tw-flex-col tw-rounded-md tw-overflow-hidden
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px] tw-border-[#888] ">
<div className="tw-h-[200px] tw-w-full tw-overflow-hidden">
<img src={img} alt={name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
</div>
<span className="tw-text-xl">{name}</span>
<div className="tw-flex tw-text-lg tw-justify-between tw-px-4">
<a href={url} className="tw-text-black" target="_blank" rel="noopener noreferrer">
{urlIcon}
</a>
</div>
</div>
</Draggable>
)
}
export default DraggableWidgetCard

View File

View File

@@ -1,6 +1,6 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./styles/index.css";
import App from "./App";
import store from "./redux/store"
@@ -9,6 +9,8 @@ import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "react-query";
import "./styles/tailwind.css";
import "./styles/index.css";
const originalSetItem = localStorage.setItem;
// triggers itemsChaned event whenever the item in localstorage is chanegd.

101
src/sidebar/sidebar.js Normal file
View File

@@ -0,0 +1,101 @@
import { useEffect, useRef, useMemo, useState } from "react";
import { CloseCircleFilled } from "@ant-design/icons";
function Sidebar({tabs}){
const sideBarRef = useRef()
const sideBarExtraRef = useRef()
const [activeTab, setActiveTab] = useState(-1) // -1 indicates no active tabs
const [hoverIndex, setHoverIndex] = useState(-1) // -1 indicates no active tabs
const [sidebarOpen, setSidebarOpen] = useState(false)
const sidebarTabs = useMemo(() => tabs, [tabs])
useEffect(() => {
}, [sideBarRef, sideBarExtraRef, sidebarOpen])
const openSidebar = () => {
sideBarRef.current?.classList.add("tw-w-[400px]")
sideBarRef.current?.classList.remove("tw-w-[80px]")
setSidebarOpen(true)
}
const closeSidebar = () => {
sideBarRef.current?.classList.add("tw-w-[80px]")
sideBarRef.current?.classList.remove("tw-w-[400px]")
setSidebarOpen(false)
setActiveTab(-1)
setHoverIndex(-1)
}
const hideOnMouseLeave = () => {
if (activeTab === -1){
closeSidebar()
}
}
return (
<div className={`tw-relative tw-min-w-[80px] tw-duration-[0.3s] tw-transition-all
tw-max-w-[400px] tw-flex tw-h-full
${sidebarOpen ? "tw-bg-white tw-w-[400px] tw-shadow-lg": "tw-bg-primaryBg tw-w-[80px]"}
`} ref={sideBarRef}
onMouseLeave={hideOnMouseLeave}
>
<div className="tw-w-full tw-max-w-[80px] tw-h-full tw-flex tw-flex-col tw-gap-4 tw-p-3 tw-place-items-center">
{
sidebarTabs.map((tab, index) => {
return (
<div className={`${activeTab === index ? "tw-text-blue-400 " : "tw-text-gray-600"} tw-cursor-pointer
hover:tw-text-blue-400 tw-flex tw-flex-col tw-gap-2 tw-place-items-center`}
key={tab.name}
onMouseEnter={() => {
openSidebar()
setHoverIndex(index)
}}
onClick={() => {
setActiveTab(index)
}}
>
<div className="tw-bg-white tw-shadow-lg tw-p-2 tw-rounded-md">
{tab.icon}
</div>
<span className="tw-text-[12px] ">{tab.name}</span>
</div>
)
})
}
</div>
<div className="tw-w-full tw-h-full tw-bg-inherit tw-flex tw-flex-col tw-overflow-x-hidden" ref={sideBarExtraRef}>
<div className="tw-w-full tw-h-[50px] tw-flex tw-place-content-end tw-p-1">
<button className="tw-outline-none tw-bg-transparent tw-border-none tw-text-gray-600 tw-cursor-pointer tw-text-xl"
onClick={closeSidebar}
>
<CloseCircleFilled />
</button>
</div>
<div className="tw-flex tw-w-full tw-h-full tw-max-h-full tw-overflow-y-auto">
{(activeTab > -1 || hoverIndex > -1) && tabs[activeTab > -1 ? activeTab : hoverIndex].content}
</div>
</div>
</div>
);
}
export default Sidebar;

View File

@@ -0,0 +1,100 @@
import { useEffect, useMemo, useState } from "react"
import { CloseCircleFilled, SearchOutlined } from "@ant-design/icons"
import DraggableWidgetCard from "../components/utils/widgetCard"
import ButtonWidget from "../assets/widgets/button.png"
import { filterObjectListStartingWith } from "../utils/filter"
function WidgetsContainer(){
const widgets = useMemo(() => {
return [
{
name: "TopLevel",
img: ButtonWidget,
link: "https://github.com"
},
{
name: "Frame",
img: ButtonWidget,
link: "https://github.com"
},
{
name: "Button",
img: ButtonWidget,
link: "https://github.com"
},
{
name: "Input",
img: ButtonWidget,
link: "https://github.com"
},
]
}, [])
const [searchValue, setSearchValue] = useState("")
const [widgetData, setWidgetData] = useState(widgets)
useEffect(() => {
setWidgetData(widgets)
}, [widgets])
useEffect(() => {
if (searchValue.length > 0){
const searchData = filterObjectListStartingWith(widgets, "name", searchValue)
setWidgetData(searchData)
}else{
setWidgetData(widgets)
}
}, [searchValue])
function onSearch(event){
setSearchValue(event.target.value)
}
return (
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col">
<div className="tw-flex tw-gap-2 input tw-place-items-center">
<SearchOutlined />
<input type="text" placeholder="Search" className="tw-outline-none tw-w-full tw-border-none"
id="" onInput={onSearch} value={searchValue}/>
<div className="">
{
searchValue.length > 0 &&
<div className="tw-cursor-pointer tw-text-gray-600" onClick={() => setSearchValue("")}>
<CloseCircleFilled />
</div>
}
</div>
</div>
<div className="tw-flex tw-flex-col tw-gap-2 tw-h-full tw-p-1">
{
widgetData.map((widget, index) => {
return (
<DraggableWidgetCard key={widget.name}
name={widget.name}
img={widget.img}
url={widget.link}
/>
)
})
}
</div>
</div>
)
}
export default WidgetsContainer

View File

@@ -1,13 +1,25 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
*{
box-sizing: border-box !important;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.input{
border: 2px solid #e3e5e8;
padding: 2px 8px;
min-height: 40px;
border-radius: 10px;
outline: none;
}
.input:active, .input:focus, .input:focus-within{
border-color: #60a5fa;
}

21
src/utils/filter.js Normal file
View File

@@ -0,0 +1,21 @@
/**
* given a list of objects filters out objects starting with specific value for a given key
* @param {any[]} list
* @param {string} key
* @param {string} valueStart
* @param {boolean} ignoreCase - default true
*/
export function filterObjectListStartingWith(list, key, valueStart, ignoreCase = true) {
if (ignoreCase)
valueStart = valueStart.toLocaleLowerCase()
return list.filter(obj => {
const value = obj[key]
if (ignoreCase)
return value.toLowerCase().startsWith(valueStart)
else
return value.startsWith(valueStart)
})
}

View File

@@ -11,6 +11,7 @@ module.exports = {
theme: {
extend: {
colors: {
primaryBg: "#f6f7f8"
}
},
},