@dolanske/vui 0.1.0 → 0.1.1

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 (161) hide show
  1. package/README.md +7 -0
  2. package/dist/components/Accordion/Accordion.vue.d.ts +45 -0
  3. package/dist/components/Accordion/AccordionGroup.vue.d.ts +32 -0
  4. package/dist/components/Alert/Alert.vue.d.ts +29 -0
  5. package/dist/components/Avatar/Avatar.vue.d.ts +9 -0
  6. package/dist/components/Badge/Badge.vue.d.ts +21 -0
  7. package/dist/components/Breadcrumbs/BreadcrumbItem.vue.d.ts +21 -0
  8. package/dist/components/Breadcrumbs/Breadcrumbs.vue.d.ts +27 -0
  9. package/dist/components/Button/Button.vue.d.ts +41 -0
  10. package/dist/components/ButtonGroup/ButtonGroup.vue.d.ts +19 -0
  11. package/dist/components/Calendar/Calendar.vue.d.ts +27 -0
  12. package/dist/components/Card/Card.vue.d.ts +25 -0
  13. package/dist/components/Checkbox/Checkbox.vue.d.ts +31 -0
  14. package/dist/components/CopyClipboard/CopyClipboard.vue.d.ts +40 -0
  15. package/dist/components/Divider/Divider.vue.d.ts +24 -0
  16. package/dist/components/Drawer/Drawer.vue.d.ts +52 -0
  17. package/dist/components/Dropdown/DropdownItem.vue.d.ts +21 -0
  18. package/dist/components/Dropdown/DropdownTitle.vue.d.ts +17 -0
  19. package/dist/components/Flex/Flex.vue.d.ts +38 -0
  20. package/dist/components/Grid/Grid.vue.d.ts +27 -0
  21. package/dist/components/Input/Counter.vue.d.ts +19 -0
  22. package/dist/components/Input/Dropzone.vue.d.ts +107 -0
  23. package/dist/components/Input/File.vue.d.ts +7 -0
  24. package/dist/components/Input/Input.vue.d.ts +54 -0
  25. package/dist/components/Input/Password.vue.d.ts +6 -0
  26. package/dist/components/Input/Textarea.vue.d.ts +30 -0
  27. package/dist/components/Kbd/Kbd.vue.d.ts +23 -0
  28. package/dist/components/Kbd/KbdGroup.vue.d.ts +31 -0
  29. package/dist/components/Modal/Confirm.vue.d.ts +45 -0
  30. package/dist/components/Modal/Modal.vue.d.ts +55 -0
  31. package/dist/components/Pagination/Pagination.vue.d.ts +42 -0
  32. package/dist/components/Pagination/pagination.d.ts +12 -0
  33. package/dist/components/Popout/Popout.vue.d.ts +34 -0
  34. package/dist/components/Progress/Progress.vue.d.ts +31 -0
  35. package/dist/components/Radio/Radio.vue.d.ts +27 -0
  36. package/dist/components/Radio/RadioGroup.vue.d.ts +40 -0
  37. package/dist/components/Select/Select.vue.d.ts +37 -0
  38. package/dist/components/Sheet/Sheet.vue.d.ts +35 -0
  39. package/dist/components/Skeleton/Skeleton.vue.d.ts +8 -0
  40. package/dist/components/Spinner/Spinner.vue.d.ts +6 -0
  41. package/dist/components/Switch/Switch.vue.d.ts +26 -0
  42. package/dist/components/Table/Cell.vue.d.ts +19 -0
  43. package/dist/components/Table/Header.vue.d.ts +29 -0
  44. package/dist/components/Table/Row.vue.d.ts +16 -0
  45. package/dist/components/Table/SelectAll.vue.d.ts +2 -0
  46. package/dist/components/Table/SelectRow.vue.d.ts +6 -0
  47. package/dist/components/Table/Table.vue.d.ts +40 -0
  48. package/dist/components/Table/table.d.ts +68 -0
  49. package/dist/components/Tabs/Tab.vue.d.ts +8 -0
  50. package/dist/components/Tabs/Tabs.vue.d.ts +43 -0
  51. package/dist/components/Toast/Toasts.vue.d.ts +2 -0
  52. package/dist/components/Toast/toast.d.ts +42 -0
  53. package/dist/components/Tooltip/Tooltip.vue.d.ts +32 -0
  54. package/dist/index.d.ts +54 -1
  55. package/dist/internal/Backdrop/Backdrop.vue.d.ts +20 -0
  56. package/dist/shared/composables.d.ts +3 -0
  57. package/dist/shared/helpers.d.ts +16 -0
  58. package/dist/shared/types.d.ts +10 -0
  59. package/dist/style.css +1 -1
  60. package/package.json +11 -9
  61. package/src/App.vue +158 -0
  62. package/src/components/Accordion/Accordion.vue +75 -0
  63. package/src/components/Accordion/AccordionGroup.vue +43 -0
  64. package/src/components/Accordion/accordion.scss +44 -0
  65. package/src/components/Alert/Alert.vue +53 -0
  66. package/src/components/Alert/alert.scss +80 -0
  67. package/src/components/Avatar/Avatar.vue +36 -0
  68. package/src/components/Avatar/avatar.scss +46 -0
  69. package/src/components/Badge/Badge.vue +21 -0
  70. package/src/components/Badge/badge.scss +89 -0
  71. package/src/components/Breadcrumbs/BreadcrumbItem.vue +26 -0
  72. package/src/components/Breadcrumbs/Breadcrumbs.vue +33 -0
  73. package/src/components/Breadcrumbs/breadcrumbs.scss +30 -0
  74. package/src/components/Button/Button.vue +90 -0
  75. package/src/components/Button/button.scss +176 -0
  76. package/src/components/ButtonGroup/ButtonGroup.vue +25 -0
  77. package/src/components/ButtonGroup/button-group.scss +51 -0
  78. package/src/components/Calendar/Calendar.vue +58 -0
  79. package/src/components/Calendar/calendar.scss +56 -0
  80. package/src/components/Card/Card.vue +48 -0
  81. package/src/components/Card/card.scss +53 -0
  82. package/src/components/Checkbox/Checkbox.vue +49 -0
  83. package/src/components/Checkbox/checkbox.scss +60 -0
  84. package/src/components/CopyClipboard/CopyClipboard.vue +82 -0
  85. package/src/components/CopyClipboard/copy-clipboard.scss +17 -0
  86. package/src/components/Divider/Divider.vue +34 -0
  87. package/src/components/Divider/divider.scss +35 -0
  88. package/src/components/Drawer/Drawer.vue +93 -0
  89. package/src/components/Drawer/drawer.scss +49 -0
  90. package/src/components/Dropdown/Dropdown.vue +100 -0
  91. package/src/components/Dropdown/DropdownItem.vue +29 -0
  92. package/src/components/Dropdown/DropdownTitle.vue +8 -0
  93. package/src/components/Dropdown/dropdown.scss +112 -0
  94. package/src/components/Flex/Flex.vue +109 -0
  95. package/src/components/Grid/Grid.vue +59 -0
  96. package/src/components/Input/Counter.vue +70 -0
  97. package/src/components/Input/Dropzone.vue +63 -0
  98. package/src/components/Input/File.vue +15 -0
  99. package/src/components/Input/Input.vue +118 -0
  100. package/src/components/Input/Password.vue +47 -0
  101. package/src/components/Input/Textarea.vue +73 -0
  102. package/src/components/Input/input.scss +199 -0
  103. package/src/components/Kbd/Kbd.vue +48 -0
  104. package/src/components/Kbd/KbdGroup.vue +31 -0
  105. package/src/components/Kbd/kbd.scss +18 -0
  106. package/src/components/Modal/Confirm.vue +56 -0
  107. package/src/components/Modal/Modal.vue +91 -0
  108. package/src/components/Modal/modal.scss +49 -0
  109. package/src/components/Pagination/Pagination.vue +74 -0
  110. package/src/components/Pagination/pagination.ts +78 -0
  111. package/src/components/Popout/Popout.vue +39 -0
  112. package/src/components/Popout/popout.scss +7 -0
  113. package/src/components/Progress/Progress.vue +84 -0
  114. package/src/components/Progress/progress.scss +41 -0
  115. package/src/components/Radio/Radio.vue +36 -0
  116. package/src/components/Radio/RadioGroup.vue +35 -0
  117. package/src/components/Radio/radio.scss +59 -0
  118. package/src/components/Select/Select.vue +180 -0
  119. package/src/components/Select/select.scss +43 -0
  120. package/src/components/Sheet/Sheet.vue +91 -0
  121. package/src/components/Sheet/sheet.scss +56 -0
  122. package/src/components/Skeleton/Skeleton.vue +46 -0
  123. package/src/components/Skeleton/skeleton.scss +14 -0
  124. package/src/components/Spinner/Spinner.vue +44 -0
  125. package/src/components/Spinner/spinner.scss +46 -0
  126. package/src/components/Switch/Switch.vue +30 -0
  127. package/src/components/Switch/switch.scss +52 -0
  128. package/src/components/Table/Cell.vue +23 -0
  129. package/src/components/Table/Header.vue +59 -0
  130. package/src/components/Table/Row.vue +9 -0
  131. package/src/components/Table/SelectAll.vue +23 -0
  132. package/src/components/Table/SelectRow.vue +29 -0
  133. package/src/components/Table/Table.vue +66 -0
  134. package/src/components/Table/table.scss +134 -0
  135. package/src/components/Table/table.ts +243 -0
  136. package/src/components/Tabs/Tab.vue +21 -0
  137. package/src/components/Tabs/Tabs.vue +76 -0
  138. package/src/components/Tabs/tabs.scss +78 -0
  139. package/src/components/Toast/Toasts.vue +47 -0
  140. package/src/components/Toast/toast.scss +41 -0
  141. package/src/components/Toast/toast.ts +92 -0
  142. package/src/components/Tooltip/Tooltip.vue +80 -0
  143. package/src/components/Tooltip/tooltip.scss +4 -0
  144. package/src/index.scss +1 -0
  145. package/src/index.ts +111 -0
  146. package/src/internal/Backdrop/Backdrop.vue +22 -0
  147. package/src/internal/Backdrop/backdrop.scss +28 -0
  148. package/src/main.ts +5 -0
  149. package/src/shared/composables.ts +18 -0
  150. package/src/shared/helpers.ts +53 -0
  151. package/src/shared/types.ts +11 -0
  152. package/src/style/animation.scss +21 -0
  153. package/src/style/core.scss +128 -0
  154. package/src/style/fonts.scss +0 -0
  155. package/src/style/layout.scss +9 -0
  156. package/src/style/media-query.scss +29 -0
  157. package/src/style/reset.scss +135 -0
  158. package/src/style/tooltip.scss +128 -0
  159. package/src/style/typography.scss +339 -0
  160. package/src/style/utils.scss +22 -0
  161. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,63 @@
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 { 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>('dropzoneRef')
27
+
28
+ useEventListener(dropzoneRef, 'dragenter', onSubmitHandler, false)
29
+ useEventListener(dropzoneRef, 'dragleave', onSubmitHandler, false)
30
+ useEventListener(dropzoneRef, 'dragover', onSubmitHandler, false)
31
+ useEventListener(dropzoneRef, 'drop', onSubmitHandler, false)
32
+ useEventListener(dropzoneRef, 'input', e => onSubmitHandler(e, true), false)
33
+ </script>
34
+
35
+ <template>
36
+ <Input
37
+ v-bind="props"
38
+ ref="dropzoneRef"
39
+ type="file"
40
+ class="vui-dropzone"
41
+ :class="{ dragging }"
42
+ @dragenter="dragging = true"
43
+ @mouseleave="dragging = false"
44
+ >
45
+ <template #__internal_replace_input="{ inputId }">
46
+ <input :id="inputId" type="file">
47
+ <label :for="inputId">
48
+ <slot :dragging>
49
+ <Flex justify-center gap="s" align-baseline>
50
+ <template v-if="dragging">
51
+ <Icon icon="ph:target" />
52
+ Drop it
53
+ </template>
54
+ <template v-else>
55
+ <Icon icon="ph:file" />
56
+ Click or drag files over here
57
+ </template>
58
+ </Flex>
59
+ </slot>
60
+ </label>
61
+ </template>
62
+ </Input>
63
+ </template>
@@ -0,0 +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>
@@ -0,0 +1,118 @@
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
+ }
27
+
28
+ const {
29
+ type = 'text',
30
+ limit,
31
+ label,
32
+ expand = false,
33
+ hint,
34
+ placeholder,
35
+ required,
36
+ modelValue = '',
37
+ readonly,
38
+ focus,
39
+ accept,
40
+ min,
41
+ max,
42
+ errors = [] as string[],
43
+ } = defineProps<InputProps>()
44
+
45
+ const model = defineModel<string | number>({
46
+ default: '',
47
+ set(newValue) {
48
+ // Completely ignore inputs
49
+ if (readonly)
50
+ return modelValue
51
+
52
+ if (getMaybeRefLength(newValue) > Number(limit)) {
53
+ return modelValue
54
+ }
55
+ return newValue
56
+ },
57
+ })
58
+ const id = useId()
59
+
60
+ const inputRef = useTemplateRef('inputRef')
61
+
62
+ watchEffect(() => {
63
+ if (focus)
64
+ inputRef.value?.focus()
65
+ })
66
+
67
+ defineExpose({
68
+ focus: () => {
69
+ inputRef.value?.focus()
70
+ },
71
+ })
72
+
73
+ const renderLimit = computed(() => {
74
+ return `${getMaybeRefLength(model.value)}/${limit}`
75
+ })
76
+ </script>
77
+
78
+ <template>
79
+ <div class="vui-input-container" :class="{ expand, required, readonly, 'has-errors': errors.length > 0 }">
80
+ <slot name="before" />
81
+ <div class="vui-input">
82
+ <label v-if="label" for="id">{{ label }}</label>
83
+ <p v-if="hint" class="vui-input-hint">
84
+ {{ hint }}
85
+ </p>
86
+ <Flex class="vui-input-style" :gap="3" align-center>
87
+ <slot name="start" />
88
+ <slot name="__internal_replace_input" :input-id="id" />
89
+ <input
90
+ v-if="!$slots.__internal_replace_input"
91
+ :id
92
+ ref="inputRef"
93
+ v-model="model"
94
+ :readonly
95
+ :type
96
+ name="id"
97
+ :placeholder
98
+ :required
99
+ :max="max ?? limit"
100
+ :accept
101
+ :multiple
102
+ :min
103
+ >
104
+ <slot name="end" />
105
+ </Flex>
106
+ </div>
107
+ <p v-if="limit" class="vui-input-limit">
108
+ {{ renderLimit }}
109
+ </p>
110
+ <ul v-if="errors.length > 0" class="vui-input-errors">
111
+ <li v-for="err in errors" :key="err">
112
+ {{ err }}
113
+ </li>
114
+ </ul>
115
+
116
+ <slot name="after" />
117
+ </div>
118
+ </template>
@@ -0,0 +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>
@@ -0,0 +1,73 @@
1
+ <script setup lang="ts">
2
+ import type { InputProps } from './Input.vue'
3
+ import { 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
+ </script>
39
+
40
+ <template>
41
+ <div class="vui-input-container" :class="{ expand, required, readonly, 'has-errors': errors.length > 0 }">
42
+ <slot name="before" />
43
+ <div class="vui-input">
44
+ <label v-if="label" for="id">{{ label }}</label>
45
+ <p v-if="hint" class="vui-input-hint">
46
+ {{ hint }}
47
+ </p>
48
+ <textarea
49
+ :id
50
+ v-model="model"
51
+ :readonly
52
+ name="id"
53
+ :placeholder
54
+ :required
55
+ :max="limit"
56
+ :style="{
57
+ 'resize': resize === true ? 'both' : (resize || 'initial'),
58
+ // @ts-expect-error This is only supported in chrome for now
59
+ 'field-sizing': autoResize ? 'content' : 'auto',
60
+ }"
61
+ />
62
+ </div>
63
+ <p v-if="limit" class="vui-input-limit">
64
+ {{ `${model.length}/${limit}` }}
65
+ </p>
66
+ <ul v-if="errors.length > 0" class="vui-input-errors">
67
+ <li v-for="err in errors" :key="err">
68
+ {{ err }}
69
+ </li>
70
+ </ul>
71
+ <slot name="after" />
72
+ </div>
73
+ </template>
@@ -0,0 +1,199 @@
1
+ .vui-input-container {
2
+ // Each component should have its own specification as it allows more granual
3
+ // modification via CSS variables if needed
4
+ --input-color-text-red: var(--color-text-red);
5
+ --input-color-text: var(--color-text);
6
+ --input-color-text-light: var(--color-text-light);
7
+ --input-color-text-lighter: var(--color-text-lighter);
8
+ --input-color-border: var(--color-border);
9
+ --input-color-border-strong: var(--color-border-strong);
10
+ --input-color-border-weak: var(--color-border-weak);
11
+
12
+ --input-height: 34px;
13
+ --input-padding: 8px;
14
+ --textarea-padding: 8px;
15
+
16
+ //
17
+
18
+ width: 224px;
19
+
20
+ &.expand {
21
+ width: 100%;
22
+ }
23
+
24
+ &.required .vui-input > label:after {
25
+ content: '*';
26
+ color: var(--input-color-text-red);
27
+ }
28
+
29
+ &.readonly .vui-input input {
30
+ pointer-events: none;
31
+ color: var(--input-color-text-light);
32
+ border-color: var(--input-color-border-weak);
33
+ }
34
+
35
+ &.has-errors {
36
+ .vui-input {
37
+ .vui-input-style,
38
+ textarea {
39
+ border-color: var(--color-text-red) !important;
40
+ }
41
+ }
42
+ }
43
+
44
+ .vui-input {
45
+ label {
46
+ display: block;
47
+ text-align: left;
48
+ margin-bottom: 8px;
49
+ font-size: var(--font-size-ms);
50
+ color: var(--input-color-text);
51
+ }
52
+
53
+ .vui-input-hint {
54
+ margin-bottom: 8px;
55
+ margin-top: -4px;
56
+ color: var(--input-color-text-lighter);
57
+ font-size: var(--font-size-s);
58
+ display: block;
59
+ text-align: left;
60
+ }
61
+
62
+ ::placeholder {
63
+ font-weight: 400;
64
+ color: var(--color-text-lighter);
65
+ font-family: var(--global-font);
66
+ }
67
+
68
+ .vui-input-style,
69
+ textarea {
70
+ display: block;
71
+ border-radius: var(--border-radius-s);
72
+ border: 1px solid var(--input-color-border);
73
+ background: transparent;
74
+ height: var(--input-height);
75
+ line-height: var(--input-height);
76
+ color: var(--color-text);
77
+ outline: none !important;
78
+ padding-inline: var(--input-padding);
79
+ font-size: var(--font-size-ms);
80
+ width: 100%;
81
+ transition: var(--transition);
82
+
83
+ &:has(input:focus-visible),
84
+ &:focus-visible {
85
+ border-color: var(--input-color-border-strong);
86
+ }
87
+ }
88
+
89
+ input,
90
+ textarea {
91
+ font-family: var(--global-font);
92
+ }
93
+
94
+ input[type='file']::file-selector-button {
95
+ background-color: var(--color-bg);
96
+ color: var(--color-text);
97
+ border: none;
98
+ }
99
+
100
+ input[type='range'] {
101
+ -webkit-appearance: none; /* Override default CSS styles */
102
+ appearance: none;
103
+ height: 4px;
104
+ border-radius: 2px;
105
+ background-color: var(--color-border);
106
+ }
107
+
108
+ ::-moz-range-thumb,
109
+ ::-webkit-slider-thumb {
110
+ width: 16px;
111
+ height: 16px;
112
+ background-color: var(--color-text-light);
113
+ border: none;
114
+ }
115
+
116
+ input {
117
+ display: block;
118
+ width: 100%;
119
+ border: none;
120
+ height: calc(var(--input-height) - 1px);
121
+ line-height: calc(var(--input-height) - 1px);
122
+ background: transparent;
123
+ outline: none;
124
+ font-size: var(--font-size-ms);
125
+ color: var(--color-text);
126
+
127
+ &:-webkit-autofill {
128
+ box-shadow: 0 0 0px 1000px var(--color-bg) inset;
129
+ -webkit-text-fill-color: var(--color-text);
130
+ }
131
+ }
132
+
133
+ textarea {
134
+ line-height: 1.3em;
135
+ height: auto;
136
+ min-height: 4lh;
137
+ field-sizing: content;
138
+ padding: var(--textarea-padding);
139
+ transition: none;
140
+ }
141
+ }
142
+
143
+ .vui-input-limit {
144
+ display: block;
145
+ margin-top: 6px;
146
+ text-align: left;
147
+ font-size: var(--font-size-xs);
148
+ color: var(--input-color-text-lighter);
149
+ }
150
+
151
+ .vui-input-errors {
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: 6px;
155
+ padding-top: 6px;
156
+
157
+ li {
158
+ display: block;
159
+ font-size: var(--font-size-s);
160
+ color: var(--color-text-red);
161
+ }
162
+ }
163
+
164
+ &.vui-dropzone {
165
+ &.dragging .vui-input .vui-input-style {
166
+ border-color: var(--color-border-accent);
167
+
168
+ &:hover {
169
+ border-color: var(--color-border);
170
+ }
171
+ }
172
+ .vui-input {
173
+ .vui-input-style {
174
+ height: unset;
175
+ border-width: 2px;
176
+ border-style: dashed;
177
+ border-radius: var(--border-radius-m);
178
+ line-height: 1.2em;
179
+
180
+ &:hover {
181
+ border-color: var(--color-border-strong);
182
+ }
183
+
184
+ input {
185
+ display: none;
186
+
187
+ & + label {
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ width: 100%;
192
+ min-height: 96px;
193
+ color: var(--color-text-light);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,48 @@
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>
@@ -0,0 +1,31 @@
1
+ <script setup lang='ts'>
2
+ import type { VNode } from 'vue'
3
+ import { useMagicKeys, whenever } from '@vueuse/core'
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
+ default: () => Array<VNode & {
16
+ props: {
17
+ keys: string
18
+ }
19
+ }>
20
+ }>()
21
+ const keys = useMagicKeys()
22
+
23
+ whenever(
24
+ keys[slots.default().map(vnode => vnode.props.keys).join('+')],
25
+ () => emits('trigger'),
26
+ )
27
+ </script>
28
+
29
+ <template>
30
+ <slot />
31
+ </template>
@@ -0,0 +1,18 @@
1
+ .vui-kbd {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ height: 24px;
6
+ border-radius: var(--border-radius-xs);
7
+ // border: 1px solid var(--color-border-strong);
8
+ font-size: var(--font-size-s);
9
+ padding: 0 4px;
10
+ transition: var(--transition);
11
+ font-weight: 600;
12
+ background-color: var(--color-text);
13
+ color: var(--color-text-invert);
14
+
15
+ &.active {
16
+ background-color: var(--color-text-yellow);
17
+ }
18
+ }
@@ -0,0 +1,56 @@
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: 'default',
24
+ })
25
+
26
+ const emits = defineEmits<{
27
+ cancel: []
28
+ confirm: []
29
+ }>()
30
+
31
+ const open = defineModel<boolean>()
32
+ </script>
33
+
34
+ <template>
35
+ <pre>{{ $props }}</pre>
36
+ <Modal
37
+ v-bind="props"
38
+ v-model="open"
39
+ >
40
+ <template #default>
41
+ <div class="typeset">
42
+ <slot />
43
+ </div>
44
+ </template>
45
+ <template #footer>
46
+ <Flex justify-end>
47
+ <Button v-if="props.showCancel" plain @click="emits('cancel'), open = false">
48
+ {{ props.cancelText }}
49
+ </Button>
50
+ <Button :variant="props.confirmVariant" @click="emits('confirm'), open = false">
51
+ {{ props.confirmText }}
52
+ </Button>
53
+ </Flex>
54
+ </template>
55
+ </Modal>
56
+ </template>