@@ -9,7 +9,7 @@
|
||||
import RefreshButton from "@/components/base/RefreshButton.svelte";
|
||||
import SortHeader from "@/components/base/SortHeader.svelte";
|
||||
import FormattedDate from "@/components/base/FormattedDate.svelte";
|
||||
import HorizontalScroller from "@/components/base/HorizontalScroller.svelte";
|
||||
import Scroller from "@/components/base/Scroller.svelte";
|
||||
import CopyIcon from "@/components/base/CopyIcon.svelte";
|
||||
import SettingsSidebar from "@/components/settings/SettingsSidebar.svelte";
|
||||
import AdminUpsertPanel from "@/components/admins/AdminUpsertPanel.svelte";
|
||||
@@ -97,7 +97,7 @@
|
||||
/>
|
||||
<div class="clearfix m-b-base" />
|
||||
|
||||
<HorizontalScroller class="table-wrapper">
|
||||
<Scroller class="table-wrapper">
|
||||
<table class="table" class:table-loading={isLoading}>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -211,11 +211,11 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</HorizontalScroller>
|
||||
</Scroller>
|
||||
|
||||
{#if admins.length}
|
||||
<small class="block txt-hint txt-right m-t-sm">Showing {admins.length} of {admins.length}</small>
|
||||
{/if}
|
||||
<svelte:fragment slot="footer">
|
||||
<div class="m-r-auto txt-sm txt-hint">Total found: {admins.length}</div>
|
||||
</svelte:fragment>
|
||||
</PageWrapper>
|
||||
|
||||
<AdminUpsertPanel bind:this={adminUpsertPanel} on:save={() => loadAdmins()} on:delete={() => loadAdmins()} />
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let classes = "";
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
let wrapper = null;
|
||||
let scrollClasses = "";
|
||||
let scrollTimeoutId = null;
|
||||
let observer;
|
||||
|
||||
export function refresh() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(scrollTimeoutId);
|
||||
|
||||
scrollTimeoutId = setTimeout(() => {
|
||||
const offsetWidth = wrapper.offsetWidth;
|
||||
const scrollWidth = wrapper.scrollWidth;
|
||||
|
||||
if (scrollWidth - offsetWidth) {
|
||||
scrollClasses = "scrollable";
|
||||
if (wrapper.scrollLeft === 0) {
|
||||
scrollClasses += " scroll-start";
|
||||
} else if (wrapper.scrollLeft + offsetWidth == scrollWidth) {
|
||||
scrollClasses += " scroll-end";
|
||||
}
|
||||
} else {
|
||||
scrollClasses = "";
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
refresh();
|
||||
|
||||
observer = new MutationObserver(() => {
|
||||
refresh();
|
||||
});
|
||||
|
||||
observer.observe(wrapper, {
|
||||
attributeFilter: ["width"],
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
clearTimeout(scrollTimeoutId);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={refresh} />
|
||||
|
||||
<div class="horizontal-scroller-wrapper">
|
||||
<slot name="before" />
|
||||
|
||||
<div bind:this={wrapper} class="horizontal-scroller {classes} {scrollClasses}" on:scroll={refresh}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<slot name="after" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.horizontal-scroller {
|
||||
width: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.horizontal-scroller-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
:global(.horizontal-scroller-wrapper .columns-dropdown) {
|
||||
top: 40px;
|
||||
z-index: 100;
|
||||
max-height: 340px;
|
||||
}
|
||||
</style>
|
||||
@@ -11,6 +11,8 @@
|
||||
</main>
|
||||
|
||||
<footer class="page-footer">
|
||||
<slot name="footer" />
|
||||
|
||||
<a href={import.meta.env.PB_DOCS_URL} target="_blank" rel="noopener noreferrer">
|
||||
<i class="ri-book-open-line txt-sm" />
|
||||
<span class="txt">Docs</span>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<script>
|
||||
import { onMount, createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let classes = "";
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
export let vThreshold = 0;
|
||||
export let hThreshold = 0;
|
||||
export let dispatchOnNoScroll = true;
|
||||
|
||||
let wrapper = null;
|
||||
let scrollClasses = "";
|
||||
let throttleTimeoutId = null;
|
||||
let hDiff;
|
||||
let vDiff;
|
||||
let wrapperWidth;
|
||||
let wrapperHeight;
|
||||
let observer;
|
||||
|
||||
export function resetVerticalScroll() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.scrollTop = 0;
|
||||
}
|
||||
|
||||
export function resetHorizontalScroll() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.scrollLeft = 0;
|
||||
}
|
||||
|
||||
export function refresh() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollClasses = "";
|
||||
|
||||
// +2 extra threshold as a workaround for the lack of subpixel precision
|
||||
wrapperWidth = wrapper.clientWidth + 2;
|
||||
wrapperHeight = wrapper.clientHeight + 2;
|
||||
|
||||
hDiff = wrapper.scrollWidth - wrapperWidth;
|
||||
vDiff = wrapper.scrollHeight - wrapperHeight;
|
||||
|
||||
// vertical scroller
|
||||
if (vDiff > 0) {
|
||||
scrollClasses += " v-scroll";
|
||||
|
||||
if (vThreshold >= wrapperHeight) {
|
||||
vThreshold = 0;
|
||||
}
|
||||
|
||||
if (wrapper.scrollTop - vThreshold <= 0) {
|
||||
scrollClasses += " v-scroll-start";
|
||||
dispatch("vScrollStart");
|
||||
}
|
||||
|
||||
if (wrapper.scrollTop + vThreshold >= vDiff) {
|
||||
scrollClasses += " v-scroll-end";
|
||||
dispatch("vScrollEnd");
|
||||
}
|
||||
} else if (dispatchOnNoScroll) {
|
||||
dispatch("vScrollEnd");
|
||||
}
|
||||
|
||||
// horizontal scroller
|
||||
if (hDiff > 0) {
|
||||
scrollClasses += " h-scroll";
|
||||
|
||||
if (hThreshold >= wrapperWidth) {
|
||||
hThreshold = 0;
|
||||
}
|
||||
|
||||
if (wrapper.scrollLeft - hThreshold <= 0) {
|
||||
scrollClasses += " h-scroll-start";
|
||||
dispatch("hScrollStart");
|
||||
}
|
||||
if (wrapper.scrollLeft + hThreshold >= hDiff) {
|
||||
scrollClasses += " h-scroll-end";
|
||||
dispatch("hScrollEnd");
|
||||
}
|
||||
} else if (dispatchOnNoScroll) {
|
||||
dispatch("hScrollEnd");
|
||||
}
|
||||
}
|
||||
|
||||
export function throttleRefresh() {
|
||||
if (throttleTimeoutId) {
|
||||
return;
|
||||
}
|
||||
|
||||
throttleTimeoutId = setTimeout(() => {
|
||||
refresh();
|
||||
|
||||
throttleTimeoutId = null;
|
||||
}, 150);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
throttleRefresh();
|
||||
|
||||
observer = new MutationObserver(throttleRefresh);
|
||||
|
||||
observer.observe(wrapper, {
|
||||
attributeFilter: ["width", "height"],
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
clearTimeout(throttleTimeoutId);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={throttleRefresh} />
|
||||
|
||||
<div class="scroller-wrapper">
|
||||
<slot name="before" />
|
||||
|
||||
<div bind:this={wrapper} class="scroller {classes} {scrollClasses}" on:scroll={throttleRefresh}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<slot name="after" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scroller {
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.scroller-wrapper {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
:global(.scroller-wrapper .columns-dropdown) {
|
||||
top: 40px;
|
||||
z-index: 100;
|
||||
max-height: 340px;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import SortHeader from "@/components/base/SortHeader.svelte";
|
||||
import FormattedDate from "@/components/base/FormattedDate.svelte";
|
||||
import HorizontalScroller from "@/components/base/HorizontalScroller.svelte";
|
||||
import Scroller from "@/components/base/Scroller.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const labelMethodClass = {
|
||||
@@ -82,7 +82,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<HorizontalScroller class="table-wrapper">
|
||||
<Scroller class="table-wrapper">
|
||||
<table class="table" class:table-loading={isLoading}>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -211,7 +211,7 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</HorizontalScroller>
|
||||
</Scroller>
|
||||
|
||||
{#if items.length}
|
||||
<small class="block txt-hint txt-right m-t-sm">Showing {items.length} of {totalItems}</small>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import RecordUpsertPanel from "@/components/records/RecordUpsertPanel.svelte";
|
||||
import RecordPreviewPanel from "@/components/records/RecordPreviewPanel.svelte";
|
||||
import RecordsList from "@/components/records/RecordsList.svelte";
|
||||
import RecordsCount from "@/components/records/RecordsCount.svelte";
|
||||
|
||||
const queryParams = new URLSearchParams($querystring);
|
||||
|
||||
@@ -27,9 +28,11 @@
|
||||
let recordUpsertPanel;
|
||||
let recordPreviewPanel;
|
||||
let recordsList;
|
||||
let recordsCount;
|
||||
let filter = queryParams.get("filter") || "";
|
||||
let sort = queryParams.get("sort") || "-created";
|
||||
let selectedCollectionId = queryParams.get("collectionId") || $activeCollection?.id;
|
||||
let totalCount = 0; // used to manully change the count without the need of reloading the recordsCount component
|
||||
|
||||
$: reactiveParams = new URLSearchParams($querystring);
|
||||
|
||||
@@ -129,7 +132,7 @@
|
||||
{:else}
|
||||
<CollectionsSidebar />
|
||||
|
||||
<PageWrapper>
|
||||
<PageWrapper class="flex-content">
|
||||
<header class="page-header">
|
||||
<nav class="breadcrumbs">
|
||||
<div class="breadcrumb-item">Collections</div>
|
||||
@@ -176,7 +179,8 @@
|
||||
autocompleteCollection={$activeCollection}
|
||||
on:submit={(e) => (filter = e.detail)}
|
||||
/>
|
||||
<div class="clearfix m-b-base" />
|
||||
|
||||
<div class="clearfix m-b-sm" />
|
||||
|
||||
<RecordsList
|
||||
bind:this={recordsList}
|
||||
@@ -188,8 +192,21 @@
|
||||
? recordPreviewPanel.show(e?.detail)
|
||||
: recordUpsertPanel?.show(e?.detail);
|
||||
}}
|
||||
on:delete={() => {
|
||||
recordsCount?.reload();
|
||||
}}
|
||||
on:new={() => recordUpsertPanel?.show()}
|
||||
/>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<RecordsCount
|
||||
bind:this={recordsCount}
|
||||
class="m-r-auto txt-sm txt-hint"
|
||||
collection={$activeCollection}
|
||||
{filter}
|
||||
bind:totalCount
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</PageWrapper>
|
||||
{/if}
|
||||
|
||||
@@ -200,8 +217,16 @@
|
||||
<RecordUpsertPanel
|
||||
bind:this={recordUpsertPanel}
|
||||
collection={$activeCollection}
|
||||
on:save={() => recordsList?.reloadLoadedPages()}
|
||||
on:delete={() => recordsList?.reloadLoadedPages()}
|
||||
on:save={(e) => {
|
||||
recordsList?.reloadLoadedPages();
|
||||
if (e.detail?.isNew) {
|
||||
totalCount++;
|
||||
}
|
||||
}}
|
||||
on:delete={() => {
|
||||
recordsList?.reloadLoadedPages();
|
||||
totalCount--;
|
||||
}}
|
||||
/>
|
||||
|
||||
<RecordPreviewPanel bind:this={recordPreviewPanel} collection={$activeCollection} />
|
||||
|
||||
@@ -209,7 +209,10 @@
|
||||
replaceOriginal(result);
|
||||
}
|
||||
|
||||
dispatch("save", result);
|
||||
dispatch("save", {
|
||||
isNew: isNew,
|
||||
record: result,
|
||||
});
|
||||
} catch (err) {
|
||||
ApiClient.error(err);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let collection;
|
||||
export let filter = "";
|
||||
export let totalCount = 0;
|
||||
|
||||
let classes = undefined;
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
let isLoading = false;
|
||||
|
||||
$: if (collection?.id && filter !== -1) {
|
||||
reload();
|
||||
}
|
||||
|
||||
export async function reload() {
|
||||
if (!collection?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
totalCount = 0;
|
||||
|
||||
try {
|
||||
const fallbackSearchFields = CommonHelper.getAllCollectionIdentifiers(collection);
|
||||
|
||||
const result = await ApiClient.collection(collection.id).getList(1, 1, {
|
||||
filter: CommonHelper.normalizeSearchFilter(filter, fallbackSearchFields),
|
||||
fields: "id",
|
||||
requestKey: "records_count",
|
||||
});
|
||||
|
||||
totalCount = result.totalItems;
|
||||
dispatch("count", totalCount);
|
||||
isLoading = false;
|
||||
} catch (err) {
|
||||
if (!err?.isAbort) {
|
||||
isLoading = false;
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="inline-flex flex-gap-5 records-counter {classes}">
|
||||
<span class="txt">Total found:</span>
|
||||
<span class="txt">{!isLoading ? totalCount : "..."}</span>
|
||||
</div>
|
||||
@@ -12,19 +12,21 @@
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
import CopyIcon from "@/components/base/CopyIcon.svelte";
|
||||
import FormattedDate from "@/components/base/FormattedDate.svelte";
|
||||
import HorizontalScroller from "@/components/base/HorizontalScroller.svelte";
|
||||
import Scroller from "@/components/base/Scroller.svelte";
|
||||
import RecordFieldValue from "@/components/records/RecordFieldValue.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const sortRegex = /^([\+\-])?(\w+)$/;
|
||||
const perPage = 40;
|
||||
|
||||
export let collection;
|
||||
export let sort = "";
|
||||
export let filter = "";
|
||||
|
||||
let scrollWrapper;
|
||||
let records = [];
|
||||
let currentPage = 1;
|
||||
let totalRecords = 0;
|
||||
let lastTotal = 0;
|
||||
let bulkSelected = {};
|
||||
let isLoading = true;
|
||||
let isDeleting = false;
|
||||
@@ -44,6 +46,8 @@
|
||||
|
||||
$: fields = collection?.schema || [];
|
||||
|
||||
$: editorFields = fields.filter((field) => field.type === "editor");
|
||||
|
||||
$: relFields = fields.filter((field) => field.type === "relation");
|
||||
|
||||
$: visibleFields = fields.filter((field) => !hiddenColumns.includes(field.id));
|
||||
@@ -52,7 +56,7 @@
|
||||
load(1);
|
||||
}
|
||||
|
||||
$: canLoadMore = totalRecords > records.length;
|
||||
$: canLoadMore = lastTotal >= perPage;
|
||||
|
||||
$: totalBulkSelected = Object.keys(bulkSelected).length;
|
||||
|
||||
@@ -121,11 +125,11 @@
|
||||
// allow sorting by the relation display fields
|
||||
let listSort = sort;
|
||||
const sortMatch = listSort.match(sortRegex);
|
||||
const relField = sortMatch ? relFields.find((f) => f.name === sortMatch[2]) : null;
|
||||
if (sortMatch && relField) {
|
||||
const sortRelField = sortMatch ? relFields.find((f) => f.name === sortMatch[2]) : null;
|
||||
if (sortMatch && sortRelField) {
|
||||
const relPresentableFields =
|
||||
$collections
|
||||
?.find((c) => c.id == relField.options?.collectionId)
|
||||
?.find((c) => c.id == sortRelField.options?.collectionId)
|
||||
?.schema?.filter((f) => f.presentable)
|
||||
?.map((f) => f.name) || [];
|
||||
|
||||
@@ -140,12 +144,22 @@
|
||||
|
||||
const fallbackSearchFields = CommonHelper.getAllCollectionIdentifiers(collection);
|
||||
|
||||
const listFields = editorFields
|
||||
.map((f) => f.name + ":excerpt(200)")
|
||||
.concat(relFields.map((field) => "expand." + field.name + ".*:excerpt(200)"));
|
||||
if (listFields.length) {
|
||||
listFields.unshift("*");
|
||||
}
|
||||
|
||||
return ApiClient.collection(collection.id)
|
||||
.getList(page, 30, {
|
||||
.getList(page, perPage, {
|
||||
sort: listSort,
|
||||
skipTotal: 1,
|
||||
filter: CommonHelper.normalizeSearchFilter(filter, fallbackSearchFields),
|
||||
expand: relFields.map((field) => field.name).join(","),
|
||||
$cancelKey: "records_list",
|
||||
// @todo temp disable the :excerpt fields until individual RecordUpsert loader is implemented
|
||||
// fields: listFields.join(","),
|
||||
requestKey: "records_list",
|
||||
})
|
||||
.then(async (result) => {
|
||||
if (page <= 1) {
|
||||
@@ -154,7 +168,7 @@
|
||||
|
||||
isLoading = false;
|
||||
currentPage = result.page;
|
||||
totalRecords = result.totalItems;
|
||||
lastTotal = result.items.length;
|
||||
dispatch("load", records.concat(result.items));
|
||||
|
||||
// optimize the records listing by rendering the rows in task batches
|
||||
@@ -184,9 +198,10 @@
|
||||
}
|
||||
|
||||
function clearList() {
|
||||
scrollWrapper?.resetVerticalScroll();
|
||||
records = [];
|
||||
currentPage = 1;
|
||||
totalRecords = 0;
|
||||
lastTotal = 0;
|
||||
bulkSelected = {};
|
||||
}
|
||||
|
||||
@@ -244,6 +259,9 @@
|
||||
addSuccessToast(
|
||||
`Successfully deleted the selected ${totalBulkSelected === 1 ? "record" : "records"}.`
|
||||
);
|
||||
|
||||
dispatch("delete", bulkSelected);
|
||||
|
||||
deselectAllRecords();
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -258,7 +276,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<HorizontalScroller class="table-wrapper">
|
||||
<Scroller bind:this={scrollWrapper} class="table-wrapper">
|
||||
<svelte:fragment slot="before">
|
||||
{#if columnsTrigger}
|
||||
<Toggler
|
||||
@@ -517,27 +535,24 @@
|
||||
</tr>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if records.length && canLoadMore}
|
||||
<tr>
|
||||
<td colspan="99" class="txt-center">
|
||||
<button
|
||||
class="btn btn-expanded-lg btn-secondary"
|
||||
disabled={isLoading}
|
||||
class:btn-loading={isLoading}
|
||||
on:click|preventDefault={() => load(currentPage + 1)}
|
||||
>
|
||||
<span class="txt">Load more</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</HorizontalScroller>
|
||||
|
||||
{#if records.length}
|
||||
<small class="block txt-hint txt-right m-t-sm">Showing {records.length} of {totalRecords}</small>
|
||||
{/if}
|
||||
|
||||
{#if records.length && canLoadMore}
|
||||
<div class="block txt-center m-t-xs">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-lg btn-secondary btn-expanded"
|
||||
class:btn-loading={isLoading}
|
||||
class:btn-disabled={isLoading}
|
||||
on:click={() => load(currentPage + 1)}
|
||||
>
|
||||
<span class="txt">Load more ({totalRecords - records.length})</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</Scroller>
|
||||
|
||||
{#if totalBulkSelected}
|
||||
<div class="bulkbar" transition:fly={{ duration: 150, y: 5 }}>
|
||||
|
||||
@@ -330,11 +330,11 @@
|
||||
bind:this={upsertPanel}
|
||||
{collection}
|
||||
on:save={(e) => {
|
||||
CommonHelper.removeByKey(list, "id", e.detail.id);
|
||||
list.unshift(e.detail);
|
||||
CommonHelper.removeByKey(list, "id", e.detail.record.id);
|
||||
list.unshift(e.detail.record);
|
||||
list = list;
|
||||
|
||||
select(e.detail);
|
||||
select(e.detail.record);
|
||||
}}
|
||||
on:delete={(e) => {
|
||||
CommonHelper.removeByKey(list, "id", e.detail.id);
|
||||
|
||||
@@ -323,6 +323,10 @@ a,
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-gutter-stable {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.no-pointer-events {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.bulkbar {
|
||||
position: sticky;
|
||||
position: absolute;
|
||||
bottom: var(--baseSpacing);
|
||||
left: 50%;
|
||||
z-index: 101;
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
@@ -8,10 +9,11 @@
|
||||
align-items: center;
|
||||
width: var(--smWrapperWidth);
|
||||
max-width: 100%;
|
||||
margin: var(--smSpacing) auto;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px var(--smSpacing);
|
||||
border-radius: var(--btnHeight);
|
||||
background: var(--baseColor);
|
||||
border: 1px solid var(--baseAlt2Color);
|
||||
@include shadowize();
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
padding: calc(var(--baseSpacing) - 5px) var(--baseSpacing);
|
||||
padding: calc(var(--baseSpacing) - 5px) var(--baseSpacing) var(--smSpacing);
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
@@ -316,8 +316,7 @@
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
text-align: right;
|
||||
padding: 0px var(--baseSpacing) 15px;
|
||||
padding: 0px var(--baseSpacing) var(--smSpacing);
|
||||
color: var(--txtDisabledColor);
|
||||
font-size: var(--xsFontSize);
|
||||
line-height: var(--smLineHeight);
|
||||
@@ -343,12 +342,13 @@
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-gutter: stable;
|
||||
.overlay-active & {
|
||||
overflow-y: hidden; // prevent double scrollbar
|
||||
}
|
||||
&.full-page {
|
||||
scrollbar-gutter: auto;
|
||||
background: var(--baseColor);
|
||||
}
|
||||
&.center-content {
|
||||
@@ -357,4 +357,12 @@
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
&.flex-content {
|
||||
scrollbar-gutter: auto;
|
||||
.page-content {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
align-items: center;
|
||||
min-height: var(--searchHeight);
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
padding: 5px 7px;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
+24
-3
@@ -251,9 +251,11 @@ table {
|
||||
.table-wrapper {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
max-height: 100%;
|
||||
max-width: calc(100% + 2*var(--baseSpacing));
|
||||
margin-left: calc(var(--baseSpacing) * -1);
|
||||
margin-right: calc(var(--baseSpacing) * -1);
|
||||
border-bottom: 1px solid var(--baseAlt2Color);
|
||||
.bulk-select-col {
|
||||
min-width: 70px;
|
||||
}
|
||||
@@ -266,6 +268,20 @@ table {
|
||||
padding-right: calc(var(--baseSpacing) + 3px);
|
||||
}
|
||||
}
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
transition: box-shadow var(--baseAnimationSpeed);
|
||||
}
|
||||
tbody {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
tr:last-child td,
|
||||
tr:last-child th {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// horizontal scrolling
|
||||
.bulk-select-col,
|
||||
@@ -290,16 +306,21 @@ table {
|
||||
}
|
||||
|
||||
// scrolling styles
|
||||
&.scrollable {
|
||||
&.h-scroll {
|
||||
.bulk-select-col {
|
||||
box-shadow: 3px 0px 5px 0px var(--shadowColor);
|
||||
}
|
||||
.col-type-action {
|
||||
box-shadow: -3px 0px 5px 0px var(--shadowColor);
|
||||
}
|
||||
&.scroll-start .bulk-select-col,
|
||||
&.scroll-end .col-type-action {
|
||||
&.h-scroll-start .bulk-select-col,
|
||||
&.h-scroll-end .col-type-action {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
&.v-scroll:not(.v-scroll-start) {
|
||||
thead {
|
||||
box-shadow: 0px 2px 5px 0px var(--shadowColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user