@naptics/vue-collection 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +5 -1
  2. package/components/NAlert.js +81 -0
  3. package/components/NBadge.js +57 -0
  4. package/components/NBreadcrub.js +66 -0
  5. package/components/NButton.js +65 -0
  6. package/components/NCheckbox.js +42 -0
  7. package/components/NCheckboxLabel.js +39 -0
  8. package/components/NCrudModal.js +105 -0
  9. package/components/NDialog.js +160 -0
  10. package/components/NDropdown.js +108 -0
  11. package/components/NDropzone.js +210 -0
  12. package/components/NForm.js +28 -0
  13. package/components/NFormModal.js +54 -0
  14. package/components/NIconButton.js +81 -0
  15. package/components/NIconCircle.js +66 -0
  16. package/components/NInput.js +105 -0
  17. package/components/NInputPhone.js +46 -0
  18. package/components/NInputSelect.js +114 -0
  19. package/components/NInputSuggestion.js +63 -0
  20. package/components/NLink.js +59 -0
  21. package/components/NList.js +24 -0
  22. package/components/NLoadingIndicator.js +53 -0
  23. package/components/NModal.js +210 -0
  24. package/components/NPagination.js +108 -0
  25. package/components/NSearchbar.js +66 -0
  26. package/components/NSearchbarList.js +36 -0
  27. package/components/NSelect.js +84 -0
  28. package/components/NSuggestionList.js +156 -0
  29. package/components/NTable.js +126 -0
  30. package/components/NTableAction.js +49 -0
  31. package/components/NTextArea.js +128 -0
  32. package/components/NTooltip.js +178 -0
  33. package/components/NValInput.js +104 -0
  34. package/components/ValidatedForm.js +18 -18
  35. package/i18n/index.js +4 -8
  36. package/index.js +1 -1
  37. package/package.json +9 -2
  38. package/utils/breakpoints.js +21 -21
  39. package/utils/component.js +17 -9
  40. package/utils/deferred.js +12 -12
  41. package/utils/identifiable.js +27 -29
  42. package/utils/stringMaxLength.js +8 -13
  43. package/utils/tailwind.js +1 -1
  44. package/utils/utils.js +5 -5
  45. package/utils/vModel.js +82 -73
  46. package/utils/validation.js +55 -81
  47. package/utils/vue.js +7 -5
@@ -0,0 +1,66 @@
1
+ import { createVNode as _createVNode } from "vue";
2
+ import { trsl } from '../i18n';
3
+ import { createComponent, createProps } from '../utils/component';
4
+ import { MagnifyingGlassIcon } from '@heroicons/vue/24/solid';
5
+ import { ref } from 'vue';
6
+ import { vModelProps } from '../utils/vModel';
7
+ export const nSearchbarProps = createProps({
8
+ ...vModelProps(String),
9
+ /**
10
+ * The placeholder of the search-bar.
11
+ */
12
+ placeholder: {
13
+ type: String,
14
+ default: trsl('vue-collection.action.search')
15
+ },
16
+ /**
17
+ * If set to `true` the search-bar is displayed smaller.
18
+ */
19
+ small: Boolean,
20
+ /**
21
+ * The classes are directly added to the input (e.g. for shadow).
22
+ */
23
+ inputClass: String,
24
+ /**
25
+ * This is called when the search-bar receives focus.
26
+ */
27
+ onFocus: Function,
28
+ /**
29
+ * This is called when the search-bar looses focus.
30
+ */
31
+ onBlur: Function
32
+ });
33
+ /**
34
+ * The `NSearchbar` is a styled input with a search icon.
35
+ */
36
+ export default createComponent('NSearchbar', nSearchbarProps, (props, context) => {
37
+ const inputRef = ref();
38
+ const exposed = {
39
+ focus: () => {
40
+ inputRef.value?.focus();
41
+ }
42
+ };
43
+ context.expose(exposed);
44
+ return () => _createVNode("div", null, [_createVNode("label", {
45
+ "for": "search",
46
+ "class": "sr-only"
47
+ }, [props.placeholder]), _createVNode("div", {
48
+ "class": "relative"
49
+ }, [_createVNode("div", {
50
+ "class": "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
51
+ }, [_createVNode(MagnifyingGlassIcon, {
52
+ "class": "h-5 w-5 text-default-400",
53
+ "aria-hidden": "true"
54
+ }, null)]), _createVNode("input", {
55
+ "ref": inputRef,
56
+ "value": props.value,
57
+ "onInput": event => props.onUpdateValue?.(event.target.value),
58
+ "type": "text",
59
+ "name": "search",
60
+ "placeholder": props.placeholder,
61
+ "autocomplete": "off",
62
+ "class": ['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', props.small ? 'py-1' : 'py-2', props.inputClass],
63
+ "onFocus": props.onFocus,
64
+ "onBlur": props.onBlur
65
+ }, null)])]);
66
+ });
@@ -0,0 +1,36 @@
1
+ import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue";
2
+ import { createComponent, createProps } from '../utils/component';
3
+ import { ref } from 'vue';
4
+ import { vModelProps } from '../utils/vModel';
5
+ import NSearchbar, { nSearchbarProps } from './NSearchbar';
6
+ import NSuggestionList, { nSuggestionListPropsForConfig } from './NSuggestionList';
7
+ export const nSearchbarListProps = createProps({
8
+ ...nSuggestionListPropsForConfig,
9
+ ...vModelProps(String),
10
+ /**
11
+ * @see {@link nSearchbarProps.placeholder}
12
+ */
13
+ placeholder: nSearchbarProps.placeholder
14
+ });
15
+ /**
16
+ * The `NSearchbarList` is a {@link NSearchbar} with a {@link NSuggestionList}.
17
+ */
18
+ export default createComponent('NSearchbarList', nSearchbarListProps, props => {
19
+ const searchbarRef = ref();
20
+ return () => _createVNode(NSuggestionList, _mergeProps(props, {
21
+ "inputValue": props.value || '',
22
+ "input": ({
23
+ onFocus,
24
+ onBlur
25
+ }) => _createVNode(NSearchbar, {
26
+ "ref": searchbarRef,
27
+ "value": props.value,
28
+ "onUpdateValue": props.onUpdateValue,
29
+ "placeholder": props.placeholder,
30
+ "inputClass": "shadow-lg",
31
+ "onFocus": onFocus,
32
+ "onBlur": onBlur
33
+ }, null),
34
+ "onRequestInputFocus": () => searchbarRef.value?.focus()
35
+ }), null);
36
+ });
@@ -0,0 +1,84 @@
1
+ import { mergeProps as _mergeProps, createVNode as _createVNode, Fragment as _Fragment } from "vue";
2
+ import { trsl } from '../i18n';
3
+ import { createComponent, createProps } from '../utils/component';
4
+ import { ref } from 'vue';
5
+ import NTooltip, { mapTooltipProps, nToolTipPropsForImplementor } from './NTooltip';
6
+ import NValInput, { nValInputProps } from './NValInput';
7
+ export const nSelectProps = createProps({
8
+ /**
9
+ * The id of the currently selected option of this input.
10
+ */
11
+ value: String,
12
+ /**
13
+ * This is called with the newly selected id when the selection has changed.
14
+ * If no id is selected, the empty string is passed, in order to
15
+ * match the API of all other inputs who never pass `undefined`.
16
+ */
17
+ onUpdateValue: Function,
18
+ /**
19
+ * The different options which can be selected.
20
+ */
21
+ options: {
22
+ type: Array,
23
+ default: () => []
24
+ },
25
+ /**
26
+ * @see {@link nValInputProps.name}
27
+ */
28
+ name: nValInputProps.name,
29
+ /**
30
+ * @see {@link nValInputProps.optional}
31
+ */
32
+ optional: nValInputProps.optional,
33
+ /**
34
+ * @see {@link nValInputProps.disabled}
35
+ */
36
+ disabled: nValInputProps.disabled,
37
+ /**
38
+ * @see {@link nValInputProps.form}
39
+ */
40
+ form: nValInputProps.form,
41
+ ...nToolTipPropsForImplementor
42
+ });
43
+ /**
44
+ * The `NSelect` is a styled html select-input.
45
+ */
46
+ export default createComponent('NSelect', nSelectProps, (props, context) => {
47
+ const inputRef = ref();
48
+ const exposed = {
49
+ focus: () => inputRef.value?.focus(),
50
+ validate: () => {
51
+ if (inputRef.value == null) 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 () => _createVNode(NValInput, _mergeProps({
58
+ "ref": inputRef
59
+ }, props, {
60
+ "input": slotProps => _createVNode(_Fragment, null, [_createVNode("label", {
61
+ "for": props.name,
62
+ "class": ['block text-sm font-medium mb-1', props.disabled ? 'text-default-300' : 'text-default-700']
63
+ }, [props.name]), _createVNode(NTooltip, _mergeProps({
64
+ "block": true
65
+ }, mapTooltipProps(props)), {
66
+ default: () => [_createVNode("select", {
67
+ "name": props.name,
68
+ "disabled": props.disabled,
69
+ "value": props.value,
70
+ "onChange": event => slotProps.onUpdateValue(event.target.value),
71
+ "onBlur": slotProps.onBlur,
72
+ "class": ['block w-full py-2 pl-4 pr-10 rounded-md border focus:outline-none focus:ring-1', props.disabled ? 'text-default-300 ' : 'text-default-900 ', slotProps.error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'border-default-300 focus:border-primary-500 focus:ring-primary-500']
73
+ }, [_createVNode("option", {
74
+ "disabled": !props.optional,
75
+ "selected": !props.value,
76
+ "value": ""
77
+ }, [trsl('vue-collection.action.select')]), props.options.map(option => _createVNode("option", {
78
+ "key": option.id,
79
+ "value": option.id,
80
+ "selected": props.value == option.id
81
+ }, [option.label]))])]
82
+ })])
83
+ }), null);
84
+ });
@@ -0,0 +1,156 @@
1
+ import { createTextVNode as _createTextVNode, createVNode as _createVNode } from "vue";
2
+ import { trsl } from '../i18n';
3
+ import { createComponent, createProps } from '../utils/component';
4
+ import { computed, ref } from 'vue';
5
+ import NLoadingIndicator from './NLoadingIndicator';
6
+ export const nSuggestionListPropsForConfig = createProps({
7
+ /**
8
+ * The items which are available to show in the list. The first `maxItems` will be displayed.
9
+ */
10
+ items: {
11
+ type: Array,
12
+ default: () => []
13
+ },
14
+ /**
15
+ * The maximum items which are displayed in the list.
16
+ */
17
+ maxItems: {
18
+ type: Number,
19
+ default: () => 8
20
+ },
21
+ /**
22
+ * If set to `true` the list is hidden.
23
+ */
24
+ hideList: Boolean,
25
+ /**
26
+ * If set to `true` the list shows a loading indicator when the `items` array is empty.
27
+ */
28
+ loading: Boolean,
29
+ /**
30
+ * This is called with the id of the selected item.
31
+ */
32
+ onSelect: Function,
33
+ /**
34
+ * The slot for every item of the list.
35
+ */
36
+ listItem: Function,
37
+ /**
38
+ * This function is called, when the input and the suggestion list are really blurred.
39
+ * This means, it's not just the input temporarly beeing blurred because the user clicks on the item list,
40
+ * but the focus has completely disappeared from the input and the list.
41
+ */
42
+ onRealBlur: Function
43
+ });
44
+ export const nSuggestionListPropsForInput = createProps({
45
+ /**
46
+ * The slot for the input, which will be enhanced with the suggestion list.
47
+ */
48
+ input: {
49
+ type: Function,
50
+ required: true
51
+ },
52
+ /**
53
+ * When this function is called, the parent is required to call focus() on the input element.
54
+ * It won't work properly if the parent does not request focus on the input.
55
+ */
56
+ onRequestInputFocus: {
57
+ type: Function,
58
+ required: true
59
+ },
60
+ /**
61
+ * The current value of the input. This is just needed to display the «No results found for {value}» message.
62
+ */
63
+ inputValue: {
64
+ type: String,
65
+ required: true
66
+ }
67
+ });
68
+ export const nSuggestionListProps = createProps({
69
+ ...nSuggestionListPropsForConfig,
70
+ ...nSuggestionListPropsForInput
71
+ });
72
+ /**
73
+ * The `NSuggestionList` can be added to an input and adds a list below it which is shown when the input is focused.
74
+ */
75
+ export default createComponent('NSuggestionList', nSuggestionListProps, props => {
76
+ const selectedIndex = ref(null);
77
+ const displayItems = computed(() => props.items.slice(0, props.maxItems));
78
+ const isInFocus = ref(false);
79
+ const showList = computed(() => isInFocus.value && !props.hideList);
80
+ let listButtonClicked = false;
81
+ const onFocus = () => isInFocus.value = true;
82
+ const onListMouseDown = () => listButtonClicked = true;
83
+ const onListMouseLeave = () => props.onRequestInputFocus();
84
+ const onBlur = () => {
85
+ if (!listButtonClicked) {
86
+ isInFocus.value = false;
87
+ props.onRealBlur?.();
88
+ }
89
+ listButtonClicked = false;
90
+ };
91
+ const onSelect = id => {
92
+ props.onSelect?.(id);
93
+ props.onRequestInputFocus();
94
+ };
95
+ const keydown = event => {
96
+ if (event.key == 'ArrowDown') {
97
+ event.preventDefault();
98
+ nextItem();
99
+ } else if (event.key == 'ArrowUp') {
100
+ event.preventDefault();
101
+ previoiusItem();
102
+ } else if (event.key == 'Enter') {
103
+ event.preventDefault();
104
+ const index = selectedIndex.value;
105
+ if (index != null && index < displayItems.value.length) {
106
+ onSelect(displayItems.value[index].id);
107
+ }
108
+ }
109
+ };
110
+ const nextItem = () => {
111
+ adjustIndexToSize();
112
+ const currentIndex = selectedIndex.value;
113
+ let nextIndex = currentIndex == null ? 0 : currentIndex + 1;
114
+ if (nextIndex >= displayItems.value.length) nextIndex = null;
115
+ selectedIndex.value = nextIndex;
116
+ };
117
+ const previoiusItem = () => {
118
+ adjustIndexToSize();
119
+ const currentIndex = selectedIndex.value;
120
+ let previousIndex = currentIndex == null ? displayItems.value.length - 1 : currentIndex - 1;
121
+ if (previousIndex < 0) previousIndex = null;
122
+ selectedIndex.value = previousIndex;
123
+ };
124
+ const adjustIndexToSize = () => {
125
+ if (selectedIndex.value != null && selectedIndex.value >= displayItems.value.length) {
126
+ selectedIndex.value = null;
127
+ }
128
+ };
129
+ return () => _createVNode("div", {
130
+ "onKeydown": keydown
131
+ }, [props.input({
132
+ onFocus,
133
+ onBlur
134
+ }), _createVNode("div", {
135
+ "class": "relative"
136
+ }, [showList.value && _createVNode("div", {
137
+ "class": "bg-white rounded-md shadow-lg p-2 absolute top-2 left-0 min-w-full z-10"
138
+ }, [_createVNode("ul", null, [displayItems.value.map((item, index) => _createVNode("li", {
139
+ "key": item.id,
140
+ "class": ['focus:outline-none hover:bg-default-50 rounded-md select-none p-2 cursor-pointer', selectedIndex.value === index ? 'bg-default-50' : ''],
141
+ "onMousedown": onListMouseDown,
142
+ "onMouseleave": onListMouseLeave,
143
+ "onClick": () => onSelect(item.id)
144
+ }, [props.listItem?.({
145
+ item,
146
+ highlighted: selectedIndex.value === index
147
+ }) || item.label])), displayItems.value.length == 0 && _createVNode("div", {
148
+ "class": "p-2 text-sm font-medium text-default-700"
149
+ }, [props.loading ? _createVNode("div", {
150
+ "class": "flex items-center space-x-2"
151
+ }, [_createVNode(NLoadingIndicator, {
152
+ "size": 6
153
+ }, null), _createVNode("span", null, [_createTextVNode(" "), trsl('vue-collection.text.loading-search-results')])]) : _createVNode("div", null, [trsl('vue-collection.text.no-search-results', {
154
+ input: props.inputValue
155
+ })])])])])])]);
156
+ });
@@ -0,0 +1,126 @@
1
+ import { Fragment as _Fragment, createVNode as _createVNode } from "vue";
2
+ import { isWidthBreakpoint } from '../utils/breakpoints';
3
+ import { createComponent, createProps } from '../utils/component';
4
+ import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/solid';
5
+ import { computed, Fragment, ref, watch } from 'vue';
6
+ import NIconButton from './NIconButton';
7
+ import './NTable.css';
8
+ const N_TABLE_ACTION_KEY = 'action';
9
+ export const nTableProps = createProps({
10
+ /**
11
+ * The headings of the table. These define which columns are shown in the table and in which order.
12
+ */
13
+ headings: {
14
+ type: Array,
15
+ required: true
16
+ },
17
+ /**
18
+ * Details can be added additionally to headings.
19
+ * If details are set a little chevron icon is displayed at the end of each table row
20
+ * and if clicked on it the data is displayed in a key-value fashion.
21
+ * The values of the entries should be passed via the `items` prop
22
+ * in the same way as if the details were normal headings.
23
+ * Note that details can be added dynamically by adding the `breakpoint` property to the headings.
24
+ */
25
+ details: {
26
+ type: Array,
27
+ default: () => []
28
+ },
29
+ /**
30
+ * The items of the table. They consist of an array of table rows.
31
+ * Every tablerow is an object containing elements for the heading keys.
32
+ * The elements can either be a primitive value or a function which returns a {@link JSX.Element}.
33
+ * If the item should be treated as an action (e.g. icon-button to display at the end of the row)
34
+ * the dedicated key 'action' can be used.
35
+ * @see TableRow
36
+ * @example
37
+ * // These headings are defined
38
+ * const headings: TableHeading[] = [
39
+ * { key: 'id', label: 'ID' },
40
+ * { key: 'name', label: 'Name' },
41
+ * { key: 'status', label: 'Status' }
42
+ * ]
43
+ *
44
+ * // Appropriate rows for these headings
45
+ * const items: TableRow[] = [
46
+ * { id: 1, name: 'Hubert', status: () => <NBadge ... />, action: ... }, // Row 1
47
+ * { id: 2, name: 'Franzi', status: () => <NBadge ... />, action: ... } // Row 2
48
+ * ]
49
+ */
50
+ items: {
51
+ type: Array,
52
+ default: () => []
53
+ }
54
+ });
55
+ /**
56
+ * The `NTable` is a styled html table which accepts data and displays it appropriately.
57
+ */
58
+ export default createComponent('NTable', nTableProps, props => {
59
+ const headings = computed(() => {
60
+ // remove all headings which are below the breakpoint
61
+ const headings = props.headings.filter(heading => !heading.breakpoint || isWidthBreakpoint(heading.breakpoint).value);
62
+ // The column for actions is shown if there are details
63
+ // or if any of the items contain an element with the action-key.
64
+ if (showDetails.value || props.items.filter(row => row.action != null).length != 0) {
65
+ headings.push({
66
+ key: N_TABLE_ACTION_KEY
67
+ });
68
+ }
69
+ return headings;
70
+ });
71
+ const details = computed(() => {
72
+ // take all headings which are below the breakpoint
73
+ const details = props.headings.filter(heading => heading.breakpoint && !isWidthBreakpoint(heading.breakpoint).value);
74
+ details.push(...props.details);
75
+ return details;
76
+ });
77
+ const showDetails = computed(() => details.value.length > 0);
78
+ const detailsOpen = ref([]);
79
+ const isDetailsOpen = index => detailsOpen.value[index] || false;
80
+ const toggleDetailsOpen = index => detailsOpen.value[index] = !detailsOpen.value[index];
81
+ // if the items change, reset all open details to closed
82
+ // and create correct amount of booleans for all items
83
+ watch(() => props.items, newItems => detailsOpen.value = Array({
84
+ length: newItems.length
85
+ }).map(() => false), {
86
+ immediate: true
87
+ });
88
+ return () => _createVNode("div", {
89
+ "class": "overflow-x-auto"
90
+ }, [_createVNode("table", {
91
+ "class": "min-w-full text-default-500 text-sm"
92
+ }, [_createVNode("thead", {
93
+ "class": "bg-default-50 "
94
+ }, [_createVNode("tr", null, [headings.value.map(heading => _createVNode("th", {
95
+ "key": heading.key,
96
+ "scope": "col",
97
+ "class": "p-4 table-heading"
98
+ }, [heading.label]))])]), props.items.length > 0 && props.items.map((item, itemIndex) => _createVNode(_Fragment, {
99
+ "key": itemIndex
100
+ }, [_createVNode("tbody", {
101
+ "class": ['border-default-200 border-t', itemIndex % 2 === 0 ? 'bg-white' : 'bg-default-50']
102
+ }, [_createVNode("tr", null, [headings.value.map(heading => _createVNode("td", {
103
+ "key": itemIndex + '-' + heading.key,
104
+ "class": "p-4"
105
+ }, [_createVNode("div", {
106
+ "class": ['flex', heading.emph ? 'font-medium text-default-900' : '', heading.cellClass, heading.key == N_TABLE_ACTION_KEY ? 'justify-end items-center space-x-3' : '']
107
+ }, [item[heading.key] && buildItem(item[heading.key]), heading.key == N_TABLE_ACTION_KEY && showDetails.value && _createVNode(NIconButton, {
108
+ "icon": isDetailsOpen(itemIndex) ? ChevronDownIcon : ChevronUpIcon,
109
+ "onClick": () => toggleDetailsOpen(itemIndex)
110
+ }, null)])]))])]), showDetails.value && isDetailsOpen(itemIndex) && _createVNode("tbody", {
111
+ "class": itemIndex % 2 === 0 ? 'bg-white' : 'bg-default-50'
112
+ }, [details.value.map((detail, detailIndex) => _createVNode("tr", {
113
+ "key": `detail-${detailIndex}`
114
+ }, [_createVNode("td", {
115
+ "class": ['table-heading px-4 py-1', details.value.length - 1 == detailIndex ? 'pb-4' : '']
116
+ }, [detail.label]), _createVNode("td", {
117
+ "class": ['px-4 py-1', details.value.length - 1 == detailIndex ? 'pb-4' : ''],
118
+ "colspan": headings.value.length - 1
119
+ }, [item[detail.key] && buildItem(item[detail.key])])]))])]))])]);
120
+ });
121
+ /**
122
+ * Builds a JSX-Element out of the item
123
+ */
124
+ function buildItem(item) {
125
+ if (typeof item == 'string' || typeof item == 'number') return _createVNode(_Fragment, null, [item]);else return item();
126
+ }
@@ -0,0 +1,49 @@
1
+ import { isVNode as _isVNode, createVNode as _createVNode, Fragment as _Fragment } from "vue";
2
+ import { createComponent, createProps } from '../utils/component';
3
+ import { RouterLink } from 'vue-router';
4
+ import { nButtonProps } from './NButton';
5
+ function _isSlot(s) {
6
+ return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s);
7
+ }
8
+ export const nTableActionProps = createProps({
9
+ /**
10
+ * The route of the action. If set the component will be a {@link RouterLink}.
11
+ */
12
+ route: [String, Object],
13
+ /**
14
+ * The text of the action.
15
+ */
16
+ text: String,
17
+ /**
18
+ * The html attribute, which indicates the type of the button.
19
+ */
20
+ type: nButtonProps.type,
21
+ /**
22
+ * This is called when the action is clicked.
23
+ * It is only called when the `route` prop is not set on the action.
24
+ */
25
+ onClick: Function
26
+ });
27
+ /**
28
+ * The `NTableAction` is a button or {@link RouterLink} which is styled to fit into a table.
29
+ * It is basically styled as an emphasized text in the table.
30
+ */
31
+ export default createComponent('NTableAction', nTableActionProps, (props, {
32
+ slots
33
+ }) => {
34
+ const content = () => slots.default?.() || _createVNode(_Fragment, null, [props.text]);
35
+ const classes = 'text-left font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-default-900 rounded-sm ring-offset-2 text-default-900 hover:underline hover:text-default-700';
36
+ return () => {
37
+ let _slot;
38
+ return props.route ? _createVNode(RouterLink, {
39
+ "to": props.route,
40
+ "class": classes
41
+ }, _isSlot(_slot = content()) ? _slot : {
42
+ default: () => [_slot]
43
+ }) : _createVNode("button", {
44
+ "type": props.type,
45
+ "class": classes,
46
+ "onClick": props.onClick
47
+ }, [content()]);
48
+ };
49
+ });
@@ -0,0 +1,128 @@
1
+ import { withDirectives as _withDirectives, vShow as _vShow, createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
2
+ import { createComponent, createProps } from '../utils/component';
3
+ import { ref } from 'vue';
4
+ import { ExclamationCircleIcon } from '@heroicons/vue/24/solid';
5
+ import NTooltip, { mapTooltipProps, nToolTipPropsForImplementor } from './NTooltip';
6
+ import { vModelProps } from '../utils/vModel';
7
+ import NValInput, { validationProps } from './NValInput';
8
+ const nTextAreaBaseProps = createProps({
9
+ ...vModelProps(String),
10
+ /**
11
+ * The name of the text area. Is displayed as a label above the text area.
12
+ */
13
+ name: String,
14
+ /**
15
+ * The placeholder of the text area.
16
+ */
17
+ placeholder: String,
18
+ /**
19
+ * The html autocomplete attribute of the text area.
20
+ */
21
+ autocomplete: {
22
+ type: String,
23
+ default: 'off'
24
+ },
25
+ /**
26
+ * If set to `true`, the text area is resizable in y-direction.
27
+ */
28
+ resizable: {
29
+ type: Boolean,
30
+ default: true
31
+ },
32
+ /**
33
+ * The initial height of the text area in terms of
34
+ * how many text rows fit inside the text area.
35
+ * The height can be change if {@link nTextAreaProps.resizable} is `true`
36
+ */
37
+ rows: Number,
38
+ /**
39
+ * The maximum length of the input string. Entering longer strings are simply
40
+ * prevented, but no error message is shown to the user.
41
+ */
42
+ maxLength: Number,
43
+ /**
44
+ * If set to `true` the text area is displayed with a red border.
45
+ */
46
+ error: Boolean,
47
+ /**
48
+ * If set to `true` the text area is disabled and no interaction is possible.
49
+ */
50
+ disabled: Boolean,
51
+ /**
52
+ * If set to `true` the text area's label is hidden.
53
+ */
54
+ hideLabel: Boolean,
55
+ /**
56
+ * This is called when the text area reveices focus.
57
+ */
58
+ onFocus: Function,
59
+ /**
60
+ * This is called when the text area looses focus.
61
+ */
62
+ onBlur: Function,
63
+ ...nToolTipPropsForImplementor
64
+ });
65
+ export const nTextAreaProps = createProps({
66
+ ...nTextAreaBaseProps,
67
+ ...validationProps
68
+ });
69
+ export default createComponent('NTextArea', nTextAreaProps, (props, context) => {
70
+ const textAreaRef = ref();
71
+ const exposed = {
72
+ focus: () => textAreaRef.value?.focus()
73
+ };
74
+ context.expose(exposed);
75
+ return () => _createVNode(NValInput, _mergeProps(props, {
76
+ "input": ({
77
+ error,
78
+ onBlur,
79
+ onUpdateValue
80
+ }) => _createVNode(NTextAreaBase, _mergeProps({
81
+ "ref": textAreaRef
82
+ }, {
83
+ ...props,
84
+ error,
85
+ onBlur,
86
+ onUpdateValue
87
+ }), null)
88
+ }), null);
89
+ });
90
+ /**
91
+ * The `NTextArea` wraps the html text area with all the features from {@link NInput} and {@link NValInput}.
92
+ */
93
+ const NTextAreaBase = createComponent('NTextAreaBase', nTextAreaBaseProps, (props, context) => {
94
+ const textAreaRef = ref();
95
+ const exposed = {
96
+ focus: () => textAreaRef.value?.focus()
97
+ };
98
+ context.expose(exposed);
99
+ return () => _createVNode("div", null, [props.name && !props.hideLabel && _createVNode("label", {
100
+ "for": props.name,
101
+ "class": ['block text-sm font-medium mb-1', props.disabled ? 'text-default-300' : 'text-default-700']
102
+ }, [props.name]), _createVNode(NTooltip, _mergeProps({
103
+ "block": true
104
+ }, mapTooltipProps(props)), {
105
+ default: () => [_createVNode("div", {
106
+ "class": "relative"
107
+ }, [_createVNode("textarea", {
108
+ "ref": textAreaRef,
109
+ "name": props.name,
110
+ "value": props.value,
111
+ "onInput": event => props.onUpdateValue?.(event.target.value),
112
+ "placeholder": props.placeholder,
113
+ "autocomplete": props.autocomplete,
114
+ "disabled": props.disabled,
115
+ "rows": props.rows,
116
+ "maxlength": props.maxLength,
117
+ "onFocus": () => props.onFocus?.(),
118
+ "onBlur": () => props.onBlur?.(),
119
+ "onInvalid": event => event.preventDefault(),
120
+ "class": ['block w-full rounded-md border focus:outline-none focus:ring-1 ', props.disabled ? 'text-default-500 placeholder-default-300 bg-default-50' : 'text-default-900 placeholder-default-400 ', props.error ? 'border-red-500 focus:border-red-500 focus:ring-red-500 pr-10' : 'border-default-300 focus:border-primary-500 focus:ring-primary-500', props.resizable ? 'resize-y' : 'resize-none']
121
+ }, null), _withDirectives(_createVNode("div", {
122
+ "class": "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none"
123
+ }, [_createVNode(ExclamationCircleIcon, {
124
+ "class": "h-5 w-5 text-red-700",
125
+ "aria-hidden": "true"
126
+ }, null)]), [[_vShow, props.error]])])]
127
+ })]);
128
+ });