@dolanske/vui 1.2.1 → 1.3.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dolanske/vui",
3
3
  "type": "module",
4
- "version": "1.2.1",
4
+ "version": "1.3.0",
5
5
  "private": false,
6
6
  "description": "Brother in Christ there's a new UI library",
7
7
  "author": "dolanske",
@@ -1,9 +1,10 @@
1
1
  <script setup lang='ts'>
2
+ import type { Placement } from '@floating-ui/vue'
2
3
  import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue'
3
4
  import { Icon } from '@iconify/vue'
4
5
  import { useClipboard } from '@vueuse/core'
5
6
  import { computed, onMounted, useSlots, useTemplateRef } from 'vue'
6
- import { isNil } from '../../shared/helpers'
7
+ import { getPlacementAnimationName, isNil } from '../../shared/helpers'
7
8
  import Flex from '../Flex/Flex.vue'
8
9
  import './copy-clipboard.scss'
9
10
 
@@ -20,12 +21,17 @@ interface Props {
20
21
  * How long will the copy confirmation tooltip be visible in milliseconds.
21
22
  */
22
23
  confirmTime?: number
24
+ /**
25
+ * Tooltip position. Default is top
26
+ */
27
+ confirmPlacement?: Placement
23
28
  }
24
29
 
25
30
  const {
26
31
  text,
27
32
  confirm,
28
33
  confirmTime,
34
+ confirmPlacement = 'top',
29
35
  } = defineProps<Props>()
30
36
 
31
37
  const {
@@ -61,7 +67,8 @@ const tooltipRef = useTemplateRef('tooltip')
61
67
  const { floatingStyles } = useFloating(anchorRef, tooltipRef, {
62
68
  whileElementsMounted: autoUpdate,
63
69
  transform: false,
64
- placement: 'top',
70
+ strategy: 'fixed',
71
+ placement: confirmPlacement,
65
72
  middleware: [
66
73
  offset(8),
67
74
  shift(),
@@ -75,7 +82,7 @@ const { floatingStyles } = useFloating(anchorRef, tooltipRef, {
75
82
  <slot :copy :copied />
76
83
  </div>
77
84
 
78
- <Transition name="fade-up" mode="in-out">
85
+ <Transition :name="getPlacementAnimationName(confirmPlacement)" mode="in-out">
79
86
  <div v-if="copied && (!!parsedConfirm || $slots.confirm)" ref="tooltip" class="vui-clipboard-tooltip" :style="floatingStyles">
80
87
  <slot name="confirm">
81
88
  <template v-if="typeof parsedConfirm === 'string'">
@@ -104,10 +104,9 @@ onMounted(() => {
104
104
  <slot name="trigger" :open :is-open="showMenu" :close :toggle />
105
105
  </div>
106
106
 
107
- <!-- <Transition name="dropdown" mode="out-in"> -->
108
107
  <Popout
109
- v-if="showMenu"
110
108
  ref="dropdown"
109
+ :visible="showMenu"
111
110
  :anchor="anchorRef"
112
111
  class="vui-dropdown"
113
112
  :placement
@@ -118,17 +117,4 @@ onMounted(() => {
118
117
  >
119
118
  <slot :open :close :toggle :is-open="showMenu" />
120
119
  </Popout>
121
- <!-- </Transition> -->
122
120
  </template>
123
-
124
- <!-- <style scoped lang="scss">
125
- .dropdown-enter-active,
126
- .dropdown-leave-active {
127
- transition: var(--transition-fast);
128
- }
129
-
130
- .dropdown-enter-from,
131
- .dropdown-leave-to {
132
- opacity: 0;
133
- }
134
- </style> -->
@@ -36,7 +36,7 @@ const count = defineModel<number>({
36
36
 
37
37
  <template #end>
38
38
  <ButtonGroup>
39
- <Button v-if="!hideDecrement" key="decrease" :disabled="!decrementEnabled" :style="{ 'border-top-left-radius': 0, 'border-bottom-left-radius': 0 }" @click="count -= decrementBy">
39
+ <Button v-if="!hideDecrement" key="decrease" :disabled="!decrementEnabled" :style="{ 'border-top-left-radius': 0, 'border-bottom-left-radius': 0, 'marginRight': '-1px' }" @click="count -= decrementBy">
40
40
  <Icon icon="ph:minus" /> {{ decrementBy > 1 ? decrementBy : '' }}
41
41
  </Button>
42
42
  <Button v-if="!hideIncrement" key="increase" :disabled="!incrementEnabled" @click="count += incrementBy">
@@ -25,7 +25,7 @@ const show = ref(showPassword)
25
25
  <template #end>
26
26
  <Button
27
27
  square
28
- :data-title-top="show ? 'Hide' : 'Reveal'"
28
+ :aria-label="show ? 'Hide' : 'Reveal'"
29
29
  @click="show = !show"
30
30
  >
31
31
  <Icon :width="18" :height="18" :icon="show ? 'ph:eye-slash' : 'ph:eye'" />
@@ -4,6 +4,7 @@ import type { Pagination } from './pagination'
4
4
  import { computed } from 'vue'
5
5
  import Button from '../Button/Button.vue'
6
6
  import Flex from '../Flex/Flex.vue'
7
+ import Tooltip from '../Tooltip/Tooltip.vue'
7
8
 
8
9
  interface Props {
9
10
  numbers?: boolean
@@ -39,11 +40,21 @@ function setPrev() {
39
40
  <template>
40
41
  <Flex inline class="vui-pagination" gap="xxs">
41
42
  <slot name="start">
42
- <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)" />
43
+ <Tooltip v-if="props.firstLast">
44
+ <Button plain :disabled="props.pagination.startPage === props.pagination.currentPage" square icon="ph:caret-double-left" @click="emit('change', props.pagination.startPage)" />
45
+ <template #tooltip>
46
+ <p>First page</p>
47
+ </template>
48
+ </Tooltip>
43
49
  </slot>
44
50
 
45
51
  <slot name="prev" :disabled="canPrevPage" :set-page="setPrev">
46
- <Button v-if="props.prevNext" data-title-top="Previous page" plain :disabled="!canPrevPage" square icon="ph:caret-left" @click="setPrev" />
52
+ <Tooltip v-if="props.prevNext">
53
+ <Button plain :disabled="!canPrevPage" square icon="ph:caret-left" @click="setPrev" />
54
+ <template #tooltip>
55
+ <p>Previous page</p>
56
+ </template>
57
+ </Tooltip>
47
58
  </slot>
48
59
 
49
60
  <template v-if="props.numbers">
@@ -61,17 +72,21 @@ function setPrev() {
61
72
  </Flex>
62
73
  </template>
63
74
  <slot name="next" :disabled="canNextPage" :set-page="setNext">
64
- <Button v-if="props.prevNext" data-title-top="Next page" plain :disabled="!canNextPage" square icon="ph:caret-right" @click="setNext" />
75
+ <Tooltip v-if="props.prevNext">
76
+ <Button plain :disabled="!canNextPage" square icon="ph:caret-right" @click="setNext" />
77
+ <template #tooltip>
78
+ <p>Next page</p>
79
+ </template>
80
+ </Tooltip>
65
81
  </slot>
66
82
 
67
83
  <slot name="end">
68
- <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)" />
84
+ <Tooltip v-if="props.firstLast">
85
+ <Button plain :disabled="props.pagination.endPage === props.pagination.currentPage" square icon="ph:caret-double-right" @click="emit('change', props.pagination.endPage)" />
86
+ <template #tooltip>
87
+ <p>Last page</p>
88
+ </template>
89
+ </Tooltip>
69
90
  </slot>
70
91
  </Flex>
71
92
  </template>
72
-
73
- <style scoped>
74
- [data-title-top]:before {
75
- white-space: nowrap;
76
- }
77
- </style>
@@ -1,8 +1,9 @@
1
1
  <script setup lang='ts'>
2
2
  import type { Placement, PopoutMaybeElement } from '../../shared/types'
3
- import { flip, offset, shift, useFloating } from '@floating-ui/vue'
3
+ import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue'
4
4
  import { onClickOutside } from '@vueuse/core'
5
- import { toRef, useTemplateRef } from 'vue'
5
+ import { toRef, useTemplateRef, watch } from 'vue'
6
+ import { getPlacementAnimationName } from '../../shared/helpers'
6
7
  import './popout.scss'
7
8
 
8
9
  export interface Props {
@@ -18,10 +19,15 @@ export interface Props {
18
19
  * Distance between the anchor and the rendered tooltip
19
20
  */
20
21
  offset?: number
22
+ /**
23
+ * Set the visibility of the dropdown
24
+ */
25
+ visible: boolean
21
26
  }
22
27
 
23
28
  const props = withDefaults(defineProps<Props>(), {
24
29
  offset: 8,
30
+ placement: 'top',
25
31
  })
26
32
 
27
33
  const emit = defineEmits<{
@@ -30,23 +36,38 @@ const emit = defineEmits<{
30
36
  const popoutRef = useTemplateRef('popout')
31
37
  const anchorRef = toRef(props.anchor)
32
38
 
33
- const { floatingStyles } = useFloating(anchorRef, popoutRef, {
39
+ const { floatingStyles, update } = useFloating(anchorRef, popoutRef, {
40
+ whileElementsMounted: autoUpdate,
41
+ strategy: 'fixed',
42
+ transform: false,
34
43
  placement: props.placement,
35
44
  middleware: [
36
- // ...(props.placement
37
- // ? []
38
- // : [autoPlacement()]),
39
45
  shift({ padding: 8 }),
40
46
  flip(),
41
47
  offset(props.offset),
42
48
  ],
43
49
  })
44
50
 
51
+ // Make sure to update the popout when the anchor is mounted
52
+ watch(() => props.anchor, (value) => {
53
+ if (value) {
54
+ anchorRef.value = value
55
+ update()
56
+ }
57
+ })
58
+
45
59
  onClickOutside(popoutRef, () => emit('clickOutside'))
46
60
  </script>
47
61
 
48
62
  <template>
49
- <div ref="popout" :style="floatingStyles" class="vui-popout">
50
- <slot />
51
- </div>
63
+ <Transition :name="getPlacementAnimationName(props.placement)">
64
+ <div
65
+ v-if="props.visible"
66
+ ref="popout"
67
+ :style="floatingStyles"
68
+ class="vui-popout"
69
+ >
70
+ <slot />
71
+ </div>
72
+ </Transition>
52
73
  </template>
@@ -5,6 +5,7 @@
5
5
  background-color: var(--color-bg-medium);
6
6
  border-radius: var(--border-radius-m);
7
7
  z-index: 1000;
8
+ position: fixed;
8
9
  }
9
10
 
10
11
  :root.light {
@@ -2,6 +2,7 @@
2
2
  import type { Header } from './table'
3
3
  import { computed } from 'vue'
4
4
  import Button from '../Button/Button.vue'
5
+ import Tooltip from '../Tooltip/Tooltip.vue'
5
6
 
6
7
  interface Props {
7
8
  /**
@@ -23,16 +24,16 @@ const sortStateBind = computed(() => {
23
24
  return
24
25
  switch (props.header.sortKey) {
25
26
  case 'asc': return {
26
- 'icon': 'ph:sort-ascending',
27
- 'data-title-top': 'Ascending',
27
+ icon: 'ph:sort-ascending',
28
+ tooltipText: 'Ascending',
28
29
  }
29
30
  case 'desc': return {
30
- 'icon': 'ph:sort-descending',
31
- 'data-title-top': 'Descending',
31
+ icon: 'ph:sort-descending',
32
+ tooltipText: 'Descending',
32
33
  }
33
34
  default: return {
34
- 'icon': 'ph:arrows-down-up',
35
- 'data-title-top': 'Sort column',
35
+ icon: 'ph:arrows-down-up',
36
+ tooltipText: 'Sort column',
36
37
  }
37
38
  }
38
39
  })
@@ -44,16 +45,22 @@ const sortStateBind = computed(() => {
44
45
  <slot>
45
46
  {{ props.header?.label }}
46
47
  </slot>
47
- <Button
48
- v-if="props.sort && props.header"
49
- class="vui-table-sort-button"
50
- v-bind="sortStateBind"
51
- size="s"
52
- :plain="!!!props.header.sortKey"
53
- square
54
- variant="gray"
55
- @click="props.header.sortToggle"
56
- />
48
+ <template v-if="props.sort && props.header">
49
+ <Tooltip placement="top">
50
+ <Button
51
+ :icon="sortStateBind?.icon"
52
+ class="vui-table-sort-button"
53
+ size="s"
54
+ :plain="!!!props.header.sortKey"
55
+ square
56
+ variant="gray"
57
+ @click="props.header.sortToggle"
58
+ />
59
+ <template #tooltip>
60
+ {{ sortStateBind?.tooltipText }}
61
+ </template>
62
+ </Tooltip>
63
+ </template>
57
64
  </div>
58
65
  </th>
59
66
  </template>
@@ -1,3 +1,10 @@
1
+ .vui-table-overflow {
2
+ display: block;
3
+ max-width: 100%;
4
+ overflow-x: auto;
5
+ overflow-y: visible;
6
+ }
7
+
1
8
  .vui-table-container {
2
9
  display: block;
3
10
  width: 100%;
@@ -68,7 +75,6 @@
68
75
 
69
76
  th,
70
77
  td {
71
- font-size: var(--font-size-m);
72
78
  border: none;
73
79
  border-left: none !important;
74
80
  transition: var(--transition-fast);
@@ -139,11 +145,6 @@
139
145
  .vui-table-pagination-wrap {
140
146
  padding: var(--space-xs) var(--space-m);
141
147
  border-top: 1px solid var(--color-border);
142
-
143
- p {
144
- font-size: var(--font-size-s);
145
- color: var(--color-text-lighter);
146
- }
147
148
  }
148
149
  }
149
150
 
@@ -1,6 +1,6 @@
1
1
  <script setup lang='ts'>
2
2
  import type { Placement } from '../../shared/types'
3
- import { ref, useId, useTemplateRef, watch } from 'vue'
3
+ import { computed, ref, useId, useTemplateRef, watch } from 'vue'
4
4
  import Popout from '../Popout/Popout.vue'
5
5
  import './tooltip.scss'
6
6
 
@@ -52,35 +52,27 @@ watch(hoverAnchor, (isHovering) => {
52
52
  })
53
53
 
54
54
  const id = useId()
55
+ const anchor = computed(() => popoutAnchorRef.value?.children[0] as HTMLElement | null)
55
56
  </script>
56
57
 
57
58
  <template>
58
59
  <div
59
60
  ref="popoutAnchor"
60
- :style="{
61
- width: 'fit-content',
62
- }"
61
+ class="popout-anchor"
63
62
  :aria-describedby="id"
64
63
  @mouseenter="hoverAnchor = true"
65
64
  @mouseleave="hoverAnchor = false"
66
65
  >
67
66
  <slot />
68
67
  </div>
69
- <!-- <Transition name="tooltip"> -->
70
- <Popout v-if="showTooltip" :id :anchor="popoutAnchorRef" class="vui-tooltip" :placement>
68
+ <Popout :id :visible="showTooltip" :anchor class="vui-tooltip" :placement>
71
69
  <slot name="tooltip" />
72
70
  </Popout>
73
- <!-- </Transition> -->
74
71
  </template>
75
72
 
76
- <!-- <style scoped>
77
- .tooltip-enter-active,
78
- .tooltip-leave-active {
79
- transition: var(--transition-fast);
73
+ <style scoped lang="scss">
74
+ .popout-anchor {
75
+ display: contents;
76
+ width: fit-content;
80
77
  }
81
-
82
- .tooltip-enter-from,
83
- .tooltip-leave-to {
84
- opacity: 0;
85
- }
86
- </style> -->
78
+ </style>
@@ -1,8 +1,5 @@
1
1
  .vui-tooltip {
2
- padding: var(--space-s);
2
+ padding: var(--space-xs) var(--space-s);
3
3
  max-width: 512px;
4
4
  background-color: var(--color-bg-raised);
5
5
  }
6
-
7
- // :root.light {
8
- // }
@@ -28,9 +28,9 @@ import Flex from '../components/Flex/Flex.vue'
28
28
  </td>
29
29
  </tr>
30
30
  <tr>
31
- <th>Custom confirm popup</th>
31
+ <th>Custom confirm popup & positioned at the right</th>
32
32
  <td>
33
- <CopyClipboard text="Copy me!" confirm>
33
+ <CopyClipboard text="Copy me!" confirm confirm-placement="right">
34
34
  <Button>Copy me!</Button>
35
35
  <template #confirm>
36
36
  <Flex column gap="s" y-center>
@@ -26,7 +26,7 @@ const open = ref(false)
26
26
  This popout has offset of <code>32</code> and its placement is <code>bottom-start</code>. It also has an attached event to <code>clickOutside</code> which is fired when user clicks outside of the popout. In that case, we manually close it.
27
27
  </p>
28
28
  </Flex>
29
- <Popout v-if="open" :anchor="anchRef" class="test-popout" :offset="32" placement="bottom-start" @click-outside="open = false">
29
+ <Popout :visible="open" :anchor="anchRef" class="test-popout" :offset="32" placement="bottom-start" @click-outside="open = false">
30
30
  <h3>Popout content</h3>
31
31
  <p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolorem facere eligendi ex, alias itaque molestiae, vero animi, vitae vel fuga corporis aut consectetur temporibus ipsum placeat dolores perferendis. Deleniti, et!</p>
32
32
  </Popout>
@@ -6,6 +6,7 @@ import { paginate } from '../components/Pagination/pagination'
6
6
  import Pagination from '../components/Pagination/Pagination.vue'
7
7
  import * as Table from '../components/Table/index'
8
8
  import { defineTable } from '../components/Table/table'
9
+ import Tooltip from '../components/Tooltip/Tooltip.vue'
9
10
 
10
11
  interface Item {
11
12
  'ID Nation': string
@@ -191,5 +192,168 @@ const exampleToRender = computed(() => testData.slice(paginationExample.value.st
191
192
  <Divider :size="40" class="w-40" />
192
193
 
193
194
  <p>The <code>defineTable</code> hook can also be used with other UI components. Not just tables. That's why all the table interactivity is not within the component, but in a hook. It's very flexible and allows custom functionality.</p>
195
+
196
+ <Divider :size="40" />
197
+
198
+ <h5 class="mb-s">
199
+ Responsitivity
200
+ </h5>
201
+
202
+ <p class="mb-m">
203
+ Table can be made responsible if they overflow their width and are wrapped in a <code>.vui-table-container</code> element.
204
+ </p>
205
+
206
+ <div class="container container-m">
207
+ <div class="vui-table-overflow">
208
+ <table>
209
+ <thead>
210
+ <tr>
211
+ <th>
212
+ <Tooltip placement="top">
213
+ ID
214
+ <template #tooltip>
215
+ <p>ID</p>
216
+ </template>
217
+ </Tooltip>
218
+ </th>
219
+ <th>
220
+ <Tooltip placement="top">
221
+ Name
222
+ <template #tooltip>
223
+ <p>Name</p>
224
+ </template>
225
+ </Tooltip>
226
+ </th>
227
+ <th>
228
+ <Tooltip placement="top">
229
+ Email
230
+ <template #tooltip>
231
+ <p>Email</p>
232
+ </template>
233
+ </Tooltip>
234
+ </th>
235
+ <th>
236
+ <Tooltip placement="top">
237
+ Phone
238
+ <template #tooltip>
239
+ <p>Phone</p>
240
+ </template>
241
+ </Tooltip>
242
+ </th>
243
+ <th>
244
+ <Tooltip placement="top">
245
+ Address
246
+ <template #tooltip>
247
+ <p>Address</p>
248
+ </template>
249
+ </Tooltip>
250
+ </th>
251
+ <th>
252
+ <Tooltip placement="top">
253
+ City
254
+ <template #tooltip>
255
+ <p>City</p>
256
+ </template>
257
+ </Tooltip>
258
+ </th>
259
+ <th>
260
+ <Tooltip placement="top">
261
+ State
262
+ <template #tooltip>
263
+ <p>State</p>
264
+ </template>
265
+ </Tooltip>
266
+ </th>
267
+ <th>
268
+ <Tooltip placement="top">
269
+ Zip
270
+ <template #tooltip>
271
+ <p>Zip</p>
272
+ </template>
273
+ </Tooltip>
274
+ </th>
275
+ <th>
276
+ <Tooltip placement="top">
277
+ Country
278
+ <template #tooltip>
279
+ <p>Country</p>
280
+ </template>
281
+ </Tooltip>
282
+ </th>
283
+ <th>
284
+ <Tooltip placement="top">
285
+ Status
286
+ <template #tooltip>
287
+ <p>Status</p>
288
+ </template>
289
+ </Tooltip>
290
+ </th>
291
+ </tr>
292
+ </thead>
293
+ <tbody>
294
+ <tr>
295
+ <td>001</td>
296
+ <td>John Doe</td>
297
+ <td>john.doe@example.com</td>
298
+ <td>(555) 123-4567</td>
299
+ <td>123 Main St</td>
300
+ <td>New York</td>
301
+ <td>NY</td>
302
+ <td>10001</td>
303
+ <td>USA</td>
304
+ <td>Active</td>
305
+ </tr>
306
+ <tr>
307
+ <td>002</td>
308
+ <td>Jane Smith</td>
309
+ <td>jane.smith@example.com</td>
310
+ <td>(555) 987-6543</td>
311
+ <td>456 Oak Ave</td>
312
+ <td>Los Angeles</td>
313
+ <td>CA</td>
314
+ <td>90001</td>
315
+ <td>USA</td>
316
+ <td>Inactive</td>
317
+ </tr>
318
+ <tr>
319
+ <td>003</td>
320
+ <td>Robert Johnson</td>
321
+ <td>robert.j@example.com</td>
322
+ <td>(555) 456-7890</td>
323
+ <td>789 Pine Blvd</td>
324
+ <td>Chicago</td>
325
+ <td>IL</td>
326
+ <td>60601</td>
327
+ <td>USA</td>
328
+ <td>Active</td>
329
+ </tr>
330
+ <tr>
331
+ <td>004</td>
332
+ <td>Emily Davis</td>
333
+ <td>emily.d@example.com</td>
334
+ <td>(555) 234-5678</td>
335
+ <td>321 Cedar Ln</td>
336
+ <td>Houston</td>
337
+ <td>TX</td>
338
+ <td>77001</td>
339
+ <td>USA</td>
340
+ <td>Pending</td>
341
+ </tr>
342
+ <tr>
343
+ <td>005</td>
344
+ <td>Michael Wilson</td>
345
+ <td>michael.w@example.com</td>
346
+ <td>(555) 876-5432</td>
347
+ <td>654 Maple Dr</td>
348
+ <td>Miami</td>
349
+ <td>FL</td>
350
+ <td>33101</td>
351
+ <td>USA</td>
352
+ <td>Active</td>
353
+ </tr>
354
+ </tbody>
355
+ </table>
356
+ </div>
357
+ </div>
194
358
  </div>
195
359
  </template>
@@ -1,3 +1,5 @@
1
+ import type { Placement } from '@floating-ui/vue'
2
+
1
3
  export function createArray(length: number, startOffset: number = 0): number[] {
2
4
  return Array
3
5
  .from({ length })
@@ -115,3 +117,8 @@ export function isObjectInSet(set: Set<any>, obj: any): boolean {
115
117
 
116
118
  return false
117
119
  }
120
+
121
+ export function getPlacementAnimationName(position: Placement): string {
122
+ const suffix = position.includes('-') ? position.split('-')[0] : position
123
+ return `fade-${suffix}`
124
+ }