@konoma-development/vue-components 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nuxtrc +1 -0
- package/.playground/app.vue +64 -0
- package/.playground/eslint.config.ts +3 -0
- package/.playground/nuxt.config.ts +9 -0
- package/.tool-versions +1 -0
- package/.vscode/extensions.json +9 -0
- package/.vscode/settings.json +12 -0
- package/README.md +70 -0
- package/app.config.ts +13 -0
- package/components/KonomaTheme.vue +92 -0
- package/components/defaults/button.ts +19 -0
- package/components/defaults/checkbox.ts +13 -0
- package/components/defaults/checkboxList.ts +9 -0
- package/components/defaults/columnChooser.ts +8 -0
- package/components/defaults/input.ts +16 -0
- package/components/defaults/pagination.ts +10 -0
- package/components/defaults/radioButtonGroup.ts +13 -0
- package/components/defaults/select.ts +20 -0
- package/components/defaults/table.ts +16 -0
- package/components/defaults/tabs.ts +10 -0
- package/components/defaults/tag.ts +7 -0
- package/components/defaults/tagList.ts +14 -0
- package/components/defaults/textarea.ts +16 -0
- package/components/form/KonomaCheckbox.vue +68 -0
- package/components/form/KonomaCheckboxList.vue +42 -0
- package/components/form/KonomaForm.vue +71 -0
- package/components/form/KonomaFormField.vue +36 -0
- package/components/form/KonomaInput.vue +92 -0
- package/components/form/KonomaPhoneInput.vue +9 -0
- package/components/form/KonomaRadioButtonGroup.vue +41 -0
- package/components/form/KonomaSelect.vue +9 -0
- package/components/form/KonomaTagList.vue +55 -0
- package/components/form/KonomaTextarea.vue +81 -0
- package/components/form/injectionKeys.ts +8 -0
- package/components/table/KonomaColumnChooser.vue +64 -0
- package/components/table/KonomaColumnChooserEntry.vue +18 -0
- package/components/table/KonomaPagination.vue +81 -0
- package/components/table/KonomaTable.vue +355 -0
- package/components/table/KonomaTableActionEntry.vue +27 -0
- package/components/table/KonomaTableActions.vue +32 -0
- package/components/ui/KonomaButton.vue +109 -0
- package/components/ui/KonomaIcon.vue +13 -0
- package/components/ui/KonomaLoadingIndicator.vue +14 -0
- package/components/ui/KonomaModal.vue +120 -0
- package/components/ui/KonomaTabs.vue +70 -0
- package/components/ui/KonomaTag.vue +49 -0
- package/composables/useKonomaTheme.ts +5 -0
- package/eslint.config.ts +43 -0
- package/index.d.ts +33 -0
- package/nuxt.config.ts +20 -0
- package/package.json +60 -0
- package/tsconfig.json +11 -0
- package/types/form.ts +149 -0
- package/types/table.ts +33 -0
- package/unocss.config.ts +83 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- TODO: The cast for errors[props.name] should not be neccessary -->
|
|
3
|
+
<component
|
|
4
|
+
:is="component"
|
|
5
|
+
v-bind="$props"
|
|
6
|
+
:error="errors && props.name ? errors[props.name as keyof typeof errors] : undefined"
|
|
7
|
+
/>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script lang="ts" setup generic="DataType extends FormDataType">
|
|
11
|
+
import type { FormDataType, FormFieldProps } from '../../types/form';
|
|
12
|
+
import type KonomaCheckbox from './KonomaCheckbox.vue';
|
|
13
|
+
import type KonomaCheckboxList from './KonomaCheckboxList.vue';
|
|
14
|
+
import type KonomaInput from './KonomaInput.vue';
|
|
15
|
+
import type KonomaPhoneInput from './KonomaPhoneInput.vue';
|
|
16
|
+
import type KonomaRadioButtonGroup from './KonomaRadioButtonGroup.vue';
|
|
17
|
+
import type KonomaSelect from './KonomaSelect.vue';
|
|
18
|
+
import type KonomaTagList from './KonomaTagList.vue';
|
|
19
|
+
import type KonomaTextarea from './KonomaTextarea.vue';
|
|
20
|
+
import { formInjectionKeys } from './injectionKeys';
|
|
21
|
+
|
|
22
|
+
const props = defineProps<FormFieldProps<DataType> & { component: keyof FormFieldComponents }>()
|
|
23
|
+
|
|
24
|
+
interface FormFieldComponents {
|
|
25
|
+
checkbox: typeof KonomaCheckbox
|
|
26
|
+
checkboxList: typeof KonomaCheckboxList
|
|
27
|
+
input: typeof KonomaInput
|
|
28
|
+
radioButtonGroup: typeof KonomaRadioButtonGroup
|
|
29
|
+
select: typeof KonomaSelect
|
|
30
|
+
textarea: typeof KonomaTextarea
|
|
31
|
+
tagList: typeof KonomaTagList
|
|
32
|
+
phoneInput: typeof KonomaPhoneInput
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const errors = inject(formInjectionKeys<DataType>().errors)
|
|
36
|
+
</script>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label :class="wrapperClasses">
|
|
3
|
+
<span v-if="$slots.label" :class="labelClasses">
|
|
4
|
+
<slot name="label" /><template v-if="required">*</template>
|
|
5
|
+
</span>
|
|
6
|
+
<div class="relative">
|
|
7
|
+
<div v-if="mask">
|
|
8
|
+
<!-- TODO: masked input -->
|
|
9
|
+
</div>
|
|
10
|
+
<input
|
|
11
|
+
v-else
|
|
12
|
+
:class="combinedClasses"
|
|
13
|
+
:placeholder="placeholder"
|
|
14
|
+
:value="value"
|
|
15
|
+
:step="step"
|
|
16
|
+
:name="name?.toString()"
|
|
17
|
+
v-bind="$attrs"
|
|
18
|
+
@input="(e) => $emit('input', (e.target as HTMLInputElement).value, e)"
|
|
19
|
+
@click="$emit('click', $event)"
|
|
20
|
+
@blur="$emit('blur', $event)"
|
|
21
|
+
@keydown="$emit('keyDown', $event)"
|
|
22
|
+
>
|
|
23
|
+
<KonomaIcon
|
|
24
|
+
v-if="iconLeftPath || iconLeftName"
|
|
25
|
+
:name="iconLeftName"
|
|
26
|
+
:path="iconLeftPath"
|
|
27
|
+
:class="iconLeftClasses"
|
|
28
|
+
@click="$emit('iconLeftClick')"
|
|
29
|
+
/>
|
|
30
|
+
<div v-if="iconRightPath || iconRightName || textRight" :class="wrapperRightClasses">
|
|
31
|
+
<span v-if="textRight">{{ textRight }}</span>
|
|
32
|
+
<KonomaIcon
|
|
33
|
+
v-if="iconRightPath || iconRightName"
|
|
34
|
+
:name="iconRightName"
|
|
35
|
+
:path="iconRightPath"
|
|
36
|
+
:class="iconRightClasses"
|
|
37
|
+
@click="$emit('iconRightClick', $event)"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<template v-if="error && error.length > 0">
|
|
42
|
+
<span v-for="(e, i) in error" :key="i" :class="errorClasses">
|
|
43
|
+
{{ e }}
|
|
44
|
+
</span>
|
|
45
|
+
</template>
|
|
46
|
+
</label>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script lang="ts" setup generic="DataType extends FormDataType">
|
|
50
|
+
import type { FormDataType, FormFieldEmits, FormFieldProps } from '../../types/form';
|
|
51
|
+
import { baseClasses } from '../defaults/input';
|
|
52
|
+
import KonomaIcon from '../ui/KonomaIcon.vue';
|
|
53
|
+
|
|
54
|
+
const props = withDefaults(defineProps<FormFieldProps<DataType> & { class?: string }>(), {
|
|
55
|
+
classes: baseClasses.classes,
|
|
56
|
+
wrapperClasses: baseClasses.wrapperClasses,
|
|
57
|
+
labelClasses: baseClasses.labelClasses,
|
|
58
|
+
iconLeftClasses: baseClasses.iconLeftClasses,
|
|
59
|
+
iconRightClasses: baseClasses.iconRightClasses,
|
|
60
|
+
errorClasses: baseClasses.errorClasses,
|
|
61
|
+
classesError: baseClasses.classesError,
|
|
62
|
+
classesNeutral: baseClasses.classesNeutral,
|
|
63
|
+
additionalClassesIconLeft: baseClasses.additionalClassesIconLeft,
|
|
64
|
+
additionalClassesIconRight: baseClasses.additionalClassesIconRight,
|
|
65
|
+
wrapperRightClasses: baseClasses.wrapperRightClasses,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
defineEmits<FormFieldEmits>()
|
|
69
|
+
|
|
70
|
+
const combinedClasses = computed(() => {
|
|
71
|
+
const classesFull = [props.classes];
|
|
72
|
+
if (props.iconLeftPath || props.iconLeftName) {
|
|
73
|
+
classesFull.push(props.additionalClassesIconLeft);
|
|
74
|
+
}
|
|
75
|
+
if (props.iconRightPath || props.iconRightName || props.textRight) {
|
|
76
|
+
classesFull.push(props.additionalClassesIconRight);
|
|
77
|
+
}
|
|
78
|
+
if (props.centered) {
|
|
79
|
+
classesFull.push('text-center');
|
|
80
|
+
}
|
|
81
|
+
if (props.error && props.error.length > 0) {
|
|
82
|
+
classesFull.push(props.classesError);
|
|
83
|
+
} else {
|
|
84
|
+
classesFull.push(props.classesNeutral);
|
|
85
|
+
}
|
|
86
|
+
if (props.class) {
|
|
87
|
+
classesFull.push(props.class);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return classesFull;
|
|
91
|
+
});
|
|
92
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="wrapperClasses">
|
|
3
|
+
<span :class="labelClasses">
|
|
4
|
+
<slot name="label" /><template v-if="required">*</template>
|
|
5
|
+
</span>
|
|
6
|
+
<div :class="['flex gap-5', arrangement === 'vertical' ? 'flex-col' : 'flex-row'].join(' ')">
|
|
7
|
+
<label v-for="(option, i) in options" :key="i" :class="labelWrapperClasses">
|
|
8
|
+
<div :class="[controlClasses, value?.toString() === option.value.toString() ? classesFilled : classesEmpty].join(' ')">
|
|
9
|
+
<div :class="classes" />
|
|
10
|
+
<input class="appearance-none" type="radio" :name="name?.toString()" :value="option.value.toString()" :checked="value?.toString() === option.value.toString()" v-bind="$attrs" @change="$emit('change', option.value, $event)">
|
|
11
|
+
</div>
|
|
12
|
+
<span :class="optionClasses">{{ option.label }}</span>
|
|
13
|
+
</label>
|
|
14
|
+
</div>
|
|
15
|
+
<template v-if="error && error.length > 0">
|
|
16
|
+
<span v-for="(e, i) in error" :key="i" :class="errorClasses">
|
|
17
|
+
{{ e }}
|
|
18
|
+
</span>
|
|
19
|
+
</template>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script lang="ts" setup generic="DataType extends FormDataType">
|
|
24
|
+
import type { FormDataType, FormFieldEmits, FormFieldProps } from '../../types/form';
|
|
25
|
+
import { baseClasses } from '../defaults/radioButtonGroup';
|
|
26
|
+
|
|
27
|
+
withDefaults(defineProps<FormFieldProps<DataType> & { class?: string }>(), {
|
|
28
|
+
classes: baseClasses.classes,
|
|
29
|
+
controlClasses: baseClasses.controlClasses,
|
|
30
|
+
optionClasses: baseClasses.optionClasses,
|
|
31
|
+
labelClasses: baseClasses.labelClasses,
|
|
32
|
+
classesFilled: baseClasses.classesFilled,
|
|
33
|
+
classesEmpty: baseClasses.classesEmpty,
|
|
34
|
+
wrapperClasses: baseClasses.wrapperClasses,
|
|
35
|
+
labelWrapperClasses: baseClasses.labelWrapperClasses,
|
|
36
|
+
errorClasses: baseClasses.errorClasses,
|
|
37
|
+
arrangement: 'horizontal',
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
defineEmits<FormFieldEmits>()
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label :class="wrapperClasses">
|
|
3
|
+
<span :class="labelClasses">
|
|
4
|
+
<slot name="label" /><template v-if="required">*</template>
|
|
5
|
+
</span>
|
|
6
|
+
<div :class="combinedClasses">
|
|
7
|
+
<KonomaTag
|
|
8
|
+
v-for="(value, i) in values"
|
|
9
|
+
:key="i"
|
|
10
|
+
:title="value.toString()"
|
|
11
|
+
icon-right-name="heroicons:x-mark-16-solid"
|
|
12
|
+
@click="$emit('change', value)"
|
|
13
|
+
/>
|
|
14
|
+
<KonomaTag
|
|
15
|
+
:title="addTagTitle"
|
|
16
|
+
icon-left-name="heroicons:plus-16-solid"
|
|
17
|
+
wrapper-classes="flex flex-row h-6 cursor-pointer items-center justify-center gap-1 rounded-krc-tag-list-add border border-secondary-300 px-3 py-1 bg-white"
|
|
18
|
+
@click="$emit('change', '')"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
<template v-if="error && error.length > 0">
|
|
22
|
+
<span v-for="(e, i) in error" :key="i" :class="errorClasses">
|
|
23
|
+
{{ e }}
|
|
24
|
+
</span>
|
|
25
|
+
</template>
|
|
26
|
+
</label>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script lang="ts" setup generic="DataType extends FormDataType">
|
|
30
|
+
import type { FormDataType, FormFieldEmits, FormFieldProps } from '../../types/form';
|
|
31
|
+
import { baseClasses } from '../defaults/tagList';
|
|
32
|
+
import KonomaTag from '../ui/KonomaTag.vue';
|
|
33
|
+
|
|
34
|
+
const props = withDefaults(defineProps<FormFieldProps<DataType>>(), {
|
|
35
|
+
wrapperClasses: baseClasses.wrapperClasses,
|
|
36
|
+
labelClasses: baseClasses.labelClasses,
|
|
37
|
+
errorClasses: baseClasses.errorClasses,
|
|
38
|
+
classesError: baseClasses.classesError,
|
|
39
|
+
classesNeutral: baseClasses.classesNeutral,
|
|
40
|
+
addTagTitle: '',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
defineEmits<FormFieldEmits>()
|
|
44
|
+
|
|
45
|
+
const combinedClasses = computed(() => {
|
|
46
|
+
const classesFull = [props.classes];
|
|
47
|
+
if (props.error && props.error.length > 0) {
|
|
48
|
+
classesFull.push(props.classesError);
|
|
49
|
+
} else {
|
|
50
|
+
classesFull.push(props.classesNeutral);
|
|
51
|
+
}
|
|
52
|
+
classesFull.push(props.class);
|
|
53
|
+
return classesFull;
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label :class="wrapperClasses">
|
|
3
|
+
<div :class="labelWrapperClasses">
|
|
4
|
+
<span :class="labelClasses">
|
|
5
|
+
<slot name="label" /><template v-if="required">*</template>
|
|
6
|
+
</span>
|
|
7
|
+
<span v-if="maxLength" :class="hintClasses">{{ maxLengthLabel }}</span>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="relative">
|
|
10
|
+
<!-- TODO: Resizable -->
|
|
11
|
+
<textarea
|
|
12
|
+
ref="textareaRef"
|
|
13
|
+
:maxlength="maxLength"
|
|
14
|
+
:value="text"
|
|
15
|
+
:disabled="disabled"
|
|
16
|
+
:name="name?.toString()"
|
|
17
|
+
v-bind="$attrs"
|
|
18
|
+
:class="controlClasses"
|
|
19
|
+
@input="$event => $emit('change', ($event.target as HTMLTextAreaElement).value, $event)"
|
|
20
|
+
@focus="isFocused = true"
|
|
21
|
+
@blur="isFocused = false"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</label>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script lang="ts" setup generic="DataType extends FormDataType">
|
|
28
|
+
import type { FormDataType, FormFieldEmits, FormFieldProps } from '../../types/form';
|
|
29
|
+
import { baseClasses } from '../defaults/textarea';
|
|
30
|
+
|
|
31
|
+
const props = withDefaults(defineProps<FormFieldProps<DataType>>(), {
|
|
32
|
+
classes: baseClasses.classes,
|
|
33
|
+
classesNeutral: baseClasses.classesNeutral,
|
|
34
|
+
classesError: baseClasses.classesError,
|
|
35
|
+
errorClasses: baseClasses.errorClasses,
|
|
36
|
+
labelClasses: baseClasses.labelClasses,
|
|
37
|
+
hintClasses: baseClasses.hintClasses,
|
|
38
|
+
wrapperClasses: baseClasses.wrapperClasses,
|
|
39
|
+
resizeClasses: baseClasses.resizeClasses,
|
|
40
|
+
resizeIconClasses: baseClasses.resizeIconClasses,
|
|
41
|
+
controlClasses: baseClasses.controlClasses,
|
|
42
|
+
labelWrapperClasses: baseClasses.labelWrapperClasses,
|
|
43
|
+
classesDisabled: baseClasses.classesDisabled,
|
|
44
|
+
initialHeight: 100,
|
|
45
|
+
maxLength: 150,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
defineEmits<FormFieldEmits>()
|
|
49
|
+
// const height = ref(props.initialHeight)
|
|
50
|
+
const isFocused = ref(false)
|
|
51
|
+
const textareaRef = useTemplateRef('textareaRef')
|
|
52
|
+
|
|
53
|
+
// const combinedClasses = computed(() => {
|
|
54
|
+
// const classesFull = [];
|
|
55
|
+
|
|
56
|
+
// if (props.disabled) {
|
|
57
|
+
// classesFull.push(props.classesDisabled);
|
|
58
|
+
// } else {
|
|
59
|
+
// classesFull.push(props.classes);
|
|
60
|
+
// }
|
|
61
|
+
// if (props.error && props.error.length > 0) {
|
|
62
|
+
// classesFull.push(props.classesError);
|
|
63
|
+
// } else {
|
|
64
|
+
// classesFull.push(props.classesNeutral);
|
|
65
|
+
// }
|
|
66
|
+
// return classesFull;
|
|
67
|
+
// })
|
|
68
|
+
|
|
69
|
+
const text = computed(() => {
|
|
70
|
+
let t = props.value?.toString() || '';
|
|
71
|
+
if (!isFocused.value) {
|
|
72
|
+
if (!props.replacements) {
|
|
73
|
+
return t;
|
|
74
|
+
}
|
|
75
|
+
Object.entries(props.replacements).forEach(([key, value]) => {
|
|
76
|
+
t = t.replace(new RegExp(`{${key}}`, 'g'), value);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return t;
|
|
80
|
+
})
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { InjectionKey } from 'vue';
|
|
2
|
+
|
|
3
|
+
export function formInjectionKeys<DataType>() {
|
|
4
|
+
return {
|
|
5
|
+
errors: Symbol('errors') as InjectionKey<Record<keyof DataType, string[]>>,
|
|
6
|
+
updateErrors: Symbol('updateErrors') as InjectionKey<(newErrors: Partial<Record<keyof DataType, string[]>>) => void>,
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Dropdown>
|
|
3
|
+
<div :class="wrapperClasses">
|
|
4
|
+
<div :class="headerClasses">
|
|
5
|
+
{{ columnsLabel }}
|
|
6
|
+
</div>
|
|
7
|
+
<div :class="columnsWrapperClasses">
|
|
8
|
+
<KonomaColumnChooserEntry
|
|
9
|
+
v-for="col, i in columns.filter(col => col.title)"
|
|
10
|
+
:id="col.id"
|
|
11
|
+
:key="i"
|
|
12
|
+
:title="col.title?.toString() || ''"
|
|
13
|
+
:visible-columns="visibleColumns"
|
|
14
|
+
:entry-classes="entryClasses"
|
|
15
|
+
:visible-column-classes="visibleColumnClasses"
|
|
16
|
+
:hidden-column-classes="hiddenColumnClasses"
|
|
17
|
+
@update:columns="update"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</Dropdown>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts" setup generic="DataType">
|
|
25
|
+
import type { TableColumn } from '../../types/table';
|
|
26
|
+
import { Dropdown } from 'floating-vue';
|
|
27
|
+
import { baseClasses } from '../defaults/columnChooser';
|
|
28
|
+
import KonomaColumnChooserEntry from './KonomaColumnChooserEntry.vue';
|
|
29
|
+
|
|
30
|
+
const props = withDefaults(defineProps<{
|
|
31
|
+
columns: TableColumn<DataType>[]
|
|
32
|
+
wrapperClasses?: string
|
|
33
|
+
headerClasses?: string
|
|
34
|
+
columnsWrapperClasses?: string
|
|
35
|
+
visibleColumnClasses?: string
|
|
36
|
+
hiddenColumnClasses?: string
|
|
37
|
+
entryClasses?: string
|
|
38
|
+
columnsLabel?: string
|
|
39
|
+
}>(), {
|
|
40
|
+
wrapperClasses: baseClasses.wrapperClasses,
|
|
41
|
+
headerClasses: baseClasses.headerClasses,
|
|
42
|
+
columnsWrapperClasses: baseClasses.columnsWrapperClasses,
|
|
43
|
+
entryClasses: baseClasses.entryClasses,
|
|
44
|
+
visibleColumnClasses: baseClasses.visibleColumnClasses,
|
|
45
|
+
hiddenColumnClasses: baseClasses.hiddenColumnClasses,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits<{
|
|
49
|
+
(e: 'update:columns', column: TableColumn<DataType>): void
|
|
50
|
+
(e: 'getFloatingProps'): Record<string, unknown>
|
|
51
|
+
}>()
|
|
52
|
+
|
|
53
|
+
const visibleColumns = computed(() => {
|
|
54
|
+
return props.columns.filter(column => !column.hidden).map(column => column.id);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function update(id: keyof DataType) {
|
|
58
|
+
const columnToUpdate = props.columns.find(column => column.id === id);
|
|
59
|
+
if (!columnToUpdate) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
emit('update:columns', { ...columnToUpdate, hidden: !columnToUpdate?.hidden });
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :class="[entryClasses, visibleColumns.includes(id) ? visibleColumnClasses : hiddenColumnClasses].join(' ')" @click="$emit('update:columns', id)" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup generic="DataType">
|
|
6
|
+
defineProps<{
|
|
7
|
+
title: string
|
|
8
|
+
id: keyof DataType
|
|
9
|
+
entryClasses: string
|
|
10
|
+
visibleColumnClasses: string
|
|
11
|
+
hiddenColumnClasses: string
|
|
12
|
+
visibleColumns: (keyof DataType)[]
|
|
13
|
+
}>()
|
|
14
|
+
|
|
15
|
+
defineEmits<{
|
|
16
|
+
(e: 'update:columns', id: keyof DataType): void
|
|
17
|
+
}>()
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="showButtons">
|
|
3
|
+
<div :class="resultsClasses">
|
|
4
|
+
<span :class="resultsTextClasses">
|
|
5
|
+
{{ xToY }}
|
|
6
|
+
</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div v-if="totalPages > 1" :class="controlClasses">
|
|
9
|
+
<KonomaIcon :class="previousPageActive ? activeIconClasses : inactiveIconClasses" name="lucide:chevron-first" @click="previousPageActive ? emit('onFirstPage') : undefined" />
|
|
10
|
+
<KonomaIcon :class="previousPageActive ? activeIconClasses : inactiveIconClasses" name="lucide:chevron-left" @click="previousPageActive ? emit('onPreviousPage') : undefined" />
|
|
11
|
+
<div class="w-16">
|
|
12
|
+
<KonomaInput centered :value="pageInternal" @change="pageInternal = +$event" @key-down="onKeyDown($event)" />
|
|
13
|
+
</div>
|
|
14
|
+
<KonomaIcon :class="nextPageActive ? activeIconClasses : inactiveIconClasses" name="lucide:chevron-right" @click="nextPageActive ? emit('onNextPage') : undefined" />
|
|
15
|
+
<KonomaIcon :class="nextPageActive ? activeIconClasses : inactiveIconClasses" name="lucide:chevron-last" @click="nextPageActive ? emit('onLastPage') : undefined" />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div v-else :class="wrapperClasses">
|
|
19
|
+
<div :class="resultsClasses">
|
|
20
|
+
<span :class="resultsTextClasses">
|
|
21
|
+
{{ xToY }}
|
|
22
|
+
</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script lang="ts" setup>
|
|
28
|
+
import { baseClasses } from '../defaults/pagination';
|
|
29
|
+
import KonomaInput from '../form/KonomaInput.vue';
|
|
30
|
+
import KonomaIcon from '../ui/KonomaIcon.vue';
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(defineProps<{
|
|
33
|
+
currentLoaded: number
|
|
34
|
+
currentStart: number
|
|
35
|
+
currentEnd: number
|
|
36
|
+
currentTotal: number
|
|
37
|
+
currentPage: number
|
|
38
|
+
totalPages: number
|
|
39
|
+
inactiveIconClasses?: string
|
|
40
|
+
activeIconClasses?: string
|
|
41
|
+
wrapperClasses?: string
|
|
42
|
+
resultsClasses?: string
|
|
43
|
+
resultsTextClasses?: string
|
|
44
|
+
controlClasses?: string
|
|
45
|
+
xToY: string
|
|
46
|
+
showButtons: boolean
|
|
47
|
+
}>(), {
|
|
48
|
+
wrapperClasses: baseClasses.wrapperClasses,
|
|
49
|
+
resultsClasses: baseClasses.resultsClasses,
|
|
50
|
+
resultsTextClasses: baseClasses.resultsTextClasses,
|
|
51
|
+
activeIconClasses: baseClasses.activeIconClasses,
|
|
52
|
+
inactiveIconClasses: baseClasses.inactiveIconClasses,
|
|
53
|
+
controlClasses: baseClasses.controlClasses,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const emit = defineEmits<{
|
|
57
|
+
(e: 'onFirstPage'): void
|
|
58
|
+
(e: 'onPreviousPage'): void
|
|
59
|
+
(e: 'onNextPage'): void
|
|
60
|
+
(e: 'onLastPage'): void
|
|
61
|
+
(e: 'toPage', page: number): void
|
|
62
|
+
}>()
|
|
63
|
+
|
|
64
|
+
const previousPageActive = computed(() => {
|
|
65
|
+
return props.currentPage !== 1;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const nextPageActive = computed(() => {
|
|
69
|
+
return props.currentPage !== props.totalPages;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const pageInternal = ref(props.currentPage);
|
|
73
|
+
|
|
74
|
+
function onKeyDown(event: KeyboardEvent) {
|
|
75
|
+
switch (event.key) {
|
|
76
|
+
case 'Enter':
|
|
77
|
+
emit('toPage', pageInternal.value);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</script>
|