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.
Files changed (47) hide show
  1. package/README.md +147 -4
  2. package/dist/components/BaseAuthLayout.vue.d.ts +27 -0
  3. package/dist/components/BaseBooleanBadge.vue.d.ts +12 -0
  4. package/dist/components/BaseCard.vue.d.ts +33 -0
  5. package/dist/components/BaseDetailList.vue.d.ts +13 -0
  6. package/dist/components/BaseField.vue.d.ts +38 -0
  7. package/dist/components/BasePageHeader.vue.d.ts +3 -0
  8. package/dist/components/BaseSearchSelect.vue.d.ts +41 -0
  9. package/dist/components/BaseStatCard.vue.d.ts +33 -0
  10. package/dist/components/BaseTable.vue.d.ts +3 -0
  11. package/dist/components/BaseToggle.vue.d.ts +33 -0
  12. package/dist/index.d.ts +9 -0
  13. package/dist/types/detail.d.ts +10 -0
  14. package/dist/ui-lib.css +1 -1
  15. package/dist/ui-lib.js +1388 -881
  16. package/dist/ui-lib.umd.cjs +1 -1
  17. package/package.json +1 -1
  18. package/src/components/BaseAuthLayout.test.ts +48 -0
  19. package/src/components/BaseAuthLayout.vue +50 -0
  20. package/src/components/BaseBooleanBadge.vue +33 -0
  21. package/src/components/BaseBreadcrumb.vue +68 -68
  22. package/src/components/BaseButton.vue +1 -1
  23. package/src/components/BaseCard.test.ts +55 -0
  24. package/src/components/BaseCard.vue +48 -0
  25. package/src/components/BaseDetailList.test.ts +36 -0
  26. package/src/components/BaseDetailList.vue +43 -0
  27. package/src/components/BaseField.vue +47 -0
  28. package/src/components/BaseInput.test.ts +1 -1
  29. package/src/components/BaseInput.vue +46 -46
  30. package/src/components/BaseLogo.test.ts +26 -0
  31. package/src/components/BaseLogo.vue +7 -1
  32. package/src/components/BasePageHeader.vue +13 -2
  33. package/src/components/BaseSearchSelect.test.ts +117 -0
  34. package/src/components/BaseSearchSelect.vue +318 -0
  35. package/src/components/BaseSelect.test.ts +3 -3
  36. package/src/components/BaseSelect.vue +62 -46
  37. package/src/components/BaseStatCard.test.ts +40 -0
  38. package/src/components/BaseStatCard.vue +103 -0
  39. package/src/components/BaseTable.test.ts +69 -55
  40. package/src/components/BaseTable.vue +62 -50
  41. package/src/components/BaseToggle.test.ts +39 -0
  42. package/src/components/BaseToggle.vue +73 -0
  43. package/src/components/Pagination.test.ts +93 -85
  44. package/src/components/Pagination.vue +87 -82
  45. package/src/index.ts +9 -0
  46. package/src/style.css +58 -58
  47. 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
- totalPages.value = Math.ceil(props.totalItems / props.itemsPerPage);
77
- if (currentPage.value > totalPages.value) {
78
- currentPage.value = Math.max(1, totalPages.value);
79
- }
80
- buildCells(currentPage.value);
81
- }, { immediate: true })
82
- </script>
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.25rem;
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: #f8fafc; /* slate-50 */
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
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * One row of a BaseDetailList. `href` renders the value as an external link;
3
+ * `mono` sets it in the monospace face.
4
+ */
5
+ export interface DetailItem {
6
+ label: string
7
+ value?: string | number | boolean | null
8
+ href?: string
9
+ mono?: boolean
10
+ }