@gameap/ui 1.0.0 → 1.1.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.
@@ -11,7 +11,7 @@
11
11
  class="ml-[2px] text-xs h-full inline-flex items-center px-2 py-1 bg-red-500 hover:bg-red-600 text-white rounded-e cursor-pointer"
12
12
  @click="onClickDelete(item.id)"
13
13
  >
14
- <i class="fa-solid fa-trash"></i>&nbsp;
14
+ <GIcon name="delete" />&nbsp;
15
15
  </div>
16
16
  </div>
17
17
  </div>
@@ -19,8 +19,10 @@
19
19
  </template>
20
20
 
21
21
  <script setup>
22
+ import GIcon from './GIcon.vue'
23
+
22
24
  const props = defineProps({
23
- items: [],
25
+ items: Array,
24
26
  clickCallback: Function,
25
27
  deleteCallback: Function,
26
28
  });
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <component
3
+ v-if="isComponent"
4
+ :is="resolvedIcon"
5
+ :class="props.class"
6
+ :style="sizeStyle"
7
+ />
8
+ <i
9
+ v-else
10
+ :class="[resolvedIcon, sizeClass, props.class]"
11
+ />
12
+ </template>
13
+
14
+ <script setup>
15
+ import { computed } from 'vue'
16
+ import { getIcon, hasIcon } from '../icons/registry.js'
17
+
18
+ const props = defineProps({
19
+ name: {
20
+ type: String,
21
+ required: true
22
+ },
23
+ size: {
24
+ type: String,
25
+ default: 'md',
26
+ validator: (value) => ['sm', 'md', 'lg', 'xl'].includes(value)
27
+ },
28
+ class: {
29
+ type: String,
30
+ default: ''
31
+ }
32
+ })
33
+
34
+ const resolvedIcon = computed(() => {
35
+ if (!hasIcon(props.name)) {
36
+ console.warn(`GIcon: Unknown icon "${props.name}"`)
37
+ return 'fa-solid fa-circle-question'
38
+ }
39
+ return getIcon(props.name)
40
+ })
41
+
42
+ const isComponent = computed(() => {
43
+ return typeof resolvedIcon.value === 'object' || typeof resolvedIcon.value === 'function'
44
+ })
45
+
46
+ const fontAwesomeSizeMap = {
47
+ sm: 'fa-sm',
48
+ md: '',
49
+ lg: 'fa-lg',
50
+ xl: 'fa-2x'
51
+ }
52
+
53
+ const componentSizeMap = {
54
+ sm: '0.875em',
55
+ md: '1em',
56
+ lg: '1.25em',
57
+ xl: '2em'
58
+ }
59
+
60
+ const sizeClass = computed(() => fontAwesomeSizeMap[props.size] || '')
61
+
62
+ const sizeStyle = computed(() => ({
63
+ width: componentSizeMap[props.size],
64
+ height: componentSizeMap[props.size]
65
+ }))
66
+ </script>
@@ -0,0 +1,67 @@
1
+ <script setup>
2
+ import { ref, provide, onMounted, onUnmounted } from 'vue'
3
+
4
+ const props = defineProps({
5
+ as: { type: String, default: 'div' }
6
+ })
7
+
8
+ const isOpen = ref(false)
9
+ const menuRef = ref(null)
10
+ const activeIndex = ref(-1)
11
+ const items = ref([])
12
+
13
+ const toggle = () => {
14
+ isOpen.value = !isOpen.value
15
+ if (!isOpen.value) activeIndex.value = -1
16
+ }
17
+
18
+ const close = () => {
19
+ isOpen.value = false
20
+ activeIndex.value = -1
21
+ }
22
+
23
+ const registerItem = (id) => {
24
+ items.value.push(id)
25
+ return items.value.length - 1
26
+ }
27
+
28
+ const unregisterItem = (id) => {
29
+ const idx = items.value.indexOf(id)
30
+ if (idx > -1) items.value.splice(idx, 1)
31
+ }
32
+
33
+ const setActiveIndex = (index) => {
34
+ activeIndex.value = index
35
+ }
36
+
37
+ const handleClickOutside = (event) => {
38
+ if (menuRef.value && !menuRef.value.contains(event.target)) {
39
+ close()
40
+ }
41
+ }
42
+
43
+ onMounted(() => {
44
+ document.addEventListener('click', handleClickOutside, true)
45
+ })
46
+
47
+ onUnmounted(() => {
48
+ document.removeEventListener('click', handleClickOutside, true)
49
+ })
50
+
51
+ provide('menu', {
52
+ isOpen,
53
+ toggle,
54
+ close,
55
+ activeIndex,
56
+ items,
57
+ registerItem,
58
+ unregisterItem,
59
+ setActiveIndex
60
+ })
61
+ </script>
62
+
63
+ <template>
64
+ <component :is="as" ref="menuRef">
65
+ <slot />
66
+ </component>
67
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup>
2
+ import { inject } from 'vue'
3
+
4
+ const { isOpen, toggle } = inject('menu')
5
+
6
+ const handleClick = () => {
7
+ toggle()
8
+ }
9
+
10
+ const handleKeydown = (event) => {
11
+ if (['Enter', ' ', 'ArrowDown'].includes(event.key)) {
12
+ event.preventDefault()
13
+ toggle()
14
+ }
15
+ }
16
+ </script>
17
+
18
+ <template>
19
+ <button
20
+ type="button"
21
+ :aria-expanded="isOpen"
22
+ aria-haspopup="true"
23
+ @click="handleClick"
24
+ @keydown="handleKeydown"
25
+ >
26
+ <slot />
27
+ </button>
28
+ </template>
@@ -0,0 +1,36 @@
1
+ <script setup>
2
+ import { inject, ref, computed, onMounted, onUnmounted } from 'vue'
3
+
4
+ const { close, activeIndex, registerItem, unregisterItem, setActiveIndex } = inject('menu')
5
+
6
+ const itemId = Symbol()
7
+ const index = ref(-1)
8
+
9
+ onMounted(() => {
10
+ index.value = registerItem(itemId)
11
+ })
12
+
13
+ onUnmounted(() => {
14
+ unregisterItem(itemId)
15
+ })
16
+
17
+ const active = computed(() => activeIndex.value === index.value)
18
+
19
+ const handleMouseEnter = () => {
20
+ setActiveIndex(index.value)
21
+ }
22
+
23
+ const handleMouseLeave = () => {
24
+ setActiveIndex(-1)
25
+ }
26
+ </script>
27
+
28
+ <template>
29
+ <div
30
+ role="menuitem"
31
+ @mouseenter="handleMouseEnter"
32
+ @mouseleave="handleMouseLeave"
33
+ >
34
+ <slot :active="active" :close="close" />
35
+ </div>
36
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup>
2
+ import { inject, computed } from 'vue'
3
+
4
+ const props = defineProps({
5
+ unmount: { type: Boolean, default: true }
6
+ })
7
+
8
+ const { isOpen, close } = inject('menu')
9
+
10
+ const handleKeydown = (event) => {
11
+ if (event.key === 'Escape') {
12
+ event.preventDefault()
13
+ close()
14
+ }
15
+ }
16
+
17
+ const shouldRender = computed(() => {
18
+ if (props.unmount) return isOpen.value
19
+ return true
20
+ })
21
+
22
+ const shouldShow = computed(() => isOpen.value)
23
+ </script>
24
+
25
+ <template>
26
+ <div
27
+ v-if="shouldRender"
28
+ v-show="shouldShow"
29
+ role="menu"
30
+ tabindex="-1"
31
+ @keydown="handleKeydown"
32
+ >
33
+ <slot />
34
+ </div>
35
+ </template>
@@ -1,24 +1,22 @@
1
1
  <template>
2
2
  <div class="flex justify-center">
3
- <TransitionRoot
4
- :show="showTransition"
5
- enter="transition-opacity duration-[2000ms]"
6
- enter-from="opacity-0"
7
- enter-to="opacity-100"
8
- leave="transition-opacity duration-150"
9
- leave-from="opacity-100"
10
- leave-to="opacity-0"
3
+ <Transition
4
+ enter-active-class="transition-opacity duration-[2000ms]"
5
+ enter-from-class="opacity-0"
6
+ enter-to-class="opacity-100"
7
+ leave-active-class="transition-opacity duration-150"
8
+ leave-from-class="opacity-100"
9
+ leave-to-class="opacity-0"
11
10
  >
12
- <div class="fa-3x">
11
+ <div v-if="showTransition" class="fa-3x">
13
12
  <i class="fa-solid fa-gear fa-spin"></i>
14
13
  </div>
15
- </TransitionRoot>
14
+ </Transition>
16
15
  </div>
17
16
  </template>
18
17
 
19
18
  <script setup>
20
19
  import { onMounted, ref } from "vue"
21
- import { TransitionRoot } from '@headlessui/vue'
22
20
 
23
21
  const showTransition = ref(false)
24
22
 
@@ -27,6 +25,4 @@ onMounted(() => {
27
25
  showTransition.value = true
28
26
  }, 10)
29
27
  })
30
-
31
-
32
28
  </script>
package/index.js CHANGED
@@ -3,6 +3,25 @@ export { default as GDeletableList } from './components/GDeletableList.vue'
3
3
  export { default as GStatusBadge } from './components/GStatusBadge.vue'
4
4
  export { default as Loading } from './components/Loading.vue'
5
5
  export { default as Progressbar } from './components/Progressbar.vue'
6
+ export { default as GMenu } from './components/GMenu.vue'
7
+ export { default as GMenuButton } from './components/GMenuButton.vue'
8
+ export { default as GMenuItems } from './components/GMenuItems.vue'
9
+ export { default as GMenuItem } from './components/GMenuItem.vue'
10
+ export { default as GIcon } from './components/GIcon.vue'
11
+
12
+ export { registerIcons, iconRegistry, getIcon, hasIcon } from './icons/registry.js'
13
+ export { defaultIconMap } from './icons/iconMap.js'
14
+
15
+ import GBreadcrumbs from './components/GBreadcrumbs.vue'
16
+ import GDeletableList from './components/GDeletableList.vue'
17
+ import GStatusBadge from './components/GStatusBadge.vue'
18
+ import Loading from './components/Loading.vue'
19
+ import Progressbar from './components/Progressbar.vue'
20
+ import GMenu from './components/GMenu.vue'
21
+ import GMenuButton from './components/GMenuButton.vue'
22
+ import GMenuItems from './components/GMenuItems.vue'
23
+ import GMenuItem from './components/GMenuItem.vue'
24
+ import GIcon from './components/GIcon.vue'
6
25
 
7
26
  export function install(app) {
8
27
  app.component('GBreadcrumbs', GBreadcrumbs)
@@ -10,6 +29,11 @@ export function install(app) {
10
29
  app.component('GStatusBadge', GStatusBadge)
11
30
  app.component('Loading', Loading)
12
31
  app.component('Progressbar', Progressbar)
32
+ app.component('GMenu', GMenu)
33
+ app.component('GMenuButton', GMenuButton)
34
+ app.component('GMenuItems', GMenuItems)
35
+ app.component('GMenuItem', GMenuItem)
36
+ app.component('GIcon', GIcon)
13
37
  }
14
38
 
15
39
  export default { install }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gameap/ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -17,15 +17,11 @@
17
17
  ],
18
18
  "peerDependencies": {
19
19
  "vue": "^3.5.0",
20
- "vue-router": "^4.0.0",
21
- "@headlessui/vue": "^1.7.0"
20
+ "vue-router": "^4.0.0"
22
21
  },
23
22
  "peerDependenciesMeta": {
24
23
  "vue-router": {
25
24
  "optional": true
26
- },
27
- "@headlessui/vue": {
28
- "optional": true
29
25
  }
30
26
  }
31
27
  }