picocrank 1.24.0 → 1.25.1
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/AGENTS.md +2 -0
- package/docs/filter-query-v1.md +180 -0
- package/logo.svg +7 -7
- package/package.json +7 -5
- package/public/themes/README.md +34 -2
- package/public/themes/index.json +1 -5
- package/styles/dark-theme.css +42 -186
- package/styles/picocrank-extensions.css +15 -0
- package/styles.css +1 -0
- package/supplemental-themes/README.md +9 -0
- package/supplemental-themes/catppuccin-latte-frappe/theme.css +211 -0
- package/supplemental-themes/dracula-alucard/theme.css +193 -0
- package/supplemental-themes/gruvbox-dark-light/theme.css +199 -0
- package/supplemental-themes/index.json +17 -0
- package/{public/themes → supplemental-themes}/waffles/theme.css +69 -46
- package/vite.config.js +91 -0
- package/vue/components/Header.vue +3 -3
- package/vue/components/Login.vue +1 -3
- package/vue/components/Section.vue +45 -14
- package/vue/components/Sidebar.vue +6 -11
- package/vue/components/SidebarNavLink.vue +6 -9
- package/vue/components/Table.vue +1115 -75
- package/vue/components/TableColumnFilterPopover.vue +263 -44
- package/vue/components/TableColumnOptionsPopover.vue +1116 -0
- package/vue/components/Tabs.vue +4 -25
- package/vue/components/ThemeSwitcher.vue +158 -0
- package/vue/components/TopBar.vue +2 -2
- package/vue/composables/tableColumnPresets.js +296 -0
- package/vue/composables/tableFilterQuery.js +202 -0
- package/vue/composables/tableFilters.js +167 -3
- package/vue/composables/useCustomTheme.js +167 -24
- package/vue/data/examplePeople.js +2 -6
- package/vue/examples/App.vue +14 -1
- package/vue/examples/ExampleHeaderThemeSwitcher.vue +78 -0
- package/vue/examples/LoginExample.vue +1 -3
- package/vue/examples/PageStructure.vue +17 -0
- package/vue/examples/TableExample.vue +329 -11
- package/vue/examples/TableRemoteExample.vue +112 -4
- package/vue/examples/ThemePreview.vue +45 -38
- package/public/themes/catppuccin/theme.css +0 -195
- package/public/themes/dracula/theme.css +0 -181
package/vue/examples/App.vue
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<Navigation ref="navigation">
|
|
3
3
|
<Header username = "Guest" @toggleSidebar="toggleSidebar" @logoClick="goToIndex" @userClick="goToUserDetails" title = "PicoCrank" :logoUrl="logoUrl" :sidebarEnabled="sidebarEnabled" :navigation="navigation" :topBarEnabled="topBarEnabled" :showBranding="brandingEnabled" :breadcrumbs="breadcrumbsEnabled" :themeToggleEnabled="themeToggleEnabled">
|
|
4
4
|
<template #toolbar>
|
|
5
|
+
<ExampleHeaderThemeSwitcher
|
|
6
|
+
v-if="themeSwitcherEnabled"
|
|
7
|
+
:include-supplemental-themes="includeSupplementalThemes"
|
|
8
|
+
/>
|
|
5
9
|
<QuickSearch
|
|
6
10
|
v-if="quickSearchEnabled"
|
|
7
11
|
ref="quickSearchRef"
|
|
@@ -37,6 +41,7 @@
|
|
|
37
41
|
import { Pin02Icon, UserIcon } from '@hugeicons/core-free-icons'
|
|
38
42
|
|
|
39
43
|
import QuickSearch from './../components/QuickSearch.vue'
|
|
44
|
+
import ExampleHeaderThemeSwitcher from './ExampleHeaderThemeSwitcher.vue'
|
|
40
45
|
import Navigation from './../components/Navigation.vue'
|
|
41
46
|
import NotificationPopups from './../components/NotificationPopups.vue'
|
|
42
47
|
import '../../styles.css'
|
|
@@ -54,10 +59,12 @@
|
|
|
54
59
|
const quickSearchEnabled = ref(true);
|
|
55
60
|
const dummySearchFetchEnabled = ref(false);
|
|
56
61
|
const themeToggleEnabled = ref(true);
|
|
62
|
+
const themeSwitcherEnabled = ref(true);
|
|
63
|
+
const includeSupplementalThemes = ref(true);
|
|
57
64
|
const topBarEnabled = ref(false);
|
|
58
65
|
const breadcrumbsEnabled = ref(false);
|
|
59
66
|
|
|
60
|
-
const { discoverThemes } = initCustomTheme();
|
|
67
|
+
const { discoverThemes } = initCustomTheme({ includeSupplementalThemes: true });
|
|
61
68
|
|
|
62
69
|
function toggleSidebar() {
|
|
63
70
|
if (sidebar.value) {
|
|
@@ -93,6 +100,10 @@
|
|
|
93
100
|
themeToggleEnabled.value = !themeToggleEnabled.value;
|
|
94
101
|
}
|
|
95
102
|
|
|
103
|
+
function toggleThemeSwitcherEnabled() {
|
|
104
|
+
themeSwitcherEnabled.value = !themeSwitcherEnabled.value;
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
function toggleTopBarEnabled() {
|
|
97
108
|
topBarEnabled.value = !topBarEnabled.value;
|
|
98
109
|
}
|
|
@@ -161,6 +172,8 @@
|
|
|
161
172
|
provide('toggleDummySearchFetchEnabled', toggleDummySearchFetchEnabled);
|
|
162
173
|
provide('themeToggleEnabled', themeToggleEnabled);
|
|
163
174
|
provide('toggleThemeToggleEnabled', toggleThemeToggleEnabled);
|
|
175
|
+
provide('themeSwitcherEnabled', themeSwitcherEnabled);
|
|
176
|
+
provide('toggleThemeSwitcherEnabled', toggleThemeSwitcherEnabled);
|
|
164
177
|
provide('topBarEnabled', topBarEnabled);
|
|
165
178
|
provide('toggleTopBarEnabled', toggleTopBarEnabled);
|
|
166
179
|
provide('breadcrumbsEnabled', breadcrumbsEnabled);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ThemeSwitcher
|
|
3
|
+
v-if="enabled"
|
|
4
|
+
compact
|
|
5
|
+
label=""
|
|
6
|
+
:select-id="HEADER_THEME_SWITCHER_SELECT_ID"
|
|
7
|
+
default-option-label="Default (Femtocrank only)"
|
|
8
|
+
:include-supplemental-themes="includeSupplementalThemes"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup>
|
|
13
|
+
import ThemeSwitcher, { HEADER_THEME_SWITCHER_SELECT_ID } from '../components/ThemeSwitcher.vue'
|
|
14
|
+
|
|
15
|
+
defineProps({
|
|
16
|
+
enabled: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
default: true,
|
|
19
|
+
},
|
|
20
|
+
includeSupplementalThemes: {
|
|
21
|
+
type: Boolean,
|
|
22
|
+
default: true,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<!-- Examples app only: match Header toolbar chrome (see Header search-trigger styles). -->
|
|
28
|
+
<style>
|
|
29
|
+
/*
|
|
30
|
+
* Header renders theme toggle before the toolbar slot; use flex order to swap
|
|
31
|
+
* the drop-in theme selector ahead of the light/dark button without changing Header.
|
|
32
|
+
*/
|
|
33
|
+
header .header-actions .theme-switcher--compact {
|
|
34
|
+
order: 1;
|
|
35
|
+
align-self: stretch;
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
margin: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
header .header-actions .theme-toggle {
|
|
42
|
+
order: 2;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
header .header-actions .quick-search {
|
|
46
|
+
order: 3;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
header .header-actions .user-info {
|
|
50
|
+
order: 4;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
header .header-actions .theme-switcher--compact select {
|
|
54
|
+
align-self: stretch;
|
|
55
|
+
height: 100%;
|
|
56
|
+
border: 0;
|
|
57
|
+
border-radius: 0;
|
|
58
|
+
background-color: transparent;
|
|
59
|
+
color: var(--header-fg-color, var(--header-text-color, #fff));
|
|
60
|
+
padding-top: 0;
|
|
61
|
+
padding-bottom: 0;
|
|
62
|
+
padding-left: 0.6em;
|
|
63
|
+
padding-right: 0.6em;
|
|
64
|
+
font-size: small;
|
|
65
|
+
font-family: inherit;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
header .header-actions .theme-switcher--compact select:hover {
|
|
70
|
+
background-color: var(--header-hover-background-color);
|
|
71
|
+
color: var(--header-hover-text-color);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
header .header-actions .theme-switcher--compact select:focus-visible {
|
|
75
|
+
outline: 2px solid var(--header-hover-text-color, #fff);
|
|
76
|
+
outline-offset: -2px;
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
@@ -233,12 +233,10 @@ function handleSSOLogin(provider) {
|
|
|
233
233
|
|
|
234
234
|
.magic-link-form input {
|
|
235
235
|
padding: 0.75rem;
|
|
236
|
-
border: 1px solid var(--border-color, #ccc);
|
|
237
236
|
border-radius: 4px;
|
|
238
237
|
font-size: 1em;
|
|
239
|
-
background-color: var(--input-bg, #fff);
|
|
240
|
-
color: var(--text-color, #000);
|
|
241
238
|
width: 100%;
|
|
239
|
+
box-sizing: border-box;
|
|
242
240
|
}
|
|
243
241
|
|
|
244
242
|
.magic-link-form input:focus {
|
|
@@ -62,6 +62,17 @@
|
|
|
62
62
|
/>
|
|
63
63
|
</FormField>
|
|
64
64
|
|
|
65
|
+
<FormField label="Theme switcher" fake>
|
|
66
|
+
<RadioGroup
|
|
67
|
+
name="ps-theme-switcher"
|
|
68
|
+
variant="boolean"
|
|
69
|
+
aria-label="Theme switcher"
|
|
70
|
+
:model-value="themeSwitcherEnabled"
|
|
71
|
+
:options="showHideOptions"
|
|
72
|
+
@update:model-value="setThemeSwitcherEnabled"
|
|
73
|
+
/>
|
|
74
|
+
</FormField>
|
|
75
|
+
|
|
65
76
|
<FormField label="Top bar" fake>
|
|
66
77
|
<div>
|
|
67
78
|
<RadioGroup
|
|
@@ -170,6 +181,8 @@ const dummySearchFetchEnabled = inject('dummySearchFetchEnabled')
|
|
|
170
181
|
const toggleDummySearchFetchEnabled = inject('toggleDummySearchFetchEnabled')
|
|
171
182
|
const themeToggleEnabled = inject('themeToggleEnabled')
|
|
172
183
|
const toggleThemeToggleEnabled = inject('toggleThemeToggleEnabled')
|
|
184
|
+
const themeSwitcherEnabled = inject('themeSwitcherEnabled')
|
|
185
|
+
const toggleThemeSwitcherEnabled = inject('toggleThemeSwitcherEnabled')
|
|
173
186
|
const topBarEnabled = inject('topBarEnabled')
|
|
174
187
|
const toggleTopBarEnabled = inject('toggleTopBarEnabled')
|
|
175
188
|
const breadcrumbsEnabled = inject('breadcrumbsEnabled')
|
|
@@ -200,6 +213,10 @@ function setThemeToggleEnabled(want) {
|
|
|
200
213
|
setFlag(themeToggleEnabled, toggleThemeToggleEnabled, want)
|
|
201
214
|
}
|
|
202
215
|
|
|
216
|
+
function setThemeSwitcherEnabled(want) {
|
|
217
|
+
setFlag(themeSwitcherEnabled, toggleThemeSwitcherEnabled, want)
|
|
218
|
+
}
|
|
219
|
+
|
|
203
220
|
function setTopBarEnabled(want) {
|
|
204
221
|
setFlag(topBarEnabled, toggleTopBarEnabled, want)
|
|
205
222
|
}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Section
|
|
3
3
|
title="Table Example"
|
|
4
|
+
:icon="TableIcon"
|
|
4
5
|
subtitle="Sortable columns, pagination, and column filters. Right-click or long-press a column header to filter."
|
|
5
6
|
:padding="false"
|
|
6
7
|
>
|
|
7
|
-
<template #toolbar>
|
|
8
|
-
<button>Add Item</button>
|
|
9
|
-
</template>
|
|
10
|
-
|
|
11
8
|
<Table
|
|
12
|
-
|
|
9
|
+
table-id="table-example"
|
|
10
|
+
row-key="name"
|
|
11
|
+
:data="tableData"
|
|
13
12
|
:headers="headers"
|
|
14
|
-
:show-pagination="
|
|
13
|
+
:show-pagination="enabledFeatures.includes('pagination')"
|
|
14
|
+
:filterable="enabledFeatures.includes('filters')"
|
|
15
|
+
:column-options="enabledFeatures.includes('columnOptions')"
|
|
16
|
+
:loading="enabledFeatures.includes('loading')"
|
|
17
|
+
:horizontal-scroll="layout === 'scroll'"
|
|
18
|
+
:sticky-cols="stickyCols"
|
|
19
|
+
v-model:column-visibility="columnVisibility"
|
|
20
|
+
:default-column-visibility="defaultColumnVisibility"
|
|
21
|
+
v-bind="tableListeners"
|
|
15
22
|
>
|
|
16
23
|
<template #cell-name="{ row, value }">
|
|
17
24
|
<router-link :to="{ name: 'ViewItem', params: { id: row.name } }">
|
|
@@ -24,23 +31,334 @@
|
|
|
24
31
|
{{ value }}
|
|
25
32
|
</span>
|
|
26
33
|
</template>
|
|
34
|
+
|
|
35
|
+
<template #cell-status="{ value }">
|
|
36
|
+
<span class="tag fg-good">{{ value }}</span>
|
|
37
|
+
</template>
|
|
27
38
|
</Table>
|
|
28
39
|
</Section>
|
|
40
|
+
|
|
41
|
+
<Section
|
|
42
|
+
title="Explore table options"
|
|
43
|
+
:icon="Settings01Icon"
|
|
44
|
+
subtitle="Adjust layout and behaviour for the table above."
|
|
45
|
+
>
|
|
46
|
+
<template #toolbar>
|
|
47
|
+
<button type="button" @click="resetControls">Reset options</button>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<FormLayout>
|
|
51
|
+
<FormField label="Data" fake description="Connect or disconnect row data for the table above. Saved column layouts persist in localStorage for this table.">
|
|
52
|
+
<div class="table-data-actions">
|
|
53
|
+
<button type="button" :disabled="dataConnected" @click="dataConnected = true">
|
|
54
|
+
Connect data
|
|
55
|
+
</button>
|
|
56
|
+
<button type="button" class="neutral" :disabled="!dataConnected" @click="dataConnected = false">
|
|
57
|
+
Disconnect data
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
</FormField>
|
|
61
|
+
|
|
62
|
+
<FormField label="Features" fake description="Toggle core table behaviour.">
|
|
63
|
+
<CheckGroup
|
|
64
|
+
v-model="enabledFeatures"
|
|
65
|
+
name="table-features"
|
|
66
|
+
:options="featureOptions"
|
|
67
|
+
/>
|
|
68
|
+
</FormField>
|
|
69
|
+
|
|
70
|
+
<FormField
|
|
71
|
+
label="Responsive layout"
|
|
72
|
+
fake
|
|
73
|
+
description="Column priorities hide lower-priority columns as the table narrows. Horizontal scroll keeps every column visible with optional sticky leading columns."
|
|
74
|
+
>
|
|
75
|
+
<RadioGroup
|
|
76
|
+
v-model="layout"
|
|
77
|
+
name="table-layout"
|
|
78
|
+
:options="layoutOptions"
|
|
79
|
+
/>
|
|
80
|
+
</FormField>
|
|
81
|
+
|
|
82
|
+
<FormField
|
|
83
|
+
v-if="layout === 'scroll'"
|
|
84
|
+
label="Sticky columns"
|
|
85
|
+
fake
|
|
86
|
+
description="Pin the first columns while scrolling horizontally."
|
|
87
|
+
>
|
|
88
|
+
<RadioGroup
|
|
89
|
+
v-model="stickyColsOption"
|
|
90
|
+
name="table-sticky-cols"
|
|
91
|
+
:options="stickyColOptions"
|
|
92
|
+
/>
|
|
93
|
+
</FormField>
|
|
94
|
+
|
|
95
|
+
<FormField
|
|
96
|
+
v-if="layout === 'scroll'"
|
|
97
|
+
label="Wide table"
|
|
98
|
+
fake
|
|
99
|
+
description="Adds extra columns so horizontal scroll is easier to see."
|
|
100
|
+
>
|
|
101
|
+
<label>
|
|
102
|
+
<input v-model="wideTable" type="checkbox" />
|
|
103
|
+
Show extended columns
|
|
104
|
+
</label>
|
|
105
|
+
</FormField>
|
|
106
|
+
</FormLayout>
|
|
107
|
+
|
|
108
|
+
<div class="table-events">
|
|
109
|
+
<h4>Events</h4>
|
|
110
|
+
<p v-if="lastRowClick" class="event-line">
|
|
111
|
+
<strong>Row click:</strong>
|
|
112
|
+
index {{ lastRowClick.index }}, name “{{ lastRowClick.row.name }}”
|
|
113
|
+
</p>
|
|
114
|
+
<p v-else class="event-line subtle">Row click: enable “Row click” above, then click a row to open the detail dialog.</p>
|
|
115
|
+
|
|
116
|
+
<p v-if="lastQuery" class="event-line">
|
|
117
|
+
<strong>Query:</strong>
|
|
118
|
+
page {{ lastQuery.page }},
|
|
119
|
+
sort {{ lastQuery.sortBy ?? 'none' }} {{ lastQuery.sortDir }},
|
|
120
|
+
{{ activeFilterCount }} active filter(s)
|
|
121
|
+
</p>
|
|
122
|
+
<p v-else class="event-line subtle">Query: waiting for the first table update…</p>
|
|
123
|
+
</div>
|
|
124
|
+
</Section>
|
|
125
|
+
|
|
126
|
+
<dialog
|
|
127
|
+
ref="rowDialogRef"
|
|
128
|
+
class="row-detail-dialog"
|
|
129
|
+
@click="onRowDialogBackdropClick"
|
|
130
|
+
>
|
|
131
|
+
<div v-if="lastRowClick" class="dialog-panel" @click.stop>
|
|
132
|
+
<h3>{{ lastRowClick.row.name }}</h3>
|
|
133
|
+
<p class="subtle">Row index {{ lastRowClick.index }} in the current filtered and sorted result set.</p>
|
|
134
|
+
|
|
135
|
+
<dl>
|
|
136
|
+
<dt>Index</dt>
|
|
137
|
+
<dd>{{ lastRowClick.index }}</dd>
|
|
138
|
+
|
|
139
|
+
<template v-for="header in headers" :key="header.key">
|
|
140
|
+
<dt>{{ header.label || header.key }}</dt>
|
|
141
|
+
<dd>
|
|
142
|
+
<span v-if="header.key === 'status'" class="tag fg-good">
|
|
143
|
+
{{ lastRowClick.row[header.key] }}
|
|
144
|
+
</span>
|
|
145
|
+
<template v-else>
|
|
146
|
+
{{ lastRowClick.row[header.key] }}
|
|
147
|
+
</template>
|
|
148
|
+
</dd>
|
|
149
|
+
</template>
|
|
150
|
+
</dl>
|
|
151
|
+
|
|
152
|
+
<form method="dialog">
|
|
153
|
+
<button type="submit" class="neutral">Close</button>
|
|
154
|
+
</form>
|
|
155
|
+
</div>
|
|
156
|
+
</dialog>
|
|
29
157
|
</template>
|
|
30
158
|
|
|
31
159
|
<script setup>
|
|
32
|
-
import { ref, onMounted } from 'vue'
|
|
160
|
+
import { ref, computed, onMounted } from 'vue'
|
|
161
|
+
import { TableIcon, Settings01Icon } from '@hugeicons/core-free-icons'
|
|
33
162
|
import { getExampleTableRows } from '../data/examplePeople.js'
|
|
163
|
+
import { countActiveFilters } from '../composables/tableFilters.js'
|
|
164
|
+
|
|
165
|
+
const defaultFeatures = ['pagination', 'filters', 'columnOptions', 'rowClick']
|
|
34
166
|
|
|
35
|
-
const
|
|
36
|
-
|
|
167
|
+
const defaultColumnVisibility = {
|
|
168
|
+
birthYear: false,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const enabledFeatures = ref([...defaultFeatures])
|
|
172
|
+
const columnVisibility = ref({ ...defaultColumnVisibility })
|
|
173
|
+
const layout = ref('default')
|
|
174
|
+
const stickyColsOption = ref('2')
|
|
175
|
+
const wideTable = ref(true)
|
|
176
|
+
const dataConnected = ref(true)
|
|
177
|
+
const data = ref([])
|
|
178
|
+
const lastRowClick = ref(null)
|
|
179
|
+
const lastQuery = ref(null)
|
|
180
|
+
const rowDialogRef = ref(null)
|
|
181
|
+
|
|
182
|
+
const featureOptions = [
|
|
183
|
+
{ value: 'pagination', label: 'Pagination' },
|
|
184
|
+
{ value: 'filters', label: 'Column filters' },
|
|
185
|
+
{ value: 'columnOptions', label: 'Column options' },
|
|
186
|
+
{ value: 'rowClick', label: 'Row click' },
|
|
187
|
+
{ value: 'loading', label: 'Loading state' },
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
const layoutOptions = [
|
|
191
|
+
{ value: 'default', label: 'Default' },
|
|
192
|
+
{ value: 'priorities', label: 'Column priorities' },
|
|
193
|
+
{ value: 'scroll', label: 'Horizontal scroll' },
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
const stickyColOptions = [
|
|
197
|
+
{ value: 'none', label: 'None' },
|
|
198
|
+
{ value: '1', label: '1 column' },
|
|
199
|
+
{ value: '2', label: '2 columns' },
|
|
200
|
+
{ value: '3', label: '3 columns' },
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
const baseHeaders = [
|
|
204
|
+
{ key: 'name', label: 'Name', sortable: true, filterable: true, filterType: 'text', width: '20%' },
|
|
37
205
|
{ key: 'age', label: 'Age', sortable: true, filterable: true, filterType: 'number' },
|
|
206
|
+
{ key: 'birthYear', label: 'Birth year', sortable: true, filterable: true, filterType: 'number' },
|
|
38
207
|
{ key: 'city', label: 'City', sortable: true, filterable: true, filterType: 'select', width: '200px' },
|
|
39
|
-
]
|
|
208
|
+
]
|
|
40
209
|
|
|
41
|
-
const
|
|
210
|
+
const extendedHeaders = [
|
|
211
|
+
{ key: 'email', label: 'Email', sortable: true, filterable: true, filterType: 'text' },
|
|
212
|
+
{ key: 'department', label: 'Department', sortable: true, filterable: true, filterType: 'select' },
|
|
213
|
+
{ key: 'role', label: 'Role', sortable: true, filterable: true, filterType: 'text' },
|
|
214
|
+
{ key: 'status', label: 'Status', sortable: true, filterable: true, filterType: 'select' },
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
const priorityByKey = {
|
|
218
|
+
age: 2,
|
|
219
|
+
birthYear: 3,
|
|
220
|
+
city: 1,
|
|
221
|
+
department: 2,
|
|
222
|
+
role: 1,
|
|
223
|
+
status: 1,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const stickyCols = computed(() => {
|
|
227
|
+
if (layout.value !== 'scroll' || stickyColsOption.value === 'none') {
|
|
228
|
+
return null
|
|
229
|
+
}
|
|
230
|
+
return Number(stickyColsOption.value)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const useExtendedColumns = computed(() => layout.value === 'scroll' && wideTable.value)
|
|
234
|
+
|
|
235
|
+
const headers = computed(() => {
|
|
236
|
+
const columns = useExtendedColumns.value
|
|
237
|
+
? [...baseHeaders, ...extendedHeaders]
|
|
238
|
+
: [...baseHeaders]
|
|
239
|
+
|
|
240
|
+
if (layout.value !== 'priorities') {
|
|
241
|
+
return columns
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return columns.map((header) => {
|
|
245
|
+
const colPriority = priorityByKey[header.key]
|
|
246
|
+
if (!colPriority) {
|
|
247
|
+
return { ...header }
|
|
248
|
+
}
|
|
249
|
+
return { ...header, colPriority }
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const tableData = computed(() => {
|
|
254
|
+
if (!dataConnected.value) {
|
|
255
|
+
return []
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const birthYear = new Date().getFullYear()
|
|
259
|
+
|
|
260
|
+
return data.value.map((row) => {
|
|
261
|
+
const withBirthYear = {
|
|
262
|
+
...row,
|
|
263
|
+
birthYear: birthYear - row.age,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!useExtendedColumns.value) {
|
|
267
|
+
return withBirthYear
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
...withBirthYear,
|
|
272
|
+
email: `${String(row.name).toLowerCase()}@example.com`,
|
|
273
|
+
department: row.age >= 30 ? 'Operations' : 'Engineering',
|
|
274
|
+
role: row.age >= 35 ? 'Manager' : 'Member',
|
|
275
|
+
status: row.age % 2 === 0 ? 'Active' : 'Pending',
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
const activeFilterCount = computed(() => {
|
|
281
|
+
if (!lastQuery.value?.filters) {
|
|
282
|
+
return 0
|
|
283
|
+
}
|
|
284
|
+
return countActiveFilters(lastQuery.value.filters, headers.value, tableData.value)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const tableListeners = computed(() => {
|
|
288
|
+
const listeners = {
|
|
289
|
+
onQueryChange: handleQueryChange,
|
|
290
|
+
}
|
|
291
|
+
if (enabledFeatures.value.includes('rowClick')) {
|
|
292
|
+
listeners.onRowClick = handleRowClick
|
|
293
|
+
}
|
|
294
|
+
return listeners
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
function handleRowClick(payload) {
|
|
298
|
+
lastRowClick.value = payload
|
|
299
|
+
rowDialogRef.value?.showModal()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function onRowDialogBackdropClick(event) {
|
|
303
|
+
if (event.target === rowDialogRef.value) {
|
|
304
|
+
rowDialogRef.value.close()
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function handleQueryChange(query) {
|
|
309
|
+
lastQuery.value = query
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function resetControls() {
|
|
313
|
+
enabledFeatures.value = [...defaultFeatures]
|
|
314
|
+
columnVisibility.value = { ...defaultColumnVisibility }
|
|
315
|
+
layout.value = 'default'
|
|
316
|
+
stickyColsOption.value = '2'
|
|
317
|
+
wideTable.value = true
|
|
318
|
+
dataConnected.value = true
|
|
319
|
+
lastRowClick.value = null
|
|
320
|
+
if (rowDialogRef.value?.open) {
|
|
321
|
+
rowDialogRef.value.close()
|
|
322
|
+
}
|
|
323
|
+
}
|
|
42
324
|
|
|
43
325
|
onMounted(() => {
|
|
44
326
|
data.value = getExampleTableRows()
|
|
45
327
|
})
|
|
46
328
|
</script>
|
|
329
|
+
|
|
330
|
+
<style scoped>
|
|
331
|
+
.table-events {
|
|
332
|
+
margin-top: 1.5rem;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.table-data-actions {
|
|
336
|
+
display: flex;
|
|
337
|
+
flex-wrap: wrap;
|
|
338
|
+
gap: 0.5rem;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.table-events h4 {
|
|
342
|
+
margin-top: 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.event-line {
|
|
346
|
+
margin: 0.35rem 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.row-detail-dialog .dialog-panel h3 {
|
|
350
|
+
margin: 0 0 0.5rem;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.row-detail-dialog .dialog-panel p {
|
|
354
|
+
margin: 0 0 1rem;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.row-detail-dialog dl {
|
|
358
|
+
margin: 0 0 1rem;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.row-detail-dialog form {
|
|
362
|
+
margin: 0;
|
|
363
|
+
}
|
|
364
|
+
</style>
|