@alaarab/ogrid-vue-vuetify 2.0.2

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 (27) hide show
  1. package/dist/esm/ColumnChooser/ColumnChooser.js +74 -0
  2. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +210 -0
  3. package/dist/esm/ColumnHeaderFilter/MultiSelectFilterPopover.js +68 -0
  4. package/dist/esm/ColumnHeaderFilter/PeopleFilterPopover.js +82 -0
  5. package/dist/esm/ColumnHeaderFilter/TextFilterPopover.js +40 -0
  6. package/dist/esm/ColumnHeaderFilter/index.js +1 -0
  7. package/dist/esm/DataGridTable/DataGridTable.js +467 -0
  8. package/dist/esm/DataGridTable/GridContextMenu.js +55 -0
  9. package/dist/esm/DataGridTable/InlineCellEditor.js +124 -0
  10. package/dist/esm/DataGridTable/StatusBar.js +45 -0
  11. package/dist/esm/OGrid/OGrid.js +95 -0
  12. package/dist/esm/PaginationControls/PaginationControls.js +131 -0
  13. package/dist/esm/index.js +8 -0
  14. package/dist/types/ColumnChooser/ColumnChooser.d.ts +36 -0
  15. package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +177 -0
  16. package/dist/types/ColumnHeaderFilter/MultiSelectFilterPopover.d.ts +88 -0
  17. package/dist/types/ColumnHeaderFilter/PeopleFilterPopover.d.ts +66 -0
  18. package/dist/types/ColumnHeaderFilter/TextFilterPopover.d.ts +38 -0
  19. package/dist/types/ColumnHeaderFilter/index.d.ts +2 -0
  20. package/dist/types/DataGridTable/DataGridTable.d.ts +15 -0
  21. package/dist/types/DataGridTable/GridContextMenu.d.ts +116 -0
  22. package/dist/types/DataGridTable/InlineCellEditor.d.ts +72 -0
  23. package/dist/types/DataGridTable/StatusBar.d.ts +80 -0
  24. package/dist/types/OGrid/OGrid.d.ts +15 -0
  25. package/dist/types/PaginationControls/PaginationControls.d.ts +74 -0
  26. package/dist/types/index.d.ts +9 -0
  27. package/package.json +40 -0
@@ -0,0 +1,74 @@
1
+ import { defineComponent, ref, computed, h } from 'vue';
2
+ import { VBtn, VMenu, VList, VListItem, VDivider } from 'vuetify/components';
3
+ import { useColumnChooserState } from '@alaarab/ogrid-vue';
4
+ export const ColumnChooser = defineComponent({
5
+ name: 'ColumnChooser',
6
+ props: {
7
+ columns: { type: Array, required: true },
8
+ visibleColumns: { type: Object, required: true },
9
+ onVisibilityChange: { type: Function, required: true },
10
+ },
11
+ setup(props) {
12
+ const menuOpen = ref(false);
13
+ const columnsRef = computed(() => props.columns);
14
+ const visibleColumnsRef = computed(() => props.visibleColumns);
15
+ const state = useColumnChooserState({
16
+ columns: columnsRef,
17
+ visibleColumns: visibleColumnsRef,
18
+ onVisibilityChange: props.onVisibilityChange,
19
+ });
20
+ return () => {
21
+ return h(VMenu, {
22
+ modelValue: menuOpen.value,
23
+ 'onUpdate:modelValue': (v) => { menuOpen.value = v; },
24
+ closeOnContentClick: false,
25
+ location: 'bottom end',
26
+ }, {
27
+ activator: ({ props: activatorProps }) => h(VBtn, {
28
+ ...activatorProps,
29
+ variant: 'outlined',
30
+ size: 'small',
31
+ prependIcon: 'mdi-view-column',
32
+ appendIcon: menuOpen.value ? 'mdi-chevron-up' : 'mdi-chevron-down',
33
+ }, () => `Column Visibility (${state.visibleCount.value} of ${state.totalCount.value})`),
34
+ default: () => h('div', { style: { minWidth: '220px' } }, [
35
+ // Header
36
+ h('div', {
37
+ style: {
38
+ padding: '8px 12px',
39
+ borderBottom: '1px solid rgba(0,0,0,0.12)',
40
+ backgroundColor: 'rgba(0,0,0,0.04)',
41
+ fontWeight: '600',
42
+ fontSize: '0.875rem',
43
+ },
44
+ }, `Select Columns (${state.visibleCount.value} of ${state.totalCount.value})`),
45
+ // Column list
46
+ h(VList, { density: 'compact', style: { maxHeight: '320px', overflowY: 'auto' } }, () => props.columns.map((column) => h(VListItem, { key: column.columnId, style: { minHeight: '32px' } }, () => h('label', {
47
+ style: { display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer', width: '100%' },
48
+ }, [
49
+ h('input', {
50
+ type: 'checkbox',
51
+ checked: props.visibleColumns.has(column.columnId),
52
+ onChange: (e) => state.handleCheckboxChange(column.columnId)(e.target.checked),
53
+ }),
54
+ h('span', { style: { fontSize: '0.875rem' } }, column.name),
55
+ ])))),
56
+ // Footer with actions
57
+ h(VDivider),
58
+ h('div', {
59
+ style: {
60
+ display: 'flex',
61
+ justifyContent: 'flex-end',
62
+ gap: '8px',
63
+ padding: '8px 12px',
64
+ backgroundColor: 'rgba(0,0,0,0.04)',
65
+ },
66
+ }, [
67
+ h(VBtn, { size: 'small', variant: 'text', onClick: state.handleClearAll }, () => 'Clear All'),
68
+ h(VBtn, { size: 'small', variant: 'flat', color: 'primary', onClick: state.handleSelectAll }, () => 'Select All'),
69
+ ]),
70
+ ]),
71
+ });
72
+ };
73
+ },
74
+ });
@@ -0,0 +1,210 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import { VBtn, VIcon, VMenu, VTooltip } from 'vuetify/components';
3
+ import { useColumnHeaderFilterState, } from '@alaarab/ogrid-vue';
4
+ import { TextFilterPopover } from './TextFilterPopover';
5
+ import { MultiSelectFilterPopover } from './MultiSelectFilterPopover';
6
+ import { PeopleFilterPopover } from './PeopleFilterPopover';
7
+ // Vuetify component types don't align with h() overloads; cast to Component
8
+ /* eslint-disable @typescript-eslint/no-explicit-any */
9
+ const _VBtn = VBtn;
10
+ const _VIcon = VIcon;
11
+ const _VMenu = VMenu;
12
+ const _VTooltip = VTooltip;
13
+ export const ColumnHeaderFilter = defineComponent({
14
+ name: 'ColumnHeaderFilter',
15
+ props: {
16
+ columnKey: { type: String, required: true },
17
+ columnName: { type: String, required: true },
18
+ filterType: { type: String, required: true },
19
+ isSorted: { type: Boolean, default: false },
20
+ isSortedDescending: { type: Boolean, default: false },
21
+ onSort: { type: Function, default: undefined },
22
+ selectedValues: { type: Array, default: undefined },
23
+ onFilterChange: { type: Function, default: undefined },
24
+ options: { type: Array, default: () => [] },
25
+ isLoadingOptions: { type: Boolean, default: false },
26
+ textValue: { type: String, default: '' },
27
+ onTextChange: { type: Function, default: undefined },
28
+ selectedUser: { type: Object, default: undefined },
29
+ onUserChange: { type: Function, default: undefined },
30
+ peopleSearch: { type: Function, default: undefined },
31
+ dateValue: { type: Object, default: undefined },
32
+ onDateChange: { type: Function, default: undefined },
33
+ },
34
+ setup(props) {
35
+ const state = useColumnHeaderFilterState({
36
+ filterType: props.filterType,
37
+ isSorted: props.isSorted,
38
+ isSortedDescending: props.isSortedDescending,
39
+ onSort: props.onSort,
40
+ selectedValues: props.selectedValues,
41
+ onFilterChange: props.onFilterChange,
42
+ options: props.options,
43
+ isLoadingOptions: props.isLoadingOptions,
44
+ textValue: props.textValue,
45
+ onTextChange: props.onTextChange,
46
+ selectedUser: props.selectedUser,
47
+ onUserChange: props.onUserChange,
48
+ peopleSearch: props.peopleSearch,
49
+ dateValue: props.dateValue,
50
+ onDateChange: props.onDateChange,
51
+ });
52
+ const renderPopoverContent = () => {
53
+ if (props.filterType === 'multiSelect') {
54
+ return h(MultiSelectFilterPopover, {
55
+ searchText: state.searchText.value,
56
+ onSearchChange: state.setSearchText,
57
+ options: props.options ?? [],
58
+ filteredOptions: state.filteredOptions.value,
59
+ selected: state.tempSelected.value,
60
+ onOptionToggle: state.handlers.handleCheckboxChange,
61
+ onSelectAll: state.handlers.handleSelectAll,
62
+ onClearSelection: state.handlers.handleClearSelection,
63
+ onApply: state.handlers.handleApplyMultiSelect,
64
+ isLoading: props.isLoadingOptions,
65
+ });
66
+ }
67
+ if (props.filterType === 'text') {
68
+ return h(TextFilterPopover, {
69
+ value: state.tempTextValue.value ?? '',
70
+ onValueChange: state.setTempTextValue,
71
+ onApply: state.handlers.handleTextApply,
72
+ onClear: state.handlers.handleTextClear,
73
+ });
74
+ }
75
+ if (props.filterType === 'people') {
76
+ return h(PeopleFilterPopover, {
77
+ selectedUser: props.selectedUser,
78
+ searchText: state.peopleSearchText.value,
79
+ onSearchChange: state.setPeopleSearchText,
80
+ suggestions: state.peopleSuggestions.value,
81
+ isLoading: state.isPeopleLoading.value,
82
+ onUserSelect: state.handlers.handleUserSelect,
83
+ onClearUser: state.handlers.handleClearUser,
84
+ });
85
+ }
86
+ if (props.filterType === 'date') {
87
+ return h('div', { style: { padding: '12px', display: 'flex', flexDirection: 'column', gap: '8px' } }, [
88
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [
89
+ h('span', { style: { minWidth: '36px', fontSize: '0.75rem' } }, 'From:'),
90
+ h('input', {
91
+ type: 'date',
92
+ value: state.tempDateFrom.value ?? '',
93
+ onInput: (e) => state.setTempDateFrom(e.target.value),
94
+ style: { flex: '1', padding: '4px 6px' },
95
+ }),
96
+ ]),
97
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [
98
+ h('span', { style: { minWidth: '36px', fontSize: '0.75rem' } }, 'To:'),
99
+ h('input', {
100
+ type: 'date',
101
+ value: state.tempDateTo.value ?? '',
102
+ onInput: (e) => state.setTempDateTo(e.target.value),
103
+ style: { flex: '1', padding: '4px 6px' },
104
+ }),
105
+ ]),
106
+ h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '4px' } }, [
107
+ h('button', {
108
+ onClick: state.handlers.handleDateClear,
109
+ disabled: !state.tempDateFrom.value && !state.tempDateTo.value,
110
+ style: { padding: '4px 12px', cursor: 'pointer' },
111
+ }, 'Clear'),
112
+ h('button', {
113
+ onClick: state.handlers.handleDateApply,
114
+ style: { padding: '4px 12px', cursor: 'pointer' },
115
+ }, 'Apply'),
116
+ ]),
117
+ ]);
118
+ }
119
+ return null;
120
+ };
121
+ return () => {
122
+ return h('div', {
123
+ ref: (el) => { state.headerRef.value = el; },
124
+ style: { display: 'flex', alignItems: 'center', width: '100%', minWidth: '0' },
125
+ }, [
126
+ // Column name with tooltip
127
+ h('div', { style: { flex: '1', minWidth: '0', overflow: 'hidden' } }, h(_VTooltip, { text: props.columnName, location: 'top' }, {
128
+ activator: ({ props: tipProps }) => h('span', {
129
+ ...tipProps,
130
+ 'data-header-label': '',
131
+ style: {
132
+ fontWeight: '600',
133
+ fontSize: '0.875rem',
134
+ lineHeight: '1.4',
135
+ whiteSpace: 'nowrap',
136
+ overflow: 'hidden',
137
+ textOverflow: 'ellipsis',
138
+ display: 'block',
139
+ },
140
+ }, props.columnName),
141
+ })),
142
+ // Sort + filter buttons
143
+ h('div', { style: { display: 'flex', alignItems: 'center', marginLeft: '4px', flexShrink: '0' } }, [
144
+ // Sort button
145
+ ...(props.onSort ? [
146
+ h(_VBtn, {
147
+ icon: true,
148
+ size: 'x-small',
149
+ variant: 'text',
150
+ color: props.isSorted ? 'primary' : undefined,
151
+ 'aria-label': `Sort by ${props.columnName}`,
152
+ title: props.isSorted ? (props.isSortedDescending ? 'Sorted descending' : 'Sorted ascending') : 'Sort',
153
+ onClick: state.handlers.handleSortClick,
154
+ }, () => h(_VIcon, { size: '16' }, () => props.isSorted
155
+ ? (props.isSortedDescending ? 'mdi-arrow-down' : 'mdi-arrow-up')
156
+ : 'mdi-swap-vertical')),
157
+ ] : []),
158
+ // Filter icon + menu
159
+ ...(props.filterType !== 'none' ? [
160
+ h(_VMenu, {
161
+ modelValue: state.isFilterOpen.value,
162
+ 'onUpdate:modelValue': (v) => state.setFilterOpen(v),
163
+ closeOnContentClick: false,
164
+ location: 'bottom start',
165
+ }, {
166
+ activator: ({ props: menuProps }) => h('div', { style: { position: 'relative' } }, [
167
+ h(_VBtn, {
168
+ ...menuProps,
169
+ icon: true,
170
+ size: 'x-small',
171
+ variant: 'text',
172
+ color: state.hasActiveFilter.value || state.isFilterOpen.value ? 'primary' : undefined,
173
+ 'aria-label': `Filter ${props.columnName}`,
174
+ title: `Filter ${props.columnName}`,
175
+ }, () => h(_VIcon, { size: '16' }, () => 'mdi-filter-variant')),
176
+ ...(state.hasActiveFilter.value ? [
177
+ h('div', {
178
+ style: {
179
+ position: 'absolute',
180
+ top: '2px',
181
+ right: '2px',
182
+ width: '6px',
183
+ height: '6px',
184
+ borderRadius: '50%',
185
+ backgroundColor: 'rgb(var(--v-theme-primary))',
186
+ },
187
+ }),
188
+ ] : []),
189
+ ]),
190
+ default: () => h('div', {
191
+ ref: (el) => { state.popoverRef.value = el; },
192
+ onClick: (e) => e.stopPropagation(),
193
+ }, [
194
+ h('div', {
195
+ style: {
196
+ borderBottom: '1px solid rgba(0,0,0,0.12)',
197
+ padding: '8px 12px',
198
+ fontWeight: '600',
199
+ fontSize: '0.875rem',
200
+ },
201
+ }, `Filter: ${props.columnName}`),
202
+ renderPopoverContent(),
203
+ ]),
204
+ }),
205
+ ] : []),
206
+ ]),
207
+ ]);
208
+ };
209
+ },
210
+ });
@@ -0,0 +1,68 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import { VBtn, VTextField, VCheckbox, VProgressCircular, VDivider } from 'vuetify/components';
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ const _VBtn = VBtn;
5
+ const _VTextField = VTextField;
6
+ const _VCheckbox = VCheckbox;
7
+ const _VProgressCircular = VProgressCircular;
8
+ const _VDivider = VDivider;
9
+ /* eslint-enable @typescript-eslint/no-explicit-any */
10
+ export const MultiSelectFilterPopover = defineComponent({
11
+ name: 'MultiSelectFilterPopover',
12
+ props: {
13
+ searchText: { type: String, required: true },
14
+ onSearchChange: { type: Function, required: true },
15
+ options: { type: Array, required: true },
16
+ filteredOptions: { type: Array, required: true },
17
+ selected: { type: Object, required: true },
18
+ onOptionToggle: { type: Function, required: true },
19
+ onSelectAll: { type: Function, required: true },
20
+ onClearSelection: { type: Function, required: true },
21
+ onApply: { type: Function, required: true },
22
+ isLoading: { type: Boolean, default: false },
23
+ },
24
+ setup(props) {
25
+ return () => h('div', { style: { width: '280px' } }, [
26
+ // Search
27
+ h('div', { style: { padding: '12px 12px 4px' } }, [
28
+ h(_VTextField, {
29
+ modelValue: props.searchText,
30
+ 'onUpdate:modelValue': (v) => props.onSearchChange(v),
31
+ placeholder: 'Search...',
32
+ density: 'compact',
33
+ variant: 'outlined',
34
+ hideDetails: true,
35
+ autocomplete: 'off',
36
+ prependInnerIcon: 'mdi-magnify',
37
+ onKeydown: (e) => e.stopPropagation(),
38
+ }),
39
+ h('span', {
40
+ style: { display: 'block', marginTop: '4px', fontSize: '0.75rem', color: 'rgba(0,0,0,0.6)' },
41
+ }, `${props.filteredOptions.length} of ${props.options.length} options`),
42
+ ]),
43
+ // Select all / clear
44
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', padding: '4px 12px' } }, [
45
+ h(_VBtn, { size: 'small', variant: 'text', onClick: props.onSelectAll }, () => `Select All (${props.filteredOptions.length})`),
46
+ h(_VBtn, { size: 'small', variant: 'text', onClick: props.onClearSelection }, () => 'Clear'),
47
+ ]),
48
+ // Options list
49
+ h('div', { style: { maxHeight: '240px', overflowY: 'auto', padding: '0 4px' } }, props.isLoading
50
+ ? h('div', { style: { display: 'flex', justifyContent: 'center', padding: '16px 0' } }, h(_VProgressCircular, { size: 24, indeterminate: true }))
51
+ : props.filteredOptions.length === 0
52
+ ? h('div', { style: { padding: '16px 0', textAlign: 'center', fontSize: '0.875rem', color: 'rgba(0,0,0,0.6)' } }, 'No options found')
53
+ : props.filteredOptions.map((option) => h('div', { key: option, style: { display: 'flex', alignItems: 'center', minHeight: '32px' } }, h(_VCheckbox, {
54
+ modelValue: props.selected.has(option),
55
+ label: option,
56
+ density: 'compact',
57
+ hideDetails: true,
58
+ 'onUpdate:modelValue': (checked) => props.onOptionToggle(option, checked),
59
+ })))),
60
+ // Footer
61
+ h(_VDivider),
62
+ h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: '8px', padding: '8px 12px' } }, [
63
+ h(_VBtn, { size: 'small', variant: 'text', onClick: props.onClearSelection }, () => 'Clear'),
64
+ h(_VBtn, { size: 'small', variant: 'flat', color: 'primary', onClick: props.onApply }, () => 'Apply'),
65
+ ]),
66
+ ]);
67
+ },
68
+ });
@@ -0,0 +1,82 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import { VBtn, VTextField, VProgressCircular, VAvatar, VIcon, VDivider } from 'vuetify/components';
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ const _VBtn = VBtn;
5
+ const _VTextField = VTextField;
6
+ const _VProgressCircular = VProgressCircular;
7
+ const _VAvatar = VAvatar;
8
+ const _VIcon = VIcon;
9
+ const _VDivider = VDivider;
10
+ export const PeopleFilterPopover = defineComponent({
11
+ name: 'PeopleFilterPopover',
12
+ props: {
13
+ selectedUser: { type: Object, default: undefined },
14
+ searchText: { type: String, required: true },
15
+ onSearchChange: { type: Function, required: true },
16
+ suggestions: { type: Array, required: true },
17
+ isLoading: { type: Boolean, default: false },
18
+ onUserSelect: { type: Function, required: true },
19
+ onClearUser: { type: Function, required: true },
20
+ },
21
+ setup(props) {
22
+ return () => h('div', { style: { width: '300px' } }, [
23
+ // Selected user display
24
+ ...(props.selectedUser ? [
25
+ h('div', { style: { padding: '12px 12px 8px', borderBottom: '1px solid rgba(0,0,0,0.12)' } }, [
26
+ h('span', { style: { fontSize: '0.75rem', color: 'rgba(0,0,0,0.6)' } }, 'Currently filtered by:'),
27
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '4px' } }, [
28
+ h(_VAvatar, { size: 32, image: props.selectedUser.photo }, () => props.selectedUser.displayName?.[0] ?? ''),
29
+ h('div', { style: { flex: '1', minWidth: '0' } }, [
30
+ h('div', { style: { fontSize: '0.875rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, props.selectedUser.displayName),
31
+ h('div', { style: { fontSize: '0.75rem', color: 'rgba(0,0,0,0.6)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, props.selectedUser.email),
32
+ ]),
33
+ h(_VBtn, {
34
+ icon: true,
35
+ size: 'x-small',
36
+ variant: 'text',
37
+ 'aria-label': 'Remove filter',
38
+ onClick: props.onClearUser,
39
+ }, () => h(_VIcon, { size: '16' }, () => 'mdi-close')),
40
+ ]),
41
+ ]),
42
+ ] : []),
43
+ // Search input
44
+ h('div', { style: { padding: '12px 12px 4px' } }, h(_VTextField, {
45
+ modelValue: props.searchText,
46
+ 'onUpdate:modelValue': (v) => props.onSearchChange(v),
47
+ placeholder: 'Search for a person...',
48
+ density: 'compact',
49
+ variant: 'outlined',
50
+ hideDetails: true,
51
+ autocomplete: 'off',
52
+ prependInnerIcon: 'mdi-magnify',
53
+ onKeydown: (e) => e.stopPropagation(),
54
+ })),
55
+ // Suggestions list
56
+ h('div', { style: { maxHeight: '240px', overflowY: 'auto' } }, props.isLoading && props.searchText.trim()
57
+ ? h('div', { style: { display: 'flex', justifyContent: 'center', padding: '16px 0' } }, h(_VProgressCircular, { size: 24, indeterminate: true }))
58
+ : props.suggestions.length === 0 && props.searchText.trim()
59
+ ? h('div', { style: { padding: '16px 0', textAlign: 'center', fontSize: '0.875rem', color: 'rgba(0,0,0,0.6)' } }, 'No results found')
60
+ : props.searchText.trim()
61
+ ? props.suggestions.map((user) => h('div', {
62
+ key: user.id || user.email || user.displayName,
63
+ style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '8px 12px', cursor: 'pointer' },
64
+ onClick: () => props.onUserSelect(user),
65
+ onMouseenter: (e) => { e.currentTarget.style.backgroundColor = 'rgba(0,0,0,0.04)'; },
66
+ onMouseleave: (e) => { e.currentTarget.style.backgroundColor = ''; },
67
+ }, [
68
+ h(_VAvatar, { size: 32, image: user.photo }, () => user.displayName?.[0] ?? ''),
69
+ h('div', { style: { flex: '1', minWidth: '0' } }, [
70
+ h('div', { style: { fontSize: '0.875rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, user.displayName),
71
+ h('div', { style: { fontSize: '0.75rem', color: 'rgba(0,0,0,0.6)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, user.email),
72
+ ]),
73
+ ]))
74
+ : h('div', { style: { padding: '16px 0', textAlign: 'center', fontSize: '0.875rem', color: 'rgba(0,0,0,0.6)' } }, 'Type to search...')),
75
+ // Clear filter button
76
+ ...(props.selectedUser ? [
77
+ h(_VDivider),
78
+ h('div', { style: { padding: '8px 12px' } }, h(_VBtn, { size: 'small', variant: 'text', block: true, onClick: props.onClearUser }, () => 'Clear Filter')),
79
+ ] : []),
80
+ ]);
81
+ },
82
+ });
@@ -0,0 +1,40 @@
1
+ import { defineComponent, h } from 'vue';
2
+ import { VBtn, VTextField } from 'vuetify/components';
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ const _VBtn = VBtn;
5
+ const _VTextField = VTextField;
6
+ /* eslint-enable @typescript-eslint/no-explicit-any */
7
+ export const TextFilterPopover = defineComponent({
8
+ name: 'TextFilterPopover',
9
+ props: {
10
+ value: { type: String, required: true },
11
+ onValueChange: { type: Function, required: true },
12
+ onApply: { type: Function, required: true },
13
+ onClear: { type: Function, required: true },
14
+ },
15
+ setup(props) {
16
+ return () => h('div', { style: { width: '260px' } }, [
17
+ h('div', { style: { padding: '12px' } }, h(_VTextField, {
18
+ modelValue: props.value,
19
+ 'onUpdate:modelValue': (v) => props.onValueChange(v),
20
+ placeholder: 'Enter search term...',
21
+ density: 'compact',
22
+ variant: 'outlined',
23
+ hideDetails: true,
24
+ autocomplete: 'off',
25
+ prependInnerIcon: 'mdi-magnify',
26
+ onKeydown: (e) => {
27
+ e.stopPropagation();
28
+ if (e.key === 'Enter') {
29
+ e.preventDefault();
30
+ props.onApply();
31
+ }
32
+ },
33
+ })),
34
+ h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: '8px', padding: '0 12px 12px' } }, [
35
+ h(_VBtn, { size: 'small', variant: 'text', disabled: !props.value, onClick: props.onClear }, () => 'Clear'),
36
+ h(_VBtn, { size: 'small', variant: 'flat', color: 'primary', onClick: props.onApply }, () => 'Apply'),
37
+ ]),
38
+ ]);
39
+ },
40
+ });
@@ -0,0 +1 @@
1
+ export { ColumnHeaderFilter } from './ColumnHeaderFilter';