UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: CTA создания проекта и полировка project-menu и cover-header

This commit is contained in:
DCCONSTRUCTIONS 2026-04-22 22:37:26 +03:00
parent b7b0388dc1
commit 49c32fccf1
3 changed files with 115 additions and 88 deletions

View File

@ -96,6 +96,7 @@ const ProjectsToolbarMenu = observer(function ProjectsToolbarMenu() {
const pathname = usePathname(); const pathname = usePathname();
const { workspaceSlug } = useParams(); const { workspaceSlug } = useParams();
const { joinedProjectIds } = useProject(); const { joinedProjectIds } = useProject();
const { toggleCreateProjectModal } = useCommandPalette();
const handleCopyText = (projectId: string) => const handleCopyText = (projectId: string) =>
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/issues`).then(() => { copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/issues`).then(() => {
@ -139,6 +140,18 @@ const ProjectsToolbarMenu = observer(function ProjectsToolbarMenu() {
/> />
))} ))}
</div> </div>
<div className="mt-2 border-t border-white/8 px-1 pt-2">
<Menu.Item>
<button
type="button"
className="nodedc-toolbar-primary nodedc-toolbar-primary-wide flex w-full items-center justify-center gap-2 text-13 font-medium"
onClick={() => toggleCreateProjectModal(true)}
>
<PlusIcon className="size-4" />
<span>{t("create_project")}</span>
</button>
</Menu.Item>
</div>
</div> </div>
</Menu.Items> </Menu.Items>
</Menu> </Menu>

View File

@ -197,52 +197,54 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" /> <div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
<CoverImage src={coverImage} alt="Project cover image" className="h-44 w-full rounded-md" /> <CoverImage src={coverImage} alt="Project cover image" className="h-44 w-full rounded-md" />
<div className="absolute bottom-4 z-5 flex w-full items-end justify-between gap-3 px-4"> <div className="absolute bottom-4 z-5 flex w-full items-end justify-between gap-3 px-4">
<div className="flex flex-grow gap-3 truncate"> <div className="flex min-w-0 flex-grow gap-3 truncate">
<Controller <div className="flex min-w-0 flex-1 items-center gap-3 rounded-[1.2rem] bg-white/10 px-2.5 py-2.5 backdrop-blur-2xl">
control={control} <Controller
name="logo_props" control={control}
render={({ field: { value, onChange } }) => ( name="logo_props"
<EmojiPicker render={({ field: { value, onChange } }) => (
iconType="material" <EmojiPicker
closeOnSelect={false} iconType="material"
isOpen={isOpen} closeOnSelect={false}
handleToggle={(val: boolean) => setIsOpen(val)} isOpen={isOpen}
className="flex items-center justify-center" handleToggle={(val: boolean) => setIsOpen(val)}
buttonClassName="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-white/10" className="flex items-center justify-center"
label={<Logo logo={value} size={28} />} buttonClassName="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-white/[0.06]"
// TODO: fix types label={<Logo logo={value} size={28} />}
onChange={(val: any) => { // TODO: fix types
let logoValue = {}; onChange={(val: any) => {
let logoValue = {};
if (val?.type === "emoji") if (val?.type === "emoji")
logoValue = { logoValue = {
value: val.value, value: val.value,
}; };
else if (val?.type === "icon") logoValue = val.value; else if (val?.type === "icon") logoValue = val.value;
onChange({ onChange({
in_use: val?.type, in_use: val?.type,
[val?.type]: logoValue, [val?.type]: logoValue,
}); });
setIsOpen(false); setIsOpen(false);
}} }}
defaultIconColor={value?.in_use && value.in_use === "icon" ? value?.icon?.color : undefined} defaultIconColor={value?.in_use && value.in_use === "icon" ? value?.icon?.color : undefined}
defaultOpen={ defaultOpen={
value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON value.in_use && value.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON
} }
disabled={!isAdmin} disabled={!isAdmin}
/> />
)} )}
/> />
<div className="flex flex-col gap-1 truncate text-on-color"> <div className="flex flex-col gap-1 truncate text-on-color">
<span className="truncate text-16 font-semibold">{watch("name")}</span> <span className="truncate text-16 font-semibold">{watch("name")}</span>
<span className="flex items-center gap-2 text-13"> <span className="flex items-center gap-2 text-13">
<span>{watch("identifier")} .</span> <span>{watch("identifier")} .</span>
<span className="flex items-center gap-1.5"> <span className="flex items-center gap-1.5">
{project.network === 0 && <LockIcon className="h-2.5 w-2.5 text-on-color" />} {project.network === 0 && <LockIcon className="h-2.5 w-2.5 text-on-color" />}
{currentNetwork && t(currentNetwork?.i18n_label)} {currentNetwork && t(currentNetwork?.i18n_label)}
</span>
</span> </span>
</span> </div>
</div> </div>
</div> </div>
<div className="flex flex-shrink-0 justify-center"> <div className="flex flex-shrink-0 justify-center">
@ -397,7 +399,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
)} )}
</div> </div>
} }
menuButtonWrapperClassName="nodedc-settings-select !h-12 font-medium" menuButtonWrapperClassName="nodedc-settings-select !h-12 min-w-[12.75rem] px-4 font-medium"
disabled={!isAdmin} disabled={!isAdmin}
/> />
); );

View File

@ -392,35 +392,66 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
</button> </button>
</Tooltip> </Tooltip>
)} )}
<> {isAccordionMode ? (
<ControlLink href={defaultTabUrl} className="flex flex-grow truncate" onClick={handleItemClick}> <div className="flex w-full items-center gap-1">
{isAccordionMode ? ( <button
<Disclosure.Button type="button"
as="button" className="flex min-w-0 flex-1 items-center gap-3 text-left select-none"
type="button" onClick={() => setIsProjectListOpen(!isProjectListOpen)}
className={cn("flex w-full flex-grow items-center gap-1.5 text-left select-none", {})} aria-label={
aria-label={ isProjectListOpen
isProjectListOpen ? t("aria_labels.projects_sidebar.close_project_menu")
? t("aria_labels.projects_sidebar.close_project_menu") : t("aria_labels.projects_sidebar.open_project_menu")
: t("aria_labels.projects_sidebar.open_project_menu") }
>
<div className="grid size-8 flex-shrink-0 place-items-center rounded-full border border-white/8 bg-white/[0.04]">
<WorkItemsIcon className="size-4 text-tertiary" />
</div>
<p className="truncate text-13 font-medium text-secondary">{project.name}</p>
<span className="ml-auto grid size-7 flex-shrink-0 place-items-center rounded-full text-placeholder transition-colors group-hover/project-item:bg-layer-transparent-hover">
<ChevronRightIcon
className={cn("size-4 transition-transform", {
"rotate-90": isProjectListOpen,
})}
/>
</span>
</button>
{!renderInToolbarMenu && (
<ActionDropdown
button={
<span className="grid place-items-center">
<MoreHorizontal className="h-3.5 w-3.5 text-placeholder" />
</span>
} }
> className={cn(
<div className="grid size-8 flex-shrink-0 place-items-center rounded-full border border-white/8 bg-white/[0.04]"> "pointer-events-none flex-shrink-0 opacity-0 group-hover/project-item:pointer-events-auto group-hover/project-item:opacity-100",
<WorkItemsIcon className="size-4 text-tertiary" /> {
</div> "pointer-events-auto opacity-100": isMenuActive,
<p className="truncate text-13 font-medium text-secondary">{project.name}</p> }
</Disclosure.Button> )}
) : ( buttonClassName={cn(
"grid size-7 place-items-center rounded-full text-placeholder transition-colors hover:bg-layer-transparent-hover",
{
"bg-layer-transparent-hover": isMenuActive,
}
)}
placement="bottom-start"
onOpenChange={setIsMenuActive}
items={projectActionMenuItems}
/>
)}
</div>
) : (
<>
<ControlLink href={defaultTabUrl} className="flex flex-grow truncate" onClick={handleItemClick}>
<div className="flex w-full flex-grow items-center gap-3 text-left select-none"> <div className="flex w-full flex-grow items-center gap-3 text-left select-none">
<div className="grid size-8 flex-shrink-0 place-items-center rounded-full border border-white/8 bg-white/[0.04]"> <div className="grid size-8 flex-shrink-0 place-items-center rounded-full border border-white/8 bg-white/[0.04]">
<WorkItemsIcon className="size-4 text-tertiary" /> <WorkItemsIcon className="size-4 text-tertiary" />
</div> </div>
<p className="truncate text-13 font-medium text-secondary">{project.name}</p> <p className="truncate text-13 font-medium text-secondary">{project.name}</p>
</div> </div>
)} </ControlLink>
</ControlLink> <div className="flex items-center gap-1">
<div className="flex items-center gap-1">
{!renderInToolbarMenu && (
<ActionDropdown <ActionDropdown
button={ button={
<span className="grid place-items-center"> <span className="grid place-items-center">
@ -444,28 +475,9 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
onOpenChange={setIsMenuActive} onOpenChange={setIsMenuActive}
items={projectActionMenuItems} items={projectActionMenuItems}
/> />
)} </div>
{isAccordionMode && ( </>
<IconButton )}
variant="ghost"
size="sm"
icon={ChevronRightIcon}
onClick={() => setIsProjectListOpen(!isProjectListOpen)}
className={cn("hidden text-placeholder group-hover/project-item:inline-flex", {
"inline-flex": isMenuActive,
})}
iconClassName={cn("transition-transform", {
"rotate-90": isProjectListOpen,
})}
aria-label={t(
isProjectListOpen
? "aria_labels.projects_sidebar.close_project_menu"
: "aria_labels.projects_sidebar.open_project_menu"
)}
/>
)}
</div>
</>
</div> </div>
{isAccordionMode && ( {isAccordionMode && (
<Transition <Transition