@dolanske/vui 0.3.4 → 0.5.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 (114) hide show
  1. package/LICENSE +673 -673
  2. package/README.md +41 -40
  3. package/dist/components/Dropdown/DropdownItem.vue.d.ts +1 -0
  4. package/dist/components/Flex/Flex.vue.d.ts +3 -1
  5. package/dist/components/Grid/Grid.vue.d.ts +3 -1
  6. package/dist/components/Tabs/Tabs.vue.d.ts +4 -0
  7. package/dist/style.css +1 -1
  8. package/dist/vui.js +1547 -1534
  9. package/package.json +68 -68
  10. package/src/App.vue +176 -175
  11. package/src/components/Accordion/Accordion.vue +91 -91
  12. package/src/components/Accordion/AccordionGroup.vue +43 -43
  13. package/src/components/Accordion/accordion.scss +81 -80
  14. package/src/components/Alert/Alert.vue +53 -53
  15. package/src/components/Alert/alert.scss +80 -80
  16. package/src/components/Avatar/Avatar.vue +50 -50
  17. package/src/components/Avatar/avatar.scss +52 -52
  18. package/src/components/Badge/Badge.vue +21 -21
  19. package/src/components/Badge/badge.scss +89 -89
  20. package/src/components/Breadcrumbs/BreadcrumbItem.vue +26 -26
  21. package/src/components/Breadcrumbs/Breadcrumbs.vue +33 -33
  22. package/src/components/Breadcrumbs/breadcrumbs.scss +30 -30
  23. package/src/components/Button/Button.vue +90 -90
  24. package/src/components/Button/button.scss +178 -176
  25. package/src/components/ButtonGroup/ButtonGroup.vue +25 -25
  26. package/src/components/ButtonGroup/button-group.scss +51 -51
  27. package/src/components/Calendar/Calendar.vue +63 -60
  28. package/src/components/Calendar/calendar.scss +60 -56
  29. package/src/components/Card/Card.vue +48 -48
  30. package/src/components/Card/card.scss +53 -53
  31. package/src/components/Checkbox/Checkbox.vue +51 -52
  32. package/src/components/Checkbox/checkbox.scss +75 -66
  33. package/src/components/CopyClipboard/CopyClipboard.vue +82 -82
  34. package/src/components/CopyClipboard/copy-clipboard.scss +17 -17
  35. package/src/components/Divider/Divider.vue +44 -44
  36. package/src/components/Divider/divider.scss +35 -35
  37. package/src/components/Drawer/Drawer.vue +97 -97
  38. package/src/components/Drawer/drawer.scss +36 -36
  39. package/src/components/Dropdown/Dropdown.vue +111 -111
  40. package/src/components/Dropdown/DropdownItem.vue +33 -29
  41. package/src/components/Dropdown/DropdownTitle.vue +8 -8
  42. package/src/components/Dropdown/dropdown-item.scss +77 -0
  43. package/src/components/Dropdown/dropdown.scss +39 -117
  44. package/src/components/Flex/Flex.vue +113 -106
  45. package/src/components/Grid/Grid.vue +60 -54
  46. package/src/components/Input/Counter.vue +70 -70
  47. package/src/components/Input/Dropzone.vue +65 -65
  48. package/src/components/Input/File.vue +15 -15
  49. package/src/components/Input/Input.vue +121 -121
  50. package/src/components/Input/Password.vue +47 -47
  51. package/src/components/Input/Textarea.vue +76 -76
  52. package/src/components/Input/input.scss +208 -208
  53. package/src/components/Kbd/Kbd.vue +48 -48
  54. package/src/components/Kbd/KbdGroup.vue +31 -31
  55. package/src/components/Kbd/kbd.scss +18 -18
  56. package/src/components/Modal/Confirm.vue +56 -56
  57. package/src/components/Modal/Modal.vue +91 -91
  58. package/src/components/Modal/modal.scss +49 -49
  59. package/src/components/OTP/OTP.vue +133 -133
  60. package/src/components/OTP/OTPItem.vue +37 -37
  61. package/src/components/OTP/otp.scss +83 -83
  62. package/src/components/Pagination/Pagination.vue +74 -74
  63. package/src/components/Pagination/pagination.ts +78 -78
  64. package/src/components/Popout/Popout.vue +42 -42
  65. package/src/components/Popout/popout.scss +8 -8
  66. package/src/components/Progress/Progress.vue +90 -90
  67. package/src/components/Progress/progress.scss +41 -41
  68. package/src/components/Radio/Radio.vue +36 -36
  69. package/src/components/Radio/RadioGroup.vue +40 -40
  70. package/src/components/Radio/radio.scss +68 -59
  71. package/src/components/Select/Select.vue +180 -180
  72. package/src/components/Select/select.scss +44 -44
  73. package/src/components/Sheet/Sheet.vue +92 -92
  74. package/src/components/Sheet/sheet.scss +60 -60
  75. package/src/components/Sidebar/Sidebar.vue +102 -0
  76. package/src/components/Sidebar/sidebar.scss +123 -0
  77. package/src/components/Skeleton/Skeleton.vue +43 -43
  78. package/src/components/Skeleton/skeleton.scss +14 -14
  79. package/src/components/Spinner/Spinner.vue +42 -42
  80. package/src/components/Spinner/spinner.scss +46 -46
  81. package/src/components/Switch/Switch.vue +30 -30
  82. package/src/components/Switch/switch.scss +60 -52
  83. package/src/components/Table/Cell.vue +23 -23
  84. package/src/components/Table/Header.vue +59 -59
  85. package/src/components/Table/Row.vue +9 -9
  86. package/src/components/Table/SelectAll.vue +23 -23
  87. package/src/components/Table/SelectRow.vue +29 -29
  88. package/src/components/Table/Table.vue +66 -66
  89. package/src/components/Table/table.scss +134 -134
  90. package/src/components/Table/table.ts +244 -244
  91. package/src/components/Tabs/Tab.vue +27 -27
  92. package/src/components/Tabs/Tabs.vue +89 -82
  93. package/src/components/Tabs/tabs.scss +80 -79
  94. package/src/components/Toast/Toasts.vue +47 -47
  95. package/src/components/Toast/toast.scss +41 -41
  96. package/src/components/Toast/toast.ts +68 -68
  97. package/src/components/Tooltip/Tooltip.vue +86 -86
  98. package/src/components/Tooltip/tooltip.scss +4 -4
  99. package/src/index.scss +1 -1
  100. package/src/index.ts +119 -119
  101. package/src/internal/Backdrop/Backdrop.vue +22 -22
  102. package/src/internal/Backdrop/backdrop.scss +28 -28
  103. package/src/main.ts +5 -5
  104. package/src/shared/helpers.ts +74 -74
  105. package/src/shared/types.ts +29 -29
  106. package/src/style/animation.scss +21 -21
  107. package/src/style/core.scss +150 -148
  108. package/src/style/layout.scss +168 -136
  109. package/src/style/media-query.scss +29 -29
  110. package/src/style/reset.scss +135 -135
  111. package/src/style/{fonts.scss → text.scss} +74 -53
  112. package/src/style/tooltip.scss +128 -128
  113. package/src/style/typography.scss +338 -338
  114. package/src/style/utils.scss +36 -36
@@ -1,65 +1,65 @@
1
- <script setup lang='ts'>
2
- import type { InputProps } from './Input.vue'
3
- import { Icon } from '@iconify/vue'
4
- import { useEventListener } from '@vueuse/core'
5
- import { onMounted, ref, useTemplateRef } from 'vue'
6
- import Flex from '../Flex/Flex.vue'
7
- import Input from './Input.vue'
8
-
9
- type Props = Omit<InputProps, 'type' | 'modelValue' | 'focus' | 'limit' | 'placeholder'>
10
-
11
- const props = defineProps<Props>()
12
- const emits = defineEmits<{ files: [FileList] }>()
13
-
14
- const dragging = ref(false)
15
-
16
- function onSubmitHandler(e: any, isFromField?: boolean) {
17
- e.preventDefault()
18
- e.stopPropagation()
19
-
20
- const files = isFromField ? e.target.files : e.dataTransfer.files
21
-
22
- if (files.length > 0)
23
- emits('files', files)
24
- }
25
-
26
- const dropzoneRef = useTemplateRef<HTMLDivElement>('dropzone')
27
-
28
- onMounted(() => {
29
- useEventListener(dropzoneRef, 'dragenter', onSubmitHandler, false)
30
- useEventListener(dropzoneRef, 'dragleave', onSubmitHandler, false)
31
- useEventListener(dropzoneRef, 'dragover', onSubmitHandler, false)
32
- useEventListener(dropzoneRef, 'drop', onSubmitHandler, false)
33
- useEventListener(dropzoneRef, 'input', e => onSubmitHandler(e, true), false)
34
- })
35
- </script>
36
-
37
- <template>
38
- <Input
39
- v-bind="props"
40
- ref="dropzone"
41
- type="file"
42
- class="vui-dropzone"
43
- :class="{ dragging }"
44
- @dragenter="dragging = true"
45
- @mouseleave="dragging = false"
46
- >
47
- <template #__internal_replace_input="{ inputId }">
48
- <input :id="inputId" type="file">
49
- <label :for="inputId">
50
- <slot :dragging>
51
- <Flex justify-center gap="s" align-baseline>
52
- <template v-if="dragging">
53
- <Icon icon="ph:target" />
54
- Drop it
55
- </template>
56
- <template v-else>
57
- <Icon icon="ph:file" />
58
- Click or drag files over here
59
- </template>
60
- </Flex>
61
- </slot>
62
- </label>
63
- </template>
64
- </Input>
65
- </template>
1
+ <script setup lang='ts'>
2
+ import type { InputProps } from './Input.vue'
3
+ import { Icon } from '@iconify/vue'
4
+ import { useEventListener } from '@vueuse/core'
5
+ import { onMounted, ref, useTemplateRef } from 'vue'
6
+ import Flex from '../Flex/Flex.vue'
7
+ import Input from './Input.vue'
8
+
9
+ type Props = Omit<InputProps, 'type' | 'modelValue' | 'focus' | 'limit' | 'placeholder'>
10
+
11
+ const props = defineProps<Props>()
12
+ const emits = defineEmits<{ files: [FileList] }>()
13
+
14
+ const dragging = ref(false)
15
+
16
+ function onSubmitHandler(e: any, isFromField?: boolean) {
17
+ e.preventDefault()
18
+ e.stopPropagation()
19
+
20
+ const files = isFromField ? e.target.files : e.dataTransfer.files
21
+
22
+ if (files.length > 0)
23
+ emits('files', files)
24
+ }
25
+
26
+ const dropzoneRef = useTemplateRef<HTMLDivElement>('dropzone')
27
+
28
+ onMounted(() => {
29
+ useEventListener(dropzoneRef, 'dragenter', onSubmitHandler, false)
30
+ useEventListener(dropzoneRef, 'dragleave', onSubmitHandler, false)
31
+ useEventListener(dropzoneRef, 'dragover', onSubmitHandler, false)
32
+ useEventListener(dropzoneRef, 'drop', onSubmitHandler, false)
33
+ useEventListener(dropzoneRef, 'input', e => onSubmitHandler(e, true), false)
34
+ })
35
+ </script>
36
+
37
+ <template>
38
+ <Input
39
+ v-bind="props"
40
+ ref="dropzone"
41
+ type="file"
42
+ class="vui-dropzone"
43
+ :class="{ dragging }"
44
+ @dragenter="dragging = true"
45
+ @mouseleave="dragging = false"
46
+ >
47
+ <template #__internal_replace_input="{ inputId }">
48
+ <input :id="inputId" type="file">
49
+ <label :for="inputId">
50
+ <slot :dragging>
51
+ <Flex justify-center gap="s" align-baseline>
52
+ <template v-if="dragging">
53
+ <Icon icon="ph:target" />
54
+ Drop it
55
+ </template>
56
+ <template v-else>
57
+ <Icon icon="ph:file" />
58
+ Click or drag files over here
59
+ </template>
60
+ </Flex>
61
+ </slot>
62
+ </label>
63
+ </template>
64
+ </Input>
65
+ </template>
@@ -1,15 +1,15 @@
1
- <script setup lang='ts'>
2
- import type { InputProps } from './Input.vue'
3
- import Input from './Input.vue'
4
-
5
- const props = defineProps<Omit<InputProps, 'type'>>()
6
- const emits = defineEmits<{ files: [FileList] }>()
7
- </script>
8
-
9
- <template>
10
- <Input
11
- type="file"
12
- v-bind="{ ...props }"
13
- @input="emits('files', $event.target.files)"
14
- />
15
- </template>
1
+ <script setup lang='ts'>
2
+ import type { InputProps } from './Input.vue'
3
+ import Input from './Input.vue'
4
+
5
+ const props = defineProps<Omit<InputProps, 'type'>>()
6
+ const emits = defineEmits<{ files: [FileList] }>()
7
+ </script>
8
+
9
+ <template>
10
+ <Input
11
+ type="file"
12
+ v-bind="{ ...props }"
13
+ @input="emits('files', $event.target.files)"
14
+ />
15
+ </template>
@@ -1,121 +1,121 @@
1
- <script setup lang="ts">
2
- import { computed, type InputTypeHTMLAttribute, useId, useTemplateRef, watchEffect } from 'vue'
3
- import { getMaybeRefLength } from '../../shared/helpers'
4
- import Flex from '../Flex/Flex.vue'
5
- import '../../style/core.scss'
6
- import './input.scss'
7
-
8
- // FIXME: sometimes spamming inputs when limit is set, it will _just_ let me write one more letter
9
-
10
- export interface InputProps {
11
- type?: InputTypeHTMLAttribute
12
- label?: string
13
- hint?: string
14
- limit?: number | string
15
- expand?: boolean
16
- placeholder?: string
17
- required?: boolean
18
- modelValue?: string | number
19
- readonly?: boolean
20
- focus?: boolean
21
- errors?: string[]
22
- accept?: string
23
- multiple?: boolean
24
- min?: number
25
- max?: number
26
- disabled?: boolean
27
- }
28
-
29
- const {
30
- type = 'text',
31
- limit,
32
- label,
33
- expand = false,
34
- hint,
35
- placeholder,
36
- required,
37
- modelValue = '',
38
- readonly,
39
- focus,
40
- accept,
41
- min,
42
- max,
43
- errors = [] as string[],
44
- disabled,
45
- } = defineProps<InputProps>()
46
-
47
- const model = defineModel<string | number>({
48
- default: '',
49
- set(newValue) {
50
- // Completely ignore inputs
51
- if (readonly)
52
- return modelValue
53
-
54
- if (getMaybeRefLength(newValue) > Number(limit)) {
55
- return modelValue
56
- }
57
- return newValue
58
- },
59
- })
60
- const id = useId()
61
-
62
- const inputRef = useTemplateRef('input')
63
-
64
- watchEffect(() => {
65
- if (focus)
66
- inputRef.value?.focus()
67
- })
68
-
69
- defineExpose({
70
- focus: () => {
71
- inputRef.value?.focus()
72
- },
73
- })
74
-
75
- const renderLimit = computed(() => {
76
- return `${getMaybeRefLength(model.value)}/${limit}`
77
- })
78
- </script>
79
-
80
- <template>
81
- <div class="vui-input-container" :class="{ expand, disabled, required, readonly, 'has-errors': errors.length > 0 }">
82
- <slot name="before" />
83
- <div class="vui-input">
84
- <label v-if="label" for="id">{{ label }}</label>
85
- <p v-if="hint" class="vui-input-hint">
86
- {{ hint }}
87
- </p>
88
- <Flex class="vui-input-style" :gap="3" align-center>
89
- <slot name="start" />
90
- <slot name="__internal_replace_input" :input-id="id" />
91
- <input
92
- v-if="!$slots.__internal_replace_input"
93
- :id
94
- ref="input"
95
- v-model="model"
96
- :readonly
97
- :type
98
- name="id"
99
- :placeholder
100
- :required
101
- :max="max ?? limit"
102
- :accept
103
- :multiple
104
- :min
105
- :disabled
106
- >
107
- <slot name="end" />
108
- </Flex>
109
- </div>
110
- <p v-if="limit" class="vui-input-limit">
111
- {{ renderLimit }}
112
- </p>
113
- <ul v-if="errors.length > 0" class="vui-input-errors">
114
- <li v-for="err in errors" :key="err">
115
- {{ err }}
116
- </li>
117
- </ul>
118
-
119
- <slot name="after" />
120
- </div>
121
- </template>
1
+ <script setup lang="ts">
2
+ import { computed, type InputTypeHTMLAttribute, useId, useTemplateRef, watchEffect } from 'vue'
3
+ import { getMaybeRefLength } from '../../shared/helpers'
4
+ import Flex from '../Flex/Flex.vue'
5
+ import '../../style/core.scss'
6
+ import './input.scss'
7
+
8
+ // FIXME: sometimes spamming inputs when limit is set, it will _just_ let me write one more letter
9
+
10
+ export interface InputProps {
11
+ type?: InputTypeHTMLAttribute
12
+ label?: string
13
+ hint?: string
14
+ limit?: number | string
15
+ expand?: boolean
16
+ placeholder?: string
17
+ required?: boolean
18
+ modelValue?: string | number
19
+ readonly?: boolean
20
+ focus?: boolean
21
+ errors?: string[]
22
+ accept?: string
23
+ multiple?: boolean
24
+ min?: number
25
+ max?: number
26
+ disabled?: boolean
27
+ }
28
+
29
+ const {
30
+ type = 'text',
31
+ limit,
32
+ label,
33
+ expand = false,
34
+ hint,
35
+ placeholder,
36
+ required,
37
+ modelValue = '',
38
+ readonly,
39
+ focus,
40
+ accept,
41
+ min,
42
+ max,
43
+ errors = [] as string[],
44
+ disabled,
45
+ } = defineProps<InputProps>()
46
+
47
+ const model = defineModel<string | number>({
48
+ default: '',
49
+ set(newValue) {
50
+ // Completely ignore inputs
51
+ if (readonly)
52
+ return modelValue
53
+
54
+ if (getMaybeRefLength(newValue) > Number(limit)) {
55
+ return modelValue
56
+ }
57
+ return newValue
58
+ },
59
+ })
60
+ const id = useId()
61
+
62
+ const inputRef = useTemplateRef('input')
63
+
64
+ watchEffect(() => {
65
+ if (focus)
66
+ inputRef.value?.focus()
67
+ })
68
+
69
+ defineExpose({
70
+ focus: () => {
71
+ inputRef.value?.focus()
72
+ },
73
+ })
74
+
75
+ const renderLimit = computed(() => {
76
+ return `${getMaybeRefLength(model.value)}/${limit}`
77
+ })
78
+ </script>
79
+
80
+ <template>
81
+ <div class="vui-input-container" :class="{ expand, disabled, required, readonly, 'has-errors': errors.length > 0 }">
82
+ <slot name="before" />
83
+ <div class="vui-input">
84
+ <label v-if="label" for="id">{{ label }}</label>
85
+ <p v-if="hint" class="vui-input-hint">
86
+ {{ hint }}
87
+ </p>
88
+ <Flex class="vui-input-style" :gap="3" align-center>
89
+ <slot name="start" />
90
+ <slot name="__internal_replace_input" :input-id="id" />
91
+ <input
92
+ v-if="!$slots.__internal_replace_input"
93
+ :id
94
+ ref="input"
95
+ v-model="model"
96
+ :readonly
97
+ :type
98
+ name="id"
99
+ :placeholder
100
+ :required
101
+ :max="max ?? limit"
102
+ :accept
103
+ :multiple
104
+ :min
105
+ :disabled
106
+ >
107
+ <slot name="end" />
108
+ </Flex>
109
+ </div>
110
+ <p v-if="limit" class="vui-input-limit">
111
+ {{ renderLimit }}
112
+ </p>
113
+ <ul v-if="errors.length > 0" class="vui-input-errors">
114
+ <li v-for="err in errors" :key="err">
115
+ {{ err }}
116
+ </li>
117
+ </ul>
118
+
119
+ <slot name="after" />
120
+ </div>
121
+ </template>
@@ -1,47 +1,47 @@
1
- <script setup lang='ts'>
2
- import type { InputProps } from './Input.vue'
3
- import { Icon } from '@iconify/vue'
4
- import { ref } from 'vue'
5
- import Button from '../Button/Button.vue'
6
- import Input from './Input.vue'
7
-
8
- type Props = Omit<InputProps, 'type'> & {
9
- showPassword?: boolean
10
- }
11
-
12
- const {
13
- showPassword = false,
14
- // @ts-expect-error Don't want to actually provide type as we handle that in
15
- // this component manually
16
- type,
17
- ...inputProps
18
- } = defineProps<Props>()
19
-
20
- const show = ref(showPassword)
21
- </script>
22
-
23
- <template>
24
- <Input v-bind="inputProps" :type="show ? 'text' : 'password'" autocomplete="off">
25
- <template #end>
26
- <Button
27
- square
28
- size="s"
29
- :data-title-top="show ? 'Hide password' : 'Show password'"
30
- @click="show = !show"
31
- >
32
- <Icon :width="18" :height="18" :icon="show ? 'ph:eye-slash' : 'ph:eye'" />
33
- </Button>
34
- </template>
35
- </Input>
36
- </template>
37
-
38
- <style scoped lang="scss">
39
- :deep(.vui-input-style) {
40
- padding-right: 4px !important;
41
- gap: 8px !important;
42
- }
43
-
44
- [data-title-top]:before {
45
- min-width: 104px;
46
- }
47
- </style>
1
+ <script setup lang='ts'>
2
+ import type { InputProps } from './Input.vue'
3
+ import { Icon } from '@iconify/vue'
4
+ import { ref } from 'vue'
5
+ import Button from '../Button/Button.vue'
6
+ import Input from './Input.vue'
7
+
8
+ type Props = Omit<InputProps, 'type'> & {
9
+ showPassword?: boolean
10
+ }
11
+
12
+ const {
13
+ showPassword = false,
14
+ // @ts-expect-error Don't want to actually provide type as we handle that in
15
+ // this component manually
16
+ type,
17
+ ...inputProps
18
+ } = defineProps<Props>()
19
+
20
+ const show = ref(showPassword)
21
+ </script>
22
+
23
+ <template>
24
+ <Input v-bind="inputProps" :type="show ? 'text' : 'password'" autocomplete="off">
25
+ <template #end>
26
+ <Button
27
+ square
28
+ size="s"
29
+ :data-title-top="show ? 'Hide password' : 'Show password'"
30
+ @click="show = !show"
31
+ >
32
+ <Icon :width="18" :height="18" :icon="show ? 'ph:eye-slash' : 'ph:eye'" />
33
+ </Button>
34
+ </template>
35
+ </Input>
36
+ </template>
37
+
38
+ <style scoped lang="scss">
39
+ :deep(.vui-input-style) {
40
+ padding-right: 4px !important;
41
+ gap: 8px !important;
42
+ }
43
+
44
+ [data-title-top]:before {
45
+ min-width: 104px;
46
+ }
47
+ </style>
@@ -1,76 +1,76 @@
1
- <script setup lang="ts">
2
- import type { InputProps } from './Input.vue'
3
- import { computed, useId } from 'vue'
4
- import '../Input/input.scss'
5
-
6
- type Props = Omit<InputProps, 'type'> & {
7
- resize?: boolean | 'vertical' | 'horizontal'
8
- autoResize?: boolean
9
- }
10
-
11
- const {
12
- limit,
13
- label,
14
- expand = false,
15
- hint,
16
- placeholder,
17
- required,
18
- modelValue = '',
19
- readonly,
20
- resize = 'vertical',
21
- autoResize,
22
- errors = [] as string[],
23
- } = defineProps<Props>()
24
-
25
- const model = defineModel<string>({
26
- default: '',
27
- set(newValue) {
28
- if (readonly)
29
- return modelValue
30
-
31
- if (newValue.length > Number(limit)) {
32
- return modelValue
33
- }
34
- return newValue
35
- },
36
- })
37
- const id = useId()
38
-
39
- const r = computed(() => resize === true ? 'both' : (resize || 'initial'))
40
- const fS = computed(() => autoResize ? 'content' : 'auto')
41
- </script>
42
-
43
- <template>
44
- <div class="vui-input-container" :class="{ expand, required, readonly, 'has-errors': errors.length > 0 }">
45
- <slot name="before" />
46
- <div class="vui-input">
47
- <label v-if="label" for="id">{{ label }}</label>
48
- <p v-if="hint" class="vui-input-hint">
49
- {{ hint }}
50
- </p>
51
- <textarea
52
- :id
53
- v-model="model"
54
- :readonly
55
- name="id"
56
- :placeholder
57
- :required
58
- :max="limit"
59
- :style="{
60
- resize: r,
61
- // @ts-expect-error Early-adoption CSS attribute
62
- fieldSizing: fS,
63
- }"
64
- />
65
- </div>
66
- <p v-if="limit" class="vui-input-limit">
67
- {{ `${model.length}/${limit}` }}
68
- </p>
69
- <ul v-if="errors.length > 0" class="vui-input-errors">
70
- <li v-for="err in errors" :key="err">
71
- {{ err }}
72
- </li>
73
- </ul>
74
- <slot name="after" />
75
- </div>
76
- </template>
1
+ <script setup lang="ts">
2
+ import type { InputProps } from './Input.vue'
3
+ import { computed, useId } from 'vue'
4
+ import '../Input/input.scss'
5
+
6
+ type Props = Omit<InputProps, 'type'> & {
7
+ resize?: boolean | 'vertical' | 'horizontal'
8
+ autoResize?: boolean
9
+ }
10
+
11
+ const {
12
+ limit,
13
+ label,
14
+ expand = false,
15
+ hint,
16
+ placeholder,
17
+ required,
18
+ modelValue = '',
19
+ readonly,
20
+ resize = 'vertical',
21
+ autoResize,
22
+ errors = [] as string[],
23
+ } = defineProps<Props>()
24
+
25
+ const model = defineModel<string>({
26
+ default: '',
27
+ set(newValue) {
28
+ if (readonly)
29
+ return modelValue
30
+
31
+ if (newValue.length > Number(limit)) {
32
+ return modelValue
33
+ }
34
+ return newValue
35
+ },
36
+ })
37
+ const id = useId()
38
+
39
+ const r = computed(() => resize === true ? 'both' : (resize || 'initial'))
40
+ const fS = computed(() => autoResize ? 'content' : 'auto')
41
+ </script>
42
+
43
+ <template>
44
+ <div class="vui-input-container" :class="{ expand, required, readonly, 'has-errors': errors.length > 0 }">
45
+ <slot name="before" />
46
+ <div class="vui-input">
47
+ <label v-if="label" for="id">{{ label }}</label>
48
+ <p v-if="hint" class="vui-input-hint">
49
+ {{ hint }}
50
+ </p>
51
+ <textarea
52
+ :id
53
+ v-model="model"
54
+ :readonly
55
+ name="id"
56
+ :placeholder
57
+ :required
58
+ :max="limit"
59
+ :style="{
60
+ resize: r,
61
+ // @ts-expect-error Early-adoption CSS attribute
62
+ fieldSizing: fS,
63
+ }"
64
+ />
65
+ </div>
66
+ <p v-if="limit" class="vui-input-limit">
67
+ {{ `${model.length}/${limit}` }}
68
+ </p>
69
+ <ul v-if="errors.length > 0" class="vui-input-errors">
70
+ <li v-for="err in errors" :key="err">
71
+ {{ err }}
72
+ </li>
73
+ </ul>
74
+ <slot name="after" />
75
+ </div>
76
+ </template>