mgv-backoffice 1.37.0 → 1.40.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/README.md +147 -4
- package/dist/components/BaseAuthLayout.vue.d.ts +27 -0
- package/dist/components/BaseBooleanBadge.vue.d.ts +12 -0
- package/dist/components/BaseCard.vue.d.ts +33 -0
- package/dist/components/BaseDetailList.vue.d.ts +13 -0
- package/dist/components/BaseField.vue.d.ts +38 -0
- package/dist/components/BasePageHeader.vue.d.ts +3 -0
- package/dist/components/BaseSearchSelect.vue.d.ts +41 -0
- package/dist/components/BaseStatCard.vue.d.ts +33 -0
- package/dist/components/BaseTable.vue.d.ts +3 -0
- package/dist/components/BaseToggle.vue.d.ts +33 -0
- package/dist/index.d.ts +9 -0
- package/dist/types/detail.d.ts +10 -0
- package/dist/ui-lib.css +1 -1
- package/dist/ui-lib.js +1388 -881
- package/dist/ui-lib.umd.cjs +1 -1
- package/package.json +1 -1
- package/src/components/BaseAuthLayout.test.ts +48 -0
- package/src/components/BaseAuthLayout.vue +50 -0
- package/src/components/BaseBooleanBadge.vue +33 -0
- package/src/components/BaseBreadcrumb.vue +68 -68
- package/src/components/BaseButton.vue +1 -1
- package/src/components/BaseCard.test.ts +55 -0
- package/src/components/BaseCard.vue +48 -0
- package/src/components/BaseDetailList.test.ts +36 -0
- package/src/components/BaseDetailList.vue +43 -0
- package/src/components/BaseField.vue +47 -0
- package/src/components/BaseInput.test.ts +1 -1
- package/src/components/BaseInput.vue +46 -46
- package/src/components/BaseLogo.test.ts +26 -0
- package/src/components/BaseLogo.vue +7 -1
- package/src/components/BasePageHeader.vue +13 -2
- package/src/components/BaseSearchSelect.test.ts +117 -0
- package/src/components/BaseSearchSelect.vue +318 -0
- package/src/components/BaseSelect.test.ts +3 -3
- package/src/components/BaseSelect.vue +62 -46
- package/src/components/BaseStatCard.test.ts +40 -0
- package/src/components/BaseStatCard.vue +103 -0
- package/src/components/BaseTable.test.ts +69 -55
- package/src/components/BaseTable.vue +62 -50
- package/src/components/BaseToggle.test.ts +39 -0
- package/src/components/BaseToggle.vue +73 -0
- package/src/components/Pagination.test.ts +93 -85
- package/src/components/Pagination.vue +87 -82
- package/src/index.ts +9 -0
- package/src/style.css +58 -58
- package/src/types/detail.ts +10 -0
|
@@ -1,85 +1,93 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
3
|
-
import Pagination from './Pagination.vue'
|
|
4
|
-
|
|
5
|
-
/** Visible cell labels in DOM order ('…' for the ellipsis span). */
|
|
6
|
-
function cellLabels(wrapper: ReturnType<typeof mount>): string[] {
|
|
7
|
-
return wrapper.findAll('li').map(li => li.text().trim())
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async function clickPage(wrapper: ReturnType<typeof mount>, page: number) {
|
|
11
|
-
const link = wrapper.findAll('button').find(b => b.text().trim() === String(page))
|
|
12
|
-
expect(link, `expected a visible cell for page ${page}`).toBeTruthy()
|
|
13
|
-
await link!.trigger('click')
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('Pagination', () => {
|
|
17
|
-
it('renders every page without an ellipsis when totalPages <= 5', () => {
|
|
18
|
-
const wrapper = mount(Pagination, {
|
|
19
|
-
props: { totalItems: 100, itemsPerPage: 20 },
|
|
20
|
-
})
|
|
21
|
-
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '4', '5'])
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('renders a window with a trailing ellipsis on the first page', () => {
|
|
25
|
-
const wrapper = mount(Pagination, {
|
|
26
|
-
props: { totalItems: 200, itemsPerPage: 20 },
|
|
27
|
-
})
|
|
28
|
-
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '...', '10'])
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('keeps cells in ascending order around a middle page', async () => {
|
|
32
|
-
const wrapper = mount(Pagination, {
|
|
33
|
-
props: { totalItems: 200, itemsPerPage: 20 },
|
|
34
|
-
})
|
|
35
|
-
await clickPage(wrapper, 3)
|
|
36
|
-
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '4', '5', '...', '10'])
|
|
37
|
-
|
|
38
|
-
await clickPage(wrapper, 5)
|
|
39
|
-
expect(cellLabels(wrapper)).toEqual(['1', '...', '3', '4', '5', '6', '7', '...', '10'])
|
|
40
|
-
|
|
41
|
-
expect(wrapper.emitted('page-changed')).toEqual([[3], [5]])
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('omits the ellipsis when no pages are hidden next to the edges', async () => {
|
|
45
|
-
const wrapper = mount(Pagination, {
|
|
46
|
-
props: { totalItems: 200, itemsPerPage: 20 },
|
|
47
|
-
})
|
|
48
|
-
await clickPage(wrapper, 3)
|
|
49
|
-
await clickPage(wrapper, 4)
|
|
50
|
-
// window is 2..6, so only page 1 precedes it — no gap, no ellipsis
|
|
51
|
-
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '4', '5', '6', '...', '10'])
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('renders the trailing window on the last page', async () => {
|
|
55
|
-
const wrapper = mount(Pagination, {
|
|
56
|
-
props: { totalItems: 200, itemsPerPage: 20 },
|
|
57
|
-
})
|
|
58
|
-
await clickPage(wrapper, 3)
|
|
59
|
-
await clickPage(wrapper, 5)
|
|
60
|
-
await clickPage(wrapper, 7)
|
|
61
|
-
await clickPage(wrapper, 9)
|
|
62
|
-
await clickPage(wrapper, 10)
|
|
63
|
-
expect(cellLabels(wrapper)).toEqual(['1', '...', '8', '9', '10'])
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('does not emit when the active page is clicked again', async () => {
|
|
67
|
-
const wrapper = mount(Pagination, {
|
|
68
|
-
props: { totalItems: 200, itemsPerPage: 20 },
|
|
69
|
-
})
|
|
70
|
-
await clickPage(wrapper, 1)
|
|
71
|
-
expect(wrapper.emitted('page-changed')).toBeUndefined()
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('clamps the current page when the item count shrinks', async () => {
|
|
75
|
-
const wrapper = mount(Pagination, {
|
|
76
|
-
props: { totalItems: 200, itemsPerPage: 20 },
|
|
77
|
-
})
|
|
78
|
-
await clickPage(wrapper, 3)
|
|
79
|
-
await clickPage(wrapper, 5)
|
|
80
|
-
await clickPage(wrapper, 7)
|
|
81
|
-
await wrapper.setProps({ totalItems: 40 })
|
|
82
|
-
expect(cellLabels(wrapper)).toEqual(['1', '2'])
|
|
83
|
-
expect(wrapper.find('button.active').text()).toBe('2')
|
|
84
|
-
})
|
|
85
|
-
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import Pagination from './Pagination.vue'
|
|
4
|
+
|
|
5
|
+
/** Visible cell labels in DOM order ('…' for the ellipsis span). */
|
|
6
|
+
function cellLabels(wrapper: ReturnType<typeof mount>): string[] {
|
|
7
|
+
return wrapper.findAll('li').map(li => li.text().trim())
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function clickPage(wrapper: ReturnType<typeof mount>, page: number) {
|
|
11
|
+
const link = wrapper.findAll('button').find(b => b.text().trim() === String(page))
|
|
12
|
+
expect(link, `expected a visible cell for page ${page}`).toBeTruthy()
|
|
13
|
+
await link!.trigger('click')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('Pagination', () => {
|
|
17
|
+
it('renders every page without an ellipsis when totalPages <= 5', () => {
|
|
18
|
+
const wrapper = mount(Pagination, {
|
|
19
|
+
props: { totalItems: 100, itemsPerPage: 20 },
|
|
20
|
+
})
|
|
21
|
+
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '4', '5'])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('renders a window with a trailing ellipsis on the first page', () => {
|
|
25
|
+
const wrapper = mount(Pagination, {
|
|
26
|
+
props: { totalItems: 200, itemsPerPage: 20 },
|
|
27
|
+
})
|
|
28
|
+
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '...', '10'])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('keeps cells in ascending order around a middle page', async () => {
|
|
32
|
+
const wrapper = mount(Pagination, {
|
|
33
|
+
props: { totalItems: 200, itemsPerPage: 20 },
|
|
34
|
+
})
|
|
35
|
+
await clickPage(wrapper, 3)
|
|
36
|
+
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '4', '5', '...', '10'])
|
|
37
|
+
|
|
38
|
+
await clickPage(wrapper, 5)
|
|
39
|
+
expect(cellLabels(wrapper)).toEqual(['1', '...', '3', '4', '5', '6', '7', '...', '10'])
|
|
40
|
+
|
|
41
|
+
expect(wrapper.emitted('page-changed')).toEqual([[3], [5]])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('omits the ellipsis when no pages are hidden next to the edges', async () => {
|
|
45
|
+
const wrapper = mount(Pagination, {
|
|
46
|
+
props: { totalItems: 200, itemsPerPage: 20 },
|
|
47
|
+
})
|
|
48
|
+
await clickPage(wrapper, 3)
|
|
49
|
+
await clickPage(wrapper, 4)
|
|
50
|
+
// window is 2..6, so only page 1 precedes it — no gap, no ellipsis
|
|
51
|
+
expect(cellLabels(wrapper)).toEqual(['1', '2', '3', '4', '5', '6', '...', '10'])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('renders the trailing window on the last page', async () => {
|
|
55
|
+
const wrapper = mount(Pagination, {
|
|
56
|
+
props: { totalItems: 200, itemsPerPage: 20 },
|
|
57
|
+
})
|
|
58
|
+
await clickPage(wrapper, 3)
|
|
59
|
+
await clickPage(wrapper, 5)
|
|
60
|
+
await clickPage(wrapper, 7)
|
|
61
|
+
await clickPage(wrapper, 9)
|
|
62
|
+
await clickPage(wrapper, 10)
|
|
63
|
+
expect(cellLabels(wrapper)).toEqual(['1', '...', '8', '9', '10'])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('does not emit when the active page is clicked again', async () => {
|
|
67
|
+
const wrapper = mount(Pagination, {
|
|
68
|
+
props: { totalItems: 200, itemsPerPage: 20 },
|
|
69
|
+
})
|
|
70
|
+
await clickPage(wrapper, 1)
|
|
71
|
+
expect(wrapper.emitted('page-changed')).toBeUndefined()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('clamps the current page when the item count shrinks', async () => {
|
|
75
|
+
const wrapper = mount(Pagination, {
|
|
76
|
+
props: { totalItems: 200, itemsPerPage: 20 },
|
|
77
|
+
})
|
|
78
|
+
await clickPage(wrapper, 3)
|
|
79
|
+
await clickPage(wrapper, 5)
|
|
80
|
+
await clickPage(wrapper, 7)
|
|
81
|
+
await wrapper.setProps({ totalItems: 40 })
|
|
82
|
+
expect(cellLabels(wrapper)).toEqual(['1', '2'])
|
|
83
|
+
expect(wrapper.find('button.active').text()).toBe('2')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('falls back to 20 per page when itemsPerPage is null or 0 (no "Infinity")', () => {
|
|
87
|
+
for (const itemsPerPage of [null, 0]) {
|
|
88
|
+
const wrapper = mount(Pagination, { props: { totalItems: 16, itemsPerPage: itemsPerPage as unknown as number } })
|
|
89
|
+
expect(wrapper.text()).not.toContain('Infinity')
|
|
90
|
+
expect(wrapper.findAll('button').map((b) => b.text()).filter((t) => /^\d+$/.test(t))).toEqual(['1'])
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -1,82 +1,87 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<nav aria-label="Pagination">
|
|
3
|
-
<ul class="flex list-none p-0 m-0">
|
|
4
|
-
<li v-for="(cell, index) in cells" :key="index" class="mr-1">
|
|
5
|
-
<button
|
|
6
|
-
v-if="cell !== 0"
|
|
7
|
-
type="button"
|
|
8
|
-
:aria-current="cell === currentPage ? 'page' : undefined"
|
|
9
|
-
:aria-label="`Go to page ${cell}`"
|
|
10
|
-
@click="changePage(cell)"
|
|
11
|
-
class="inline-block min-w-[44px] border border-gray-300 dark:border-gray-600 rounded-lg p-3 text-black dark:text-gray-200 cursor-pointer transition-colors hover:bg-gray-300 dark:hover:bg-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
|
|
12
|
-
:class="{ 'active font-bold bg-gray-200 dark:bg-gray-700': cell === currentPage }"
|
|
13
|
-
>{{ cell }}</button>
|
|
14
|
-
<span v-else aria-hidden="true" class="inline-block border border-gray-300 dark:border-gray-600 rounded-lg p-3 text-black dark:text-gray-200 cursor-not-allowed">...</span>
|
|
15
|
-
</li>
|
|
16
|
-
</ul>
|
|
17
|
-
</nav>
|
|
18
|
-
</template>
|
|
19
|
-
|
|
20
|
-
<script lang="ts" setup>
|
|
21
|
-
import { ref, watch } from "vue";
|
|
22
|
-
|
|
23
|
-
const props = defineProps({
|
|
24
|
-
totalItems: {
|
|
25
|
-
type: Number,
|
|
26
|
-
required: true
|
|
27
|
-
},
|
|
28
|
-
itemsPerPage: {
|
|
29
|
-
type: Number,
|
|
30
|
-
default: 20
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const currentPage = ref(1);
|
|
35
|
-
const totalPages = ref(0);
|
|
36
|
-
const cells = ref([] as number[]);
|
|
37
|
-
const emit = defineEmits(['page-changed']);
|
|
38
|
-
|
|
39
|
-
// Rebuild the visible cells around `page`. A cell value of 0 renders as
|
|
40
|
-
// an ellipsis. The window is the current page ± 2 neighbours, plus the
|
|
41
|
-
// first/last page with an ellipsis only when pages are actually hidden.
|
|
42
|
-
function buildCells(page: number) {
|
|
43
|
-
if (totalPages.value <= 5) {
|
|
44
|
-
cells.value = Array.from({ length: totalPages.value }, (_, i) => i + 1);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const out: number[] = [];
|
|
49
|
-
const start = Math.max(1, page - 2);
|
|
50
|
-
const end = Math.min(totalPages.value, page + 2);
|
|
51
|
-
|
|
52
|
-
if (start > 1) {
|
|
53
|
-
out.push(1);
|
|
54
|
-
if (start > 2) out.push(0);
|
|
55
|
-
}
|
|
56
|
-
for (let i = start; i <= end; i++) {
|
|
57
|
-
out.push(i);
|
|
58
|
-
}
|
|
59
|
-
if (end < totalPages.value) {
|
|
60
|
-
if (end < totalPages.value - 1) out.push(0);
|
|
61
|
-
out.push(totalPages.value);
|
|
62
|
-
}
|
|
63
|
-
cells.value = out;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function changePage(pageNumber: number) {
|
|
67
|
-
if (pageNumber === currentPage.value || pageNumber < 1 || pageNumber > totalPages.value) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
currentPage.value = pageNumber;
|
|
71
|
-
buildCells(pageNumber);
|
|
72
|
-
emit('page-changed', pageNumber);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
watch(() => [props.totalItems, props.itemsPerPage], () => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<nav aria-label="Pagination">
|
|
3
|
+
<ul class="flex list-none p-0 m-0">
|
|
4
|
+
<li v-for="(cell, index) in cells" :key="index" class="mr-1">
|
|
5
|
+
<button
|
|
6
|
+
v-if="cell !== 0"
|
|
7
|
+
type="button"
|
|
8
|
+
:aria-current="cell === currentPage ? 'page' : undefined"
|
|
9
|
+
:aria-label="`Go to page ${cell}`"
|
|
10
|
+
@click="changePage(cell)"
|
|
11
|
+
class="inline-block min-w-[44px] border border-gray-300 dark:border-gray-600 rounded-lg p-3 text-black dark:text-gray-200 cursor-pointer transition-colors hover:bg-gray-300 dark:hover:bg-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
|
|
12
|
+
:class="{ 'active font-bold bg-gray-200 dark:bg-gray-700': cell === currentPage }"
|
|
13
|
+
>{{ cell }}</button>
|
|
14
|
+
<span v-else aria-hidden="true" class="inline-block border border-gray-300 dark:border-gray-600 rounded-lg p-3 text-black dark:text-gray-200 cursor-not-allowed">...</span>
|
|
15
|
+
</li>
|
|
16
|
+
</ul>
|
|
17
|
+
</nav>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script lang="ts" setup>
|
|
21
|
+
import { ref, watch } from "vue";
|
|
22
|
+
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
totalItems: {
|
|
25
|
+
type: Number,
|
|
26
|
+
required: true
|
|
27
|
+
},
|
|
28
|
+
itemsPerPage: {
|
|
29
|
+
type: Number,
|
|
30
|
+
default: 20
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const currentPage = ref(1);
|
|
35
|
+
const totalPages = ref(0);
|
|
36
|
+
const cells = ref([] as number[]);
|
|
37
|
+
const emit = defineEmits(['page-changed']);
|
|
38
|
+
|
|
39
|
+
// Rebuild the visible cells around `page`. A cell value of 0 renders as
|
|
40
|
+
// an ellipsis. The window is the current page ± 2 neighbours, plus the
|
|
41
|
+
// first/last page with an ellipsis only when pages are actually hidden.
|
|
42
|
+
function buildCells(page: number) {
|
|
43
|
+
if (totalPages.value <= 5) {
|
|
44
|
+
cells.value = Array.from({ length: totalPages.value }, (_, i) => i + 1);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const out: number[] = [];
|
|
49
|
+
const start = Math.max(1, page - 2);
|
|
50
|
+
const end = Math.min(totalPages.value, page + 2);
|
|
51
|
+
|
|
52
|
+
if (start > 1) {
|
|
53
|
+
out.push(1);
|
|
54
|
+
if (start > 2) out.push(0);
|
|
55
|
+
}
|
|
56
|
+
for (let i = start; i <= end; i++) {
|
|
57
|
+
out.push(i);
|
|
58
|
+
}
|
|
59
|
+
if (end < totalPages.value) {
|
|
60
|
+
if (end < totalPages.value - 1) out.push(0);
|
|
61
|
+
out.push(totalPages.value);
|
|
62
|
+
}
|
|
63
|
+
cells.value = out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function changePage(pageNumber: number) {
|
|
67
|
+
if (pageNumber === currentPage.value || pageNumber < 1 || pageNumber > totalPages.value) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
currentPage.value = pageNumber;
|
|
71
|
+
buildCells(pageNumber);
|
|
72
|
+
emit('page-changed', pageNumber);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
watch(() => [props.totalItems, props.itemsPerPage], () => {
|
|
76
|
+
// Guard against a missing/zero page size or total (e.g. a reset form that
|
|
77
|
+
// nulled them): fall back to 20 per page / 0 items instead of rendering
|
|
78
|
+
// "Infinity" or "NaN" pages.
|
|
79
|
+
const perPage = Number(props.itemsPerPage) > 0 ? Number(props.itemsPerPage) : 20;
|
|
80
|
+
const total = Number(props.totalItems) > 0 ? Number(props.totalItems) : 0;
|
|
81
|
+
totalPages.value = Math.ceil(total / perPage);
|
|
82
|
+
if (currentPage.value > totalPages.value) {
|
|
83
|
+
currentPage.value = Math.max(1, totalPages.value);
|
|
84
|
+
}
|
|
85
|
+
buildCells(currentPage.value);
|
|
86
|
+
}, { immediate: true })
|
|
87
|
+
</script>
|
package/src/index.ts
CHANGED
|
@@ -56,6 +56,15 @@ export type { CredentialsView, CredentialsUpdate } from './components/BaseCreden
|
|
|
56
56
|
// Components — third-party wrappers
|
|
57
57
|
export { default as BaseDateTimePicker } from './components/BaseDateTimePicker.vue'
|
|
58
58
|
export type { DateTimePickerMode } from './components/BaseDateTimePicker.vue'
|
|
59
|
+
export { default as BaseSearchSelect } from './components/BaseSearchSelect.vue'
|
|
60
|
+
export { default as BaseCard } from './components/BaseCard.vue'
|
|
61
|
+
export { default as BaseField } from './components/BaseField.vue'
|
|
62
|
+
export { default as BaseStatCard } from './components/BaseStatCard.vue'
|
|
63
|
+
export { default as BaseToggle } from './components/BaseToggle.vue'
|
|
64
|
+
export { default as BaseAuthLayout } from './components/BaseAuthLayout.vue'
|
|
65
|
+
export { default as BaseBooleanBadge } from './components/BaseBooleanBadge.vue'
|
|
66
|
+
export { default as BaseDetailList } from './components/BaseDetailList.vue'
|
|
67
|
+
export type { DetailItem } from './types/detail'
|
|
59
68
|
|
|
60
69
|
// Composables
|
|
61
70
|
export { useTheme, initTheme } from './composables/useTheme'
|
package/src/style.css
CHANGED
|
@@ -30,61 +30,61 @@
|
|
|
30
30
|
body {
|
|
31
31
|
font-family: var(--font-sans);
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
/* BaseDateTimePicker skin: re-point @vuepic/vue-datepicker's CSS variables at
|
|
35
|
-
the lib palette (emerald accent, slate surfaces — same as BaseInput). The
|
|
36
|
-
picker is third-party markup, so its variables are the only styling hook;
|
|
37
|
-
Tailwind utilities can't reach inside it. `html` prefix out-specifies the
|
|
38
|
-
package's own `.dp--theme-*` blocks regardless of stylesheet order. */
|
|
39
|
-
html .dp--theme-light,
|
|
40
|
-
html .dp--theme-dark {
|
|
41
|
-
--dp-font-family: var(--font-sans);
|
|
42
|
-
--dp-font-size: 0.875rem;
|
|
43
|
-
--dp-border-radius: 0.
|
|
44
|
-
--dp-cell-border-radius: 0.25rem;
|
|
45
|
-
--dp-input-padding: 8px 30px 8px 12px;
|
|
46
|
-
--dp-primary-text-color: #fff;
|
|
47
|
-
}
|
|
48
|
-
html .dp--theme-light {
|
|
49
|
-
--dp-background-color: #
|
|
50
|
-
--dp-text-color: #0f172a; /* slate-900 */
|
|
51
|
-
--dp-hover-color: #f1f5f9; /* slate-100 */
|
|
52
|
-
--dp-hover-text-color: #0f172a;
|
|
53
|
-
--dp-hover-icon-color: #64748b; /* slate-500 */
|
|
54
|
-
--dp-icon-color: #94a3b8; /* slate-400 */
|
|
55
|
-
--dp-primary-color: #059669; /* emerald-600 */
|
|
56
|
-
--dp-primary-disabled-color: #6ee7b7; /* emerald-300 */
|
|
57
|
-
--dp-secondary-color: #94a3b8;
|
|
58
|
-
--dp-border-color: #cbd5e1; /* slate-300 */
|
|
59
|
-
--dp-menu-border-color: #cbd5e1;
|
|
60
|
-
--dp-border-color-hover: #94a3b8;
|
|
61
|
-
--dp-border-color-focus: #10b981; /* emerald-500 */
|
|
62
|
-
--dp-highlight-color: rgb(16 185 129 / 0.12);
|
|
63
|
-
--dp-success-color: #10b981;
|
|
64
|
-
--dp-danger-color: #ef4444; /* red-500 */
|
|
65
|
-
--dp-marker-color: #ef4444;
|
|
66
|
-
}
|
|
67
|
-
html .dp--theme-dark {
|
|
68
|
-
--dp-background-color: #0f172a; /* slate-900 */
|
|
69
|
-
--dp-text-color: #f1f5f9; /* slate-100 */
|
|
70
|
-
--dp-hover-color: #1e293b; /* slate-800 */
|
|
71
|
-
--dp-hover-text-color: #f1f5f9;
|
|
72
|
-
--dp-hover-icon-color: #cbd5e1; /* slate-300 */
|
|
73
|
-
--dp-icon-color: #64748b; /* slate-500 */
|
|
74
|
-
--dp-primary-color: #059669; /* emerald-600 */
|
|
75
|
-
--dp-primary-disabled-color: #065f46; /* emerald-800 */
|
|
76
|
-
--dp-secondary-color: #64748b;
|
|
77
|
-
--dp-border-color: #475569; /* slate-600 */
|
|
78
|
-
--dp-menu-border-color: #334155; /* slate-700 */
|
|
79
|
-
--dp-border-color-hover: #64748b;
|
|
80
|
-
--dp-border-color-focus: #10b981; /* emerald-500 */
|
|
81
|
-
--dp-highlight-color: rgb(16 185 129 / 0.2);
|
|
82
|
-
--dp-disabled-color: #1e293b;
|
|
83
|
-
--dp-disabled-color-text: #64748b;
|
|
84
|
-
--dp-scroll-bar-background: #0f172a;
|
|
85
|
-
--dp-scroll-bar-color: #334155;
|
|
86
|
-
--dp-success-color: #10b981;
|
|
87
|
-
--dp-danger-color: #ef4444;
|
|
88
|
-
--dp-marker-color: #ef4444;
|
|
89
|
-
--dp-tooltip-color: #1e293b;
|
|
90
|
-
}
|
|
33
|
+
|
|
34
|
+
/* BaseDateTimePicker skin: re-point @vuepic/vue-datepicker's CSS variables at
|
|
35
|
+
the lib palette (emerald accent, slate surfaces — same as BaseInput). The
|
|
36
|
+
picker is third-party markup, so its variables are the only styling hook;
|
|
37
|
+
Tailwind utilities can't reach inside it. `html` prefix out-specifies the
|
|
38
|
+
package's own `.dp--theme-*` blocks regardless of stylesheet order. */
|
|
39
|
+
html .dp--theme-light,
|
|
40
|
+
html .dp--theme-dark {
|
|
41
|
+
--dp-font-family: var(--font-sans);
|
|
42
|
+
--dp-font-size: 0.875rem;
|
|
43
|
+
--dp-border-radius: 0.5rem; /* rounded-lg, like BaseInput */
|
|
44
|
+
--dp-cell-border-radius: 0.25rem;
|
|
45
|
+
--dp-input-padding: 8px 30px 8px 12px;
|
|
46
|
+
--dp-primary-text-color: #fff;
|
|
47
|
+
}
|
|
48
|
+
html .dp--theme-light {
|
|
49
|
+
--dp-background-color: #fff; /* white, like BaseInput */
|
|
50
|
+
--dp-text-color: #0f172a; /* slate-900 */
|
|
51
|
+
--dp-hover-color: #f1f5f9; /* slate-100 */
|
|
52
|
+
--dp-hover-text-color: #0f172a;
|
|
53
|
+
--dp-hover-icon-color: #64748b; /* slate-500 */
|
|
54
|
+
--dp-icon-color: #94a3b8; /* slate-400 */
|
|
55
|
+
--dp-primary-color: #059669; /* emerald-600 */
|
|
56
|
+
--dp-primary-disabled-color: #6ee7b7; /* emerald-300 */
|
|
57
|
+
--dp-secondary-color: #94a3b8;
|
|
58
|
+
--dp-border-color: #cbd5e1; /* slate-300 */
|
|
59
|
+
--dp-menu-border-color: #cbd5e1;
|
|
60
|
+
--dp-border-color-hover: #94a3b8;
|
|
61
|
+
--dp-border-color-focus: #10b981; /* emerald-500 */
|
|
62
|
+
--dp-highlight-color: rgb(16 185 129 / 0.12);
|
|
63
|
+
--dp-success-color: #10b981;
|
|
64
|
+
--dp-danger-color: #ef4444; /* red-500 */
|
|
65
|
+
--dp-marker-color: #ef4444;
|
|
66
|
+
}
|
|
67
|
+
html .dp--theme-dark {
|
|
68
|
+
--dp-background-color: #0f172a; /* slate-900 */
|
|
69
|
+
--dp-text-color: #f1f5f9; /* slate-100 */
|
|
70
|
+
--dp-hover-color: #1e293b; /* slate-800 */
|
|
71
|
+
--dp-hover-text-color: #f1f5f9;
|
|
72
|
+
--dp-hover-icon-color: #cbd5e1; /* slate-300 */
|
|
73
|
+
--dp-icon-color: #64748b; /* slate-500 */
|
|
74
|
+
--dp-primary-color: #059669; /* emerald-600 */
|
|
75
|
+
--dp-primary-disabled-color: #065f46; /* emerald-800 */
|
|
76
|
+
--dp-secondary-color: #64748b;
|
|
77
|
+
--dp-border-color: #475569; /* slate-600 */
|
|
78
|
+
--dp-menu-border-color: #334155; /* slate-700 */
|
|
79
|
+
--dp-border-color-hover: #64748b;
|
|
80
|
+
--dp-border-color-focus: #10b981; /* emerald-500 */
|
|
81
|
+
--dp-highlight-color: rgb(16 185 129 / 0.2);
|
|
82
|
+
--dp-disabled-color: #1e293b;
|
|
83
|
+
--dp-disabled-color-text: #64748b;
|
|
84
|
+
--dp-scroll-bar-background: #0f172a;
|
|
85
|
+
--dp-scroll-bar-color: #334155;
|
|
86
|
+
--dp-success-color: #10b981;
|
|
87
|
+
--dp-danger-color: #ef4444;
|
|
88
|
+
--dp-marker-color: #ef4444;
|
|
89
|
+
--dp-tooltip-color: #1e293b;
|
|
90
|
+
}
|