@dolanske/vui 0.1.4 → 0.2.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.
Files changed (55) hide show
  1. package/README.md +27 -96
  2. package/dist/components/Accordion/Accordion.vue.d.ts +1 -0
  3. package/dist/components/Avatar/Avatar.vue.d.ts +4 -2
  4. package/dist/components/Divider/Divider.vue.d.ts +2 -4
  5. package/dist/components/Dropdown/Dropdown.vue.d.ts +105 -0
  6. package/dist/components/Input/Dropzone.vue.d.ts +1 -0
  7. package/dist/components/Input/Input.vue.d.ts +1 -0
  8. package/dist/components/OTP/OTP.vue.d.ts +43 -0
  9. package/dist/components/OTP/OTPItem.vue.d.ts +5 -0
  10. package/dist/components/Popout/Popout.vue.d.ts +3 -3
  11. package/dist/components/Radio/Radio.vue.d.ts +1 -1
  12. package/dist/components/Radio/RadioGroup.vue.d.ts +3 -12
  13. package/dist/components/Tooltip/Tooltip.vue.d.ts +1 -1
  14. package/dist/index.d.ts +3 -1
  15. package/dist/shared/helpers.d.ts +8 -1
  16. package/dist/shared/types.d.ts +3 -0
  17. package/dist/style.css +1 -1
  18. package/dist/vui.js +4752 -4542
  19. package/package.json +1 -1
  20. package/src/App.vue +8 -26
  21. package/src/components/Accordion/Accordion.vue +17 -3
  22. package/src/components/Accordion/accordion.scss +38 -2
  23. package/src/components/Avatar/Avatar.vue +25 -4
  24. package/src/components/Avatar/avatar.scss +5 -5
  25. package/src/components/Button/Button.vue +4 -4
  26. package/src/components/Calendar/Calendar.vue +10 -8
  27. package/src/components/Card/Card.vue +2 -2
  28. package/src/components/Checkbox/Checkbox.vue +4 -1
  29. package/src/components/Checkbox/checkbox.scss +12 -6
  30. package/src/components/Divider/Divider.vue +22 -12
  31. package/src/components/Drawer/Drawer.vue +6 -8
  32. package/src/components/Drawer/drawer.scss +0 -13
  33. package/src/components/Dropdown/Dropdown.vue +13 -8
  34. package/src/components/Input/Input.vue +4 -1
  35. package/src/components/Input/Textarea.vue +11 -6
  36. package/src/components/Input/input.scss +13 -4
  37. package/src/components/OTP/OTP.vue +133 -0
  38. package/src/components/OTP/OTPItem.vue +37 -0
  39. package/src/components/OTP/otp.scss +84 -0
  40. package/src/components/Popout/Popout.vue +3 -3
  41. package/src/components/Progress/Progress.vue +19 -11
  42. package/src/components/Progress/progress.scss +1 -1
  43. package/src/components/Radio/Radio.vue +1 -1
  44. package/src/components/Radio/RadioGroup.vue +10 -5
  45. package/src/components/Sheet/Sheet.vue +16 -15
  46. package/src/components/Sheet/sheet.scss +4 -0
  47. package/src/components/Skeleton/Skeleton.vue +19 -20
  48. package/src/components/Spinner/Spinner.vue +5 -5
  49. package/src/components/Table/table.ts +2 -1
  50. package/src/components/Tooltip/Tooltip.vue +1 -1
  51. package/src/index.ts +4 -0
  52. package/src/shared/helpers.ts +22 -1
  53. package/src/shared/types.ts +6 -0
  54. package/src/style/core.scss +4 -1
  55. package/src/style/layout.scss +4 -0
@@ -0,0 +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>
@@ -0,0 +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>
@@ -0,0 +1,84 @@
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-l);
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
+ // TODO: animate blinking cursor
51
+ &.active {
52
+ z-index: 2;
53
+ outline-width: 2px;
54
+
55
+ .blinker {
56
+ display: block;
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: 5;
83
+ }
84
+ }
@@ -1,11 +1,11 @@
1
1
  <script setup lang='ts'>
2
- import type { MaybeElement, Placement } from '@floating-ui/vue'
2
+ import type { Placement, PopoutMaybeElement } from '../../shared/types'
3
3
  import { autoPlacement, offset, useFloating } from '@floating-ui/vue'
4
4
  import { toRef, useTemplateRef } from 'vue'
5
5
  import './popout.scss'
6
6
 
7
- interface Props {
8
- anchor: MaybeElement<HTMLElement>
7
+ export interface Props {
8
+ anchor: PopoutMaybeElement<HTMLElement>
9
9
  /**
10
10
  * Override the autoPlacement option
11
11
  */
@@ -1,6 +1,6 @@
1
1
  <script setup lang='ts'>
2
- import { onMounted, useTemplateRef, watchEffect } from 'vue'
3
- import { delay, isNil, randomMinMax } from '../../shared/helpers'
2
+ import { computed, onMounted, useTemplateRef, watchEffect } from 'vue'
3
+ import { delay, formatUnitValue, isNil, randomMinMax } from '../../shared/helpers'
4
4
  import './progress.scss'
5
5
 
6
6
  interface Props {
@@ -42,16 +42,19 @@ const progressRef = useTemplateRef('progress')
42
42
 
43
43
  watchEffect(() => {
44
44
  if (progressRef.value && !isNil(height)) {
45
- progressRef.value.style.setProperty('--vui-progress-height', `${height}px`)
45
+ progressRef.value.style.setProperty(
46
+ '--vui-progress-height',
47
+ formatUnitValue(height),
48
+ )
46
49
  }
47
50
  })
48
51
 
49
52
  // Automatically / randomly increment but never reach 100% until
50
53
  async function fakeIncrement() {
51
54
  if (fake && progressAmount.value < 100) {
52
- if (progressAmount.value > 95) {
55
+ if (progressAmount.value > 90) {
53
56
  // Only in crement by the fraction of the remaining amount
54
- progressAmount.value += (100 - progressAmount.value) * 0.075
57
+ progressAmount.value += (100 - progressAmount.value) * 0.05
55
58
  await delay(randomMinMax(500, 3000))
56
59
  }
57
60
  else {
@@ -63,6 +66,9 @@ async function fakeIncrement() {
63
66
  }
64
67
 
65
68
  onMounted(fakeIncrement)
69
+
70
+ const w = computed(() => `${progressAmount.value}%`)
71
+ const bC = computed(() => color)
66
72
  </script>
67
73
 
68
74
  <template>
@@ -74,11 +80,13 @@ onMounted(fakeIncrement)
74
80
  'fixed-active': progressAmount > 0 && progressAmount < 100,
75
81
  }"
76
82
  >
77
- <div
78
- class="vui-progress-indicator" :style="{
79
- width: `${progressAmount}%`,
80
- backgroundColor: color,
81
- }"
82
- />
83
+ <div class="vui-progress-indicator" />
83
84
  </div>
84
85
  </template>
86
+
87
+ <style scoped lang="scss">
88
+ .vui-progress-indicator {
89
+ width: v-bind(w);
90
+ background-color: v-bind(bC);
91
+ }
92
+ </style>
@@ -1,5 +1,6 @@
1
1
  .vui-progress {
2
2
  --vui-progress-height: 3px;
3
+
3
4
  display: block;
4
5
  width: 100%;
5
6
  position: relative;
@@ -18,7 +19,6 @@
18
19
  border-radius: none !important;
19
20
 
20
21
  &.fixed-active {
21
- // Some arbitrary value which should never happen to people with
22
22
  height: var(--vui-progress-height);
23
23
  }
24
24
 
@@ -6,7 +6,7 @@ import './radio.scss'
6
6
  export interface RadioProps {
7
7
  label?: string
8
8
  disabled?: boolean
9
- value: string
9
+ value: any
10
10
  }
11
11
 
12
12
  const {
@@ -1,7 +1,7 @@
1
1
  <script setup lang='ts'>
2
- import type { VNode } from 'vue'
3
2
  import type { FlexProps } from '../Flex/Flex.vue'
4
- import type { RadioProps } from './Radio.vue'
3
+ import type Radio from './Radio.vue'
4
+ import { watchEffect } from 'vue'
5
5
  import Flex from '../Flex/Flex.vue'
6
6
 
7
7
  interface Props extends FlexProps {
@@ -14,22 +14,27 @@ const {
14
14
  } = defineProps<Props>()
15
15
 
16
16
  const slots = defineSlots<{
17
- default: () => { children: Array<VNode & { props: RadioProps }> }[]
17
+ default: () => Array<typeof Radio>
18
18
  }>()
19
19
 
20
20
  const checked = defineModel()
21
+
22
+ watchEffect(() => {
23
+ if (slots.default().some(s => s.type.__name !== 'Radio')) {
24
+ console.error('You can only pass `<Radio />` components as children.')
25
+ }
26
+ })
21
27
  </script>
22
28
 
23
29
  <template>
24
30
  <Flex v-bind="flexProps">
25
31
  <Component
26
32
  :is="vnode"
27
- v-for="vnode of slots.default()[0].children"
33
+ v-for="vnode of slots.default()"
28
34
  :key="vnode.props.value"
29
35
  v-bind="vnode.props"
30
36
  v-model="checked"
31
37
  :class="{ disabled: disabled || vnode.props.disabled }"
32
38
  />
33
39
  </Flex>
34
- <!-- <div class="vui-radio-group" /> -->
35
40
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang='ts'>
2
2
  import { computed } from 'vue'
3
3
  import Backdrop from '../../internal/Backdrop/Backdrop.vue'
4
+ import { formatUnitValue } from '../../shared/helpers'
4
5
  import Button from '../Button/Button.vue'
5
6
  import Divider from '../Divider/Divider.vue'
6
7
  import './sheet.scss'
@@ -17,6 +18,8 @@ const {
17
18
  separator,
18
19
  } = defineProps<Props>()
19
20
 
21
+ const TRANSITION_OFFSET = 16
22
+
20
23
  const open = defineModel<boolean>()
21
24
 
22
25
  function close() {
@@ -24,27 +27,21 @@ function close() {
24
27
  }
25
28
 
26
29
  const style = computed(() => {
27
- const formattedSizeValue = typeof size === 'number' ? `${size}px` : size
28
- let style
29
-
30
30
  if (position === 'left' || position === 'right') {
31
- style = { width: formattedSizeValue }
32
- }
33
- else {
34
- style = { minHeight: formattedSizeValue }
31
+ return { width: formatUnitValue(size) }
35
32
  }
36
33
 
37
- return style
34
+ return undefined
38
35
  })
39
36
 
40
37
  // Used to compute base location so that sheet appears to animate form the edge of the screen
41
38
  const baseTransform = computed(() => {
42
39
  switch (position) {
43
- case 'left': return `translate(-16px, 0)`
44
- case 'top': return `translate(0, -16px)`
45
- case 'bottom': return `translate(0, 16px)`
40
+ case 'left': return `translate(-${TRANSITION_OFFSET}px, 0)`
41
+ case 'top': return `translate(0, -${TRANSITION_OFFSET}px)`
42
+ case 'bottom': return `translate(0, ${TRANSITION_OFFSET}px)`
46
43
  case 'right':
47
- default: return `translate(16px, 0)`
44
+ default: return `translate(${TRANSITION_OFFSET}px, 0)`
48
45
  }
49
46
  })
50
47
  </script>
@@ -52,16 +49,16 @@ const baseTransform = computed(() => {
52
49
  <template>
53
50
  <Teleport to="body">
54
51
  <Transition appear name="sheet">
55
- <Backdrop v-if="open" :style="{ padding: 0 }" @close="open = false">
52
+ <Backdrop v-if="open" @close="open = false">
56
53
  <div v-if="open" class="vui-sheet" :class="[`vui-sheet-position-${position}`]" :style>
57
54
  <div class="vui-sheet-header">
58
- <div :style="{ flex: 1 }">
55
+ <div class="flex-1">
59
56
  <slot name="header" :close />
60
57
  </div>
61
58
  <Button square icon="ph:x" @click="open = false" />
62
59
  </div>
63
60
 
64
- <Divider v-if="separator && $slots.header" :size="1" />
61
+ <Divider v-if="separator && $slots.header" :space="1" />
65
62
 
66
63
  <div v-if="$slots.default" class="vui-sheet-content">
67
64
  <slot :close />
@@ -73,6 +70,10 @@ const baseTransform = computed(() => {
73
70
  </template>
74
71
 
75
72
  <style scoped lang="scss">
73
+ .vui-backdrop {
74
+ padding: 0;
75
+ }
76
+
76
77
  .sheet-enter-active,
77
78
  .sheet-leave-active {
78
79
  transition: var(--transition);
@@ -9,20 +9,24 @@
9
9
  &.vui-sheet-position-top {
10
10
  top: 0;
11
11
  border-bottom: 1px solid var(--color-border);
12
+ height: auto;
12
13
  }
13
14
 
14
15
  &.vui-sheet-position-bottom {
15
16
  bottom: 0;
16
17
  border-top: 1px solid var(--color-border);
18
+ height: auto;
17
19
  }
18
20
 
19
21
  &.vui-sheet-position-right {
20
22
  right: 0;
23
+ top: 0;
21
24
  border-left: 1px solid var(--color-border);
22
25
  }
23
26
 
24
27
  &.vui-sheet-position-left {
25
28
  left: 0;
29
+ top: 0;
26
30
  border-right: 1px solid var(--color-border);
27
31
  }
28
32
 
@@ -1,5 +1,7 @@
1
1
  <!-- eslint-disable ts/no-use-before-define -->
2
2
  <script setup lang='ts'>
3
+ import { computed } from 'vue'
4
+ import { formatUnitValue } from '../../shared/helpers'
3
5
  import './skeleton.scss'
4
6
 
5
7
  interface Props {
@@ -20,27 +22,24 @@ const {
20
22
 
21
23
  const DEFAULT_RADIUS = 'var(--border-radius-s)'
22
24
 
23
- function valueToPixels(value: string | number) {
24
- return typeof value === 'number'
25
- ? `${value}px`
26
- // If last value of string is NOT a number, do not add "px" at the end
27
- // eslint-disable-next-line unicorn/prefer-number-properties
28
- : isNaN(Number(value[value.length - 1]))
29
- ? value
30
- : `${value}px`
31
- }
25
+ // Give priority to radius, if it is NOT set to default
26
+ const bR = computed(() => formatUnitValue(circle && radius === DEFAULT_RADIUS ? 9999 : radius))
27
+
28
+ // When using `circle` prop, we want to use the same
29
+ // value for both height and width, but we can't
30
+ // know which one will be defined
31
+ const w = computed(() => formatUnitValue(circle ? (width || height) : width))
32
+ const h = computed(() => formatUnitValue(circle ? (width || height) : height))
32
33
  </script>
33
34
 
34
35
  <template>
35
- <div
36
- class="vui-skeleton" :style="{
37
- // Give priority to radius, if it is NOT set to default
38
- borderRadius: valueToPixels(circle && radius === DEFAULT_RADIUS ? 9999 : radius),
39
- // When using `circle` prop, we want to use the same
40
- // value for both height and width, but we can't
41
- // know which one will be defined
42
- width: valueToPixels(circle ? (width || height) : width),
43
- height: valueToPixels(circle ? (width || height) : height),
44
- }"
45
- />
36
+ <div class="vui-skeleton" />
46
37
  </template>
38
+
39
+ <style lang="scss" scoped>
40
+ .vui-skeleton {
41
+ border-radius: v-bind(bR);
42
+ width: v-bind(w);
43
+ height: v-bind(h);
44
+ }
45
+ </style>
@@ -12,7 +12,7 @@ const {
12
12
  size = 's',
13
13
  } = defineProps<Props>()
14
14
 
15
- const actualSize = computed(() => {
15
+ const w = computed(() => {
16
16
  switch (size) {
17
17
  case Size.s: return '16px'
18
18
  case Size.l: return '38px'
@@ -21,7 +21,7 @@ const actualSize = computed(() => {
21
21
  }
22
22
  })
23
23
 
24
- const actualBorderWidth = computed(() => {
24
+ const bW = computed(() => {
25
25
  switch (size) {
26
26
  case Size.s: return '3px'
27
27
  case Size.l: return '5px'
@@ -37,8 +37,8 @@ const actualBorderWidth = computed(() => {
37
37
 
38
38
  <style lang="scss">
39
39
  .vui-spinner {
40
- width: v-bind(actualSize);
41
- height: v-bind(actualSize);
42
- border-width: v-bind(actualBorderWidth);
40
+ width: v-bind(w);
41
+ height: v-bind(w);
42
+ border-width: v-bind(bW);
43
43
  }
44
44
  </style>
@@ -125,7 +125,8 @@ export function defineTable<const Dataset extends Array<BaseRow>>(
125
125
  const key = sorting.value.key
126
126
 
127
127
  if (key) {
128
- final = final.toSorted((a: Dataset[number], b: Dataset[number]) => {
128
+ // FIXME: change to `toSorted` when typescript is ok
129
+ final = [...final].sort((a: Dataset[number], b: Dataset[number]) => {
129
130
  const aValue = a[key]
130
131
  const bValue = b[key]
131
132
  return sorting.value.type === 'asc'
@@ -1,5 +1,5 @@
1
1
  <script setup lang='ts'>
2
- import type { Placement } from '@floating-ui/vue'
2
+ import type { Placement } from '../../shared/types'
3
3
  import { ref, useTemplateRef, watch } from 'vue'
4
4
  import Popout from '../Popout/Popout.vue'
5
5
  import './tooltip.scss'
package/src/index.ts CHANGED
@@ -28,6 +28,8 @@ import Kbd from './components/Kbd/Kbd.vue'
28
28
  import KbdGroup from './components/Kbd/KbdGroup.vue'
29
29
  import Confirm from './components/Modal/Confirm.vue'
30
30
  import Modal from './components/Modal/Modal.vue'
31
+ import OTP from './components/OTP/OTP.vue'
32
+ import OTPItem from './components/OTP/OTPItem.vue'
31
33
  import Pagination from './components/Pagination/Pagination.vue'
32
34
  import Popout from './components/Popout/Popout.vue'
33
35
  import Progress from './components/Progress/Progress.vue'
@@ -85,6 +87,8 @@ export {
85
87
  Kbd,
86
88
  KbdGroup,
87
89
  Modal,
90
+ OTP,
91
+ OTPItem,
88
92
  Pagination,
89
93
  Password,
90
94
  Popout,
@@ -21,7 +21,7 @@ export function getMaybeRefLength(value: string | number): number {
21
21
  return typeof value === 'number' ? value : value.length
22
22
  }
23
23
 
24
- export function isNil(value: any): value is undefined {
24
+ export function isNil(value: any): value is undefined | null {
25
25
  return value === undefined || value === null
26
26
  }
27
27
 
@@ -51,3 +51,24 @@ export function randomMinMax(min: number, max: number): number {
51
51
  export function delay(amount: number): Promise<any> {
52
52
  return new Promise(r => setTimeout(r, amount))
53
53
  }
54
+
55
+ export function setCharAt(str: string, char: string | number, index: number): string {
56
+ if (str.length === 0)
57
+ return char.toString()
58
+ return str.substring(0, index) + char + str.substring(index + 1)
59
+ }
60
+
61
+ /**
62
+ * Takes in a value and if it is a number, appends "px" to it, otherwise returns
63
+ * the original value.
64
+ *
65
+ */
66
+ export function formatUnitValue(value: string | number, unit: string = 'px'): string {
67
+ return typeof value === 'number'
68
+ ? `${value}${unit}`
69
+ // If last value of string is NOT a number, do not add "px" at the end
70
+ // eslint-disable-next-line unicorn/prefer-number-properties
71
+ : isNaN(Number(value[value.length - 1]))
72
+ ? value
73
+ : `${value}${unit}`
74
+ }
@@ -1,3 +1,5 @@
1
+ import type { ComponentPublicInstance } from 'vue'
2
+
1
3
  export enum Size {
2
4
  s = 's',
3
5
  m = 'm',
@@ -9,3 +11,7 @@ export type Sizes = 's' | 'm' | 'l'
9
11
  export type DeepRequired<T> = { [K in keyof T]: DeepRequired<T[K]> } & Required<T>
10
12
 
11
13
  export type VueClass = string | Record<string, | boolean> | Array<string | Record<string, string | boolean>>
14
+
15
+ // FLoating UI imported types were ruining the build so here we go
16
+ export type PopoutMaybeElement<T> = T | ComponentPublicInstance | null | undefined
17
+ export type Placement = 'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end'