adminforth 4.5.0 → 4.6.0
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/dist/spa/package-lock.json +0 -14
- package/dist/spa/package.json +0 -1
- package/dist/spa/pnpm-lock.yaml +0 -15
- package/dist/spa/src/afcl/DialogPanel.vue +108 -0
- package/dist/spa/src/afcl/Input.vue +31 -4
- package/dist/spa/src/afcl/Tooltip.vue +75 -51
- package/dist/spa/src/afcl/index.ts +1 -0
- package/dist/spa/src/views/ListView.vue +0 -1
- package/dist/spa/src/views/LoginView.vue +20 -29
- package/package.json +1 -1
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"eslint": "^8.57.0",
|
|
51
51
|
"eslint-plugin-vue": "^9.23.0",
|
|
52
52
|
"flag-icons": "^7.2.3",
|
|
53
|
-
"flowbite": "^3.1.2",
|
|
54
53
|
"i18n-iso-countries": "^7.12.0",
|
|
55
54
|
"npm-run-all2": "^9.0.2",
|
|
56
55
|
"portfinder": "^1.0.32",
|
|
@@ -3111,19 +3110,6 @@
|
|
|
3111
3110
|
"dev": true,
|
|
3112
3111
|
"license": "ISC"
|
|
3113
3112
|
},
|
|
3114
|
-
"node_modules/flowbite": {
|
|
3115
|
-
"version": "3.1.2",
|
|
3116
|
-
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-3.1.2.tgz",
|
|
3117
|
-
"integrity": "sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==",
|
|
3118
|
-
"dev": true,
|
|
3119
|
-
"license": "MIT",
|
|
3120
|
-
"dependencies": {
|
|
3121
|
-
"@popperjs/core": "^2.9.3",
|
|
3122
|
-
"flowbite-datepicker": "^1.3.1",
|
|
3123
|
-
"mini-svg-data-uri": "^1.4.3",
|
|
3124
|
-
"postcss": "^8.5.1"
|
|
3125
|
-
}
|
|
3126
|
-
},
|
|
3127
3113
|
"node_modules/flowbite-datepicker": {
|
|
3128
3114
|
"version": "1.3.2",
|
|
3129
3115
|
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz",
|
package/dist/spa/package.json
CHANGED
package/dist/spa/pnpm-lock.yaml
CHANGED
|
@@ -129,9 +129,6 @@ importers:
|
|
|
129
129
|
flag-icons:
|
|
130
130
|
specifier: ^7.2.3
|
|
131
131
|
version: 7.5.0
|
|
132
|
-
flowbite:
|
|
133
|
-
specifier: ^3.1.2
|
|
134
|
-
version: 3.1.2(rollup@4.60.2)
|
|
135
132
|
i18n-iso-countries:
|
|
136
133
|
specifier: ^7.12.0
|
|
137
134
|
version: 7.14.0
|
|
@@ -1308,9 +1305,6 @@ packages:
|
|
|
1308
1305
|
flowbite@2.5.2:
|
|
1309
1306
|
resolution: {integrity: sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==}
|
|
1310
1307
|
|
|
1311
|
-
flowbite@3.1.2:
|
|
1312
|
-
resolution: {integrity: sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==}
|
|
1313
|
-
|
|
1314
1308
|
fraction.js@5.3.4:
|
|
1315
1309
|
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
|
1316
1310
|
|
|
@@ -3263,15 +3257,6 @@ snapshots:
|
|
|
3263
3257
|
transitivePeerDependencies:
|
|
3264
3258
|
- rollup
|
|
3265
3259
|
|
|
3266
|
-
flowbite@3.1.2(rollup@4.60.2):
|
|
3267
|
-
dependencies:
|
|
3268
|
-
'@popperjs/core': 2.11.8
|
|
3269
|
-
flowbite-datepicker: 1.3.2(rollup@4.60.2)
|
|
3270
|
-
mini-svg-data-uri: 1.4.4
|
|
3271
|
-
postcss: 8.5.30
|
|
3272
|
-
transitivePeerDependencies:
|
|
3273
|
-
- rollup
|
|
3274
|
-
|
|
3275
3260
|
fraction.js@5.3.4: {}
|
|
3276
3261
|
|
|
3277
3262
|
fs.realpath@1.0.0: {}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-bind="restAttrs" :class="rootClasses">
|
|
3
|
+
<div :class="mergeClasses(
|
|
4
|
+
'afcl-dialog-panel-header flex items-center gap-3.5 border-b border-lightDialogBreakLine py-5 pl-6 dark:border-darkDialogBreakLine',
|
|
5
|
+
closable ? 'pr-4' : 'pr-6',
|
|
6
|
+
classes.header,
|
|
7
|
+
)">
|
|
8
|
+
<div
|
|
9
|
+
v-if="$slots.icon"
|
|
10
|
+
:class="mergeClasses(
|
|
11
|
+
'afcl-dialog-panel-icon flex w-10 h-10 shrink-0 items-center justify-center rounded-lg [&>svg]:w-5 [&>svg]:h-5',
|
|
12
|
+
'bg-lightPrimary/10 text-lightPrimary dark:bg-darkPrimary/20 dark:text-darkPrimary',
|
|
13
|
+
classes.icon,
|
|
14
|
+
)"
|
|
15
|
+
>
|
|
16
|
+
<slot name="icon" />
|
|
17
|
+
</div>
|
|
18
|
+
<div :class="mergeClasses('afcl-dialog-panel-title-wrap flex-1 min-w-0', classes.titleWrap)">
|
|
19
|
+
<slot name="title">
|
|
20
|
+
<h2 :class="mergeClasses('afcl-dialog-panel-title break-words text-lg leading-6 font-semibold text-lightDialogHeaderText dark:text-darkDialogHeaderText', classes.title)">{{ title }}</h2>
|
|
21
|
+
<p v-if="subtitle" :class="mergeClasses('afcl-dialog-panel-subtitle mt-0.5 text-sm leading-5 text-lightInputIconColor dark:text-darkInputIconColor', classes.subtitle)">{{ subtitle }}</p>
|
|
22
|
+
</slot>
|
|
23
|
+
</div>
|
|
24
|
+
<Button
|
|
25
|
+
v-if="closable"
|
|
26
|
+
type="button"
|
|
27
|
+
variant="ghost"
|
|
28
|
+
size="sm"
|
|
29
|
+
iconOnly
|
|
30
|
+
:aria-label="$t('Close')"
|
|
31
|
+
:class="mergeClasses(
|
|
32
|
+
'afcl-dialog-panel-close text-lightDialogCloseButton hover:text-lightDialogCloseButtonHover hover:bg-lightDialogCloseButtonHoverBackground',
|
|
33
|
+
'dark:text-darkDialogCloseButton dark:hover:text-darkDialogCloseButtonHover dark:hover:bg-darkDialogCloseButtonHoverBackground',
|
|
34
|
+
classes.close,
|
|
35
|
+
)"
|
|
36
|
+
@click="emit('close')"
|
|
37
|
+
>
|
|
38
|
+
<IconX aria-hidden="true" />
|
|
39
|
+
</Button>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div :class="mergeClasses('afcl-dialog-panel-body flex flex-col gap-4 px-6 pt-5 pb-6', classes.body)">
|
|
43
|
+
<slot />
|
|
44
|
+
<p v-if="error" role="alert" :class="mergeClasses('afcl-dialog-panel-error -mt-1 flex items-center gap-1.5 text-sm text-lightDanger dark:text-darkDanger', classes.error)">
|
|
45
|
+
<IconCircleAlert class="w-4 h-4 shrink-0" aria-hidden="true" />
|
|
46
|
+
{{ error }}
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<!-- a main action on the right, a secondary one may sit on the left with ms-0 me-auto -->
|
|
51
|
+
<div
|
|
52
|
+
v-if="$slots.footer"
|
|
53
|
+
:class="mergeClasses(
|
|
54
|
+
'afcl-dialog-panel-footer flex flex-wrap items-center justify-end gap-x-3 gap-y-2 border-t border-lightDialogBreakLine bg-lightFormHeading py-3 pl-6 pr-5',
|
|
55
|
+
'dark:border-darkDialogBreakLine dark:bg-darkFormHeading',
|
|
56
|
+
classes.footer,
|
|
57
|
+
)"
|
|
58
|
+
>
|
|
59
|
+
<slot name="footer" />
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup lang="ts">
|
|
65
|
+
import { computed, useAttrs } from 'vue';
|
|
66
|
+
import { type ClassNameValue } from 'tailwind-merge';
|
|
67
|
+
import { IconCircleAlert, IconX } from '@iconify-prerendered/vue-lucide';
|
|
68
|
+
import { mergeClasses } from './classMerge';
|
|
69
|
+
import Button from './Button.vue';
|
|
70
|
+
|
|
71
|
+
type DialogPanelPart = 'header' | 'icon' | 'titleWrap' | 'title' | 'subtitle' | 'close' | 'body' | 'error' | 'footer';
|
|
72
|
+
|
|
73
|
+
defineOptions({ inheritAttrs: false });
|
|
74
|
+
|
|
75
|
+
withDefaults(defineProps<{
|
|
76
|
+
title?: string,
|
|
77
|
+
subtitle?: string,
|
|
78
|
+
error?: string | null,
|
|
79
|
+
// shows a close button in the header which emits close
|
|
80
|
+
closable?: boolean,
|
|
81
|
+
// extra classes for the parts, e.g. the af-* classes apps restyle a screen by
|
|
82
|
+
classes?: Partial<Record<DialogPanelPart, ClassNameValue>>,
|
|
83
|
+
}>(), {
|
|
84
|
+
title: '',
|
|
85
|
+
subtitle: '',
|
|
86
|
+
error: null,
|
|
87
|
+
closable: false,
|
|
88
|
+
classes: () => ({}),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const emit = defineEmits<{
|
|
92
|
+
(e: 'close'): void,
|
|
93
|
+
}>();
|
|
94
|
+
|
|
95
|
+
const attrs = useAttrs();
|
|
96
|
+
const restAttrs = computed(() => {
|
|
97
|
+
const { class: _class, ...rest } = attrs;
|
|
98
|
+
return rest;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// a panel on the page is drawn like other surfaces: a hairline border and the light shadow of controls,
|
|
102
|
+
// a panel which floats over the page (in a modal) passes shadow-popover
|
|
103
|
+
const rootClasses = computed(() => mergeClasses(
|
|
104
|
+
'afcl-dialog-panel relative flex flex-col w-full max-w-md overflow-hidden rounded-xl border shadow-customLight',
|
|
105
|
+
'border-lightDialogBreakLine bg-lightDialogBackgorund dark:border-darkDialogBreakLine dark:bg-darkDialogBackgorund',
|
|
106
|
+
attrs.class as ClassNameValue,
|
|
107
|
+
));
|
|
108
|
+
</script>
|
|
@@ -11,19 +11,39 @@
|
|
|
11
11
|
<input
|
|
12
12
|
ref="input"
|
|
13
13
|
v-bind="$attrs"
|
|
14
|
-
:type="type"
|
|
14
|
+
:type="passwordToggle && revealed ? 'text' : type"
|
|
15
15
|
@input="$emit('update:modelValue', type === 'number' ? numberInputValue(($event.target as HTMLInputElement)?.value) : ($event.target as HTMLInputElement)?.value)"
|
|
16
16
|
:value="modelValue"
|
|
17
17
|
aria-describedby="helper-text-explanation"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
:aria-invalid="invalid || undefined"
|
|
19
|
+
class="afcl-input inline-flex bg-lightInputBackground text-lightInputText dark:text-darkInputText border rounded-0 shadow-customLight focus:outline-none focus:ring-[3px]
|
|
20
|
+
block w-20 px-2.5 py-1.5 leading-5 dark:bg-darkInputBackground placeholder-lightInputPlaceholderText dark:placeholder-darkInputPlaceholderText dark:text-darkInputText translate-y-0 transition-colors disabled:cursor-not-allowed"
|
|
21
|
+
:class="[
|
|
22
|
+
invalid
|
|
23
|
+
? 'border-lightDanger focus:border-lightDanger focus:ring-lightDanger/20 dark:border-darkDanger dark:focus:border-darkDanger dark:focus:ring-darkDanger/30'
|
|
24
|
+
: 'border-lightInputBorder hover:border-lightInputBorderHover focus:ring-lightInputFocusRing focus:border-lightInputFocusBorder dark:border-darkInputBorder dark:hover:border-darkInputBorderHover dark:focus:ring-darkInputFocusRing dark:focus:border-darkInputFocusBorder',
|
|
25
|
+
{'rounded-l-default': !$slots.prefix && !prefix, 'rounded-r-default': !$slots.suffix && !suffix, 'w-full': fullWidth, 'text-base': isIos, 'text-sm': !isIos, 'pr-10': $slots.rightIcon || passwordToggle },
|
|
26
|
+
]"
|
|
21
27
|
:disabled="readonly"
|
|
22
28
|
>
|
|
23
29
|
|
|
24
30
|
<div v-if="$slots.rightIcon" class="absolute inset-y-0 right-0 pr-3 flex items-center">
|
|
25
31
|
<slot name="rightIcon" />
|
|
26
32
|
</div>
|
|
33
|
+
<div v-else-if="passwordToggle" class="absolute inset-y-0 right-0 pr-1 flex items-center">
|
|
34
|
+
<Button
|
|
35
|
+
type="button"
|
|
36
|
+
variant="ghost"
|
|
37
|
+
size="sm"
|
|
38
|
+
iconOnly
|
|
39
|
+
class="afcl-input-password-toggle"
|
|
40
|
+
:aria-label="revealed ? $t('Hide password') : $t('Show password')"
|
|
41
|
+
@click="revealed = !revealed"
|
|
42
|
+
>
|
|
43
|
+
<IconEyeOff v-if="revealed" aria-hidden="true" />
|
|
44
|
+
<IconEye v-else aria-hidden="true" />
|
|
45
|
+
</Button>
|
|
46
|
+
</div>
|
|
27
47
|
<span
|
|
28
48
|
v-if="$slots.suffix || suffix"
|
|
29
49
|
class="inline-flex items-center px-2.5 text-sm text-lightInputIconColor bg-lightSecondaryDarken border border-s-0 border-lightInputBorder rounded-e-default dark:bg-darkSecondaryLighten dark:text-darkInputIconColor dark:border-darkInputBorder ">
|
|
@@ -36,8 +56,10 @@
|
|
|
36
56
|
<script setup lang="ts">
|
|
37
57
|
|
|
38
58
|
import { ref } from 'vue';
|
|
59
|
+
import { IconEye, IconEyeOff } from '@iconify-prerendered/vue-lucide';
|
|
39
60
|
import { useCoreStore } from '@/stores/core';
|
|
40
61
|
import { numberInputValue } from './inputValue';
|
|
62
|
+
import Button from './Button.vue';
|
|
41
63
|
|
|
42
64
|
const coreStore = useCoreStore();
|
|
43
65
|
const isIos = coreStore.isIos;
|
|
@@ -49,9 +71,14 @@ const props = defineProps<{
|
|
|
49
71
|
suffix?: string,
|
|
50
72
|
prefix?: string,
|
|
51
73
|
readonly?: boolean,
|
|
74
|
+
// paints the border red, e.g. for a value which did not pass validation
|
|
75
|
+
invalid?: boolean,
|
|
76
|
+
// for type="password": a button on the right shows and hides the value
|
|
77
|
+
passwordToggle?: boolean,
|
|
52
78
|
}>()
|
|
53
79
|
|
|
54
80
|
const input = ref<HTMLInputElement | null>(null)
|
|
81
|
+
const revealed = ref(false)
|
|
55
82
|
|
|
56
83
|
defineExpose({
|
|
57
84
|
focus: () => input.value?.focus(),
|
|
@@ -1,87 +1,111 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="triggerEl" class="afcl-tooltip inline-flex items-center" @mouseenter="
|
|
2
|
+
<div ref="triggerEl" class="afcl-tooltip inline-flex items-center" :style="{ anchorName }" @mouseenter="show" @mouseleave="hide">
|
|
3
3
|
<slot></slot>
|
|
4
4
|
</div>
|
|
5
|
-
<teleport to="body" v-if="
|
|
5
|
+
<teleport to="body" v-if="isOpen">
|
|
6
6
|
<div
|
|
7
|
+
ref="tooltipEl"
|
|
8
|
+
popover="manual"
|
|
7
9
|
role="tooltip"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
:data-placement="placement"
|
|
11
|
+
:style="{ positionAnchor: anchorName }"
|
|
12
|
+
class="afcl-tooltip-popover inline-block px-2.5 py-1.5 text-xs font-medium text-lightTooltipText dark:text-darkTooltipText transition-opacity duration-150 bg-lightTooltipBackground rounded-md shadow-popover dark:bg-darkTooltipBackground dark:shadow-darkPopover"
|
|
10
13
|
>
|
|
11
14
|
<slot name="tooltip"></slot>
|
|
12
|
-
<div class="tooltip-arrow"
|
|
15
|
+
<div class="afcl-tooltip-arrow" :style="{ left: `${arrowLeft}px` }"></div>
|
|
13
16
|
</div>
|
|
14
17
|
</teleport>
|
|
15
18
|
</template>
|
|
16
19
|
|
|
17
20
|
<script setup lang="ts">
|
|
18
|
-
import { ref, nextTick,
|
|
19
|
-
import { Tooltip } from 'flowbite';
|
|
21
|
+
import { ref, nextTick, onBeforeUnmount, useId } from 'vue';
|
|
20
22
|
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const showTooltip = ref(false);
|
|
24
|
-
const tp: Ref<Tooltip|null> = ref(null);
|
|
23
|
+
const OFFSET = 8;
|
|
24
|
+
const SUPPORTS_ANCHOR = CSS.supports('anchor-name: --a');
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const anchorName = `--afcl-tooltip-${useId()}`;
|
|
27
|
+
const triggerEl = ref<HTMLElement>();
|
|
28
|
+
const tooltipEl = ref<HTMLElement>();
|
|
29
|
+
const isOpen = ref(false);
|
|
30
|
+
const placement = ref<'top' | 'bottom'>('bottom');
|
|
31
|
+
const arrowLeft = ref(0);
|
|
32
|
+
|
|
33
|
+
async function show() {
|
|
34
|
+
isOpen.value = true;
|
|
28
35
|
await nextTick();
|
|
36
|
+
tooltipEl.value!.showPopover();
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
const trigger = triggerEl.value!.getBoundingClientRect();
|
|
39
|
+
if (!SUPPORTS_ANCHOR) {
|
|
40
|
+
placeByRect(trigger);
|
|
41
|
+
}
|
|
42
|
+
const tooltip = tooltipEl.value!.getBoundingClientRect();
|
|
43
|
+
placement.value = tooltip.top < trigger.top ? 'top' : 'bottom';
|
|
44
|
+
arrowLeft.value = trigger.left + trigger.width / 2 - tooltip.left;
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
triggerEl.value,
|
|
35
|
-
{
|
|
36
|
-
placement: 'bottom',
|
|
37
|
-
triggerType: 'hover',
|
|
38
|
-
},
|
|
39
|
-
);
|
|
46
|
+
document.addEventListener('keydown', onKeydown);
|
|
47
|
+
}
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
function hide() {
|
|
50
|
+
isOpen.value = false;
|
|
51
|
+
document.removeEventListener('keydown', onKeydown);
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
showTooltip.value = false;
|
|
54
|
+
function onKeydown(e: KeyboardEvent) {
|
|
55
|
+
if (e.key === 'Escape') {
|
|
56
|
+
hide();
|
|
57
|
+
}
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
// Safari before 26 and Firefox before 147 have no CSS anchor positioning
|
|
61
|
+
function placeByRect(trigger: DOMRect) {
|
|
62
|
+
const el = tooltipEl.value!;
|
|
63
|
+
const { width, height } = el.getBoundingClientRect();
|
|
64
|
+
const fitsBelow = trigger.bottom + OFFSET + height <= window.innerHeight;
|
|
65
|
+
const top = fitsBelow ? trigger.bottom + OFFSET : trigger.top - OFFSET - height;
|
|
66
|
+
const left = Math.min(Math.max(trigger.left + (trigger.width - width) / 2, 0), window.innerWidth - width);
|
|
67
|
+
el.style.top = `${top}px`;
|
|
68
|
+
el.style.left = `${left}px`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onBeforeUnmount(hide);
|
|
51
72
|
</script>
|
|
52
73
|
|
|
53
74
|
<style>
|
|
54
|
-
|
|
55
|
-
.tooltip
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
background: inherit;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.tooltip .tooltip-arrow {
|
|
63
|
-
visibility: hidden;
|
|
75
|
+
/* popover puts the tooltip into the top layer, so no z-index and no clipping by overflow of the parents */
|
|
76
|
+
.afcl-tooltip-popover {
|
|
77
|
+
inset: auto;
|
|
78
|
+
margin: 0;
|
|
79
|
+
overflow: visible;
|
|
64
80
|
}
|
|
65
81
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
@supports (anchor-name: --a) {
|
|
83
|
+
.afcl-tooltip-popover {
|
|
84
|
+
position-area: bottom;
|
|
85
|
+
position-try-fallbacks: flip-block;
|
|
86
|
+
margin-top: 8px;
|
|
87
|
+
}
|
|
70
88
|
}
|
|
71
89
|
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
@starting-style {
|
|
91
|
+
.afcl-tooltip-popover {
|
|
92
|
+
opacity: 0;
|
|
93
|
+
}
|
|
74
94
|
}
|
|
75
95
|
|
|
76
|
-
.
|
|
77
|
-
|
|
96
|
+
.afcl-tooltip-arrow {
|
|
97
|
+
position: absolute;
|
|
98
|
+
width: 8px;
|
|
99
|
+
height: 8px;
|
|
100
|
+
background: inherit;
|
|
101
|
+
transform: translateX(-50%) rotate(45deg);
|
|
78
102
|
}
|
|
79
103
|
|
|
80
|
-
.tooltip[data-
|
|
81
|
-
|
|
104
|
+
.afcl-tooltip-popover[data-placement='top'] > .afcl-tooltip-arrow {
|
|
105
|
+
bottom: -4px;
|
|
82
106
|
}
|
|
83
107
|
|
|
84
|
-
.tooltip[data-
|
|
85
|
-
|
|
108
|
+
.afcl-tooltip-popover[data-placement='bottom'] > .afcl-tooltip-arrow {
|
|
109
|
+
top: -4px;
|
|
86
110
|
}
|
|
87
|
-
</style>
|
|
111
|
+
</style>
|
|
@@ -19,6 +19,7 @@ export { default as ProgressBar } from './ProgressBar.vue';
|
|
|
19
19
|
export { default as Spinner } from './Spinner.vue';
|
|
20
20
|
export { default as Skeleton } from './Skeleton.vue';
|
|
21
21
|
export { default as Dialog } from './Dialog.vue';
|
|
22
|
+
export { default as DialogPanel } from './DialogPanel.vue';
|
|
22
23
|
export { default as MixedChart } from './MixedChart.vue';
|
|
23
24
|
export { default as CountryFlag } from './CountryFlag.vue';
|
|
24
25
|
export { default as JsonViewer } from './JsonViewer.vue';
|
|
@@ -32,11 +32,15 @@
|
|
|
32
32
|
class="af-login-modal overflow-y-auto flex flex-grow
|
|
33
33
|
overflow-x-hidden z-50 min-w-[350px] justify-center items-center md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
|
34
34
|
<div class="relative p-4 w-full max-h-full max-w-[400px]">
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
<DialogPanel
|
|
36
|
+
class="af-login-popup af-login-modal-content max-w-none bg-lightLoginViewBackground dark:bg-darkLoginViewBackground"
|
|
37
|
+
:error="error"
|
|
38
|
+
:classes="{
|
|
39
|
+
header: 'af-login-modal-header',
|
|
40
|
+
body: 'af-login-modal-body',
|
|
41
|
+
}"
|
|
42
|
+
>
|
|
43
|
+
<template #title>
|
|
40
44
|
<template v-if="coreStore?.config?.loginPageInjections?.panelHeader.length && coreStore?.config?.loginPageInjections?.panelHeader.length > 0">
|
|
41
45
|
<component
|
|
42
46
|
v-for="(c, index) in coreStore?.config?.loginPageInjections?.panelHeader || []"
|
|
@@ -46,15 +50,13 @@
|
|
|
46
50
|
@update:setLoginParam="setLoginParam"
|
|
47
51
|
/>
|
|
48
52
|
</template>
|
|
49
|
-
<
|
|
53
|
+
<p v-else class="text-lg leading-6 font-semibold text-lightLoginViewTextColor dark:text-darkLoginViewTextColor">
|
|
50
54
|
{{ $t('Sign in to') }} {{ coreStore.config?.brandName }}
|
|
51
|
-
</
|
|
52
|
-
</
|
|
53
|
-
|
|
54
|
-
<div class="af-login-modal-body p-4 md:p-5">
|
|
55
|
-
<form class="space-y-4" @submit.prevent>
|
|
55
|
+
</p>
|
|
56
|
+
</template>
|
|
57
|
+
<form class="space-y-4" @submit.prevent>
|
|
56
58
|
<div>
|
|
57
|
-
<label for="username" class="block mb-2 text-sm font-medium text-
|
|
59
|
+
<label for="username" class="block mb-2 text-sm font-medium text-lightLoginViewTextColor dark:text-darkLoginViewTextColor">{{ $t('Your') }} {{ coreStore.config?.usernameFieldName?.toLowerCase() }}</label>
|
|
58
60
|
<Input
|
|
59
61
|
v-model="username"
|
|
60
62
|
autocomplete="username"
|
|
@@ -67,20 +69,13 @@
|
|
|
67
69
|
placeholder="name@company.com" required />
|
|
68
70
|
</div>
|
|
69
71
|
<div class="">
|
|
70
|
-
<label for="password" class="block mb-2 text-sm font-medium text-
|
|
72
|
+
<label for="password" class="block mb-2 text-sm font-medium text-lightLoginViewTextColor dark:text-darkLoginViewTextColor">{{ $t('Your password') }}</label>
|
|
71
73
|
<Input
|
|
72
74
|
v-model="password"
|
|
73
75
|
ref="passwordInput"
|
|
74
76
|
autocomplete="current-password"
|
|
75
77
|
@keydown.enter="login"
|
|
76
|
-
|
|
77
|
-
<template #rightIcon>
|
|
78
|
-
<button type="button" @click="showPw = !showPw" class="text-lightLoginViewSubTextColor dark:text-darkLoginViewSubTextColor">
|
|
79
|
-
<IconEye class="w-4 h-4" v-if="!showPw" />
|
|
80
|
-
<IconEyeOff class="w-4 h-4" v-else />
|
|
81
|
-
</button>
|
|
82
|
-
</template>
|
|
83
|
-
</Input>
|
|
78
|
+
type="password" passwordToggle name="password" id="password" placeholder="••••••••" class="w-full" required />
|
|
84
79
|
</div>
|
|
85
80
|
|
|
86
81
|
<div v-if="coreStore?.config?.rememberMeDuration"
|
|
@@ -123,10 +118,8 @@
|
|
|
123
118
|
@update:setLoginParam="setLoginParam"
|
|
124
119
|
@update:oauthRedirecting="oauthRedirecting = $event"
|
|
125
120
|
/>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
</div>
|
|
129
|
-
<ErrorMessage v-if="error" :error="error" class="absolute left-4 right-4 rounded-t-none mb-0 shadow px-9" />
|
|
121
|
+
</form>
|
|
122
|
+
</DialogPanel>
|
|
130
123
|
</div>
|
|
131
124
|
</div>
|
|
132
125
|
|
|
@@ -140,11 +133,10 @@ import { getCustomComponent, formatComponent } from '@/utils';
|
|
|
140
133
|
import { onMounted, ref, computed } from 'vue';
|
|
141
134
|
import { useCoreStore } from '@/stores/core';
|
|
142
135
|
import { useUserStore } from '@/stores/user';
|
|
143
|
-
import {
|
|
136
|
+
import { IconInfo } from '@iconify-prerendered/vue-lucide';
|
|
144
137
|
import { callAdminForthApi, loadFile } from '@/utils';
|
|
145
138
|
import { useRouter, useRoute } from 'vue-router';
|
|
146
|
-
import { Button, Checkbox, Input, Spinner } from '@/afcl';
|
|
147
|
-
import ErrorMessage from '@/components/ErrorMessage.vue';
|
|
139
|
+
import { Button, Checkbox, DialogPanel, Input, Spinner } from '@/afcl';
|
|
148
140
|
|
|
149
141
|
const passwordInput = ref<InstanceType<typeof Input> | null>(null);
|
|
150
142
|
const usernameInput = ref<InstanceType<typeof Input> | null>(null);
|
|
@@ -161,7 +153,6 @@ const isSuccess = ref<boolean>(false);
|
|
|
161
153
|
const coreStore = useCoreStore();
|
|
162
154
|
const user = useUserStore();
|
|
163
155
|
|
|
164
|
-
const showPw = ref(false);
|
|
165
156
|
|
|
166
157
|
const error = ref(null);
|
|
167
158
|
const disableLoginButton = ref(false);
|