@dolanske/vui 1.4.1 → 1.4.2
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/package.json +1 -2
- package/src/App.vue +0 -103
- package/src/components/Accordion/Accordion.vue +0 -98
- package/src/components/Accordion/AccordionGroup.vue +0 -49
- package/src/components/Accordion/accordion.scss +0 -97
- package/src/components/Alert/Alert.vue +0 -59
- package/src/components/Alert/alert.scss +0 -162
- package/src/components/Avatar/Avatar.vue +0 -53
- package/src/components/Avatar/avatar.scss +0 -52
- package/src/components/Badge/Badge.vue +0 -21
- package/src/components/Badge/badge.scss +0 -210
- package/src/components/Breadcrumbs/BreadcrumbItem.vue +0 -26
- package/src/components/Breadcrumbs/Breadcrumbs.vue +0 -29
- package/src/components/Breadcrumbs/breadcrumbs.scss +0 -31
- package/src/components/Button/Button.vue +0 -86
- package/src/components/Button/button.scss +0 -292
- package/src/components/ButtonGroup/ButtonGroup.vue +0 -28
- package/src/components/ButtonGroup/button-group.scss +0 -51
- package/src/components/Calendar/Calendar.vue +0 -66
- package/src/components/Calendar/calendar.scss +0 -88
- package/src/components/Card/Card.vue +0 -48
- package/src/components/Card/card.scss +0 -55
- package/src/components/Checkbox/Checkbox.vue +0 -54
- package/src/components/Checkbox/checkbox.scss +0 -80
- package/src/components/CopyClipboard/CopyClipboard.vue +0 -98
- package/src/components/CopyClipboard/copy-clipboard.scss +0 -25
- package/src/components/Divider/Divider.vue +0 -38
- package/src/components/Divider/divider.scss +0 -37
- package/src/components/Drawer/Drawer.vue +0 -102
- package/src/components/Drawer/drawer.scss +0 -37
- package/src/components/Dropdown/Dropdown.vue +0 -120
- package/src/components/Dropdown/DropdownItem.vue +0 -33
- package/src/components/Dropdown/DropdownTitle.vue +0 -14
- package/src/components/Dropdown/dropdown-item.scss +0 -84
- package/src/components/Dropdown/dropdown.scss +0 -53
- package/src/components/Flex/Flex.vue +0 -113
- package/src/components/Grid/Grid.vue +0 -87
- package/src/components/Input/Color.vue +0 -26
- package/src/components/Input/Counter.vue +0 -66
- package/src/components/Input/Dropzone.vue +0 -65
- package/src/components/Input/File.vue +0 -15
- package/src/components/Input/Input.vue +0 -123
- package/src/components/Input/Password.vue +0 -35
- package/src/components/Input/Textarea.vue +0 -78
- package/src/components/Input/input.scss +0 -302
- package/src/components/Kbd/Kbd.vue +0 -48
- package/src/components/Kbd/KbdGroup.vue +0 -28
- package/src/components/Kbd/kbd.scss +0 -19
- package/src/components/Modal/Confirm.vue +0 -56
- package/src/components/Modal/Modal.vue +0 -103
- package/src/components/Modal/modal.scss +0 -54
- package/src/components/OTP/OTP.vue +0 -133
- package/src/components/OTP/OTPItem.vue +0 -37
- package/src/components/OTP/otp.scss +0 -84
- package/src/components/Pagination/Pagination.vue +0 -92
- package/src/components/Pagination/pagination.ts +0 -78
- package/src/components/Popout/Popout.vue +0 -73
- package/src/components/Popout/popout.scss +0 -16
- package/src/components/Progress/Progress.vue +0 -103
- package/src/components/Progress/progress.scss +0 -47
- package/src/components/Radio/Radio.vue +0 -38
- package/src/components/Radio/RadioGroup.vue +0 -34
- package/src/components/Radio/radio.scss +0 -78
- package/src/components/Select/Select.vue +0 -212
- package/src/components/Select/select.scss +0 -82
- package/src/components/Sheet/Sheet.vue +0 -106
- package/src/components/Sheet/sheet.scss +0 -71
- package/src/components/Sidebar/Sidebar.vue +0 -116
- package/src/components/Sidebar/sidebar.scss +0 -124
- package/src/components/Skeleton/Skeleton.vue +0 -43
- package/src/components/Skeleton/skeleton.scss +0 -14
- package/src/components/Spinner/Spinner.vue +0 -42
- package/src/components/Spinner/spinner.scss +0 -47
- package/src/components/Switch/Switch.vue +0 -31
- package/src/components/Switch/switch.scss +0 -93
- package/src/components/Table/Cell.vue +0 -23
- package/src/components/Table/Head.vue +0 -66
- package/src/components/Table/Root.vue +0 -66
- package/src/components/Table/SelectAll.vue +0 -23
- package/src/components/Table/SelectRow.vue +0 -30
- package/src/components/Table/index.ts +0 -7
- package/src/components/Table/table.scss +0 -155
- package/src/components/Table/table.ts +0 -248
- package/src/components/Tabs/Tab.vue +0 -25
- package/src/components/Tabs/Tabs.vue +0 -90
- package/src/components/Tabs/tabs.scss +0 -87
- package/src/components/Toast/Toasts.vue +0 -52
- package/src/components/Toast/toast.scss +0 -45
- package/src/components/Toast/toast.ts +0 -75
- package/src/components/Tooltip/Tooltip.vue +0 -78
- package/src/components/Tooltip/tooltip.scss +0 -5
- package/src/examples/ExampleAccordions.vue +0 -69
- package/src/examples/ExampleAlerts.vue +0 -78
- package/src/examples/ExampleAvatars.vue +0 -44
- package/src/examples/ExampleBadges.vue +0 -48
- package/src/examples/ExampleBreadcrumbs.vue +0 -46
- package/src/examples/ExampleButtons.vue +0 -148
- package/src/examples/ExampleCalendars.vue +0 -40
- package/src/examples/ExampleCards.vue +0 -94
- package/src/examples/ExampleCheckboxes.vue +0 -123
- package/src/examples/ExampleCopyClipboard.vue +0 -47
- package/src/examples/ExampleDividers.vue +0 -39
- package/src/examples/ExampleDrawers.vue +0 -67
- package/src/examples/ExampleDropdowns.vue +0 -114
- package/src/examples/ExampleFlexGrid.vue +0 -124
- package/src/examples/ExampleInputs.vue +0 -236
- package/src/examples/ExampleKBD.vue +0 -65
- package/src/examples/ExampleModals.vue +0 -143
- package/src/examples/ExamplePalette.vue +0 -165
- package/src/examples/ExamplePopouts.vue +0 -41
- package/src/examples/ExampleSheets.vue +0 -77
- package/src/examples/ExampleSidebars.vue +0 -276
- package/src/examples/ExampleSkeletons.vue +0 -26
- package/src/examples/ExampleSpinners.vue +0 -80
- package/src/examples/ExampleTables.vue +0 -359
- package/src/examples/ExampleTabs.vue +0 -142
- package/src/examples/ExampleToasts.vue +0 -96
- package/src/examples/ExampleTooltips.vue +0 -70
- package/src/examples/shared/ExampleColor.vue +0 -28
- package/src/index.ts +0 -116
- package/src/internal/Backdrop/Backdrop.vue +0 -22
- package/src/internal/Backdrop/backdrop.scss +0 -34
- package/src/main.ts +0 -5
- package/src/shared/helpers.ts +0 -124
- package/src/shared/slots.ts +0 -61
- package/src/shared/theme.ts +0 -22
- package/src/shared/types.ts +0 -29
- package/src/style/animation.scss +0 -50
- package/src/style/core.scss +0 -133
- package/src/style/fonts.scss +0 -73
- package/src/style/layout.scss +0 -179
- package/src/style/media-query.scss +0 -29
- package/src/style/reset.scss +0 -135
- package/src/style/text.scss +0 -137
- package/src/style/theme.scss +0 -195
- package/src/style/tooltip.scss +0 -146
- package/src/style/typography.scss +0 -435
- package/src/style/utils.scss +0 -36
- package/src/style.scss +0 -1
- package/src/vite-env.d.ts +0 -1
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import { useMagicKeys, whenever } from '@vueuse/core'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
|
-
import './kbd.scss'
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
/**
|
|
8
|
-
* Specify the key or the combination of keys connected with "+". Make sure
|
|
9
|
-
* there are no white spaces between letters.
|
|
10
|
-
*
|
|
11
|
-
* keys="Escape" keys="Ctrl+A"
|
|
12
|
-
*/
|
|
13
|
-
keys: string
|
|
14
|
-
/**
|
|
15
|
-
* Display custom label instead of the automatically formatted keys.
|
|
16
|
-
*/
|
|
17
|
-
label?: string
|
|
18
|
-
/**
|
|
19
|
-
* Show active state when this combination of keys is pressed.
|
|
20
|
-
*/
|
|
21
|
-
highlight?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const props = defineProps<Props>()
|
|
25
|
-
const emits = defineEmits<{
|
|
26
|
-
trigger: []
|
|
27
|
-
}>()
|
|
28
|
-
const keyHandler = useMagicKeys()
|
|
29
|
-
|
|
30
|
-
whenever(keyHandler[props.keys], () => {
|
|
31
|
-
emits('trigger')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const isActive = computed(() => {
|
|
35
|
-
if (!props.highlight)
|
|
36
|
-
return false
|
|
37
|
-
|
|
38
|
-
return props.keys.split('+').every((key) => {
|
|
39
|
-
return keyHandler.current.has(key.toLowerCase())
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
</script>
|
|
43
|
-
|
|
44
|
-
<template>
|
|
45
|
-
<kbd class="vui-kbd" :class="{ active: isActive }">
|
|
46
|
-
{{ props.label ?? props.keys.replaceAll("+", " + ") }}
|
|
47
|
-
</kbd>
|
|
48
|
-
</template>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import { useMagicKeys, whenever } from '@vueuse/core'
|
|
3
|
-
import { enforceSlotType, useFlattenedSlot } from '../../shared/slots'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Can be used to wrap multiple <Kbd /> elements and triggers the callback when
|
|
7
|
-
* all of them are active
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const emits = defineEmits<{
|
|
11
|
-
trigger: []
|
|
12
|
-
}>()
|
|
13
|
-
|
|
14
|
-
const slots = defineSlots()
|
|
15
|
-
const keys = useMagicKeys()
|
|
16
|
-
|
|
17
|
-
const flattened = useFlattenedSlot(slots.default)
|
|
18
|
-
enforceSlotType(flattened, 'Kbd')
|
|
19
|
-
|
|
20
|
-
whenever(
|
|
21
|
-
keys[flattened.value.map(vnode => vnode.props.keys).join('+')],
|
|
22
|
-
() => emits('trigger'),
|
|
23
|
-
)
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<template>
|
|
27
|
-
<slot />
|
|
28
|
-
</template>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
.vui-kbd {
|
|
2
|
-
display: inline-flex;
|
|
3
|
-
align-items: center;
|
|
4
|
-
justify-content: center;
|
|
5
|
-
height: 24px;
|
|
6
|
-
border-radius: var(--border-radius-xs);
|
|
7
|
-
font-size: var(--font-size-s);
|
|
8
|
-
padding: 0 4px;
|
|
9
|
-
transition: var(--transition-fast);
|
|
10
|
-
font-weight: 600;
|
|
11
|
-
background-color: var(--color-button-gray);
|
|
12
|
-
color: var(--color-text-accent);
|
|
13
|
-
|
|
14
|
-
&.active {
|
|
15
|
-
transform: translateY(2px);
|
|
16
|
-
color: var(--color-text-invert);
|
|
17
|
-
background-color: var(--color-button-fill);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { Variants } from '../Button/Button.vue'
|
|
3
|
-
import type { ModalProps } from './Modal.vue'
|
|
4
|
-
import Button from '../Button/Button.vue'
|
|
5
|
-
import Flex from '../Flex/Flex.vue'
|
|
6
|
-
import Modal from './Modal.vue'
|
|
7
|
-
|
|
8
|
-
type Props = {
|
|
9
|
-
title?: string
|
|
10
|
-
content?: string
|
|
11
|
-
confirmText?: string
|
|
12
|
-
confirmVariant?: Variants
|
|
13
|
-
cancelText?: string
|
|
14
|
-
showCancel?: boolean
|
|
15
|
-
} & Partial<ModalProps>
|
|
16
|
-
|
|
17
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
-
cancelText: 'Cancel',
|
|
19
|
-
confirmText: 'Ok',
|
|
20
|
-
size: 's',
|
|
21
|
-
canDismiss: true,
|
|
22
|
-
showCancel: true,
|
|
23
|
-
confirmVariant: 'gray',
|
|
24
|
-
open: false,
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const emit = defineEmits<{
|
|
28
|
-
cancel: []
|
|
29
|
-
confirm: []
|
|
30
|
-
close: []
|
|
31
|
-
}>()
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
<template>
|
|
35
|
-
<Modal
|
|
36
|
-
v-bind="props"
|
|
37
|
-
:open="props.open"
|
|
38
|
-
@close="emit('close')"
|
|
39
|
-
>
|
|
40
|
-
<template #default>
|
|
41
|
-
<div class="typeset">
|
|
42
|
-
<slot />
|
|
43
|
-
</div>
|
|
44
|
-
</template>
|
|
45
|
-
<template #footer>
|
|
46
|
-
<Flex x-end>
|
|
47
|
-
<Button v-if="props.showCancel" plain @click="emit('cancel'), emit('close')">
|
|
48
|
-
{{ props.cancelText }}
|
|
49
|
-
</Button>
|
|
50
|
-
<Button :variant="props.confirmVariant" @click="emit('confirm'), emit('close')">
|
|
51
|
-
{{ props.confirmText }}
|
|
52
|
-
</Button>
|
|
53
|
-
</Flex>
|
|
54
|
-
</template>
|
|
55
|
-
</Modal>
|
|
56
|
-
</template>
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { Sizes } from '../../shared/types'
|
|
3
|
-
import type { Props as CardProps } from '../Card/Card.vue'
|
|
4
|
-
import { useAttrs } from 'vue'
|
|
5
|
-
import Backdrop from '../../internal/Backdrop/Backdrop.vue'
|
|
6
|
-
import Button from '../Button/Button.vue'
|
|
7
|
-
import Card from '../Card/Card.vue'
|
|
8
|
-
import './modal.scss'
|
|
9
|
-
|
|
10
|
-
export interface ModalProps {
|
|
11
|
-
/**
|
|
12
|
-
* Controls the visibility of the modal
|
|
13
|
-
*/
|
|
14
|
-
open?: boolean
|
|
15
|
-
/**
|
|
16
|
-
* Controls the width of the modal
|
|
17
|
-
*/
|
|
18
|
-
size?: Sizes | 'full' | 'screen'
|
|
19
|
-
/**
|
|
20
|
-
* Modal wraps a floating card. You can optinally pass in any props you'd pass
|
|
21
|
-
* into the <Card /> component.
|
|
22
|
-
*/
|
|
23
|
-
card?: CardProps
|
|
24
|
-
/**
|
|
25
|
-
* Modal will not overflow the screen, but its card's content will be scrollable instead.
|
|
26
|
-
*/
|
|
27
|
-
scrollable?: boolean
|
|
28
|
-
/**
|
|
29
|
-
* Modal appears in the center of the screen
|
|
30
|
-
*/
|
|
31
|
-
centered?: boolean
|
|
32
|
-
/**
|
|
33
|
-
* Wether modal can be closed by clicking the X button
|
|
34
|
-
*/
|
|
35
|
-
canDismiss?: boolean
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const {
|
|
39
|
-
size = 'm',
|
|
40
|
-
card = {},
|
|
41
|
-
scrollable,
|
|
42
|
-
centered,
|
|
43
|
-
canDismiss = true,
|
|
44
|
-
open = false,
|
|
45
|
-
} = defineProps<ModalProps>()
|
|
46
|
-
|
|
47
|
-
const emit = defineEmits<{ close: [] }>()
|
|
48
|
-
|
|
49
|
-
const attrs = useAttrs()
|
|
50
|
-
|
|
51
|
-
function tryClose() {
|
|
52
|
-
if (canDismiss) {
|
|
53
|
-
emit('close')
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
</script>
|
|
57
|
-
|
|
58
|
-
<template>
|
|
59
|
-
<Teleport to="body">
|
|
60
|
-
<Transition appear name="modal">
|
|
61
|
-
<Backdrop v-if="open" :class="{ 'p-0': size === 'screen' }" @close="tryClose">
|
|
62
|
-
<div class="vui-modal" :class="[`vui-modal-size-${size}`, { scrollable: scrollable || size === 'screen', centered }]" v-bind="attrs" @click.self="tryClose">
|
|
63
|
-
<Card v-bind="card">
|
|
64
|
-
<template v-if="$slots.header" #header>
|
|
65
|
-
<slot name="header" :close="() => emit('close')" />
|
|
66
|
-
</template>
|
|
67
|
-
<template #header-end>
|
|
68
|
-
<Button
|
|
69
|
-
v-if="canDismiss"
|
|
70
|
-
class="vui-modal-close"
|
|
71
|
-
plain
|
|
72
|
-
square
|
|
73
|
-
icon="ph:x"
|
|
74
|
-
@click="emit('close')"
|
|
75
|
-
/>
|
|
76
|
-
</template>
|
|
77
|
-
<template v-if="$slots.default" #default>
|
|
78
|
-
<div>
|
|
79
|
-
<slot name="default" :close="() => emit('close')" />
|
|
80
|
-
</div>
|
|
81
|
-
</template>
|
|
82
|
-
<template v-if="$slots.footer" #footer>
|
|
83
|
-
<slot name="footer" :close="() => emit('close')" />
|
|
84
|
-
</template>
|
|
85
|
-
</Card>
|
|
86
|
-
</div>
|
|
87
|
-
</Backdrop>
|
|
88
|
-
</Transition>
|
|
89
|
-
</Teleport>
|
|
90
|
-
</template>
|
|
91
|
-
|
|
92
|
-
<style scoped>
|
|
93
|
-
.modal-enter-active,
|
|
94
|
-
.modal-leave-active {
|
|
95
|
-
transition: var(--transition);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.modal-enter-from,
|
|
99
|
-
.modal-leave-to {
|
|
100
|
-
opacity: 0;
|
|
101
|
-
transform: scale(1.025);
|
|
102
|
-
}
|
|
103
|
-
</style>
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
.vui-modal {
|
|
2
|
-
width: 100%;
|
|
3
|
-
margin-inline: auto;
|
|
4
|
-
height: 100%;
|
|
5
|
-
position: relative;
|
|
6
|
-
|
|
7
|
-
&.centered {
|
|
8
|
-
display: flex;
|
|
9
|
-
align-items: center;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
&.vui-modal-size-s {
|
|
13
|
-
max-width: 440px;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
&.vui-modal-size-m {
|
|
17
|
-
max-width: 620px;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
&.vui-modal-size-l {
|
|
21
|
-
max-width: 728px;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
&.vui-modal-size-screen {
|
|
25
|
-
position: fixed;
|
|
26
|
-
inset: 0;
|
|
27
|
-
|
|
28
|
-
& > .vui-card {
|
|
29
|
-
border-radius: 0;
|
|
30
|
-
border: none;
|
|
31
|
-
height: 100vh;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
&.scrollable {
|
|
36
|
-
& > .vui-card {
|
|
37
|
-
display: flex;
|
|
38
|
-
flex-direction: column;
|
|
39
|
-
inline-size: 100%;
|
|
40
|
-
max-height: 100%;
|
|
41
|
-
|
|
42
|
-
.vui-card-content {
|
|
43
|
-
flex: 1 1 0%;
|
|
44
|
-
overflow-y: auto;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
& > .vui-card {
|
|
50
|
-
width: 100%;
|
|
51
|
-
margin-bottom: var(--backdrop-offset);
|
|
52
|
-
box-shadow: var(--box-shadow-strong);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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,84 +0,0 @@
|
|
|
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: var(--interactive-el-height);
|
|
14
|
-
height: var(--interactive-el-height);
|
|
15
|
-
border: 1px solid var(--color-border-strong);
|
|
16
|
-
color: var(--color-text);
|
|
17
|
-
z-index: var(--z-default);
|
|
18
|
-
font-size: var(--font-size-m);
|
|
19
|
-
outline: 0 solid var(--color-text-light);
|
|
20
|
-
transition: var(--transition-fast);
|
|
21
|
-
|
|
22
|
-
.blinker {
|
|
23
|
-
display: block;
|
|
24
|
-
height: 16px;
|
|
25
|
-
width: 1px;
|
|
26
|
-
background-color: var(--color-text);
|
|
27
|
-
animation: blink 1s ease-out infinite;
|
|
28
|
-
visibility: hidden;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@keyframes blink {
|
|
32
|
-
0%,
|
|
33
|
-
70%,
|
|
34
|
-
100% {
|
|
35
|
-
opacity: 1;
|
|
36
|
-
}
|
|
37
|
-
20%,
|
|
38
|
-
50% {
|
|
39
|
-
opacity: 0;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
&.has-value {
|
|
44
|
-
background-color: var(--color-bg-medium);
|
|
45
|
-
|
|
46
|
-
.blinker {
|
|
47
|
-
display: none !important;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
&.active {
|
|
52
|
-
z-index: var(--z-active);
|
|
53
|
-
outline-width: 2px;
|
|
54
|
-
|
|
55
|
-
.blinker {
|
|
56
|
-
visibility: visible;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
&:not(:first-child) {
|
|
61
|
-
margin-left: -1px;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
&:first-child {
|
|
65
|
-
border-top-left-radius: var(--border-radius-m);
|
|
66
|
-
border-bottom-left-radius: var(--border-radius-m);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
&:last-child {
|
|
70
|
-
border-top-right-radius: var(--border-radius-m);
|
|
71
|
-
border-bottom-right-radius: var(--border-radius-m);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.vui-otp-input {
|
|
77
|
-
position: absolute;
|
|
78
|
-
inset: 0;
|
|
79
|
-
outline-width: 0px;
|
|
80
|
-
opacity: 0;
|
|
81
|
-
background: transparent;
|
|
82
|
-
z-index: var(--z-mask);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
<script setup lang='ts'>
|
|
2
|
-
import type { Variants } from '../Button/Button.vue'
|
|
3
|
-
import type { Pagination } from './pagination'
|
|
4
|
-
import { computed } from 'vue'
|
|
5
|
-
import Button from '../Button/Button.vue'
|
|
6
|
-
import Flex from '../Flex/Flex.vue'
|
|
7
|
-
import Tooltip from '../Tooltip/Tooltip.vue'
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
numbers?: boolean
|
|
11
|
-
pagination: Pagination
|
|
12
|
-
prevNext?: boolean
|
|
13
|
-
firstLast?: boolean
|
|
14
|
-
variant?: Variants
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
-
numbers: true,
|
|
19
|
-
prevNext: true,
|
|
20
|
-
firstLast: true,
|
|
21
|
-
variant: 'gray',
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const emit = defineEmits<{
|
|
25
|
-
change: [page: number]
|
|
26
|
-
}>()
|
|
27
|
-
|
|
28
|
-
const canNextPage = computed(() => props.pagination.currentPage < props.pagination.endPage)
|
|
29
|
-
const canPrevPage = computed(() => props.pagination.currentPage > props.pagination.startPage)
|
|
30
|
-
|
|
31
|
-
function setNext() {
|
|
32
|
-
emit('change', props.pagination.currentPage + 1)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function setPrev() {
|
|
36
|
-
emit('change', props.pagination.currentPage - 1)
|
|
37
|
-
}
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<template>
|
|
41
|
-
<Flex inline class="vui-pagination" gap="xxs">
|
|
42
|
-
<slot name="start">
|
|
43
|
-
<Tooltip v-if="props.firstLast">
|
|
44
|
-
<Button plain :disabled="props.pagination.startPage === props.pagination.currentPage" square icon="ph:caret-double-left" @click="emit('change', props.pagination.startPage)" />
|
|
45
|
-
<template #tooltip>
|
|
46
|
-
<p>First page</p>
|
|
47
|
-
</template>
|
|
48
|
-
</Tooltip>
|
|
49
|
-
</slot>
|
|
50
|
-
|
|
51
|
-
<slot name="prev" :disabled="canPrevPage" :set-page="setPrev">
|
|
52
|
-
<Tooltip v-if="props.prevNext">
|
|
53
|
-
<Button plain :disabled="!canPrevPage" square icon="ph:caret-left" @click="setPrev" />
|
|
54
|
-
<template #tooltip>
|
|
55
|
-
<p>Previous page</p>
|
|
56
|
-
</template>
|
|
57
|
-
</Tooltip>
|
|
58
|
-
</slot>
|
|
59
|
-
|
|
60
|
-
<template v-if="props.numbers">
|
|
61
|
-
<Flex gap="xxs">
|
|
62
|
-
<Button
|
|
63
|
-
v-for="page in props.pagination.pages"
|
|
64
|
-
:key="page"
|
|
65
|
-
square
|
|
66
|
-
:plain="props.pagination.currentPage !== page"
|
|
67
|
-
variant="gray"
|
|
68
|
-
@click="emit('change', page)"
|
|
69
|
-
>
|
|
70
|
-
{{ page }}
|
|
71
|
-
</Button>
|
|
72
|
-
</Flex>
|
|
73
|
-
</template>
|
|
74
|
-
<slot name="next" :disabled="canNextPage" :set-page="setNext">
|
|
75
|
-
<Tooltip v-if="props.prevNext">
|
|
76
|
-
<Button plain :disabled="!canNextPage" square icon="ph:caret-right" @click="setNext" />
|
|
77
|
-
<template #tooltip>
|
|
78
|
-
<p>Next page</p>
|
|
79
|
-
</template>
|
|
80
|
-
</Tooltip>
|
|
81
|
-
</slot>
|
|
82
|
-
|
|
83
|
-
<slot name="end">
|
|
84
|
-
<Tooltip v-if="props.firstLast">
|
|
85
|
-
<Button plain :disabled="props.pagination.endPage === props.pagination.currentPage" square icon="ph:caret-double-right" @click="emit('change', props.pagination.endPage)" />
|
|
86
|
-
<template #tooltip>
|
|
87
|
-
<p>Last page</p>
|
|
88
|
-
</template>
|
|
89
|
-
</Tooltip>
|
|
90
|
-
</slot>
|
|
91
|
-
</Flex>
|
|
92
|
-
</template>
|