From 2265d6f8d00ef902ca7d8621aecf5424a5ccd685 Mon Sep 17 00:00:00 2001
From: Vyacheslav Tumanov <me@slavatumanov.me>
Date: Tue, 13 Feb 2024 20:11:38 +0500
Subject: [PATCH] Add dropdown with creating project button for "New issue"
 button in tracker (#4612)

Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
---
 .../src/components/ButtonWithDropdown.svelte  | 98 +++++++++++++++++++
 packages/ui/src/index.ts                      |  1 +
 .../src/components/NewIssueHeader.svelte      | 43 ++++++--
 .../sanity/tests/model/tracker/issues-page.ts |  2 +-
 4 files changed, 134 insertions(+), 10 deletions(-)
 create mode 100644 packages/ui/src/components/ButtonWithDropdown.svelte

diff --git a/packages/ui/src/components/ButtonWithDropdown.svelte b/packages/ui/src/components/ButtonWithDropdown.svelte
new file mode 100644
index 0000000000..e6406befe9
--- /dev/null
+++ b/packages/ui/src/components/ButtonWithDropdown.svelte
@@ -0,0 +1,98 @@
+<!--
+// Copyright © 2023 Hardcore Engineering Inc.
+//
+// Licensed under the Eclipse Public License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License. You may
+// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+-->
+<script lang="ts">
+  import { Asset, IntlString } from '@hcengineering/platform'
+  import {
+    AnySvelteComponent,
+    Button,
+    ButtonKind,
+    Icon,
+    Label,
+    SelectPopup,
+    SelectPopupValueType,
+    eventToHTMLElement,
+    showPopup,
+    LabelAndProps
+  } from '../index'
+  import { createEventDispatcher } from 'svelte'
+
+  export let dropdownItems: SelectPopupValueType[]
+  export let label: IntlString | undefined = undefined
+  export let kind: ButtonKind = 'primary'
+  export let justify: 'left' | 'center' = 'center'
+  export let icon: Asset | AnySvelteComponent | undefined = undefined
+  export let dropdownIcon: Asset | AnySvelteComponent | undefined = undefined
+  export let showTooltipMain: LabelAndProps | undefined = undefined
+  export let mainButtonId: string | undefined = undefined
+
+  const dispatch = createEventDispatcher()
+
+  function openDropdown (ev: MouseEvent): void {
+    showPopup(SelectPopup, { value: dropdownItems }, eventToHTMLElement(ev), (res) => {
+      dispatch('dropdown-selected', res)
+    })
+  }
+</script>
+
+<div class="w-full flex-row-center">
+  <div class="flex-grow">
+    <Button
+      width="100%"
+      {icon}
+      {kind}
+      shape="rectangle-right"
+      {justify}
+      borderStyle="none"
+      on:click
+      showTooltip={showTooltipMain}
+      id={mainButtonId}
+    >
+      <div class="flex w-full" slot="content">
+        <div class="flex-row-center w-full flex-between">
+          {#if label}
+            <Label {label} />
+            <slot name="content" />
+            <div class="{kind} vertical-divider max-h-5 h-5" />
+          {/if}
+        </div>
+      </div>
+    </Button>
+  </div>
+  <Button width="1.75rem" {kind} shape="rectangle-left" justify="center" borderStyle="none" on:click={openDropdown}>
+    <div slot="icon">
+      {#if dropdownIcon}
+        <Icon icon={dropdownIcon} size="small" />
+      {/if}
+    </div>
+  </Button>
+</div>
+
+<style lang="scss">
+  .vertical-divider {
+    background-color: var(--theme-content-color);
+    min-width: 1px;
+    opacity: 0.25;
+    margin-right: -0.75rem;
+
+    &.primary,
+    &.secondary,
+    &.positive,
+    &.negative,
+    &.dangerous,
+    &.contrast {
+      background-color: var(--primary-button-content-color);
+    }
+  }
+</style>
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 0f75cdc7cf..c8283f7d39 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -52,6 +52,7 @@ export { getCurrentLocation, locationToUrl, navigate, location, setLocationStora
 export { default as EditBox } from './components/EditBox.svelte'
 export { default as Label } from './components/Label.svelte'
 export { default as Button } from './components/Button.svelte'
+export { default as ButtonWithDropdown } from './components/ButtonWithDropdown.svelte'
 export { default as ButtonGroup } from './components/ButtonGroup.svelte'
 export { default as Status } from './components/Status.svelte'
 export { default as Component } from './components/Component.svelte'
diff --git a/plugins/tracker-resources/src/components/NewIssueHeader.svelte b/plugins/tracker-resources/src/components/NewIssueHeader.svelte
index b1315aaa17..52070995ca 100644
--- a/plugins/tracker-resources/src/components/NewIssueHeader.svelte
+++ b/plugins/tracker-resources/src/components/NewIssueHeader.svelte
@@ -15,7 +15,7 @@
 <script lang="ts">
   import { Ref, Space } from '@hcengineering/core'
   import { MultipleDraftController, getClient } from '@hcengineering/presentation'
-  import { Button, IconAdd, showPopup } from '@hcengineering/ui'
+  import { ButtonWithDropdown, IconAdd, IconDropdown, SelectPopupValueType, showPopup } from '@hcengineering/ui'
   import view from '@hcengineering/view'
   import { onDestroy } from 'svelte'
   import tracker from '../plugin'
@@ -41,25 +41,49 @@
   }
 
   $: label = draftExists || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue
-
+  $: dropdownItems = [
+    {
+      id: tracker.string.CreateProject,
+      label: tracker.string.CreateProject
+    },
+    {
+      id: tracker.string.NewIssue,
+      label
+    }
+  ]
   const client = getClient()
 
   let keys: string[] | undefined = undefined
+  async function dropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
+    if (res == null) return
+
+    if (res === tracker.string.CreateProject) {
+      closed = false
+      showPopup(tracker.component.CreateProject, {}, 'top', () => {
+        closed = true
+      })
+    } else {
+      await newIssue()
+    }
+  }
 
   client.findOne(view.class.Action, { _id: tracker.action.NewIssue }).then((p) => (keys = p?.keyBinding))
 </script>
 
 <div class="antiNav-subheader">
-  <Button
+  <ButtonWithDropdown
     icon={IconAdd}
-    {label}
     justify={'left'}
     kind={'primary'}
-    width={'100%'}
-    gap={'large'}
+    {label}
     on:click={newIssue}
-    id={'new-issue'}
-    showTooltip={{
+    {dropdownItems}
+    dropdownIcon={IconDropdown}
+    on:dropdown-selected={(ev) => {
+      dropdownItemSelected(ev.detail)
+    }}
+    mainButtonId={'new-issue'}
+    showTooltipMain={{
       direction: 'bottom',
       label,
       keys
@@ -70,12 +94,13 @@
         <div class="draft-circle" />
       {/if}
     </div>
-  </Button>
+  </ButtonWithDropdown>
 </div>
 
 <style lang="scss">
   .draft-circle-container {
     margin-left: auto;
+    padding-right: 12px;
   }
 
   .draft-circle {
diff --git a/tests/sanity/tests/model/tracker/issues-page.ts b/tests/sanity/tests/model/tracker/issues-page.ts
index 5c865600bb..e2cfe3cce3 100644
--- a/tests/sanity/tests/model/tracker/issues-page.ts
+++ b/tests/sanity/tests/model/tracker/issues-page.ts
@@ -39,7 +39,7 @@ export class IssuesPage extends CommonTrackerPage {
     this.modelSelectorAll = page.locator('div[data-id="tab-all"]')
     this.modelSelectorActive = page.locator('div[data-id="tab-active"]')
     this.modelSelectorBacklog = page.locator('div[data-id="tab-backlog"]')
-    this.buttonCreateNewIssue = page.locator('button > span', { hasText: 'New issue' })
+    this.buttonCreateNewIssue = page.locator('button > div', { hasText: 'New issue' })
     this.inputPopupCreateNewIssueTitle = page.locator('form[id="tracker:string:NewIssue"] input[type="text"]')
     this.inputPopupCreateNewIssueDescription = page.locator('form[id="tracker:string:NewIssue"] div.tiptap')
     this.buttonPopupCreateNewIssueStatus = page.locator('form[id="tracker:string:NewIssue"] div#status-editor button')