@dolanske/vui 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +6 -13
  2. package/dist/components/Alert/Alert.vue.d.ts +7 -1
  3. package/dist/components/Avatar/Avatar.vue.d.ts +15 -1
  4. package/dist/components/Badge/Badge.vue.d.ts +1 -1
  5. package/dist/components/Breadcrumbs/BreadcrumbItem.vue.d.ts +1 -1
  6. package/dist/components/Button/Button.vue.d.ts +5 -15
  7. package/dist/components/ButtonGroup/ButtonGroup.vue.d.ts +2 -0
  8. package/dist/components/Calendar/Calendar.vue.d.ts +1 -1
  9. package/dist/components/Checkbox/Checkbox.vue.d.ts +1 -0
  10. package/dist/components/Dropdown/Dropdown.vue.d.ts +19 -4
  11. package/dist/components/Dropdown/DropdownTitle.vue.d.ts +5 -1
  12. package/dist/components/Flex/Flex.vue.d.ts +3 -1
  13. package/dist/components/Grid/Grid.vue.d.ts +7 -2
  14. package/dist/components/Input/Dropzone.vue.d.ts +1 -1
  15. package/dist/components/Input/Input.vue.d.ts +2 -2
  16. package/dist/components/Kbd/KbdGroup.vue.d.ts +3 -11
  17. package/dist/components/Modal/Confirm.vue.d.ts +1 -1
  18. package/dist/components/Modal/Modal.vue.d.ts +1 -1
  19. package/dist/components/Pagination/Pagination.vue.d.ts +3 -0
  20. package/dist/components/Popout/Popout.vue.d.ts +8 -1
  21. package/dist/components/Progress/Progress.vue.d.ts +2 -0
  22. package/dist/components/Radio/Radio.vue.d.ts +1 -0
  23. package/dist/components/Select/Select.vue.d.ts +2 -0
  24. package/dist/components/Sheet/Sheet.vue.d.ts +3 -0
  25. package/dist/components/Switch/Switch.vue.d.ts +1 -0
  26. package/dist/components/Table/index.d.ts +6 -0
  27. package/dist/components/Table/table.d.ts +1 -1
  28. package/dist/components/Tabs/Tab.vue.d.ts +16 -3
  29. package/dist/components/Tabs/Tabs.vue.d.ts +4 -0
  30. package/dist/components/Toast/toast.d.ts +245 -0
  31. package/dist/index.d.ts +2 -7
  32. package/dist/shared/helpers.d.ts +9 -0
  33. package/dist/shared/theme.d.ts +3 -0
  34. package/dist/style.css +1 -1
  35. package/dist/vui.js +6423 -6046
  36. package/package.json +8 -4
  37. package/src/App.vue +89 -192
  38. package/src/components/Accordion/accordion.scss +2 -0
  39. package/src/components/Alert/Alert.vue +11 -5
  40. package/src/components/Alert/alert.scss +104 -23
  41. package/src/components/Avatar/Avatar.vue +4 -1
  42. package/src/components/Avatar/avatar.scss +1 -1
  43. package/src/components/Badge/Badge.vue +1 -1
  44. package/src/components/Badge/badge.scss +134 -17
  45. package/src/components/Breadcrumbs/BreadcrumbItem.vue +2 -2
  46. package/src/components/Breadcrumbs/Breadcrumbs.vue +1 -2
  47. package/src/components/Breadcrumbs/breadcrumbs.scss +2 -1
  48. package/src/components/Button/Button.vue +16 -21
  49. package/src/components/Button/button.scss +159 -56
  50. package/src/components/ButtonGroup/ButtonGroup.vue +4 -1
  51. package/src/components/ButtonGroup/button-group.scss +2 -2
  52. package/src/components/Calendar/Calendar.vue +9 -3
  53. package/src/components/Calendar/calendar.scss +29 -2
  54. package/src/components/Card/Card.vue +2 -2
  55. package/src/components/Card/card.scss +4 -4
  56. package/src/components/Checkbox/Checkbox.vue +7 -5
  57. package/src/components/Checkbox/checkbox.scss +27 -13
  58. package/src/components/CopyClipboard/CopyClipboard.vue +15 -6
  59. package/src/components/CopyClipboard/copy-clipboard.scss +10 -2
  60. package/src/components/Drawer/Drawer.vue +4 -4
  61. package/src/components/Drawer/drawer.scss +1 -0
  62. package/src/components/Dropdown/Dropdown.vue +44 -20
  63. package/src/components/Dropdown/DropdownItem.vue +5 -4
  64. package/src/components/Dropdown/DropdownTitle.vue +7 -1
  65. package/src/components/Dropdown/dropdown-item.scss +84 -0
  66. package/src/components/Dropdown/dropdown.scss +21 -86
  67. package/src/components/Flex/Flex.vue +4 -1
  68. package/src/components/Grid/Grid.vue +25 -2
  69. package/src/components/Input/Color.vue +26 -0
  70. package/src/components/Input/Counter.vue +12 -16
  71. package/src/components/Input/Dropzone.vue +1 -1
  72. package/src/components/Input/File.vue +1 -1
  73. package/src/components/Input/Input.vue +8 -6
  74. package/src/components/Input/Password.vue +1 -13
  75. package/src/components/Input/Textarea.vue +4 -2
  76. package/src/components/Input/input.scss +113 -19
  77. package/src/components/Kbd/KbdGroup.vue +2 -6
  78. package/src/components/Kbd/kbd.scss +7 -6
  79. package/src/components/Modal/Confirm.vue +1 -1
  80. package/src/components/Modal/Modal.vue +23 -15
  81. package/src/components/Modal/modal.scss +11 -6
  82. package/src/components/OTP/otp.scss +8 -7
  83. package/src/components/Pagination/Pagination.vue +6 -3
  84. package/src/components/Popout/Popout.vue +15 -5
  85. package/src/components/Popout/popout.scss +8 -1
  86. package/src/components/Progress/Progress.vue +18 -5
  87. package/src/components/Progress/progress.scss +7 -1
  88. package/src/components/Radio/Radio.vue +4 -2
  89. package/src/components/Radio/radio.scss +28 -9
  90. package/src/components/Select/Select.vue +49 -18
  91. package/src/components/Select/select.scss +35 -2
  92. package/src/components/Sheet/Sheet.vue +8 -2
  93. package/src/components/Sheet/sheet.scss +9 -0
  94. package/src/components/Sidebar/Sidebar.vue +46 -16
  95. package/src/components/Sidebar/sidebar.scss +6 -5
  96. package/src/components/Spinner/spinner.scss +2 -1
  97. package/src/components/Switch/Switch.vue +4 -3
  98. package/src/components/Switch/switch.scss +48 -7
  99. package/src/components/Table/{Header.vue → Head.vue} +5 -5
  100. package/src/components/Table/{Table.vue → Root.vue} +2 -2
  101. package/src/components/Table/SelectRow.vue +2 -1
  102. package/src/components/Table/index.ts +7 -0
  103. package/src/components/Table/table.scss +25 -5
  104. package/src/components/Table/table.ts +7 -3
  105. package/src/components/Tabs/Tab.vue +7 -9
  106. package/src/components/Tabs/Tabs.vue +9 -2
  107. package/src/components/Tabs/tabs.scss +11 -3
  108. package/src/components/Toast/Toasts.vue +5 -0
  109. package/src/components/Toast/toast.scss +6 -2
  110. package/src/components/Toast/toast.ts +7 -0
  111. package/src/components/Tooltip/Tooltip.vue +9 -9
  112. package/src/components/Tooltip/tooltip.scss +4 -0
  113. package/src/examples/ExampleAccordions.vue +58 -0
  114. package/src/examples/ExampleAlerts.vue +78 -0
  115. package/src/examples/ExampleAvatars.vue +44 -0
  116. package/src/examples/ExampleBadges.vue +48 -0
  117. package/src/examples/ExampleBreadcrumbs.vue +46 -0
  118. package/src/examples/ExampleButtons.vue +140 -0
  119. package/src/examples/ExampleCalendars.vue +40 -0
  120. package/src/examples/ExampleCards.vue +94 -0
  121. package/src/examples/ExampleCheckboxes.vue +123 -0
  122. package/src/examples/ExampleCopyClipboard.vue +47 -0
  123. package/src/examples/ExampleDividers.vue +39 -0
  124. package/src/examples/ExampleDrawers.vue +67 -0
  125. package/src/examples/ExampleDropdowns.vue +114 -0
  126. package/src/examples/ExampleFlexGrid.vue +122 -0
  127. package/src/examples/ExampleInputs.vue +234 -0
  128. package/src/examples/ExampleKBD.vue +65 -0
  129. package/src/examples/ExampleModals.vue +143 -0
  130. package/src/examples/ExamplePalette.vue +159 -0
  131. package/src/examples/ExamplePopouts.vue +41 -0
  132. package/src/examples/ExampleSheets.vue +77 -0
  133. package/src/examples/ExampleSidebars.vue +270 -0
  134. package/src/examples/ExampleSkeletons.vue +26 -0
  135. package/src/examples/ExampleSpinners.vue +78 -0
  136. package/src/examples/ExampleTables.vue +195 -0
  137. package/src/examples/ExampleTabs.vue +119 -0
  138. package/src/examples/ExampleToasts.vue +96 -0
  139. package/src/examples/ExampleTooltips.vue +70 -0
  140. package/src/examples/shared/ExampleColor.vue +28 -0
  141. package/src/index.ts +4 -11
  142. package/src/internal/Backdrop/backdrop.scss +7 -1
  143. package/src/shared/helpers.ts +43 -0
  144. package/src/shared/theme.ts +22 -0
  145. package/src/style/animation.scss +1 -0
  146. package/src/style/core.scss +34 -57
  147. package/src/style/layout.scss +102 -5
  148. package/src/style/{fonts.scss → text.scss} +39 -0
  149. package/src/style/theme.scss +195 -0
  150. package/src/style/tooltip.scss +22 -4
  151. package/src/style/typography.scss +95 -18
  152. package/dist/components/Table/Row.vue.d.ts +0 -16
  153. package/src/components/Table/Row.vue +0 -9
  154. /package/dist/components/Table/{Header.vue.d.ts → Head.vue.d.ts} +0 -0
  155. /package/dist/components/Table/{Table.vue.d.ts → Root.vue.d.ts} +0 -0
@@ -20,7 +20,7 @@ const props = withDefaults(defineProps<Props>(), {
20
20
  size: 's',
21
21
  canDismiss: true,
22
22
  showCancel: true,
23
- confirmVariant: 'default',
23
+ confirmVariant: 'gray',
24
24
  })
25
25
 
26
26
  const emits = defineEmits<{
@@ -8,7 +8,7 @@ import Card from '../Card/Card.vue'
8
8
  import './modal.scss'
9
9
 
10
10
  export interface ModalProps {
11
- size?: Sizes | 'full'
11
+ size?: Sizes | 'full' | 'screen'
12
12
  /**
13
13
  * Modal wraps a floating card. You can optinally pass in any props you'd pass
14
14
  * into the <Card /> component.
@@ -43,32 +43,40 @@ function close() {
43
43
  }
44
44
 
45
45
  const attrs = useAttrs()
46
+
47
+ function tryClose() {
48
+ if (canDismiss) {
49
+ close()
50
+ }
51
+ }
46
52
  </script>
47
53
 
48
54
  <template>
49
55
  <Teleport to="body">
50
56
  <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
- />
57
+ <Backdrop v-if="open" :class="{ 'p-0': size === 'screen' }" @close="tryClose">
58
+ <div class="vui-modal" :class="[`vui-modal-size-${size}`, { scrollable: scrollable || size === 'screen', centered }]" v-bind="attrs" @click.self="tryClose">
61
59
  <Card v-bind="card">
62
60
  <template v-if="$slots.header" #header>
63
- <slot name="header" :close />
61
+ <slot name="header" :close="close" />
62
+ </template>
63
+ <template #header-end>
64
+ <Button
65
+ v-if="canDismiss"
66
+ class="vui-modal-close"
67
+ plain
68
+ square
69
+ icon="ph:x"
70
+ @click="open = false"
71
+ />
64
72
  </template>
65
73
  <template v-if="$slots.default" #default>
66
74
  <div>
67
- <slot name="default" :close />
75
+ <slot name="default" :close="close" />
68
76
  </div>
69
77
  </template>
70
78
  <template v-if="$slots.footer" #footer>
71
- <slot name="footer" :close />
79
+ <slot name="footer" :close="close" />
72
80
  </template>
73
81
  </Card>
74
82
  </div>
@@ -86,6 +94,6 @@ const attrs = useAttrs()
86
94
  .modal-enter-from,
87
95
  .modal-leave-to {
88
96
  opacity: 0;
89
- transform: scale(0.95);
97
+ transform: scale(1.025);
90
98
  }
91
99
  </style>
@@ -4,12 +4,6 @@
4
4
  height: 100%;
5
5
  position: relative;
6
6
 
7
- .vui-modal-close {
8
- position: absolute;
9
- top: 16px;
10
- right: 16px;
11
- }
12
-
13
7
  &.centered {
14
8
  display: flex;
15
9
  align-items: center;
@@ -27,6 +21,17 @@
27
21
  max-width: 728px;
28
22
  }
29
23
 
24
+ &.vui-modal-size-screen {
25
+ position: fixed;
26
+ inset: 0;
27
+
28
+ & > .vui-card {
29
+ border-radius: 0;
30
+ border: none;
31
+ height: 100vh;
32
+ }
33
+ }
34
+
30
35
  &.scrollable {
31
36
  & > .vui-card {
32
37
  display: flex;
@@ -10,21 +10,22 @@
10
10
  display: flex;
11
11
  align-items: center;
12
12
  justify-content: center;
13
- width: 42px;
14
- height: 42px;
13
+ width: var(--interactive-el-height);
14
+ height: var(--interactive-el-height);
15
15
  border: 1px solid var(--color-border-strong);
16
16
  color: var(--color-text);
17
17
  z-index: 1;
18
18
  font-size: var(--font-size-m);
19
19
  outline: 0 solid var(--color-text-light);
20
- transition: var(--transition);
20
+ transition: var(--transition-fast);
21
21
 
22
22
  .blinker {
23
- display: none;
23
+ display: block;
24
24
  height: 16px;
25
25
  width: 1px;
26
26
  background-color: var(--color-text);
27
- animation: blink 1.25s ease-out infinite;
27
+ animation: blink 1s ease-out infinite;
28
+ visibility: hidden;
28
29
  }
29
30
 
30
31
  @keyframes blink {
@@ -40,7 +41,7 @@
40
41
  }
41
42
 
42
43
  &.has-value {
43
- background-color: var(--color-bg-raised);
44
+ background-color: var(--color-bg-medium);
44
45
 
45
46
  .blinker {
46
47
  display: none !important;
@@ -52,7 +53,7 @@
52
53
  outline-width: 2px;
53
54
 
54
55
  .blinker {
55
- display: block;
56
+ visibility: visible;
56
57
  }
57
58
  }
58
59
 
@@ -1,4 +1,5 @@
1
1
  <script setup lang='ts'>
2
+ import type { Variants } from '../Button/Button.vue'
2
3
  import type { Pagination } from './pagination'
3
4
  import { computed } from 'vue'
4
5
  import Button from '../Button/Button.vue'
@@ -9,12 +10,14 @@ interface Props {
9
10
  pagination: Pagination
10
11
  prevNext?: boolean
11
12
  firstLast?: boolean
13
+ variant?: Variants
12
14
  }
13
15
 
14
16
  const props = withDefaults(defineProps<Props>(), {
15
17
  numbers: true,
16
18
  prevNext: true,
17
19
  firstLast: true,
20
+ variant: 'gray',
18
21
  })
19
22
 
20
23
  const emit = defineEmits<{
@@ -34,7 +37,7 @@ function setPrev() {
34
37
  </script>
35
38
 
36
39
  <template>
37
- <Flex inline class="vui-pagination" gap="s">
40
+ <Flex inline class="vui-pagination" gap="xxs">
38
41
  <slot name="start">
39
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)" />
40
43
  </slot>
@@ -44,13 +47,13 @@ function setPrev() {
44
47
  </slot>
45
48
 
46
49
  <template v-if="props.numbers">
47
- <Flex gap="s">
50
+ <Flex gap="xxs">
48
51
  <Button
49
52
  v-for="page in props.pagination.pages"
50
53
  :key="page"
51
54
  square
52
55
  :plain="props.pagination.currentPage !== page"
53
- :variant="props.pagination.currentPage === page ? 'blue' : 'default'"
56
+ variant="gray"
54
57
  @click="emit('change', page)"
55
58
  >
56
59
  {{ page }}
@@ -1,10 +1,14 @@
1
1
  <script setup lang='ts'>
2
2
  import type { Placement, PopoutMaybeElement } from '../../shared/types'
3
- import { autoPlacement, offset, shift, useFloating } from '@floating-ui/vue'
3
+ import { flip, offset, shift, useFloating } from '@floating-ui/vue'
4
+ import { onClickOutside } from '@vueuse/core'
4
5
  import { toRef, useTemplateRef } from 'vue'
5
6
  import './popout.scss'
6
7
 
7
8
  export interface Props {
9
+ /**
10
+ * Reference to the HTML element the Popout is anchored to
11
+ */
8
12
  anchor: PopoutMaybeElement<HTMLElement>
9
13
  /**
10
14
  * Override the autoPlacement option
@@ -20,19 +24,25 @@ const props = withDefaults(defineProps<Props>(), {
20
24
  offset: 8,
21
25
  })
22
26
 
27
+ const emit = defineEmits<{
28
+ clickOutside: []
29
+ }>()
23
30
  const popoutRef = useTemplateRef('popout')
24
31
  const anchorRef = toRef(props.anchor)
25
32
 
26
33
  const { floatingStyles } = useFloating(anchorRef, popoutRef, {
27
34
  placement: props.placement,
28
35
  middleware: [
29
- ...(props.placement ? [] : [autoPlacement()]),
36
+ // ...(props.placement
37
+ // ? []
38
+ // : [autoPlacement()]),
39
+ shift({ padding: 8 }),
40
+ flip(),
30
41
  offset(props.offset),
31
- shift({
32
- padding: 8,
33
- }),
34
42
  ],
35
43
  })
44
+
45
+ onClickOutside(popoutRef, () => emit('clickOutside'))
36
46
  </script>
37
47
 
38
48
  <template>
@@ -2,7 +2,14 @@
2
2
  border-radius: var(--border-radius);
3
3
  box-shadow: var(--box-shadow);
4
4
  min-width: 80px;
5
- background-color: var(--color-bg-raised);
5
+ background-color: var(--color-bg-medium);
6
6
  border-radius: var(--border-radius-m);
7
7
  z-index: 1000;
8
8
  }
9
+
10
+ :root.light {
11
+ .vui-popout {
12
+ background-color: var(--color-bg);
13
+ box-shadow: var(--box-shadow-strong);
14
+ }
15
+ }
@@ -1,6 +1,7 @@
1
1
  <script setup lang='ts'>
2
- import { computed, onMounted, useTemplateRef, watchEffect } from 'vue'
3
- import { delay, formatUnitValue, isNil, randomMinMax } from '../../shared/helpers'
2
+ import { whenever } from '@vueuse/core'
3
+ import { computed, onMounted, useTemplateRef, watch, watchEffect } from 'vue'
4
+ import { clamp, delay, formatUnitValue, isNil, randomMinMax } from '../../shared/helpers'
4
5
  import './progress.scss'
5
6
 
6
7
  interface Props {
@@ -30,10 +31,14 @@ const {
30
31
  height,
31
32
  } = defineProps<Props>()
32
33
 
34
+ const emit = defineEmits<{
35
+ done: []
36
+ }>()
37
+
33
38
  const progressAmount = defineModel<number>({
34
39
  default: 0,
35
40
  set(value) {
36
- return Math.min(value, 100)
41
+ return clamp(0, 100, value)
37
42
  },
38
43
  })
39
44
 
@@ -49,6 +54,8 @@ watchEffect(() => {
49
54
  }
50
55
  })
51
56
 
57
+ whenever(() => fake, fakeIncrement)
58
+
52
59
  // Automatically / randomly increment but never reach 100% until
53
60
  async function fakeIncrement() {
54
61
  if (fake && progressAmount.value < 100) {
@@ -59,15 +66,21 @@ async function fakeIncrement() {
59
66
  }
60
67
  else {
61
68
  progressAmount.value += randomMinMax(1, 10)
62
- await delay(randomMinMax(200, 1000))
69
+ await delay(randomMinMax(200, 12000))
63
70
  }
64
71
  fakeIncrement()
65
72
  }
66
73
  }
67
74
 
75
+ watch(progressAmount, (v) => {
76
+ if (v >= 100) {
77
+ emit('done')
78
+ }
79
+ })
80
+
68
81
  onMounted(fakeIncrement)
69
82
 
70
- const w = computed(() => `${progressAmount.value}%`)
83
+ const w = computed(() => `${clamp(0, 100, progressAmount.value)}%`)
71
84
  const bC = computed(() => color)
72
85
  </script>
73
86
 
@@ -5,7 +5,7 @@
5
5
  width: 100%;
6
6
  position: relative;
7
7
  border-radius: 999px;
8
- background-color: var(--color-bg-lowered);
8
+ background-color: var(--color-bg-raised);
9
9
  overflow: hidden;
10
10
  height: var(--vui-progress-height);
11
11
 
@@ -39,3 +39,9 @@
39
39
  transition: var(--transition-slow);
40
40
  }
41
41
  }
42
+
43
+ :root.light {
44
+ .vui-progress-indicator {
45
+ background-color: var(--);
46
+ }
47
+ }
@@ -7,12 +7,14 @@ export interface RadioProps {
7
7
  label?: string
8
8
  disabled?: boolean
9
9
  value: any
10
+ accent?: boolean
10
11
  }
11
12
 
12
13
  const {
13
14
  label,
14
15
  disabled,
15
16
  value,
17
+ accent,
16
18
  } = defineProps<RadioProps>()
17
19
  const slots = defineSlots()
18
20
  const checked = defineModel()
@@ -21,8 +23,8 @@ const isChecked = computed(() => value === checked.value)
21
23
  </script>
22
24
 
23
25
  <template>
24
- <div class="vui-radio" :class="{ disabled: !!disabled, checked: isChecked }">
25
- <input :id v-model="checked" type="radio" :value :checked="isChecked">
26
+ <div class="vui-radio" :class="{ disabled: !!disabled, checked: isChecked, accent }">
27
+ <input :id v-model="checked" type="radio" :value :checked="isChecked" :disabled>
26
28
  <label :for="id">
27
29
  <span class="vui-radio-icon">
28
30
  <Icon :icon="isChecked ? 'ph:radio-button-fill' : 'ph:circle'" />
@@ -5,18 +5,21 @@
5
5
  .vui-radio-icon svg {
6
6
  color: var(--color-text);
7
7
  }
8
+
9
+ &.accent .vui-radio-icon svg {
10
+ color: var(--color-accent);
11
+ }
8
12
  }
9
13
 
10
14
  &.disabled {
11
- cursor: not-allowed;
12
- pointer-events: none;
13
-
14
- .vui-radio-icon svg path {
15
- color: var(--color-text-lighter) !important;
15
+ .vui-radio-icon {
16
+ opacity: 0.25;
17
+ cursor: not-allowed;
16
18
  }
17
19
 
18
- input + label p {
19
- color: var(--color-text-lighter);
20
+ input + label .vui-radio-content {
21
+ opacity: 0.4;
22
+ cursor: not-allowed;
20
23
  }
21
24
  }
22
25
 
@@ -33,7 +36,17 @@
33
36
  }
34
37
 
35
38
  input {
36
- display: none;
39
+ width: 1px;
40
+ height: 1px;
41
+ position: absolute;
42
+ overflow: hidden;
43
+ outline: none !important;
44
+ opacity: 0;
45
+
46
+ &:focus-visible + label .vui-radio-icon {
47
+ outline: 2px solid var(--color-text);
48
+ border-radius: 999px;
49
+ }
37
50
 
38
51
  & + label {
39
52
  display: grid;
@@ -50,10 +63,16 @@
50
63
  display: flex;
51
64
  align-items: center;
52
65
  min-height: var(--radio-size);
53
- font-size: var(--font-size-ms);
66
+ font-size: var(--font-size-m);
54
67
  // line-height: var(--radio-size);
55
68
  }
56
69
  }
57
70
  }
58
71
  }
59
72
  }
73
+
74
+ :root.light {
75
+ .vui-radio.checked.accent .vui-radio-icon svg {
76
+ color: var(--color-accent);
77
+ }
78
+ }
@@ -1,7 +1,7 @@
1
1
  <!-- eslint-disable ts/consistent-type-definitions -->
2
2
  <script setup lang='ts' generic="T">
3
3
  import { Icon } from '@iconify/vue'
4
- import { computed, onMounted, ref, useTemplateRef } from 'vue'
4
+ import { computed, onMounted, ref, useId, useTemplateRef } from 'vue'
5
5
  import { searchString } from '../../shared/helpers'
6
6
  import Button from '../Button/Button.vue'
7
7
  import Dropdown from '../Dropdown/Dropdown.vue'
@@ -28,6 +28,8 @@ type Props = {
28
28
  search?: boolean
29
29
  maxActiveOptions?: number
30
30
  showClear?: boolean
31
+ disabled?: boolean
32
+ errors?: string[]
31
33
  }
32
34
 
33
35
  const {
@@ -39,11 +41,14 @@ const {
39
41
  options,
40
42
  single = true,
41
43
  search,
42
- maxActiveOptions = 3,
44
+ maxActiveOptions,
43
45
  showClear,
46
+ disabled,
47
+ errors = [] as string[],
44
48
  } = defineProps<Props>()
45
49
 
46
50
  const selected = defineModel<SelectOption[] | undefined>()
51
+ const trigger = useTemplateRef('trigger')
47
52
 
48
53
  //
49
54
  function setValue(option: SelectOption) {
@@ -68,7 +73,7 @@ function setValue(option: SelectOption) {
68
73
  if (!selected.value) {
69
74
  selected.value = [option]
70
75
  }
71
- else {
76
+ else if (!maxActiveOptions || (selected.value.length < maxActiveOptions)) {
72
77
  selected.value?.push(option)
73
78
  }
74
79
  }
@@ -97,7 +102,7 @@ const renderPlaceholder = computed(() => {
97
102
  return selected.value[0].label
98
103
 
99
104
  // If amount of selected exceeds the active capacity
100
- if (selected.value.length > maxActiveOptions) {
105
+ if (selected.value.length > 3) {
101
106
  return `${selected.value.length} selected`
102
107
  }
103
108
 
@@ -117,42 +122,59 @@ function clearValue() {
117
122
  selected.value = undefined
118
123
  dropdownRef.value?.close()
119
124
  }
125
+
126
+ const id = useId()
120
127
  </script>
121
128
 
122
129
  <template>
123
- <div class="vui-input-container vui-select" :class="{ expand, required, readonly }">
124
- <Dropdown ref="dropdown" :expand>
130
+ <div class="vui-input-container vui-select" :class="{ expand, required, readonly, disabled, 'has-errors': errors.length > 0 }">
131
+ <Dropdown ref="dropdown" :expand @close="trigger?.focus({ preventScroll: true })">
125
132
  <template #trigger="{ toggle, isOpen }">
126
133
  <div class="vui-input vui-select-trigger-content">
127
- <label v-if="label" for="id">{{ label }}</label>
134
+ <label v-if="label" :for="id">{{ label }}</label>
128
135
  <p v-if="hint" class="vui-input-hint">
129
136
  {{ hint }}
130
137
  </p>
131
138
 
132
- <button class="vui-input-style vui-select-trigger-container" @click="toggle">
139
+ <button
140
+ :id
141
+ ref="trigger"
142
+ class="vui-input-style vui-select-trigger-container"
143
+ :class="{ 'has-value': selected && selected.length > 0 }"
144
+ :disabled
145
+ @click="toggle"
146
+ >
133
147
  <span>
134
148
  {{ renderPlaceholder }}
135
149
  </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
- />
150
+ <template v-if="showClear && !required && selected">
151
+ <div class="flex-1" />
152
+ <Button
153
+ plain
154
+ icon="ph:x"
155
+ square
156
+ size="s"
157
+
158
+ @click.stop="clearValue"
159
+ />
160
+ </template>
144
161
  <Icon :icon="isOpen ? 'ph:caret-up' : 'ph:caret-down'" />
145
162
  </button>
146
163
  </div>
147
164
  </template>
148
165
 
149
166
  <template #default="{ close, isOpen }">
150
- <DropdownTitle v-if="search">
167
+ <DropdownTitle v-if="search" sticky>
151
168
  <Input
152
169
  v-model="searchStr"
153
170
  placeholder="Search..."
154
171
  :focus="isOpen"
155
- />
172
+ expand
173
+ >
174
+ <template #start>
175
+ <Icon icon="ph:magnifying-glass" />
176
+ </template>
177
+ </Input>
156
178
  </DropdownTitle>
157
179
 
158
180
  <p v-if="filteredOptions.length === 0" class="vue-select-no-results">
@@ -176,5 +198,14 @@ function clearValue() {
176
198
  </DropdownItem>
177
199
  </template>
178
200
  </Dropdown>
201
+
202
+ <p v-if="maxActiveOptions && !single" class="vui-input-limit">
203
+ {{ `${selected ? selected.length : 0}/${maxActiveOptions}` }}
204
+ </p>
205
+ <ul v-if="errors.length > 0" class="vui-input-errors">
206
+ <li v-for="err in errors" :key="err">
207
+ {{ err }}
208
+ </li>
209
+ </ul>
179
210
  </div>
180
211
  </template>
@@ -1,5 +1,7 @@
1
1
  .vui-input-container {
2
2
  &.vui-select {
3
+ width: auto;
4
+
3
5
  &.expand {
4
6
  .vui-dropdown-trigger-wrap,
5
7
  .vui-dropdown-trigger-content {
@@ -7,7 +9,22 @@
7
9
  }
8
10
  }
9
11
 
12
+ &.disabled {
13
+ .vui-input-style {
14
+ cursor: not-allowed;
15
+
16
+ &:hover {
17
+ border-color: var(--color-border);
18
+ }
19
+ }
20
+ }
21
+
10
22
  .vui-select-trigger-container {
23
+ &.has-value span {
24
+ color: var(--color-text);
25
+ font-weight: var(--font-weight-medium);
26
+ }
27
+
11
28
  span {
12
29
  white-space: nowrap;
13
30
  text-overflow: ellipsis;
@@ -30,8 +47,6 @@
30
47
  margin-right: -6px;
31
48
 
32
49
  .vui-button-slot svg {
33
- width: 14px;
34
- height: 14px;
35
50
  color: var(--color-text-light);
36
51
  }
37
52
  }
@@ -42,3 +57,21 @@
42
57
  }
43
58
  }
44
59
  }
60
+
61
+ select {
62
+ display: block;
63
+ height: var(--interactive-el-height);
64
+ line-height: var(--interactive-el-height);
65
+ background-color: var(--color-bg);
66
+ border: 1px solid var(--color-border);
67
+ border-radius: var(--border-radius-s);
68
+ padding-inline: var(--space-xs);
69
+ transition: var(--transition-fast);
70
+ z-index: 1;
71
+ font-size: var(--font-size-m);
72
+ color: var(--color-text);
73
+
74
+ &:hover {
75
+ border-color: var(--color-border-strong);
76
+ }
77
+ }
@@ -55,14 +55,20 @@ const baseTransform = computed(() => {
55
55
  <div class="flex-1">
56
56
  <slot name="header" :close />
57
57
  </div>
58
- <Button square icon="ph:x" @click="open = false" />
58
+ <Button plain square icon="ph:x" @click="open = false" />
59
59
  </div>
60
60
 
61
- <Divider v-if="separator && $slots.header" :space="1" />
61
+ <Divider v-if="separator && $slots.header" :size="1" />
62
62
 
63
63
  <div v-if="$slots.default" class="vui-sheet-content">
64
64
  <slot :close />
65
65
  </div>
66
+
67
+ <Divider v-if="separator && $slots.footer" :size="1" />
68
+
69
+ <div class="vui-sheet-footer">
70
+ <slot name="footer" :close />
71
+ </div>
66
72
  </div>
67
73
  </Backdrop>
68
74
  </Transition>