Updated List layout

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov 2025-05-26 05:49:03 +07:00
parent 0c8c2cc84e
commit 3d6994d5cc
No known key found for this signature in database
GPG Key ID: 67AE092F5EB4180C
16 changed files with 227 additions and 193 deletions

View File

@ -2248,26 +2248,31 @@
display: flex;
align-items: center;
padding: 0 2.5rem 0 0.25rem;
width: 100%;
width: max-content;
height: 2.75rem;
min-width: 100%;
min-height: 2.75rem;
color: var(--theme-caption-color);
background-color: var(--theme-list-row-color);
&.row {
border-left: 1px solid var(--theme-list-border-color);
border-right: 1px solid var(--theme-list-border-color);
border-left: 1px solid transparent;
border-right: 1px solid transparent;
// border-left: 1px solid var(--theme-list-border-color);
// border-right: 1px solid var(--theme-list-border-color);
}
&.row:not(.lastCat, .last) {
border-bottom: 1px solid var(--theme-divider-color);
}
&.row.last {
border-bottom: 1px solid var(--theme-list-subheader-divider);
border-bottom: 1px solid transparent;
// border-bottom: 1px solid var(--theme-list-subheader-divider);
}
&.row.lastCat {
border-bottom-left-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
border-bottom: 1px solid var(--theme-list-border-color);
border-bottom: 1px solid transparent;
// border-bottom: 1px solid var(--theme-list-border-color);
}
&.compactMode {
@ -2390,6 +2395,36 @@
}
}
.listGrid-container {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: fit-content;
min-width: 0;
border-radius: 0.25rem;
&:not(:first-child) {
margin-top: 0.5rem;
}
&::after {
content: '';
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 1px solid var(--theme-list-border-color);
border-radius: 0.25rem;
z-index: 1;
pointer-events: none;
}
.expandcollapse-content {
width: max-content !important;
min-width: 100% !important;
}
}
/* ListView - global style */
.category-container.disableHeader .listGrid.row:first-child {
border-top-left-radius: 0.25rem;

View File

@ -21,14 +21,14 @@
afterUpdate(() => dispatch('changeContent'))
</script>
<div class="root" hidden={!isExpanded}>
<div class="flex-no-shrink clear-mins">
<div class="expandcollapse-container" hidden={!isExpanded}>
<div class="expandcollapse-content">
<slot />
</div>
</div>
<style lang="scss">
.root {
.expandcollapse-container {
min-height: 0;
flex-shrink: 0;
@ -38,5 +38,11 @@
&::-webkit-scrollbar:horizontal {
height: 0;
}
.expandcollapse-content {
flex-shrink: 0;
min-width: 0;
min-height: 0;
}
}
</style>

View File

@ -156,7 +156,7 @@
</div>
</AttachmentDroppable>
{:else if wSection < 640}
<Scroller horizontal>
<Scroller horizontal noFade={false}>
<Table
_class={attachmentClass}
config={[

View File

@ -16,16 +16,7 @@
import { Card, CardEvents } from '@hcengineering/card'
import core, { Data, Doc, fillDefaults, MarkupBlobRef, SortingOrder, WithLookup } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
Label,
Scroller,
ModernButton,
getCurrentLocation,
IconAdd,
navigate,
resizeObserver,
Section
} from '@hcengineering/ui'
import { Label, ButtonIcon, getCurrentLocation, IconAdd, navigate, resizeObserver, Section } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
import {
List,
@ -147,10 +138,9 @@
<div class="buttons-group xsmall-gap">
<ViewletsSettingButton bind:viewOptions viewletQuery={{ _id: viewletId }} kind={'tertiary'} bind:viewlet />
{#if !$restrictionStore.readonly && !readonly}
<ModernButton
<ButtonIcon
id="add-child-card"
icon={IconAdd}
label={card.string.CreateChild}
size={'small'}
kind={'tertiary'}
tooltip={{ label: card.string.CreateChild, direction: 'bottom' }}
@ -163,39 +153,37 @@
</svelte:fragment>
<svelte:fragment slot="content">
{#if (object?.children ?? 0) > 0 && viewOptions !== undefined && viewlet}
<Scroller horizontal>
<div
class="list"
use:resizeObserver={(evt) => {
listWidth = evt.clientWidth
<div
class="list"
use:resizeObserver={(evt) => {
listWidth = evt.clientWidth
}}
>
<List
bind:this={list}
{listProvider}
_class={card.class.Card}
query={{
parent: object._id
}}
>
<List
bind:this={list}
{listProvider}
_class={card.class.Card}
query={{
parent: object._id
}}
selectedObjectIds={$selection ?? []}
configurations={undefined}
{config}
{viewOptions}
compactMode={listWidth <= 600}
on:docs
on:row-focus={(event) => {
listProvider.updateFocus(event.detail ?? undefined)
}}
on:check={(event) => {
listProvider.updateSelection(event.detail.docs, event.detail.value)
}}
on:content={(evt) => {
docs = evt.detail
listProvider.update(evt.detail)
}}
/>
</div>
</Scroller>
selectedObjectIds={$selection ?? []}
configurations={undefined}
{config}
{viewOptions}
compactMode={listWidth <= 600}
on:docs
on:row-focus={(event) => {
listProvider.updateFocus(event.detail ?? undefined)
}}
on:check={(event) => {
listProvider.updateSelection(event.detail.docs, event.detail.value)
}}
on:content={(evt) => {
docs = evt.detail
listProvider.update(evt.detail)
}}
/>
</div>
{:else if !readonly}
<div class="antiSection-empty" class:solid={emptyKind === 'create'} class:noBorder={emptyKind === 'placeholder'}>
<!-- svelte-ignore a11y-click-events-have-key-events -->

View File

@ -16,7 +16,7 @@
import { Member } from '@hcengineering/contact'
import type { Class, Doc, Ref, Space } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Button, IconAdd, Label, Section, showPopup } from '@hcengineering/ui'
import { Button, IconAdd, Label, Section, showPopup, Scroller } from '@hcengineering/ui'
import { Viewlet, ViewletPreference } from '@hcengineering/view'
import { Table, ViewletSelector, ViewletSettingButton } from '@hcengineering/view-resources'
import contact from '../plugin'
@ -87,14 +87,16 @@
<svelte:fragment slot="content">
{#if members > 0 && viewlet}
<Table
_class={contact.class.Member}
config={preference?.config ?? viewlet.config}
options={viewlet.options}
query={{ attachedTo: objectId }}
loadingProps={{ length: members }}
{readonly}
/>
<Scroller horizontal noFade={false}>
<Table
_class={contact.class.Member}
config={preference?.config ?? viewlet.config}
options={viewlet.options}
query={{ attachedTo: objectId }}
loadingProps={{ length: members }}
{readonly}
/>
</Scroller>
{:else}
<div class="antiSection-empty solid flex-col mt-3">
<span class="content-dark-color">

View File

@ -149,7 +149,7 @@
}}
/>
{#if docsProvided && docs.length === 0}
<div class="flex-center content-color">
<div class="flex-center content-color empty-content">
<Label label={process.string.NoProcesses} />
</div>
{/if}
@ -157,3 +157,9 @@
</div>
</svelte:fragment>
</Section>
<style lang="scss">
.antiSection-empty:has(.empty-content) :global(.list-container) {
display: none;
}
</style>

View File

@ -54,7 +54,7 @@
<svelte:fragment slot="content">
{#if applications > 0 && viewlet !== undefined && !loading}
<Scroller horizontal>
<Scroller horizontal noFade={false}>
<Table
_class={recruit.class.Applicant}
config={preference?.config ?? viewlet.config}

View File

@ -73,7 +73,7 @@
</div>
{#if applications > 0}
{#if viewlet !== undefined && !loading}
<Scroller horizontal>
<Scroller horizontal noFade={false}>
<Table
_class={recruit.class.Applicant}
config={preference?.config ?? viewlet.config}

View File

@ -61,7 +61,7 @@
{/if}
</div>
{#if (vacancies ?? 0) > 0}
<Scroller horizontal>
<Scroller horizontal noFade={false}>
<Table
_class={recruit.class.Vacancy}
{config}

View File

@ -63,7 +63,7 @@
{/if}
</div>
{#if reviews > 0}
<Scroller horizontal>
<Scroller horizontal noFade={false}>
<Table
_class={recruit.class.Review}
config={[

View File

@ -18,7 +18,7 @@
import type { Class, Doc, Ref, Space } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Survey } from '@hcengineering/survey'
import { Button, IconAdd, Label, Section, navigate, showPopup } from '@hcengineering/ui'
import { Button, IconAdd, Label, Section, navigate, showPopup, Scroller } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { Table, ViewletSelector, ViewletSettingButton, getObjectLinkFragment } from '@hcengineering/view-resources'
import SurveyPopup from './SurveyPopup.svelte'
@ -79,14 +79,16 @@
<svelte:fragment slot="content">
{#if polls > 0 && viewlet}
<Table
_class={survey.class.Poll}
config={preference?.config ?? viewlet.config}
options={viewlet.options}
query={{ attachedTo: objectId }}
loadingProps={{ length: polls }}
{readonly}
/>
<Scroller horizontal noFade={false}>
<Table
_class={survey.class.Poll}
config={preference?.config ?? viewlet.config}
options={viewlet.options}
query={{ attachedTo: objectId }}
loadingProps={{ length: polls }}
{readonly}
/>
</Scroller>
{:else}
<div class="antiSection-empty solid flex-col mt-3">
<span class="content-dark-color">

View File

@ -115,7 +115,7 @@
isHeader={false}
isAside={true}
isSub={false}
adaptive={'default'}
adaptive={'disabled'}
withoutActivity={true}
on:open
on:close={() => dispatch('close')}

View File

@ -152,7 +152,7 @@
isHeader={false}
isAside={true}
isSub={false}
adaptive={'default'}
adaptive={'disabled'}
withoutActivity={true}
on:open
on:close={() => dispatch('close')}

View File

@ -251,7 +251,7 @@
display: flex;
flex-direction: column;
width: 100%;
height: max-content;
height: 100%;
min-width: auto;
min-height: auto;
}

View File

@ -36,7 +36,8 @@
ExpandCollapse,
mouseAttractor,
Loading,
Label
Label,
Scroller
} from '@hcengineering/ui'
import { AttributeModel, BuildModelKey, ViewOptionModel, ViewOptions, Viewlet } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
@ -441,10 +442,10 @@
in:fade|local={{ duration: 50 }}
bind:this={div}
class="category-container"
class:listGrid-container={level === 0}
class:collapsed
class:category-one={oneCat}
class:category-last={lastCat}
class:zero-container={level === 0}
class:disableHeader
on:drop|preventDefault={drop}
on:dragover={dragOverCat}
@ -489,114 +490,106 @@
}}
/>
{/if}
<ExpandCollapse isExpanded={!collapsed || dragItemIndex !== undefined}>
{#if !lastLevel}
<slot
name="category"
docs={itemProj}
{_class}
{space}
{lookup}
{baseMenuClass}
{config}
{configurations}
{selectedObjectIds}
{createItemDialog}
{createItemLabel}
{viewOptions}
{docKeys}
newObjectProps={_newObjectProps}
{flatHeaders}
{props}
level={level + 1}
{groupPersistKey}
{viewOptionsConfig}
{listDiv}
dragItem
dragstart={dragStartHandler}
/>
{:else if itemModels != null && itemModels.size > 0 && (!collapsed || wasLoaded || dragItemIndex !== undefined)}
{@const HLimited = lastLevel ? limited.length : itemProj.length}
{#if limited}
{#key configurationsVersion}
{#each limited as docObject, i (docObject._id)}
<ListItem
bind:this={listItems[i]}
{docObject}
model={getDocItemModel(Hierarchy.mixinOrClass(docObject))}
{groupByKey}
selected={isSelected(docObject, $focusStore)}
checked={selectedObjectIdsSet.has(docObject._id)}
last={i === limited.length - 1 && HLimited >= itemProj.length}
lastCat={i === limited.length - 1 && (oneCat || lastCat) && HLimited >= itemProj.length}
on:dragstart={(e) => {
dragStart(e, docObject, i)
}}
on:dragenter={(e) => {
if (dragItemIndex !== undefined) {
e.stopPropagation()
e.preventDefault()
}
}}
on:dragleave={(e) => {
dragItemLeave(e, i)
}}
on:dragover={(e) => {
dragover(e, i)
}}
on:drop={dropItemHandle}
on:check={(ev) => dispatch('check', { docs: ev.detail.docs, value: ev.detail.value })}
on:contextmenu={async (event) => {
await handleMenuOpened(event, docObject)
}}
on:focus={() => {}}
on:mouseover={mouseAttractor(() => {
handleRowFocused(docObject)
})}
on:mouseenter={mouseAttractor(() => {
handleRowFocused(docObject)
})}
{props}
{compactMode}
on:on-mount={() => {
wasLoaded = true
}}
/>
{/each}
{#if HLimited < itemProj.length}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="listGrid antiList__row row gap-2 flex-grow hoverable showMore last"
class:lastCat={oneCat || lastCat}
on:mouseenter={() => {
$focusStore.focus = undefined
}}
on:click={() => {
if (limit !== undefined) limit += 50
}}
>
<span class="caption-color"><Label label={ui.string.ShowMore} /></span>
{#if loading}
<div class="p-1">
<Loading shrink size={'small'} />
</div>
{:else}
<span class="content-halfcontent-color ml-0-5">({HLimited} / {itemProj.length})</span>
{/if}
</div>
{/if}
{/key}
<Scroller horizontal shrink noFade={false}>
<ExpandCollapse isExpanded={!collapsed || dragItemIndex !== undefined}>
{#if !lastLevel}
<slot
name="category"
docs={itemProj}
{_class}
{space}
{lookup}
{baseMenuClass}
{config}
{configurations}
{selectedObjectIds}
{createItemDialog}
{createItemLabel}
{viewOptions}
{docKeys}
newObjectProps={_newObjectProps}
{flatHeaders}
{props}
level={level + 1}
{groupPersistKey}
{viewOptionsConfig}
{listDiv}
dragItem
dragstart={dragStartHandler}
/>
{:else if itemModels != null && itemModels.size > 0 && (!collapsed || wasLoaded || dragItemIndex !== undefined)}
{@const HLimited = lastLevel ? limited.length : itemProj.length}
{#if limited}
{#key configurationsVersion}
{#each limited as docObject, i (docObject._id)}
<ListItem
bind:this={listItems[i]}
{docObject}
model={getDocItemModel(Hierarchy.mixinOrClass(docObject))}
{groupByKey}
selected={isSelected(docObject, $focusStore)}
checked={selectedObjectIdsSet.has(docObject._id)}
last={i === limited.length - 1 && HLimited >= itemProj.length}
lastCat={i === limited.length - 1 && (oneCat || lastCat) && HLimited >= itemProj.length}
on:dragstart={(e) => {
dragStart(e, docObject, i)
}}
on:dragenter={(e) => {
if (dragItemIndex !== undefined) {
e.stopPropagation()
e.preventDefault()
}
}}
on:dragleave={(e) => {
dragItemLeave(e, i)
}}
on:dragover={(e) => {
dragover(e, i)
}}
on:drop={dropItemHandle}
on:check={(ev) => dispatch('check', { docs: ev.detail.docs, value: ev.detail.value })}
on:contextmenu={async (event) => {
await handleMenuOpened(event, docObject)
}}
on:focus={() => {}}
on:mouseover={mouseAttractor(() => {
handleRowFocused(docObject)
})}
on:mouseenter={mouseAttractor(() => {
handleRowFocused(docObject)
})}
{props}
{compactMode}
on:on-mount={() => {
wasLoaded = true
}}
/>
{/each}
{#if HLimited < itemProj.length}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="listGrid antiList__row row gap-2 flex-grow hoverable showMore last"
class:lastCat={oneCat || lastCat}
on:mouseenter={() => {
$focusStore.focus = undefined
}}
on:click={() => {
if (limit !== undefined) limit += 50
}}
>
<span class="caption-color"><Label label={ui.string.ShowMore} /></span>
{#if loading}
<div class="p-1">
<Loading shrink size={'small'} />
</div>
{:else}
<span class="content-halfcontent-color ml-0-5">({HLimited} / {itemProj.length})</span>
{/if}
</div>
{/if}
{/key}
{/if}
{/if}
{/if}
</ExpandCollapse>
</ExpandCollapse>
</Scroller>
</div>
<style lang="scss">
.zero-container {
border-radius: 0.25rem;
&:not(:first-child) {
margin-top: 0.5rem;
}
}
</style>

View File

@ -298,11 +298,13 @@
}
}
&.subLevel {
top: 2.75rem;
top: 0;
padding: 0 2.5rem;
background: var(--theme-list-subheader-color);
border-left: 1px solid var(--theme-list-subheader-divider);
border-right: 1px solid var(--theme-list-subheader-divider);
border-left: 1px solid transparent;
border-right: 1px solid transparent;
// border-left: 1px solid var(--theme-list-subheader-divider);
// border-right: 1px solid var(--theme-list-subheader-divider);
border-bottom: 1px solid var(--theme-list-subheader-divider);
// here should be top 3rem for sticky, but with ExpandCollapse it gives strange behavior