Update TagEditor layout (#1420)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-04-16 06:06:32 +03:00 committed by GitHub
parent 5dee8d9072
commit f0cca26e9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 391 additions and 241 deletions

View File

@ -15,6 +15,6 @@
"Search": "Search...",
"Unassigned": "Unassigned",
"CreateMore": "Create more",
"NumberMembers": "{lenght, plural, =0 {no members} =1 {1 member} other {# members}}"
"NumberMembers": "{count, plural, =0 {no members} =1 {1 member} other {# members}}"
}
}

View File

@ -15,6 +15,6 @@
"Search": "Поиск...",
"Unassigned": "Не назначен",
"CreateMore": "Создать еще",
"NumberMembers": "{lenght, plural, =0 {нет участников} =1 {1 участик} other {# участника}}"
"NumberMembers": "{count, plural, =0 {нет участников} =1 {1 участик} other {# участника}}"
}
}

View File

@ -79,7 +79,7 @@
{#if persons.length > 0}
<div class="flex-row-center flex-nowrap">
<CombineAvatars {_class} bind:items size={'inline'} />
{#await translate(presentation.string.NumberMembers, { lenght: persons.length }) then text}
{#await translate(presentation.string.NumberMembers, { count: persons.length }) then text}
<span class="ml-1-5">{text}</span>
{/await}
</div>

View File

@ -296,6 +296,7 @@ p:last-child { margin-block-end: 0; }
.ml-12 { margin-left: 3rem; }
.ml-22 { margin-left: 5.5rem; }
.mr-1 { margin-right: .25rem; }
.mr-1-5 { margin-right: .375rem; }
.mr-2 { margin-right: .5rem; }
.mr-3 { margin-right: .75rem; }
.mr-4 { margin-right: 1rem; }
@ -461,6 +462,7 @@ a.no-line {
color: var(--theme-content-trans-color);
user-select: none;
}
.text-xs { font-size: .625rem; }
.text-sm { font-size: .75rem; }
.text-md { font-size: .8125rem; }
.text-lg { font-size: 1.125rem; }

View File

@ -18,18 +18,20 @@
overflow: hidden;
display: flex;
flex-direction: column;
width: min-content;
width: fit-content;
min-width: 10rem;
max-width: 45rem;
max-height: 20rem;
max-width: 15rem;
max-height: 22rem;
background: var(--popup-bg-color);
border-radius: .5rem;
box-shadow: var(--popup-shadow);
will-change: transform;
&.maxHeight { height: 22rem; }
.header {
border-bottom: 1px solid var(--popup-divider);
&.no-border { border-bottom-color: transparent; }
input {
margin: 0;
padding: .625rem .75rem;
@ -59,25 +61,33 @@
.menu-item {
flex-shrink: 0;
justify-content: start;
padding: 0 .75rem;
height: 2rem;
padding: .25rem .75rem;
min-height: 2rem;
text-align: left;
color: var(--caption-color);
cursor: pointer;
&.high { height: 3rem; }
.icon {
.icon, .color, .tag {
flex-shrink: 0;
margin-right: .75rem;
}
.icon {
width: 1rem;
height: 1rem;
}
.color {
margin-right: .75rem;
width: .875rem;
height: .875rem;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: .25rem;
}
.tag {
width: .5rem;
height: .5rem;
border-radius: 50%;
}
.label {
flex-grow: 1;
min-width: 0;
@ -89,11 +99,50 @@
display: flex;
align-items: center;
margin-right: .75rem;
height: 2rem;
}
.check-right { margin: 0 0 0 2rem; }
&:hover { background-color: var(--popup-bg-hover); }
}
.sticky-wrapper {
display: flex;
flex-direction: column;
&:not(:first-child) { margin-top: 1px; }
}
.menu-group {
overflow: hidden;
display: flex;
flex-direction: column;
height: 0;
transition: height .5s ease;
&__header {
position: sticky;
top: 0;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: .125rem .25rem;
min-height: 1.5rem;
font-weight: 500;
font-size: .75rem;
text-align: left;
color: var(--accent-color);
background-color: var(--button-bg-color);
cursor: pointer;
.icon {
width: .25rem;
transform-origin: 40% 50%;
transform: rotate(0deg);
transition: transform .15s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
&.show .icon { transform: rotate(90deg); }
&:hover { color: var(--caption-color); }
&.show + .menu-group { height: auto; }
}
}
}
.antiPopup {

View File

@ -17,7 +17,7 @@
import { AnySvelteComponent } from '../types'
export let icon: Asset | AnySvelteComponent
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
export let size: 'inline' | 'x-small' | 'small' | 'medium' | 'large' | 'full'
export let fill = 'currentColor'
export let filled: boolean = false

View File

@ -31,6 +31,7 @@
let modalHTML: HTMLElement
let componentInstance: any
let show: boolean = false
let height: number
function _update (result: any): void {
if (onUpdate !== undefined) onUpdate(result)
@ -61,11 +62,12 @@
}
}
afterUpdate(() => fitPopup())
$: if (height) fitPopup()
</script>
<svelte:window on:resize={fitPopup} on:keydown={handleKeydown} />
<div class="popup" bind:this={modalHTML} style={`z-index: ${zIndex + 1};`}>
<div class="popup" bind:this={modalHTML} bind:clientHeight={height} style={`z-index: ${zIndex + 1};`}>
<svelte:component bind:this={componentInstance} this={is} {...props} on:update={(ev) => _update(ev.detail)} on:close={(ev) => _close(ev.detail)} />
</div>
<div class="modal-overlay" class:antiOverlay={show} style={`z-index: ${zIndex};`} on:click={() => escapeClose()} />

View File

@ -86,7 +86,8 @@
"Participants": "Participants",
"NoParticipants": "No participants added",
"PersonsLabel": "{name}",
"AddDescription": "Add description"
"AddDescription": "Add description",
"NumberSkills": "{count, plural, =0 {no skills} =1 {1 skill} other {# skills}}"
},
"status": {
"CandidateRequired": "Please select candidate",

View File

@ -87,7 +87,8 @@
"Participants": "Участники",
"NoParticipants": "Участники не добавлены",
"PersonsLabel": "{name}",
"AddDescription": "Add description"
"AddDescription": "Add description",
"NumberSkills": "{count, plural, =0 {нет навыков} =1 {1 навык} =2 {2 навыка} other {# навыков}}"
},
"status": {
"CandidateRequired": "Пожалуйста выберите кандидата",

View File

@ -231,22 +231,22 @@
</Card>
<style lang="scss">
.card {
align-self: stretch;
width: calc(50% - 3rem);
min-height: 16rem;
// .card {
// align-self: stretch;
// width: calc(50% - 3rem);
// min-height: 16rem;
&.empty {
display: flex;
justify-content: center;
align-items: center;
font-size: .75rem;
color: var(--dark-color);
border: 1px solid var(--divider-color);
border-radius: .25rem;
}
}
.arrows { width: 4rem; }
// &.empty {
// display: flex;
// justify-content: center;
// align-items: center;
// font-size: .75rem;
// color: var(--dark-color);
// border: 1px solid var(--divider-color);
// border-radius: .25rem;
// }
// }
// .arrows { width: 4rem; }
.color {
margin-right: .375rem;
width: .875rem;

View File

@ -415,29 +415,26 @@
{#if channels.length > 0}
<div class="ml-22"><ChannelsView value={channels} size={'small'} on:click /></div>
{/if}
<div class="flex-col">
<span class="text-sm fs-bold content-accent-color"><Label label={recruit.string.SkillsLabel} /></span>
<div class="flex-grow">
<Component
is={tags.component.TagsEditor}
props={{ items: skills, key, targetClass: recruit.mixin.Candidate, showTitle: false, elements }}
on:open={(evt) => {
addTagRef(evt.detail)
}}
on:delete={(evt) => {
skills = skills.filter((it) => it._id !== evt.detail)
}}
/>
</div>
</div>
<svelte:fragment slot="pool">
<div class="flex-between w-full">
<span class="ml-2 content-color overflow-label"><Label label={recruit.string.WorkLocationPreferences} /></span>
<div class="buttons-group small-gap">
<YesNo label={recruit.string.Onsite} bind:value={object.onsite} />
<YesNo label={recruit.string.Remote} bind:value={object.remote} />
</div>
</div>
<YesNo label={recruit.string.Onsite} tooltip={recruit.string.WorkLocationPreferences} bind:value={object.onsite} />
<YesNo label={recruit.string.Remote} tooltip={recruit.string.WorkLocationPreferences} bind:value={object.remote} />
<Component
is={tags.component.TagsEditor}
props={{
items: skills,
key,
targetClass: recruit.mixin.Candidate,
showTitle: false,
elements,
countLabel: recruit.string.NumberSkills
}}
on:open={(evt) => {
addTagRef(evt.detail)
}}
on:delete={(evt) => {
skills = skills.filter((it) => it._id !== evt.detail)
}}
/>
</svelte:fragment>
<svelte:fragment slot="footer">
<Button

View File

@ -15,35 +15,40 @@
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import { Label } from '@anticrm/ui'
import type { TooltipAlignment } from '@anticrm/ui'
import { Label, Tooltip } from '@anticrm/ui'
import recruit from '../plugin'
export let label: IntlString
export let tooltip: IntlString
export let value: boolean | undefined
export let disabled: boolean = false
export let labelDirection: TooltipAlignment | undefined = undefined
</script>
<button class="yesno-container" {disabled} class:yes={value === true} class:no={value === false} on:click={() => {
if (value === true) value = false
else if (value === false) value = undefined
else value = true
}}>
<span class="overflow-label">
<Label {label} />
</span>
<div class="btn-icon ml-1">
<svg class="yesno-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle class="circle" class:yes={value === true} class:no={value === false} cx="8" cy="8" r="6"/>
{#if value === true}
<polygon fill="#fff" points="7.4,10.9 4.9,8.4 5.7,7.6 7.3,9.1 10.2,5.6 11.1,6.4 "/>
{:else if value === false}
<polygon fill="#fff" points="10.7,6 10,5.3 8,7.3 6,5.3 5.3,6 7.3,8 5.3,10 6,10.7 8,8.7 10,10.7 10.7,10 8.7,8 "/>
{:else}
<path fill="#fff" d="M7.3,9.3h1.3V9c0.1-0.5,0.6-0.9,1.1-1.4c0.4-0.4,0.8-0.9,0.8-1.6c0-1.1-0.8-1.8-2.2-1.8c-1.4,0-2.4,0.8-2.5,2.2 h1.4c0.1-0.6,0.4-1,1-1C8.8,5.4,9,5.7,9,6.2c0,0.4-0.3,0.7-0.7,1.1c-0.5,0.5-1,0.9-1,1.7V9.3z M8,11.6c0.5,0,0.9-0.4,0.9-0.9 c0-0.5-0.4-0.9-0.9-0.9c-0.5,0-0.9,0.4-0.9,0.9C7.1,11.2,7.5,11.6,8,11.6z"/>
{/if}
</svg>
</div>
</button>
<Tooltip direction={labelDirection} label={tooltip}>
<button class="yesno-container" {disabled} class:yes={value === true} class:no={value === false} on:click={() => {
if (value === true) value = false
else if (value === false) value = undefined
else value = true
}}>
<span class="overflow-label">
<Label {label} />
</span>
<div class="btn-icon ml-1">
<svg class="yesno-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle class="circle" class:yes={value === true} class:no={value === false} cx="8" cy="8" r="6"/>
{#if value === true}
<polygon fill="#fff" points="7.4,10.9 4.9,8.4 5.7,7.6 7.3,9.1 10.2,5.6 11.1,6.4 "/>
{:else if value === false}
<polygon fill="#fff" points="10.7,6 10,5.3 8,7.3 6,5.3 5.3,6 7.3,8 5.3,10 6,10.7 8,8.7 10,10.7 10.7,10 8.7,8 "/>
{:else}
<path fill="#fff" d="M7.3,9.3h1.3V9c0.1-0.5,0.6-0.9,1.1-1.4c0.4-0.4,0.8-0.9,0.8-1.6c0-1.1-0.8-1.8-2.2-1.8c-1.4,0-2.4,0.8-2.5,2.2 h1.4c0.1-0.6,0.4-1,1-1C8.8,5.4,9,5.7,9,6.2c0,0.4-0.3,0.7-0.7,1.1c-0.5,0.5-1,0.9-1,1.7V9.3z M8,11.6c0.5,0,0.9-0.4,0.9-0.9 c0-0.5-0.4-0.9-0.9-0.9c-0.5,0-0.9,0.4-0.9,0.9C7.1,11.2,7.5,11.6,8,11.6z"/>
{/if}
</svg>
</div>
</button>
</Tooltip>
<style lang="scss">
.yesno-container {

View File

@ -102,7 +102,8 @@ export default mergeIds(recruitId, recruit, {
StartDate: '' as IntlString,
DueDate: '' as IntlString,
CandidateReviews: '' as IntlString,
AddDescription: '' as IntlString
AddDescription: '' as IntlString,
NumberSkills: '' as IntlString
},
space: {
CandidatesPublic: '' as Ref<Space>

View File

@ -16,7 +16,7 @@
import { Class, Doc, Ref, SortingOrder } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import { TagCategory, TagElement } from '@anticrm/tags'
import { getPlatformColorForText, Label } from '@anticrm/ui'
import { getPlatformColorForText, Button } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import tags from '../plugin'
import { getTagStyle } from '../utils'
@ -76,7 +76,7 @@
$: visibleCategories = categories.filter((it) => categoryKeys.includes(it._id))
const selectItem = (ev: Event, item: TagCategory): void => {
const selectItem = (item: TagCategory): void => {
if (category === item._id) {
category = undefined
} else {
@ -87,28 +87,27 @@
</script>
{#if visibleCategories.length > 0}
<div class="flex-between mb-4 header">
<div class="flex-between header">
<div class="flex-row-center buttons">
<div
class="button flex-center"
class:active={category === undefined}
<Button
label={tags.string.AllCategories}
kind={'transparent'}
size={'large'}
on:click={() => {
category = undefined
dispatch('change', { category, elements: [] })
}}
>
<Label label={tags.string.AllCategories} />
</div>
/>
</div>
<div class="flex-row-center caption-color states">
<div class="antiStatesBar mask-none {stepStyle}">
{#each visibleCategories as item, i (item._id)}
{#each visibleCategories as item (item._id)}
<div
class="categoryElement flex-center"
label={item.label}
style={getTagStyle(getPlatformColorForText(item.label), item._id === category)}
on:click={(ev) => {
if (item._id !== category) selectItem(ev, item)
on:click={() => {
if (item._id !== category) selectItem(item)
}}
>
{item.label} ({categoryCounts.get(item._id)?.length ?? ''})
@ -121,20 +120,20 @@
<style lang="scss">
.categoryElement {
padding: 0.5rem 0.75rem;
height: 2.5rem;
padding: .375rem .75rem;
// height: 2.5rem;
white-space: nowrap;
border: 1px solid var(--theme-button-border-enabled);
border-radius: 0.5rem;
border-radius: .25rem;
cursor: pointer;
}
.categoryElement + .categoryElement {
margin-left: 0.75rem;
margin-left: .125rem;
}
.header {
margin-left: 2.5rem;
margin-right: 1.75rem;
padding: 0 1.75rem 1rem 2.5rem;
border-bottom: 1px solid var(--divider-color);
.buttons {
padding: 0.125rem 0;
}

View File

@ -56,16 +56,17 @@
<style lang="scss">
.tag-item {
margin: .25rem;
padding: .5rem;
margin: .125rem;
padding: .125rem .25rem;
border-radius: .5rem;
border-radius: .25rem;
font-weight: 500;
font-size: .625rem;
text-transform: uppercase;
color: var(--theme-caption-color);
color: var(--accent-color);
&:hover { color: var(--caption-color); }
display: flex;
align-items: center;

View File

@ -63,4 +63,5 @@
targetClass={_class}
on:open={(evt) => addRef(evt.detail)}
on:delete={(evt) => removeTag(evt.detail)}
countLabel={key.attr.label}
/>

View File

@ -14,13 +14,13 @@
-->
<script lang="ts">
import type { AttachedDoc, Class, Collection, Doc, Ref } from '@anticrm/core'
import { translate } from '@anticrm/platform'
import { IntlString, translate } from '@anticrm/platform'
import { KeyedAttribute } from '@anticrm/presentation'
import { TagElement, TagReference } from '@anticrm/tags'
import { CircleButton, IconAdd, IconClose, Label, ShowMore, showPopup, Tooltip } from '@anticrm/ui'
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@anticrm/ui'
import { Button, showPopup, Tooltip } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import tags from '../plugin'
import TagItem from './TagItem.svelte'
import TagsPopup from './TagsPopup.svelte'
export let items: TagReference[] = []
@ -28,6 +28,13 @@
export let key: KeyedAttribute
export let showTitle = true
export let elements: Map<Ref<TagElement>, TagElement>
export let countLabel: IntlString
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
const dispatch = createEventDispatcher()
@ -47,17 +54,12 @@
TagsPopup,
{
targetClass,
title: keyLabel,
addRef,
selected: items.map((it) => it.tag)
removeTag,
selected: items.map((it) => it.tag),
keyLabel
},
evt.target as HTMLElement,
async (result?: TagElement) => {
if (result === null || result === undefined) {
return
}
addRef(result)
}
evt.target as HTMLElement
)
}
@ -66,70 +68,22 @@
}
</script>
<div class="flex-row">
{#if showTitle}
<div class="flex-row-center">
<div class="title">
<Label label={key.attr.label} />
</div>
</div>
{/if}
<ShowMore ignore={!showTitle}>
<div class:tags-container={showTitle} class:mt-4={showTitle}>
<div class="flex flex-reverse">
<div id='add-tag' class="ml-4">
<Tooltip label={tags.string.AddTagTooltip} props={{ word: keyLabel }}>
<CircleButton icon={IconAdd} size={'small'} selected on:click={addTag} />
</Tooltip>
<Tooltip label={key.attr.label} direction={labelDirection}>
<Button
icon={tags.icon.Tags}
label={items.length > 0 ? undefined : key.attr.label}
width={width ?? 'min-content'}
{kind} {size} {justify}
on:click={addTag}
>
<svelte:fragment slot="content">
{#if items.length > 0}
<div class="flex-row-center flex-nowrap">
{#await translate(countLabel, { count: items.length }) then text}
{text}
{/await}
</div>
<div class="tag-items" class:tag-items-scroll={!showTitle}>
{#if items.length === 0}
{#if keyLabel}
<div class="flex flex-grow title-center">
<Label label={tags.string.NoItems} params={{ word: keyLabel }} />
</div>
{/if}
{/if}
{#each items as tag}
<TagItem
{tag}
element={elements.get(tag.tag)}
action={IconClose}
on:action={() => {
removeTag(tag._id)
}}
/>
{/each}
</div>
</div>
</div>
</ShowMore>
</div>
<style lang="scss">
.title {
margin-right: 0.75rem;
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
.tags-container {
padding: 1rem;
color: var(--theme-caption-color);
background: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0.75rem;
}
.tag-items {
flex-grow: 1;
display: flex;
flex-wrap: wrap;
}
.tag-items-scroll {
overflow-y: scroll;
max-height: 10rem;
}
.title-center {
align-items: center;
}
</style>
{/if}
</svelte:fragment>
</Button>
</Tooltip>

View File

@ -15,27 +15,38 @@
<script lang="ts">
import { Class, Doc, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { TagElement } from '@anticrm/tags'
import ui, { ActionIcon, Button, EditWithIcon, IconAdd, IconSearch, Label, showPopup } from '@anticrm/ui'
import { translate } from '@anticrm/platform'
import presentation, { createQuery, getClient } from '@anticrm/presentation'
import { TagCategory, TagElement } from '@anticrm/tags'
import { CheckBox, Button, Icon, IconAdd, IconClose, Label, showPopup, getPlatformColor } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import tags from '../plugin'
import CreateTagElement from './CreateTagElement.svelte'
import TagItem from './TagItem.svelte'
import IconView from './icons/View.svelte'
import IconViewHide from './icons/ViewHide.svelte'
export let targetClass: Ref<Class<Doc>>
export let title: string
export let caption: IntlString = ui.string.Suggested
export let placeholder: IntlString = presentation.string.Search
export let addRef: (tag: TagElement) => Promise<void>
export let removeTag: (tag: TagElement) => Promise<void>
export let selected: Ref<TagElement>[] = []
export let keyLabel: string = ''
let search: string = ''
let searchElement: HTMLInputElement
let show: boolean = false
let objects: TagElement[] = []
let available: TagElement[] = []
let categories: TagCategory[] = []
const dispatch = createEventDispatcher()
const query = createQuery()
const client = getClient()
client.findAll(tags.class.TagCategory, { targetClass }).then((res) => { categories = res })
let phTraslate: string = ''
$: if (placeholder) translate(placeholder, {}).then(res => { phTraslate = res })
// TODO: Add $not: {$in: []} query
$: query.query(
tags.class.TagElement,
@ -46,70 +57,141 @@
{ limit: 200 }
)
$: available = objects.filter((it) => !selected.includes(it._id))
let anchor: HTMLElement
async function createTagElement (): Promise<void> {
showPopup(CreateTagElement, { targetClass, keyTitle: title }, anchor)
showPopup(CreateTagElement, { targetClass }, 'top')
}
async function addTag (element: TagElement): Promise<void> {
await addRef(element)
selected = [...selected, element._id]
}
const isSelected = (element: TagElement): boolean => {
if (selected.filter(p => p === element._id).length > 0) return true
return false
}
const checkSelected = (element: TagElement): void => {
if (isSelected(element)) {
selected = selected.filter(p => p !== element._id)
removeTag(element)
} else {
selected.push(element._id)
addTag(element)
}
objects = objects
categories = categories
dispatch('update', selected)
}
const toggleGroup = (ev: MouseEvent): void => {
const el: HTMLElement = ev.currentTarget as HTMLElement
el.classList.toggle('show')
}
const getCount = (cat: TagCategory): string => {
const count = objects.filter(el => el.category === cat._id).filter((it) => selected.includes(it._id)).length
if (count > 0) return count.toString()
return ''
}
</script>
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle">
<div class="ap-title">
<Label label={tags.string.AddTagTooltip} params={{ word: title }} />
</div>
<div class="ap-header">
<EditWithIcon icon={IconSearch} bind:value={search} placeholder={tags.string.SearchCreate} focus>
<svelte:fragment slot="extra">
<div id='new-tag' class="ml-27" bind:this={anchor}>
<ActionIcon
label={tags.string.AddNowTooltip}
labelProps={{ word: title }}
icon={IconAdd}
action={createTagElement}
size={'small'}
/>
<div class="selectPopup maxHeight">
<div class="header no-border">
<div class="flex-between flex-grow pr-2">
<div class="flex-grow">
<input bind:this={searchElement} type="text" bind:value={search} placeholder={phTraslate} style="width: 100%;" on:change/>
</div>
<div class="buttons-group small-gap">
<div class="clear-btn" class:show={search !== ''} on:click={() => {
search = ''
searchElement.focus()
}}>
{#if search !== ''}<div class="icon"><Icon icon={IconClose} size={'inline'} /></div>{/if}
</div>
</svelte:fragment>
</EditWithIcon>
<div class="ap-caption">
<Label label={caption} />
<Button kind={'transparent'} size={'small'} icon={show ? IconView : IconViewHide} on:click={() => show = !show} />
<Button kind={'transparent'} size={'small'} icon={IconAdd} on:click={createTagElement} />
</div>
</div>
</div>
<div class="ap-space" />
<div class="ap-scroll">
<div class="flex flex-wrap" style={'max-height: 15rem;'}>
{#each available as element}
<div
class="hover-trans"
on:click={() => {
addTag(element)
}}
>
<div class="flex-between">
<TagItem
{element}
action={IconAdd}
on:action={() => {
addTag(element)
}}
/>
<div class="scroll">
<div class="box">
{#each categories as cat}
{#if objects.filter(el => el.category === cat._id).length > 0}
<div class="sticky-wrapper">
<button class="menu-group__header" class:show={search !== '' || show} on:click={toggleGroup}>
<div class="flex-row-center">
<span class="mr-1-5">{cat.label}</span>
<div class="icon">
<svg fill="var(--content-color)" viewBox="0 0 6 6" xmlns="http://www.w3.org/2000/svg">
<path d="M0,0L6,3L0,6Z" />
</svg>
</div>
</div>
<div class="flex-row-center text-xs">
<span class="content-color mr-1">({objects.filter(el => el.category === cat._id).length})</span>
<span class="counter">{getCount(cat)}</span>
</div>
</button>
<div class="menu-group">
{#each objects.filter(el => el.category === cat._id) as element}
<button class="menu-item" on:click={() => {
checkSelected(element)
}}>
<div class="check pointer-events-none">
<CheckBox checked={isSelected(element)} primary />
</div>
<div class="tag" style="background-color: {getPlatformColor(element.color)};" />
{element.title}
</button>
{/each}
</div>
</div>
</div>
{/if}
{/each}
{#if objects.length === 0}
<div class="empty">
<Label label={tags.string.NoItems} params={{ word: keyLabel }} />
</div>
{/if}
</div>
</div>
<div class="ap-footer">
<Button
label={tags.string.CancelLabel}
size={'small'}
on:click={() => {
dispatch('close')
}}
/>
</div>
</div>
<style lang="scss">
.clear-btn {
display: flex;
justify-content: center;
align-items: center;
width: .75rem;
height: .75rem;
border-radius: 50%;
.icon {
width: .625rem;
height: .625rem;
}
&.show {
color: var(--content-color);
background-color: var(--button-border-color);
cursor: pointer;
&:hover {
color: var(--accent-color);
background-color: var(--button-border-hover);
}
}
}
.counter {
padding-right: .125rem;
min-width: 1.5rem;
text-align: right;
font-size: .8125rem;
color: var(--caption-color);
}
.empty {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: .75rem;
color: var(--dark-color);
border-top: 1px solid var(--popup-divider);
}
</style>

View File

@ -42,7 +42,7 @@
}
}
function showCreateDialog (ev: Event) {
function showCreateDialog () {
showPopup(CreateTagElement, { targetClass, keyTitle }, 'top')
}
const opt: FindOptions<TagElement> = {
@ -65,7 +65,7 @@
updateResultQuery(search, category)
}}
/>
<Button icon={IconAdd} label={сreateItemLabel} kind={'primary'} on:click={(ev) => showCreateDialog(ev)} />
<Button icon={IconAdd} label={сreateItemLabel} kind={'primary'} on:click={showCreateDialog} />
</div>
<CategoryBar

View File

@ -0,0 +1,25 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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">
export let size: 'x-small' | 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z M12,15c-1.7,0-3-1.3-3-3s1.3-3,3-3s3,1.3,3,3 S13.7,15,12,15z" />
<path d="M20.6,10.6l-0.4,0.3l-0.4,0.3c0.4,0.5,0.5,0.6,0.5,0.7s-0.1,0.3-0.5,0.7c-1.5,1.8-4.4,4.8-7.8,4.8c-3.4,0-6.3-3-7.8-4.8 c-0.4-0.5-0.5-0.6-0.5-0.7c0-0.2,0.1-0.3,0.5-0.7C5.7,9.5,8.6,6.5,12,6.5c3.4,0,6.3,3,7.8,4.8l0.4-0.3L20.6,10.6L20.6,10.6 C19,8.7,15.8,5.5,12,5.5c-3.8,0-7,3.2-8.6,5.1C3,11.1,2.7,11.5,2.7,12s0.3,0.9,0.7,1.4c1.6,1.9,4.8,5.1,8.6,5.1 c3.8,0,7-3.2,8.6-5.1c0.4-0.5,0.7-0.8,0.7-1.4S21,11.1,20.6,10.6z" />
</svg>

View File

@ -0,0 +1,26 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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">
export let size: 'x-small' | 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M8.7,9.8C8.2,10.4,8,11.2,8,12c0,2.2,1.8,4,4,4c0.8,0,1.6-0.2,2.2-0.7l-0.7-0.7C13,14.9,12.5,15,12,15c-1.7,0-3-1.3-3-3 c0-0.5,0.1-1,0.4-1.5L8.7,9.8z" />
<path d="M12,17.5c-1.7,0-3.3-0.7-4.6-1.7c-1.4-1-2.5-2.2-3.2-3c-0.4-0.5-0.5-0.6-0.5-0.7s0-0.2,0.5-0.7c0.7-0.8,1.7-2,3-2.9 L6.5,7.6c-1.3,1-2.4,2.2-3.1,3l-0.1,0.1C3,11.1,2.7,11.5,2.7,12s0.3,0.9,0.6,1.3l0.1,0.1c0.7,0.9,1.9,2.1,3.4,3.2 c1.5,1,3.3,1.9,5.2,1.9c1.5,0,2.9-0.5,4.2-1.2l-0.7-0.7C14.4,17.1,13.2,17.5,12,17.5z" />
<path d="M18.9,15.2c0.7-0.7,1.3-1.3,1.7-1.8l0.1-0.1c0.3-0.4,0.6-0.8,0.6-1.3s-0.3-0.9-0.6-1.3l-0.1-0.1c-0.7-0.9-1.9-2.1-3.4-3.2 c-1.5-1-3.3-1.9-5.2-1.9c-0.8,0-1.6,0.2-2.4,0.4L5.4,1.6L4.6,2.4l16,16l0.7-0.7L18.9,15.2z M12,6.5c1.7,0,3.3,0.7,4.6,1.7 c1.4,1,2.5,2.2,3.2,3c0.4,0.5,0.5,0.6,0.5,0.7s0,0.2-0.5,0.7c-0.4,0.5-1,1.1-1.6,1.7L16,12.3c0-0.1,0-0.2,0-0.3c0-2.2-1.8-4-4-4 c-0.1,0-0.2,0-0.3,0l-1.3-1.3C10.9,6.6,11.5,6.5,12,6.5z M14.9,11.2l-2-2C13.8,9.4,14.6,10.2,14.9,11.2z" />
</svg>

View File

@ -24,17 +24,19 @@ test.describe('recruit tests', () => {
// Fill [placeholder="Appleseed"]
await page.fill('[placeholder="Appleseed"]', 'Dooliutl')
// Click .ml-4 .tooltip-trigger .flex-center
await page.click('#add-tag')
await page.click('button:has-text("Skills")')
// Click text=Add/Create Skill Suggested Cancel >> button
await page.click('#new-tag')
await page.click('.buttons-group button:nth-child(3)')
// Fill [placeholder="Please\ type\ Skill\ title"]
await page.fill('[placeholder="Please\\ type\\ Skill\\ title"]', 's1')
await page.fill('[placeholder="Please\\ type\\ \\ title"]', 's1')
// Click text=Create Skill s1 Please type description here Category Other Create Cancel >> button
await page.click('text=Create more Create >> button')
await page.click('button:has-text("Other (1)")')
// Click text=s1
await page.click('text=s1')
// Click :nth-match(:text("Cancel"), 2)
await page.click('button:has-text("Cancel")')
// await page.click('button:has-text("Cancel")')
await page.keyboard.press('Escape')
// Click button:has-text("Create")
await page.click('button:has-text("Create")')
})
@ -74,11 +76,13 @@ test.describe('recruit tests', () => {
// Click button:has-text("Candidate")
await page.click('button:has-text("Candidate")')
// Click #add-tag div div
await page.click('#add-tag div div')
await page.click('button:has-text("Skills")')
await page.click('button:has-text("Backend development (1)")')
// Click text=java
await page.click('text=java')
// Click :nth-match(:text("Cancel"), 2)
await page.click('button:has-text("Cancel")')
// await page.click('button:has-text("Cancel")')
await page.keyboard.press('Escape')
// Click [placeholder="John"]
await page.click('[placeholder="John"]')
// Fill [placeholder="John"]