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')