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