Update Table layout, combine check and notify cells. Optimize CSS. (#1075)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-03-02 16:03:58 +07:00 committed by GitHub
parent 2a061d58ec
commit 9d7d349a0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 263 additions and 268 deletions

View File

@ -40,98 +40,24 @@
const dispatch = createEventDispatcher()
</script>
<form class="card-container" on:submit|preventDefault={ () => {} }>
<div class="card-bg" />
<div class="flex-between header">
<div class="overflow-label fs-title"><Label {label} params={labelProps ?? {}} /></div>
<form class="antiCard" on:submit|preventDefault={ () => {} }>
<div class="antiCard-header">
<div class="antiCard-header__title"><Label {label} params={labelProps ?? {}} /></div>
{#if $$slots.error}
<div class="flex-grow error">
<div class="antiCard-header__error">
<slot name="error" />
</div>
{/if}
</div>
<div class="content"><slot /></div>
<div class="antiCard-content"><slot /></div>
{#if spaceClass && spaceLabel && spacePlaceholder}
<div class="flex-col pool">
<div class="separator" />
<div class="antiCard-pool">
<div class="antiCard-pool__separator" />
<SpaceSelect _class={spaceClass} spaceQuery={spaceQuery} label={spaceLabel} placeholder={spacePlaceholder} bind:value={space} />
</div>
{/if}
<div class="footer">
<div class="antiCard-footer">
<Button disabled={!canSave} label={okLabel} size={'small'} transparent primary on:click={() => { okAction(); dispatch('close') }} />
<Button label={cancelLabel} size={'small'} transparent on:click={() => { dispatch('close') }} />
</div>
</form>
<style lang="scss">
.card-container {
position: relative;
display: flex;
flex-direction: column;
width: 21.25rem;
min-width: 21.25rem;
max-width: 21.25rem;
border-radius: 1.25rem;
.header {
position: relative;
flex-shrink: 0;
padding: 1.75rem;
.error {
position: absolute;
display: flex;
top: 3.25rem;
left: 1.75rem;
right: 1.75rem;
font-weight: 500;
font-size: .75rem;
color: var(--system-error-color);
&:empty { visibility: hidden; }
}
}
.content {
flex-shrink: 0;
flex-grow: 1;
margin: 0 1.75rem;
height: fit-content;
}
.pool {
margin: 0 1.75rem .75rem;
color: var(--theme-caption-color);
.separator {
margin: 1rem 0;
height: 1px;
background-color: var(--theme-card-divider);
}
}
.footer {
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
direction: rtl;
justify-content: start;
align-items: center;
column-gap: .75rem;
padding: 1rem 1.75rem 1.75rem;
height: 5.25rem;
overflow: hidden;
border-radius: 0 0 1.25rem 1.25rem;
}
.card-bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--theme-card-bg);
border-radius: 1.25rem;
box-shadow: var(--theme-card-shadow);
z-index: -1;
}
}
</style>

View File

@ -318,3 +318,93 @@
}
}
}
/* Table */
.antiTable {
width: 100%;
th, td {
padding: .5rem 1.5rem;
text-align: left;
&:first-child { padding-left: 0; }
&:last-child { padding-right: 0; }
}
th {
height: 2.5rem;
font-weight: 500;
font-size: .75rem;
color: var(--theme-content-dark-color);
box-shadow: inset 0 -1px 0 0 var(--theme-bg-focused-color);
user-select: none;
// z-index: 5;
&.sortable { cursor: pointer; }
&.sorted .icon {
margin-left: .25rem;
opacity: .6;
}
&:hover .antiTable-cells__checkCell { visibility: visible; }
.checkall { visibility: visible; }
}
&.metaColumn {
th, td {
&:first-child {
padding: 0;
min-width: 2.5rem;
width: 2.5rem;
}
&:nth-child(2) { padding-left: 0; }
&:last-child { padding-right: 1.5rem; }
}
}
.antiTable-cells {
display: flex;
align-items: center;
white-space: nowrap;
&__checkCell, &__notifyCell {
display: flex;
justify-content: center;
align-items: center;
}
&__checkCell { visibility: hidden; }
&__firstCell {
display: flex;
align-items: center;
&-menuRow {
visibility: hidden;
margin-left: .5rem;
opacity: .6;
cursor: pointer;
&:hover { opacity: 1; }
}
}
}
.antiTable-body__row {
height: 3.25rem;
color: var(--theme-caption-color);
border-bottom: 1px solid var(--theme-button-border-hovered);
&:last-child { border-bottom: none; }
&:hover .antiTable-cells__firstCell .antiTable-cells__firstCell-menuRow { visibility: visible; }
&:hover, &.checking {
background-color: var(--theme-table-bg-hover);
.antiTable-cells__checkCell { visibility: visible; }
.antiTable-cells__notifyCell .notify-table-kind {
width: 1rem;
height: 1rem;
background-color: var(--theme-button-bg-hovered);
outline-color: currentColor;
}
}
&.fixed {
.antiTable-cells__checkCell { visibility: visible; }
.antiTable-cells__firstCell-menuRow { visibility: visible; }
}
}
}

View File

@ -68,3 +68,83 @@
background: #000;
opacity: .5;
}
/* Cards */
.antiCard {
display: flex;
flex-direction: column;
width: 21.25rem;
min-width: 21.25rem;
max-width: 21.25rem;
background-color: var(--theme-card-bg);
border-radius: 1.25rem;
box-shadow: var(--theme-card-shadow);
.antiCard-header {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
padding: 1.75rem;
&__title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
min-width: 0;
font-weight: 500;
font-size: 1rem;
color: var(--theme-caption-color);
}
&__error {
min-width: 0;
flex-grow: 1;
position: absolute;
display: flex;
top: 3.25rem;
left: 1.75rem;
right: 1.75rem;
font-weight: 500;
font-size: .75rem;
color: var(--system-error-color);
&:empty { visibility: hidden; }
}
}
.antiCard-content {
flex-shrink: 0;
flex-grow: 1;
margin: 0 1.75rem;
height: fit-content;
}
.antiCard-pool {
display: flex;
flex-direction: column;
margin: 0 1.75rem .75rem;
color: var(--theme-caption-color);
&__separator {
margin: 1rem 0;
height: 1px;
background-color: var(--theme-card-divider);
}
}
.antiCard-footer {
overflow: hidden;
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
direction: rtl;
justify-content: start;
align-items: center;
column-gap: .75rem;
padding: 1rem 1.75rem 1.75rem;
height: 5.25rem;
border-radius: 0 0 1.25rem 1.25rem;
}
}

View File

@ -60,9 +60,7 @@
width: 3.5rem;
height: 1.75rem;
border-radius: 3.125rem;
// vertical-align: top;
background-color: var(--theme-off-color);
box-shadow: 1px 2px 7px rgba(119, 129, 142, 0.1);
transition: left .2s, background-color .2s;
&:before {
content: '';

View File

@ -151,8 +151,4 @@
border: 1px dashed var(--theme-zone-border-lite);
border-radius: 0.75rem;
}
.lower {
text-transform: lowercase;
}
</style>

View File

@ -116,7 +116,7 @@
</div>
</div>
<div class="flex-row-center channels">
<div class="flex-row-center mt-5">
<Channels
bind:channels
on:change={(e) => {
@ -127,9 +127,6 @@
</Card>
<style lang="scss">
.channels {
margin-top: 1.25rem;
}
.update-container {
margin-left: -1rem;
margin-right: -1rem;
@ -137,13 +134,9 @@
margin-bottom: 1rem;
user-select: none;
font-size: 14px;
color: var(--theme-content-color);
&.WARNING {
color: yellow;
}
&.ERROR {
color: var(--system-error-color);
}
&.ERROR { color: var(--system-error-color); }
border: 1px dashed var(--theme-zone-border);
border-radius: 0.5rem;

View File

@ -18,6 +18,7 @@
import { NotificationClientImpl } from '../utils'
export let value: Doc
export let kind: 'table' | 'block' = 'block'
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
@ -27,13 +28,28 @@
</script>
{#if hasNotification}
<div class="color" style="background-color: {getPlatformColor(11)}" />
<div class="notify-{kind}-kind" style="color: {getPlatformColor(11)}" />
{/if}
<style lang="scss">
.color {
.notify-block-kind {
width: .5rem;
height: .5rem;
background-color: currentColor;
border-radius: .5rem;
}
</style>
.notify-table-kind {
position: absolute;
top: 50%;
left: 50%;
width: .5rem;
height: .5rem;
background-color: currentColor;
border-radius: .25rem;
outline: 1px solid transparent;
outline-offset: 2px;
transform: translate(-50%, -50%);
transition: all .1s ease-in-out;
z-index: -1;
}
</style>

View File

@ -540,13 +540,9 @@
margin-bottom: 1rem;
user-select: none;
font-size: 14px;
color: var(--theme-content-color);
&.WARNING {
color: yellow;
}
&.ERROR {
color: var(--system-error-color);
}
&.ERROR { color: var(--system-error-color); }
border: 1px dashed var(--theme-zone-border);
border-radius: 0.5rem;

View File

@ -125,30 +125,31 @@
<Loading />
{/if}
{:then model}
<table class="table-body" class:enableChecking class:showNotification>
<table class="antiTable" class:metaColumn={enableChecking || showNotification}>
<thead class="scroller-thead">
<tr class="tr-head scroller-thead__tr">
{#if enableChecking}
<th class="checkCell" class:checkall={checked.size > 0}>
<CheckBox
symbol={'minus'}
checked={objects?.length === checked.size && objects?.length > 0}
on:change={(e) => {
objects.map((o) => check(o._id, e))
}}
/>
<tr class="scroller-thead__tr">
{#if enableChecking || showNotification}
<th>
{#if enableChecking && objects?.length > 0}
<div class="antiTable-cells__checkCell" class:checkall={checked.size > 0}>
<CheckBox
symbol={'minus'}
checked={objects?.length === checked.size && objects?.length > 0}
on:change={(e) => {
objects.map((o) => check(o._id, e))
}}
/>
</div>
{/if}
</th>
{/if}
{#if showNotification}
<th />
{/if}
{#each model as attribute}
<th
class:sortable={attribute.sortingKey}
class:sorted={attribute.sortingKey === sortKey}
on:click={() => changeSorting(attribute.sortingKey)}
>
<div class="flex-row-center whitespace-nowrap">
<div class="antiTable-cells">
<Label label={attribute.label} />
{#if attribute.sortingKey === sortKey}
<div class="icon">
@ -167,34 +168,47 @@
{#if objects}
<tbody>
{#each objects as object, row (object._id)}
<tr class="tr-body" class:checking={checked.has(object._id)} class:fixed={row === selectRow}>
<tr class="antiTable-body__row" class:checking={checked.has(object._id)} class:fixed={row === selectRow}>
{#each model as attribute, cell}
{#if !cell}
{#if enableChecking}
<td>
<div class="checkCell">
<CheckBox
checked={checked.has(object._id)}
on:change={(e) => {
check(object._id, e)
}}
/>
</div>
</td>
{/if}
{#if showNotification}
<td class="notificationCell">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
{#if enableChecking || showNotification}
<td class="relative">
{#if showNotification}
<div class="antiTable-cells__notifyCell">
{#if enableChecking}
<div class="antiTable-cells__checkCell">
<CheckBox
checked={checked.has(object._id)}
on:change={(e) => {
check(object._id, e)
}}
/>
</div>
{/if}
<Component is={notification.component.NotificationPresenter} props={{ value: object, kind: enableChecking ? 'table' : 'block' }} />
</div>
{:else}
<div class="antiTable-cells__checkCell">
<CheckBox
checked={checked.has(object._id)}
on:change={(e) => {
check(object._id, e)
}}
/>
</div>
{/if}
</td>
{/if}
<td>
<div class="firstCell">
<div class="antiTable-cells__firstCell">
<svelte:component
this={attribute.presenter}
value={getValue(object, attribute.key)}
{...attribute.props}
/>
<div class="menuRow" on:click={(ev) => showMenu(ev, object, row)}><MoreV size={'small'} /></div>
<div class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, object, row)}>
<MoreV size={'small'} />
</div>
</div>
</td>
{:else}
@ -213,13 +227,15 @@
{:else if loadingProps !== undefined}
<tbody>
{#each Array(getLoadingLength(loadingProps, options)) as i, row}
<tr class="tr-body" class:fixed={row === selectRow}>
<tr class="antiTable-body__tr" class:fixed={row === selectRow}>
{#each model as attribute, cell}
{#if !cell}
{#if enableChecking}
<td>
<div class="checkCell">
<CheckBox checked={false} />
<div class="antiTable-cells__checkCell">
<CheckBox
checked={false}
/>
</div>
</td>
{/if}
@ -238,119 +254,3 @@
{#if loading}
<Loading />
{/if}
<style lang="scss">
.table-body {
width: 100%;
}
.notificationCell {
padding: 0.5rem;
padding-left: 1rem !important;
padding-right: 0rem;
}
.firstCell {
display: flex;
// justify-content: space-between;
align-items: center;
.menuRow {
visibility: hidden;
margin-left: 0.5rem;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
.checkCell {
visibility: hidden;
display: flex;
align-items: center;
}
th,
td {
padding: 0.5rem 1.5rem;
text-align: left;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
.enableChecking,
.showNotification {
th,
td {
&:first-child {
padding: 0 0.75rem;
width: 2.5rem;
}
&:nth-child(2) {
padding-left: 0;
// padding-right: 0;
}
&:last-child {
padding-right: 1.5rem;
}
}
}
th {
height: 2.5rem;
font-weight: 500;
font-size: 0.75rem;
color: var(--theme-content-dark-color);
box-shadow: inset 0 -1px 0 0 var(--theme-bg-focused-color);
user-select: none;
z-index: 5;
&.sortable {
cursor: pointer;
}
&.sorted .icon {
margin-left: 0.25rem;
opacity: 0.6;
}
&:hover {
.checkCell {
visibility: visible;
}
}
.checkall {
visibility: visible;
}
}
.tr-body {
height: 3.25rem;
color: var(--theme-caption-color);
border-bottom: 1px solid var(--theme-button-border-hovered);
&:hover .firstCell .menuRow {
visibility: visible;
}
&:last-child {
border-bottom: none;
}
&:hover,
&.checking {
background-color: var(--theme-table-bg-hover);
.checkCell {
visibility: visible;
}
}
}
.fixed {
.checkCell {
visibility: visible;
}
.menuRow {
visibility: visible;
}
}
</style>

View File

@ -30,5 +30,5 @@
</script>
<Scroller>
<Table {_class} {config} {options} query={resultQuery} {baseMenuClass} showNotification/>
<Table {_class} {config} {options} query={resultQuery} {baseMenuClass} showNotification />
</Scroller>

View File

@ -20,7 +20,7 @@ test.describe('contact tests', () => {
await lastName.click()
await lastName.fill('John')
await page.locator('.card-container').locator('button:has-text("Create")').click()
await page.locator('.antiCard').locator('button:has-text("Create")').click()
})
test('contact-search', async ({ page }) => {
// Create user and workspace
@ -29,18 +29,18 @@ test.describe('contact tests', () => {
await page.locator('[id="app-contact\\:string\\:Contacts"]').click()
await expect(page.locator('text=Marina M.')).toBeVisible()
expect(await page.locator('.tr-body').count()).toBeGreaterThan(5)
expect(await page.locator('.antiTable-body__row').count()).toBeGreaterThan(5)
const searchBox = page.locator('[placeholder="Search"]')
await searchBox.fill('Marina')
await searchBox.press('Enter')
await expect(page.locator('.tr-body')).toHaveCount(1)
await expect(page.locator('.antiTable-body__row')).toHaveCount(1)
await searchBox.fill('')
await searchBox.press('Enter')
await expect(page.locator('text=Rosamund Chen')).toBeVisible()
expect(await page.locator('.tr-body').count()).toBeGreaterThan(5)
expect(await page.locator('.antiTable-body__row').count()).toBeGreaterThan(5)
})
})

View File

@ -28,7 +28,7 @@ test.describe('recruit tests', () => {
await location.click()
await location.fill('Cupertino')
await page.locator('.card-container').locator('button:has-text("Create")').click()
await page.locator('.antiCard').locator('button:has-text("Create")').click()
})
test('create-application', async ({ page }) => {
@ -105,18 +105,18 @@ test.describe('recruit tests', () => {
await page.click('text=Software Engineer')
await expect(page.locator('text=Andrey P.')).toBeVisible()
expect(await page.locator('.tr-body').count()).toBeGreaterThan(2)
expect(await page.locator('.antiTable-body__row').count()).toBeGreaterThan(2)
const searchBox = page.locator('[placeholder="Search"]')
await searchBox.fill('Frontend Engineer')
await searchBox.press('Enter')
await expect(page.locator('.tr-body')).toHaveCount(1)
await expect(page.locator('.antiTable-body__row')).toHaveCount(1)
await searchBox.fill('')
await searchBox.press('Enter')
await expect(page.locator('text=Andrey P.')).toBeVisible()
expect(await page.locator('.tr-body').count()).toBeGreaterThan(2)
expect(await page.locator('.antiTable-body__row').count()).toBeGreaterThan(2)
})
})