UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: быстрый выбор проектов на home
This commit is contained in:
parent
d9a63f5f74
commit
d28f83fe5e
|
|
@ -43,11 +43,16 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
|
|||
} = props;
|
||||
|
||||
const activeProject = projects.find((project: THomeProjectData) => project.id === selectedProjectId);
|
||||
const orderedProjects = activeProject
|
||||
const selectedProject = activeProject ?? projects[0];
|
||||
const selectedProjectIndex = Math.max(
|
||||
projects.findIndex((project: THomeProjectData) => project.id === selectedProject?.id),
|
||||
0
|
||||
);
|
||||
const stackProjects = activeProject
|
||||
? [activeProject, ...projects.filter((project: THomeProjectData) => project.id !== activeProject.id)]
|
||||
: projects;
|
||||
|
||||
const visibleProjects = orderedProjects.slice(0, STACK_VISIBLE_LIMIT);
|
||||
const visibleProjects = stackProjects.slice(0, STACK_VISIBLE_LIMIT);
|
||||
const activityCountByProject = (recents ?? []).reduce<Record<string, number>>((acc, activity) => {
|
||||
const projectId = getActivityProjectId(activity);
|
||||
if (!projectId) return acc;
|
||||
|
|
@ -56,8 +61,6 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const selectedProject =
|
||||
orderedProjects.find((project: THomeProjectData) => project.id === selectedProjectId) ?? orderedProjects[0];
|
||||
const stackHeight =
|
||||
visibleProjects.length > 0 ? ACTIVE_CARD_HEIGHT + (visibleProjects.length - 1) * STACK_OFFSET : 228;
|
||||
const isHorizontal = orientation === "horizontal";
|
||||
|
|
@ -191,19 +194,7 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{isHorizontal ? (
|
||||
<div className="nodedc-home-project-deck-scroller">
|
||||
<div className="nodedc-home-project-deck-row flex items-start px-1 py-2">
|
||||
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, true))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative" style={{ height: `${stackHeight}px` }}>
|
||||
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, false))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="nodedc-home-project-quick-section mt-4 rounded-[24px] bg-black/10 p-4 xl:mt-auto">
|
||||
<div className="nodedc-home-project-quick-section mb-4 rounded-[24px] bg-black/10 p-4">
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-13 font-semibold text-primary">Быстрый выбор</div>
|
||||
|
|
@ -216,9 +207,11 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
|
|||
</div>
|
||||
|
||||
<div className="nodedc-home-project-quick-list">
|
||||
{orderedProjects.map((project: THomeProjectData) => {
|
||||
{projects.map((project: THomeProjectData, index: number) => {
|
||||
const analytics = analyticsMap[project.id];
|
||||
const isActive = project.id === selectedProject?.id;
|
||||
const completionRate = getCompletionRate(analytics);
|
||||
const distanceFromActive = Math.min(Math.abs(index - selectedProjectIndex), 2);
|
||||
|
||||
return (
|
||||
<button
|
||||
|
|
@ -226,6 +219,7 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
|
|||
type="button"
|
||||
className="nodedc-home-project-quick-button"
|
||||
data-active={isActive}
|
||||
data-distance={distanceFromActive}
|
||||
aria-label={
|
||||
isActive ? `Открыть рабочую область проекта ${project.name}` : `Выбрать проект ${project.name}`
|
||||
}
|
||||
|
|
@ -240,45 +234,54 @@ export function HomeProjectStack(props: HomeProjectStackProps) {
|
|||
>
|
||||
<span className="nodedc-home-project-quick-main">
|
||||
<span className="nodedc-home-project-quick-logo">
|
||||
<Logo logo={project.logo_props} size={14} />
|
||||
<FolderOpenDot className="size-4" />
|
||||
</span>
|
||||
<span className="truncate">{project.identifier}</span>
|
||||
</span>
|
||||
<span className="nodedc-home-project-quick-metric">
|
||||
<span className="nodedc-home-project-quick-dot" aria-hidden="true" />
|
||||
<span>{getCompletionRate(analytics)}%</span>
|
||||
<span className="nodedc-home-project-quick-name truncate">{project.name}</span>
|
||||
</span>
|
||||
<span className="nodedc-home-project-quick-rate">{completionRate}%</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedProject && (
|
||||
<div className="nodedc-home-project-focus-grid mt-4 grid grid-cols-2 gap-3 md:grid-cols-3">
|
||||
<div className="nodedc-home-project-focus-item px-3 py-2">
|
||||
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Фокус</div>
|
||||
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
|
||||
{selectedProject.identifier}
|
||||
</div>
|
||||
</div>
|
||||
<div className="nodedc-home-project-focus-item px-3 py-2">
|
||||
<div className="flex items-center gap-1 text-[11px] tracking-[0.18em] text-placeholder uppercase">
|
||||
<UsersRound className="size-3.5" />
|
||||
<span>Команда</span>
|
||||
</div>
|
||||
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
|
||||
{analyticsMap[selectedProject.id]?.total_members ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="nodedc-home-project-focus-item px-3 py-2">
|
||||
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Контур</div>
|
||||
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
|
||||
{activityCountByProject[selectedProject.id] ?? 0} касаний
|
||||
</div>
|
||||
{isHorizontal ? (
|
||||
<div className="nodedc-home-project-deck-scroller">
|
||||
<div className="nodedc-home-project-deck-row flex items-start px-1 py-2">
|
||||
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, true))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative" style={{ height: `${stackHeight}px` }}>
|
||||
{visibleProjects.map((project: THomeProjectData, index: number) => renderProjectCard(project, index, false))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProject && (
|
||||
<div className="nodedc-home-project-focus-grid mt-4 grid grid-cols-2 gap-3 md:grid-cols-3">
|
||||
<div className="nodedc-home-project-focus-item px-3 py-2">
|
||||
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Фокус</div>
|
||||
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
|
||||
{selectedProject.identifier}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="nodedc-home-project-focus-item px-3 py-2">
|
||||
<div className="flex items-center gap-1 text-[11px] tracking-[0.18em] text-placeholder uppercase">
|
||||
<UsersRound className="size-3.5" />
|
||||
<span>Команда</span>
|
||||
</div>
|
||||
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
|
||||
{analyticsMap[selectedProject.id]?.total_members ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="nodedc-home-project-focus-item px-3 py-2">
|
||||
<div className="text-[11px] tracking-[0.18em] text-placeholder uppercase">Контур</div>
|
||||
<div className="nodedc-home-project-focus-value mt-1 text-13 font-semibold text-primary">
|
||||
{activityCountByProject[selectedProject.id] ?? 0} касаний
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3168,89 +3168,112 @@
|
|||
|
||||
.nodedc-home-project-quick-list {
|
||||
display: flex;
|
||||
width: calc(100% + 2rem);
|
||||
margin-inline: -1rem;
|
||||
width: calc(100% + 1rem);
|
||||
margin-inline: -0.5rem;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 1.25rem;
|
||||
background: rgba(8, 8, 10, 0.62);
|
||||
gap: 0.22rem;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 3.22rem;
|
||||
min-height: 3.18rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.8rem;
|
||||
gap: 0.86rem;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
border-radius: 0 !important;
|
||||
background: rgba(255, 255, 255, 0.028) !important;
|
||||
border-radius: 999px !important;
|
||||
background: rgba(255, 255, 255, 0.04) !important;
|
||||
padding: 0.7rem 0.9rem;
|
||||
padding-left: 3.72rem;
|
||||
color: var(--text-color-primary);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 520;
|
||||
text-align: left;
|
||||
opacity: 0.42;
|
||||
transition:
|
||||
background 160ms ease,
|
||||
color 160ms ease,
|
||||
opacity 160ms ease,
|
||||
transform 160ms ease,
|
||||
box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button + .nodedc-home-project-quick-button {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.045);
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button:first-child {
|
||||
border-top-left-radius: 1.25rem !important;
|
||||
border-top-right-radius: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button:last-child {
|
||||
border-bottom-right-radius: 1.25rem !important;
|
||||
border-bottom-left-radius: 1.25rem !important;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button:hover {
|
||||
background: rgba(255, 255, 255, 0.08) !important;
|
||||
background: rgba(255, 255, 255, 0.085) !important;
|
||||
color: var(--text-color-primary);
|
||||
opacity: 0.92;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button[data-active="true"] {
|
||||
background: rgba(255, 255, 255, 0.13) !important;
|
||||
background: rgba(255, 255, 255, 0.115) !important;
|
||||
color: var(--text-color-primary);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09);
|
||||
opacity: 0.82;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.048),
|
||||
0 10px 22px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button[data-distance="1"] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button[data-distance="2"] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-main {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 0.62rem;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-logo {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0.12rem;
|
||||
transform: translateY(-50%);
|
||||
display: grid;
|
||||
width: 1.7rem;
|
||||
min-width: 1.7rem;
|
||||
height: 1.7rem;
|
||||
width: 2.92rem;
|
||||
min-width: 2.92rem;
|
||||
height: 2.92rem;
|
||||
place-items: center;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
color: rgba(255, 255, 255, 0.74);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05),
|
||||
0 8px 20px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button[data-active="true"] .nodedc-home-project-quick-logo {
|
||||
background: rgba(var(--nodedc-card-active-rgb), 0.92);
|
||||
color: #0a0a0b;
|
||||
background: rgb(var(--nodedc-card-active-rgb));
|
||||
color: rgb(var(--nodedc-on-card-active-rgb));
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-name {
|
||||
min-width: 0;
|
||||
color: var(--text-color-primary);
|
||||
font-size: 0.91rem;
|
||||
font-weight: 570;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-rate {
|
||||
min-width: max-content;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 650;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-button[data-active="true"] .nodedc-home-project-quick-rate {
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.nodedc-home-project-quick-metric {
|
||||
display: inline-flex;
|
||||
display: none;
|
||||
min-width: max-content;
|
||||
align-items: center;
|
||||
gap: 0.38rem;
|
||||
|
|
@ -3260,7 +3283,7 @@
|
|||
}
|
||||
|
||||
.nodedc-home-project-quick-dot {
|
||||
display: block;
|
||||
display: none;
|
||||
width: 0.48rem;
|
||||
height: 0.48rem;
|
||||
border-radius: 999px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue