@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
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
import { external } from '../utils/validation';
|
|
4
|
+
import { computed, Suspense } from 'vue';
|
|
5
|
+
import NValInput, { nValInputProps } from './NValInput';
|
|
6
|
+
import { trsl } from '../i18n';
|
|
7
|
+
export const nInputPhoneProps = createProps(nValInputProps);
|
|
8
|
+
/**
|
|
9
|
+
* The `NInputPhone` autoformats phone numbers and checks if they are valid.
|
|
10
|
+
*/
|
|
11
|
+
export default createComponent('NInputPhoneSuspended', nInputPhoneProps, props => {
|
|
12
|
+
// Async components have to be wrapped in a suspense component.
|
|
13
|
+
return () => _createVNode(Suspense, null, {
|
|
14
|
+
default: () => [_createVNode(NPhoneInput, props, null)]
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
const NPhoneInput = createComponent('NInputPhone', nInputPhoneProps, async props => {
|
|
18
|
+
// import dynamically for better codesplitting as the library is pretty large
|
|
19
|
+
const {
|
|
20
|
+
parsePhoneNumber
|
|
21
|
+
} = await import('awesome-phonenumber');
|
|
22
|
+
const DEFAULT_COUNTRY_CODE = 'CH';
|
|
23
|
+
const formattedToPlain = number => parsePhoneNumber(number, {
|
|
24
|
+
regionCode: DEFAULT_COUNTRY_CODE
|
|
25
|
+
}).number?.e164;
|
|
26
|
+
const plainToFormatted = number => parsePhoneNumber(number, {
|
|
27
|
+
regionCode: DEFAULT_COUNTRY_CODE
|
|
28
|
+
}).number?.international;
|
|
29
|
+
const onUpdateValue = newValue => {
|
|
30
|
+
const plain = formattedToPlain(newValue);
|
|
31
|
+
props.onUpdateValue?.(plain || newValue);
|
|
32
|
+
};
|
|
33
|
+
const value = computed(() => {
|
|
34
|
+
const formatted = plainToFormatted(props.value || '');
|
|
35
|
+
return formatted || props.value;
|
|
36
|
+
});
|
|
37
|
+
const isValid = computed(() => parsePhoneNumber(props.value || '').valid);
|
|
38
|
+
return () => _createVNode(NValInput, _mergeProps({
|
|
39
|
+
...props,
|
|
40
|
+
onUpdateValue
|
|
41
|
+
}, {
|
|
42
|
+
"value": value.value,
|
|
43
|
+
"rules": external(isValid.value, trsl('vue-collection.validation.rules.phone')),
|
|
44
|
+
"type": "tel"
|
|
45
|
+
}), null);
|
|
46
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
import { Id } from '../utils/identifiable';
|
|
4
|
+
import { option } from '../utils/validation';
|
|
5
|
+
import { vModelForRef } from '../utils/vModel';
|
|
6
|
+
import { watchRef } from '../utils/vue';
|
|
7
|
+
import { computed, ref, watch } from 'vue';
|
|
8
|
+
import { nInputProps } from './NInput';
|
|
9
|
+
import NSuggestionList, { nSuggestionListProps } from './NSuggestionList';
|
|
10
|
+
import NValInput, { nValInputProps } from './NValInput';
|
|
11
|
+
export const nInputSelectProps = createProps({
|
|
12
|
+
...nInputProps,
|
|
13
|
+
/**
|
|
14
|
+
* The id of the currently selected option of this input.
|
|
15
|
+
*/
|
|
16
|
+
value: String,
|
|
17
|
+
/**
|
|
18
|
+
* This is called with the newly selected id when the selection has changed.
|
|
19
|
+
* This happens, when an item from the suggestion list is selected or the
|
|
20
|
+
* input matches a selection option exactly.
|
|
21
|
+
* If no id is selected, the empty string is passed, in order to
|
|
22
|
+
* match the API of all other inputs who never pass `undefined`.
|
|
23
|
+
*/
|
|
24
|
+
onUpdateValue: Function,
|
|
25
|
+
/**
|
|
26
|
+
* The options which are allowed and suggested for this input.
|
|
27
|
+
* The options are filtered based on the user input.
|
|
28
|
+
*/
|
|
29
|
+
options: {
|
|
30
|
+
type: Array,
|
|
31
|
+
default: () => []
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* @see {@link nValInputProps.optional}
|
|
35
|
+
*/
|
|
36
|
+
optional: nValInputProps.optional,
|
|
37
|
+
/**
|
|
38
|
+
* @see {@link nValInputProps.form}
|
|
39
|
+
*/
|
|
40
|
+
form: nValInputProps.form,
|
|
41
|
+
/**
|
|
42
|
+
* @see {@link nValInputProps.error}
|
|
43
|
+
*/
|
|
44
|
+
error: nValInputProps.error,
|
|
45
|
+
/**
|
|
46
|
+
* @see {@link nValInputProps.errorMessage}
|
|
47
|
+
*/
|
|
48
|
+
errorMessage: nValInputProps.errorMessage,
|
|
49
|
+
/**
|
|
50
|
+
* If set to `true` the list is hidden even if there are still matching items in the list.
|
|
51
|
+
*/
|
|
52
|
+
hideList: nSuggestionListProps.hideList,
|
|
53
|
+
/**
|
|
54
|
+
* @see {@link nSuggestionListProps.maxItems}
|
|
55
|
+
*/
|
|
56
|
+
maxItems: nSuggestionListProps.maxItems,
|
|
57
|
+
/**
|
|
58
|
+
* @see {@link nSuggestionListProps.listItem}
|
|
59
|
+
*/
|
|
60
|
+
listItem: nSuggestionListProps.listItem
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* The `NInputSelect` is very similar to the {@link NSelect}, but instead of a select input it is a regular input.
|
|
64
|
+
* The user is forced to use a value from the specified options of the input.
|
|
65
|
+
* While they type, the list of options is shown to them and filtered based on their input.
|
|
66
|
+
*/
|
|
67
|
+
export default createComponent('NInputSelect', nInputSelectProps, props => {
|
|
68
|
+
const inputRef = ref();
|
|
69
|
+
const inputValue = ref('');
|
|
70
|
+
watch(() => props.value, newValue => {
|
|
71
|
+
if (newValue) {
|
|
72
|
+
const chosenOption = Id.find(props.options, newValue);
|
|
73
|
+
if (chosenOption) inputValue.value = chosenOption.label;
|
|
74
|
+
}
|
|
75
|
+
}, {
|
|
76
|
+
immediate: true
|
|
77
|
+
});
|
|
78
|
+
const filteredOptions = computed(() => props.options.filter(option => option.label.includes(inputValue.value || '')));
|
|
79
|
+
const matchedOption = computed(() => {
|
|
80
|
+
const matches = props.options.filter(option => option.label === inputValue.value);
|
|
81
|
+
return matches.length > 0 ? matches[0] : null;
|
|
82
|
+
});
|
|
83
|
+
watchRef(matchedOption, newOption => props.onUpdateValue?.(newOption?.id || ''));
|
|
84
|
+
return () => _createVNode(NSuggestionList, {
|
|
85
|
+
"items": filteredOptions.value,
|
|
86
|
+
"onSelect": props.onUpdateValue,
|
|
87
|
+
"inputValue": inputValue.value,
|
|
88
|
+
"hideList": props.hideList || matchedOption.value != null || filteredOptions.value.length == 0,
|
|
89
|
+
"maxItems": props.maxItems,
|
|
90
|
+
"listItem": props.listItem,
|
|
91
|
+
"input": ({
|
|
92
|
+
onFocus,
|
|
93
|
+
onBlur
|
|
94
|
+
}) => _createVNode(NValInput, _mergeProps({
|
|
95
|
+
"ref": inputRef
|
|
96
|
+
}, {
|
|
97
|
+
...props,
|
|
98
|
+
...vModelForRef(inputValue)
|
|
99
|
+
}, {
|
|
100
|
+
"rules": option(props.options.map(opt => opt.label)),
|
|
101
|
+
"onFocus": () => {
|
|
102
|
+
onFocus();
|
|
103
|
+
props.onFocus?.();
|
|
104
|
+
},
|
|
105
|
+
"onBlur": onBlur,
|
|
106
|
+
"disableBlurValidation": true
|
|
107
|
+
}), null),
|
|
108
|
+
"onRequestInputFocus": () => inputRef.value?.focus(),
|
|
109
|
+
"onRealBlur": () => {
|
|
110
|
+
props.onBlur?.();
|
|
111
|
+
inputRef?.value?.validate();
|
|
112
|
+
}
|
|
113
|
+
}, null);
|
|
114
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
import { Id } from '../utils/identifiable';
|
|
4
|
+
import { computed, ref } from 'vue';
|
|
5
|
+
import NSuggestionList, { nSuggestionListProps } from './NSuggestionList';
|
|
6
|
+
import NValInput, { nValInputProps } from './NValInput';
|
|
7
|
+
export const nInputSuggestionProps = createProps({
|
|
8
|
+
...nValInputProps,
|
|
9
|
+
/**
|
|
10
|
+
* If set to `true` the list is hidden even if there are still matching items in the list.
|
|
11
|
+
*/
|
|
12
|
+
hideList: nSuggestionListProps.hideList,
|
|
13
|
+
/**
|
|
14
|
+
* @see {@link nSuggestionListProps.maxItems}
|
|
15
|
+
*/
|
|
16
|
+
maxItems: nSuggestionListProps.maxItems,
|
|
17
|
+
/**
|
|
18
|
+
* The suggestions which are shown to the user for this input.
|
|
19
|
+
* The suggestions are filtered based on the user input.
|
|
20
|
+
*/
|
|
21
|
+
suggestions: {
|
|
22
|
+
type: Array,
|
|
23
|
+
default: () => []
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* `NInputSuggestion` is an input, which shows a list of possible suggestions to the user
|
|
28
|
+
* which is filtered while typing. Contrary to {@link NInputSelect} the user is not required to choose any of the suggestions.
|
|
29
|
+
*/
|
|
30
|
+
export default createComponent('NInputSuggestion', nInputSuggestionProps, props => {
|
|
31
|
+
const suggestionItems = computed(() => props.suggestions.filter(suggestion => suggestion.includes(props.value || '')).map((value, index) => ({
|
|
32
|
+
id: index.toString(),
|
|
33
|
+
label: value
|
|
34
|
+
})));
|
|
35
|
+
const select = id => props.onUpdateValue?.(Id.find(suggestionItems.value, id)?.label || '');
|
|
36
|
+
const hideList = computed(() => props.hideList || suggestionItems.value.length == 0 || suggestionItems.value.filter(suggestion => suggestion.label !== props.value).length == 0);
|
|
37
|
+
const inputRef = ref();
|
|
38
|
+
return () => _createVNode(NSuggestionList, {
|
|
39
|
+
"items": suggestionItems.value,
|
|
40
|
+
"onSelect": id => select(id),
|
|
41
|
+
"inputValue": props.value || '',
|
|
42
|
+
"hideList": hideList.value,
|
|
43
|
+
"maxItems": props.maxItems,
|
|
44
|
+
"input": ({
|
|
45
|
+
onFocus,
|
|
46
|
+
onBlur
|
|
47
|
+
}) => _createVNode(NValInput, _mergeProps({
|
|
48
|
+
"ref": inputRef
|
|
49
|
+
}, props, {
|
|
50
|
+
"onFocus": () => {
|
|
51
|
+
onFocus();
|
|
52
|
+
props.onFocus?.();
|
|
53
|
+
},
|
|
54
|
+
"onBlur": onBlur,
|
|
55
|
+
"disableBlurValidation": true
|
|
56
|
+
}), null),
|
|
57
|
+
"onRequestInputFocus": () => inputRef.value?.focus(),
|
|
58
|
+
"onRealBlur": () => {
|
|
59
|
+
props.onBlur?.();
|
|
60
|
+
inputRef?.value?.validate();
|
|
61
|
+
}
|
|
62
|
+
}, null);
|
|
63
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createVNode as _createVNode } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
import { computed } from 'vue';
|
|
4
|
+
import { RouterLink } from 'vue-router';
|
|
5
|
+
export const nLinkProps = createProps({
|
|
6
|
+
/**
|
|
7
|
+
* The text of the link. Can also be set in the default slot.
|
|
8
|
+
*/
|
|
9
|
+
text: String,
|
|
10
|
+
/**
|
|
11
|
+
* The route of the link. If this is set,
|
|
12
|
+
* the link becomes a {@link RouterLink} and does not emit the `onClick` event.
|
|
13
|
+
*/
|
|
14
|
+
route: [Object, String],
|
|
15
|
+
/**
|
|
16
|
+
* The color of the link.
|
|
17
|
+
*/
|
|
18
|
+
color: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: 'primary'
|
|
21
|
+
},
|
|
22
|
+
/**
|
|
23
|
+
* The text size, a standard tailwind text-size class.
|
|
24
|
+
*/
|
|
25
|
+
textSize: String,
|
|
26
|
+
/**
|
|
27
|
+
* The shade of the link.
|
|
28
|
+
*/
|
|
29
|
+
shade: {
|
|
30
|
+
type: Number,
|
|
31
|
+
default: 500
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* This is called when the link is clicked but only, if the `route` prop is not set.
|
|
35
|
+
* If the `route` prop is not set, the link will act as a regular button.
|
|
36
|
+
*/
|
|
37
|
+
onClick: Function
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* The `NLink` is a styled text which can be used as a {@link RouterLink} or a regular button.
|
|
41
|
+
*/
|
|
42
|
+
export default createComponent('NLink', nLinkProps, (props, {
|
|
43
|
+
slots
|
|
44
|
+
}) => {
|
|
45
|
+
const hoverShade = computed(() => {
|
|
46
|
+
const shade = props.shade;
|
|
47
|
+
if (shade <= 500) return shade + 100;else return shade - 200;
|
|
48
|
+
});
|
|
49
|
+
const classes = computed(() => ['font-medium focus:outline-none focus-visible:ring-2 rounded-sm ring-offset-2 hover:underline text-left', `${props.textSize} text-${props.color}-${props.shade} hover:text-${props.color}-${hoverShade.value} focus-visible:ring-${props.color}-${props.shade}`]);
|
|
50
|
+
return () => props.route ? _createVNode(RouterLink, {
|
|
51
|
+
"to": props.route,
|
|
52
|
+
"class": classes.value
|
|
53
|
+
}, {
|
|
54
|
+
default: () => [slots.default?.() || props.text]
|
|
55
|
+
}) : _createVNode("button", {
|
|
56
|
+
"onClick": props.onClick,
|
|
57
|
+
"class": classes.value
|
|
58
|
+
}, [slots.default?.() || props.text]);
|
|
59
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createVNode as _createVNode } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
export const nListProps = createProps({
|
|
4
|
+
/**
|
|
5
|
+
* The items which are displayed in the list.
|
|
6
|
+
*/
|
|
7
|
+
items: {
|
|
8
|
+
type: Array,
|
|
9
|
+
default: () => []
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* The `NList` displays key-value data in an appealing way.
|
|
14
|
+
*/
|
|
15
|
+
export default createComponent('NList', nListProps, props => {
|
|
16
|
+
return () => _createVNode("dl", null, [props.items.map((item, index) => _createVNode("div", {
|
|
17
|
+
"key": index,
|
|
18
|
+
"class": ['py-5 px-4 sm:grid sm:grid-cols-3 sm:gap-4', index % 2 === 1 ? 'bg-white' : 'bg-default-50']
|
|
19
|
+
}, [_createVNode("dt", {
|
|
20
|
+
"class": "text-sm font-medium text-default-500"
|
|
21
|
+
}, [item.title]), _createVNode("dd", {
|
|
22
|
+
"class": "mt-1 text-sm sm:mt-0 sm:col-span-2"
|
|
23
|
+
}, [item.text])]))]);
|
|
24
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createVNode as _createVNode } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
import { computed, useCssVars } from 'vue';
|
|
4
|
+
import './NLoadingIndicator.css';
|
|
5
|
+
export const nLoadingIndicator = createProps({
|
|
6
|
+
/**
|
|
7
|
+
* The color of the loading-indicator.
|
|
8
|
+
*/
|
|
9
|
+
color: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: 'primary'
|
|
12
|
+
},
|
|
13
|
+
/**
|
|
14
|
+
* The shade of the loading-indicator.
|
|
15
|
+
*/
|
|
16
|
+
shade: {
|
|
17
|
+
type: Number,
|
|
18
|
+
default: 400
|
|
19
|
+
},
|
|
20
|
+
/**
|
|
21
|
+
* The height of the loading-indicator in px.
|
|
22
|
+
*/
|
|
23
|
+
size: {
|
|
24
|
+
type: Number,
|
|
25
|
+
default: 10
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* The `NLoadingIndicator` is a styled loading indicator.
|
|
30
|
+
*/
|
|
31
|
+
export default createComponent('NLoadingIndicator', nLoadingIndicator, props => {
|
|
32
|
+
const gap = computed(() => props.size / 13 * 24);
|
|
33
|
+
const totalWidth = computed(() => gap.value * 2 + props.size);
|
|
34
|
+
useCssVars(() => ({
|
|
35
|
+
'n-loading-indicator-gap': `${gap.value}px`
|
|
36
|
+
}));
|
|
37
|
+
return () => _createVNode("div", {
|
|
38
|
+
"class": "lds-ellipsis",
|
|
39
|
+
"style": `height:${props.size}px;width:${totalWidth.value}px`
|
|
40
|
+
}, [_createVNode("div", {
|
|
41
|
+
"class": `bg-${props.color}-${props.shade}`,
|
|
42
|
+
"style": `height:${props.size}px;width:${props.size}px;left:0px`
|
|
43
|
+
}, null), _createVNode("div", {
|
|
44
|
+
"class": `bg-${props.color}-${props.shade}`,
|
|
45
|
+
"style": `height:${props.size}px;width:${props.size}px;left:0px`
|
|
46
|
+
}, null), _createVNode("div", {
|
|
47
|
+
"class": `bg-${props.color}-${props.shade}`,
|
|
48
|
+
"style": `height:${props.size}px;width:${props.size}px;left:${gap.value}px`
|
|
49
|
+
}, null), _createVNode("div", {
|
|
50
|
+
"class": `bg-${props.color}-${props.shade}`,
|
|
51
|
+
"style": `height:${props.size}px;width:${props.size}px;left:${2 * gap.value}px`
|
|
52
|
+
}, null)]);
|
|
53
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createTextVNode as _createTextVNode, createVNode as _createVNode } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
import { Dialog, DialogOverlay, DialogTitle, TransitionRoot, TransitionChild } from '@headlessui/vue';
|
|
4
|
+
import NButton from './NButton';
|
|
5
|
+
import NIconButton from './NIconButton';
|
|
6
|
+
import { XMarkIcon } from '@heroicons/vue/24/solid';
|
|
7
|
+
import { trsl } from '../i18n';
|
|
8
|
+
import { vModelProps } from '../utils/vModel';
|
|
9
|
+
export const nModalProps = createProps({
|
|
10
|
+
...vModelProps(Boolean),
|
|
11
|
+
/**
|
|
12
|
+
* If set to `true` the header of the modal is hidden.
|
|
13
|
+
*/
|
|
14
|
+
hideHeader: Boolean,
|
|
15
|
+
/**
|
|
16
|
+
* If set to `true` the footer of the modal is hidden.
|
|
17
|
+
*/
|
|
18
|
+
hideFooter: Boolean,
|
|
19
|
+
/**
|
|
20
|
+
* If set to `true` the X-button in the top right is hidden.
|
|
21
|
+
*/
|
|
22
|
+
hideX: Boolean,
|
|
23
|
+
/**
|
|
24
|
+
* The maximum width of the modal. A regular tailwind class.
|
|
25
|
+
*/
|
|
26
|
+
maxWidth: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: 'max-w-md'
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* The title of the modal which is displayed in the header.
|
|
32
|
+
*/
|
|
33
|
+
title: String,
|
|
34
|
+
/**
|
|
35
|
+
* The text of the ok-button.
|
|
36
|
+
*/
|
|
37
|
+
okText: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: trsl('vue-collection.action.save')
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* The color of the ok-button.
|
|
43
|
+
*/
|
|
44
|
+
okColor: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: 'primary'
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* If set to `true` the modal is closed when `onOk` is called.
|
|
50
|
+
*/
|
|
51
|
+
closeOnOk: {
|
|
52
|
+
type: Boolean,
|
|
53
|
+
default: true
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* If set to `true` the ok-button is hidden.
|
|
57
|
+
*/
|
|
58
|
+
hideOk: Boolean,
|
|
59
|
+
/**
|
|
60
|
+
* If set to `true` the ok-button is disabled.
|
|
61
|
+
*/
|
|
62
|
+
okDisabled: Boolean,
|
|
63
|
+
/**
|
|
64
|
+
* The text of the cancel-button.
|
|
65
|
+
*/
|
|
66
|
+
cancelText: {
|
|
67
|
+
type: String,
|
|
68
|
+
default: trsl('vue-collection.action.cancel')
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* The color of the cancel-button.
|
|
72
|
+
*/
|
|
73
|
+
cancelColor: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: 'default'
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* If set to `true`, the modal is closed when clicking on the background.
|
|
79
|
+
* This will call `onCancel`. Default is `true`.
|
|
80
|
+
*/
|
|
81
|
+
closeOnBackground: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: true
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* If set to `true` the cancel-button is hidden.
|
|
87
|
+
*/
|
|
88
|
+
hideCancel: Boolean,
|
|
89
|
+
/**
|
|
90
|
+
* This is called when the ok-button was clicked.
|
|
91
|
+
*/
|
|
92
|
+
onOk: Function,
|
|
93
|
+
/**
|
|
94
|
+
* This is called when the cancel-button or X-button was clicked or
|
|
95
|
+
* if the modal was closed by clicking on the background.
|
|
96
|
+
*/
|
|
97
|
+
onCancel: Function,
|
|
98
|
+
/**
|
|
99
|
+
* A slot to replace the whole modal content including all buttons, header and footer.
|
|
100
|
+
*/
|
|
101
|
+
modal: Function,
|
|
102
|
+
/**
|
|
103
|
+
* A slot to replace the whole header section (excluding the x).
|
|
104
|
+
*/
|
|
105
|
+
header: Function,
|
|
106
|
+
/**
|
|
107
|
+
* A slot to replace the whole footer section.
|
|
108
|
+
*/
|
|
109
|
+
footer: Function
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* The `NModal` is the base component for all modals and dialogs.
|
|
113
|
+
* It provides the core mechanics to display a window in front of everything else.
|
|
114
|
+
*/
|
|
115
|
+
export default createComponent('NModal', nModalProps, (props, {
|
|
116
|
+
slots
|
|
117
|
+
}) => {
|
|
118
|
+
const ok = () => {
|
|
119
|
+
props.onOk?.();
|
|
120
|
+
if (props.closeOnOk) props.onUpdateValue?.(false);
|
|
121
|
+
};
|
|
122
|
+
const cancel = () => {
|
|
123
|
+
props.onCancel?.();
|
|
124
|
+
props.onUpdateValue?.(false);
|
|
125
|
+
};
|
|
126
|
+
return () => _createVNode(TransitionRoot, {
|
|
127
|
+
"as": "template",
|
|
128
|
+
"show": props.value
|
|
129
|
+
}, {
|
|
130
|
+
default: () => [_createVNode(Dialog, {
|
|
131
|
+
"as": "div",
|
|
132
|
+
"static": true,
|
|
133
|
+
"class": "fixed z-40 inset-0 overflow-y-auto",
|
|
134
|
+
"onClose": cancel,
|
|
135
|
+
"open": props.value
|
|
136
|
+
}, {
|
|
137
|
+
default: () => [_createVNode("div", {
|
|
138
|
+
"class": "flex items-center justify-center min-h-screen"
|
|
139
|
+
}, [_createVNode(TransitionChild, {
|
|
140
|
+
"as": "template",
|
|
141
|
+
"enter": "ease-out duration-300",
|
|
142
|
+
"enter-from": "opacity-0",
|
|
143
|
+
"enter-to": "opacity-100",
|
|
144
|
+
"leave": "ease-in duration-200",
|
|
145
|
+
"leave-from": "opacity-100",
|
|
146
|
+
"leave-to": "opacity-0"
|
|
147
|
+
}, {
|
|
148
|
+
default: () => [props.closeOnBackground ? _createVNode(DialogOverlay, {
|
|
149
|
+
"class": "fixed inset-0 bg-default-700 bg-opacity-75"
|
|
150
|
+
}, null) : _createVNode("div", {
|
|
151
|
+
"class": "fixed inset-0 bg-default-700 bg-opacity-75"
|
|
152
|
+
}, null)]
|
|
153
|
+
}), _createVNode("span", {
|
|
154
|
+
"class": "hidden align-middle h-screen",
|
|
155
|
+
"aria-hidden": "true"
|
|
156
|
+
}, [_createTextVNode("\u200B")]), _createVNode(TransitionChild, {
|
|
157
|
+
"as": "template",
|
|
158
|
+
"enter": "ease-out duration-300",
|
|
159
|
+
"enter-from": "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
|
|
160
|
+
"enter-to": "opacity-100 translate-y-0 sm:scale-100",
|
|
161
|
+
"leave": "ease-in duration-200",
|
|
162
|
+
"leave-from": "opacity-100 translate-y-0 sm:scale-100",
|
|
163
|
+
"leave-to": "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
|
164
|
+
}, {
|
|
165
|
+
default: () => [_createVNode("div", {
|
|
166
|
+
"class": ['transform m-4 w-full align-middle', props.maxWidth]
|
|
167
|
+
}, [props.modal?.({
|
|
168
|
+
ok,
|
|
169
|
+
cancel
|
|
170
|
+
}) || _createVNode("div", {
|
|
171
|
+
"class": ['shadow-xl rounded-lg bg-white divide-y divide-default-100', props.maxWidth]
|
|
172
|
+
}, [!props.hideX && _createVNode("div", {
|
|
173
|
+
"class": "sm:block absolute top-0 right-0 mt-3 mr-3"
|
|
174
|
+
}, [_createVNode(NIconButton, {
|
|
175
|
+
"icon": XMarkIcon,
|
|
176
|
+
"color": "default",
|
|
177
|
+
"size": 5,
|
|
178
|
+
"onClick": cancel
|
|
179
|
+
}, null)]), !props.hideHeader && _createVNode("div", {
|
|
180
|
+
"class": "px-4 sm:px-6 pt-4 pb-2 bg-default-50 rounded-t-lg"
|
|
181
|
+
}, [props.header?.() || _createVNode(DialogTitle, {
|
|
182
|
+
"as": "h4",
|
|
183
|
+
"class": "text-lg font-semibold"
|
|
184
|
+
}, {
|
|
185
|
+
default: () => [props.title]
|
|
186
|
+
})]), _createVNode("div", {
|
|
187
|
+
"class": "px-4 sm:px-6 py-4 rounded-lg"
|
|
188
|
+
}, [slots.default?.()]), !props.hideFooter && _createVNode("div", {
|
|
189
|
+
"class": "px-4 sm:px-6 pb-4 pt-2 bg-default-50 rounded-b-lg"
|
|
190
|
+
}, [props.footer?.({
|
|
191
|
+
ok,
|
|
192
|
+
cancel
|
|
193
|
+
}) || _createVNode("div", {
|
|
194
|
+
"class": "flex justify-end space-x-2"
|
|
195
|
+
}, [!props.hideCancel && _createVNode(NButton, {
|
|
196
|
+
"color": props.cancelColor,
|
|
197
|
+
"onClick": cancel
|
|
198
|
+
}, {
|
|
199
|
+
default: () => [props.cancelText]
|
|
200
|
+
}), !props.hideOk && _createVNode(NButton, {
|
|
201
|
+
"color": props.okColor,
|
|
202
|
+
"onClick": ok,
|
|
203
|
+
"disabled": props.okDisabled
|
|
204
|
+
}, {
|
|
205
|
+
default: () => [props.okText]
|
|
206
|
+
})])])])])]
|
|
207
|
+
})])]
|
|
208
|
+
})]
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createVNode as _createVNode, createTextVNode as _createTextVNode } from "vue";
|
|
2
|
+
import { createComponent, createProps } from '../utils/component';
|
|
3
|
+
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/vue/24/solid';
|
|
4
|
+
import { computed, watch } from 'vue';
|
|
5
|
+
import './NPagination.css';
|
|
6
|
+
export const nPaginationProps = createProps({
|
|
7
|
+
/**
|
|
8
|
+
* The page number which is currently selected.
|
|
9
|
+
*/
|
|
10
|
+
value: {
|
|
11
|
+
type: Number,
|
|
12
|
+
default: () => 1
|
|
13
|
+
},
|
|
14
|
+
/**
|
|
15
|
+
* This is called, when a new page number has been selected.
|
|
16
|
+
*/
|
|
17
|
+
onUpdateValue: Function,
|
|
18
|
+
/**
|
|
19
|
+
* The total pages which exists. This is needed to correctly display the selectable pages.
|
|
20
|
+
*/
|
|
21
|
+
total: {
|
|
22
|
+
type: Number,
|
|
23
|
+
default: () => 1
|
|
24
|
+
},
|
|
25
|
+
/**
|
|
26
|
+
* If set to `true`, the pagination is displayed smaller.
|
|
27
|
+
*/
|
|
28
|
+
small: Boolean,
|
|
29
|
+
/**
|
|
30
|
+
* This is called, when the visible pages, which are selectable in the pagination, have changed.
|
|
31
|
+
* This is useful as only these pages can be navigated to on the next click.
|
|
32
|
+
* This information can be useful for prefetching.
|
|
33
|
+
*/
|
|
34
|
+
onVisiblePagesChanged: Function
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* The `NPagination` is a styled pagination component.
|
|
38
|
+
*/
|
|
39
|
+
export default createComponent('NPagination', nPaginationProps, props => {
|
|
40
|
+
const numbers = computed(() => {
|
|
41
|
+
if (props.total <= 7) {
|
|
42
|
+
return range(1, props.total);
|
|
43
|
+
} else if (props.value <= 4) {
|
|
44
|
+
return [...range(1, 5), -1, props.total];
|
|
45
|
+
} else if (props.value >= props.total - 3) {
|
|
46
|
+
return [1, -1, ...range(props.total - 4, props.total)];
|
|
47
|
+
} else {
|
|
48
|
+
return [1, -1, ...range(props.value - 1, props.value + 1), -1, props.total];
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
watch(() => numbers.value, () => {
|
|
52
|
+
const visiblePages = numbers.value.filter(value => value != -1);
|
|
53
|
+
props.onVisiblePagesChanged?.(visiblePages);
|
|
54
|
+
}, {
|
|
55
|
+
immediate: true
|
|
56
|
+
});
|
|
57
|
+
const clickedNumber = value => {
|
|
58
|
+
if (value <= props.total && value >= 1) props.onUpdateValue?.(value);
|
|
59
|
+
};
|
|
60
|
+
const next = () => clickedNumber(props.value + 1);
|
|
61
|
+
const previous = () => clickedNumber(props.value - 1);
|
|
62
|
+
const items = computed(() => numbers.value.map(number => {
|
|
63
|
+
if (number == -1) return {
|
|
64
|
+
label: '...',
|
|
65
|
+
selectable: false,
|
|
66
|
+
selected: false
|
|
67
|
+
};else return {
|
|
68
|
+
label: `${number}`,
|
|
69
|
+
selectable: true,
|
|
70
|
+
selected: number == props.value,
|
|
71
|
+
click: () => clickedNumber(number)
|
|
72
|
+
};
|
|
73
|
+
}));
|
|
74
|
+
const classesForItem = item => ['pagination-item', item.selectable ? 'selectable ' : '', item.selected ? 'selected' : '', props.small ? '' : 'not-small'];
|
|
75
|
+
return () => _createVNode("nav", {
|
|
76
|
+
"class": "inline-flex rounded-md shadow -space-x-px"
|
|
77
|
+
}, [_createVNode("button", {
|
|
78
|
+
"class": ['pagination-item selectable rounded-l-md', props.small ? '' : 'not-small'],
|
|
79
|
+
"onClick": previous
|
|
80
|
+
}, [_createVNode("span", {
|
|
81
|
+
"class": "sr-only"
|
|
82
|
+
}, [_createTextVNode("Previous")]), _createVNode(ChevronLeftIcon, {
|
|
83
|
+
"class": "h-5 w-5",
|
|
84
|
+
"aria-hidden": "true"
|
|
85
|
+
}, null)]), items.value.map((item, index) => item.selectable ? _createVNode("button", {
|
|
86
|
+
"key": index,
|
|
87
|
+
"class": classesForItem(item),
|
|
88
|
+
"onClick": item.click
|
|
89
|
+
}, [item.label]) : _createVNode("div", {
|
|
90
|
+
"key": index,
|
|
91
|
+
"class": classesForItem(item)
|
|
92
|
+
}, [item.label])), _createVNode("button", {
|
|
93
|
+
"class": ['pagination-item selectable rounded-r-md', props.small ? '' : 'not-small'],
|
|
94
|
+
"onClick": next
|
|
95
|
+
}, [_createVNode("span", {
|
|
96
|
+
"class": "sr-only"
|
|
97
|
+
}, [_createTextVNode("Next")]), _createVNode(ChevronRightIcon, {
|
|
98
|
+
"class": "h-5 w-5",
|
|
99
|
+
"aria-hidden": "true"
|
|
100
|
+
}, null)])]);
|
|
101
|
+
});
|
|
102
|
+
function range(from, to) {
|
|
103
|
+
const array = [];
|
|
104
|
+
for (let i = from; i <= to; i++) {
|
|
105
|
+
array.push(i);
|
|
106
|
+
}
|
|
107
|
+
return array;
|
|
108
|
+
}
|