Skip to content

Commit 03a0642

Browse files
authored
Merge pull request #54 from cortex-reply/feat/restruture
feat: foundary components init
2 parents 5e9963f + 13b5733 commit 03a0642

48 files changed

Lines changed: 12112 additions & 27 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/components/AdvancedComponents/user-selection.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
"use client"
1+
'use client'
22

3-
import { useState, useMemo } from "react"
4-
import { Search, User, Check, ChevronLeft } from "lucide-react"
5-
import { Button } from "@/components/ui/button"
6-
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
7-
import { Input } from "@/components/ui/input"
8-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
9-
import { Badge } from "@/components/ui/badge"
10-
import { motion, AnimatePresence } from "motion/react"
11-
import { type User as UserType} from "../DigitalColleagues/types"
3+
import { useState, useMemo } from 'react'
4+
import { Search, User, Check, ChevronLeft } from 'lucide-react'
5+
import { Button } from '@/components/ui/button'
6+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
7+
import { Input } from '@/components/ui/input'
8+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
9+
import { Badge } from '@/components/ui/badge'
10+
import { motion, AnimatePresence } from 'motion/react'
11+
import { type User as UserType } from '../DigitalColleagues/types'
1212

1313
interface UserSelectionProps {
1414
users: UserType[]
@@ -23,15 +23,15 @@ export function UserSelection({
2323
onCancel,
2424
selectedUserId,
2525
}: UserSelectionProps) {
26-
const [searchTerm, setSearchTerm] = useState("")
26+
const [searchTerm, setSearchTerm] = useState('')
2727

2828
const filteredUsers = useMemo(() => {
2929
return users.filter((user) => {
30-
const matchesSearch =
30+
const matchesSearch =
3131
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
3232
user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
3333
user.role.toLowerCase().includes(searchTerm.toLowerCase()) ||
34-
(user.skills || []).some(skill => skill.toLowerCase().includes(searchTerm.toLowerCase()))
34+
(user.skills || []).some((skill) => skill.toLowerCase().includes(searchTerm.toLowerCase()))
3535

3636
return matchesSearch
3737
})
@@ -92,7 +92,7 @@ export function UserSelection({
9292
animate={{ opacity: 1, scale: 1 }}
9393
transition={{ duration: 0.2 }}
9494
>
95-
<Card
95+
<Card
9696
className={`cursor-pointer transition-all hover:shadow-lg ${
9797
selectedUserId === user.id ? 'ring-2 ring-primary' : ''
9898
}`}
@@ -112,9 +112,7 @@ export function UserSelection({
112112
<p className="text-sm text-muted-foreground">{user.role}</p>
113113
</div>
114114
</div>
115-
{selectedUserId === user.id && (
116-
<Check className="h-5 w-5 text-primary" />
117-
)}
115+
{selectedUserId === user.id && <Check className="h-5 w-5 text-primary" />}
118116
</div>
119117
</CardHeader>
120118
<CardContent className="pt-0">
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
'use client'
2+
import React from 'react'
3+
import { ChevronRight } from 'lucide-react'
4+
5+
import {
6+
Collapsible,
7+
CollapsibleContent,
8+
CollapsibleTrigger,
9+
} from '@/components//ui/collapsible'
10+
import clsx from 'clsx'
11+
import { Skeleton } from '@/components//ui/skeleton'
12+
13+
import {
14+
Sidebar,
15+
SidebarContent,
16+
SidebarFooter,
17+
SidebarGroup,
18+
SidebarGroupContent,
19+
SidebarHeader,
20+
SidebarMenu,
21+
SidebarMenuAction,
22+
SidebarMenuButton,
23+
SidebarMenuItem,
24+
SidebarMenuSub,
25+
SidebarMenuSubButton,
26+
SidebarMenuSubItem,
27+
} from '@/components//ui/sidebar'
28+
29+
30+
31+
type FileType = 'published' | 'draft' | 'note'
32+
33+
type Metadata = {
34+
backend: 'github'
35+
owner: string
36+
repo: string
37+
path: string
38+
branch?: string
39+
encoding?: string
40+
}
41+
42+
interface LinkItem {
43+
label: string
44+
url: string
45+
type?: FileType
46+
metadata?: Metadata
47+
}
48+
49+
type MenuStructure ={
50+
label: string
51+
url?: string
52+
isActive?: boolean
53+
icon?: React.ComponentType<React.ComponentProps<'svg'>>
54+
type?: FileType
55+
links?: LinkItem[] | undefined
56+
}
57+
58+
export type NavigationItem = {
59+
label: string
60+
url: string
61+
isActive?: boolean // Optional property for active items
62+
isDraft?: boolean // Optional property for draft items
63+
items?: NavigationItem[] // Recursive type for nested items
64+
icon?: React.ComponentType<React.ComponentProps<'svg'>>
65+
}
66+
67+
interface SidebarLeftProps extends React.ComponentProps<typeof Sidebar> {
68+
mainNav?: MenuStructure[]
69+
secondaryNav?: MenuStructure[]
70+
ongoingWork?: MenuStructure[]
71+
title?: string
72+
subTitle?: string
73+
pathName?: string
74+
menuHeading?: string
75+
onNavClick?: (callback: any) => void
76+
loading?: boolean
77+
LinkComponent?: React.ComponentType<React.ComponentProps<'a'>>
78+
}
79+
80+
export function AppSidebarLeft({
81+
mainNav,
82+
secondaryNav,
83+
ongoingWork,
84+
title,
85+
subTitle,
86+
pathName,
87+
menuHeading,
88+
onNavClick,
89+
loading = false,
90+
LinkComponent,
91+
...props
92+
}: SidebarLeftProps) {
93+
interface ButtonProps {
94+
href?: string
95+
children: React.ReactNode
96+
}
97+
const Button: React.FC<ButtonProps> = ({ href, children, ...props }) => {
98+
return (
99+
<button
100+
onClick={() => onNavClick && onNavClick(href)}
101+
className="flex items-center justify-start w-full text-sm text-left px-4 py-2"
102+
{...props}
103+
>
104+
{children}
105+
</button>
106+
)
107+
}
108+
// if onNavClick is provided, pass the callback to the buttons. else, render an anchor tag
109+
const Link: React.FC<ButtonProps & React.ComponentProps<'a'>> = LinkComponent
110+
? (props) => <LinkComponent {...props} />
111+
: onNavClick
112+
? Button
113+
: (props) => <a {...props} />
114+
115+
return (
116+
<Sidebar {...props} variant="inset">
117+
<SidebarHeader>
118+
<SidebarMenu>
119+
<SidebarMenuItem>
120+
{loading ? (
121+
<div className="grid flex-1 p-2 text-left text-m leading-tight">
122+
<Skeleton className="bg-gray-200 my-2 w-full h-7" />
123+
<Skeleton className="bg-gray-200 w-3/4 my-2 h-5" />
124+
</div>
125+
) : (
126+
<div className="grid flex-1 p-2 text-left text-m leading-tight">
127+
<span className="truncate font-semibold">{title}</span>
128+
{subTitle && <span className="truncate text-sm">{subTitle}</span>}
129+
</div>
130+
)}
131+
</SidebarMenuItem>
132+
</SidebarMenu>
133+
</SidebarHeader>
134+
135+
{loading && (
136+
<SidebarContent>
137+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
138+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
139+
140+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
141+
142+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
143+
144+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
145+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
146+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
147+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
148+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
149+
<Skeleton className="bg-gray-200 w-3/4 h-5" />
150+
</SidebarContent>
151+
)}
152+
153+
<SidebarContent>
154+
<div className='px-4 text-sm'>Recent Work</div>
155+
{ongoingWork &&
156+
!loading &&
157+
ongoingWork.map((item, index) => (
158+
<Menu key={index} subNav={item} pathName={pathName} Link={Link} />
159+
))}
160+
{/* horizontal line */}
161+
<div className="border-t border-gray-200 my-4"></div>
162+
{mainNav &&
163+
!loading &&
164+
mainNav.map((item, index) => (
165+
<Menu key={index} subNav={item} pathName={pathName} Link={Link} />
166+
))}
167+
<SidebarGroup className="mt-auto">
168+
<SidebarGroupContent>
169+
<SidebarMenu>
170+
{secondaryNav &&
171+
!loading &&
172+
secondaryNav.map((item) => (
173+
<SidebarMenuItem key={item.label}>
174+
<SidebarMenuButton asChild size="sm">
175+
<Link href={item.url || ''}>
176+
{item.icon && <item.icon />}
177+
<span>{item.label}</span>
178+
</Link>
179+
</SidebarMenuButton>
180+
</SidebarMenuItem>
181+
))}
182+
</SidebarMenu>
183+
</SidebarGroupContent>
184+
</SidebarGroup>
185+
</SidebarContent>
186+
<SidebarFooter></SidebarFooter>
187+
</Sidebar>
188+
)
189+
}
190+
191+
function Menu({
192+
subNav,
193+
pathName,
194+
Link,
195+
}: {
196+
menuHeading?: string
197+
subNav: MenuStructure
198+
pathName?: string
199+
Link: React.FC<
200+
{
201+
href?: string
202+
children: React.ReactNode
203+
} & React.ComponentProps<'a'>
204+
>
205+
}) {
206+
const item = subNav
207+
let isActive = false
208+
// if a link within subNav.links equals the current pathName, set isActive to true ( this will open the collapsible )
209+
if (item.links) {
210+
item.links.map((link) => {
211+
if (link.url === pathName) {
212+
isActive = true
213+
}
214+
})
215+
}
216+
return (
217+
<Collapsible defaultOpen className="group/collapsible">
218+
<SidebarGroup className="py-0">
219+
<SidebarMenu>
220+
<Collapsible key={item.label} asChild defaultOpen={item.isActive}>
221+
<SidebarMenuItem>
222+
<SidebarMenuButton
223+
asChild
224+
tooltip={item.label}
225+
className={clsx(isActive && 'font-bold text-accent')}
226+
>
227+
<Link href={item.url}>
228+
{item.icon && <item.icon />}
229+
<span>{item.label}</span>
230+
</Link>
231+
</SidebarMenuButton>
232+
{item.links?.length ? (
233+
<>
234+
<CollapsibleTrigger asChild>
235+
<SidebarMenuAction className="data-[state=open]:rotate-90">
236+
<ChevronRight />
237+
<span className="sr-only">Toggle</span>
238+
</SidebarMenuAction>
239+
</CollapsibleTrigger>
240+
<CollapsibleContent>
241+
<SidebarMenuSub>
242+
{item.links?.map((subItem) => (
243+
<SidebarMenuSubItem key={subItem.label}>
244+
<SidebarMenuSubButton
245+
asChild
246+
className={clsx(subItem.url === pathName && 'font-bold text-accent')}
247+
>
248+
<Link href={subItem.url}>
249+
<span>{subItem.label}</span>
250+
</Link>
251+
</SidebarMenuSubButton>
252+
</SidebarMenuSubItem>
253+
))}
254+
</SidebarMenuSub>
255+
</CollapsibleContent>
256+
</>
257+
) : null}
258+
</SidebarMenuItem>
259+
</Collapsible>
260+
</SidebarMenu>
261+
</SidebarGroup>
262+
</Collapsible>
263+
)
264+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
import type { Meta, StoryObj } from '@storybook/react';
3+
import { action } from '@storybook/addon-actions';
4+
import { DashboardHeader } from './DashboardHeader';
5+
6+
const meta: Meta<typeof DashboardHeader> = {
7+
title: 'Digital Colleagues/DashboardHeader',
8+
component: DashboardHeader,
9+
parameters: {
10+
layout: 'fullscreen',
11+
},
12+
argTypes: {
13+
isRightSidebarOpen: {
14+
control: 'boolean',
15+
description: 'Whether the right sidebar is open',
16+
},
17+
onToggleRightSidebar: {
18+
action: 'toggled sidebar',
19+
description: 'Function called when sidebar toggle is clicked',
20+
},
21+
},
22+
};
23+
24+
export default meta;
25+
type Story = StoryObj<typeof DashboardHeader>;
26+
27+
export const Default: Story = {
28+
args: {
29+
isRightSidebarOpen: false,
30+
onToggleRightSidebar: action('onToggleRightSidebar'),
31+
},
32+
};
33+
34+
export const SidebarOpen: Story = {
35+
args: {
36+
isRightSidebarOpen: true,
37+
onToggleRightSidebar: action('onToggleRightSidebar'),
38+
},
39+
};

0 commit comments

Comments
 (0)