@omnitend/dashboard-for-laravel 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/extended/DXTable.vue.d.ts +7 -0
- package/dist/components/extended/DXTableShell.vue.d.ts +24 -0
- package/dist/dashboard-for-laravel.js +7082 -7048
- package/dist/dashboard-for-laravel.js.map +1 -1
- package/dist/dashboard-for-laravel.umd.cjs +7 -7
- package/dist/dashboard-for-laravel.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/docs/public/api-reference.json +40 -4
- package/docs/public/docs-map.md +1 -1
- package/package.json +1 -1
- package/resources/js/components/base/DAutocomplete.vue +35 -0
- package/resources/js/components/extended/DXField.vue +39 -3
- package/resources/js/components/extended/DXTable.vue +47 -58
- package/resources/js/components/extended/DXTableShell.vue +54 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated": "2026-07-
|
|
2
|
+
"generated": "2026-07-05T08:20:04.640Z",
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@omnitend/dashboard-for-laravel",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.14.0"
|
|
6
6
|
},
|
|
7
7
|
"components": {
|
|
8
8
|
"base": [
|
|
@@ -2475,6 +2475,13 @@
|
|
|
2475
2475
|
"default": "\"12\"",
|
|
2476
2476
|
"description": "Column size (Bootstrap grid)"
|
|
2477
2477
|
},
|
|
2478
|
+
{
|
|
2479
|
+
"name": "card",
|
|
2480
|
+
"type": "boolean",
|
|
2481
|
+
"required": false,
|
|
2482
|
+
"default": "true",
|
|
2483
|
+
"description": "Wrap the table in a `DCard`. Defaults to `true`. Set `false` for a plain,\nborderless variant — the same header, filter row, table and pagination\nrendered directly on the page background (no card border/shadow), for\ndata-heavy admin index pages that read as busy inside a card."
|
|
2484
|
+
},
|
|
2478
2485
|
{
|
|
2479
2486
|
"name": "editFields",
|
|
2480
2487
|
"type": "Array",
|
|
@@ -2794,12 +2801,41 @@
|
|
|
2794
2801
|
}
|
|
2795
2802
|
],
|
|
2796
2803
|
"methods": []
|
|
2804
|
+
},
|
|
2805
|
+
{
|
|
2806
|
+
"name": "DXTableShell",
|
|
2807
|
+
"category": "extended",
|
|
2808
|
+
"filePath": "resources/js/components/extended/DXTableShell.vue",
|
|
2809
|
+
"description": "Internal chrome wrapper for `DXTable`. Renders its content either inside a `DCard` (the default) or as a plain, borderless block on the page background (`:card=\"false\"`). Extracted so `DXTable`'s ~460-line body and all its slots aren't duplicated across a card/plain `v-if`. Slots: - `#header` — the title / action row. In card mode it fills `DCard`'s header; in plain mode it renders above the table with a subtle bottom rule. - default — the table body (spinner/error/table/pagination).",
|
|
2810
|
+
"props": [
|
|
2811
|
+
{
|
|
2812
|
+
"name": "card",
|
|
2813
|
+
"type": "boolean",
|
|
2814
|
+
"required": false,
|
|
2815
|
+
"default": "true",
|
|
2816
|
+
"description": "Wrap the content in a `DCard`. When false, render plain/borderless."
|
|
2817
|
+
}
|
|
2818
|
+
],
|
|
2819
|
+
"events": [],
|
|
2820
|
+
"slots": [
|
|
2821
|
+
{
|
|
2822
|
+
"name": "header",
|
|
2823
|
+
"description": "",
|
|
2824
|
+
"bindings": []
|
|
2825
|
+
},
|
|
2826
|
+
{
|
|
2827
|
+
"name": "default",
|
|
2828
|
+
"description": "",
|
|
2829
|
+
"bindings": []
|
|
2830
|
+
}
|
|
2831
|
+
],
|
|
2832
|
+
"methods": []
|
|
2797
2833
|
}
|
|
2798
2834
|
]
|
|
2799
2835
|
},
|
|
2800
2836
|
"stats": {
|
|
2801
|
-
"totalComponents":
|
|
2837
|
+
"totalComponents": 71,
|
|
2802
2838
|
"baseComponents": 57,
|
|
2803
|
-
"extendedComponents":
|
|
2839
|
+
"extendedComponents": 14
|
|
2804
2840
|
}
|
|
2805
2841
|
}
|
package/docs/public/docs-map.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Documentation Map
|
|
2
2
|
|
|
3
3
|
> Auto-generated hierarchical overview of all documentation
|
|
4
|
-
> Last updated: 2026-07-
|
|
4
|
+
> Last updated: 2026-07-05T08:20:04.705Z
|
|
5
5
|
|
|
6
6
|
This file provides a complete map of all available documentation for AI agents and developers.
|
|
7
7
|
|
package/package.json
CHANGED
|
@@ -21,3 +21,38 @@ defineOptions({
|
|
|
21
21
|
</template>
|
|
22
22
|
</BAutocomplete>
|
|
23
23
|
</template>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
/*
|
|
27
|
+
BAutocomplete renders `.input-group > .b-autocomplete-input-wrapper +
|
|
28
|
+
.b-autocomplete-trigger`. The input wrapper takes the full width, so under
|
|
29
|
+
Bootstrap's default `flex-wrap: wrap` the trigger chevron wraps onto its own
|
|
30
|
+
line below the input. Keep the group on a single row and let the input wrapper
|
|
31
|
+
flex, so the trigger sits inline as an append. (#53)
|
|
32
|
+
*/
|
|
33
|
+
:deep(.input-group) {
|
|
34
|
+
flex-wrap: nowrap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
:deep(.b-autocomplete-input-wrapper) {
|
|
38
|
+
flex: 1 1 auto;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
The trigger renders as a heavy `btn btn-secondary` (dark), which reads as a
|
|
44
|
+
detached dark block. Soften it to a subtle bordered chevron that matches the
|
|
45
|
+
input and reads as a combobox affordance. Uses theme variables (no hardcoded
|
|
46
|
+
colours) and a more specific selector than `.btn-secondary` (no `!important`).
|
|
47
|
+
*/
|
|
48
|
+
:deep(.b-autocomplete-trigger) {
|
|
49
|
+
background-color: var(--bs-body-bg);
|
|
50
|
+
border-color: var(--bs-border-color);
|
|
51
|
+
color: var(--bs-secondary-color);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:deep(.b-autocomplete-trigger:hover) {
|
|
55
|
+
background-color: var(--bs-tertiary-bg);
|
|
56
|
+
color: var(--bs-body-color);
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
@@ -590,15 +590,51 @@ onBeforeUnmount(() => {
|
|
|
590
590
|
border-radius: var(--bs-border-radius);
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
-
/* Switch field:
|
|
594
|
-
|
|
593
|
+
/* Switch field: a full-width "filled box" toggle, the same height as a select
|
|
594
|
+
or text input, that colour-codes its state — a neutral box when off, a filled
|
|
595
|
+
success green when on. The label sits on the left and the toggle on the right.
|
|
596
|
+
Renders the underlying `.form-check` (from the switch checkbox) as the box. */
|
|
597
|
+
.dx-switch :deep(.form-check) {
|
|
598
|
+
display: flex;
|
|
599
|
+
align-items: center;
|
|
600
|
+
justify-content: space-between;
|
|
601
|
+
gap: 0.75rem;
|
|
602
|
+
width: 100%;
|
|
603
|
+
min-height: calc(1.5em + 0.75rem + 2px); /* match .form-control height */
|
|
604
|
+
margin: 0;
|
|
605
|
+
/* Override Bootstrap `.form-switch`'s 2.5em left padding for the floated input. */
|
|
606
|
+
padding: 0.375rem 0.75rem;
|
|
607
|
+
border: 1px solid var(--bs-border-color);
|
|
608
|
+
border-radius: var(--bs-border-radius);
|
|
609
|
+
background-color: var(--bs-body-bg);
|
|
610
|
+
transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/* Label on the left, toggle on the right (DOM order is input then label). */
|
|
595
614
|
.dx-switch :deep(.form-check-label) {
|
|
615
|
+
order: 1;
|
|
616
|
+
margin: 0;
|
|
596
617
|
color: var(--bs-secondary-color);
|
|
618
|
+
cursor: pointer;
|
|
597
619
|
transition: color 0.15s ease-in-out;
|
|
598
620
|
}
|
|
599
621
|
|
|
622
|
+
.dx-switch :deep(.form-check-input) {
|
|
623
|
+
order: 2;
|
|
624
|
+
flex-shrink: 0;
|
|
625
|
+
/* Override `.form-switch`'s negative left margin now that we're flex-laid-out. */
|
|
626
|
+
margin: 0;
|
|
627
|
+
cursor: pointer;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/* On-state: filled success green. */
|
|
631
|
+
.dx-switch--on :deep(.form-check) {
|
|
632
|
+
background-color: var(--bs-success-bg-subtle);
|
|
633
|
+
border-color: var(--bs-success);
|
|
634
|
+
}
|
|
635
|
+
|
|
600
636
|
.dx-switch--on :deep(.form-check-label) {
|
|
601
|
-
color: var(--bs-success);
|
|
637
|
+
color: var(--bs-success-text-emphasis);
|
|
602
638
|
font-weight: 500;
|
|
603
639
|
}
|
|
604
640
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<DContainer :fluid="fluid" :class="containerClass">
|
|
10
10
|
<DRow class="justify-content-center">
|
|
11
11
|
<DCol :md="columnSize">
|
|
12
|
-
<
|
|
12
|
+
<DXTableShell :card="card">
|
|
13
13
|
<template v-if="title || createUrl || $slots.header" #header>
|
|
14
14
|
<!-- @slot Card header content; overrides the default title heading and the "New {item}" button. -->
|
|
15
15
|
<slot name="header">
|
|
@@ -69,13 +69,15 @@
|
|
|
69
69
|
@update:model-value="handleFilterChange(field.key, $event as string)"
|
|
70
70
|
/>
|
|
71
71
|
|
|
72
|
-
<!-- Select Filter -->
|
|
73
|
-
<
|
|
72
|
+
<!-- Select Filter: typeahead — browse the full list on focus, or type to narrow; clear (✕) resets to "no filter" -->
|
|
73
|
+
<DAutocomplete
|
|
74
74
|
v-else-if="field.filter === 'select'"
|
|
75
75
|
:model-value="effectiveFilters[field.key] || ''"
|
|
76
|
-
:options="
|
|
76
|
+
:options="getFieldFilterOptions(field)"
|
|
77
|
+
:placeholder="field.filterPlaceholder || `All ${field.label || field.key}`"
|
|
77
78
|
size="sm"
|
|
78
|
-
|
|
79
|
+
open-on-focus
|
|
80
|
+
@update:model-value="handleFilterChange(field.key, ($event ?? '') as string)"
|
|
79
81
|
/>
|
|
80
82
|
|
|
81
83
|
<!-- Number Filter -->
|
|
@@ -165,13 +167,15 @@
|
|
|
165
167
|
@update:model-value="handleFilterChange(field.key, $event as string)"
|
|
166
168
|
/>
|
|
167
169
|
|
|
168
|
-
<!-- Select Filter -->
|
|
169
|
-
<
|
|
170
|
+
<!-- Select Filter: typeahead — browse the full list on focus, or type to narrow; clear (✕) resets to "no filter" -->
|
|
171
|
+
<DAutocomplete
|
|
170
172
|
v-else-if="field.filter === 'select'"
|
|
171
173
|
:model-value="effectiveFilters[field.key] || ''"
|
|
172
|
-
:options="
|
|
174
|
+
:options="getFieldFilterOptions(field)"
|
|
175
|
+
:placeholder="field.filterPlaceholder || `All ${field.label || field.key}`"
|
|
173
176
|
size="sm"
|
|
174
|
-
|
|
177
|
+
open-on-focus
|
|
178
|
+
@update:model-value="handleFilterChange(field.key, ($event ?? '') as string)"
|
|
175
179
|
/>
|
|
176
180
|
|
|
177
181
|
<!-- Number Filter -->
|
|
@@ -262,13 +266,15 @@
|
|
|
262
266
|
@update:model-value="handleFilterChange(field.key, $event as string)"
|
|
263
267
|
/>
|
|
264
268
|
|
|
265
|
-
<!-- Select Filter -->
|
|
266
|
-
<
|
|
269
|
+
<!-- Select Filter: typeahead — browse the full list on focus, or type to narrow; clear (✕) resets to "no filter" -->
|
|
270
|
+
<DAutocomplete
|
|
267
271
|
v-else-if="field.filter === 'select'"
|
|
268
272
|
:model-value="effectiveFilters[field.key] || ''"
|
|
269
|
-
:options="
|
|
273
|
+
:options="getFieldFilterOptions(field)"
|
|
274
|
+
:placeholder="field.filterPlaceholder || `All ${field.label || field.key}`"
|
|
270
275
|
size="sm"
|
|
271
|
-
|
|
276
|
+
open-on-focus
|
|
277
|
+
@update:model-value="handleFilterChange(field.key, ($event ?? '') as string)"
|
|
272
278
|
/>
|
|
273
279
|
|
|
274
280
|
<!-- Number Filter -->
|
|
@@ -473,7 +479,7 @@
|
|
|
473
479
|
</div>
|
|
474
480
|
</div>
|
|
475
481
|
</div>
|
|
476
|
-
</
|
|
482
|
+
</DXTableShell>
|
|
477
483
|
</DCol>
|
|
478
484
|
</DRow>
|
|
479
485
|
|
|
@@ -628,15 +634,17 @@ import { router } from "@inertiajs/vue3";
|
|
|
628
634
|
import axios from "axios";
|
|
629
635
|
import pluralize from "pluralize";
|
|
630
636
|
import { useToast } from "../../composables/useToast";
|
|
637
|
+
import { useForm } from "../../composables/useForm";
|
|
631
638
|
import DContainer from "../base/DContainer.vue";
|
|
632
639
|
import DRow from "../base/DRow.vue";
|
|
633
640
|
import DCol from "../base/DCol.vue";
|
|
634
|
-
import
|
|
641
|
+
import DXTableShell from "./DXTableShell.vue";
|
|
635
642
|
import DSpinner from "../base/DSpinner.vue";
|
|
636
643
|
import DTable from "../base/DTable.vue";
|
|
637
644
|
import DPagination from "../base/DPagination.vue";
|
|
638
645
|
import DFormInput from "../base/DFormInput.vue";
|
|
639
646
|
import DFormSelect from "../base/DFormSelect.vue";
|
|
647
|
+
import DAutocomplete from "../base/DAutocomplete.vue";
|
|
640
648
|
import DModal from "../base/DModal.vue";
|
|
641
649
|
import DButton from "../base/DButton.vue";
|
|
642
650
|
import DXForm from "./DXForm.vue";
|
|
@@ -785,6 +793,14 @@ export interface Props<TItem = any> {
|
|
|
785
793
|
/** Column size (Bootstrap grid) */
|
|
786
794
|
columnSize?: string | number;
|
|
787
795
|
|
|
796
|
+
/**
|
|
797
|
+
* Wrap the table in a `DCard`. Defaults to `true`. Set `false` for a plain,
|
|
798
|
+
* borderless variant — the same header, filter row, table and pagination
|
|
799
|
+
* rendered directly on the page background (no card border/shadow), for
|
|
800
|
+
* data-heavy admin index pages that read as busy inside a card.
|
|
801
|
+
*/
|
|
802
|
+
card?: boolean;
|
|
803
|
+
|
|
788
804
|
// Edit Modal Props
|
|
789
805
|
/** Form field definitions for edit modal (if provided, enables edit on row click) */
|
|
790
806
|
editFields?: any[]; // FieldDefinition[] - using any to avoid circular import
|
|
@@ -855,6 +871,7 @@ const props = withDefaults(defineProps<Props<T>>(), {
|
|
|
855
871
|
fluid: false,
|
|
856
872
|
containerClass: "py-5",
|
|
857
873
|
columnSize: "12",
|
|
874
|
+
card: true,
|
|
858
875
|
editModalSize: "lg",
|
|
859
876
|
});
|
|
860
877
|
|
|
@@ -1501,12 +1518,6 @@ const editLoading = ref(false);
|
|
|
1501
1518
|
// the form after the user has since opened a different row.
|
|
1502
1519
|
let editFetchToken = 0;
|
|
1503
1520
|
|
|
1504
|
-
// Monotonic token for opening the modal itself. The first edit dynamically
|
|
1505
|
-
// imports useForm; if the user clicks row A then row B before that import
|
|
1506
|
-
// resolves, A's callback must not seed/open/fetch over B. Each open captures
|
|
1507
|
-
// the current token and bails if it's since been superseded.
|
|
1508
|
-
let modalOpenToken = 0;
|
|
1509
|
-
|
|
1510
1521
|
// Toast (may not be available in test environment)
|
|
1511
1522
|
let createToast: ((obj: any) => any) | undefined;
|
|
1512
1523
|
try {
|
|
@@ -1587,40 +1598,27 @@ const handleRowClick = (item: T, index: number, event: MouseEvent) => {
|
|
|
1587
1598
|
// Reset to first tab
|
|
1588
1599
|
activeTabIndex.value = 0;
|
|
1589
1600
|
|
|
1590
|
-
//
|
|
1591
|
-
//
|
|
1592
|
-
const openToken = ++modalOpenToken;
|
|
1593
|
-
|
|
1594
|
-
// Initialize form with item data
|
|
1601
|
+
// Initialize form with item data. `useForm` is statically imported, so
|
|
1602
|
+
// seeding is synchronous — no interleaving between successive row opens.
|
|
1595
1603
|
if (!editForm.value) {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
const formData: Record<string, any> = {};
|
|
1600
|
-
props.editFields!.forEach(field => {
|
|
1601
|
-
formData[field.key] = (item as any)[field.key] ?? field.default ?? '';
|
|
1602
|
-
});
|
|
1603
|
-
editForm.value = useForm(formData);
|
|
1604
|
-
|
|
1605
|
-
// Open modal
|
|
1606
|
-
showEditModal.value = true;
|
|
1607
|
-
|
|
1608
|
-
// Optionally replace the row-seeded data with the full record.
|
|
1609
|
-
if (props.showUrl) void fetchFullRecordForEdit(item);
|
|
1604
|
+
const formData: Record<string, any> = {};
|
|
1605
|
+
props.editFields!.forEach(field => {
|
|
1606
|
+
formData[field.key] = (item as any)[field.key] ?? field.default ?? '';
|
|
1610
1607
|
});
|
|
1608
|
+
editForm.value = useForm(formData);
|
|
1611
1609
|
} else {
|
|
1612
1610
|
// Update existing form
|
|
1613
1611
|
props.editFields.forEach(field => {
|
|
1614
1612
|
editForm.value.data[field.key] = (item as any)[field.key] ?? field.default ?? '';
|
|
1615
1613
|
});
|
|
1616
1614
|
editForm.value.clearErrors();
|
|
1615
|
+
}
|
|
1617
1616
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1617
|
+
// Open modal
|
|
1618
|
+
showEditModal.value = true;
|
|
1620
1619
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
}
|
|
1620
|
+
// Optionally replace the row-seeded data with the full record.
|
|
1621
|
+
if (props.showUrl) void fetchFullRecordForEdit(item);
|
|
1624
1622
|
}
|
|
1625
1623
|
};
|
|
1626
1624
|
|
|
@@ -1673,34 +1671,25 @@ const handleCreateNew = () => {
|
|
|
1673
1671
|
selectedItem.value = null;
|
|
1674
1672
|
activeTabIndex.value = 0;
|
|
1675
1673
|
|
|
1676
|
-
// Supersede any in-flight row-open
|
|
1677
|
-
//
|
|
1678
|
-
const openToken = ++modalOpenToken;
|
|
1674
|
+
// Supersede any in-flight row-open fetch so a slow showUrl fetch from an
|
|
1675
|
+
// earlier edit can't seed over this create.
|
|
1679
1676
|
editFetchToken++;
|
|
1680
1677
|
editLoading.value = false;
|
|
1681
1678
|
|
|
1682
|
-
|
|
1679
|
+
if (!editForm.value) {
|
|
1683
1680
|
const formData: Record<string, any> = {};
|
|
1684
1681
|
props.editFields!.forEach(field => {
|
|
1685
1682
|
formData[field.key] = field.default ?? '';
|
|
1686
1683
|
});
|
|
1687
1684
|
editForm.value = useForm(formData);
|
|
1688
|
-
showEditModal.value = true;
|
|
1689
|
-
};
|
|
1690
|
-
|
|
1691
|
-
if (!editForm.value) {
|
|
1692
|
-
import('../../composables/useForm').then(({ useForm }) => {
|
|
1693
|
-
if (openToken !== modalOpenToken) return; // superseded
|
|
1694
|
-
initForm(useForm);
|
|
1695
|
-
});
|
|
1696
1685
|
} else {
|
|
1697
1686
|
// Reset existing form to defaults
|
|
1698
1687
|
props.editFields!.forEach(field => {
|
|
1699
1688
|
editForm.value.data[field.key] = field.default ?? '';
|
|
1700
1689
|
});
|
|
1701
1690
|
editForm.value.clearErrors();
|
|
1702
|
-
showEditModal.value = true;
|
|
1703
1691
|
}
|
|
1692
|
+
showEditModal.value = true;
|
|
1704
1693
|
};
|
|
1705
1694
|
|
|
1706
1695
|
// Handle save from edit modal
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Internal chrome wrapper for `DXTable`. Renders its content either inside a
|
|
4
|
+
`DCard` (the default) or as a plain, borderless block on the page background
|
|
5
|
+
(`:card="false"`). Extracted so `DXTable`'s ~460-line body and all its slots
|
|
6
|
+
aren't duplicated across a card/plain `v-if`.
|
|
7
|
+
|
|
8
|
+
Slots:
|
|
9
|
+
- `#header` — the title / action row. In card mode it fills `DCard`'s header;
|
|
10
|
+
in plain mode it renders above the table with a subtle bottom rule.
|
|
11
|
+
- default — the table body (spinner/error/table/pagination).
|
|
12
|
+
-->
|
|
13
|
+
<template>
|
|
14
|
+
<DCard v-if="card">
|
|
15
|
+
<template v-if="$slots.header" #header>
|
|
16
|
+
<slot name="header" />
|
|
17
|
+
</template>
|
|
18
|
+
<slot />
|
|
19
|
+
</DCard>
|
|
20
|
+
|
|
21
|
+
<div v-else class="dx-table-plain">
|
|
22
|
+
<div v-if="$slots.header" class="dx-table-plain__header">
|
|
23
|
+
<slot name="header" />
|
|
24
|
+
</div>
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import DCard from '../base/DCard.vue';
|
|
31
|
+
|
|
32
|
+
interface Props {
|
|
33
|
+
/** Wrap the content in a `DCard`. When false, render plain/borderless. */
|
|
34
|
+
card?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
withDefaults(defineProps<Props>(), {
|
|
38
|
+
card: true,
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style scoped>
|
|
43
|
+
/*
|
|
44
|
+
Plain variant: no card border/shadow/background — the table sits directly on
|
|
45
|
+
the page. The header row gets a subtle bottom rule so it still reads as a
|
|
46
|
+
distinct band above the table (mirrors the card header's separation without
|
|
47
|
+
the full card chrome).
|
|
48
|
+
*/
|
|
49
|
+
.dx-table-plain__header {
|
|
50
|
+
padding-bottom: 0.75rem;
|
|
51
|
+
margin-bottom: 0.75rem;
|
|
52
|
+
border-bottom: 1px solid var(--bs-border-color);
|
|
53
|
+
}
|
|
54
|
+
</style>
|