Updated CardPresenter layout. Table, ListView fixes. (#9197)

This commit is contained in:
Alexander Platov
2025-06-10 08:57:59 +07:00
committed by GitHub
parent 95b7fccca2
commit f1c76f6846
24 changed files with 195 additions and 104 deletions
+8 -4
View File
@@ -153,8 +153,8 @@ export class TFavoriteCard extends TPreference implements FavoriteCard {
export * from './migration'
const listConfig: (BuildModelKey | string)[] = [
{ key: '', props: { showParent: true }, displayProps: { fixed: 'left', key: 'card' } },
{ key: '_class', displayProps: { fixed: 'left', key: 'type' } },
{ key: '', props: { showParent: true } },
{ key: '_class' },
{ key: '', displayProps: { grow: true } },
{
key: '',
@@ -172,7 +172,11 @@ const listConfig: (BuildModelKey | string)[] = [
},
{
key: 'modifiedOn',
displayProps: { fixed: 'right', dividerBefore: true }
displayProps: { fixed: 'right', key: 'modifiedOn', dividerBefore: true }
},
{
key: 'modifiedBy',
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
}
]
@@ -260,7 +264,7 @@ export function createSystemType (
hiddenKeys: ['content', 'title']
},
config: [
'',
{ key: '', props: { shrink: true } },
'_class',
{ key: '', presenter: view.component.RolePresenter, label: card.string.Tags, props: { fullSize: true } },
{
@@ -18,6 +18,7 @@
import presentation from '../plugin'
export let href: string | undefined
export let title: string | undefined = undefined
export let disabled = false
export let onClick: ((event: MouseEvent) => void) | undefined = undefined
export let noUnderline = disabled
@@ -29,6 +30,7 @@
export let inlineReference: boolean = false
export let transparent: boolean = false
export let inlineBlock = false
export let noSelect: boolean = true
function clickHandler (e: MouseEvent): void {
if (disabled) return
@@ -79,7 +81,9 @@
class:antiMention={inlineReference}
class:transparent
class:fs-bold={accent}
class:select-text={!noSelect}
style:flex-shrink={shrink}
{title}
on:click={clickHandler}
>
<slot />
@@ -96,6 +100,8 @@
class:transparent
class:fs-bold={accent}
style:flex-shrink={shrink}
class:select-text={!noSelect}
{title}
on:click={clickHandler}
>
<slot />
@@ -43,7 +43,7 @@
{#each narrowModel as model, i}
{#if hasComponent(model)}
{@const { component, props } = model}
<div class="title">
<div class="title inline-flex min-w-6">
{#if typeof component === 'string'}
<Component is={component} {props} />
{:else}
+25 -5
View File
@@ -23,6 +23,7 @@
min-width: 0;
border: 1px solid var(--theme-divider-color); // var(--global-surface-02-BorderColor);
border-radius: var(--small-focus-BorderRadius);
container-type: inline-size;
&:not(.modal) {
background-color: var(--theme-panel-color); // var(--global-surface-02-BackgroundColor);
@@ -1777,10 +1778,14 @@
}
td {
max-width: 50cqw;
&.align-left { text-align: left; }
&.align-center { text-align: center; }
&.align-right { text-align: right; }
}
&:has(td:nth-child(4)) td { max-width: 40cqw; }
&:has(td:nth-child(6)) td { max-width: 30cqw; }
&.editable {
th, td, tr {
@@ -2244,11 +2249,12 @@
/* ListView */
.listGrid {
overflow: hidden;
position: relative;
display: flex;
align-items: center;
padding: 0 2.5rem 0 0.25rem;
width: max-content;
padding: 0 2.5rem 0 0.75rem;
width: 100%;
height: 2.75rem;
min-width: 100%;
min-height: 2.75rem;
@@ -2276,7 +2282,7 @@
}
&.compactMode {
padding: 0 1.125rem 0 0.25rem;
padding: 0 1.125rem 0 0.75rem;
}
&.hoverable:hover,
&.mListGridSelected {
@@ -2403,6 +2409,7 @@
height: fit-content;
min-width: 0;
border-radius: 0.25rem;
container: listGridContainer / inline-size;
&:not(:first-child) {
margin-top: 0.5rem;
@@ -2418,11 +2425,24 @@
z-index: 1;
pointer-events: none;
}
.expandcollapse-content {
width: max-content !important;
min-width: 100% !important;
}
// Responsive style for ListView: horizontal scrolling mode for widths less than 480px
@container listGridContainer (max-width: 480px) {
.expandcollapse-content {
width: max-content !important;
.listGrid {
width: max-content;
.cropped-text-presenters > :is(span:not(.separator), a):not(:empty),
.cropped-text-presenter { max-width: 10rem; }
}
}
}
}
/* ListView - global style */
@@ -206,6 +206,7 @@
margin-right: var(--spacing-1);
width: var(--global-min-Size);
height: var(--global-min-Size);
min-width: var(--global-min-Size);
color: var(--global-primary-TextColor);
&__tag {
@@ -275,6 +276,7 @@
.hulyNavItem-icon {
width: 0.75rem;
min-width: 0.75rem;
margin-right: 0.625rem;
}
}
@@ -285,6 +287,7 @@
margin-right: var(--spacing-0_75);
width: var(--global-extra-small-Size);
height: var(--global-extra-small-Size);
min-width: var(--global-extra-small-Size);
background-color: var(--global-ui-BackgroundColor);
border-radius: var(--extra-small-BorderRadius);
}
+1 -1
View File
@@ -78,7 +78,7 @@
<span
use:tooltip={{ label: ui.string.TimeTooltip, props: { value: tooltipValue } }}
class="overflow-label"
class="no-word-wrap"
class:text-sm={kind === 'list'}
class:content-dark-color={kind === 'list'}
>
@@ -33,6 +33,7 @@
export let noSelect: boolean = true
export let inline = false
export let showParent: boolean = false
export let shrink: boolean = false
export let kind: 'list' | undefined = undefined
export let type: ObjectPresenterType = 'link'
export let icon: Asset | AnySvelteComponent | undefined = undefined
@@ -57,33 +58,55 @@
<ObjectMention object={cardObj} {disabled} {onClick} component={card.component.EditCard} />
{:else if cardObj}
{#if type === 'link'}
<div class="flex-row-center">
{#if showParent}
<ParentNamesPresenter value={cardObj} />
{/if}
<DocNavLink
object={cardObj}
{onClick}
{disabled}
{noUnderline}
{inline}
{colorInherit}
component={card.component.EditCard}
shrink={0}
>
<span class="presenterRoot" class:cursor-pointer={!disabled}>
{#if showParent}
<ParentNamesPresenter value={cardObj}>
<DocNavLink
object={cardObj}
{onClick}
{disabled}
{noUnderline}
{colorInherit}
{noSelect}
inline
component={card.component.EditCard}
shrink={kind === 'list' || shrink ? 1 : 0}
title={cardObj?.title}
>
{#if shouldShowAvatar}
<div class="icon" use:tooltip={{ label: card.string.Card }}>
<Icon icon={icon ?? card.icon.Card} size={'small'} />
</div>
{/if}
<span class="overflow-label" class:select-text={!noSelect} title={cardObj?.title}>
<span class="overflow-label">
{cardObj.title}
<slot name="details" />
</span>
</DocNavLink>
</ParentNamesPresenter>
{:else}
<DocNavLink
object={cardObj}
{onClick}
{disabled}
{noUnderline}
{colorInherit}
{noSelect}
inline
component={card.component.EditCard}
shrink={kind === 'list' || shrink ? 1 : 0}
title={cardObj?.title}
>
{#if shouldShowAvatar}
<div class="icon" use:tooltip={{ label: card.string.Card }}>
<Icon icon={icon ?? card.icon.Card} size={'small'} />
</div>
{/if}
<span class="overflow-label cropped-text-presenter">
{cardObj.title}
<slot name="details" />
</span>
</DocNavLink>
</div>
{/if}
{:else}
<span class="overflow-label" class:select-text={!noSelect} use:tooltip={{ label: getEmbeddedLabel(cardObj.title) }}>
{cardObj.title}
@@ -92,14 +115,8 @@
{/if}
<style lang="scss">
.presenterRoot {
display: flex;
align-items: center;
flex-shrink: 0;
.icon {
margin-right: 0.5rem;
color: var(--theme-dark-color);
}
.icon {
margin-right: 0.5rem;
color: var(--theme-dark-color);
}
</style>
@@ -26,6 +26,7 @@
export let kind: 'list' | undefined = undefined
export let type: ObjectPresenterType = 'link'
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let shrink: boolean = false
let doc: Card | undefined
const query = createQuery()
@@ -40,4 +41,4 @@
)
</script>
<CardPresenter value={doc} {kind} {type} {icon} />
<CardPresenter value={doc} {kind} {type} {icon} {shrink} />
@@ -23,6 +23,8 @@
export let maxWidth = ''
const MIN_WIDTH = 2 // rem
function getHref (parentInfo: ParentInfo) {
const loc = getCurrentLocation()
loc.path[2] = cardId
@@ -33,49 +35,57 @@
}
</script>
{#if value && Array.isArray(value.parentInfo)}
<div class="root" style:max-width={maxWidth}>
<span class="names">
{#each value.parentInfo as parentInfo}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<NavLink href={getHref(parentInfo)}>
<span class="parent-label overflow-label cursor-pointer" title={parentInfo.title}>
{parentInfo.title}
</span>
</NavLink>
{/each}
</span>
{#if value && Array.isArray(value.parentInfo) && (value.parentInfo.length > 0 || $$slots.default)}
<div
class="cards-container cropped-text-presenters"
style:max-width={maxWidth}
style:--cards-container-card-min-width={`${MIN_WIDTH}rem`}
style:--cards-container-parents={value.parentInfo.length}
>
{#each value.parentInfo as parentInfo}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<NavLink
href={getHref(parentInfo)}
title={parentInfo.title}
shrink={parentInfo.title.length > 100 ? 2 : 1}
colorInherit
>
{parentInfo.title}
</NavLink>
<span class="separator"></span>
{/each}
<slot />
</div>
{/if}
<style lang="scss">
.root {
.cards-container {
display: inline-flex;
flex-shrink: 1;
margin-left: 0;
min-width: 0;
min-width: calc(
(var(--cards-container-card-min-width, 2rem) + 1.26rem) * var(--cards-container-parents, 1) +
var(--cards-container-card-min-width, 2rem)
);
color: var(--theme-darker-color);
.names {
display: inline-flex;
min-width: 0;
color: var(--theme-dark-color);
.separator {
flex-shrink: 0;
padding: 0 0.5rem;
color: var(--theme-content-color);
}
.parent-label {
flex-shrink: 5;
color: var(--theme-dark-color);
&:hover {
color: var(--theme-caption-color);
text-decoration: underline;
}
&:active {
color: var(--theme-content-color);
}
&::after {
content: '';
padding: 0 0.25rem;
}
:global(a:hover) {
color: var(--theme-caption-color);
// text-decoration: underline;
}
:global(a),
:global(span:not(.separator)) {
color: var(--theme-content-color);
}
:global(:is(span:not(.separator), a):not(:empty)) {
min-width: var(--cards-container-card-min-width, 2rem);
}
}
</style>
@@ -34,20 +34,10 @@
const defaultConfig: (BuildModelKey | string)[] = [
{
displayProps: {
fixed: 'left',
key: 'createdBy'
},
key: 'createdBy'
},
{
displayProps: {
fixed: 'left',
key: 'card'
},
key: '',
props: {
showParent: false
showParent: true,
shrink: true
}
},
{
@@ -59,7 +49,8 @@
{
displayProps: {
fixed: 'left',
key: 'tags'
key: 'tags',
compression: true
},
key: '',
label: card.string.Tags,
@@ -72,13 +63,17 @@
key: '',
presenter: card.component.LabelsPresenter,
label: card.string.Labels,
displayProps: { compression: true },
props: { fullSize: true }
},
{
key: 'parent'
key: 'modifiedOn', // 'createdOn',
displayProps: { key: 'modifiedOn', fixed: 'left', dividerBefore: true }
},
{
key: 'createdOn'
key: 'modifiedBy', // 'createdBy',
displayProps: { key: 'modifiedBy', fixed: 'right', align: 'center' },
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
}
]
</script>
+1 -2
View File
@@ -188,8 +188,7 @@ function createThreadTitle (message: Message, parent: Card): string {
const markup = jsonToMarkup(markdownToMarkup(message.content))
const messageText = markupToText(markup).trim()
const titleFromMessage = `${messageText.slice(0, 100)}${messageText.length > 100 ? '...' : ''}`
return titleFromMessage.length > 0 ? titleFromMessage : `Thread from ${parent.title}`
return messageText.length > 0 ? messageText : `Thread from ${parent.title}`
}
export async function loadLinkPreviewData (url: string): Promise<LinkPreviewData | undefined> {
@@ -52,6 +52,7 @@
export let type: ObjectPresenterType = 'link'
export let overflowLabel = true
export let inlineBlock = false
export let shrink: boolean = false
const client = getClient()
@@ -96,6 +97,7 @@
{maxWidth}
{showStatus}
{overflowLabel}
{shrink}
/>
<span class="status">
<Label label={statusLabel} />
@@ -121,6 +123,7 @@
{showStatus}
{overflowLabel}
{inlineBlock}
{shrink}
/>
{/if}
{:else if shouldShowPlaceholder}
@@ -33,11 +33,12 @@
export let enlargedText: boolean = false
export let colorInherit: boolean = false
export let accent: boolean = false
export let maxWidth: string = ''
export let maxWidth: string | undefined = undefined
export let type: ObjectPresenterType = 'link'
export let showStatus = true
export let overflowLabel = true
export let inlineBlock = false
export let shrink: boolean = false
</script>
{#if value}
@@ -52,6 +53,7 @@
{colorInherit}
{accent}
{inlineBlock}
shrink={shrink ? 1 : 0}
noOverflow
>
<span
@@ -77,7 +79,12 @@
</span>
</DocNavLink>
{:else if type === 'text'}
<span class:overflow-label={overflowLabel} use:tooltip={disabled ? undefined : showTooltip}>
<span
class:overflow-label={overflowLabel}
class:flex-no-shrink={!shrink}
style:max-width={maxWidth}
use:tooltip={disabled ? undefined : showTooltip}
>
{name}
</span>
{/if}
@@ -46,6 +46,7 @@
export let showStatus: boolean = false
export let overflowLabel = true
export let inlineBlock = false
export let shrink: boolean = false
const client = getClient()
$: personValue = typeof value === 'string' ? $personByIdStore.get(value) : value
@@ -104,6 +105,7 @@
{showStatus}
{overflowLabel}
{inlineBlock}
{shrink}
on:accent-color
/>
{/if}
@@ -18,13 +18,14 @@
import { Label } from '@hcengineering/ui'
export let value: Ref<Class<Doc>>
export let shrink: boolean = false
const client = getClient()
const _class = value !== undefined ? client.getModel().getObject(value) : undefined
</script>
{#if _class}
<div class="whitespace-nowrap">
<div class="whitespace-nowrap" class:flex-no-shrink={!shrink}>
<Label label={_class.label} />
</div>
{/if}
@@ -34,6 +34,8 @@
export let inlineReference: boolean = false
export let transparent: boolean = false
export let inlineBlock = false
export let noSelect: boolean = true
export let title: string | undefined = undefined
let _disabled = disabled || $restrictionStore.disableNavigation
$: _disabled = disabled || $restrictionStore.disableNavigation
@@ -74,6 +76,8 @@
{inlineReference}
{transparent}
{inlineBlock}
{noSelect}
{title}
>
<slot />
</NavLink>
@@ -38,6 +38,7 @@
shouldShowAvatar: false,
noUnderline: true,
noSelect: true,
shrink: true,
...(attributeModel.props ?? {}),
value: element
}
@@ -24,6 +24,7 @@
export let shouldShowName = true
export let shouldShowAvatar = true
export let noUnderline = false
export let shrink: boolean = false
export let avatarSize: IconSize = 'x-small'
const client = getClient()
@@ -44,6 +45,7 @@
{shouldShowName}
{shouldShowAvatar}
{noUnderline}
shrink={shrink ? 1 : 0}
props={{ avatarSize }}
/>
{/if}
@@ -363,7 +363,7 @@
{/if}
</th>
{/if}
{#each model as attribute}
{#each model.filter((m) => !m.displayProps?.grow) as attribute}
<th
class:w-full={attribute.displayProps?.grow === true}
class:sortable={attribute.sortingKey}
@@ -446,14 +446,25 @@
{/if}
{#await canEditSpace(object) then canEditObject}
{#if row < rowLimit}
{#each model as attribute, cell}
{#each model.filter((m) => !m.displayProps?.grow) as attribute, cell}
<td
class:align-left={attribute.displayProps?.align === 'left'}
class:align-center={attribute.displayProps?.align === 'center'}
class:align-right={attribute.displayProps?.align === 'right'}
>
<div class:antiTable-cells__firstCell={!cell}>
<!-- {getOnChange(object, attribute) !== undefined} -->
{#if !cell}
<div class="antiTable-cells__firstCell">
<!-- {getOnChange(object, attribute) !== undefined} -->
<svelte:component
this={attribute.presenter}
value={getValue(attribute, object)}
onChange={getOnChange(object, attribute)}
label={attribute.label}
attribute={attribute.attribute}
{...joinProps(attribute, object, readonly || $restrictionStore.readonly, canEditObject)}
/>
</div>
{:else}
<svelte:component
this={attribute.presenter}
value={getValue(attribute, object)}
@@ -462,7 +473,7 @@
attribute={attribute.attribute}
{...joinProps(attribute, object, readonly || $restrictionStore.readonly, canEditObject)}
/>
</div>
{/if}
</td>
{/each}
{/if}
@@ -474,7 +485,7 @@
<tbody>
{#each Array(getLoadingLength(loadingProps, options)) as i, row}
<tr class="antiTable-body__row" class:fixed={row === selection}>
{#each model as attribute, cell}
{#each model.filter((m) => !m.displayProps?.grow) as attribute, cell}
{#if !cell}
{#if enableChecking}
<td>
@@ -247,8 +247,8 @@
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
height: max-content;
min-width: auto;
min-height: auto;
min-height: 0;
}
</style>
@@ -490,7 +490,7 @@
}}
/>
{/if}
<Scroller horizontal shrink noFade={false}>
<Scroller horizontal shrink noFade={false} showOverflowArrows>
<ExpandCollapse isExpanded={!collapsed || dragItemIndex !== undefined}>
{#if !lastLevel}
<slot
@@ -149,6 +149,7 @@
colorInherit={!$themeStore.dark && level === 0}
accent={level === 0}
disabled
shrink
on:accent-color={(evt) => {
accentColor = evt.detail
}}
@@ -84,7 +84,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
bind:this={elem}
class="listGrid antiList__row row gap-2 flex-grow"
class="listGrid antiList__row row flex-gap-2 flex-grow"
class:compactMode
class:checking={checked}
class:mListGridSelected={selected}
@@ -12,12 +12,16 @@ export class CategoriesPage extends CalendarPage {
}
async openCategory (categoryTitle: string): Promise<void> {
await this.page.locator(`//span[text()='${categoryTitle}']/../../..//div[contains(@class, "firstCell")]/a`).click()
await this.page
.locator(`//span[text()='${categoryTitle}']/../..//div[contains(@class, "antiTable-cells__firstCell")]/a`)
.click()
}
async checkCategoryNotExist (categoryTitle: string): Promise<void> {
await expect(
this.page.locator(`//span[text()='${categoryTitle}']/../../..//div[contains(@class, "firstCell")]/a`)
this.page.locator(
`//span[text()='${categoryTitle}']/../..//div[contains(@class, "antiTable-cells__firstCell")]/a`
)
).toBeVisible({ visible: false })
}
}