Update dropdown, popups and icons (#713)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2021-12-23 12:03:35 +03:00 committed by GitHub
parent b380f7a12e
commit 8be72b58aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 256 additions and 278 deletions

View File

@ -53,13 +53,13 @@
.popup {
display: flex;
flex-direction: column;
padding: 1rem;
padding: .5rem;
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
user-select: none;
filter: drop-shadow(0 1.5rem 4rem rgba(0, 0, 0, .35));
}
.label {
@ -72,21 +72,22 @@
.scroll {
overflow-y: scroll;
.box { margin-right: 1px; }
.box { padding-right: 1px; }
}
.menu-item {
justify-content: start;
padding: .375rem;
padding: .5rem;
color: var(--theme-content-color);
border-radius: .5rem;
&:hover {
background-color: var(--theme-button-bg-pressed);
border: 1px solid var(--theme-bg-accent-color);
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
}
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
color: var(--theme-content-accent-color);
background-color: var(--theme-button-bg-pressed);
z-index: 1;
}
}

View File

@ -23,8 +23,7 @@
import type { Person } from '@anticrm/contact'
import { createQuery } from '../utils'
import presentation from '..'
import { ActionIcon } from '@anticrm/ui'
import BlueCheck from './icons/BlueCheck.svelte'
import { ActionIcon, IconBlueCheck } from '@anticrm/ui'
export let _class: Ref<Class<Person>>
export let title: IntlString
@ -44,8 +43,8 @@
</script>
<div class="popup">
<div class="header">
<div class="title"><Label label={title} /></div>
<div class="title"><Label label={title} /></div>
<div class="flex-col header">
<EditWithIcon icon={IconSearch} bind:value={search} placeholder={'Search...'} />
<div class="caption"><Label label={caption} /></div>
</div>
@ -57,7 +56,7 @@
<UserInfo size={'medium'} value={person} />
</div>
{#if allowDeselect && person._id === selected}
<ActionIcon direction={'top'} label={titleDeselect ?? presentation.string.Deselect} icon={BlueCheck} action={() => { dispatch('close', null) }} size={'small'}/>
<ActionIcon direction={'top'} label={titleDeselect ?? presentation.string.Deselect} icon={IconBlueCheck} action={() => { dispatch('close', null) }} size={'small'}/>
{/if}
</button>
{/each}
@ -69,34 +68,32 @@
.popup {
display: flex;
flex-direction: column;
padding: 1rem;
max-height: 100%;
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem;
user-select: none;
filter: drop-shadow(0 1.5rem 4rem rgba(0, 0, 0, .35));
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
}
.title {
margin: 1rem 1rem .25rem;
font-weight: 500;
color: var(--theme-caption-color);
}
.header {
margin: .25rem 1rem 0;
text-align: left;
.title {
margin-bottom: 1rem;
font-weight: 500;
color: var(--theme-caption-color);
}
.caption {
margin: 1rem 0 .625rem .375rem;
margin-top: .5rem;
font-size: .75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--theme-content-dark-color);
color: var(--theme-content-trans-color);
}
}
.scroll {
flex-grow: 1;
padding: .5rem;
overflow-x: hidden;
overflow-y: auto;
.box {
@ -108,15 +105,16 @@
.menu-item {
justify-content: start;
padding: .375rem;
color: var(--theme-content-color);
border-radius: .5rem;
&:hover {
background-color: var(--theme-button-bg-pressed);
border: 1px solid var(--theme-bg-accent-color);
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
}
&:focus {
border: 1px solid var(--primary-button-focused-border);
box-shadow: 0 0 0 3px var(--primary-button-outline);
color: var(--theme-content-accent-color);
background-color: var(--theme-button-bg-pressed);
z-index: 1;
}
}

View File

@ -1,22 +0,0 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<svg viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_b_991_16450)">
<rect width="24" height="24" rx="12" fill="#4474F6"/>
</g>
<path d="M8.25 12L10.9989 15L15.75 9" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<filter id="filter0_b_991_16450" x="-100" y="-100" width="224" height="224" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImage" stdDeviation="50"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_991_16450"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_991_16450" result="shape"/>
</filter>
</defs>
</svg>
</svg>

View File

@ -0,0 +1,85 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 { IntlString } from '@anticrm/platform'
import DropdownLabelsPopup from './DropdownLabelsPopup.svelte'
import Label from './Label.svelte'
import IconUp from './icons/Up.svelte'
import IconDown from './icons/Down.svelte'
import type { DropdownTextItem } from '../types'
import { showPopup } from '..'
export let title: IntlString
export let caption: IntlString | undefined = undefined
export let items: DropdownTextItem[]
export let selected: DropdownTextItem['id'] | undefined = undefined
export let header: boolean = false
let btn: HTMLElement
let opened: boolean = false
let isDisabled = false
$: isDisabled = items.length === 0
let selectedItem = items.find((x) => x.id === selected)
$: selectedItem = items.find((x) => x.id === selected)
$: if (selected === undefined && items[0] !== undefined) {
selected = items[0].id
}
const none = 'None' as IntlString
</script>
<div class="flex-col cursor-pointer"
bind:this={btn}
on:click|preventDefault={() => {
if (!opened) {
opened = true
showPopup(DropdownLabelsPopup, { title, caption, items, selected, header }, btn, (result) => {
if (result) selected = result
opened = false
})
}
}}
>
<div class="overflow-label label"><Label label={title} /></div>
<div class="flex-row-center space">
<span class="mr-1">
{#if opened}
<IconUp size={'small'} />
{:else}
<IconDown size={'small'} />
{/if}
</span>
<span class="overflow-label" class:caption-color={selected} class:content-dark-color={!selected}>
{#if selectedItem}
{selectedItem.label}
{:else}
<Label label={none} />
{/if}
</span>
</div>
</div>
<style lang="scss">
.label {
margin-bottom: .125rem;
font-weight: 500;
font-size: .75rem;
color: var(--theme-content-accent-color);
}
</style>

View File

@ -0,0 +1,119 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 type { IntlString } from '@anticrm/platform'
import { createEventDispatcher } from 'svelte'
import Label from './Label.svelte'
import EditWithIcon from './EditWithIcon.svelte'
import IconSearch from './icons/Search.svelte'
import IconBlueCheck from './icons/BlueCheck.svelte'
import type { DropdownTextItem } from '../types'
export let title: IntlString
export let caption: IntlString | undefined = undefined
export let items: DropdownTextItem[]
export let selected: DropdownTextItem['id'] | undefined = undefined
export let header: boolean = false
let search: string = ''
const dispatch = createEventDispatcher()
</script>
<div class="popup">
{#if header}
{#if title}<div class="title"><Label label={title} /></div>{/if}
<div class="flex-col header">
<EditWithIcon icon={IconSearch} bind:value={search} placeholder={'Search...'} />
{#if caption}<div class="caption"><Label label={caption} /></div>{/if}
</div>
{/if}
<div class="scroll">
<div class="flex-col box">
{#each items.filter((x) => x.label.toLowerCase().includes(search.toLowerCase())) as item}
<button class="flex-between menu-item" on:click={() => { dispatch('close', item.id) }}>
<div class="flex-grow caption-color">{item.label}</div>
{#if item.id === selected}
<div class="check"><IconBlueCheck size={'small'} /></div>
{/if}
</button>
{/each}
</div>
</div>
</div>
<style lang="scss">
.popup {
display: flex;
flex-direction: column;
min-width: 15rem;
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
}
.title {
margin: 1rem 1rem .25rem;
font-weight: 500;
color: var(--theme-caption-color);
}
.header {
margin: .25rem 1rem 0;
text-align: left;
.caption {
margin-top: .5rem;
font-size: .75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--theme-content-trans-color);
}
}
.scroll {
flex-grow: 1;
padding: .5rem;
overflow-x: hidden;
overflow-y: auto;
.box {
margin-right: 1px;
height: 100%;
}
}
.menu-item {
text-align: left;
padding: .5rem;
border-radius: .5rem;
&:hover {
color: var(--theme-caption-color);
background-color: var(--theme-button-bg-hovered);
}
&:focus {
color: var(--theme-content-accent-color);
background-color: var(--theme-button-bg-pressed);
z-index: 1;
}
}
.check {
margin-left: 1rem;
width: 1rem;
height: 1rem;
}
</style>

View File

@ -32,7 +32,6 @@
</script>
<div class="popup">
<div class="card-bg" />
<div class="title"><Label label={title} /></div>
{#if header}
<div class="flex-col header">
@ -56,10 +55,12 @@
<style lang="scss">
.popup {
position: relative;
display: flex;
flex-direction: column;
background-color: var(--theme-button-bg-focused);
border: 1px solid var(--theme-button-border-enabled);
border-radius: .75rem;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .2);
}
.title {
@ -108,23 +109,10 @@
}
}
&:hover { background-color: var(--theme-button-bg-focused); }
&:hover { background-color: var(--theme-button-bg-hovered); }
&:focus {
box-shadow: 0 0 0 3px var(--primary-button-outline);
z-index: 1;
}
}
.card-bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--theme-card-bg);
border-radius: .75rem;
backdrop-filter: blur(24px);
box-shadow: var(--theme-card-shadow);
z-index: -1;
}
</style>

View File

@ -1,200 +0,0 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 { IntlString } from '@anticrm/platform'
import Label from './Label.svelte'
import IconUp from './icons/Up.svelte'
import IconDown from './icons/Down.svelte'
import { DumbDropdownItem } from '../types';
export let items: DumbDropdownItem[]
export let selected: DumbDropdownItem['id'] | undefined
export let title: IntlString | undefined
let isDisabled = false
$: isDisabled = items.length === 0
let isOpened = false
let selectedItem = items.find((x) => x.id === selected)
$: selectedItem = items.find((x) => x.id === selected)
$: if (selected === undefined && items[0] !== undefined) {
selected = items[0].id
}
function onItemClick (id: DumbDropdownItem['id']) {
selected = id
}
function onClick () {
isOpened = !isOpened
}
function onClickOutside () {
isOpened = false
}
function clickOutside (node: any, onEventFunction: any) {
const handleClick = (event: any) => {
const path = event.composedPath()
if (!path.includes(node)) {
onEventFunction()
}
}
document.addEventListener('click', handleClick)
return {
destroy () {
document.removeEventListener('click', handleClick)
}
}
}
const none = 'None' as IntlString
</script>
<div class="root" class:disabled={isDisabled} on:click={onClick} use:clickOutside={onClickOutside}>
<div class="selected">
<div class="content">
{#if title !== undefined}
<div class="title">
<Label label={title} />
</div>
{/if}
<div class="label">
{#if selectedItem?.label !== undefined}
{selectedItem.label}
{:else}
<Label label={none} />
{/if}
</div>
</div>
<div class="icon">
{#if isOpened}
<IconUp size="small" />
{:else}
<IconDown size="small" />
{/if}
</div>
</div>
{#if isOpened}
<div class="items-container">
<div class="items">
{#each items as item (item.id)}
<div class="item" on:click={() => onItemClick(item.id)}>
{item.label}
</div>
{/each}
</div>
</div>
{/if}
</div>
<style lang="scss">
.root {
font-family: inherit;
font-size: 14px;
border: 2px solid transparent;
border-radius: 2px;
cursor: pointer;
&.disabled {
cursor: unset;
}
}
.selected {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
}
.content {
display: flex;
flex-direction: column;
justify-content: center;
}
.title {
font-size: 12px;
font-weight: 500;
color: var(--theme-content-accent-color);
opacity: 0.8;
user-select: none;
}
.label {
flex-grow: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding-right: 15px;
color: var(--theme-caption-color);
}
.items-container {
position: relative;
}
.items {
position: absolute;
width: 100%;
top: 18px;
display: flex;
flex-direction: column;
max-height: 300px;
overflow-y: auto;
background-color: var(--theme-button-bg-hovered);
border: 1px solid var(--theme-button-border-enabled);
border-radius: 12px;
z-index: 1000;
}
.item {
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding-right: 15px;
padding: 10px 20px;
&:first-child {
padding-top: 20px;
padding-bottom: 10px;
}
&:last-child {
padding-bottom: 20px;
}
&:hover {
background-color: var(--theme-bg-accent-color);
}
}
</style>

View File

@ -0,0 +1,8 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
</script>
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill={'#4474F6'} d="M8,0L8,0c4.4,0,8,3.6,8,8l0,0c0,4.4-3.6,8-8,8l0,0c-4.4,0-8-3.6-8-8l0,0C0,3.6,3.6,0,8,0z"/>
<path fill={'#fff'} d="M7.3,10.6c-0.2,0-0.3-0.1-0.4-0.2l-1.8-2c-0.2-0.2-0.2-0.6,0-0.8c0.2-0.2,0.6-0.2,0.8,0l1.4,1.5L10,5.6 c0.2-0.3,0.6-0.3,0.8-0.1c0.3,0.2,0.3,0.6,0.1,0.8l-3.2,4C7.7,10.5,7.5,10.6,7.3,10.6C7.3,10.6,7.3,10.6,7.3,10.6z"/>
</svg>

View File

@ -60,7 +60,7 @@ export { default as CircleButton } from './components/CircleButton.svelte'
export { default as Link } from './components/Link.svelte'
export { default as TimeSince } from './components/TimeSince.svelte'
export { default as Dropdown } from './components/Dropdown.svelte'
export { default as DumbDropdown } from './components/DumbDropdown.svelte'
export { default as DropdownLabels } from './components/DropdownLabels.svelte'
export { default as ShowMore } from './components/ShowMore.svelte'
export { default as IconAdd } from './components/icons/Add.svelte'
@ -81,6 +81,7 @@ export { default as IconShare } from './components/icons/Share.svelte'
export { default as IconDelete } from './components/icons/Delete.svelte'
export { default as IconEdit } from './components/icons/Edit.svelte'
export { default as IconInfo } from './components/icons/Info.svelte'
export { default as IconBlueCheck } from './components/icons/BlueCheck.svelte'
export { default as Menu } from './components/Menu.svelte'
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'

View File

@ -72,7 +72,7 @@ export interface ListItem {
label: string
}
export interface DumbDropdownItem {
export interface DropdownTextItem {
id: string
label: IntlString | string
}

View File

@ -9,11 +9,11 @@
<path d="M13,1h-2.2H9.4H6.6H5.2H3C1.9,1,1,1.9,1,3v10c0,1.1,0.9,2,2,2h2.2h1.4h2.8h1.4H13c1.1,0,2-0.9,2-2V3C15,1.9,14.1,1,13,1z M3,13.8c-0.4,0-0.8-0.4-0.8-0.8V3c0-0.4,0.4-0.8,0.8-0.8h2.2v11.6H3z M6.6,13.8V2.2h2.8v11.6H6.6z M13.8,13c0,0.4-0.4,0.8-0.8,0.8 h-2.2V2.2H13c0.4,0,0.8,0.4,0.8,0.8V13z"/>
</symbol>
<symbol id='todo-check' viewBox="0 0 16 16">
<circle cx="8" cy="8" r="6" stroke="white" fill="none"/>
<path d="M5.33268 8L7.33268 10L10.666 6" stroke="white"/>
<path d="M8,14.5c-3.6,0-6.5-2.9-6.5-6.5S4.4,1.5,8,1.5s6.5,2.9,6.5,6.5S11.6,14.5,8,14.5z M8,2.5C5,2.5,2.5,5,2.5,8 c0,3,2.5,5.5,5.5,5.5c3,0,5.5-2.5,5.5-5.5C13.5,5,11,2.5,8,2.5z"/>
<polygon points="7.4,10.7 5,8.4 5.7,7.6 7.3,9.3 10.3,5.7 11,6.3 "/>
</symbol>
<symbol id='todo-uncheck' viewBox="0 0 16 16">
<circle cx="8" cy="8" r="6" stroke="white" fill="none"/>
<path d="M8,14.5c-3.6,0-6.5-2.9-6.5-6.5S4.4,1.5,8,1.5s6.5,2.9,6.5,6.5S11.6,14.5,8,14.5z M8,2.5C5,2.5,2.5,5,2.5,8 c0,3,2.5,5.5,5.5,5.5c3,0,5.5-2.5,5.5-5.5C13.5,5,11,2.5,8,2.5z"/>
</symbol>
<symbol id="manage-statuses" viewBox="0 0 16 16">
<path d="M13.3,8.3c-0.1,2.8-2.5,5.1-5.4,5.1C5,13.4,2.6,11,2.6,8c0-2.9,2.3-5.2,5.1-5.4c0.1-0.4,0.2-0.7,0.4-1c0,0-0.1,0-0.1,0 C4.4,1.7,1.6,4.5,1.6,8c0,3.5,2.9,6.4,6.4,6.4s6.4-2.9,6.4-6.4c0,0,0-0.1,0-0.1C14,8.1,13.7,8.2,13.3,8.3z"/>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -15,8 +15,8 @@
<script lang="ts">
import type { Ref } from '@anticrm/core'
import { createQuery } from '@anticrm/presentation'
import { DumbDropdown } from '@anticrm/ui'
import type { DumbDropdownItem } from '@anticrm/ui/src/types'
import { DropdownLabels } from '@anticrm/ui'
import type { DropdownTextItem } from '@anticrm/ui/src/types'
import type { KanbanTemplate, KanbanTemplateSpace } from '@anticrm/task'
import task from '@anticrm/task'
@ -27,11 +27,11 @@
const templatesQ = createQuery()
$: templatesQ.query(task.class.KanbanTemplate, { space: { $in: folders } }, (result) => { templates = result })
let items: DumbDropdownItem[] = []
let items: DropdownTextItem[] = []
$: items = templates.map(x => ({ id: x._id, label: x.title }))
let selectedItem: string | undefined
$: template = selectedItem === undefined ? undefined : selectedItem as Ref<KanbanTemplate>
</script>
<DumbDropdown {items} bind:selected={selectedItem} title="Statuses" />
<DropdownLabels {items} bind:selected={selectedItem} title="Statuses" />