daisy-ui-kit 2.1.17 → 3.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.
@@ -1,96 +1,93 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, ref, watch } from 'vue'
2
+ import { provide, ref } from 'vue'
3
+ import { autoUpdate, useFloating } from '@floating-ui/vue'
3
4
  import { onClickOutside, syncRefs, useElementHover } from '@vueuse/core'
5
+ import { randomString } from '../utils/random-string'
4
6
 
5
7
  const props = withDefaults(defineProps<{
6
- open?: boolean
8
+ autoFocus?: boolean
7
9
 
8
- position?: string
9
- top?: boolean
10
- bottom?: boolean
11
- right?: boolean
12
- left?: boolean
10
+ placement?: 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'
11
+ strategy?: 'fixed' | 'absolute'
13
12
 
14
- end?: boolean
15
13
  hover?: boolean
16
14
  delayEnter?: number
17
15
  delayLeave?: number
18
16
  closeOnClickOutside?: boolean
19
17
  }>(), {
20
- position: 'bottom',
21
- end: false,
18
+ autoFocus: false,
19
+ placement: 'bottom-start',
22
20
  hover: false,
23
21
  delayEnter: 0,
24
22
  delayLeave: 300,
25
- closeOnClickOutside: false,
23
+ closeOnClickOutside: true,
26
24
  })
27
- const emit = defineEmits(['update:open'])
25
+ // Dropdown Visibility
26
+ const isOpen = defineModel('open', { local: true })
27
+ provide('isDropdownOpen', isOpen)
28
28
 
29
- // defineModel 'open'
30
- const _isOpen = ref(props.open)
31
- watch(() => props.open, (val) => {
32
- _isOpen.value = val
33
- })
34
- const isOpen = computed({
35
- get: () => _isOpen.value,
36
- set: (val) => {
37
- _isOpen.value = val
38
- if (props.open !== val)
39
- emit('update:open', val)
40
- },
41
- })
29
+ const autoFocus = ref(props.autoFocus)
30
+ provide('dropdownAutoFocus', autoFocus)
42
31
 
43
- const classes = computed(() => {
44
- return {
45
- 'dropdown-top': props.top || props.position === 'top',
46
- 'dropdown-bottom': props.bottom || props.position === 'bottom',
47
- 'dropdown-left': props.left || props.position === 'left',
48
- 'dropdown-right': props.right || props.position === 'right',
49
- 'dropdown-end': props.end,
50
- 'dropdown-hover': props.hover,
51
- }
32
+ const randomValue = randomString(12)
33
+ const wrapperId = `dropdown-wrapper-${randomValue}`
34
+ const id = `dropdown-${randomValue}`
35
+ provide('dropdownId', id)
36
+
37
+ // set up the floating ui instance
38
+ const buttonEl = ref(null)
39
+ const contentEl = ref(null)
40
+ const floatingConfig = reactive({
41
+ placement: ref(props.placement),
42
+ strategy: ref(props.strategy),
43
+ whileElementsMounted: autoUpdate,
52
44
  })
45
+ const { floatingStyles } = useFloating(buttonEl, contentEl, floatingConfig)
53
46
 
54
- // const isOpen = ref(props.open)
55
- watch(() => props.open, value => isOpen.value = value)
47
+ provide('buttonEl', buttonEl)
48
+ provide('contentEl', contentEl)
49
+ provide('floatingStyles', floatingStyles)
50
+
51
+ // Visibility Utils
52
+ function toggle() {
53
+ isOpen.value = !isOpen.value
54
+ }
55
+ function open() {
56
+ isOpen.value = true
57
+ }
58
+ function close() {
59
+ isOpen.value = false
60
+ }
61
+ provide('toggleDropdown', toggle)
62
+ provide('openDropdown', open)
63
+ provide('closeDropdown', close)
56
64
 
57
- const dropdown = ref()
58
- const isHovered = ref(false)
65
+ const dropdownWrapper = ref(null)
59
66
 
60
67
  onMounted(() => {
61
68
  // Close when clicking outside the element
62
- onClickOutside(dropdown, () => {
63
- if (props.closeOnClickOutside)
64
- isOpen.value = false
69
+ // use a slight delay to avoid conflict with the focus trap in the DropdownContent.
70
+ onClickOutside(contentEl, () => {
71
+ if (props.closeOnClickOutside) {
72
+ setTimeout(() => {
73
+ isOpen.value = false
74
+ }, 50)
75
+ }
65
76
  })
66
77
 
67
78
  // Sync with top-level isHovered ref. For SSR compatibility.
68
- const hover = useElementHover(dropdown, {
69
- delayLeave: props.delayLeave,
70
- delayEnter: props.delayEnter,
71
- })
72
- syncRefs(hover, isHovered)
73
- })
74
-
75
- const shouldBeOpen = computed(() => {
76
- return isOpen.value || (props.hover && isHovered.value)
79
+ if (props.hover) {
80
+ const hover = useElementHover(dropdownWrapper, {
81
+ delayLeave: props.delayLeave,
82
+ delayEnter: props.delayEnter,
83
+ })
84
+ syncRefs(hover, isOpen)
85
+ }
77
86
  })
78
-
79
- function handleClick(ev: MouseEvent) {
80
- ev.preventDefault()
81
- if (ev.target === dropdown.value.children[0])
82
- isOpen.value = !isOpen.value
83
- }
84
87
  </script>
85
88
 
86
89
  <template>
87
- <details ref="dropdown" class="dropdown" :open="shouldBeOpen" :class="classes" @click="handleClick">
88
- <slot />
89
- </details>
90
+ <div :id="wrapperId" ref="dropdownWrapper" class="relative inline-block floating-dropdown">
91
+ <slot v-bind="{ toggle, open, close }" />
92
+ </div>
90
93
  </template>
91
-
92
- <style>
93
- .dropdown > :first-child > * {
94
- pointer-events: none;
95
- }
96
- </style>
@@ -0,0 +1,16 @@
1
+ <script setup>
2
+ import { inject } from 'vue'
3
+
4
+ const id = inject('dropdownId')
5
+ const isOpen = inject('isDropdownOpen')
6
+ const toggleDropdown = inject('toggleDropdown')
7
+ const buttonEl = inject('buttonEl')
8
+
9
+ const toggle = toggleDropdown
10
+ </script>
11
+
12
+ <template>
13
+ <Button :id="id" ref="buttonEl" :aria-expanded="isOpen" aria-haspopup="menu" class="dropdown-button" @click="toggle">
14
+ <slot />
15
+ </Button>
16
+ </template>
@@ -1,5 +1,56 @@
1
+ <script setup>
2
+ import { inject } from 'vue'
3
+ import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
4
+
5
+ const autoFocus = inject('dropdownAutoFocus')
6
+ const id = inject('dropdownId')
7
+ const isOpen = inject('isDropdownOpen')
8
+ const isOpenDelayed = ref(isOpen.value)
9
+ const contentEl = inject('contentEl')
10
+ const floatingStyles = inject('floatingStyles')
11
+
12
+ // Dropdown Utils
13
+ const toggle = inject('toggleDropdown')
14
+ const open = inject('openDropdown')
15
+ const close = inject('closeDropdown')
16
+
17
+ let activate
18
+ let deactivate
19
+
20
+ if (autoFocus.value) {
21
+ const { activate: _activate, deactivate: _deactivate, hasFocus } = useFocusTrap(contentEl, { immediate: true })
22
+ activate = _activate
23
+ deactivate = _deactivate
24
+
25
+ // hide the dropdown when the focus-trap drops focus (by pressing escape, for example)
26
+ watchEffect(() => {
27
+ if (!hasFocus.value)
28
+ close()
29
+ })
30
+ }
31
+ // const { activate, deactivate, hasFocus } = useFocusTrap(contentEl, { immediate: true })
32
+
33
+ // synchronize isOpenDelayed with isOpen
34
+ watchEffect(async () => {
35
+ if (isOpen.value) {
36
+ isOpenDelayed.value = true
37
+ if (autoFocus.value) {
38
+ await nextTick()
39
+ activate()
40
+ }
41
+ }
42
+ else {
43
+ if (autoFocus.value) {
44
+ deactivate()
45
+ await nextTick()
46
+ }
47
+ isOpenDelayed.value = false
48
+ }
49
+ })
50
+ </script>
51
+
1
52
  <template>
2
- <div class="dropdown-content">
3
- <slot />
53
+ <div v-if="isOpen" ref="contentEl" :style="floatingStyles" :aria-labelledby="id" role="menu">
54
+ <slot v-bind="{ toggle, open, close }" />
4
55
  </div>
5
56
  </template>
@@ -1,5 +1,14 @@
1
+ <script setup>
2
+ import { inject } from 'vue'
3
+
4
+ const id = inject('dropdownId')
5
+ const isOpen = inject('isDropdownOpen')
6
+ const toggle = inject('toggleDropdown')
7
+ const buttonEl = inject('buttonEl')
8
+ </script>
9
+
1
10
  <template>
2
- <summary class="list-none">
11
+ <div :id="id" ref="buttonEl" :aria-expanded="isOpen" aria-haspopup="menu" class="dropdown-target" @click="toggle">
3
12
  <slot />
4
- </summary>
13
+ </div>
5
14
  </template>
@@ -13,6 +13,8 @@ const props = defineProps<{
13
13
  xs?: boolean
14
14
  }>()
15
15
 
16
+ provide('withinExpand', false)
17
+
16
18
  // `normal` and `compact` provide backwards compatibility with DaisyUI 2.x
17
19
  const classes = computed(() => {
18
20
  return {
@@ -0,0 +1,78 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, provide, ref } from 'vue'
3
+ import { onClickOutside, syncRefs, useElementHover } from '@vueuse/core'
4
+ import { randomString } from '../utils/random-string'
5
+
6
+ const props = withDefaults(defineProps<{
7
+ hover?: boolean
8
+ delayEnter?: number
9
+ delayLeave?: number
10
+ closeOnClickOutside?: boolean
11
+ }>(), {
12
+ position: 'bottom',
13
+ end: false,
14
+ hover: false,
15
+ delayEnter: 0,
16
+ delayLeave: 300,
17
+ closeOnClickOutside: false,
18
+ })
19
+ // "Expand" Visibility
20
+ const isOpen = defineModel('open', { local: true, default: false, type: Boolean })
21
+ provide('isExpandOpen', isOpen)
22
+
23
+ // ids for accessibility
24
+ const randomValue = randomString(12)
25
+ const wrapperId = `expand-wrapper-${randomValue}`
26
+ const id = `expand-${randomValue}`
27
+ provide('expandId', id)
28
+
29
+ // Visibility Utils
30
+ function toggle() {
31
+ setTimeout(() => {
32
+ isOpen.value = !isOpen.value
33
+ }, 50)
34
+ }
35
+ function open() {
36
+ isOpen.value = true
37
+ }
38
+ function close() {
39
+ isOpen.value = false
40
+ }
41
+ provide('toggleExpand', toggle)
42
+ provide('openExpand', open)
43
+ provide('closeExpand', close)
44
+
45
+ const expandEl = ref()
46
+
47
+ onMounted(() => {
48
+ // Close when clicking outside the element
49
+ onClickOutside(expandEl, () => {
50
+ if (props.closeOnClickOutside) {
51
+ setTimeout(() => {
52
+ isOpen.value = false
53
+ }, 500)
54
+ }
55
+ })
56
+
57
+ if (props.hover) {
58
+ // Sync with top-level isHovered ref. For SSR compatibility.
59
+ const hover = useElementHover(expandEl, {
60
+ delayLeave: props.delayLeave,
61
+ delayEnter: props.delayEnter,
62
+ })
63
+ syncRefs(hover, isOpen)
64
+ }
65
+ })
66
+
67
+ function handleClick(ev: MouseEvent) {
68
+ ev.preventDefault()
69
+ if (ev.target === expandEl.value.children[0])
70
+ isOpen.value = !isOpen.value
71
+ }
72
+ </script>
73
+
74
+ <template>
75
+ <details :id="wrapperId" ref="expandEl" class="dropdown menu-expand" :open="isOpen" @click="handleClick">
76
+ <slot v-bind="{ toggle, open, close }" />
77
+ </details>
78
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup>
2
+ import { inject } from 'vue'
3
+
4
+ const id = inject('expandId')
5
+ const isOpen = inject('isExpandOpen')
6
+ const toggle = inject('toggleExpand')
7
+ </script>
8
+
9
+ <template>
10
+ <summary :id="id" :aria-expanded="isOpen" aria-haspopup="menu" class="menu-expand-toggle" @click.prevent.stop="toggle">
11
+ <slot />
12
+ </summary>
13
+ </template>
@@ -12,6 +12,10 @@ defineProps<{
12
12
  </template>
13
13
 
14
14
  <style lang="postcss">
15
+ /*
16
+ Allow adding .active class to the MenuItem element.
17
+ DaisyUI only supports adding it to the `a` element.
18
+ */
15
19
  .menu-item.active > a, .menu-item.active > span {
16
20
  background-color: hsl(var(--n) / var(--tw-bg-opacity));
17
21
  color: hsl(var(--nc) / var(--tw-text-opacity));
@@ -19,4 +23,16 @@ defineProps<{
19
23
  .menu-item.disabled > a, .menu-item.disabled > span {
20
24
  background-color: var(--n)
21
25
  }
26
+
27
+ /* Fix padding when putting a Dropdown inside of a menu */
28
+ .menu-item > .floating-dropdown {
29
+ padding: 0;
30
+ }
31
+ .menu-item > .floating-dropdown > .dropdown-button,
32
+ .menu-item > .floating-dropdown > .dropdown-target {
33
+ padding-left: 1rem;
34
+ padding-right: 1rem;
35
+ padding-top: 0.5rem;
36
+ padding-bottom: 0.5rem
37
+ }
22
38
  </style>
package/index.ts CHANGED
@@ -32,6 +32,7 @@ export { default as DrawerContent } from './components/DrawerContent.vue'
32
32
  export { default as DrawerSide } from './components/DrawerSide.vue'
33
33
  export { default as Dropdown } from './components/Dropdown.vue'
34
34
  export { default as DropdownContent } from './components/DropdownContent.vue'
35
+ export { default as DropdownButton } from './components/DropdownButton.vue'
35
36
  export { default as DropdownTarget } from './components/DropdownTarget.vue'
36
37
  export { default as FileInput } from './components/FileInput.vue'
37
38
  export { default as Flex } from './components/Flex.vue'
@@ -60,6 +61,8 @@ export { default as Mask } from './components/Mask.vue'
60
61
  export { default as Menu } from './components/Menu.vue'
61
62
  export { default as MenuItem } from './components/MenuItem.vue'
62
63
  export { default as MenuTitle } from './components/MenuTitle.vue'
64
+ export { default as MenuExpand } from './components/MenuExpand.vue'
65
+ export { default as MenuExpandToggle } from './components/MenuExpandToggle.vue'
63
66
  export { default as MockupCode } from './components/MockupCode.vue'
64
67
  export { default as MockupBrowser } from './components/MockupBrowser.vue'
65
68
  export { default as MockupBrowserToolbar } from './components/MockupBrowserToolbar.vue'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "daisy-ui-kit",
3
- "version": "2.1.17",
3
+ "version": "3.0.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "build": "nuxi build",
@@ -20,6 +20,11 @@
20
20
  "utils/*",
21
21
  "nuxt.js"
22
22
  ],
23
+ "dependencies": {
24
+ "@floating-ui/vue": "^1.0.2",
25
+ "@vueuse/integrations": "^10.4.0",
26
+ "focus-trap": "^7.5.2"
27
+ },
23
28
  "peerDependencies": {
24
29
  "@vueuse/core": "^10.2.1",
25
30
  "daisyui": "^3",
@@ -29,10 +34,10 @@
29
34
  "prismjs": "^1.29.0"
30
35
  },
31
36
  "devDependencies": {
32
- "@antfu/eslint-config": "^0.40.2",
37
+ "@antfu/eslint-config": "^0.41.0",
33
38
  "@headlessui/vue": "^1.7.16",
34
39
  "@heroicons/vue": "^2.0.18",
35
- "@iconify/json": "^2.2.102",
40
+ "@iconify/json": "^2.2.106",
36
41
  "@nuxt/content": "^2.7.2",
37
42
  "@nuxt/kit": "link:@nuxt/kit",
38
43
  "@nuxtjs/color-mode": "^3.3.0",
@@ -41,26 +46,25 @@
41
46
  "@popperjs/core": "^2.11.8",
42
47
  "@rovit/popper": "^3.9.0",
43
48
  "@tailwindcss/aspect-ratio": "^0.4.2",
44
- "@tailwindcss/forms": "^0.5.4",
49
+ "@tailwindcss/forms": "^0.5.5",
45
50
  "@tailwindcss/line-clamp": "^0.4.4",
46
51
  "@tailwindcss/typography": "^0.5.9",
47
- "@vueuse/core": "^10.3.0",
48
- "@vueuse/integrations": "^10.3.0",
49
- "@vueuse/nuxt": "^10.3.0",
52
+ "@vueuse/core": "^10.4.0",
53
+ "@vueuse/nuxt": "^10.4.0",
50
54
  "autoprefixer": "^10.4.15",
51
55
  "cookie": "^0.5.0",
52
- "daisyui": "^3.5.1",
53
- "eslint": "^8.47.0",
54
- "feathers-pinia": "^3.0.7",
56
+ "daisyui": "^3.6.3",
57
+ "eslint": "^8.48.0",
58
+ "feathers-pinia": "^4.0.0",
55
59
  "fuse.js": "^6.6.2",
56
60
  "mobile-detect": "^1.4.5",
57
- "nuxt": "^3.6.5",
61
+ "nuxt": "^3.7.0",
58
62
  "nuxt-icon": "^0.5.0",
59
63
  "ohmyfetch": "^0.4.21",
60
64
  "pinia": "^2.1.6",
61
65
  "postcss": "^8.4.28",
62
66
  "tailwindcss": "^3.3.3",
63
- "typescript": "^5.1.6",
67
+ "typescript": "^5.2.2",
64
68
  "unplugin-icons": "^0.16.5",
65
69
  "vue": "^3.3.4"
66
70
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Generate a random string of `length` characters
3
+ *
4
+ * Usage:
5
+ * const myString = randomString(15); // This will give you a random string of 15 characters
6
+ * @param length The length of the string to generate
7
+ * @returns A random string of `length` characters
8
+ */
9
+ export function randomString(length: number = 10): string {
10
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
11
+ let result = ''
12
+
13
+ for (let i = 0; i < length; i++) {
14
+ const randomIndex = Math.floor(Math.random() * characters.length)
15
+ result += characters[randomIndex]
16
+ }
17
+
18
+ return result
19
+ }