@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,180 @@
1
+ <!-- eslint-disable ts/consistent-type-definitions -->
2
+ <script setup lang='ts' generic="T">
3
+ import { Icon } from '@iconify/vue'
4
+ import { computed, onMounted, ref, useTemplateRef } from 'vue'
5
+ import { searchString } from '../../shared/helpers'
6
+ import Button from '../Button/Button.vue'
7
+ import Dropdown from '../Dropdown/Dropdown.vue'
8
+ import DropdownItem from '../Dropdown/DropdownItem.vue'
9
+ import DropdownTitle from '../Dropdown/DropdownTitle.vue'
10
+ import Input from '../Input/Input.vue'
11
+ import '../Input/input.scss'
12
+ import './select.scss'
13
+
14
+ export type SelectOption = {
15
+ value: any
16
+ label: string
17
+ }
18
+
19
+ type Props = {
20
+ single?: boolean
21
+ readonly?: boolean
22
+ options: SelectOption[]
23
+ label?: string
24
+ placeholder?: string
25
+ required?: boolean
26
+ expand?: boolean
27
+ hint?: string
28
+ search?: boolean
29
+ maxActiveOptions?: number
30
+ showClear?: boolean
31
+ }
32
+
33
+ const {
34
+ expand,
35
+ readonly,
36
+ required,
37
+ hint,
38
+ placeholder,
39
+ options,
40
+ single = true,
41
+ search,
42
+ maxActiveOptions = 3,
43
+ showClear,
44
+ } = defineProps<Props>()
45
+
46
+ const selected = defineModel<SelectOption[] | undefined>()
47
+
48
+ //
49
+ function setValue(option: SelectOption) {
50
+ if (single) {
51
+ // Single
52
+ if (selected.value?.some(o => o.value === option.value) && !required) {
53
+ selected.value = undefined
54
+ }
55
+ else {
56
+ selected.value = [option]
57
+ }
58
+ }
59
+ else {
60
+ if (selected.value && selected.value?.some(o => o.value === option.value)) {
61
+ const values = selected.value.filter(o => o.value !== option.value)
62
+ // Cant remove the last value if it's required
63
+ if ((required && values.length > 0) || !required) {
64
+ selected.value = values.length > 0 ? values : undefined
65
+ }
66
+ }
67
+ else {
68
+ if (!selected.value) {
69
+ selected.value = [option]
70
+ }
71
+ else {
72
+ selected.value?.push(option)
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ // Search
79
+ const searchStr = ref()
80
+ const filteredOptions = computed(() => {
81
+ if (!searchStr.value || searchStr.value.length === 0)
82
+ return options
83
+
84
+ return options.filter((option) => {
85
+ return searchString(option.label, searchStr.value)
86
+ })
87
+ })
88
+
89
+ // Render text inside the button
90
+ const renderPlaceholder = computed(() => {
91
+ // Render placeholder or a default select option
92
+ if (!selected.value || selected.value.length === 0)
93
+ return placeholder ?? 'Select an option'
94
+
95
+ // Selected values
96
+ if (single)
97
+ return selected.value[0].label
98
+
99
+ // If amount of selected exceeds the active capacity
100
+ if (selected.value.length > maxActiveOptions) {
101
+ return `${selected.value.length} selected`
102
+ }
103
+
104
+ // Just list ALL selected optionsx
105
+ return selected.value.map(o => o.label).join(', ')
106
+ })
107
+
108
+ onMounted(() => {
109
+ if (readonly && showClear) {
110
+ console.warn('[Select] readonly and showClear are mutually exclusive props. The clear button will not show up if required is present.')
111
+ }
112
+ })
113
+
114
+ const dropdownRef = useTemplateRef('dropdownRef')
115
+
116
+ function clearValue() {
117
+ selected.value = undefined
118
+ dropdownRef.value?.close()
119
+ }
120
+ </script>
121
+
122
+ <template>
123
+ <div class="vui-input-container vui-select" :class="{ expand, required, readonly }">
124
+ <Dropdown ref="dropdownRef" :expand>
125
+ <template #trigger="{ toggle, isOpen }">
126
+ <div class="vui-input vui-select-trigger-content">
127
+ <label v-if="label" for="id">{{ label }}</label>
128
+ <p v-if="hint" class="vui-input-hint">
129
+ {{ hint }}
130
+ </p>
131
+
132
+ <button class="vui-input-style vui-select-trigger-container" @click="toggle">
133
+ <span>
134
+ {{ renderPlaceholder }}
135
+ </span>
136
+ <Button
137
+ v-if="showClear && !required && selected"
138
+ plain
139
+ icon="ph:x"
140
+ square
141
+ size="s"
142
+ @click.stop="clearValue"
143
+ />
144
+ <Icon :icon="isOpen ? 'ph:caret-up' : 'ph:caret-down'" />
145
+ </button>
146
+ </div>
147
+ </template>
148
+
149
+ <template #default="{ close, isOpen }">
150
+ <DropdownTitle v-if="search">
151
+ <Input
152
+ v-model="searchStr"
153
+ placeholder="Search..."
154
+ :focus="isOpen"
155
+ />
156
+ </DropdownTitle>
157
+
158
+ <p v-if="filteredOptions.length === 0" class="vue-select-no-results">
159
+ No results...
160
+ </p>
161
+
162
+ <DropdownItem
163
+ v-for="option in filteredOptions"
164
+ :key="option.value"
165
+ :class="{ selected: selected?.find(v => v.value === option.value) }"
166
+ :icon="selected?.find(v => v.value === option.value) ? 'ph:check-bold' : ''"
167
+ @click="() => {
168
+ setValue(option)
169
+ // In single mode, close modal after clicking
170
+ if (single && !(required && selected && selected[0].value === option.value)) {
171
+ close()
172
+ }
173
+ }"
174
+ >
175
+ {{ option.label }}
176
+ </DropdownItem>
177
+ </template>
178
+ </Dropdown>
179
+ </div>
180
+ </template>
@@ -0,0 +1,43 @@
1
+ .vui-input-container {
2
+ &.vui-select {
3
+ &.expand {
4
+ .vui-dropdown-trigger-wrap,
5
+ .vui-dropdown-trigger-content {
6
+ width: 100%;
7
+ }
8
+ }
9
+
10
+ .vui-select-trigger-container {
11
+ span {
12
+ white-space: nowrap;
13
+ text-overflow: ellipsis;
14
+ overflow-x: hidden;
15
+ }
16
+ }
17
+
18
+ .vue-select-no-results {
19
+ text-align: center;
20
+ padding-block: var(--space-xs);
21
+ }
22
+
23
+ .vui-input-style {
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: space-between;
27
+ gap: var(--space-s);
28
+
29
+ & > button {
30
+ margin-right: -6px;
31
+
32
+ .vui-button-slot svg {
33
+ width: 14px;
34
+ height: 14px;
35
+ }
36
+ }
37
+
38
+ &:hover {
39
+ border-color: var(--color-border-strong);
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,91 @@
1
+ <script setup lang='ts'>
2
+ import { computed } from 'vue'
3
+ import Backdrop from '../../internal/Backdrop/Backdrop.vue'
4
+ import Button from '../Button/Button.vue'
5
+ import Divider from '../Divider/Divider.vue'
6
+ import './sheet.scss'
7
+
8
+ interface Props {
9
+ position?: 'left' | 'right' | 'top' | 'bottom'
10
+ size?: number | string
11
+ separator?: boolean
12
+ }
13
+
14
+ const {
15
+ position = 'right',
16
+ size = 398,
17
+ separator,
18
+ } = defineProps<Props>()
19
+
20
+ const open = defineModel<boolean>()
21
+
22
+ function close() {
23
+ open.value = false
24
+ }
25
+
26
+ const style = computed(() => {
27
+ const formattedSizeValue = typeof size === 'number' ? `${size}px` : size
28
+ let style
29
+
30
+ if (position === 'left' || position === 'right') {
31
+ style = { width: formattedSizeValue }
32
+ }
33
+ else {
34
+ style = { minHeight: formattedSizeValue }
35
+ }
36
+
37
+ return style
38
+ })
39
+
40
+ // Used to compute base location so that sheet appears to animate form the edge of the screen
41
+ const baseTransform = computed(() => {
42
+ switch (position) {
43
+ case 'left': return `translate(-16px, 0)`
44
+ case 'top': return `translate(0, -16px)`
45
+ case 'bottom': return `translate(0, 16px)`
46
+ case 'right':
47
+ default: return `translate(16px, 0)`
48
+ }
49
+ })
50
+ </script>
51
+
52
+ <template>
53
+ <Teleport to="body">
54
+ <Transition appear name="sheet">
55
+ <Backdrop v-if="open" :style="{ padding: 0 }" @close="open = false">
56
+ <div v-if="open" class="vui-sheet" :class="[`vui-sheet-position-${position}`]" :style>
57
+ <div class="vui-sheet-header">
58
+ <div :style="{ flex: 1 }">
59
+ <slot name="header" :close />
60
+ </div>
61
+ <Button square icon="ph:x" @click="open = false" />
62
+ </div>
63
+
64
+ <Divider v-if="separator && $slots.header" :size="1" />
65
+
66
+ <div v-if="$slots.default" class="vui-sheet-content">
67
+ <slot :close />
68
+ </div>
69
+ </div>
70
+ </Backdrop>
71
+ </Transition>
72
+ </Teleport>
73
+ </template>
74
+
75
+ <style scoped lang="scss">
76
+ .sheet-enter-active,
77
+ .sheet-leave-active {
78
+ transition: var(--transition);
79
+ }
80
+
81
+ .sheet-enter-to,
82
+ .sheet-leave-from {
83
+ transform: translate(0, 0);
84
+ }
85
+
86
+ .sheet-enter-from,
87
+ .sheet-leave-to {
88
+ opacity: 0;
89
+ transform: v-bind(baseTransform);
90
+ }
91
+ </style>
@@ -0,0 +1,56 @@
1
+ .vui-sheet {
2
+ display: flex;
3
+ flex-direction: column;
4
+ position: absolute;
5
+ background-color: var(--color-bg);
6
+ border-color: var(--color-border);
7
+ height: 100dvh;
8
+
9
+ &.vui-sheet-position-top {
10
+ top: 0;
11
+ border-bottom: 1px solid var(--color-border);
12
+ }
13
+
14
+ &.vui-sheet-position-bottom {
15
+ bottom: 0;
16
+ border-top: 1px solid var(--color-border);
17
+ }
18
+
19
+ &.vui-sheet-position-right {
20
+ right: 0;
21
+ border-left: 1px solid var(--color-border);
22
+ }
23
+
24
+ &.vui-sheet-position-left {
25
+ left: 0;
26
+ border-right: 1px solid var(--color-border);
27
+ }
28
+
29
+ &.vui-sheet-position-top,
30
+ &.vui-sheet-position-bottom {
31
+ max-height: 35dvh;
32
+ width: 100%;
33
+ }
34
+
35
+ .vui-sheet-header,
36
+ .vui-sheet-content {
37
+ width: 100%;
38
+ padding: var(--space-m);
39
+ }
40
+
41
+ .vui-sheet-content {
42
+ flex: 1 1 0%;
43
+ overflow-y: auto;
44
+ height: 100%;
45
+ }
46
+
47
+ .vui-sheet-header {
48
+ display: flex;
49
+ gap: var(--space-m);
50
+ align-self: flex-start;
51
+
52
+ & + .vui-sheet-content {
53
+ padding-top: var(--space-s);
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,46 @@
1
+ <!-- eslint-disable ts/no-use-before-define -->
2
+ <script setup lang='ts'>
3
+ import './skeleton.scss'
4
+
5
+ interface Props {
6
+ radius?: string | number
7
+ width?: string | number
8
+ height?: string | number
9
+ circle?: boolean
10
+ }
11
+
12
+ const {
13
+ // @ts-expect-error I can't get ESLint to STOP moving DEFAULT_RADIUS below
14
+ // this props declaration wtf
15
+ radius = DEFAULT_RADIUS,
16
+ width = '100%',
17
+ height = '32px',
18
+ circle,
19
+ } = defineProps<Props>()
20
+
21
+ const DEFAULT_RADIUS = 'var(--border-radius-s)'
22
+
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
+ }
32
+ </script>
33
+
34
+ <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
+ />
46
+ </template>
@@ -0,0 +1,14 @@
1
+ .vui-skeleton {
2
+ animation: pulse 2.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
3
+ background-color: var(--color-border);
4
+
5
+ @keyframes pulse {
6
+ 0%,
7
+ 100% {
8
+ opacity: 1;
9
+ }
10
+ 50% {
11
+ opacity: 0.5;
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,44 @@
1
+ <script setup lang="ts">
2
+ import type { Sizes } from '../../shared/types'
3
+ import { computed } from 'vue'
4
+ import { Size } from '../../shared/types'
5
+ import './spinner.scss'
6
+
7
+ interface Props {
8
+ size?: Sizes
9
+ }
10
+
11
+ const {
12
+ size = 's',
13
+ } = defineProps<Props>()
14
+
15
+ const actualSize = computed(() => {
16
+ switch (size) {
17
+ case Size.s: return '16px'
18
+ case Size.l: return '38px'
19
+ case Size.m:
20
+ default: return '26px'
21
+ }
22
+ })
23
+
24
+ const actualBorderWidth = computed(() => {
25
+ switch (size) {
26
+ case Size.s: return '3px'
27
+ case Size.l: return '5px'
28
+ case Size.m:
29
+ default: return '4px'
30
+ }
31
+ })
32
+ </script>
33
+
34
+ <template>
35
+ <div :class="{ size }" class="vui-spinner" />
36
+ </template>
37
+
38
+ <style lang="scss">
39
+ .vui-spinner {
40
+ width: v-bind(actualSize);
41
+ height: v-bind(actualSize);
42
+ border-width: v-bind(actualBorderWidth);
43
+ }
44
+ </style>
@@ -0,0 +1,46 @@
1
+ .vui-spinner {
2
+ --spinner-color: var(--color-text);
3
+ aspect-ratio: 1;
4
+ border-radius: 50%;
5
+ border: 3px solid var(--spinner-color);
6
+ animation:
7
+ l20-1 0.8s infinite linear alternate,
8
+ l20-2 1.6s infinite linear;
9
+ }
10
+ @keyframes l20-1 {
11
+ 0% {
12
+ clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%);
13
+ }
14
+ 12.5% {
15
+ clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%);
16
+ }
17
+ 25% {
18
+ clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%);
19
+ }
20
+ 50% {
21
+ clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
22
+ }
23
+ 62.5% {
24
+ clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%);
25
+ }
26
+ 75% {
27
+ clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%);
28
+ }
29
+ 100% {
30
+ clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%);
31
+ }
32
+ }
33
+ @keyframes l20-2 {
34
+ 0% {
35
+ transform: scaleY(1) rotate(0deg);
36
+ }
37
+ 49.99% {
38
+ transform: scaleY(1) rotate(135deg);
39
+ }
40
+ 50% {
41
+ transform: scaleY(-1) rotate(0deg);
42
+ }
43
+ 100% {
44
+ transform: scaleY(-1) rotate(-135deg);
45
+ }
46
+ }
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ import { useId } from 'vue'
3
+ import './switch.scss'
4
+
5
+ interface Props {
6
+ label?: string
7
+ disabled?: boolean
8
+ }
9
+
10
+ const { label, disabled } = defineProps<Props>()
11
+ const slots = defineSlots()
12
+ const checked = defineModel<boolean>()
13
+ const id = useId()
14
+ </script>
15
+
16
+ <template>
17
+ <div class="vui-switch" :class="{ disabled, checked }">
18
+ <input :id v-model="checked" type="checkbox">
19
+ <label :for="id">
20
+ <div class="vui-switch-icon">
21
+ <span class="vui-switch-indicator" />
22
+ </div>
23
+
24
+ <p v-if="!slots.default" class="vui-switch-content">{{ label }}</p>
25
+ <div v-else class="vui-switch-content">
26
+ <slot />
27
+ </div>
28
+ </label>
29
+ </div>
30
+ </template>
@@ -0,0 +1,52 @@
1
+ .vui-switch {
2
+ --switch-size: 24px;
3
+
4
+ &.checked .vui-switch-icon .vui-switch-indicator {
5
+ left: calc(100% - 24px);
6
+ background-color: var(--color-text);
7
+ }
8
+
9
+ .vui-switch-icon {
10
+ width: 44px;
11
+ height: var(--switch-size);
12
+ border-radius: 22px;
13
+ background-color: var(--color-bg-raised);
14
+ position: relative;
15
+ cursor: pointer;
16
+
17
+ .vui-switch-indicator {
18
+ display: block;
19
+ position: absolute;
20
+ left: 2px;
21
+ top: 2px;
22
+ width: 20px;
23
+ height: 20px;
24
+ border-radius: 100%;
25
+ background-color: var(--color-bg);
26
+ transition: var(--transition);
27
+ }
28
+ }
29
+
30
+ input {
31
+ display: none;
32
+
33
+ & + label {
34
+ display: grid;
35
+ grid-template-columns: 44px 1fr;
36
+ gap: 10px;
37
+ font-size: var(--font-size-m);
38
+ color: var(--color-text);
39
+ user-select: none;
40
+
41
+ .vui-switch-content {
42
+ display: block;
43
+
44
+ &:is(p) {
45
+ height: var(--switch-size);
46
+ line-height: var(--switch-size);
47
+ font-size: var(--font-size-ms);
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import { computed, useSlots, useTemplateRef } from 'vue'
3
+
4
+ const slots = useSlots()
5
+ const context = useTemplateRef<HTMLTableCellElement>('context')
6
+ const computedStyle = computed(() => {
7
+ if (!slots.context)
8
+ return {}
9
+ const width = context.value?.getBoundingClientRect().width ?? 0
10
+ return {
11
+ paddingRight: `${width}px`,
12
+ }
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <td :style="computedStyle">
18
+ <slot />
19
+ <div v-if="$slots.context" ref="context" class="vui-cell-context">
20
+ <slot name="context" />
21
+ </div>
22
+ </td>
23
+ </template>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import type { Header } from './table'
3
+ import { computed } from 'vue'
4
+ import Button from '../Button/Button.vue'
5
+
6
+ interface Props {
7
+ /**
8
+ * Main header data. This can be empty in case we are just adding an empty
9
+ * padder. It is recommended to just use <th> instead, but this shouldn't fail
10
+ * anyway
11
+ */
12
+ header?: Header
13
+ /**
14
+ * Enable sorting on this column
15
+ */
16
+ sort?: boolean
17
+ }
18
+
19
+ const props = defineProps<Props>()
20
+
21
+ const sortStateBind = computed(() => {
22
+ if (!props.header)
23
+ return
24
+ switch (props.header.sortKey) {
25
+ case 'asc': return {
26
+ 'icon': 'ph:sort-ascending',
27
+ 'data-title-top': 'Ascending',
28
+ }
29
+ case 'desc': return {
30
+ 'icon': 'ph:sort-descending',
31
+ 'data-title-top': 'Descending',
32
+ }
33
+ default: return {
34
+ 'icon': 'ph:arrows-down-up',
35
+ 'data-title-top': 'Sort column',
36
+ }
37
+ }
38
+ })
39
+ </script>
40
+
41
+ <template>
42
+ <th>
43
+ <div v-if="props.header" class="vui-table-th-content">
44
+ <slot>
45
+ {{ props.header.label }}
46
+ </slot>
47
+ <Button
48
+ v-if="props.sort"
49
+ class="vui-table-sort-button"
50
+ v-bind="sortStateBind"
51
+ :class="{ active: !!props.header.sortKey }"
52
+ size="s"
53
+ plain
54
+ square
55
+ @click="props.header.sortToggle"
56
+ />
57
+ </div>
58
+ </th>
59
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ //
3
+ </script>
4
+
5
+ <template>
6
+ <tr class="vui-table-row">
7
+ <slot />
8
+ </tr>
9
+ </template>