@dolanske/vui 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) 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/dist/vui.js +199 -214
  61. package/package.json +11 -9
  62. package/src/App.vue +162 -0
  63. package/src/components/Accordion/Accordion.vue +75 -0
  64. package/src/components/Accordion/AccordionGroup.vue +43 -0
  65. package/src/components/Accordion/accordion.scss +44 -0
  66. package/src/components/Alert/Alert.vue +53 -0
  67. package/src/components/Alert/alert.scss +80 -0
  68. package/src/components/Avatar/Avatar.vue +36 -0
  69. package/src/components/Avatar/avatar.scss +46 -0
  70. package/src/components/Badge/Badge.vue +21 -0
  71. package/src/components/Badge/badge.scss +89 -0
  72. package/src/components/Breadcrumbs/BreadcrumbItem.vue +26 -0
  73. package/src/components/Breadcrumbs/Breadcrumbs.vue +33 -0
  74. package/src/components/Breadcrumbs/breadcrumbs.scss +30 -0
  75. package/src/components/Button/Button.vue +90 -0
  76. package/src/components/Button/button.scss +176 -0
  77. package/src/components/ButtonGroup/ButtonGroup.vue +25 -0
  78. package/src/components/ButtonGroup/button-group.scss +51 -0
  79. package/src/components/Calendar/Calendar.vue +58 -0
  80. package/src/components/Calendar/calendar.scss +56 -0
  81. package/src/components/Card/Card.vue +48 -0
  82. package/src/components/Card/card.scss +53 -0
  83. package/src/components/Checkbox/Checkbox.vue +49 -0
  84. package/src/components/Checkbox/checkbox.scss +60 -0
  85. package/src/components/CopyClipboard/CopyClipboard.vue +82 -0
  86. package/src/components/CopyClipboard/copy-clipboard.scss +17 -0
  87. package/src/components/Divider/Divider.vue +34 -0
  88. package/src/components/Divider/divider.scss +35 -0
  89. package/src/components/Drawer/Drawer.vue +93 -0
  90. package/src/components/Drawer/drawer.scss +49 -0
  91. package/src/components/Dropdown/Dropdown.vue +100 -0
  92. package/src/components/Dropdown/DropdownItem.vue +29 -0
  93. package/src/components/Dropdown/DropdownTitle.vue +8 -0
  94. package/src/components/Dropdown/dropdown.scss +112 -0
  95. package/src/components/Flex/Flex.vue +109 -0
  96. package/src/components/Grid/Grid.vue +59 -0
  97. package/src/components/Input/Counter.vue +70 -0
  98. package/src/components/Input/Dropzone.vue +63 -0
  99. package/src/components/Input/File.vue +15 -0
  100. package/src/components/Input/Input.vue +118 -0
  101. package/src/components/Input/Password.vue +47 -0
  102. package/src/components/Input/Textarea.vue +73 -0
  103. package/src/components/Input/input.scss +199 -0
  104. package/src/components/Kbd/Kbd.vue +48 -0
  105. package/src/components/Kbd/KbdGroup.vue +31 -0
  106. package/src/components/Kbd/kbd.scss +18 -0
  107. package/src/components/Modal/Confirm.vue +56 -0
  108. package/src/components/Modal/Modal.vue +91 -0
  109. package/src/components/Modal/modal.scss +49 -0
  110. package/src/components/Pagination/Pagination.vue +74 -0
  111. package/src/components/Pagination/pagination.ts +78 -0
  112. package/src/components/Popout/Popout.vue +39 -0
  113. package/src/components/Popout/popout.scss +7 -0
  114. package/src/components/Progress/Progress.vue +84 -0
  115. package/src/components/Progress/progress.scss +41 -0
  116. package/src/components/Radio/Radio.vue +36 -0
  117. package/src/components/Radio/RadioGroup.vue +35 -0
  118. package/src/components/Radio/radio.scss +59 -0
  119. package/src/components/Select/Select.vue +180 -0
  120. package/src/components/Select/select.scss +43 -0
  121. package/src/components/Sheet/Sheet.vue +91 -0
  122. package/src/components/Sheet/sheet.scss +56 -0
  123. package/src/components/Skeleton/Skeleton.vue +46 -0
  124. package/src/components/Skeleton/skeleton.scss +14 -0
  125. package/src/components/Spinner/Spinner.vue +44 -0
  126. package/src/components/Spinner/spinner.scss +46 -0
  127. package/src/components/Switch/Switch.vue +30 -0
  128. package/src/components/Switch/switch.scss +52 -0
  129. package/src/components/Table/Cell.vue +23 -0
  130. package/src/components/Table/Header.vue +59 -0
  131. package/src/components/Table/Row.vue +9 -0
  132. package/src/components/Table/SelectAll.vue +23 -0
  133. package/src/components/Table/SelectRow.vue +29 -0
  134. package/src/components/Table/Table.vue +66 -0
  135. package/src/components/Table/table.scss +134 -0
  136. package/src/components/Table/table.ts +243 -0
  137. package/src/components/Tabs/Tab.vue +21 -0
  138. package/src/components/Tabs/Tabs.vue +76 -0
  139. package/src/components/Tabs/tabs.scss +78 -0
  140. package/src/components/Toast/Toasts.vue +47 -0
  141. package/src/components/Toast/toast.scss +41 -0
  142. package/src/components/Toast/toast.ts +92 -0
  143. package/src/components/Tooltip/Tooltip.vue +80 -0
  144. package/src/components/Tooltip/tooltip.scss +4 -0
  145. package/src/index.scss +1 -0
  146. package/src/index.ts +111 -0
  147. package/src/internal/Backdrop/Backdrop.vue +22 -0
  148. package/src/internal/Backdrop/backdrop.scss +28 -0
  149. package/src/main.ts +5 -0
  150. package/src/shared/composables.ts +18 -0
  151. package/src/shared/helpers.ts +53 -0
  152. package/src/shared/types.ts +11 -0
  153. package/src/style/animation.scss +21 -0
  154. package/src/style/core.scss +128 -0
  155. package/src/style/fonts.scss +0 -0
  156. package/src/style/layout.scss +9 -0
  157. package/src/style/media-query.scss +29 -0
  158. package/src/style/reset.scss +135 -0
  159. package/src/style/tooltip.scss +128 -0
  160. package/src/style/typography.scss +339 -0
  161. package/src/style/utils.scss +22 -0
  162. package/src/vite-env.d.ts +1 -0
@@ -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>
@@ -0,0 +1,91 @@
1
+ <script setup lang='ts'>
2
+ import type { Sizes } from '../../shared/types'
3
+ import type { Props as CardProps } from '../Card/Card.vue'
4
+ import { useAttrs } from 'vue'
5
+ import Backdrop from '../../internal/Backdrop/Backdrop.vue'
6
+ import Button from '../Button/Button.vue'
7
+ import Card from '../Card/Card.vue'
8
+ import './modal.scss'
9
+
10
+ export interface ModalProps {
11
+ size?: Sizes | 'full'
12
+ /**
13
+ * Modal wraps a floating card. You can optinally pass in any props you'd pass
14
+ * into the <Card /> component.
15
+ */
16
+ card?: CardProps
17
+ /**
18
+ * Modal will not overflow the screen, but its card's content will be scrollable instead.
19
+ */
20
+ scrollable?: boolean
21
+ /**
22
+ * Modal appears in the center of the screen
23
+ */
24
+ centered?: boolean
25
+ /**
26
+ * Wether modal can be closed by clicking the X button
27
+ */
28
+ canDismiss?: boolean
29
+ }
30
+
31
+ const {
32
+ size = 'm',
33
+ card = {},
34
+ scrollable,
35
+ centered,
36
+ canDismiss = true,
37
+ } = defineProps<ModalProps>()
38
+
39
+ const open = defineModel<boolean>()
40
+
41
+ function close() {
42
+ open.value = false
43
+ }
44
+
45
+ const attrs = useAttrs()
46
+ </script>
47
+
48
+ <template>
49
+ <Teleport to="body">
50
+ <Transition appear name="modal">
51
+ <Backdrop v-if="open" @close="close">
52
+ <div class="vui-modal" :class="[`vui-modal-size-${size}`, { scrollable, centered }]" v-bind="attrs">
53
+ <Button
54
+ v-if="canDismiss"
55
+ class="vui-modal-close"
56
+ plain
57
+ square
58
+ icon="ph:x"
59
+ @click="open = false"
60
+ />
61
+ <Card v-bind="card">
62
+ <template v-if="$slots.header" #header>
63
+ <slot name="header" :close />
64
+ </template>
65
+ <template v-if="$slots.default" #default>
66
+ <div>
67
+ <slot name="default" :close />
68
+ </div>
69
+ </template>
70
+ <template v-if="$slots.footer" #footer>
71
+ <slot name="footer" :close />
72
+ </template>
73
+ </Card>
74
+ </div>
75
+ </Backdrop>
76
+ </Transition>
77
+ </Teleport>
78
+ </template>
79
+
80
+ <style scoped>
81
+ .modal-enter-active,
82
+ .modal-leave-active {
83
+ transition: var(--transition);
84
+ }
85
+
86
+ .modal-enter-from,
87
+ .modal-leave-to {
88
+ opacity: 0;
89
+ transform: scale(0.95);
90
+ }
91
+ </style>
@@ -0,0 +1,49 @@
1
+ .vui-modal {
2
+ width: 100%;
3
+ margin-inline: auto;
4
+ height: 100%;
5
+ position: relative;
6
+
7
+ .vui-modal-close {
8
+ position: absolute;
9
+ top: 16px;
10
+ right: 16px;
11
+ }
12
+
13
+ &.centered {
14
+ display: flex;
15
+ align-items: center;
16
+ }
17
+
18
+ &.vui-modal-size-s {
19
+ max-width: 440px;
20
+ }
21
+
22
+ &.vui-modal-size-m {
23
+ max-width: 620px;
24
+ }
25
+
26
+ &.vui-modal-size-l {
27
+ max-width: 728px;
28
+ }
29
+
30
+ &.scrollable {
31
+ & > .vui-card {
32
+ display: flex;
33
+ flex-direction: column;
34
+ inline-size: 100%;
35
+ max-height: 100%;
36
+
37
+ .vui-card-content {
38
+ flex: 1 1 0%;
39
+ overflow-y: auto;
40
+ }
41
+ }
42
+ }
43
+
44
+ & > .vui-card {
45
+ width: 100%;
46
+ margin-bottom: var(--backdrop-offset);
47
+ box-shadow: var(--box-shadow-strong);
48
+ }
49
+ }
@@ -0,0 +1,74 @@
1
+ <script setup lang='ts'>
2
+ import type { Pagination } from './pagination'
3
+ import { computed } from 'vue'
4
+ import Button from '../Button/Button.vue'
5
+ import Flex from '../Flex/Flex.vue'
6
+
7
+ interface Props {
8
+ numbers?: boolean
9
+ pagination: Pagination
10
+ prevNext?: boolean
11
+ firstLast?: boolean
12
+ }
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ numbers: true,
16
+ prevNext: true,
17
+ firstLast: true,
18
+ })
19
+
20
+ const emit = defineEmits<{
21
+ change: [page: number]
22
+ }>()
23
+
24
+ const canNextPage = computed(() => props.pagination.currentPage < props.pagination.endPage)
25
+ const canPrevPage = computed(() => props.pagination.currentPage > props.pagination.startPage)
26
+
27
+ function setNext() {
28
+ emit('change', props.pagination.currentPage + 1)
29
+ }
30
+
31
+ function setPrev() {
32
+ emit('change', props.pagination.currentPage - 1)
33
+ }
34
+ </script>
35
+
36
+ <template>
37
+ <Flex inline class="vui-pagination" gap="s">
38
+ <slot name="start">
39
+ <Button v-if="props.firstLast" data-title-top="First page" plain :disabled="props.pagination.startPage === props.pagination.currentPage" square icon="ph:caret-double-left" @click="emit('change', props.pagination.startPage)" />
40
+ </slot>
41
+
42
+ <slot name="prev" :disabled="canPrevPage" :set-page="setPrev">
43
+ <Button v-if="props.prevNext" data-title-top="Previous page" plain :disabled="!canPrevPage" square icon="ph:caret-left" @click="setPrev" />
44
+ </slot>
45
+
46
+ <template v-if="props.numbers">
47
+ <Flex gap="s">
48
+ <Button
49
+ v-for="page in props.pagination.pages"
50
+ :key="page"
51
+ square
52
+ :plain="props.pagination.currentPage !== page"
53
+ :variant="props.pagination.currentPage === page ? 'blue' : 'default'"
54
+ @click="emit('change', page)"
55
+ >
56
+ {{ page }}
57
+ </Button>
58
+ </Flex>
59
+ </template>
60
+ <slot name="next" :disabled="canNextPage" :set-page="setNext">
61
+ <Button v-if="props.prevNext" data-title-top="Next page" plain :disabled="!canNextPage" square icon="ph:caret-right" @click="setNext" />
62
+ </slot>
63
+
64
+ <slot name="end">
65
+ <Button v-if="props.firstLast" data-title-top="Last page" plain :disabled="props.pagination.endPage === props.pagination.currentPage" square icon="ph:caret-double-right" @click="emit('change', props.pagination.endPage)" />
66
+ </slot>
67
+ </Flex>
68
+ </template>
69
+
70
+ <style scoped>
71
+ [data-title-top]:before {
72
+ white-space: nowrap;
73
+ }
74
+ </style>
@@ -0,0 +1,78 @@
1
+ import { createArray } from '../../shared/helpers'
2
+
3
+ export interface Pagination {
4
+ totalItems: number
5
+ currentPage: number
6
+ perPage: number
7
+ totalPages: number
8
+ startPage: number
9
+ endPage: number
10
+ startIndex: number
11
+ endIndex: number
12
+ pages: number[]
13
+ }
14
+
15
+ export function paginate(
16
+ totalItems: number,
17
+ currentPage: number = 1,
18
+ perPage: number = 15,
19
+ maxPages: number = 5,
20
+ ): Pagination {
21
+ // calculate total pages
22
+ const totalPages = Math.ceil(totalItems / perPage)
23
+
24
+ // ensure current page isn't out of range
25
+ if (currentPage < 1) {
26
+ currentPage = 1
27
+ }
28
+ else if (currentPage > totalPages) {
29
+ currentPage = totalPages
30
+ }
31
+
32
+ let startPage: number, endPage: number
33
+ if (totalPages <= maxPages) {
34
+ // total pages less than max so show all pages
35
+ startPage = 1
36
+ endPage = totalPages
37
+ }
38
+ else {
39
+ // total pages more than max so calculate start and end pages
40
+ const maxPagesBeforeCurrentPage = Math.floor(maxPages / 2)
41
+ const maxPagesAfterCurrentPage = Math.ceil(maxPages / 2) - 1
42
+ if (currentPage <= maxPagesBeforeCurrentPage) {
43
+ // current page near the start
44
+ startPage = 1
45
+ endPage = maxPages
46
+ }
47
+ else if (currentPage + maxPagesAfterCurrentPage >= totalPages) {
48
+ // current page near the end
49
+ startPage = totalPages - maxPages + 1
50
+ endPage = totalPages
51
+ }
52
+ else {
53
+ // current page somewhere in the middle
54
+ startPage = currentPage - maxPagesBeforeCurrentPage
55
+ endPage = currentPage + maxPagesAfterCurrentPage
56
+ }
57
+ }
58
+
59
+ // calculate start and end item indexes
60
+ const startIndex = (currentPage - 1) * perPage
61
+ const endIndex = Math.min(startIndex + perPage - 1, totalItems - 1)
62
+
63
+ // create an array of pages to ng-repeat in the pager control
64
+ const pages = createArray((endPage + 1) - startPage).map(i => startPage + i)
65
+
66
+ // return object with all pager properties required by the view
67
+ return {
68
+ totalItems,
69
+ currentPage,
70
+ perPage,
71
+ totalPages,
72
+ startPage,
73
+ endPage,
74
+ startIndex,
75
+ endIndex,
76
+ pages,
77
+ }
78
+ }
@@ -0,0 +1,39 @@
1
+ <script setup lang='ts'>
2
+ import type { MaybeElement, Placement } from '@floating-ui/vue'
3
+ import { autoPlacement, offset, useFloating } from '@floating-ui/vue'
4
+ import { toRef, useTemplateRef } from 'vue'
5
+ import './popout.scss'
6
+
7
+ interface Props {
8
+ anchor: MaybeElement<HTMLElement>
9
+ /**
10
+ * Override the autoPlacement option
11
+ */
12
+ placement?: Placement
13
+ /**
14
+ * Distance between the anchor and the rendered tooltip
15
+ */
16
+ offset?: number
17
+ }
18
+
19
+ const props = withDefaults(defineProps<Props>(), {
20
+ offset: 8,
21
+ })
22
+
23
+ const popoutRef = useTemplateRef('popout')
24
+ const anchorRef = toRef(props.anchor)
25
+
26
+ const { floatingStyles } = useFloating(anchorRef, popoutRef, {
27
+ placement: props.placement,
28
+ middleware: [
29
+ ...(props.placement ? [] : [autoPlacement()]),
30
+ offset(props.offset),
31
+ ],
32
+ })
33
+ </script>
34
+
35
+ <template>
36
+ <div ref="popout" :style="floatingStyles" class="vui-popout">
37
+ <slot />
38
+ </div>
39
+ </template>
@@ -0,0 +1,7 @@
1
+ .vui-popout {
2
+ border-radius: var(--border-radius);
3
+ box-shadow: var(--box-shadow);
4
+ min-width: 80px;
5
+ background-color: var(--color-bg-raised);
6
+ border-radius: var(--border-radius-m);
7
+ }