Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/common/ui/SpacedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react"
import { List, Box } from "@mui/material"

const SpacedList = ({
children,
itemSpacing
}: {
children: React.ReactNode
itemSpacing: number
}) => {
return (
<List disablePadding sx={{ margin: 0 }}>
{React.Children.map(children, (child, idx) => (
<Box sx={{
marginBottom: idx < React.Children.count(children) - 1 ? itemSpacing : 0
}}>
{child}
</Box>
))}
</List>
)
}

export default SpacedList
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
import { useContext } from "react"
import { List, Box, Typography } from "@mui/material"
import { Box, Typography } from "@mui/material"
import { ProjectsContainerContext } from "@/common"
import SpacedList from "@/common/ui/SpacedList"
import { useProjectSelection } from "@/features/projects/data"
import ProjectListItem from "./ProjectListItem"
import ProjectListItemPlaceholder from "./ProjectListItemPlaceholder"
import ProjectListItem, { Skeleton as ProjectListItemSkeleton } from "./ProjectListItem"

const ProjectList = () => {
const { projects, isLoading } = useContext(ProjectsContainerContext)
const projectSelection = useProjectSelection()
const loadingItemCount = 6
if (isLoading || projects.length > 0) {
const itemSpacing = 1
if (isLoading) {
return (
<List disablePadding sx={{ margin: 0 }}>
{isLoading &&
[...new Array(loadingItemCount)].map((_, index) => (
<ProjectListItemPlaceholder key={index}/>
<SpacedList itemSpacing={itemSpacing}>
{
[...new Array(6)].map((_, idx) => (
<ProjectListItemSkeleton key={idx} />
))
}
{!isLoading && projects.map((project, idx) => (
<Box key={project.id} sx={{
marginBottom: idx < projects.length - 1 ? 0.5 : 0
}}>
<ProjectListItem
project={project}
isSelected={project.id === projectSelection.project?.id}
onSelectProject={projectSelection.selectProject}
/>
</Box>
</SpacedList>
)
} else if (projects.length > 0) {
return (
<SpacedList itemSpacing={itemSpacing}>
{projects.map(project => (
<ProjectListItem
key={project.id}
project={project}
selected={project.id === projectSelection.project?.id}
onSelect={() => projectSelection.selectProject(project)}
/>
))}
</List>
</SpacedList>
)
} else {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,67 +1,145 @@
import {
Box,
ListItem,
ListItemButton,
ListItemText,
ListItemButton,
Skeleton as MuiSkeleton,
Stack,
Typography
} from "@mui/material"
import MenuItemHover from "@/common/ui/MenuItemHover"
import { Project } from "@/features/projects/domain"
import ProjectAvatar from "./ProjectAvatar"
import ProjectAvatarSquircle from "./ProjectAvatarSquircle"

const AVATAR_SIZE = { width: 40, height: 40 }

const ProjectListItem = ({
project,
isSelected,
onSelectProject
selected,
onSelect
}: {
project: Project
isSelected: boolean
onSelectProject: (project: Project) => void
selected: boolean
onSelect: () => void
}) => {
return (
<Template
selected={selected}
onSelect={onSelect}
avatar={
<ProjectAvatar
project={project}
width={AVATAR_SIZE.width}
height={AVATAR_SIZE.height}
/>
}>
<ListItemText
primary={
<Typography
variant="body2"
style={{
fontWeight: selected ? 800 : 500,
letterSpacing: 0.1,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis"
}}
>
{project.displayName}
</Typography>
}
/>
</Template>
)
}

export default ProjectListItem

export const Skeleton = () => {
return (
<Template disabled avatar={
<MuiSkeleton
variant="rectangular"
animation="wave"
sx={{ width: "100%", height: "100%" }}
/>
}>
<MuiSkeleton variant="text" animation="wave" width={100} />
</Template>
)
}

const Template = ({
disabled,
selected,
onSelect,
avatar,
children
}: {
disabled?: boolean
selected?: boolean
onSelect?: () => void
avatar: React.ReactNode
children?: React.ReactNode
}) => {
return (
<ListItem disablePadding>
{disabled &&
<ButtonLabel disabled={disabled} avatar={avatar}>
{children}
</ButtonLabel>
}
{!disabled &&
<ListItemButton
onClick={() => onSelectProject(project)}
selected={isSelected}
disabled={disabled}
onClick={onSelect}
selected={selected}
disableGutters
sx={{ padding: 0 }}
>
<MenuItemHover
sx={{
".avatar": {
transform: "scale(1)",
transition: "transform 0.3s ease-in-out"
},
"&:hover .avatar": {
transform: "scale(1.08)"
}
}}>
<Stack direction="row" alignItems="center" spacing={1.5}>
<Box className="avatar">
<ProjectAvatar project={project} width={40} height={40} />
</Box>
<ListItemText
primary={
<Typography
variant="body2"
style={{
fontWeight: isSelected ? 800 : 500,
letterSpacing: 0.1,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis"
}}
>
{project.displayName}
</Typography>
}
/>
</Stack>
</MenuItemHover>
<ButtonLabel disabled={disabled} avatar={avatar}>
{children}
</ButtonLabel>
</ListItemButton>
}
</ListItem>
)
}

export default ProjectListItem
const ButtonLabel = ({
disabled,
avatar,
children
}: {
disabled?: boolean
avatar: React.ReactNode
children?: React.ReactNode
}) => {
return (
<MenuItemHover
disabled={disabled}
sx={{
".avatar": {
transform: "scale(1)",
transition: "transform 0.3s ease-in-out"
},
"&:hover .avatar": {
transform: `scale(${disabled ? 1 : 1.08})`
}
}}>
<Stack direction="row" alignItems="center" spacing={1.5}>
<Box className="avatar">
<ProjectAvatarSquircle
width={AVATAR_SIZE.width}
height={AVATAR_SIZE.height}
sx={{ position: "relative" }}
>
{avatar}
</ProjectAvatarSquircle>
</Box>
{children}
</Stack>
</MenuItemHover>
)
}

This file was deleted.