diff --git a/src/app/styles/animations.scss b/src/app/styles/animations.scss index 0c64494..99104e0 100644 --- a/src/app/styles/animations.scss +++ b/src/app/styles/animations.scss @@ -66,3 +66,31 @@ } } } + +.collapsible-content { + overflow: hidden; +} +.collapsible-content[data-state='open'] { + animation: slideDown 300ms ease-out; +} +.collapsible-content[data-state='closed'] { + animation: slideUp 300ms ease-out; +} + +@keyframes slideDown { + from { + height: 0; + } + to { + height: var(--radix-collapsible-content-height); + } +} + +@keyframes slideUp { + from { + height: var(--radix-collapsible-content-height); + } + to { + height: 0; + } +} diff --git a/src/shared/ui/sidebar/Sidebar.tsx b/src/shared/ui/sidebar/Sidebar.tsx index 3069e61..60af193 100644 --- a/src/shared/ui/sidebar/Sidebar.tsx +++ b/src/shared/ui/sidebar/Sidebar.tsx @@ -629,7 +629,7 @@ function SidebarMenuSubButton({ data-size={size} data-active={isActive} className={cn( - 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden group-data-[collapsible=icon]:hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', + 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden group-data-[collapsible=icon]:hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[size=md]:text-sm data-[size=sm]:text-xs [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', className )} {...props} diff --git a/src/widgets/app-sidebar/config/sidebar.ts b/src/widgets/app-sidebar/config/sidebar.ts new file mode 100644 index 0000000..7652ac1 --- /dev/null +++ b/src/widgets/app-sidebar/config/sidebar.ts @@ -0,0 +1,13 @@ +import { Mail, Settings, ShieldUser, UsersRound } from 'lucide-react'; +import { routes } from 'shared/config'; + +export const team = [ + { + url: routes.team.members(), + title: 'Участники', + icon: UsersRound, + }, + { url: routes.team.invitations(), title: 'Приглашения', icon: Mail }, + { url: routes.team.roles(), title: 'Роли', icon: ShieldUser }, + { url: routes.team.settings(), title: 'Настройки', icon: Settings }, +] as const; diff --git a/src/widgets/app-sidebar/ui/AppSidebar.tsx b/src/widgets/app-sidebar/ui/AppSidebar.tsx index 5d0c81a..5094de9 100644 --- a/src/widgets/app-sidebar/ui/AppSidebar.tsx +++ b/src/widgets/app-sidebar/ui/AppSidebar.tsx @@ -1,46 +1,16 @@ -'use client'; - -import { InviteTeamMemberDialog } from 'features/teams/invite'; -import { ChevronRight, SquarePlusIcon, UserRound, UsersRound } from 'lucide-react'; -import Link from 'next/link'; -import { routes } from 'shared/config'; import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, Separator, Sidebar, SidebarContent, - SidebarFooter, SidebarGroup, SidebarHeader, SidebarMenu, - SidebarMenuAction, - SidebarMenuButton, - SidebarMenuItem, - SidebarMenuSub, - SidebarMenuSubButton, - SidebarMenuSubItem, SidebarRail, } from 'shared/ui'; -import { NavUser } from './NavUser'; import { TeamsDropdown } from './teams/TeamsDropdown'; import { Projects } from './Projects'; - -const team = [ - { - url: routes.team.members(), - title: 'Участники', - action: ( - - - - ), - }, - { url: routes.team.invitations(), title: 'Приглашения', action: null }, - { url: routes.team.roles(), title: 'Роли', action: null }, - { url: routes.team.settings(), title: 'Настройки', action: null }, -]; +import { MyTeams } from './MyTeams'; +import { Team } from './Team'; export function AppSidebar({ ...props }: Omit, 'children'>) { return ( @@ -51,49 +21,13 @@ export function AppSidebar({ ...props }: Omit - - - - - Мой профиль - - - - - - - - - Команда - - - - - - {team.map((subItem) => ( - - - - {subItem.title} - - - {subItem.action ? ( - {subItem.action} - ) : null} - - ))} - - - - + + - - - ); diff --git a/src/widgets/app-sidebar/ui/MyTeams.tsx b/src/widgets/app-sidebar/ui/MyTeams.tsx new file mode 100644 index 0000000..1423db5 --- /dev/null +++ b/src/widgets/app-sidebar/ui/MyTeams.tsx @@ -0,0 +1,24 @@ +'use client'; +import { Network } from 'lucide-react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { routes } from 'shared/config'; +import { SidebarMenuButton, SidebarMenuItem } from 'shared/ui'; + +export function MyTeams() { + const pathname = usePathname(); + return ( + + + + + Мои команды + + + + ); +} diff --git a/src/widgets/app-sidebar/ui/Projects.tsx b/src/widgets/app-sidebar/ui/Projects.tsx index 2a13373..653a3be 100644 --- a/src/widgets/app-sidebar/ui/Projects.tsx +++ b/src/widgets/app-sidebar/ui/Projects.tsx @@ -6,8 +6,9 @@ import { useTeamStore } from 'entities/team'; import { ArchiveProjectDialog, RestoreProjectDialog } from 'features/projects/archive'; import { CreateProjectDialog } from 'features/projects/create'; import { ShareProjectDialog } from 'features/projects/share'; -import { Archive, Link2, MoreHorizontal, Plus } from 'lucide-react'; +import { Archive, BriefcaseBusiness, Link2, MoreHorizontal, Plus } from 'lucide-react'; import Link from 'next/link'; +import { usePathname } from 'next/navigation'; import { useState } from 'react'; import { routes } from 'shared/config'; import { @@ -16,7 +17,6 @@ import { DropdownMenuItem, DropdownMenuTrigger, SidebarGroup, - SidebarGroupAction, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, @@ -28,8 +28,11 @@ import { export function Projects() { const slug = useTeamStore.use.slug(); const { isMobile } = useSidebar(); + const pathname = usePathname(); const [createProjectOpen, setCreateProjectOpen] = useState(false); const projects = useQuery({ ...ProjectQueries.getProjects(slug!), enabled: !!slug }); + const projectList = projects.data?.items.slice(0, 6) ?? []; + const totalProjects = projects.data?.items.length ?? 0; if (!projects.data) { return null; @@ -39,20 +42,13 @@ export function Projects() { <> Проекты - setCreateProjectOpen(true)} - > - - - {projects.data.items.map((project) => { + {projectList.map((project) => { const canManage = Boolean(slug && project.canEdit); return ( - + {projectIconCodeToEmoji(project.icon)} {project.name} @@ -117,12 +113,25 @@ export function Projects() { ); })} - - - - Больше - - + + + + Все проекты {!!totalProjects && `(${totalProjects})`} + + + + + setCreateProjectOpen(true)} + tooltip={'Добавить проект'} + > + + Добавить проект + diff --git a/src/widgets/app-sidebar/ui/Team.tsx b/src/widgets/app-sidebar/ui/Team.tsx new file mode 100644 index 0000000..593a39e --- /dev/null +++ b/src/widgets/app-sidebar/ui/Team.tsx @@ -0,0 +1,72 @@ +'use client'; +import { InviteTeamMemberDialog } from 'features/teams/invite'; +import { UsersRound, ChevronRight, Plus } from 'lucide-react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { routes } from 'shared/config'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, + useSidebar, +} from 'shared/ui'; +import { team } from '../config/sidebar'; +import { useRouter } from 'next/navigation'; + +export function Team() { + const pathname = usePathname(); + const router = useRouter(); + const { open, isMobile } = useSidebar(); + + const isAllowedToHighlight = !open && !isMobile; + + const handleClickTrigger = () => { + if (isAllowedToHighlight) { + router.push(routes.team.members()); + } + }; + return ( + + + + + + Команда + + + + + + {team.map((subItem) => ( + + + + + {subItem.title} + + + + ))} + + + + + Пригласить участника + + + + + + + + ); +} diff --git a/src/widgets/app-sidebar/ui/teams/TeamsDropdown.tsx b/src/widgets/app-sidebar/ui/teams/TeamsDropdown.tsx index f1de64c..f972c42 100644 --- a/src/widgets/app-sidebar/ui/teams/TeamsDropdown.tsx +++ b/src/widgets/app-sidebar/ui/teams/TeamsDropdown.tsx @@ -1,7 +1,6 @@ +'use client'; import { CreateTeamDialog } from 'features/teams/create'; -import { Plus } from 'lucide-react'; import Link from 'next/link'; -import { useState } from 'react'; import { routes } from 'shared/config'; import { DropdownMenu, @@ -12,18 +11,19 @@ import { DropdownMenuShortcut, DropdownMenuTrigger, SidebarMenuButton, - useSidebar, } from 'shared/ui'; import { useTeamsDropdown } from '../../model/useTeamsDropdown'; import { TeamItem } from './TeamItem'; import { TeamTrigger } from './TeamTrigger'; +import { useIsMobile } from 'shared/lib/hooks'; +import { Plus } from 'lucide-react'; +import { useState } from 'react'; export function TeamsDropdown() { - const { isMobile } = useSidebar(); - const [createTeamOpen, setCreateTeamOpen] = useState(false); const { open, setOpen, query, visibleTeams, teams, hasMoreTeams, switchTeam } = useTeamsDropdown(); - + const isMobile = useIsMobile(); + const [createTeamOpen, setCreateTeamOpen] = useState(false); return ( <>