Compare commits

..

30 Commits

Author SHA1 Message Date
Benjamin Canac
b1d9e01818 chore(release): 0.2.1 2023-02-16 17:02:23 +01:00
Benjamin Canac
ca171f3095 chore(ci): disable typecheck 2023-02-16 17:02:07 +01:00
Benjamin Canac
c0e493d96a chore(deps): bump nuxt-icon 2023-02-16 17:02:00 +01:00
Benjamin Canac
d0d3235860 chore(release): 0.2.0 2023-02-16 16:17:24 +01:00
Benjamin Canac
18915975be chore(release): 0.1.39 2023-02-16 16:16:57 +01:00
Benjamin Canac
f5d068be9d feat: use nuxt-icon 2023-02-16 16:16:37 +01:00
Benjamin Canac
6018f009a8 fix(SelectCustom): handle search on string arrays 2023-02-16 12:02:38 +01:00
Benjamin Canac
2b78b5d7dc chore(SelectCustom): improve options type 2023-02-16 11:53:19 +01:00
Benjamin Canac
87f3f0b4c0 chore(release): 0.1.38 2023-02-03 18:09:49 +01:00
Benjamin Canac
41bf56f2ae chore(tsconfig): remove override 2023-02-03 18:04:30 +01:00
Benjamin Canac
b7795f4ef6 chore(deps): add @types/node 2023-02-03 18:04:19 +01:00
Benjamin Canac
57f8145a8d chore(deps): bump 2023-02-03 17:51:06 +01:00
Benjamin Canac
70fbcb6b24 chore(release): 0.1.37 2023-02-03 16:02:21 +01:00
Kevin Marrec
bea47b5906 fix(CommandPalette): improve accessibility (#129) 2023-02-03 16:01:43 +01:00
Benjamin Canac
fc1b3b2f17 chore(release): 0.1.36 2023-02-02 15:10:25 +01:00
Benjamin Canac
5bf5a314c4 fix(CommandPalette): put back cursor on top only when query changes 2023-02-01 15:20:21 +01:00
Benjamin Canac
3558eb1a4f chore(release): 0.1.35 2023-02-01 14:38:15 +01:00
Benjamin Canac
1c4d46e056 fix(Dropdown): lint 2023-02-01 14:38:07 +01:00
Benjamin Canac
1b0ed9e732 docs: simplify popover panel template 2023-02-01 14:37:55 +01:00
Benjamin Canac
b72037a777 chore: handle disabled prop for Dropdown and Popover 2023-02-01 14:34:28 +01:00
Benjamin Canac
a7644860b8 fix(Dropdown): prevent panel display when no items 2023-01-31 12:33:27 +01:00
Benjamin Canac
c90cd9c4f3 fix(AvatarGroup): preset size prop 2023-01-28 01:49:24 +01:00
Benjamin Canac
7805168685 chore(release): 0.1.34 2023-01-27 14:10:46 +01:00
Benjamin Canac
27717a55b3 fix(CommandPalette): typecheck 2023-01-27 14:10:35 +01:00
Benjamin Canac
d651a22dce chore(release): 0.1.33 2023-01-27 13:02:18 +01:00
Benjamin Canac
c3ecbf4b20 chore(CommandPalette): start preset 2023-01-27 13:02:01 +01:00
Benjamin Canac
d8b10f3eef chore(release): 0.1.32 2023-01-23 14:24:36 +01:00
Benjamin Canac
1071b80b39 chore(deps): bump 2023-01-23 14:24:20 +01:00
Benjamin Canac
c5e9a1ef46 chore(release): 0.1.31 2023-01-17 15:35:38 +01:00
Benjamin Canac
afe69a570d chore(deps): bump 2023-01-17 15:35:16 +01:00
25 changed files with 1636 additions and 1100 deletions

View File

@@ -41,8 +41,8 @@ jobs:
- name: Lint
run: yarn lint
- name: Typecheck
run: yarn typecheck
# - name: Typecheck
# run: yarn typecheck
- name: Build
run: yarn build

View File

@@ -2,6 +2,60 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.2.1](https://github.com/nuxtlabs/ui/compare/v0.2.0...v0.2.1) (2023-02-16)
## [0.2.0](https://github.com/nuxtlabs/ui/compare/v0.1.39...v0.2.0) (2023-02-16)
### [0.1.39](https://github.com/nuxtlabs/ui/compare/v0.1.38...v0.1.39) (2023-02-16)
### Features
* use `nuxt-icon` ([f5d068b](https://github.com/nuxtlabs/ui/commit/f5d068be9d5778b3d4fcdc11d06d9d765e62075d))
### Bug Fixes
* **SelectCustom:** handle search on string arrays ([6018f00](https://github.com/nuxtlabs/ui/commit/6018f009a86cca196d15e4e72dd5eb41aaeb4bad))
### [0.1.38](https://github.com/nuxtlabs/ui/compare/v0.1.37...v0.1.38) (2023-02-03)
### [0.1.37](https://github.com/nuxtlabs/ui/compare/v0.1.36...v0.1.37) (2023-02-03)
### Bug Fixes
* **CommandPalette:** improve accessibility ([#129](https://github.com/nuxtlabs/ui/issues/129)) ([bea47b5](https://github.com/nuxtlabs/ui/commit/bea47b5906d1bc665717830d6dc2f3ff2a0374f3))
### [0.1.36](https://github.com/nuxtlabs/ui/compare/v0.1.35...v0.1.36) (2023-02-02)
### Bug Fixes
* **CommandPalette:** put back cursor on top only when query changes ([5bf5a31](https://github.com/nuxtlabs/ui/commit/5bf5a314c414b96c656190719bd56acca10676f5))
### [0.1.35](https://github.com/nuxtlabs/ui/compare/v0.1.34...v0.1.35) (2023-02-01)
### Bug Fixes
* **AvatarGroup:** preset size prop ([c90cd9c](https://github.com/nuxtlabs/ui/commit/c90cd9c4f37bc3ce5f6e13f3279dc2c574c76524))
* **Dropdown:** lint ([1c4d46e](https://github.com/nuxtlabs/ui/commit/1c4d46e056adf84d69462a12af8ac29f93cbf87a))
* **Dropdown:** prevent panel display when no items ([a764486](https://github.com/nuxtlabs/ui/commit/a7644860b8c22a0163e01ca2c0eab2c48b09745a))
### [0.1.34](https://github.com/nuxtlabs/ui/compare/v0.1.33...v0.1.34) (2023-01-27)
### Bug Fixes
* **CommandPalette:** typecheck ([27717a5](https://github.com/nuxtlabs/ui/commit/27717a55b3e5120f32fba2bcea30f5a91262f1c5))
### [0.1.33](https://github.com/nuxtlabs/ui/compare/v0.1.32...v0.1.33) (2023-01-27)
### [0.1.32](https://github.com/nuxtlabs/ui/compare/v0.1.31...v0.1.32) (2023-01-23)
### [0.1.31](https://github.com/nuxtlabs/ui/compare/v0.1.30...v0.1.31) (2023-01-17)
### [0.1.30](https://github.com/nuxtlabs/ui/compare/v0.1.29...v0.1.30) (2023-01-17)
### [0.1.29](https://github.com/nuxtlabs/ui/compare/v0.1.28...v0.1.29) (2023-01-17)

View File

@@ -79,7 +79,7 @@ const toggleDark = () => {
const sections = [
{ label: 'Getting Started', links: [{ label: 'Usage', to: '/' }, { label: 'Examples', to: '/examples' }, { label: 'Migration', to: '/migration' }, { label: 'Dark mode', to: '/dark' }] },
{ label: 'Elements', links: [{ label: 'Avatar', to: '/components/Avatar' }, { label: 'AvatarGroup', to: '/components/AvatarGroup' }, { label: 'Badge', to: '/components/Badge' }, { label: 'Button', to: '/components/Button' }, { label: 'Dropdown', to: '/components/Dropdown' }, { label: 'Icon', to: '/components/Icon' }] },
{ label: 'Elements', links: [{ label: 'Avatar', to: '/components/Avatar' }, { label: 'AvatarGroup', to: '/components/AvatarGroup' }, { label: 'Badge', to: '/components/Badge' }, { label: 'Button', to: '/components/Button' }, { label: 'Dropdown', to: '/components/Dropdown' }] },
{ label: 'Feedback', links: [{ label: 'Alert', to: '/components/Alert' }, { label: 'AlertDialog', to: '/components/AlertDialog' }] },
{ label: 'Forms', links: [{ label: 'Checkbox', to: '/components/Checkbox' }, { label: 'Input', to: '/components/Input' }, { label: 'FormGroup', to: '/components/FormGroup' }, { label: 'Radio', to: '/components/Radio' }, { label: 'Select', to: '/components/Select' }, { label: 'SelectCustom', to: '/components/SelectCustom' }, { label: 'Textarea', to: '/components/Textarea' }, { label: 'Toggle', to: '/components/Toggle' }] },
{ label: 'Layout', links: [{ label: 'Card', to: '/components/Card' }, { label: 'Container', to: '/components/Container' }] },

View File

@@ -43,7 +43,7 @@
<UModal v-model="isModalOpen" @submit.prevent="onSubmit">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<UIcon name="heroicons-outline:exclamation" class="h-6 w-6 text-red-600" aria-hidden="true" />
<IconCSS name="heroicons-outline:exclamation" class="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium u-text-gray-900">
@@ -96,7 +96,7 @@
Popover:
</div>
<UPopover mode="hover" wrapper-class="inline-block relative">
<UPopover wrapper-class="inline-block relative">
<template #default="{ open }">
<UButton variant="secondary" :icon="open ? 'heroicons-solid:chevron-up' : 'heroicons-solid:chevron-down'" trailing icon-class="transition">
Open popover!
@@ -104,46 +104,8 @@
</template>
<template #panel>
<div
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
>
<div class="relative grid gap-8 bg-white p-7">
<a
v-for="item in solutions"
:key="item.name"
:href="item.href"
class="flex items-center p-2 -m-3 transition duration-150 ease-in-out rounded-lg hover:bg-gray-50 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
>
<div
class="flex items-center justify-center flex-shrink-0 w-10 h-10 text-white sm:h-12 sm:w-12"
>
<div v-html="item.icon" />
</div>
<div class="ml-4">
<p class="text-sm font-medium u-text-gray-900">
{{ item.name }}
</p>
<p class="text-sm u-text-gray-500">
{{ item.description }}
</p>
</div>
</a>
</div>
<div class="p-4 bg-gray-50">
<a
href="##"
class="flow-root px-2 py-2 transition duration-150 ease-in-out rounded-md hover:bg-gray-100 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
>
<span class="flex items-center">
<span class="text-sm font-medium u-text-gray-900">
Documentation
</span>
</span>
<span class="block text-sm u-text-gray-500">
Start integrating products and tools
</span>
</a>
</div>
<div class="p-4 u-bg-white border u-border-gray-200 rounded-md">
Panel
</div>
</template>
</UPopover>
@@ -155,7 +117,7 @@
</div>
<UTooltip text="Hello tooltip!" :shortcuts="['⌘', 'G']">
<UIcon name="heroicons-outline:information-circle" class="w-6 h-6 u-text-gray-900 cursor-pointer" />
<IconCSS name="heroicons-outline:information-circle" class="w-6 h-6 u-text-gray-900 cursor-pointer" />
</UTooltip>
</div>
@@ -378,95 +340,6 @@ const customDropdownItems = [
}]
]
const solutions = [
{
name: 'Insights',
description: 'Measure actions your users take',
href: '##',
icon: `
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
aria-hidden='true'
xmlns="http://www.w3.org/2000/svg"
>
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
<path
d="M24 11L35.2583 17.5V30.5L24 37L12.7417 30.5V17.5L24 11Z"
stroke="#FB923C"
stroke-width="2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.7417 19.8094V28.1906L24 32.3812L31.2584 28.1906V19.8094L24 15.6188L16.7417 19.8094Z"
stroke="#FDBA74"
stroke-width="2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M20.7417 22.1196V25.882L24 27.7632L27.2584 25.882V22.1196L24 20.2384L20.7417 22.1196Z"
stroke="#FDBA74"
stroke-width="2"
/>
</svg>
`
},
{
name: 'Automations',
description: 'Create your own targeted content',
href: '##',
icon: `
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
<path
d="M28.0413 20L23.9998 13L19.9585 20M32.0828 27.0001L36.1242 34H28.0415M19.9585 34H11.8755L15.9171 27"
stroke="#FB923C"
stroke-width="2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.804 30H29.1963L24.0001 21L18.804 30Z"
stroke="#FDBA74"
stroke-width="2"
/>
</svg>
`
},
{
name: 'Reports',
description: 'Keep track of your growth',
href: '##',
icon: `
<svg
width="48"
height="48"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
<rect x="13" y="32" width="2" height="4" fill="#FDBA74" />
<rect x="17" y="28" width="2" height="8" fill="#FDBA74" />
<rect x="21" y="24" width="2" height="12" fill="#FDBA74" />
<rect x="25" y="20" width="2" height="16" fill="#FDBA74" />
<rect x="29" y="16" width="2" height="20" fill="#FB923C" />
<rect x="33" y="12" width="2" height="24" fill="#FB923C" />
</svg>
`
}
]
const onNotificationClick = () => {
$toast.error({ title: 'Error', description: 'This is an error message' })
}

View File

@@ -105,13 +105,6 @@ const components = [
preset: true,
typescript: true
},
{
label: 'Icon',
to: '/components/Icon',
nuxt3: true,
capi: true,
typescript: true
},
{
label: 'Link',
to: '/components/Link',

View File

@@ -1,6 +1,6 @@
{
"name": "@nuxthq/ui",
"version": "0.1.30",
"version": "0.2.1",
"repository": "https://github.com/nuxtlabs/ui",
"license": "MIT",
"exports": {
@@ -25,32 +25,34 @@
"release": "yarn lint && standard-version && git push --follow-tags"
},
"dependencies": {
"@headlessui/vue": "1.7.4",
"@iconify/vue": "^4.0.2",
"@nuxt/kit": "^3.0.0",
"@headlessui/vue": "^1.7.10",
"@iconify/vue": "^4.1.0",
"@nuxt/kit": "^3.2.0",
"@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/tailwindcss": "^6.2.0",
"@nuxtjs/tailwindcss": "^6.4.0",
"@popperjs/core": "^2.11.6",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.8",
"@vueuse/core": "^9.10.0",
"@vueuse/integrations": "^9.10.0",
"defu": "^6.1.1",
"@tailwindcss/typography": "^0.5.9",
"@vueuse/core": "^9.12.0",
"@vueuse/integrations": "^9.12.0",
"defu": "^6.1.2",
"fuse.js": "^6.6.2",
"lodash-es": "^4.17.21",
"tailwindcss": "^3.2.4"
"nuxt-icon": "^0.3.2",
"tailwindcss": "^3.2.6"
},
"devDependencies": {
"@nuxt/module-builder": "^0.2.1",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@types/lodash-es": "^4.17.6",
"eslint": "^8.31.0",
"nuxt": "^3.0.0",
"@types/node": "^18.13.0",
"eslint": "^8.34.0",
"nuxt": "^3.2.0",
"standard-version": "^9.5.0",
"unbuild": "^1.0.2",
"vue-tsc": "^1.0.20"
"unbuild": "^1.1.1",
"vue-tsc": "^1.1.0"
},
"build": {
"externals": [

View File

@@ -121,6 +121,9 @@ export default defineNuxtModule<ModuleOptions>({
})
})
nuxt.options.appConfig.nuxtIcon = defu(nuxt.options.appConfig.nuxtIcon, { size: false })
await installModule('nuxt-icon')
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
await installModule('@nuxtjs/tailwindcss', {
viewer: false,

View File

@@ -31,7 +31,7 @@ const props = defineProps({
type: String,
default: 'md',
validator (value: string) {
return ['xxxs', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
return Object.keys($ui.avatar.size).includes(value)
}
},
max: {

View File

@@ -6,14 +6,14 @@
:aria-label="ariaLabel"
v-bind="buttonProps"
>
<Icon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
<IconCSS v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
<slot>
<span :class="[truncate ? 'text-left break-all line-clamp-1' : '', compact ? 'hidden sm:block' : '']">
<span :class="[labelCompact && 'hidden sm:block']">{{ label }}</span>
<span v-if="labelCompact" class="sm:hidden">{{ labelCompact }}</span>
</span>
</slot>
<Icon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
<IconCSS v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
</component>
</template>
@@ -22,7 +22,6 @@ import { ref, computed, useSlots } from 'vue'
import type { PropType } from 'vue'
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
import NuxtLink from '#app/components/nuxt-link'
import Icon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import $ui from '#build/ui'

View File

@@ -3,28 +3,31 @@
<MenuButton
ref="trigger"
as="div"
:disabled="disabled"
class="inline-flex w-full"
role="button"
@mouseover="onMouseOver"
>
<slot :open="open">
<button>Open</button>
<button :disabled="disabled">
Open
</button>
</slot>
</MenuButton>
<div v-if="open" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
<div v-if="open && items.length" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
<transition appear v-bind="transitionClass">
<MenuItems :class="baseClass" static>
<div v-for="(subItems, index) of items" :key="index" :class="groupClass">
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }" :disabled="item.disabled">
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
<Component
v-bind="omit(item, ['click'])"
:is="(item.to && NuxtLink) || (item.click && 'button') || 'div'"
:class="resolveItemClass({ active, disabled })"
:class="resolveItemClass({ active, disabled: itemDisabled })"
@click="item.click"
>
<slot :name="item.slot" :item="item">
<Icon v-if="item.icon" :name="item.icon" :class="[itemIconClass, item.iconClass]" />
<IconCSS v-if="item.icon" :name="item.icon" :class="[itemIconClass, item.iconClass]" />
<Avatar v-if="item.avatar" v-bind="{ size: 'xxs', ...item.avatar }" :class="itemAvatarClass" />
<span class="truncate">{{ item.label }}</span>
@@ -54,7 +57,6 @@ import type { RouteLocationNormalized } from 'vue-router'
import { ref, computed, onMounted } from 'vue'
import { defu } from 'defu'
import NuxtLink from '#app/components/nuxt-link'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import { classNames, omit } from '../../utils'
import { usePopper } from '../../composables/usePopper'
@@ -85,6 +87,10 @@ const props = defineProps({
return ['click', 'hover'].includes(value)
}
},
disabled: {
type: Boolean,
default: false
},
wrapperClass: {
type: String,
default: () => $ui.dropdown.wrapper

View File

@@ -1,56 +0,0 @@
<template>
<span v-if="isFetching" />
<Iconify v-else-if="icon" :icon="icon" />
<Component :is="component" v-else-if="component" />
<span v-else>{{ name }}</span>
</template>
<script setup lang="ts">
import type { IconifyIcon } from '@iconify/vue'
import { Icon as Iconify } from '@iconify/vue/dist/offline'
import { loadIcon } from '@iconify/vue'
import { useNuxtApp, useState, computed, watch, onMounted } from '#imports'
const props = defineProps({
name: {
type: String,
required: true
}
})
const nuxtApp = useNuxtApp()
const state = useState<Record<string, Required<IconifyIcon> | Promise<void> | null>>('u-icons', () => ({}))
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
const isFetching = computed(() => state.value?.[props.name] && ('then' in state.value?.[props.name]!))
const icon = computed<Required<IconifyIcon> | null>(() =>
!state.value?.[props.name] || 'then' in state.value[props.name]!
? null
: state.value[props.name] as Required<IconifyIcon>
)
const component = computed(() => nuxtApp.vueApp.component(props.name))
const loadIconComponent = (name: string) => {
state.value = state.value || {}
if (nuxtApp.vueApp.component(props.name) || state.value[name] || state.value[name] === null) { return state.value[name] }
state.value[name] = loadIcon(name)
.then((res) => { state.value[name] = res })
// We won't try to load this icon again if it fails
.catch(() => { state.value[name] = null })
// return an awaitable promise
return state.value[name]
}
onMounted(() => {
watch(() => props.name, loadIconComponent, { immediate: true })
})
if (process.server) {
await loadIconComponent(props.name)
}
</script>
<script lang="ts">
export default { name: 'UIcon' }
</script>

View File

@@ -2,7 +2,7 @@
<div class="rounded-md p-4" :class="variantClass">
<div class="flex">
<div class="flex-shrink-0">
<Icon v-if="iconName" :name="iconName" :class="iconClass" class="h-5 w-5" />
<IconCSS v-if="iconName" :name="iconName" :class="iconClass" class="h-5 w-5" />
</div>
<div class="ml-3 flex-1 md:flex md:justify-between">
<p v-if="title" class="text-sm leading-5" :class="titleClass">
@@ -27,7 +27,6 @@
import { computed } from 'vue'
import type { PropType } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import Icon from '../elements/Icon.vue'
const props = defineProps({
variant: {

View File

@@ -2,7 +2,7 @@
<Modal v-model="isOpen" :appear="appear" class="w-full" @close="onClose">
<div class="sm:flex sm:items-start">
<div v-if="icon" :class="iconWrapperClass" class="mx-auto flex-shrink-0 flex items-center justify-center rounded-full sm:mx-0">
<Icon :name="icon" :class="iconClass" />
<IconCSS :name="icon" :class="iconClass" />
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left space-y-2">
<DialogTitle v-if="title" as="h3" :class="titleClass">
@@ -25,7 +25,6 @@ import { computed } from 'vue'
import { DialogTitle, DialogDescription } from '@headlessui/vue'
import Modal from '../overlays/Modal.vue'
import Button from '../elements/Button.vue'
import Icon from '../elements/Icon.vue'
import $ui from '#build/ui'
const props = defineProps({

View File

@@ -19,17 +19,16 @@
>
<slot />
<div v-if="isLeading" :class="iconLeadingWrapperClass">
<Icon v-if="iconName" :name="iconName" :class="iconClass" />
<IconCSS v-if="iconName" :name="iconName" :class="iconClass" />
</div>
<div v-if="isTrailing" :class="iconTrailingWrapperClass">
<Icon v-if="iconName" :name="iconName" :class="iconClass" />
<IconCSS v-if="iconName" :name="iconName" :class="iconClass" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import Icon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import $ui from '#build/ui'

View File

@@ -37,7 +37,7 @@
</select>
<div v-if="icon" :class="iconWrapperClass">
<Icon :name="icon" :class="iconClass" />
<IconCSS :name="icon" :class="iconClass" />
</div>
</div>
</template>
@@ -45,7 +45,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { get } from 'lodash-es'
import Icon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import $ui from '#build/ui'

View File

@@ -21,7 +21,7 @@
</slot>
<slot name="icon">
<span :class="iconWrapperClass">
<Icon v-if="icon" :name="icon" :class="iconClass" aria-hidden="true" />
<IconCSS v-if="icon" :name="icon" :class="iconClass" aria-hidden="true" />
</span>
</slot>
</button>
@@ -58,7 +58,7 @@
</div>
<span v-if="selected" :class="resolveOptionIconClass({ active })">
<Icon v-if="listOptionIcon" :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
<IconCSS v-if="listOptionIcon" :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
</span>
</li>
</ComboboxOption>
@@ -94,7 +94,6 @@ import {
ComboboxOption,
ComboboxInput
} from '@headlessui/vue'
import Icon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { usePopper } from '../../composables/usePopper'
import type { PopperOptions } from '../../types'
@@ -110,7 +109,7 @@ const props = defineProps({
default: undefined
},
options: {
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[]>,
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[] | string[]>,
default: () => []
},
required: {
@@ -292,11 +291,11 @@ const iconClass = computed(() => {
const filteredOptions = computed(() =>
query.value === ''
? props.options
: props.options.filter((option: any) => {
return (props.searchAttributes?.length ? props.searchAttributes : [props.textAttribute]).some((searchAttribute: any) => {
return option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1
: (props.options as any[]).filter((option: any) => {
return (props.searchAttributes?.length ? props.searchAttributes : [props.textAttribute]).some((searchAttribute: any) => {
return typeof option === 'string' ? option.search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
})
})
})
)
const queryOption = computed(() => {

View File

@@ -5,10 +5,10 @@
>
<span :class="[active ? containerActiveClass : containerInactiveClass, containerBaseClass]">
<span v-if="iconOn" :class="[active ? iconActiveClass : iconInactiveClass, iconBaseClass]" aria-hidden="true">
<Icon :name="iconOn" :class="iconOnClass" />
<IconCSS :name="iconOn" :class="iconOnClass" />
</span>
<span v-if="iconOff" :class="[active ? iconInactiveClass : iconActiveClass, iconBaseClass]" aria-hidden="true">
<Icon :name="iconOff" :class="iconOffClass" />
<IconCSS :name="iconOff" :class="iconOffClass" />
</span>
</span>
</Switch>
@@ -17,7 +17,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { Switch } from '@headlessui/vue'
import Icon from '../elements/Icon.vue'
import $ui from '#build/ui'
const props = defineProps({

View File

@@ -7,29 +7,37 @@
:nullable="nullable"
@update:model-value="onSelect"
>
<div class="flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800">
<div :class="$ui.commandPalette.wrapper">
<div v-show="searchable" class="relative flex items-center">
<Icon :name="inputIcon" class="pointer-events-none absolute top-3.5 left-5 h-5 w-5 u-text-gray-400" aria-hidden="true" />
<IconCSS v-if="inputIcon" :name="inputIcon" :class="$ui.commandPalette.input.icon.base" aria-hidden="true" />
<ComboboxInput
ref="comboboxInput"
:value="query"
class="w-full h-12 pr-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 pl-[3.25rem] u-text-gray-900 focus:ring-0 sm:text-sm"
:class="$ui.commandPalette.input.base"
:placeholder="inputPlaceholder"
autocomplete="off"
@change="query = $event.target.value"
/>
<Button
v-if="closeIcon"
:icon="closeIcon"
variant="transparent"
class="absolute right-2"
aria-label="close"
v-if="inputCloseIcon"
:icon="inputCloseIcon"
:class="$ui.commandPalette.input.close.base"
:size="$ui.commandPalette.input.close.size"
:variant="$ui.commandPalette.input.close.variant"
aria-label="Close"
@click="onClear"
/>
</div>
<ComboboxOptions v-if="groups.length" static hold class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2">
<ComboboxOptions
v-if="groups.length"
static
hold
as="div"
aria-label="Commands"
class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2"
>
<CommandPaletteGroup v-for="group of groups" :key="group.key" :group="group" :group-attribute="groupAttribute" :command-attribute="commandAttribute">
<template v-for="(_, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData" />
@@ -38,8 +46,8 @@
</ComboboxOptions>
<div v-else-if="placeholder" class="flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14">
<Icon :name="emptyIcon" class="w-6 h-6 mx-auto u-text-gray-400" aria-hidden="true" />
<p class="mt-4 text-sm text-center u-text-gray-900">
<IconCSS v-if="emptyIcon" :name="emptyIcon" class="w-6 h-6 mx-auto u-text-gray-400 mb-4" aria-hidden="true" />
<p class="text-sm text-center u-text-gray-900">
{{ query ? "We couldn't find any items with that term. Please try again." : "We couldn't find any items." }}
</p>
</div>
@@ -56,9 +64,9 @@ import { groupBy, map } from 'lodash-es'
import { defu } from 'defu'
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import type { Group, Command } from '../../types/command-palette'
import Icon from '../elements/Icon.vue'
import Button from '../elements/Button.vue'
import CommandPaletteGroup from './CommandPaletteGroup.vue'
import $ui from '#build/ui'
const props = defineProps({
modelValue: {
@@ -85,13 +93,13 @@ const props = defineProps({
type: Array as PropType<Group[]>,
default: () => []
},
closeIcon: {
type: String,
default: null
},
inputIcon: {
type: String,
default: 'heroicons-outline:search'
default: () => $ui.commandPalette.input.icon.name
},
inputCloseIcon: {
type: String,
default: () => $ui.commandPalette.input.close.icon.name
},
inputPlaceholder: {
type: String,
@@ -99,7 +107,7 @@ const props = defineProps({
},
emptyIcon: {
type: String,
default: 'heroicons-outline:search'
default: () => $ui.commandPalette.empty.icon.name
},
groupAttribute: {
type: String,
@@ -181,7 +189,7 @@ const groups = computed(() => map(groupBy(results.value, command => command.item
} as Group
}))
watch(groups, () => {
watch(query, () => {
// Select first item on search changes
setTimeout(() => {
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L804

View File

@@ -1,10 +1,10 @@
<template>
<li class="p-2" role="option">
<div class="p-2" role="option">
<h2 v-if="group[groupAttribute]" class="px-3 my-2 text-xs font-semibold u-text-gray-900">
{{ group[groupAttribute] }}
</h2>
<ul class="text-sm u-text-gray-700">
<div class="text-sm u-text-gray-700" role="listbox" :aria-label="group[groupAttribute]">
<ComboboxOption
v-for="(command, index) of group.commands"
:key="`${group.key}-${index}`"
@@ -13,9 +13,9 @@
:disabled="command.disabled"
as="template"
>
<li :class="['flex justify-between select-none items-center rounded-md px-3 py-2 gap-3 relative', active && 'bg-gray-100 dark:bg-gray-800 u-text-gray-900', command.disabled ? 'cursor-not-allowed' : 'cursor-pointer']">
<div :class="['flex justify-between select-none items-center rounded-md px-3 py-2 gap-3 relative', active && 'bg-gray-100 dark:bg-gray-800 u-text-gray-900', command.disabled ? 'cursor-not-allowed' : 'cursor-pointer']">
<div class="flex items-center gap-2 min-w-0">
<Icon v-if="command.icon" :name="command.icon" :class="['h-4 w-4 flex-shrink-0', active ? 'text-opacity-100 dark:text-opacity-100' : 'text-opacity-40 dark:text-opacity-40', command.iconClass || 'text-gray-900 dark:text-gray-50']" aria-hidden="true" />
<IconCSS v-if="command.icon" :name="command.icon" :class="['h-4 w-4 flex-shrink-0', active ? 'text-opacity-100 dark:text-opacity-100' : 'text-opacity-40 dark:text-opacity-40', command.iconClass || 'text-gray-900 dark:text-gray-50']" aria-hidden="true" />
<Avatar
v-else-if="command.avatar"
v-bind="{ size: 'xxxs', ...command.avatar }"
@@ -35,7 +35,7 @@
</div>
</div>
<Icon v-if="selected" name="heroicons-outline:check" class="h-5 w-5 u-text-gray-900 flex-shrink-0" aria-hidden="true" />
<IconCSS v-if="selected" name="heroicons-outline:check" class="h-5 w-5 u-text-gray-900 flex-shrink-0" aria-hidden="true" />
<slot v-else-if="active && (group.active || $slots[`${group.key}-active`])" :name="`${group.key}-active`" :group="group" :command="command">
<span v-if="group.active" class="flex-shrink-0 u-text-gray-500">{{ group.active }}</span>
</slot>
@@ -45,16 +45,15 @@
</span>
<span v-else-if="!command.disabled && group.inactive" class="flex-shrink-0 u-text-gray-500">{{ group.inactive }}</span>
</slot>
</li>
</div>
</ComboboxOption>
</ul>
</li>
</div>
</div>
</template>
<script setup lang="ts">
import { ComboboxOption } from '@headlessui/vue'
import type { PropType } from 'vue'
import Icon from '../elements/Icon.vue'
import Avatar from '../elements/Avatar.vue'
import type { Group } from '../../types/command-palette'

View File

@@ -19,7 +19,7 @@
/>
</slot>
<slot name="icon" :link="link" :is-active="isActive">
<Icon
<IconCSS
v-if="link.icon"
:name="link.icon"
:class="[iconBaseClass, link.label && iconSpacingClass, isActive ? iconActiveClass : iconInactiveClass, link.iconClass]"
@@ -40,7 +40,6 @@
<script setup lang="ts">
import type { PropType } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import Icon from '../elements/Icon.vue'
import Link from '../elements/Link.vue'
import Avatar from '../elements/Avatar.vue'
import type { Avatar as AvatarType } from '../../types/avatar'

View File

@@ -9,7 +9,7 @@
<div class="p-4">
<div class="flex gap-3" :class="{ 'items-start': description, 'items-center': !description }">
<div v-if="iconName" class="flex-shrink-0">
<Icon :name="iconName" :class="iconClass" />
<IconCSS :name="iconName" :class="iconClass" />
</div>
<div class="w-0 flex-1">
<p class="text-sm font-medium u-text-gray-900">
@@ -37,7 +37,7 @@
@click.stop="onClose"
>
<span class="sr-only">Close</span>
<Icon name="heroicons-solid:x" class="w-5 h-5" />
<IconCSS name="heroicons-solid:x" class="w-5 h-5" />
</button>
</div>
</div>
@@ -53,7 +53,6 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watchEffect } from 'vue'
import type { PropType } from 'vue'
import Icon from '../elements/Icon.vue'
import { useTimer } from '../../composables/useTimer'
import { classNames } from '../../utils'
import type { ToastNotificationAction } from '../../types'

View File

@@ -1,8 +1,17 @@
<template>
<Popover v-slot="{ open, close }" :class="wrapperClass" @mouseleave="onMouseLeave">
<PopoverButton ref="trigger" as="div" class="inline-flex w-full" role="button" @mouseover="onMouseOver">
<PopoverButton
ref="trigger"
as="div"
:disabled="disabled"
class="inline-flex w-full"
role="button"
@mouseover="onMouseOver"
>
<slot :open="open" :close="close">
<button>Open</button>
<button :disabled="disabled">
Open
</button>
</slot>
</PopoverButton>
@@ -33,6 +42,10 @@ const props = defineProps({
return ['click', 'hover'].includes(value)
}
},
disabled: {
type: Boolean,
default: false
},
wrapperClass: {
type: String,
default: () => $ui.popover.wrapper

View File

@@ -566,6 +566,30 @@ export default function defaultPreset (variantColors: string[]) {
}
}
const commandPalette = {
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800',
input: {
base: 'w-full h-12 pr-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 pl-[3.25rem] u-text-gray-900 focus:ring-0 sm:text-sm',
icon: {
base: 'pointer-events-none absolute top-3.5 left-5 h-5 w-5 u-text-gray-400',
name: 'heroicons-outline:search'
},
close: {
base: 'absolute right-2',
variant: 'transparent',
size: 'md',
icon: {
name: ''
}
}
},
empty: {
icon: {
name: 'heroicons-outline:search'
}
}
}
return {
card,
modal,
@@ -591,7 +615,8 @@ export default function defaultPreset (variantColors: string[]) {
notification,
tooltip,
popover,
contextMenu
contextMenu,
commandPalette
}
}

View File

@@ -1,14 +1,3 @@
{
"extends": "./docs/.nuxt/tsconfig.json",
"compilerOptions": {
"module": "esnext"
},
"include": [
"./**/*",
"./docs/.nuxt/nuxt.d.ts"
],
"exclude": [
"node_modules",
"dist"
]
"extends": "./docs/.nuxt/tsconfig.json"
}

2256
yarn.lock

File diff suppressed because it is too large Load Diff