@dolanske/vui 0.3.4 → 0.4.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/LICENSE +673 -673
- package/README.md +49 -40
- package/dist/components/Dropdown/DropdownItem.vue.d.ts +1 -0
- package/dist/style.css +1 -1
- package/dist/vui.js +864 -859
- package/package.json +68 -68
- package/src/App.vue +198 -175
- package/src/components/Accordion/Accordion.vue +91 -91
- package/src/components/Accordion/AccordionGroup.vue +43 -43
- package/src/components/Accordion/accordion.scss +80 -80
- package/src/components/Alert/Alert.vue +53 -53
- package/src/components/Alert/alert.scss +80 -80
- package/src/components/Avatar/Avatar.vue +50 -50
- package/src/components/Avatar/avatar.scss +52 -52
- package/src/components/Badge/Badge.vue +21 -21
- package/src/components/Badge/badge.scss +89 -89
- package/src/components/Breadcrumbs/BreadcrumbItem.vue +26 -26
- package/src/components/Breadcrumbs/Breadcrumbs.vue +33 -33
- package/src/components/Breadcrumbs/breadcrumbs.scss +30 -30
- package/src/components/Button/Button.vue +90 -90
- package/src/components/Button/button.scss +176 -176
- package/src/components/ButtonGroup/ButtonGroup.vue +25 -25
- package/src/components/ButtonGroup/button-group.scss +51 -51
- package/src/components/Calendar/Calendar.vue +60 -60
- package/src/components/Calendar/calendar.scss +56 -56
- package/src/components/Card/Card.vue +48 -48
- package/src/components/Card/card.scss +53 -53
- package/src/components/Checkbox/Checkbox.vue +52 -52
- package/src/components/Checkbox/checkbox.scss +66 -66
- package/src/components/CopyClipboard/CopyClipboard.vue +82 -82
- package/src/components/CopyClipboard/copy-clipboard.scss +17 -17
- package/src/components/Divider/Divider.vue +44 -44
- package/src/components/Divider/divider.scss +35 -35
- package/src/components/Drawer/Drawer.vue +97 -97
- package/src/components/Drawer/drawer.scss +36 -36
- package/src/components/Dropdown/Dropdown.vue +111 -111
- package/src/components/Dropdown/DropdownItem.vue +32 -29
- package/src/components/Dropdown/DropdownTitle.vue +8 -8
- package/src/components/Dropdown/dropdown.scss +118 -117
- package/src/components/Flex/Flex.vue +110 -106
- package/src/components/Grid/Grid.vue +57 -54
- package/src/components/Input/Counter.vue +70 -70
- package/src/components/Input/Dropzone.vue +65 -65
- package/src/components/Input/File.vue +15 -15
- package/src/components/Input/Input.vue +121 -121
- package/src/components/Input/Password.vue +47 -47
- package/src/components/Input/Textarea.vue +76 -76
- package/src/components/Input/input.scss +208 -208
- package/src/components/Kbd/Kbd.vue +48 -48
- package/src/components/Kbd/KbdGroup.vue +31 -31
- package/src/components/Kbd/kbd.scss +18 -18
- package/src/components/Modal/Confirm.vue +56 -56
- package/src/components/Modal/Modal.vue +91 -91
- package/src/components/Modal/modal.scss +49 -49
- package/src/components/OTP/OTP.vue +133 -133
- package/src/components/OTP/OTPItem.vue +37 -37
- package/src/components/OTP/otp.scss +83 -83
- package/src/components/Pagination/Pagination.vue +74 -74
- package/src/components/Pagination/pagination.ts +78 -78
- package/src/components/Popout/Popout.vue +42 -42
- package/src/components/Popout/popout.scss +8 -8
- package/src/components/Progress/Progress.vue +90 -90
- package/src/components/Progress/progress.scss +41 -41
- package/src/components/Radio/Radio.vue +36 -36
- package/src/components/Radio/RadioGroup.vue +40 -40
- package/src/components/Radio/radio.scss +59 -59
- package/src/components/Select/Select.vue +180 -180
- package/src/components/Select/select.scss +44 -44
- package/src/components/Sheet/Sheet.vue +92 -92
- package/src/components/Sheet/sheet.scss +60 -60
- package/src/components/Sidebar/Sidebar.vue +85 -0
- package/src/components/Sidebar/sidebar.scss +123 -0
- package/src/components/Skeleton/Skeleton.vue +43 -43
- package/src/components/Skeleton/skeleton.scss +14 -14
- package/src/components/Spinner/Spinner.vue +42 -42
- package/src/components/Spinner/spinner.scss +46 -46
- package/src/components/Switch/Switch.vue +30 -30
- package/src/components/Switch/switch.scss +52 -52
- package/src/components/Table/Cell.vue +23 -23
- package/src/components/Table/Header.vue +59 -59
- package/src/components/Table/Row.vue +9 -9
- package/src/components/Table/SelectAll.vue +23 -23
- package/src/components/Table/SelectRow.vue +29 -29
- package/src/components/Table/Table.vue +66 -66
- package/src/components/Table/table.scss +134 -134
- package/src/components/Table/table.ts +244 -244
- package/src/components/Tabs/Tab.vue +27 -27
- package/src/components/Tabs/Tabs.vue +82 -82
- package/src/components/Tabs/tabs.scss +79 -79
- package/src/components/Toast/Toasts.vue +47 -47
- package/src/components/Toast/toast.scss +41 -41
- package/src/components/Toast/toast.ts +68 -68
- package/src/components/Tooltip/Tooltip.vue +86 -86
- package/src/components/Tooltip/tooltip.scss +4 -4
- package/src/index.scss +1 -1
- package/src/index.ts +119 -119
- package/src/internal/Backdrop/Backdrop.vue +22 -22
- package/src/internal/Backdrop/backdrop.scss +28 -28
- package/src/main.ts +5 -5
- package/src/shared/helpers.ts +74 -74
- package/src/shared/types.ts +29 -29
- package/src/style/animation.scss +21 -21
- package/src/style/core.scss +148 -148
- package/src/style/fonts.scss +53 -53
- package/src/style/layout.scss +136 -136
- package/src/style/media-query.scss +29 -29
- package/src/style/reset.scss +135 -135
- package/src/style/tooltip.scss +128 -128
- package/src/style/typography.scss +338 -338
- package/src/style/utils.scss +36 -36
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { ModelRef, Ref } from 'vue'
|
|
3
|
-
import { computed, provide, ref, toRef, useTemplateRef, watch } from 'vue'
|
|
4
|
-
import { setCharAt } from '../../shared/helpers'
|
|
5
|
-
import './otp.scss'
|
|
6
|
-
|
|
7
|
-
export interface OtpContext {
|
|
8
|
-
otpValue: ModelRef<string>
|
|
9
|
-
cursorIndex: Ref<number>
|
|
10
|
-
redacted: Ref<boolean>
|
|
11
|
-
register: () => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
mode?: 'num' | 'char' | 'both'
|
|
16
|
-
redacted?: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
mode = 'both',
|
|
21
|
-
redacted = false,
|
|
22
|
-
} = defineProps<Props>()
|
|
23
|
-
|
|
24
|
-
const emits = defineEmits<{
|
|
25
|
-
change: [value?: string]
|
|
26
|
-
complete: [value: string]
|
|
27
|
-
}>()
|
|
28
|
-
|
|
29
|
-
const otpValue = defineModel<string>({
|
|
30
|
-
default: '',
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const cursorIndex = ref<number>(-1)
|
|
34
|
-
const regexNumbers = '^\\d+$'
|
|
35
|
-
const regexChars = '^[a-z]+$'
|
|
36
|
-
const regexBoth = '^[a-z0-9]+$'
|
|
37
|
-
|
|
38
|
-
const pattern = computed(() => {
|
|
39
|
-
if (mode === 'num')
|
|
40
|
-
return new RegExp(regexNumbers)
|
|
41
|
-
else if (mode === 'char')
|
|
42
|
-
return new RegExp(regexChars, 'i')
|
|
43
|
-
else return new RegExp(regexBoth, 'i')
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const maxLen = ref(0)
|
|
47
|
-
|
|
48
|
-
const input = useTemplateRef('inputRef')
|
|
49
|
-
|
|
50
|
-
provide('otp-context', {
|
|
51
|
-
otpValue,
|
|
52
|
-
cursorIndex,
|
|
53
|
-
redacted: toRef(() => redacted),
|
|
54
|
-
// Called by all OTPItem child components to properly set max length of the input.
|
|
55
|
-
register: () => maxLen.value++,
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
watch(otpValue, value => emits('change', value))
|
|
59
|
-
|
|
60
|
-
function setOtpValue(value: string) {
|
|
61
|
-
otpValue.value = value
|
|
62
|
-
if (input.value) {
|
|
63
|
-
input.value.value = value
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function updateValue(e: KeyboardEvent) {
|
|
68
|
-
const key = e.key
|
|
69
|
-
|
|
70
|
-
// Capping at length 0 prevents all non-character keyboard inputs
|
|
71
|
-
if (pattern.value.test(key) && key.length === 1) {
|
|
72
|
-
const newValue = setCharAt(otpValue.value, key, cursorIndex.value)
|
|
73
|
-
|
|
74
|
-
if (newValue.length <= maxLen.value) {
|
|
75
|
-
setOtpValue(newValue)
|
|
76
|
-
|
|
77
|
-
if (cursorIndex.value < maxLen.value - 1)
|
|
78
|
-
cursorIndex.value++
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
else if (key === 'ArrowLeft' && cursorIndex.value > 0) {
|
|
82
|
-
cursorIndex.value--
|
|
83
|
-
}
|
|
84
|
-
else if (key === 'ArrowRight' && cursorIndex.value < otpValue.value.length) {
|
|
85
|
-
cursorIndex.value++
|
|
86
|
-
}
|
|
87
|
-
else if (key === 'Backspace') {
|
|
88
|
-
// If we press backspace multiple times make sure to traverse back by 1
|
|
89
|
-
if (otpValue.value.charAt(cursorIndex.value) === '' && cursorIndex.value > 0) {
|
|
90
|
-
cursorIndex.value--
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const newValue = setCharAt(otpValue.value, '', cursorIndex.value)
|
|
94
|
-
setOtpValue(newValue)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function handlePaste(e: any) {
|
|
99
|
-
const clipboard = e.clipboardData?.getData('text/plain')
|
|
100
|
-
if (clipboard) {
|
|
101
|
-
const clipboardTrim = clipboard.trim().slice(0, maxLen.value - cursorIndex.value)
|
|
102
|
-
|
|
103
|
-
if (!pattern.value.test(clipboardTrim)) {
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const currentTrimStart = otpValue.value.slice(0, cursorIndex.value)
|
|
108
|
-
const currentTrimEnd = otpValue.value.slice(cursorIndex.value + clipboardTrim.length)
|
|
109
|
-
const newValue = (currentTrimStart + clipboardTrim + currentTrimEnd).trim()
|
|
110
|
-
setOtpValue(newValue)
|
|
111
|
-
cursorIndex.value = Math.min(newValue.length, maxLen.value - 1)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
</script>
|
|
115
|
-
|
|
116
|
-
<template>
|
|
117
|
-
<div class="vui-otp">
|
|
118
|
-
<input
|
|
119
|
-
ref="inputRef"
|
|
120
|
-
type="text"
|
|
121
|
-
class="vui-otp-input"
|
|
122
|
-
contenteditable="true"
|
|
123
|
-
@keydown="updateValue"
|
|
124
|
-
@blur="cursorIndex = -1"
|
|
125
|
-
@focus="cursorIndex = Math.min(otpValue.length, maxLen - 1)"
|
|
126
|
-
@paste="handlePaste"
|
|
127
|
-
>
|
|
128
|
-
|
|
129
|
-
<div class="vui-otp-items">
|
|
130
|
-
<slot />
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
</template>
|
|
1
|
+
<script setup lang='ts'>
|
|
2
|
+
import type { ModelRef, Ref } from 'vue'
|
|
3
|
+
import { computed, provide, ref, toRef, useTemplateRef, watch } from 'vue'
|
|
4
|
+
import { setCharAt } from '../../shared/helpers'
|
|
5
|
+
import './otp.scss'
|
|
6
|
+
|
|
7
|
+
export interface OtpContext {
|
|
8
|
+
otpValue: ModelRef<string>
|
|
9
|
+
cursorIndex: Ref<number>
|
|
10
|
+
redacted: Ref<boolean>
|
|
11
|
+
register: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
mode?: 'num' | 'char' | 'both'
|
|
16
|
+
redacted?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
mode = 'both',
|
|
21
|
+
redacted = false,
|
|
22
|
+
} = defineProps<Props>()
|
|
23
|
+
|
|
24
|
+
const emits = defineEmits<{
|
|
25
|
+
change: [value?: string]
|
|
26
|
+
complete: [value: string]
|
|
27
|
+
}>()
|
|
28
|
+
|
|
29
|
+
const otpValue = defineModel<string>({
|
|
30
|
+
default: '',
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const cursorIndex = ref<number>(-1)
|
|
34
|
+
const regexNumbers = '^\\d+$'
|
|
35
|
+
const regexChars = '^[a-z]+$'
|
|
36
|
+
const regexBoth = '^[a-z0-9]+$'
|
|
37
|
+
|
|
38
|
+
const pattern = computed(() => {
|
|
39
|
+
if (mode === 'num')
|
|
40
|
+
return new RegExp(regexNumbers)
|
|
41
|
+
else if (mode === 'char')
|
|
42
|
+
return new RegExp(regexChars, 'i')
|
|
43
|
+
else return new RegExp(regexBoth, 'i')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const maxLen = ref(0)
|
|
47
|
+
|
|
48
|
+
const input = useTemplateRef('inputRef')
|
|
49
|
+
|
|
50
|
+
provide('otp-context', {
|
|
51
|
+
otpValue,
|
|
52
|
+
cursorIndex,
|
|
53
|
+
redacted: toRef(() => redacted),
|
|
54
|
+
// Called by all OTPItem child components to properly set max length of the input.
|
|
55
|
+
register: () => maxLen.value++,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
watch(otpValue, value => emits('change', value))
|
|
59
|
+
|
|
60
|
+
function setOtpValue(value: string) {
|
|
61
|
+
otpValue.value = value
|
|
62
|
+
if (input.value) {
|
|
63
|
+
input.value.value = value
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function updateValue(e: KeyboardEvent) {
|
|
68
|
+
const key = e.key
|
|
69
|
+
|
|
70
|
+
// Capping at length 0 prevents all non-character keyboard inputs
|
|
71
|
+
if (pattern.value.test(key) && key.length === 1) {
|
|
72
|
+
const newValue = setCharAt(otpValue.value, key, cursorIndex.value)
|
|
73
|
+
|
|
74
|
+
if (newValue.length <= maxLen.value) {
|
|
75
|
+
setOtpValue(newValue)
|
|
76
|
+
|
|
77
|
+
if (cursorIndex.value < maxLen.value - 1)
|
|
78
|
+
cursorIndex.value++
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (key === 'ArrowLeft' && cursorIndex.value > 0) {
|
|
82
|
+
cursorIndex.value--
|
|
83
|
+
}
|
|
84
|
+
else if (key === 'ArrowRight' && cursorIndex.value < otpValue.value.length) {
|
|
85
|
+
cursorIndex.value++
|
|
86
|
+
}
|
|
87
|
+
else if (key === 'Backspace') {
|
|
88
|
+
// If we press backspace multiple times make sure to traverse back by 1
|
|
89
|
+
if (otpValue.value.charAt(cursorIndex.value) === '' && cursorIndex.value > 0) {
|
|
90
|
+
cursorIndex.value--
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const newValue = setCharAt(otpValue.value, '', cursorIndex.value)
|
|
94
|
+
setOtpValue(newValue)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handlePaste(e: any) {
|
|
99
|
+
const clipboard = e.clipboardData?.getData('text/plain')
|
|
100
|
+
if (clipboard) {
|
|
101
|
+
const clipboardTrim = clipboard.trim().slice(0, maxLen.value - cursorIndex.value)
|
|
102
|
+
|
|
103
|
+
if (!pattern.value.test(clipboardTrim)) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const currentTrimStart = otpValue.value.slice(0, cursorIndex.value)
|
|
108
|
+
const currentTrimEnd = otpValue.value.slice(cursorIndex.value + clipboardTrim.length)
|
|
109
|
+
const newValue = (currentTrimStart + clipboardTrim + currentTrimEnd).trim()
|
|
110
|
+
setOtpValue(newValue)
|
|
111
|
+
cursorIndex.value = Math.min(newValue.length, maxLen.value - 1)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<template>
|
|
117
|
+
<div class="vui-otp">
|
|
118
|
+
<input
|
|
119
|
+
ref="inputRef"
|
|
120
|
+
type="text"
|
|
121
|
+
class="vui-otp-input"
|
|
122
|
+
contenteditable="true"
|
|
123
|
+
@keydown="updateValue"
|
|
124
|
+
@blur="cursorIndex = -1"
|
|
125
|
+
@focus="cursorIndex = Math.min(otpValue.length, maxLen - 1)"
|
|
126
|
+
@paste="handlePaste"
|
|
127
|
+
>
|
|
128
|
+
|
|
129
|
+
<div class="vui-otp-items">
|
|
130
|
+
<slot />
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</template>
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { OtpContext } from './OTP.vue'
|
|
3
|
-
import { Icon } from '@iconify/vue'
|
|
4
|
-
import { inject } from 'vue'
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
i: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const props = defineProps<Props>()
|
|
11
|
-
|
|
12
|
-
const {
|
|
13
|
-
otpValue,
|
|
14
|
-
cursorIndex,
|
|
15
|
-
redacted,
|
|
16
|
-
register,
|
|
17
|
-
} = inject('otp-context') as OtpContext
|
|
18
|
-
|
|
19
|
-
register()
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<template>
|
|
23
|
-
<div
|
|
24
|
-
class="vui-otp-item" :class="{
|
|
25
|
-
'active': props.i === cursorIndex,
|
|
26
|
-
'has-value': otpValue.trim().at(props.i),
|
|
27
|
-
}"
|
|
28
|
-
>
|
|
29
|
-
<div class="blinker" />
|
|
30
|
-
<template v-if="otpValue.trim().at(props.i)">
|
|
31
|
-
<Icon v-if="redacted" icon="ph:asterisk" />
|
|
32
|
-
<template v-else>
|
|
33
|
-
{{ otpValue.at(props.i) }}
|
|
34
|
-
</template>
|
|
35
|
-
</template>
|
|
36
|
-
</div>
|
|
37
|
-
</template>
|
|
1
|
+
<script setup lang='ts'>
|
|
2
|
+
import type { OtpContext } from './OTP.vue'
|
|
3
|
+
import { Icon } from '@iconify/vue'
|
|
4
|
+
import { inject } from 'vue'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
i: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = defineProps<Props>()
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
otpValue,
|
|
14
|
+
cursorIndex,
|
|
15
|
+
redacted,
|
|
16
|
+
register,
|
|
17
|
+
} = inject('otp-context') as OtpContext
|
|
18
|
+
|
|
19
|
+
register()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<div
|
|
24
|
+
class="vui-otp-item" :class="{
|
|
25
|
+
'active': props.i === cursorIndex,
|
|
26
|
+
'has-value': otpValue.trim().at(props.i),
|
|
27
|
+
}"
|
|
28
|
+
>
|
|
29
|
+
<div class="blinker" />
|
|
30
|
+
<template v-if="otpValue.trim().at(props.i)">
|
|
31
|
+
<Icon v-if="redacted" icon="ph:asterisk" />
|
|
32
|
+
<template v-else>
|
|
33
|
+
{{ otpValue.at(props.i) }}
|
|
34
|
+
</template>
|
|
35
|
+
</template>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
.vui-otp {
|
|
2
|
-
display: inline-block;
|
|
3
|
-
position: relative;
|
|
4
|
-
|
|
5
|
-
.vui-otp-items {
|
|
6
|
-
display: inline-flex;
|
|
7
|
-
gap: 0;
|
|
8
|
-
|
|
9
|
-
.vui-otp-item {
|
|
10
|
-
display: flex;
|
|
11
|
-
align-items: center;
|
|
12
|
-
justify-content: center;
|
|
13
|
-
width: 42px;
|
|
14
|
-
height: 42px;
|
|
15
|
-
border: 1px solid var(--color-border-strong);
|
|
16
|
-
color: var(--color-text);
|
|
17
|
-
z-index: 1;
|
|
18
|
-
font-size: var(--font-size-m);
|
|
19
|
-
outline: 0 solid var(--color-text-light);
|
|
20
|
-
transition: var(--transition);
|
|
21
|
-
|
|
22
|
-
.blinker {
|
|
23
|
-
display: none;
|
|
24
|
-
height: 16px;
|
|
25
|
-
width: 1px;
|
|
26
|
-
background-color: var(--color-text);
|
|
27
|
-
animation: blink 1.25s ease-out infinite;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@keyframes blink {
|
|
31
|
-
0%,
|
|
32
|
-
70%,
|
|
33
|
-
100% {
|
|
34
|
-
opacity: 1;
|
|
35
|
-
}
|
|
36
|
-
20%,
|
|
37
|
-
50% {
|
|
38
|
-
opacity: 0;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
&.has-value {
|
|
43
|
-
background-color: var(--color-bg-raised);
|
|
44
|
-
|
|
45
|
-
.blinker {
|
|
46
|
-
display: none !important;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
&.active {
|
|
51
|
-
z-index: 2;
|
|
52
|
-
outline-width: 2px;
|
|
53
|
-
|
|
54
|
-
.blinker {
|
|
55
|
-
display: block;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
&:not(:first-child) {
|
|
60
|
-
margin-left: -1px;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
&:first-child {
|
|
64
|
-
border-top-left-radius: var(--border-radius-m);
|
|
65
|
-
border-bottom-left-radius: var(--border-radius-m);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
&:last-child {
|
|
69
|
-
border-top-right-radius: var(--border-radius-m);
|
|
70
|
-
border-bottom-right-radius: var(--border-radius-m);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.vui-otp-input {
|
|
76
|
-
position: absolute;
|
|
77
|
-
inset: 0;
|
|
78
|
-
outline-width: 0px;
|
|
79
|
-
opacity: 0;
|
|
80
|
-
background: transparent;
|
|
81
|
-
z-index: 5;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
1
|
+
.vui-otp {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
position: relative;
|
|
4
|
+
|
|
5
|
+
.vui-otp-items {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
gap: 0;
|
|
8
|
+
|
|
9
|
+
.vui-otp-item {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
width: 42px;
|
|
14
|
+
height: 42px;
|
|
15
|
+
border: 1px solid var(--color-border-strong);
|
|
16
|
+
color: var(--color-text);
|
|
17
|
+
z-index: 1;
|
|
18
|
+
font-size: var(--font-size-m);
|
|
19
|
+
outline: 0 solid var(--color-text-light);
|
|
20
|
+
transition: var(--transition);
|
|
21
|
+
|
|
22
|
+
.blinker {
|
|
23
|
+
display: none;
|
|
24
|
+
height: 16px;
|
|
25
|
+
width: 1px;
|
|
26
|
+
background-color: var(--color-text);
|
|
27
|
+
animation: blink 1.25s ease-out infinite;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@keyframes blink {
|
|
31
|
+
0%,
|
|
32
|
+
70%,
|
|
33
|
+
100% {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
}
|
|
36
|
+
20%,
|
|
37
|
+
50% {
|
|
38
|
+
opacity: 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&.has-value {
|
|
43
|
+
background-color: var(--color-bg-raised);
|
|
44
|
+
|
|
45
|
+
.blinker {
|
|
46
|
+
display: none !important;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&.active {
|
|
51
|
+
z-index: 2;
|
|
52
|
+
outline-width: 2px;
|
|
53
|
+
|
|
54
|
+
.blinker {
|
|
55
|
+
display: block;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&:not(:first-child) {
|
|
60
|
+
margin-left: -1px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&:first-child {
|
|
64
|
+
border-top-left-radius: var(--border-radius-m);
|
|
65
|
+
border-bottom-left-radius: var(--border-radius-m);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&:last-child {
|
|
69
|
+
border-top-right-radius: var(--border-radius-m);
|
|
70
|
+
border-bottom-right-radius: var(--border-radius-m);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.vui-otp-input {
|
|
76
|
+
position: absolute;
|
|
77
|
+
inset: 0;
|
|
78
|
+
outline-width: 0px;
|
|
79
|
+
opacity: 0;
|
|
80
|
+
background: transparent;
|
|
81
|
+
z-index: 5;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { Pagination } from './pagination'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
|
-
import Button from '../Button/Button.vue'
|
|
5
|
-
import Flex from '../Flex/Flex.vue'
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
numbers?: boolean
|
|
9
|
-
pagination: Pagination
|
|
10
|
-
prevNext?: boolean
|
|
11
|
-
firstLast?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
-
numbers: true,
|
|
16
|
-
prevNext: true,
|
|
17
|
-
firstLast: true,
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const emit = defineEmits<{
|
|
21
|
-
change: [page: number]
|
|
22
|
-
}>()
|
|
23
|
-
|
|
24
|
-
const canNextPage = computed(() => props.pagination.currentPage < props.pagination.endPage)
|
|
25
|
-
const canPrevPage = computed(() => props.pagination.currentPage > props.pagination.startPage)
|
|
26
|
-
|
|
27
|
-
function setNext() {
|
|
28
|
-
emit('change', props.pagination.currentPage + 1)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function setPrev() {
|
|
32
|
-
emit('change', props.pagination.currentPage - 1)
|
|
33
|
-
}
|
|
34
|
-
</script>
|
|
35
|
-
|
|
36
|
-
<template>
|
|
37
|
-
<Flex inline class="vui-pagination" gap="s">
|
|
38
|
-
<slot name="start">
|
|
39
|
-
<Button v-if="props.firstLast" data-title-top="First page" plain :disabled="props.pagination.startPage === props.pagination.currentPage" square icon="ph:caret-double-left" @click="emit('change', props.pagination.startPage)" />
|
|
40
|
-
</slot>
|
|
41
|
-
|
|
42
|
-
<slot name="prev" :disabled="canPrevPage" :set-page="setPrev">
|
|
43
|
-
<Button v-if="props.prevNext" data-title-top="Previous page" plain :disabled="!canPrevPage" square icon="ph:caret-left" @click="setPrev" />
|
|
44
|
-
</slot>
|
|
45
|
-
|
|
46
|
-
<template v-if="props.numbers">
|
|
47
|
-
<Flex gap="s">
|
|
48
|
-
<Button
|
|
49
|
-
v-for="page in props.pagination.pages"
|
|
50
|
-
:key="page"
|
|
51
|
-
square
|
|
52
|
-
:plain="props.pagination.currentPage !== page"
|
|
53
|
-
:variant="props.pagination.currentPage === page ? 'blue' : 'default'"
|
|
54
|
-
@click="emit('change', page)"
|
|
55
|
-
>
|
|
56
|
-
{{ page }}
|
|
57
|
-
</Button>
|
|
58
|
-
</Flex>
|
|
59
|
-
</template>
|
|
60
|
-
<slot name="next" :disabled="canNextPage" :set-page="setNext">
|
|
61
|
-
<Button v-if="props.prevNext" data-title-top="Next page" plain :disabled="!canNextPage" square icon="ph:caret-right" @click="setNext" />
|
|
62
|
-
</slot>
|
|
63
|
-
|
|
64
|
-
<slot name="end">
|
|
65
|
-
<Button v-if="props.firstLast" data-title-top="Last page" plain :disabled="props.pagination.endPage === props.pagination.currentPage" square icon="ph:caret-double-right" @click="emit('change', props.pagination.endPage)" />
|
|
66
|
-
</slot>
|
|
67
|
-
</Flex>
|
|
68
|
-
</template>
|
|
69
|
-
|
|
70
|
-
<style scoped>
|
|
71
|
-
[data-title-top]:before {
|
|
72
|
-
white-space: nowrap;
|
|
73
|
-
}
|
|
74
|
-
</style>
|
|
1
|
+
<script setup lang='ts'>
|
|
2
|
+
import type { Pagination } from './pagination'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import Button from '../Button/Button.vue'
|
|
5
|
+
import Flex from '../Flex/Flex.vue'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
numbers?: boolean
|
|
9
|
+
pagination: Pagination
|
|
10
|
+
prevNext?: boolean
|
|
11
|
+
firstLast?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
15
|
+
numbers: true,
|
|
16
|
+
prevNext: true,
|
|
17
|
+
firstLast: true,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
change: [page: number]
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const canNextPage = computed(() => props.pagination.currentPage < props.pagination.endPage)
|
|
25
|
+
const canPrevPage = computed(() => props.pagination.currentPage > props.pagination.startPage)
|
|
26
|
+
|
|
27
|
+
function setNext() {
|
|
28
|
+
emit('change', props.pagination.currentPage + 1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setPrev() {
|
|
32
|
+
emit('change', props.pagination.currentPage - 1)
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<Flex inline class="vui-pagination" gap="s">
|
|
38
|
+
<slot name="start">
|
|
39
|
+
<Button v-if="props.firstLast" data-title-top="First page" plain :disabled="props.pagination.startPage === props.pagination.currentPage" square icon="ph:caret-double-left" @click="emit('change', props.pagination.startPage)" />
|
|
40
|
+
</slot>
|
|
41
|
+
|
|
42
|
+
<slot name="prev" :disabled="canPrevPage" :set-page="setPrev">
|
|
43
|
+
<Button v-if="props.prevNext" data-title-top="Previous page" plain :disabled="!canPrevPage" square icon="ph:caret-left" @click="setPrev" />
|
|
44
|
+
</slot>
|
|
45
|
+
|
|
46
|
+
<template v-if="props.numbers">
|
|
47
|
+
<Flex gap="s">
|
|
48
|
+
<Button
|
|
49
|
+
v-for="page in props.pagination.pages"
|
|
50
|
+
:key="page"
|
|
51
|
+
square
|
|
52
|
+
:plain="props.pagination.currentPage !== page"
|
|
53
|
+
:variant="props.pagination.currentPage === page ? 'blue' : 'default'"
|
|
54
|
+
@click="emit('change', page)"
|
|
55
|
+
>
|
|
56
|
+
{{ page }}
|
|
57
|
+
</Button>
|
|
58
|
+
</Flex>
|
|
59
|
+
</template>
|
|
60
|
+
<slot name="next" :disabled="canNextPage" :set-page="setNext">
|
|
61
|
+
<Button v-if="props.prevNext" data-title-top="Next page" plain :disabled="!canNextPage" square icon="ph:caret-right" @click="setNext" />
|
|
62
|
+
</slot>
|
|
63
|
+
|
|
64
|
+
<slot name="end">
|
|
65
|
+
<Button v-if="props.firstLast" data-title-top="Last page" plain :disabled="props.pagination.endPage === props.pagination.currentPage" square icon="ph:caret-double-right" @click="emit('change', props.pagination.endPage)" />
|
|
66
|
+
</slot>
|
|
67
|
+
</Flex>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<style scoped>
|
|
71
|
+
[data-title-top]:before {
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
</style>
|