@indielayer/ui 1.0.0-alpha.7 → 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 (209) hide show
  1. package/README.md +6 -3
  2. package/lib/index.cjs.js +1 -15
  3. package/lib/index.es.js +4453 -3651
  4. package/lib/nuxt.js +2 -1
  5. package/package.json +16 -6
  6. package/src/common/icons.ts +15 -0
  7. package/src/common/utils.ts +68 -0
  8. package/src/components/alert/Alert.theme.ts +57 -0
  9. package/src/components/alert/Alert.vue +51 -127
  10. package/src/components/alert/__tests__/Alert.spec.ts +14 -0
  11. package/src/components/avatar/Avatar.theme.ts +39 -0
  12. package/src/components/avatar/Avatar.vue +58 -96
  13. package/src/components/avatar/__tests__/Avatar.spec.ts +11 -0
  14. package/src/components/badge/Badge.theme.ts +13 -0
  15. package/src/components/badge/Badge.vue +58 -65
  16. package/src/components/badge/__tests__/Badge.spec.ts +11 -0
  17. package/src/components/breadcrumbs/Breadcrumbs.theme.ts +9 -0
  18. package/src/components/breadcrumbs/Breadcrumbs.vue +34 -24
  19. package/src/components/breadcrumbs/__tests__/Breadcrumbs.spec.ts +11 -0
  20. package/src/components/button/Button.theme.ts +234 -0
  21. package/src/components/button/Button.vue +94 -356
  22. package/src/components/button/ButtonGroup.theme.ts +5 -0
  23. package/src/components/button/ButtonGroup.vue +30 -29
  24. package/src/components/button/__tests__/ Button.spec.ts +11 -0
  25. package/src/components/button/__tests__/ ButtonGroup.spec.ts +11 -0
  26. package/src/components/card/Card.theme.ts +7 -0
  27. package/src/components/card/Card.vue +18 -11
  28. package/src/components/card/__tests__/Card.spec.ts +11 -0
  29. package/src/components/checkbox/Checkbox.theme.ts +92 -0
  30. package/src/components/checkbox/Checkbox.vue +69 -156
  31. package/src/components/checkbox/__tests__/Checkbox.spec.ts +11 -0
  32. package/src/components/collapse/Collapse.theme.ts +11 -0
  33. package/src/components/collapse/Collapse.vue +99 -118
  34. package/src/components/collapse/__tests__/Collapse.spec.ts +11 -0
  35. package/src/components/container/Container.theme.ts +7 -0
  36. package/src/components/container/Container.vue +17 -9
  37. package/src/components/container/__tests__/Container.spec.ts +11 -0
  38. package/src/components/divider/Divider.theme.ts +11 -0
  39. package/src/components/divider/Divider.vue +22 -18
  40. package/src/components/divider/__tests__/Divider.spec.ts +11 -0
  41. package/src/components/drawer/Drawer.theme.ts +9 -0
  42. package/src/components/drawer/Drawer.vue +160 -177
  43. package/src/components/drawer/__tests__/Drawer.spec.ts +11 -0
  44. package/src/components/form/Form.theme.ts +7 -0
  45. package/src/components/form/Form.vue +90 -73
  46. package/src/components/form/__tests__/Form.spec.ts +11 -0
  47. package/src/components/helpers/InputError.tsx +14 -0
  48. package/src/components/icon/Icon.theme.ts +16 -0
  49. package/src/components/icon/Icon.vue +72 -88
  50. package/src/components/icon/__tests__/Icon.spec.ts +11 -0
  51. package/src/components/image/Image.theme.ts +7 -0
  52. package/src/components/image/Image.vue +22 -23
  53. package/src/components/image/__tests__/Image.spec.ts +11 -0
  54. package/src/components/index.ts +3 -3
  55. package/src/components/input/Input.theme.ts +44 -0
  56. package/src/components/input/Input.vue +97 -130
  57. package/src/components/input/__tests__/Input.spec.ts +11 -0
  58. package/src/components/link/Link.theme.ts +26 -0
  59. package/src/components/link/Link.vue +41 -66
  60. package/src/components/link/__tests__/Link.spec.ts +11 -0
  61. package/src/components/menu/Menu.theme.ts +7 -0
  62. package/src/components/menu/Menu.vue +54 -45
  63. package/src/components/menu/MenuItem.theme.ts +107 -0
  64. package/src/components/menu/MenuItem.vue +97 -199
  65. package/src/components/menu/__tests__/Menu.spec.ts +11 -0
  66. package/src/components/menu/__tests__/MenuItem.spec.ts +11 -0
  67. package/src/components/modal/Modal.theme.ts +29 -0
  68. package/src/components/modal/Modal.vue +78 -101
  69. package/src/components/modal/__tests__/Modal.spec.ts +11 -0
  70. package/src/components/notifications/Notifications.theme.ts +11 -0
  71. package/src/components/notifications/Notifications.vue +233 -247
  72. package/src/components/notifications/__tests__/Notifications.spec.ts +11 -0
  73. package/src/components/pagination/Pagination.theme.ts +27 -0
  74. package/src/components/pagination/Pagination.vue +142 -164
  75. package/src/components/pagination/PaginationItem.theme.ts +14 -0
  76. package/src/components/pagination/PaginationItem.vue +26 -33
  77. package/src/components/pagination/__tests__/Pagination.spec.ts +11 -0
  78. package/src/components/pagination/__tests__/PaginationItem.spec.ts +11 -0
  79. package/src/components/popover/Popover.theme.ts +9 -0
  80. package/src/components/popover/Popover.vue +153 -101
  81. package/src/components/popover/PopoverContainer.theme.ts +7 -0
  82. package/src/components/popover/PopoverContainer.vue +17 -9
  83. package/src/components/popover/__tests__/Popover.spec.ts +11 -0
  84. package/src/components/popover/__tests__/PopoverContainer.spec.ts +11 -0
  85. package/src/components/progress/Progress.theme.ts +26 -0
  86. package/src/components/progress/Progress.vue +29 -53
  87. package/src/components/progress/__tests__/Progress.spec.ts +11 -0
  88. package/src/components/radio/Radio.theme.ts +121 -0
  89. package/src/components/radio/Radio.vue +81 -158
  90. package/src/components/radio/__tests__/Radio.spec.ts +11 -0
  91. package/src/components/scroll/Scroll.theme.ts +7 -0
  92. package/src/components/scroll/Scroll.vue +34 -36
  93. package/src/components/scroll/__tests__/Scroll.spec.ts +11 -0
  94. package/src/components/select/Select.theme.ts +54 -0
  95. package/src/components/select/Select.vue +219 -273
  96. package/src/components/select/__tests__/Select.spec.ts +11 -0
  97. package/src/components/skeleton/Skeleton.theme.ts +7 -0
  98. package/src/components/skeleton/Skeleton.vue +17 -9
  99. package/src/components/skeleton/__tests__/Skeleton.spec.ts +11 -0
  100. package/src/components/slider/Slider.theme.ts +30 -0
  101. package/src/components/slider/Slider.vue +135 -168
  102. package/src/components/slider/__tests__/Slider.spec.ts +11 -0
  103. package/src/components/spacer/{Spacer.vue → Spacer.tsx} +3 -6
  104. package/src/components/spacer/__tests__/Spacer.spec.ts +11 -0
  105. package/src/components/spinner/Spinner.vue +10 -34
  106. package/src/components/spinner/__tests__/Spinner.spec.ts +11 -0
  107. package/src/components/tab/Tab.theme.ts +22 -0
  108. package/src/components/tab/Tab.vue +89 -93
  109. package/src/components/tab/TabGroup.theme.ts +43 -0
  110. package/src/components/tab/TabGroup.vue +94 -127
  111. package/src/components/tab/__tests__/Tab.spec.ts +11 -0
  112. package/src/components/tab/__tests__/TabGroup.spec.ts +11 -0
  113. package/src/components/table/Table.theme.ts +19 -0
  114. package/src/components/table/Table.vue +136 -147
  115. package/src/components/table/{TableBody.vue → TableBody.tsx} +3 -8
  116. package/src/components/table/TableCell.theme.ts +27 -0
  117. package/src/components/table/TableCell.vue +30 -58
  118. package/src/components/table/TableHead.tsx +14 -0
  119. package/src/components/table/TableHeader.vue +18 -20
  120. package/src/components/table/TableRow.vue +23 -20
  121. package/src/components/table/__tests__/Table.spec.ts +11 -0
  122. package/src/components/tag/Tag.theme.ts +32 -0
  123. package/src/components/tag/Tag.vue +40 -68
  124. package/src/components/tag/__tests__/Tag.spec.ts +11 -0
  125. package/src/components/textarea/Textarea.theme.ts +62 -0
  126. package/src/components/textarea/Textarea.vue +100 -115
  127. package/src/components/textarea/__tests__/Textarea.spec.ts +11 -0
  128. package/src/components/toggle/Toggle.theme.ts +51 -0
  129. package/src/components/toggle/Toggle.vue +51 -81
  130. package/src/components/toggle/__tests__/Toggle.spec.ts +11 -0
  131. package/src/components/tooltip/Tooltip.theme.ts +51 -0
  132. package/src/components/tooltip/Tooltip.vue +9 -14
  133. package/src/components/tooltip/__tests__/Tooltip.spec.ts +11 -0
  134. package/src/composables/colors-utils.ts +68 -68
  135. package/src/composables/colors.ts +18 -6
  136. package/src/composables/common.ts +1 -0
  137. package/src/composables/css.ts +7 -2
  138. package/src/composables/index.ts +1 -1
  139. package/src/composables/inputtable.ts +1 -1
  140. package/src/composables/interactive.ts +8 -4
  141. package/src/composables/keys.ts +1 -0
  142. package/src/composables/notifications.ts +10 -0
  143. package/src/composables/theme.ts +88 -0
  144. package/src/create.ts +9 -4
  145. package/src/exports/nuxt.js +2 -1
  146. package/src/version.ts +1 -1
  147. package/volar.d.ts +1 -0
  148. package/lib/components/alert/Alert.vue.d.ts +0 -42
  149. package/lib/components/avatar/Avatar.vue.d.ts +0 -49
  150. package/lib/components/badge/Badge.vue.d.ts +0 -75
  151. package/lib/components/breadcrumbs/Breadcrumbs.vue.d.ts +0 -30
  152. package/lib/components/button/Button.vue.d.ts +0 -87
  153. package/lib/components/button/ButtonGroup.vue.d.ts +0 -49
  154. package/lib/components/card/Card.vue.d.ts +0 -17
  155. package/lib/components/checkbox/Checkbox.vue.d.ts +0 -81
  156. package/lib/components/collapse/Collapse.vue.d.ts +0 -47
  157. package/lib/components/container/Container.vue.d.ts +0 -14
  158. package/lib/components/divider/Divider.vue.d.ts +0 -10
  159. package/lib/components/drawer/Drawer.vue.d.ts +0 -73
  160. package/lib/components/form/Form.vue.d.ts +0 -46
  161. package/lib/components/icon/Icon.vue.d.ts +0 -40
  162. package/lib/components/image/Image.vue.d.ts +0 -8
  163. package/lib/components/index.d.ts +0 -45
  164. package/lib/components/input/Input.vue.d.ts +0 -117
  165. package/lib/components/link/Link.vue.d.ts +0 -36
  166. package/lib/components/menu/Menu.vue.d.ts +0 -62
  167. package/lib/components/menu/MenuItem.vue.d.ts +0 -114
  168. package/lib/components/modal/Modal.vue.d.ts +0 -34
  169. package/lib/components/notifications/Notifications.vue.d.ts +0 -104
  170. package/lib/components/pagination/Pagination.vue.d.ts +0 -58
  171. package/lib/components/pagination/PaginationItem.vue.d.ts +0 -32
  172. package/lib/components/popover/Popover.vue.d.ts +0 -64
  173. package/lib/components/popover/PopoverContainer.vue.d.ts +0 -14
  174. package/lib/components/progress/Progress.vue.d.ts +0 -42
  175. package/lib/components/radio/Radio.vue.d.ts +0 -79
  176. package/lib/components/scroll/Scroll.vue.d.ts +0 -29
  177. package/lib/components/select/Select.vue.d.ts +0 -100
  178. package/lib/components/skeleton/Skeleton.vue.d.ts +0 -14
  179. package/lib/components/slider/Slider.vue.d.ts +0 -96
  180. package/lib/components/spacer/Spacer.vue.d.ts +0 -2
  181. package/lib/components/spinner/Spinner.vue.d.ts +0 -16
  182. package/lib/components/tab/Tab.vue.d.ts +0 -52
  183. package/lib/components/tab/TabGroup.vue.d.ts +0 -61
  184. package/lib/components/table/Table.vue.d.ts +0 -82
  185. package/lib/components/table/TableBody.vue.d.ts +0 -2
  186. package/lib/components/table/TableCell.vue.d.ts +0 -33
  187. package/lib/components/table/TableHead.vue.d.ts +0 -2
  188. package/lib/components/table/TableHeader.vue.d.ts +0 -33
  189. package/lib/components/table/TableRow.vue.d.ts +0 -23
  190. package/lib/components/tag/Tag.vue.d.ts +0 -45
  191. package/lib/components/textarea/Textarea.vue.d.ts +0 -106
  192. package/lib/components/toggle/Toggle.vue.d.ts +0 -79
  193. package/lib/components/tooltip/Tooltip.vue.d.ts +0 -2
  194. package/lib/composables/colors-utils.d.ts +0 -8
  195. package/lib/composables/colors.d.ts +0 -26
  196. package/lib/composables/common.d.ts +0 -14
  197. package/lib/composables/css.d.ts +0 -5
  198. package/lib/composables/index.d.ts +0 -7
  199. package/lib/composables/inputtable.d.ts +0 -37
  200. package/lib/composables/interactive.d.ts +0 -10
  201. package/lib/composables/keys.d.ts +0 -7
  202. package/lib/composables/notification.d.ts +0 -1
  203. package/lib/create.d.ts +0 -12
  204. package/lib/index.d.ts +0 -6
  205. package/lib/install.d.ts +0 -4
  206. package/lib/style.css +0 -1
  207. package/lib/version.d.ts +0 -2
  208. package/src/components/table/TableHead.vue +0 -15
  209. package/src/composables/notification.ts +0 -10
@@ -1,18 +1,25 @@
1
1
  <script lang="ts">
2
- import { computed, defineComponent, ref, watch, type PropType } from 'vue'
3
- import { useCSS } from '../../composables/css'
2
+ export default { name: 'XSelect' }
3
+ </script>
4
+
5
+ <script setup lang="ts">
6
+ import { computed, ref, watch, type PropType } from 'vue'
7
+ import { useEventListener } from '@vueuse/core'
4
8
  import { useCommon } from '../../composables/common'
5
- import { useColors } from '../../composables/colors'
6
9
  import { useInputtable } from '../../composables/inputtable'
7
10
  import { useInteractive } from '../../composables/interactive'
8
-
9
- import { useEventListener } from '@vueuse/core'
11
+ import { useTheme } from '../../composables/theme'
12
+ import { checkIcon, chevronDownIcon } from '../../common/icons'
10
13
 
11
14
  import XTag from '../../components/tag/Tag.vue'
15
+ import XIcon from '../../components/icon/Icon.vue'
12
16
  import XMenuItem from '../../components/menu/MenuItem.vue'
13
17
  import XSpinner from '../../components/spinner/Spinner.vue'
14
18
  import XPopover from '../../components/popover/Popover.vue'
15
19
  import XPopoverContainer from '../../components/popover/PopoverContainer.vue'
20
+ import XInputError from '../helpers/InputError'
21
+
22
+ import theme from './Select.theme'
16
23
 
17
24
  export type SelectOption = {
18
25
  value: number | string,
@@ -20,292 +27,242 @@ export type SelectOption = {
20
27
  label: string
21
28
  }
22
29
 
23
- export default defineComponent({
24
- name: 'XSelect',
30
+ const props = defineProps({
31
+ ...useCommon.props(),
32
+ ...useInteractive.props(),
33
+ ...useInputtable.props(),
34
+ placeholder: String,
35
+ options: Array as PropType<Array<SelectOption>>,
36
+ multiple: Boolean,
37
+ label: String,
38
+ helper: String,
39
+ flat: Boolean,
40
+ })
25
41
 
26
- components: {
27
- XTag,
28
- XMenuItem,
29
- XSpinner,
30
- XPopover,
31
- XPopoverContainer,
32
- },
42
+ const emit = defineEmits(useInputtable.emits())
33
43
 
34
- validators: {
35
- ...useCommon.validators(),
36
- },
44
+ const elRef = ref<HTMLElement | null>(null)
45
+ const labelRef = ref<HTMLElement | null>(null)
46
+ const itemsRef = ref<typeof XMenuItem[] | null>(null)
47
+ const popoverRef = ref<typeof XPopover | null>(null)
48
+ const selectedIndex = ref<number | undefined>()
49
+
50
+ const selected = computed<any | any[]>({
51
+ get() {
52
+ if (props.multiple) {
53
+ if (!props.modelValue) return []
54
+ if (Array.isArray(props.modelValue)) return props.modelValue
55
+ else return [props.modelValue]
56
+ }
37
57
 
38
- props: {
39
- ...useCommon.props(),
40
- ...useInteractive.props(),
41
- ...useInputtable.props(),
42
- placeholder: String,
43
- options: Array as PropType<Array<SelectOption>>,
44
- multiple: Boolean,
45
- label: String,
46
- helper: String,
47
- flat: Boolean,
58
+ return props.modelValue
59
+ },
60
+ set(value: string | number | []) {
61
+ emit('update:modelValue', value)
48
62
  },
63
+ })
49
64
 
50
- emits: useInputtable.emits(),
51
-
52
- setup(props, { emit }) {
53
- const elRef = ref<HTMLElement>()
54
- const labelRef = ref()
55
- const itemsRef = ref()
56
- const popoverRef = ref()
57
- const selectedIndex = ref<number | undefined>()
58
-
59
- const interactive = useInteractive(elRef)
60
-
61
- const checkIcon = '<path d="M5 13l4 4L19 7" />'
62
-
63
- const selected = computed({
64
- get(): any {
65
- if (props.multiple) {
66
- if (!props.modelValue) return []
67
- if (Array.isArray(props.modelValue)) return props.modelValue
68
- else return [props.modelValue]
69
- }
70
-
71
- return props.modelValue
72
- },
73
- set(value: string | number | []) {
74
- emit('update:modelValue', value)
75
- },
76
- })
77
-
78
- const internalOptions = computed(() => {
79
- if (!props.options || props.options.length === 0) return []
80
-
81
- return props.options.map((option) => {
82
- let isActive = false
83
-
84
- if (props.multiple && Array.isArray(selected.value)) {
85
- // @ts-ignore
86
- isActive = selected.value.includes(option.value)
87
- } else {
88
- isActive = option.value === selected.value
89
- }
90
-
91
- return {
92
- value: option.value,
93
- label: option.label,
94
- active: isActive,
95
- disabled: option.disabled,
96
- iconRight: isActive ? checkIcon : undefined,
97
- onClick: () => handleOptionClick(option.value),
98
- }
99
- })
100
- })
101
-
102
- const labelClasses = computed(() => {
103
- if (props.size === 'xs') return 'text-xs'
104
- else if (props.size === 'sm') return 'text-sm'
105
- else if (props.size === 'lg') return 'text-lg'
106
- else if (props.size === 'xl') return 'text-xl'
107
-
108
- return ''
109
- })
110
-
111
- const sizeClasses = computed(() => {
112
- if (props.size === 'xs') return 'px-2 py-1 text-xs'
113
- else if (props.size === 'sm') return 'px-2 py-2 text-sm'
114
- else if (props.size === 'lg') return 'px-4 py-3 text-lg'
115
- else if (props.size === 'xl') return 'px-5 py-4 text-xl'
116
-
117
- return 'px-3 py-2'
118
- })
119
-
120
- const css = useCSS('select')
121
- const colors = useColors()
122
- const color = colors.getPalette('primary')
123
- const style = css.get('border', color[500])
124
-
125
- const availableOptions = computed(() => props.options?.filter((option) => !option.disabled))
126
-
127
- watch(() => popoverRef.value?.isOpen, () => {
128
- if (popoverRef.value?.isOpen && (props.multiple || typeof selectedIndex.value === 'undefined'))
129
- findSelectableIndex(-1)
130
- })
131
-
132
- watch(selectedIndex, (index) => {
133
- if (typeof index !== 'undefined') itemsRef.value[index].$el.scrollIntoView({ block: 'nearest', inline: 'nearest' })
134
- })
135
-
136
- function findSelectableIndex(start: number | undefined, direction = 'down') {
137
- if (!availableOptions.value || availableOptions.value.length === 0) {
138
- selectedIndex.value = undefined
139
-
140
- return
141
- }
65
+ const internalOptions = computed(() => {
66
+ if (!props.options || props.options.length === 0) return []
142
67
 
143
- if (typeof start === 'undefined') {
144
- start = direction === 'down' ? -1 : 1
145
- }
68
+ return props.options.map((option) => {
69
+ let isActive = false
146
70
 
147
- if (direction === 'down') {
148
- let next = start + 1
149
-
150
- if (next > internalOptions.value.length - 1) next = 0
151
- while (internalOptions.value[next].disabled) {
152
- if (++next > internalOptions.value.length - 1) next = 0
153
- }
154
- selectedIndex.value = next
155
- } else {
156
- let next = start - 1
157
-
158
- if (next < 0) next = internalOptions.value.length - 1
159
- while (internalOptions.value[next].disabled) {
160
- if (--next < 0) next = internalOptions.value.length - 1
161
- }
162
- selectedIndex.value = next
163
- }
71
+ if (props.multiple && Array.isArray(selected.value)) {
72
+ isActive = selected.value.includes(option.value)
73
+ } else {
74
+ isActive = option.value === selected.value
164
75
  }
165
76
 
166
- useEventListener(labelRef, 'keydown', handleKeydown)
167
-
168
- function handleKeydown(e: KeyboardEvent) {
169
- if (internalOptions.value.length === 0) return
170
-
171
- if (e.code === 'ArrowDown') {
172
- e.preventDefault()
173
- if (!popoverRef.value.isOpen) {
174
- popoverRef.value.open()
175
-
176
- return
177
- }
178
- findSelectableIndex(selectedIndex.value, 'down')
179
- } else if (e.code === 'ArrowUp') {
180
- e.preventDefault()
181
- if (!popoverRef.value.isOpen) return
182
- findSelectableIndex(selectedIndex.value, 'up')
183
- } else if (e.code === 'Enter' || e.code === 'Space') {
184
- e.preventDefault()
185
- e.stopPropagation()
186
- if (!popoverRef.value.isOpen) {
187
- popoverRef.value.open()
188
-
189
- return
190
- }
191
- if (typeof selectedIndex.value !== 'undefined') {
192
- handleOptionClick(internalOptions.value[selectedIndex.value].value)
193
- if (!props.multiple) popoverRef.value.close()
194
- }
195
- } else if (e.code === 'Escape') {
196
- e.preventDefault()
197
- e.stopPropagation()
198
- popoverRef.value.close()
199
- } else if (e.code === 'Tab') {
200
- popoverRef.value.close()
201
- }
77
+ return {
78
+ value: option.value,
79
+ label: option.label,
80
+ active: isActive,
81
+ disabled: option.disabled,
82
+ iconRight: isActive ? checkIcon : undefined,
83
+ onClick: () => handleOptionClick(option.value),
202
84
  }
85
+ })
86
+ })
203
87
 
204
- function handleOptionClick(value: string | number) {
205
- const option = props.options?.find((i) => i.value === value)
206
-
207
- if (!option || option.disabled) return
208
-
209
- if (props.multiple) {
210
- if (Array.isArray(selected.value)) {
211
- // @ts-ignore
212
- const index = selected.value.indexOf(value)
213
-
214
- if (index !== -1) selected.value.splice(index, 1)
215
- else {
216
- // @ts-ignore
217
- selected.value.push(value)
218
- emit('update:modelValue', selected.value)
219
- }
220
- } else {
221
- // @ts-ignore
222
- selected.value = [value]
223
- }
224
- } else {
225
- selected.value = value
226
- }
88
+ const availableOptions = computed(() => props.options?.filter((option) => !option.disabled))
89
+
90
+ watch(() => popoverRef.value?.isOpen, () => {
91
+ if (popoverRef.value?.isOpen && (props.multiple || typeof selectedIndex.value === 'undefined'))
92
+ findSelectableIndex(-1)
93
+ })
94
+
95
+ watch(selectedIndex, (index) => {
96
+ if (typeof index !== 'undefined' && itemsRef.value) itemsRef.value[index].$el.scrollIntoView({ block: 'nearest', inline: 'nearest' })
97
+ })
98
+
99
+ function findSelectableIndex(start: number | undefined, direction = 'down') {
100
+ if (!availableOptions.value || availableOptions.value.length === 0) {
101
+ selectedIndex.value = undefined
102
+
103
+ return
104
+ }
105
+
106
+ if (typeof start === 'undefined') {
107
+ start = direction === 'down' ? -1 : 1
108
+ }
109
+
110
+ if (direction === 'down') {
111
+ let next = start + 1
112
+
113
+ if (next > internalOptions.value.length - 1) next = 0
114
+ while (internalOptions.value[next].disabled) {
115
+ if (++next > internalOptions.value.length - 1) next = 0
116
+ }
117
+ selectedIndex.value = next
118
+ } else {
119
+ let next = start - 1
120
+
121
+ if (next < 0) next = internalOptions.value.length - 1
122
+ while (internalOptions.value[next].disabled) {
123
+ if (--next < 0) next = internalOptions.value.length - 1
227
124
  }
125
+ selectedIndex.value = next
126
+ }
127
+ }
128
+
129
+ useEventListener(labelRef, 'keydown', handleKeydown)
228
130
 
229
- function isEmpty(value: string | number | []) {
230
- if (typeof value === 'undefined' || value === null) return true
231
- if (value === '') return true
232
- if (Array.isArray(value) && value.length === 0) return true
233
- if (!Array.isArray(value) && typeof value === 'object' && Object.keys(value).length === 0) return true
131
+ function handleKeydown(e: KeyboardEvent) {
132
+ if (internalOptions.value.length === 0) return
234
133
 
235
- return false
134
+ if (e.code === 'ArrowDown') {
135
+ e.preventDefault()
136
+ if (!popoverRef.value?.isOpen) {
137
+ popoverRef.value?.open()
138
+
139
+ return
140
+ }
141
+ findSelectableIndex(selectedIndex.value, 'down')
142
+ } else if (e.code === 'ArrowUp') {
143
+ e.preventDefault()
144
+ if (!popoverRef.value?.isOpen) return
145
+ findSelectableIndex(selectedIndex.value, 'up')
146
+ } else if (e.code === 'Enter' || e.code === 'Space') {
147
+ e.preventDefault()
148
+ e.stopPropagation()
149
+ if (!popoverRef.value?.isOpen) {
150
+ popoverRef.value?.open()
151
+
152
+ return
236
153
  }
154
+ if (typeof selectedIndex.value !== 'undefined') {
155
+ handleOptionClick(internalOptions.value[selectedIndex.value].value)
156
+ if (!props.multiple) popoverRef.value?.close()
157
+ }
158
+ } else if (e.code === 'Escape') {
159
+ e.preventDefault()
160
+ e.stopPropagation()
161
+ popoverRef.value?.close()
162
+ } else if (e.code === 'Tab') {
163
+ popoverRef.value?.close()
164
+ }
165
+ }
237
166
 
238
- function handleRemove(e: Event, value: string) {
239
- e.stopPropagation()
167
+ function handleOptionClick(value: string | number) {
168
+ const option = props.options?.find((i) => i.value === value)
240
169
 
241
- // find value in selected and remove it
242
- // @ts-ignore
170
+ if (!option || option.disabled) return
171
+
172
+ if (props.multiple) {
173
+ if (Array.isArray(selected.value)) {
243
174
  const index = selected.value.indexOf(value)
244
175
 
245
- if (index !== -1) {
246
- // @ts-ignore
247
- selected.value.splice(index, 1)
176
+ if (index !== -1) selected.value.splice(index, 1)
177
+ else {
178
+ selected.value.push(value)
248
179
  emit('update:modelValue', selected.value)
249
180
  }
181
+ } else {
182
+ selected.value = [value]
250
183
  }
184
+ } else {
185
+ selected.value = value
186
+ }
187
+ }
251
188
 
252
- function getLabel(value: string | number | []) {
253
- const option = props.options?.find((i) => i.value === value)
189
+ function isEmpty(value: string | number | []) {
190
+ if (typeof value === 'undefined' || value === null) return true
191
+ if (value === '') return true
192
+ if (Array.isArray(value) && value.length === 0) return true
193
+ if (!Array.isArray(value) && typeof value === 'object' && Object.keys(value).length === 0) return true
254
194
 
255
- if (option) return option.label
195
+ return false
196
+ }
256
197
 
257
- return ''
258
- }
198
+ function handleRemove(e: Event, value: string) {
199
+ e.stopPropagation()
259
200
 
260
- return {
261
- ...interactive,
262
- ...useInputtable(props, { focus: interactive.focus, emit, withListeners: false }),
263
- elRef,
264
- labelRef,
265
- itemsRef,
266
- popoverRef,
267
- selected,
268
- selectedIndex,
269
- internalOptions,
270
- labelClasses,
271
- sizeClasses,
272
- style,
273
- isEmpty,
274
- getLabel,
275
- handleRemove,
276
- }
277
- },
278
- })
201
+ // find value in selected and remove it
202
+ const index = selected.value.indexOf(value)
203
+
204
+ if (index !== -1) {
205
+ selected.value.splice(index, 1)
206
+ emit('update:modelValue', selected.value)
207
+ }
208
+ }
209
+
210
+ function getLabel(value: string | number | []) {
211
+ const option = props.options?.find((i) => i.value === value)
212
+
213
+ if (option) return option.label
214
+
215
+ return ''
216
+ }
217
+
218
+ const { focus, blur } = useInteractive(elRef)
219
+
220
+ const {
221
+ errorInternal,
222
+ inputListeners,
223
+ reset,
224
+ validate,
225
+ setError,
226
+ isInsideForm,
227
+ } = useInputtable(props, { focus, emit, withListeners: false })
228
+
229
+ const { styles, classes, className } = useTheme('select', theme, props, { errorInternal })
230
+
231
+ defineExpose({ focus, blur, reset, validate, setError })
279
232
  </script>
280
233
 
281
234
  <template>
282
235
  <label
283
236
  ref="labelRef"
284
237
  tabindex="0"
285
- class="group relative inline-block align-bottom text-left focus:outline-none"
286
- :class="[{ 'mb-3': isInsideForm }]"
238
+ class="group relative"
239
+ :style="styles"
240
+ :class="[
241
+ className,
242
+ classes.wrapper,
243
+ { 'mb-3': isInsideForm }
244
+ ]"
287
245
  >
288
246
  <p
289
247
  v-if="label"
290
- class="font-medium text-gray-800 dark:text-gray-200 mb-1"
291
- :class="labelClasses"
248
+ :class="classes.label"
292
249
  v-text="label"
293
250
  ></p>
294
251
  <div class="relative">
295
- <x-popover ref="popoverRef" block :disabled="disabled || loading" :dismiss-on-click="!multiple">
252
+ <x-popover
253
+ ref="popoverRef"
254
+ block
255
+ :disabled="disabled || loading"
256
+ :dismiss-on-click="!multiple"
257
+ align="left"
258
+ >
296
259
  <div
297
- class="w-full border border-gray-300 hover:border-gray-400 dark:border-gray-700 pr-8 transition-colors duration-150 ease-in-out rounded-md shadow-sm
298
- group-focus:border-[color:var(--x-select-border)]
299
- "
300
- :style="style"
301
260
  :class="[
302
- sizeClasses,
303
- disabled
304
- ? 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed'
305
- : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200',
261
+ classes.box,
306
262
  {
307
263
  // error
308
- 'border-red-500 focus:border-red-500 dark:focus:border-red-500': errorInternal,
264
+ 'border-red-500 dark:border-red-400 group-focus:outline-red-500': errorInternal,
265
+ 'group-focus:outline-[color:var(--x-select-border)]': !disabled && !errorInternal
309
266
  },
310
267
  ]"
311
268
  >
@@ -335,7 +292,7 @@ export default defineComponent({
335
292
  </div>
336
293
 
337
294
  <template #content>
338
- <x-popover-container class="py-1 max-h-72 overflow-scroll">
295
+ <x-popover-container :class="classes.content">
339
296
  <template v-if="internalOptions.length > 0">
340
297
  <x-menu-item
341
298
  v-for="(item, index) in internalOptions"
@@ -377,32 +334,21 @@ export default defineComponent({
377
334
  </option>
378
335
  </select>
379
336
 
380
- <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2">
337
+ <div :class="classes.iconWrapper">
381
338
  <x-spinner v-if="loading" :size="size" />
382
- <svg
383
- v-else
384
- class="stroke-2"
385
- :class="[
386
- disabled ? 'text-gray-600 dark:text-gray-500': 'text-gray-500 dark:text-gray-400',
387
- {
388
- 'h-3 w-3': size === 'sm' || size === 'xs',
389
- 'h-5 w-5': !size || !['xs', 'sm', 'lg', 'xl'].includes(size),
390
- 'h-6 w-6': size === 'lg',
391
- 'h-7 w-7': size === 'xl',
392
- }
393
- ]"
394
- viewBox="0 0 24 24"
395
- stroke="currentColor"
396
- stroke-linejoin="round"
397
- stroke-linecap="round"
398
- fill="none"
399
- >
400
- <path d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
401
- </svg>
339
+ <slot v-else name="icon">
340
+ <x-icon
341
+ :icon="chevronDownIcon"
342
+ :class="[
343
+ classes.icon,
344
+ disabled ? 'text-gray-600 dark:text-gray-500': 'text-gray-500 dark:text-gray-400'
345
+ ]"
346
+ />
347
+ </slot>
348
+
402
349
  </div>
403
350
  </div>
404
351
 
405
- <p v-if="errorInternal" class="text-sm text-red-500 mt-1" v-text="errorInternal"></p>
406
- <p v-else-if="helper" class="text-sm text-gray-500 mt-1" v-text="helper"></p>
352
+ <x-input-error :error="errorInternal" :helper="helper"/>
407
353
  </label>
408
354
  </template>
@@ -0,0 +1,11 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Select from '../Select.vue'
4
+
5
+ describe('Select', () => {
6
+ it('renders without errors', () => {
7
+ const wrapper = mount(Select)
8
+
9
+ expect(wrapper.vm).toBeTruthy()
10
+ })
11
+ })
@@ -0,0 +1,7 @@
1
+ import type { ThemeParams } from '../../composables/theme'
2
+
3
+ export default {
4
+ classes: {
5
+ wrapper: 'animate-pulse bg-gray-300 dark:bg-gray-600 rounded-md',
6
+ },
7
+ }
@@ -1,22 +1,30 @@
1
1
  <script lang="ts">
2
- import { defineComponent } from 'vue'
2
+ export default { name: 'XSkeleton' }
3
+ </script>
4
+
5
+ <script setup lang="ts">
6
+ import { useTheme } from '../../composables/theme'
3
7
 
4
- export default defineComponent({
5
- name: 'XSkeleton',
8
+ import theme from './Skeleton.theme'
6
9
 
7
- props: {
8
- tag: {
9
- type: String,
10
- default: 'div',
11
- },
10
+ const props = defineProps({
11
+ tag: {
12
+ type: String,
13
+ default: 'div',
12
14
  },
13
15
  })
16
+
17
+ const { styles, classes, className } = useTheme('skeleton', theme, props)
14
18
  </script>
15
19
 
16
20
  <template>
17
21
  <component
18
22
  :is="tag"
19
- class="animate-pulse bg-gray-300 dark:bg-gray-600 rounded-md"
23
+ :style="styles"
24
+ :class="[
25
+ className,
26
+ classes.wrapper
27
+ ]"
20
28
  >
21
29
  &#8203;
22
30
  </component>
@@ -0,0 +1,11 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import Skeleton from '../Skeleton.vue'
4
+
5
+ describe('Skeleton', () => {
6
+ it('renders without errors', () => {
7
+ const wrapper = mount(Skeleton)
8
+
9
+ expect(wrapper.vm).toBeTruthy()
10
+ })
11
+ })
@@ -0,0 +1,30 @@
1
+ import type { ThemeParams } from '../../composables/theme'
2
+
3
+ export default {
4
+ classes: {
5
+ wrapper: 'inline-block align-bottom text-left focus:outline-none',
6
+
7
+ label: ({ props }: ThemeParams) => {
8
+ let c = 'font-medium text-gray-800 dark:text-gray-200 mb-1'
9
+
10
+ if (props.size === 'xs') c += ' text-xs'
11
+ else if (props.size === 'sm') c += ' text-sm'
12
+ else if (props.size === 'lg') c += ' text-lg'
13
+ else if (props.size === 'xl') c += ' text-xl'
14
+
15
+ return c
16
+ },
17
+
18
+ drag: 'w-[20px] h-[20px] -mt-[13px] -ml-[10px] rounded-full bg-white border shadow-sm',
19
+ },
20
+
21
+ styles: ({ props, colors, css }: ThemeParams) => {
22
+ const primary = colors.getPalette('primary')
23
+ const color = colors.getPalette(props.color)
24
+
25
+ return css.variables({
26
+ bg: color[500],
27
+ border: primary[500],
28
+ })
29
+ },
30
+ }