@naptics/vue-collection 0.0.3 → 0.0.5
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 +5 -1
- package/components/NAlert.js +81 -0
- package/components/NBadge.js +57 -0
- package/components/NBreadcrub.js +66 -0
- package/components/NButton.js +65 -0
- package/components/NCheckbox.js +42 -0
- package/components/NCheckboxLabel.js +39 -0
- package/components/NCrudModal.js +105 -0
- package/components/NDialog.js +160 -0
- package/components/NDropdown.js +108 -0
- package/components/NDropzone.js +210 -0
- package/components/NForm.js +28 -0
- package/components/NFormModal.js +54 -0
- package/components/NIconButton.js +81 -0
- package/components/NIconCircle.js +66 -0
- package/components/NInput.js +105 -0
- package/components/NInputPhone.js +46 -0
- package/components/NInputSelect.js +114 -0
- package/components/NInputSuggestion.js +63 -0
- package/components/NLink.js +59 -0
- package/components/NList.js +24 -0
- package/components/NLoadingIndicator.js +53 -0
- package/components/NModal.js +210 -0
- package/components/NPagination.js +108 -0
- package/components/NSearchbar.js +66 -0
- package/components/NSearchbarList.js +36 -0
- package/components/NSelect.js +84 -0
- package/components/NSuggestionList.js +156 -0
- package/components/NTable.js +126 -0
- package/components/NTableAction.js +49 -0
- package/components/NTextArea.js +128 -0
- package/components/NTooltip.js +178 -0
- package/components/NValInput.js +104 -0
- package/components/ValidatedForm.js +18 -18
- package/i18n/index.js +0 -4
- package/package.json +9 -2
- package/components/NAlert.jsx +0 -69
- package/components/NBadge.jsx +0 -58
- package/components/NBreadcrub.jsx +0 -64
- package/components/NButton.jsx +0 -58
- package/components/NCheckbox.jsx +0 -38
- package/components/NCheckboxLabel.jsx +0 -42
- package/components/NCrudModal.jsx +0 -89
- package/components/NDialog.jsx +0 -144
- package/components/NDropdown.jsx +0 -92
- package/components/NDropzone.jsx +0 -211
- package/components/NForm.jsx +0 -26
- package/components/NFormModal.jsx +0 -48
- package/components/NIconButton.jsx +0 -71
- package/components/NIconCircle.jsx +0 -67
- package/components/NInput.jsx +0 -97
- package/components/NInputPhone.jsx +0 -32
- package/components/NInputSelect.jsx +0 -89
- package/components/NInputSuggestion.jsx +0 -48
- package/components/NLink.jsx +0 -58
- package/components/NList.jsx +0 -24
- package/components/NLoadingIndicator.jsx +0 -42
- package/components/NModal.jsx +0 -170
- package/components/NPagination.jsx +0 -104
- package/components/NSearchbar.jsx +0 -58
- package/components/NSearchbarList.jsx +0 -20
- package/components/NSelect.jsx +0 -81
- package/components/NSuggestionList.jsx +0 -157
- package/components/NTable.jsx +0 -146
- package/components/NTableAction.jsx +0 -35
- package/components/NTextArea.jsx +0 -108
- package/components/NTooltip.jsx +0 -161
- package/components/NValInput.jsx +0 -101
package/components/NModal.jsx
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { createComponent, createProps } from '../utils/component';
|
|
2
|
-
import { Dialog, DialogOverlay, DialogTitle, TransitionRoot, TransitionChild } from '@headlessui/vue';
|
|
3
|
-
import NButton from './NButton';
|
|
4
|
-
import NIconButton from './NIconButton';
|
|
5
|
-
import { XMarkIcon } from '@heroicons/vue/24/solid';
|
|
6
|
-
import { trsl } from '../i18n';
|
|
7
|
-
import { vModelProps } from '../utils/vModel';
|
|
8
|
-
export const nModalProps = createProps({
|
|
9
|
-
...vModelProps(Boolean),
|
|
10
|
-
/**
|
|
11
|
-
* If set to `true` the header of the modal is hidden.
|
|
12
|
-
*/
|
|
13
|
-
hideHeader: Boolean,
|
|
14
|
-
/**
|
|
15
|
-
* If set to `true` the footer of the modal is hidden.
|
|
16
|
-
*/
|
|
17
|
-
hideFooter: Boolean,
|
|
18
|
-
/**
|
|
19
|
-
* If set to `true` the X-button in the top right is hidden.
|
|
20
|
-
*/
|
|
21
|
-
hideX: Boolean,
|
|
22
|
-
/**
|
|
23
|
-
* The maximum width of the modal. A regular tailwind class.
|
|
24
|
-
*/
|
|
25
|
-
maxWidth: {
|
|
26
|
-
type: String,
|
|
27
|
-
default: 'max-w-md',
|
|
28
|
-
},
|
|
29
|
-
/**
|
|
30
|
-
* The title of the modal which is displayed in the header.
|
|
31
|
-
*/
|
|
32
|
-
title: String,
|
|
33
|
-
/**
|
|
34
|
-
* The text of the ok-button.
|
|
35
|
-
*/
|
|
36
|
-
okText: {
|
|
37
|
-
type: String,
|
|
38
|
-
default: trsl('vue-collection.action.save'),
|
|
39
|
-
},
|
|
40
|
-
/**
|
|
41
|
-
* The color of the ok-button.
|
|
42
|
-
*/
|
|
43
|
-
okColor: {
|
|
44
|
-
type: String,
|
|
45
|
-
default: 'primary',
|
|
46
|
-
},
|
|
47
|
-
/**
|
|
48
|
-
* If set to `true` the modal is closed when `onOk` is called.
|
|
49
|
-
*/
|
|
50
|
-
closeOnOk: {
|
|
51
|
-
type: Boolean,
|
|
52
|
-
default: true,
|
|
53
|
-
},
|
|
54
|
-
/**
|
|
55
|
-
* If set to `true` the ok-button is hidden.
|
|
56
|
-
*/
|
|
57
|
-
hideOk: Boolean,
|
|
58
|
-
/**
|
|
59
|
-
* If set to `true` the ok-button is disabled.
|
|
60
|
-
*/
|
|
61
|
-
okDisabled: Boolean,
|
|
62
|
-
/**
|
|
63
|
-
* The text of the cancel-button.
|
|
64
|
-
*/
|
|
65
|
-
cancelText: {
|
|
66
|
-
type: String,
|
|
67
|
-
default: trsl('vue-collection.action.cancel'),
|
|
68
|
-
},
|
|
69
|
-
/**
|
|
70
|
-
* The color of the cancel-button.
|
|
71
|
-
*/
|
|
72
|
-
cancelColor: {
|
|
73
|
-
type: String,
|
|
74
|
-
default: 'default',
|
|
75
|
-
},
|
|
76
|
-
/**
|
|
77
|
-
* If set to `true`, the modal is closed when clicking on the background.
|
|
78
|
-
* This will call `onCancel`. Default is `true`.
|
|
79
|
-
*/
|
|
80
|
-
closeOnBackground: {
|
|
81
|
-
type: Boolean,
|
|
82
|
-
default: true,
|
|
83
|
-
},
|
|
84
|
-
/**
|
|
85
|
-
* If set to `true` the cancel-button is hidden.
|
|
86
|
-
*/
|
|
87
|
-
hideCancel: Boolean,
|
|
88
|
-
/**
|
|
89
|
-
* This is called when the ok-button was clicked.
|
|
90
|
-
*/
|
|
91
|
-
onOk: Function,
|
|
92
|
-
/**
|
|
93
|
-
* This is called when the cancel-button or X-button was clicked or
|
|
94
|
-
* if the modal was closed by clicking on the background.
|
|
95
|
-
*/
|
|
96
|
-
onCancel: Function,
|
|
97
|
-
/**
|
|
98
|
-
* A slot to replace the whole modal content including all buttons, header and footer.
|
|
99
|
-
*/
|
|
100
|
-
modal: Function,
|
|
101
|
-
/**
|
|
102
|
-
* A slot to replace the whole header section (excluding the x).
|
|
103
|
-
*/
|
|
104
|
-
header: Function,
|
|
105
|
-
/**
|
|
106
|
-
* A slot to replace the whole footer section.
|
|
107
|
-
*/
|
|
108
|
-
footer: Function,
|
|
109
|
-
});
|
|
110
|
-
/**
|
|
111
|
-
* The `NModal` is the base component for all modals and dialogs.
|
|
112
|
-
* It provides the core mechanics to display a window in front of everything else.
|
|
113
|
-
*/
|
|
114
|
-
export default createComponent('NModal', nModalProps, (props, { slots }) => {
|
|
115
|
-
const ok = () => {
|
|
116
|
-
props.onOk?.();
|
|
117
|
-
if (props.closeOnOk)
|
|
118
|
-
props.onUpdateValue?.(false);
|
|
119
|
-
};
|
|
120
|
-
const cancel = () => {
|
|
121
|
-
props.onCancel?.();
|
|
122
|
-
props.onUpdateValue?.(false);
|
|
123
|
-
};
|
|
124
|
-
return () => (<TransitionRoot as="template" show={props.value}>
|
|
125
|
-
<Dialog as="div" static class="fixed z-40 inset-0 overflow-y-auto" onClose={cancel} open={props.value}>
|
|
126
|
-
<div class="flex items-center justify-center min-h-screen">
|
|
127
|
-
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
|
|
128
|
-
{props.closeOnBackground ? (<DialogOverlay class="fixed inset-0 bg-default-700 bg-opacity-75"/>) : (<div class="fixed inset-0 bg-default-700 bg-opacity-75"/>)}
|
|
129
|
-
</TransitionChild>
|
|
130
|
-
|
|
131
|
-
{/* This element is to trick the browser into centering the modal contents. */}
|
|
132
|
-
<span class="hidden align-middle h-screen" aria-hidden="true">
|
|
133
|
-
​
|
|
134
|
-
</span>
|
|
135
|
-
|
|
136
|
-
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leave-from="opacity-100 translate-y-0 sm:scale-100" leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
|
137
|
-
<div class={['transform m-4 w-full align-middle', props.maxWidth]}>
|
|
138
|
-
{props.modal?.({ ok, cancel }) || (<div class={[
|
|
139
|
-
'shadow-xl rounded-lg bg-white divide-y divide-default-100',
|
|
140
|
-
props.maxWidth,
|
|
141
|
-
]}>
|
|
142
|
-
{!props.hideX && (<div class="sm:block absolute top-0 right-0 mt-3 mr-3">
|
|
143
|
-
<NIconButton icon={XMarkIcon} color="default" size={5} onClick={cancel}/>
|
|
144
|
-
</div>)}
|
|
145
|
-
|
|
146
|
-
{!props.hideHeader && (<div class="px-4 sm:px-6 pt-4 pb-2 bg-default-50 rounded-t-lg">
|
|
147
|
-
{props.header?.() || (<DialogTitle as="h4" class="text-lg font-semibold">
|
|
148
|
-
{props.title}
|
|
149
|
-
</DialogTitle>)}
|
|
150
|
-
</div>)}
|
|
151
|
-
|
|
152
|
-
<div class="px-4 sm:px-6 py-4 rounded-lg">{slots.default?.()}</div>
|
|
153
|
-
|
|
154
|
-
{!props.hideFooter && (<div class="px-4 sm:px-6 pb-4 pt-2 bg-default-50 rounded-b-lg">
|
|
155
|
-
{props.footer?.({ ok, cancel }) || (<div class="flex justify-end space-x-2">
|
|
156
|
-
{!props.hideCancel && (<NButton color={props.cancelColor} onClick={cancel}>
|
|
157
|
-
{props.cancelText}
|
|
158
|
-
</NButton>)}
|
|
159
|
-
{!props.hideOk && (<NButton color={props.okColor} onClick={ok} disabled={props.okDisabled}>
|
|
160
|
-
{props.okText}
|
|
161
|
-
</NButton>)}
|
|
162
|
-
</div>)}
|
|
163
|
-
</div>)}
|
|
164
|
-
</div>)}
|
|
165
|
-
</div>
|
|
166
|
-
</TransitionChild>
|
|
167
|
-
</div>
|
|
168
|
-
</Dialog>
|
|
169
|
-
</TransitionRoot>);
|
|
170
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { createComponent, createProps } from '../utils/component';
|
|
2
|
-
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/24/solid';
|
|
3
|
-
import { computed, watch } from 'vue';
|
|
4
|
-
import './NPagination.css';
|
|
5
|
-
export const nPaginationProps = createProps({
|
|
6
|
-
/**
|
|
7
|
-
* The page number which is currently selected.
|
|
8
|
-
*/
|
|
9
|
-
value: {
|
|
10
|
-
type: Number,
|
|
11
|
-
default: () => 1,
|
|
12
|
-
},
|
|
13
|
-
/**
|
|
14
|
-
* This is called, when a new page number has been selected.
|
|
15
|
-
*/
|
|
16
|
-
onUpdateValue: Function,
|
|
17
|
-
/**
|
|
18
|
-
* The total pages which exists. This is needed to correctly display the selectable pages.
|
|
19
|
-
*/
|
|
20
|
-
total: {
|
|
21
|
-
type: Number,
|
|
22
|
-
default: () => 1,
|
|
23
|
-
},
|
|
24
|
-
/**
|
|
25
|
-
* If set to `true`, the pagination is displayed smaller.
|
|
26
|
-
*/
|
|
27
|
-
small: Boolean,
|
|
28
|
-
/**
|
|
29
|
-
* This is called, when the visible pages, which are selectable in the pagination, have changed.
|
|
30
|
-
* This is useful as only these pages can be navigated to on the next click.
|
|
31
|
-
* This information can be useful for prefetching.
|
|
32
|
-
*/
|
|
33
|
-
onVisiblePagesChanged: Function,
|
|
34
|
-
});
|
|
35
|
-
/**
|
|
36
|
-
* The `NPagination` is a styled pagination component.
|
|
37
|
-
*/
|
|
38
|
-
export default createComponent('NPagination', nPaginationProps, props => {
|
|
39
|
-
const numbers = computed(() => {
|
|
40
|
-
if (props.total <= 7) {
|
|
41
|
-
return range(1, props.total);
|
|
42
|
-
}
|
|
43
|
-
else if (props.value <= 4) {
|
|
44
|
-
return [...range(1, 5), -1, props.total];
|
|
45
|
-
}
|
|
46
|
-
else if (props.value >= props.total - 3) {
|
|
47
|
-
return [1, -1, ...range(props.total - 4, props.total)];
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
return [1, -1, ...range(props.value - 1, props.value + 1), -1, props.total];
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
watch(() => numbers.value, () => {
|
|
54
|
-
const visiblePages = numbers.value.filter(value => value != -1);
|
|
55
|
-
props.onVisiblePagesChanged?.(visiblePages);
|
|
56
|
-
}, { immediate: true });
|
|
57
|
-
const clickedNumber = (value) => {
|
|
58
|
-
if (value <= props.total && value >= 1)
|
|
59
|
-
props.onUpdateValue?.(value);
|
|
60
|
-
};
|
|
61
|
-
const next = () => clickedNumber(props.value + 1);
|
|
62
|
-
const previous = () => clickedNumber(props.value - 1);
|
|
63
|
-
const items = computed(() => numbers.value.map(number => {
|
|
64
|
-
if (number == -1)
|
|
65
|
-
return { label: '...', selectable: false, selected: false };
|
|
66
|
-
else
|
|
67
|
-
return {
|
|
68
|
-
label: `${number}`,
|
|
69
|
-
selectable: true,
|
|
70
|
-
selected: number == props.value,
|
|
71
|
-
click: () => clickedNumber(number),
|
|
72
|
-
};
|
|
73
|
-
}));
|
|
74
|
-
const classesForItem = (item) => [
|
|
75
|
-
'pagination-item',
|
|
76
|
-
item.selectable ? 'selectable ' : '',
|
|
77
|
-
item.selected ? 'selected' : '',
|
|
78
|
-
props.small ? '' : 'not-small',
|
|
79
|
-
];
|
|
80
|
-
return () => (<nav class="inline-flex rounded-md shadow -space-x-px">
|
|
81
|
-
<button class={['pagination-item selectable rounded-l-md', props.small ? '' : 'not-small']} onClick={previous}>
|
|
82
|
-
<span class="sr-only">Previous</span>
|
|
83
|
-
<ChevronLeftIcon class="h-5 w-5" aria-hidden="true"/>
|
|
84
|
-
</button>
|
|
85
|
-
|
|
86
|
-
{items.value.map((item, index) => item.selectable ? (<button key={index} class={classesForItem(item)} onClick={item.click}>
|
|
87
|
-
{item.label}
|
|
88
|
-
</button>) : (<div key={index} class={classesForItem(item)}>
|
|
89
|
-
{item.label}
|
|
90
|
-
</div>))}
|
|
91
|
-
|
|
92
|
-
<button class={['pagination-item selectable rounded-r-md', props.small ? '' : 'not-small']} onClick={next}>
|
|
93
|
-
<span class="sr-only">Next</span>
|
|
94
|
-
<ChevronRightIcon class="h-5 w-5" aria-hidden="true"/>
|
|
95
|
-
</button>
|
|
96
|
-
</nav>);
|
|
97
|
-
});
|
|
98
|
-
function range(from, to) {
|
|
99
|
-
const array = [];
|
|
100
|
-
for (let i = from; i <= to; i++) {
|
|
101
|
-
array.push(i);
|
|
102
|
-
}
|
|
103
|
-
return array;
|
|
104
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { trsl } from '../i18n';
|
|
2
|
-
import { createComponent, createProps } from '../utils/component';
|
|
3
|
-
import { MagnifyingGlassIcon } from '@heroicons/vue/24/solid';
|
|
4
|
-
import { ref } from 'vue';
|
|
5
|
-
import { vModelProps } from '../utils/vModel';
|
|
6
|
-
export const nSearchbarProps = createProps({
|
|
7
|
-
...vModelProps(String),
|
|
8
|
-
/**
|
|
9
|
-
* The placeholder of the search-bar.
|
|
10
|
-
*/
|
|
11
|
-
placeholder: {
|
|
12
|
-
type: String,
|
|
13
|
-
default: trsl('vue-collection.action.search'),
|
|
14
|
-
},
|
|
15
|
-
/**
|
|
16
|
-
* If set to `true` the search-bar is displayed smaller.
|
|
17
|
-
*/
|
|
18
|
-
small: Boolean,
|
|
19
|
-
/**
|
|
20
|
-
* The classes are directly added to the input (e.g. for shadow).
|
|
21
|
-
*/
|
|
22
|
-
inputClass: String,
|
|
23
|
-
/**
|
|
24
|
-
* This is called when the search-bar receives focus.
|
|
25
|
-
*/
|
|
26
|
-
onFocus: Function,
|
|
27
|
-
/**
|
|
28
|
-
* This is called when the search-bar looses focus.
|
|
29
|
-
*/
|
|
30
|
-
onBlur: Function,
|
|
31
|
-
});
|
|
32
|
-
/**
|
|
33
|
-
* The `NSearchbar` is a styled input with a search icon.
|
|
34
|
-
*/
|
|
35
|
-
export default createComponent('NSearchbar', nSearchbarProps, (props, context) => {
|
|
36
|
-
const inputRef = ref();
|
|
37
|
-
const exposed = {
|
|
38
|
-
focus: () => {
|
|
39
|
-
inputRef.value?.focus();
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
context.expose(exposed);
|
|
43
|
-
return () => (<div>
|
|
44
|
-
<label for="search" class="sr-only">
|
|
45
|
-
{props.placeholder}
|
|
46
|
-
</label>
|
|
47
|
-
<div class="relative">
|
|
48
|
-
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
49
|
-
<MagnifyingGlassIcon class="h-5 w-5 text-default-400" aria-hidden="true"/>
|
|
50
|
-
</div>
|
|
51
|
-
<input ref={inputRef} value={props.value} onInput={event => props.onUpdateValue?.(event.target.value)} type="text" name="search" placeholder={props.placeholder} autocomplete="off" class={[
|
|
52
|
-
'block w-full pl-10 pr-4 rounded-md border focus:outline-none focus:ring-1 transition placeholder-default-400 border-default-300 focus:border-primary-500 focus:ring-primary-500',
|
|
53
|
-
props.small ? 'py-1' : 'py-2',
|
|
54
|
-
props.inputClass,
|
|
55
|
-
]} onFocus={props.onFocus} onBlur={props.onBlur}/>
|
|
56
|
-
</div>
|
|
57
|
-
</div>);
|
|
58
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { createComponent, createProps } from '../utils/component';
|
|
2
|
-
import { ref } from 'vue';
|
|
3
|
-
import { vModelProps } from '../utils/vModel';
|
|
4
|
-
import NSearchbar, { nSearchbarProps } from './NSearchbar';
|
|
5
|
-
import NSuggestionList, { nSuggestionListPropsForConfig } from './NSuggestionList';
|
|
6
|
-
export const nSearchbarListProps = createProps({
|
|
7
|
-
...nSuggestionListPropsForConfig,
|
|
8
|
-
...vModelProps(String),
|
|
9
|
-
/**
|
|
10
|
-
* @see {@link nSearchbarProps.placeholder}
|
|
11
|
-
*/
|
|
12
|
-
placeholder: nSearchbarProps.placeholder,
|
|
13
|
-
});
|
|
14
|
-
/**
|
|
15
|
-
* The `NSearchbarList` is a {@link NSearchbar} with a {@link NSuggestionList}.
|
|
16
|
-
*/
|
|
17
|
-
export default createComponent('NSearchbarList', nSearchbarListProps, props => {
|
|
18
|
-
const searchbarRef = ref();
|
|
19
|
-
return () => (<NSuggestionList {...props} inputValue={props.value || ''} input={({ onFocus, onBlur }) => (<NSearchbar ref={searchbarRef} value={props.value} onUpdateValue={props.onUpdateValue} placeholder={props.placeholder} inputClass="shadow-lg" onFocus={onFocus} onBlur={onBlur}/>)} onRequestInputFocus={() => searchbarRef.value?.focus()}/>);
|
|
20
|
-
});
|
package/components/NSelect.jsx
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { trsl } from '../i18n';
|
|
2
|
-
import { createComponent, createProps } from '../utils/component';
|
|
3
|
-
import { ref } from 'vue';
|
|
4
|
-
import NTooltip, { mapTooltipProps, nToolTipPropsForImplementor } from './NTooltip';
|
|
5
|
-
import NValInput, { nValInputProps } from './NValInput';
|
|
6
|
-
export const nSelectProps = createProps({
|
|
7
|
-
/**
|
|
8
|
-
* The id of the currently selected option of this input.
|
|
9
|
-
*/
|
|
10
|
-
value: String,
|
|
11
|
-
/**
|
|
12
|
-
* This is called with the newly selected id when the selection has changed.
|
|
13
|
-
* If no id is selected, the empty string is passed, in order to
|
|
14
|
-
* match the API of all other inputs who never pass `undefined`.
|
|
15
|
-
*/
|
|
16
|
-
onUpdateValue: Function,
|
|
17
|
-
/**
|
|
18
|
-
* The different options which can be selected.
|
|
19
|
-
*/
|
|
20
|
-
options: {
|
|
21
|
-
type: Array,
|
|
22
|
-
default: () => [],
|
|
23
|
-
},
|
|
24
|
-
/**
|
|
25
|
-
* @see {@link nValInputProps.name}
|
|
26
|
-
*/
|
|
27
|
-
name: nValInputProps.name,
|
|
28
|
-
/**
|
|
29
|
-
* @see {@link nValInputProps.optional}
|
|
30
|
-
*/
|
|
31
|
-
optional: nValInputProps.optional,
|
|
32
|
-
/**
|
|
33
|
-
* @see {@link nValInputProps.disabled}
|
|
34
|
-
*/
|
|
35
|
-
disabled: nValInputProps.disabled,
|
|
36
|
-
/**
|
|
37
|
-
* @see {@link nValInputProps.form}
|
|
38
|
-
*/
|
|
39
|
-
form: nValInputProps.form,
|
|
40
|
-
...nToolTipPropsForImplementor,
|
|
41
|
-
});
|
|
42
|
-
/**
|
|
43
|
-
* The `NSelect` is a styled html select-input.
|
|
44
|
-
*/
|
|
45
|
-
export default createComponent('NSelect', nSelectProps, (props, context) => {
|
|
46
|
-
const inputRef = ref();
|
|
47
|
-
const exposed = {
|
|
48
|
-
focus: () => inputRef.value?.focus(),
|
|
49
|
-
validate: () => {
|
|
50
|
-
if (inputRef.value == null)
|
|
51
|
-
throw new Error('Can not validate NSelect as its input was undefined');
|
|
52
|
-
return inputRef.value.validate();
|
|
53
|
-
},
|
|
54
|
-
reset: () => inputRef.value?.reset(),
|
|
55
|
-
};
|
|
56
|
-
context.expose(exposed);
|
|
57
|
-
return () => (<NValInput ref={inputRef} {...props} input={slotProps => (<>
|
|
58
|
-
<label for={props.name} class={[
|
|
59
|
-
'block text-sm font-medium mb-1',
|
|
60
|
-
props.disabled ? 'text-default-300' : 'text-default-700',
|
|
61
|
-
]}>
|
|
62
|
-
{props.name}
|
|
63
|
-
</label>
|
|
64
|
-
<NTooltip block {...mapTooltipProps(props)}>
|
|
65
|
-
<select name={props.name} disabled={props.disabled} value={props.value} onChange={event => slotProps.onUpdateValue(event.target.value)} onBlur={slotProps.onBlur} class={[
|
|
66
|
-
'block w-full py-2 pl-4 pr-10 rounded-md border focus:outline-none focus:ring-1',
|
|
67
|
-
props.disabled ? 'text-default-300 ' : 'text-default-900 ',
|
|
68
|
-
slotProps.error
|
|
69
|
-
? 'border-red-500 focus:border-red-500 focus:ring-red-500'
|
|
70
|
-
: 'border-default-300 focus:border-primary-500 focus:ring-primary-500',
|
|
71
|
-
]}>
|
|
72
|
-
<option disabled={!props.optional} selected={!props.value} value="">
|
|
73
|
-
{trsl('vue-collection.action.select')}
|
|
74
|
-
</option>
|
|
75
|
-
{props.options.map(option => (<option key={option.id} value={option.id} selected={props.value == option.id}>
|
|
76
|
-
{option.label}
|
|
77
|
-
</option>))}
|
|
78
|
-
</select>
|
|
79
|
-
</NTooltip>
|
|
80
|
-
</>)}/>);
|
|
81
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { trsl } from '../i18n';
|
|
2
|
-
import { createComponent, createProps } from '../utils/component';
|
|
3
|
-
import { computed, ref } from 'vue';
|
|
4
|
-
import NLoadingIndicator from './NLoadingIndicator';
|
|
5
|
-
export const nSuggestionListPropsForConfig = createProps({
|
|
6
|
-
/**
|
|
7
|
-
* The items which are available to show in the list. The first `maxItems` will be displayed.
|
|
8
|
-
*/
|
|
9
|
-
items: {
|
|
10
|
-
type: Array,
|
|
11
|
-
default: () => [],
|
|
12
|
-
},
|
|
13
|
-
/**
|
|
14
|
-
* The maximum items which are displayed in the list.
|
|
15
|
-
*/
|
|
16
|
-
maxItems: {
|
|
17
|
-
type: Number,
|
|
18
|
-
default: () => 8,
|
|
19
|
-
},
|
|
20
|
-
/**
|
|
21
|
-
* If set to `true` the list is hidden.
|
|
22
|
-
*/
|
|
23
|
-
hideList: Boolean,
|
|
24
|
-
/**
|
|
25
|
-
* If set to `true` the list shows a loading indicator when the `items` array is empty.
|
|
26
|
-
*/
|
|
27
|
-
loading: Boolean,
|
|
28
|
-
/**
|
|
29
|
-
* This is called with the id of the selected item.
|
|
30
|
-
*/
|
|
31
|
-
onSelect: Function,
|
|
32
|
-
/**
|
|
33
|
-
* The slot for every item of the list.
|
|
34
|
-
*/
|
|
35
|
-
listItem: Function,
|
|
36
|
-
/**
|
|
37
|
-
* This function is called, when the input and the suggestion list are really blurred.
|
|
38
|
-
* This means, it's not just the input temporarly beeing blurred because the user clicks on the item list,
|
|
39
|
-
* but the focus has completely disappeared from the input and the list.
|
|
40
|
-
*/
|
|
41
|
-
onRealBlur: Function,
|
|
42
|
-
});
|
|
43
|
-
export const nSuggestionListPropsForInput = createProps({
|
|
44
|
-
/**
|
|
45
|
-
* The slot for the input, which will be enhanced with the suggestion list.
|
|
46
|
-
*/
|
|
47
|
-
input: {
|
|
48
|
-
type: Function,
|
|
49
|
-
required: true,
|
|
50
|
-
},
|
|
51
|
-
/**
|
|
52
|
-
* When this function is called, the parent is required to call focus() on the input element.
|
|
53
|
-
* It won't work properly if the parent does not request focus on the input.
|
|
54
|
-
*/
|
|
55
|
-
onRequestInputFocus: {
|
|
56
|
-
type: Function,
|
|
57
|
-
required: true,
|
|
58
|
-
},
|
|
59
|
-
/**
|
|
60
|
-
* The current value of the input. This is just needed to display the «No results found for {value}» message.
|
|
61
|
-
*/
|
|
62
|
-
inputValue: {
|
|
63
|
-
type: String,
|
|
64
|
-
required: true,
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
export const nSuggestionListProps = createProps({
|
|
68
|
-
...nSuggestionListPropsForConfig,
|
|
69
|
-
...nSuggestionListPropsForInput,
|
|
70
|
-
});
|
|
71
|
-
/**
|
|
72
|
-
* The `NSuggestionList` can be added to an input and adds a list below it which is shown when the input is focused.
|
|
73
|
-
*/
|
|
74
|
-
export default createComponent('NSuggestionList', nSuggestionListProps, props => {
|
|
75
|
-
const selectedIndex = ref(null);
|
|
76
|
-
const displayItems = computed(() => props.items.slice(0, props.maxItems));
|
|
77
|
-
const isInFocus = ref(false);
|
|
78
|
-
const showList = computed(() => isInFocus.value && !props.hideList);
|
|
79
|
-
let listButtonClicked = false;
|
|
80
|
-
const onFocus = () => (isInFocus.value = true);
|
|
81
|
-
const onListMouseDown = () => (listButtonClicked = true);
|
|
82
|
-
const onListMouseLeave = () => props.onRequestInputFocus();
|
|
83
|
-
const onBlur = () => {
|
|
84
|
-
if (!listButtonClicked) {
|
|
85
|
-
isInFocus.value = false;
|
|
86
|
-
props.onRealBlur?.();
|
|
87
|
-
}
|
|
88
|
-
listButtonClicked = false;
|
|
89
|
-
};
|
|
90
|
-
const onSelect = (id) => {
|
|
91
|
-
props.onSelect?.(id);
|
|
92
|
-
props.onRequestInputFocus();
|
|
93
|
-
};
|
|
94
|
-
const keydown = (event) => {
|
|
95
|
-
if (event.key == 'ArrowDown') {
|
|
96
|
-
event.preventDefault();
|
|
97
|
-
nextItem();
|
|
98
|
-
}
|
|
99
|
-
else if (event.key == 'ArrowUp') {
|
|
100
|
-
event.preventDefault();
|
|
101
|
-
previoiusItem();
|
|
102
|
-
}
|
|
103
|
-
else if (event.key == 'Enter') {
|
|
104
|
-
event.preventDefault();
|
|
105
|
-
const index = selectedIndex.value;
|
|
106
|
-
if (index != null && index < displayItems.value.length) {
|
|
107
|
-
onSelect(displayItems.value[index].id);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
const nextItem = () => {
|
|
112
|
-
adjustIndexToSize();
|
|
113
|
-
const currentIndex = selectedIndex.value;
|
|
114
|
-
let nextIndex = currentIndex == null ? 0 : currentIndex + 1;
|
|
115
|
-
if (nextIndex >= displayItems.value.length)
|
|
116
|
-
nextIndex = null;
|
|
117
|
-
selectedIndex.value = nextIndex;
|
|
118
|
-
};
|
|
119
|
-
const previoiusItem = () => {
|
|
120
|
-
adjustIndexToSize();
|
|
121
|
-
const currentIndex = selectedIndex.value;
|
|
122
|
-
let previousIndex = currentIndex == null ? displayItems.value.length - 1 : currentIndex - 1;
|
|
123
|
-
if (previousIndex < 0)
|
|
124
|
-
previousIndex = null;
|
|
125
|
-
selectedIndex.value = previousIndex;
|
|
126
|
-
};
|
|
127
|
-
const adjustIndexToSize = () => {
|
|
128
|
-
if (selectedIndex.value != null && selectedIndex.value >= displayItems.value.length) {
|
|
129
|
-
selectedIndex.value = null;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
return () => (<div onKeydown={keydown}>
|
|
133
|
-
{props.input({ onFocus, onBlur })}
|
|
134
|
-
<div class="relative">
|
|
135
|
-
{showList.value && (<div class="bg-white rounded-md shadow-lg p-2 absolute top-2 left-0 min-w-full z-10">
|
|
136
|
-
<ul>
|
|
137
|
-
{displayItems.value.map((item, index) => (<li key={item.id} class={[
|
|
138
|
-
'focus:outline-none hover:bg-default-50 rounded-md select-none p-2 cursor-pointer',
|
|
139
|
-
selectedIndex.value === index ? 'bg-default-50' : '',
|
|
140
|
-
]} onMousedown={onListMouseDown} onMouseleave={onListMouseLeave} onClick={() => onSelect(item.id)}>
|
|
141
|
-
{props.listItem?.({ item, highlighted: selectedIndex.value === index }) ||
|
|
142
|
-
item.label}
|
|
143
|
-
</li>))}
|
|
144
|
-
|
|
145
|
-
{displayItems.value.length == 0 && (<div class="p-2 text-sm font-medium text-default-700">
|
|
146
|
-
{props.loading ? (<div class="flex items-center space-x-2">
|
|
147
|
-
<NLoadingIndicator size={6}/>
|
|
148
|
-
<span> {trsl('vue-collection.text.loading-search-results')}</span>
|
|
149
|
-
</div>) : (<div>
|
|
150
|
-
{trsl('vue-collection.text.no-search-results', { input: props.inputValue })}
|
|
151
|
-
</div>)}
|
|
152
|
-
</div>)}
|
|
153
|
-
</ul>
|
|
154
|
-
</div>)}
|
|
155
|
-
</div>
|
|
156
|
-
</div>);
|
|
157
|
-
});
|