quasar-ui-danx 0.0.10 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +3 -2
- package/src/components/ActionTable/ActionTable.vue +135 -0
- package/src/components/ActionTable/BatchActionMenu.vue +60 -0
- package/src/components/ActionTable/EmptyTableState.vue +33 -0
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +36 -0
- package/src/components/ActionTable/Filters/FilterGroupItem.vue +28 -0
- package/src/components/ActionTable/Filters/FilterGroupList.vue +76 -0
- package/src/components/ActionTable/Filters/FilterListToggle.vue +50 -0
- package/src/components/ActionTable/Filters/FilterableField.vue +141 -0
- package/src/components/ActionTable/Form/Fields/BooleanField.vue +37 -0
- package/src/components/ActionTable/Form/Fields/ConfirmPasswordField.vue +46 -0
- package/src/components/ActionTable/Form/Fields/DateField.vue +59 -0
- package/src/components/ActionTable/Form/Fields/DateRangeField.vue +110 -0
- package/src/components/ActionTable/Form/Fields/DateTimeField.vue +50 -0
- package/src/components/ActionTable/Form/Fields/DateTimePicker.vue +59 -0
- package/src/components/ActionTable/Form/Fields/EditableDiv.vue +39 -0
- package/src/components/ActionTable/Form/Fields/FieldLabel.vue +32 -0
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +78 -0
- package/src/components/ActionTable/Form/Fields/InlineDateTimeField.vue +44 -0
- package/src/components/ActionTable/Form/Fields/IntegerField.vue +26 -0
- package/src/components/ActionTable/Form/Fields/LabeledInput.vue +63 -0
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +91 -0
- package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +57 -0
- package/src/components/ActionTable/Form/Fields/NewPasswordField.vue +39 -0
- package/src/components/ActionTable/Form/Fields/NumberField.vue +94 -0
- package/src/components/ActionTable/Form/Fields/NumberRangeField.vue +140 -0
- package/src/components/ActionTable/Form/Fields/SelectDrawer.vue +136 -0
- package/src/components/ActionTable/Form/Fields/SelectField.vue +318 -0
- package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +81 -0
- package/src/components/ActionTable/Form/Fields/SingleFileField.vue +78 -0
- package/src/components/ActionTable/Form/Fields/TextField.vue +82 -0
- package/src/components/ActionTable/Form/Fields/WysiwygField.vue +46 -0
- package/src/components/ActionTable/Form/Fields/index.ts +23 -0
- package/src/components/ActionTable/Form/RenderedForm.vue +74 -0
- package/src/components/ActionTable/RenderComponentColumn.vue +22 -0
- package/src/components/ActionTable/TableSummaryRow.vue +95 -0
- package/src/components/ActionTable/index.ts +15 -0
- package/src/components/ActionTable/listActions.ts +361 -0
- package/src/components/ActionTable/tableColumns.ts +72 -0
- package/src/components/ActionTable/tableHelpers.ts +83 -0
- package/src/components/Utility/CollapsableSidebar.vue +119 -0
- package/src/components/Utility/ContentDrawer.vue +70 -0
- package/src/components/Utility/Dialogs/ConfirmDialog.vue +132 -0
- package/src/components/Utility/Dialogs/FullScreenDialog.vue +46 -0
- package/src/components/Utility/Dialogs/InfoDialog.vue +92 -0
- package/src/components/Utility/Dialogs/InputDialog.vue +35 -0
- package/src/components/Utility/Transitions/ListTransition.vue +50 -0
- package/src/components/Utility/Transitions/SlideTransition.vue +63 -0
- package/src/components/Utility/Transitions/StaggeredListTransition.vue +97 -0
- package/src/components/Utility/index.ts +9 -0
- package/src/components/index.ts +3 -0
- package/src/helpers/FileUpload.ts +294 -0
- package/src/helpers/FlashMessages.ts +79 -0
- package/src/helpers/array.ts +37 -0
- package/src/helpers/compatibility.ts +64 -0
- package/src/helpers/date.ts +5 -0
- package/src/helpers/download.ts +192 -0
- package/src/helpers/downloadPdf.ts +92 -0
- package/src/helpers/files.ts +52 -0
- package/src/helpers/formats.ts +183 -0
- package/src/helpers/http.ts +62 -0
- package/src/helpers/index.ts +10 -1
- package/src/helpers/multiFileUpload.ts +68 -0
- package/src/helpers/singleFileUpload.ts +54 -0
- package/src/helpers/storage.ts +8 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<SelectField
|
4
|
+
v-model="selectedFieldName"
|
5
|
+
:label="undefined"
|
6
|
+
:options="field.options"
|
7
|
+
class="mb-2"
|
8
|
+
@update:model-value="onChange"
|
9
|
+
/>
|
10
|
+
<TextField
|
11
|
+
v-model="textInput"
|
12
|
+
:field="field"
|
13
|
+
:no-label="!field.label"
|
14
|
+
label-class="text-xs font-bold text-gray-dark"
|
15
|
+
parent-class="tight-label"
|
16
|
+
input-class="!py-0"
|
17
|
+
dense
|
18
|
+
type="textarea"
|
19
|
+
:debounce="500"
|
20
|
+
@update:model-value="onChange"
|
21
|
+
/>
|
22
|
+
</div>
|
23
|
+
</template>
|
24
|
+
|
25
|
+
<script setup>
|
26
|
+
import SelectField from "danx/src/components/ActionTable/Form/Fields/SelectField";
|
27
|
+
import TextField from "danx/src/components/ActionTable/Form/Fields/TextField";
|
28
|
+
import { computed, ref, watch } from "vue";
|
29
|
+
|
30
|
+
const emit = defineEmits(["update:model-value"]);
|
31
|
+
const props = defineProps({
|
32
|
+
modelValue: {
|
33
|
+
type: [String, Number, Object],
|
34
|
+
default: ""
|
35
|
+
},
|
36
|
+
field: {
|
37
|
+
type: Object,
|
38
|
+
default: null
|
39
|
+
}
|
40
|
+
});
|
41
|
+
|
42
|
+
const selectedFieldName = ref(props.field.defaultOption);
|
43
|
+
const searchList = computed(() => props.modelValue && props.modelValue[selectedFieldName.value]);
|
44
|
+
const textInput = ref(formatModelValue());
|
45
|
+
function onChange() {
|
46
|
+
textInput.value = textInput.value?.replace(/\n/g, ",").replace(/,{2,}/g, ",") || "";
|
47
|
+
emit("update:model-value", textInput.value ? { [selectedFieldName.value]: textInput.value.split(",") } : undefined);
|
48
|
+
}
|
49
|
+
|
50
|
+
function formatModelValue() {
|
51
|
+
return Array.isArray(searchList.value) ? searchList.value?.join(",") : "";
|
52
|
+
}
|
53
|
+
|
54
|
+
watch(() => props.modelValue, () => {
|
55
|
+
textInput.value = formatModelValue();
|
56
|
+
});
|
57
|
+
</script>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<template>
|
2
|
+
<LabeledInput
|
3
|
+
type="password"
|
4
|
+
v-bind="props"
|
5
|
+
:rules="rules"
|
6
|
+
@update:model-value="$emit('update:model-value', $event)"
|
7
|
+
/>
|
8
|
+
</template>
|
9
|
+
|
10
|
+
<script setup>
|
11
|
+
import LabeledInput from "danx/src/components/ActionTable/Form/Fields/LabeledInput";
|
12
|
+
|
13
|
+
defineEmits(["update:model-value"]);
|
14
|
+
const props = defineProps({
|
15
|
+
name: {
|
16
|
+
type: String,
|
17
|
+
default: "password"
|
18
|
+
},
|
19
|
+
label: {
|
20
|
+
type: String,
|
21
|
+
default: "Password"
|
22
|
+
},
|
23
|
+
placeholder: {
|
24
|
+
type: String,
|
25
|
+
default: "Enter Password"
|
26
|
+
},
|
27
|
+
modelValue: {
|
28
|
+
type: [String, Number],
|
29
|
+
required: true
|
30
|
+
},
|
31
|
+
error: {
|
32
|
+
type: String,
|
33
|
+
default: null
|
34
|
+
},
|
35
|
+
disabled: Boolean
|
36
|
+
});
|
37
|
+
|
38
|
+
const rules = [(val) => val.length >= 8 || "Please use at least 8 characters"];
|
39
|
+
</script>
|
@@ -0,0 +1,94 @@
|
|
1
|
+
<template>
|
2
|
+
<QInput
|
3
|
+
:model-value="numberVal"
|
4
|
+
:data-testid="'number-field-' + fieldOptions.id"
|
5
|
+
:placeholder="fieldOptions.placeholder"
|
6
|
+
outlined
|
7
|
+
dense
|
8
|
+
inputmode="numeric"
|
9
|
+
:input-class="{[inputClass]: true, 'text-right bg-white': !hidePrependLabel, 'text-right !text-xs text-black font-normal': hidePrependLabel}"
|
10
|
+
:class="{'no-prepend-icon w-32': hidePrependLabel, 'prepend-label': !hidePrependLabel}"
|
11
|
+
@update:model-value="onInput"
|
12
|
+
>
|
13
|
+
<template #prepend>
|
14
|
+
<FieldLabel
|
15
|
+
:field="fieldOptions"
|
16
|
+
:show-name="showName"
|
17
|
+
/>
|
18
|
+
</template>
|
19
|
+
</QInput>
|
20
|
+
</template>
|
21
|
+
|
22
|
+
<script setup>
|
23
|
+
import FieldLabel from "danx/src/components/ActionTable/Form/Fields/FieldLabel";
|
24
|
+
import { fNumber } from "danx/src/helpers/formats";
|
25
|
+
import { computed, nextTick, ref, watch } from "vue";
|
26
|
+
|
27
|
+
const emit = defineEmits(["update:model-value"]);
|
28
|
+
const props = defineProps({
|
29
|
+
modelValue: {
|
30
|
+
type: [String, Number],
|
31
|
+
default: ""
|
32
|
+
},
|
33
|
+
precision: {
|
34
|
+
type: Number,
|
35
|
+
default: 2
|
36
|
+
},
|
37
|
+
label: {
|
38
|
+
type: String,
|
39
|
+
default: undefined
|
40
|
+
},
|
41
|
+
field: {
|
42
|
+
type: Object,
|
43
|
+
default: null
|
44
|
+
},
|
45
|
+
inputClass: {
|
46
|
+
type: String,
|
47
|
+
default: ""
|
48
|
+
},
|
49
|
+
hidePrependLabel: Boolean,
|
50
|
+
currency: Boolean,
|
51
|
+
showName: Boolean
|
52
|
+
});
|
53
|
+
|
54
|
+
const numberVal = ref(format(props.modelValue));
|
55
|
+
watch(() => props.modelValue, () => numberVal.value = format(props.modelValue));
|
56
|
+
|
57
|
+
const fieldOptions = computed(() => props.field || { label: props.label || "", placeholder: "", id: "" });
|
58
|
+
|
59
|
+
function format(number) {
|
60
|
+
if (!number && number !== 0 && number !== "0") return number;
|
61
|
+
|
62
|
+
const minimumFractionDigits = Math.min(props.precision, ("" + number).split(".")[1]?.length || 0);
|
63
|
+
let options = {
|
64
|
+
minimumFractionDigits
|
65
|
+
};
|
66
|
+
|
67
|
+
if (props.currency) {
|
68
|
+
options = {
|
69
|
+
style: "currency",
|
70
|
+
currency: "USD",
|
71
|
+
minimumFractionDigits
|
72
|
+
};
|
73
|
+
}
|
74
|
+
return fNumber(number, options);
|
75
|
+
}
|
76
|
+
function onInput(value) {
|
77
|
+
let number = "";
|
78
|
+
|
79
|
+
// Prevent invalid characters
|
80
|
+
if (value.match(/[^\d.,$]/)) {
|
81
|
+
const oldVal = numberVal.value;
|
82
|
+
// XXX: To get QInput to show only the value we want
|
83
|
+
numberVal.value += " ";
|
84
|
+
return nextTick(() => numberVal.value = oldVal);
|
85
|
+
}
|
86
|
+
|
87
|
+
if (value !== "") {
|
88
|
+
value = value.replace(/[^\d.]/g, "");
|
89
|
+
number = Number(value);
|
90
|
+
numberVal.value = format(number);
|
91
|
+
}
|
92
|
+
emit("update:model-value", number === "" ? undefined : number);
|
93
|
+
}
|
94
|
+
</script>
|
@@ -0,0 +1,140 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<div
|
4
|
+
v-if="label"
|
5
|
+
class="font-bold text-xs mb-2"
|
6
|
+
>
|
7
|
+
{{ label }}
|
8
|
+
</div>
|
9
|
+
<div class="flex items-center flex-nowrap cursor-pointer">
|
10
|
+
<component
|
11
|
+
:is="previewIcon"
|
12
|
+
class="w-5 text-blue-base"
|
13
|
+
/>
|
14
|
+
<div class="text-sm ml-3 hover:text-blue-base whitespace-nowrap">
|
15
|
+
<template v-if="range">
|
16
|
+
{{ formatNum(range.from || 0) }} - {{ formatNum(range.to) }}
|
17
|
+
</template>
|
18
|
+
<template v-else>
|
19
|
+
No Limit
|
20
|
+
</template>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
<QPopupProxy>
|
24
|
+
<NumberField
|
25
|
+
v-model="range.from"
|
26
|
+
:field="minField"
|
27
|
+
@update:model-value="onSave"
|
28
|
+
/>
|
29
|
+
<NumberField
|
30
|
+
v-model="range.to"
|
31
|
+
class="mt-2"
|
32
|
+
:field="maxField"
|
33
|
+
@update:model-value="onSave"
|
34
|
+
/>
|
35
|
+
</QPopupProxy>
|
36
|
+
</div>
|
37
|
+
</template>
|
38
|
+
|
39
|
+
<script setup>
|
40
|
+
import { PercentIcon } from "@/svg";
|
41
|
+
import { CurrencyDollarIcon as CurrencyIcon, HashtagIcon as NumberIcon } from "@heroicons/vue/outline";
|
42
|
+
import { useDebounceFn } from "@vueuse/core";
|
43
|
+
import NumberField from "danx/src/components/ActionTable/Form/Fields/NumberField";
|
44
|
+
import { fCurrency, fNumber, fPercent } from "danx/src/helpers/formats";
|
45
|
+
import { computed, ref, watch } from "vue";
|
46
|
+
|
47
|
+
const emit = defineEmits(["update:model-value"]);
|
48
|
+
const props = defineProps({
|
49
|
+
modelValue: {
|
50
|
+
type: Object,
|
51
|
+
default: null
|
52
|
+
},
|
53
|
+
label: {
|
54
|
+
type: String,
|
55
|
+
default: null
|
56
|
+
},
|
57
|
+
icon: {
|
58
|
+
type: Object,
|
59
|
+
default: null
|
60
|
+
},
|
61
|
+
currency: Boolean,
|
62
|
+
percent: Boolean,
|
63
|
+
debounce: {
|
64
|
+
type: Number,
|
65
|
+
default: 0
|
66
|
+
}
|
67
|
+
});
|
68
|
+
|
69
|
+
const symbol = computed(() => {
|
70
|
+
if (props.currency) {
|
71
|
+
return "$";
|
72
|
+
} else if (props.percent) {
|
73
|
+
return "%";
|
74
|
+
} else {
|
75
|
+
return "";
|
76
|
+
}
|
77
|
+
});
|
78
|
+
const minField = computed(() => {
|
79
|
+
return {
|
80
|
+
id: "min-field",
|
81
|
+
name: "from",
|
82
|
+
label: "Min" + symbol.value,
|
83
|
+
placeholder: "0"
|
84
|
+
};
|
85
|
+
});
|
86
|
+
|
87
|
+
const maxField = computed(() => {
|
88
|
+
return {
|
89
|
+
id: "max-field",
|
90
|
+
name: "to",
|
91
|
+
label: "Max" + symbol.value,
|
92
|
+
placeholder: "No Limit"
|
93
|
+
};
|
94
|
+
});
|
95
|
+
|
96
|
+
const previewIcon = computed(() => props.icon || (props.currency ? CurrencyIcon : (props.percent ? PercentIcon : NumberIcon)));
|
97
|
+
|
98
|
+
const range = ref({});
|
99
|
+
watch(() => props.modelValue, setRange);
|
100
|
+
function setRange(val) {
|
101
|
+
const multiplier = props.percent ? 100 : 1;
|
102
|
+
range.value = {
|
103
|
+
from: (val?.from ? val.from * multiplier : undefined),
|
104
|
+
to: (val?.to ? val.to * multiplier : undefined)
|
105
|
+
};
|
106
|
+
}
|
107
|
+
setRange(props.modelValue || { from: undefined, to: undefined });
|
108
|
+
|
109
|
+
/**
|
110
|
+
* Convert the number into a nicely formatted string
|
111
|
+
* @param num
|
112
|
+
* @returns {string}
|
113
|
+
*/
|
114
|
+
function formatNum(num) {
|
115
|
+
if (num === undefined) return "No Limit";
|
116
|
+
if (props.currency) {
|
117
|
+
return fCurrency(num);
|
118
|
+
}
|
119
|
+
if (props.percent) {
|
120
|
+
return fPercent(num, { multiplier: 1, maximumFractionDigits: 2 });
|
121
|
+
}
|
122
|
+
return fNumber(num);
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* Update the modelValue only after the debounce timeout
|
127
|
+
* Empty values are converted to undefined so they will not be filtered on
|
128
|
+
* @type {(function(): void)|*}
|
129
|
+
*/
|
130
|
+
const onSave = useDebounceFn(() => {
|
131
|
+
if (range.value && (range.value.from || range.value.to)) {
|
132
|
+
const multiplier = props.percent ? .01 : 1;
|
133
|
+
let newVal = {
|
134
|
+
from: (range.value.from ? range.value.from * multiplier : undefined),
|
135
|
+
to: (range.value.to ? range.value.to * multiplier : undefined)
|
136
|
+
};
|
137
|
+
emit("update:model-value", newVal);
|
138
|
+
}
|
139
|
+
}, props.debounce);
|
140
|
+
</script>
|
@@ -0,0 +1,136 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<ContentDrawer
|
4
|
+
v-model:show="showDrawer"
|
5
|
+
content-class=""
|
6
|
+
position="bottom"
|
7
|
+
:title="'Filter ' + label"
|
8
|
+
>
|
9
|
+
<div
|
10
|
+
v-for="option in formattedOptions"
|
11
|
+
:key="'select-drawer-' + option.value"
|
12
|
+
:data-dusk="'drawer-opt-' + option.value"
|
13
|
+
class="cursor-pointer hover:bg-gray-light px-8 py-3 flex items-center border-b border-gray-light"
|
14
|
+
@click="toggleSelect(option)"
|
15
|
+
>
|
16
|
+
<QCheckbox
|
17
|
+
:model-value="isSelected(option)"
|
18
|
+
class="mr-2"
|
19
|
+
/>
|
20
|
+
<slot
|
21
|
+
name="option"
|
22
|
+
:opt="option"
|
23
|
+
>{{ option.label }}
|
24
|
+
</slot>
|
25
|
+
</div>
|
26
|
+
</ContentDrawer>
|
27
|
+
|
28
|
+
<QChip
|
29
|
+
ref="select"
|
30
|
+
outline
|
31
|
+
clickable
|
32
|
+
size="16px"
|
33
|
+
@click="showDrawer = true"
|
34
|
+
>
|
35
|
+
<slot name="selected">
|
36
|
+
<slot name="label">{{ label }}: </slot>
|
37
|
+
<template v-if="modelValue && modelValue.length > 0">
|
38
|
+
<slot name="selection">
|
39
|
+
<template v-if="multiple">
|
40
|
+
{{ getOptionLabel(modelValue[0]) }}
|
41
|
+
<template
|
42
|
+
v-if="modelValue.length > 1"
|
43
|
+
>+ {{ modelValue.length - 1 }}
|
44
|
+
</template
|
45
|
+
>
|
46
|
+
</template>
|
47
|
+
<template v-else>
|
48
|
+
{{ getOptionLabel(modelValue) }}
|
49
|
+
</template>
|
50
|
+
</slot>
|
51
|
+
</template>
|
52
|
+
<template v-else>
|
53
|
+
<slot name="placeholder">{{ placeholder }}</slot>
|
54
|
+
</template>
|
55
|
+
</slot>
|
56
|
+
</QChip>
|
57
|
+
</div>
|
58
|
+
</template>
|
59
|
+
|
60
|
+
<script setup>
|
61
|
+
import { ContentDrawer } from "danx/src/components/Utility";
|
62
|
+
import { computed, ref } from "vue";
|
63
|
+
|
64
|
+
const emit = defineEmits(["update:modelValue"]);
|
65
|
+
const props = defineProps({
|
66
|
+
modelValue: {
|
67
|
+
type: [Object, String, Array, null],
|
68
|
+
required: true
|
69
|
+
},
|
70
|
+
options: {
|
71
|
+
type: Array,
|
72
|
+
default: () => []
|
73
|
+
},
|
74
|
+
multiple: Boolean,
|
75
|
+
label: {
|
76
|
+
type: String,
|
77
|
+
default: "Select"
|
78
|
+
},
|
79
|
+
placeholder: {
|
80
|
+
type: String,
|
81
|
+
default: "All"
|
82
|
+
}
|
83
|
+
});
|
84
|
+
|
85
|
+
const showDrawer = ref(false);
|
86
|
+
const formattedOptions = computed(() =>
|
87
|
+
props.options.map((opt) =>
|
88
|
+
typeof opt === "string"
|
89
|
+
? {
|
90
|
+
label: opt,
|
91
|
+
value: opt
|
92
|
+
}
|
93
|
+
: opt
|
94
|
+
)
|
95
|
+
);
|
96
|
+
|
97
|
+
function getOptionValue(option) {
|
98
|
+
return option.value === undefined ? option : option.value;
|
99
|
+
}
|
100
|
+
|
101
|
+
function getOptionLabel(value) {
|
102
|
+
return formattedOptions.value.find((opt) => opt.value === value).label;
|
103
|
+
}
|
104
|
+
|
105
|
+
function isSelected(option) {
|
106
|
+
const optionValue = getOptionValue(option);
|
107
|
+
|
108
|
+
if (props.multiple) {
|
109
|
+
return props.modelValue.includes(optionValue);
|
110
|
+
} else {
|
111
|
+
return props.modelValue === optionValue;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
function toggleSelect(option) {
|
116
|
+
let optionValue = getOptionValue(option);
|
117
|
+
|
118
|
+
let selection = optionValue;
|
119
|
+
|
120
|
+
if (props.multiple) {
|
121
|
+
selection = [...props.modelValue];
|
122
|
+
if (isSelected(optionValue)) {
|
123
|
+
selection = selection.filter((opt) => opt !== optionValue);
|
124
|
+
} else {
|
125
|
+
selection.push(optionValue);
|
126
|
+
}
|
127
|
+
} else {
|
128
|
+
// Allow deselection on single select
|
129
|
+
if (selection === props.modelValue) {
|
130
|
+
selection = null;
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
emit("update:modelValue", selection);
|
135
|
+
}
|
136
|
+
</script>
|