Compare commits

..

36 Commits

Author SHA1 Message Date
Benjamin Canac
ac5224cbae chore(release): 2.12.3 2024-01-18 17:40:47 +01:00
Benjamin Canac
79ec3fd031 fix(link): import type from #vue-router
Resolves #1253
2024-01-18 17:34:41 +01:00
Benjamin Canac
91b27c8581 docs: bump @nuxt/ui-pro 2024-01-18 16:13:28 +01:00
Benjamin Canac
66c78c899c chore(release): 2.12.2 2024-01-18 15:38:59 +01:00
Benjamin Canac
05e90aa1d1 fix(link): improve nuxt link rel type 2024-01-18 15:30:14 +01:00
Benjamin Canac
d28bb0efa8 chore(release): 2.12.1 2024-01-18 15:04:10 +01:00
Benjamin Canac
d67c7482ac chore(deps): pin @headlessui/vue in dependencies instead of resolutions 2024-01-18 15:00:07 +01:00
Benjamin Canac
b8db18513d chore(deps): pin @headlessui/vue as it breaks command palette 2024-01-18 12:47:16 +01:00
Benjamin Canac
a3b33ac917 chore(deps): add missing resolutions update 2024-01-17 23:54:06 +01:00
Benjamin Canac
0f25f8563e chore(deps): update 2024-01-17 23:50:07 +01:00
renovate[bot]
81126b299a chore(deps): update actions/cache action to v4 (#1248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-17 23:48:31 +01:00
Benjamin Canac
4ce8348a43 chore(Divider): clean code 2024-01-16 13:07:33 +01:00
Benjamin Canac
0776455a71 docs(accordion): add missing button colors 2024-01-16 13:07:13 +01:00
Benjamin Canac
1a937919a2 fix(InputMenu): take option-attribute into account to display label 2024-01-16 11:17:07 +01:00
Benjamin Canac
b9fe74bca5 fix(SelectMenu): take option-attribute into account to display label
Resolves #1151
2024-01-16 11:16:20 +01:00
Benjamin Canac
e116f931b2 docs(ComponentCard): wrap code in <template> 2024-01-16 11:07:08 +01:00
Benjamin Canac
393b992aeb docs(divider): remove useless color on example 2024-01-15 23:14:20 +01:00
Benjamin Canac
c187d367ff types(Link): add missing props 2024-01-15 16:05:39 +01:00
Benjamin Canac
d43fb835d8 chore(link): add missing props 2024-01-15 16:01:58 +01:00
Benjamin Canac
033fcfacd8 types(Tooltip): add interface 2024-01-15 14:47:20 +01:00
Benjamin Canac
e0977b2933 types(Chip): add missing fields 2024-01-15 14:47:20 +01:00
Benjamin Canac
4405d3239f fix(Tooltip): typo in kbd component 2024-01-15 14:47:20 +01:00
Mohamed Attia
a3a7201396 docs(installation): typo (#1235) 2024-01-14 17:51:47 +01:00
Benjamin Canac
29029ca8ae docs: bump @nuxt/ui-pro-edge 2024-01-12 11:52:25 +01:00
Benjamin Canac
2862741e5f docs(demo): add loading button 2024-01-12 11:50:49 +01:00
Benjamin Canac
e4fd20888b chore(Dropdown): use getNuxtLinkProps to bind items 2024-01-11 12:18:17 +01:00
Benjamin Canac
5c759c326d chore(Breadcrumb): chore(VerticalNavigation): use getULinkProps to bind links 2024-01-11 12:17:49 +01:00
Benjamin Canac
4c9c8d343a chore(VerticalNavigation): use getULinkProps to bind links 2024-01-11 12:17:29 +01:00
Benjamin Canac
28b736a703 chore(utils): improve link utils 2024-01-11 12:16:27 +01:00
Benjamin Canac
02d72df527 chore(Button): use utils to get link props 2024-01-11 11:25:00 +01:00
Benjamin Canac
a44bfc8511 fix(Button): pass-through nuxt link props to ULink 2024-01-11 11:15:27 +01:00
Benjamin Canac
b0df864379 fix(Link): prevent type bind on <a> 2024-01-11 11:15:08 +01:00
Benjamin Canac
969b02d936 docs(link): display props 2024-01-10 17:17:51 +01:00
Benjamin Canac
d3e19dc65a fix(Button): inherit nuxt link props without breaking nuxt-component-meta
Resolves #578
2024-01-10 16:58:47 +01:00
Benjamin Canac
cefa597664 test(Button): import from #components 2024-01-10 16:26:23 +01:00
Benjamin Canac
bad8a69a36 chore(github): improve pull request template 2024-01-10 12:52:17 +01:00
32 changed files with 1126 additions and 789 deletions

View File

@@ -4,7 +4,7 @@
### 🔗 Linked issue
<!-- Please ensure there is an open issue and mention its number as: Fixes #123 -->
<!-- If it resolves an open issue, please link the issue here. For example "Resolves #123" -->
### ❓ Type of change
@@ -21,7 +21,6 @@
<!-- Describe your changes in detail -->
<!-- Why is this change required? What problem does it solve? -->
<!-- If it resolves an open issue, please link to the issue here. For example "Resolves #1337" -->
### 📝 Checklist

View File

@@ -45,7 +45,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}

View File

@@ -38,7 +38,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}

View File

@@ -1,5 +1,31 @@
# Changelog
## [2.12.3](https://github.com/nuxt/ui/compare/v2.12.2...v2.12.3) (2024-01-18)
### Bug Fixes
* **link:** import type from `[#vue](https://github.com/nuxt/ui/issues/vue)-router` ([79ec3fd](https://github.com/nuxt/ui/commit/79ec3fd031e28d15854e2f0d4fb978df337e43d5)), closes [#1253](https://github.com/nuxt/ui/issues/1253)
## [2.12.2](https://github.com/nuxt/ui/compare/v2.12.1...v2.12.2) (2024-01-18)
### Bug Fixes
* **link:** improve nuxt link `rel` type ([05e90aa](https://github.com/nuxt/ui/commit/05e90aa1d13ab1772189d33278f482405ff88975))
## [2.12.1](https://github.com/nuxt/ui/compare/v2.12.0...v2.12.1) (2024-01-18)
### Bug Fixes
* **Button:** inherit nuxt link props without breaking `nuxt-component-meta` ([d3e19dc](https://github.com/nuxt/ui/commit/d3e19dc65a530201c3adc7738e95e5a09b0a9274)), closes [#578](https://github.com/nuxt/ui/issues/578)
* **Button:** pass-through nuxt link props to `ULink` ([a44bfc8](https://github.com/nuxt/ui/commit/a44bfc85114bed15ed25bb8c79d7ed52adc8d43c))
* **InputMenu:** take `option-attribute` into account to display label ([1a93791](https://github.com/nuxt/ui/commit/1a937919a26546cfd7edb3f6a11ef790d401999d))
* **Link:** prevent `type` bind on `<a>` ([b0df864](https://github.com/nuxt/ui/commit/b0df86437902696b594e5e7042601506a8bf4436))
* **SelectMenu:** take `option-attribute` into account to display label ([b9fe74b](https://github.com/nuxt/ui/commit/b9fe74bca5f48555e76c16237c2acc868f69e243)), closes [#1151](https://github.com/nuxt/ui/issues/1151)
* **Tooltip:** typo in kbd component ([4405d32](https://github.com/nuxt/ui/commit/4405d3239f7e19d399659347f079555318b3231b))
## [2.12.0](https://github.com/nuxt/ui/compare/v2.11.1...v2.12.0) (2024-01-09)

View File

@@ -44,10 +44,10 @@
<script setup lang="ts">
import type { NavItem } from '@nuxt/content/dist/runtime/types'
import type { Link } from '#ui-pro/types'
import type { HeaderLink } from '#ui-pro/types'
defineProps<{
links: Link[]
links: HeaderLink[]
}>()
const route = useRoute()

View File

@@ -217,6 +217,7 @@ const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
// eslint-disable-next-line vue/no-dupe-keys
const code = computed(() => {
let code = `\`\`\`html
<template>
<${name}`
for (const [key, value] of Object.entries(fullProps.value)) {
if (value === 'undefined' || value === null) {
@@ -246,7 +247,7 @@ const code = computed(() => {
} else {
code += ' />'
}
code += `
code += `\n</template>
\`\`\`
`
return code

View File

@@ -17,7 +17,7 @@ const form = reactive({ email: 'mail@example.com', password: 'password' })
<UButton label="Login" color="gray" block />
</div>
<UDivider label="OR" color="gray" orientation="vertical" />
<UDivider label="OR" orientation="vertical" />
<div class="space-y-4 flex flex-col justify-center">
<UButton color="black" label="Login with GitHub" icon="i-simple-icons-github" block />
@@ -37,7 +37,7 @@ const form = reactive({ email: 'mail@example.com', password: 'password' })
<UButton label="Login" color="gray" block />
<UDivider label="OR" color="gray" />
<UDivider label="OR" />
<UButton color="black" label="Login with GitHub" icon="i-simple-icons-github" block />
<UButton color="black" label="Login with Google" icon="i-simple-icons-google" block />

View File

@@ -13,14 +13,14 @@ const people = [{
name: 'Tom Cook'
}]
const selected = ref(people[0].name)
const selected = ref(people[0].id)
</script>
<template>
<UInputMenu
v-model="selected"
:options="people"
value-attribute="name"
value-attribute="id"
option-attribute="name"
/>
</template>

View File

@@ -13,7 +13,7 @@ const people = [{
name: 'Tom Cook'
}]
const selected = ref(people[0].name)
const selected = ref(people[0].id)
</script>
<template>
@@ -21,7 +21,7 @@ const selected = ref(people[0].name)
v-model="selected"
:options="people"
placeholder="Select people"
value-attribute="name"
value-attribute="id"
option-attribute="name"
/>
</template>

View File

@@ -75,7 +75,7 @@ onMounted(() => {
/>
</UAvatarGroup>
<UButton label="Button" icon="i-heroicons-pencil-square" />
<UButton label="Button" loading />
<UBadge label="Badge" />

View File

@@ -44,7 +44,7 @@ Nuxt UI will automatically install the [@nuxtjs/tailwindcss](https://tailwindcss
You should remove them from your `modules` and `dependencies` if you've previously installed them.
::
### `@nuxtjs/tailwindcs`
### `@nuxtjs/tailwindcss`
This module is pre-configured and will automatically load the following plugins:

View File

@@ -42,6 +42,12 @@ props:
variant: 'soft'
size: 'sm'
options:
- name: color
restriction: included
values:
- gray
- white
- black
- name: variant
restriction: included
values:

View File

@@ -32,3 +32,7 @@ Link
It also renders an `<a>` tag when a `to` prop is provided, otherwise it defaults to rendering a `<button>` tag. The default behavior can be customized using the `as` prop.
It is used underneath by the [Button](/elements/button), [Dropdown](/elements/dropdown) and [VerticalNavigation](/navigation/vertical-navigation) components.
## Props
:component-props

View File

@@ -6,26 +6,26 @@
},
"devDependencies": {
"@iconify-json/heroicons": "^1.1.19",
"@iconify-json/simple-icons": "^1.1.87",
"@iconify-json/simple-icons": "^1.1.88",
"@nuxt/content": "^2.10.0",
"@nuxt/devtools": "^1.0.6",
"@nuxt/devtools": "^1.0.8",
"@nuxt/eslint-config": "^0.2.0",
"@nuxt/image": "^1.1.0",
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.6.1-28413612.408e456",
"@nuxthq/studio": "^1.0.6",
"@nuxt/image": "^1.3.0",
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.7.0-28426506.d5bac36",
"@nuxthq/studio": "^1.0.8",
"@nuxtjs/fontaine": "^0.4.1",
"@nuxtjs/google-fonts": "^3.1.3",
"@nuxtjs/plausible": "^0.2.4",
"@octokit/rest": "^20.0.2",
"@vueuse/nuxt": "^10.7.1",
"date-fns": "^3.1.0",
"@vueuse/nuxt": "^10.7.2",
"date-fns": "^3.2.0",
"eslint": "^8.56.0",
"joi": "^17.11.0",
"nuxt": "^3.9.1",
"joi": "^17.11.1",
"nuxt": "^3.9.3",
"nuxt-cloudflare-analytics": "^1.0.8",
"nuxt-component-meta": "^0.6.0",
"nuxt-component-meta": "^0.6.2",
"nuxt-og-image": "^2.2.4",
"prettier": "^3.1.1",
"prettier": "^3.2.4",
"typescript": "^5.3.3",
"ufo": "^1.3.2",
"v-calendar": "^3.1.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@nuxt/ui",
"version": "2.12.0",
"version": "2.12.3",
"repository": "nuxt/ui",
"homepage": "https://ui.nuxt.com",
"license": "MIT",
@@ -34,52 +34,52 @@
"dependencies": {
"@egoist/tailwindcss-icons": "^1.7.2",
"@headlessui/tailwindcss": "^0.2.0",
"@headlessui/vue": "^1.7.16",
"@headlessui/vue": "1.7.16",
"@iconify-json/heroicons": "^1.1.19",
"@nuxt/kit": "^3.9.1",
"@nuxt/kit": "^3.9.3",
"@nuxtjs/color-mode": "^3.3.2",
"@nuxtjs/tailwindcss": "^6.10.3",
"@nuxtjs/tailwindcss": "^6.10.4",
"@popperjs/core": "^2.11.8",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@vueuse/core": "^10.7.1",
"@vueuse/integrations": "^10.7.1",
"@vueuse/math": "^10.7.1",
"@vueuse/core": "^10.7.2",
"@vueuse/integrations": "^10.7.2",
"@vueuse/math": "^10.7.2",
"defu": "^6.1.4",
"fuse.js": "^6.6.2",
"nuxt-icon": "^0.6.8",
"ohash": "^1.1.3",
"pathe": "^1.1.1",
"scule": "^1.1.1",
"pathe": "^1.1.2",
"scule": "^1.2.0",
"tailwind-merge": "^2.2.0",
"tailwindcss": "^3.4.1"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.2.0",
"@nuxt/module-builder": "^0.5.5",
"@nuxt/test-utils": "^3.9.0",
"@nuxt/test-utils": "^3.10.0",
"@release-it/conventional-changelog": "^8.0.1",
"@vue/test-utils": "^2.4.3",
"eslint": "^8.56.0",
"happy-dom": "^12.10.3",
"joi": "^17.11.0",
"nuxt": "^3.9.1",
"joi": "^17.11.1",
"nuxt": "^3.9.3",
"release-it": "^17.0.1",
"typescript": "^5.3.3",
"unbuild": "^2.0.0",
"valibot": "^0.25.0",
"vitest": "^1.1.3",
"vitest": "^1.2.1",
"vitest-environment-nuxt": "^1.0.0",
"vue-tsc": "^1.8.27",
"yup": "^1.3.3",
"zod": "^3.22.4"
},
"resolutions": {
"@nuxt/kit": "3.9.1",
"@nuxt/schema": "3.9.1",
"vue": "3.3.13",
"tailwindcss": "3.4.1"
"@nuxt/kit": "3.9.3",
"@nuxt/schema": "3.9.3",
"tailwindcss": "3.4.1",
"vue": "3.3.13"
}
}

1555
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<template>
<ULink :type="type" :disabled="disabled || loading" :class="buttonClass" v-bind="attrs">
<ULink :type="type" :disabled="disabled || loading" :class="buttonClass" v-bind="{ ...linkProps, ...attrs }">
<slot name="leading" :disabled="disabled" :loading="loading">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
</slot>
@@ -23,7 +23,7 @@ import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import ULink from '../elements/Link.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig } from '../../utils'
import { mergeConfig, nuxtLinkProps, getNuxtLinkProps } from '../../utils'
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
import type { ButtonColor, ButtonSize, ButtonVariant, Strategy } from '../../types'
// @ts-expect-error
@@ -39,6 +39,7 @@ export default defineComponent({
},
inheritAttrs: false,
props: {
...nuxtLinkProps,
type: {
type: String,
default: 'button'
@@ -190,6 +191,8 @@ export default defineComponent({
)
})
const linkProps = computed(() => getNuxtLinkProps(props))
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
@@ -201,7 +204,8 @@ export default defineComponent({
leadingIconName,
trailingIconName,
leadingIconClass,
trailingIconClass
trailingIconClass,
linkProps
}
}
})

View File

@@ -23,7 +23,7 @@
<HMenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.height]" static>
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal }" v-bind="omit(item, ['label', 'labelClass', 'slot', 'icon', 'iconClass', 'avatar', 'shortcuts', 'disabled', 'class', 'click'])" custom>
<NuxtLink v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ href, target, rel, navigate, isExternal }" v-bind="getNuxtLinkProps(item)" custom>
<HMenuItem v-slot="{ active, disabled: itemDisabled, close }" :disabled="item.disabled">
<component
:is="!!href ? 'a' : 'button'"
@@ -65,7 +65,7 @@ import UAvatar from '../elements/Avatar.vue'
import UKbd from '../elements/Kbd.vue'
import { useUI } from '../../composables/useUI'
import { usePopper } from '../../composables/usePopper'
import { mergeConfig, omit } from '../../utils'
import { mergeConfig, getNuxtLinkProps } from '../../utils'
import type { DropdownItem, PopperOptions, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
@@ -263,7 +263,7 @@ export default defineComponent({
onMouseOver,
onMouseLeave,
onClick,
omit,
getNuxtLinkProps,
twMerge,
twJoin,
NuxtLink

View File

@@ -2,6 +2,7 @@
<component
:is="as"
v-if="!to"
:type="type"
:disabled="disabled"
v-bind="$attrs"
:class="active ? activeClass : inactiveClass"
@@ -32,16 +33,20 @@
<script lang="ts">
import { isEqual } from 'ohash'
import { defineComponent } from 'vue'
import { NuxtLink } from '#components'
import { nuxtLinkProps } from '../../utils'
export default defineComponent({
inheritAttrs: false,
props: {
...NuxtLink.props,
...nuxtLinkProps,
as: {
type: String,
default: 'button'
},
type: {
type: String,
default: 'button'
},
disabled: {
type: Boolean,
default: null

View File

@@ -19,7 +19,7 @@
:class="inputClass"
autocomplete="off"
v-bind="attrs"
:display-value="() => query ? query : ['string', 'number'].includes(typeof modelValue) ? modelValue : modelValue[optionAttribute]"
:display-value="() => query ? query : label"
@change="onChange"
/>
@@ -293,6 +293,15 @@ export default defineComponent({
}
})
const label = computed(() => {
if (props.valueAttribute) {
const option = props.options.find(option => option[props.valueAttribute] === props.modelValue)
return option ? option[props.optionAttribute] : null
} else {
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : props.modelValue[props.optionAttribute]
}
})
const inputClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
@@ -423,6 +432,7 @@ export default defineComponent({
popper,
trigger,
container,
label,
isLeading,
isTrailing,
// eslint-disable-next-line vue/no-dupe-keys

View File

@@ -36,8 +36,7 @@
</span>
<slot name="label">
<span v-if="multiple && Array.isArray(modelValue) && modelValue.length" :class="uiMenu.label">{{ modelValue.length }} selected</span>
<span v-else-if="!multiple && modelValue" :class="uiMenu.label">{{ ['string', 'number'].includes(typeof modelValue) ? modelValue : modelValue[optionAttribute] }}</span>
<span v-if="label" :class="uiMenu.label">{{ label }}</span>
<span v-else :class="uiMenu.label">{{ placeholder || '&nbsp;' }}</span>
</slot>
@@ -355,6 +354,23 @@ export default defineComponent({
}
})
const label = computed(() => {
if (props.multiple) {
if (Array.isArray(props.modelValue) && props.modelValue.length) {
return `${props.modelValue.length} selected`
} else {
return null
}
} else {
if (props.valueAttribute) {
const option = props.options.find(option => option[props.valueAttribute] === props.modelValue)
return option ? option[props.optionAttribute] : null
} else {
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : props.modelValue[props.optionAttribute]
}
}
})
const selectClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
@@ -509,6 +525,7 @@ export default defineComponent({
popper,
trigger,
container,
label,
isLeading,
isTrailing,
// eslint-disable-next-line vue/no-dupe-keys

View File

@@ -74,27 +74,25 @@ export default defineComponent({
setup (props) {
const { ui, attrs } = useUI('divider', toRef(props, 'ui'), config)
const isHorizontal = computed(() => props.orientation === 'horizontal' )
const wrapperClass = computed(() => {
return twMerge(twJoin(
ui.value.wrapper.base,
isHorizontal.value ? ui.value.wrapper.horizontal : ui.value.wrapper.vertical
ui.value.wrapper[props.orientation]
), props.class)
})
const containerClass = computed(() => {
return twJoin(
ui.value.container.base,
isHorizontal.value ? ui.value.container.horizontal : ui.value.container.vertical
ui.value.container[props.orientation]
)
})
const borderClass = computed(() => {
return twJoin(
ui.value.border.base,
isHorizontal.value ? ui.value.border.horizontal : ui.value.border.vertical,
isHorizontal.value ? ui.value.border.size.horizontal : ui.value.border.size.vertical,
ui.value.border[props.orientation],
ui.value.border.size[props.orientation],
ui.value.border.type[props.type]
)
})

View File

@@ -5,7 +5,7 @@
<ULink
as="span"
:class="[ui.base, index === links.length - 1 ? ui.active : !!link.to ? ui.inactive : '']"
v-bind="omit(link, ['label', 'labelClass', 'icon', 'iconClass'])"
v-bind="getULinkProps(link)"
:aria-current="index === links.length - 1 ? 'page' : undefined"
>
<slot name="icon" :link="link" :index="index" :is-active="index === links.length - 1">
@@ -39,7 +39,7 @@ import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import ULink from '../elements/Link.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig, omit } from '../../utils'
import { mergeConfig, getULinkProps } from '../../utils'
import type { BreadcrumbLink, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
@@ -78,7 +78,7 @@ export default defineComponent({
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
omit,
getULinkProps,
twMerge,
twJoin
}

View File

@@ -1,10 +1,10 @@
<template>
<nav :class="ui.wrapper" v-bind="attrs">
<ul v-for="(section, sectionIndex) of linkSections" :key="`linkSection${sectionIndex}`">
<li v-for="(link, index) of section" :key="`linkSection${sectionIndex}-${index}`">
<ul v-for="(section, sectionIndex) of sections" :key="`section${sectionIndex}`">
<li v-for="(link, index) of section" :key="`section${sectionIndex}-${index}`">
<ULink
v-slot="{ isActive }"
v-bind="omit(link, ['label', 'labelClass', 'icon', 'iconClass', 'avatar', 'badge', 'click'])"
v-bind="getULinkProps(link)"
:class="[ui.base, ui.padding, ui.width, ui.ring, ui.rounded, ui.font, ui.size]"
:active-class="ui.active"
:inactive-class="ui.inactive"
@@ -40,7 +40,7 @@
</slot>
</ULink>
</li>
<UDivider v-if="sectionIndex < linkSections.length - 1" :ui="ui.divider" />
<UDivider v-if="sectionIndex < sections.length - 1" :ui="ui.divider" />
</ul>
</nav>
</template>
@@ -54,7 +54,7 @@ import UAvatar from '../elements/Avatar.vue'
import ULink from '../elements/Link.vue'
import UDivider from '../layout/Divider.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig, omit } from '../../utils'
import { mergeConfig, getULinkProps } from '../../utils'
import type { VerticalNavigationLink, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
@@ -87,16 +87,14 @@ export default defineComponent({
setup (props) {
const { ui, attrs } = useUI('verticalNavigation', toRef(props, 'ui'), config, toRef(props, 'class'))
const linkSections = computed(() => {
return (Array.isArray(props.links[0]) ? props.links : [props.links]) as VerticalNavigationLink[][]
})
const sections = computed(() => (Array.isArray(props.links[0]) ? props.links : [props.links]) as VerticalNavigationLink[][])
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
omit,
linkSections,
sections,
getULinkProps,
twMerge,
twJoin
}

View File

@@ -19,7 +19,7 @@
<UKbd v-for="shortcut of shortcuts" :key="shortcut" size="xs">
{{ shortcut }}
</Ukbd>
</UKbd>
</span>
</div>
</div>

View File

@@ -6,6 +6,10 @@ export type ChipColor = 'gray' | typeof colors[number]
export type ChipPosition = keyof typeof chip.position
export interface Chip {
size?: ChipSize
color?: ChipColor
position?: ChipPosition
text?: string
inset?: boolean
show?: boolean
}

View File

@@ -22,5 +22,6 @@ export * from './select'
export * from './tabs'
export * from './textarea'
export * from './toggle'
export * from './tooltip'
export * from './vertical-navigation'
export * from './utils'

View File

@@ -1,6 +1,9 @@
import type { NuxtLinkProps } from '#app'
export interface Link extends NuxtLinkProps {
as?: string
type?: string
disabled?: boolean
active?: boolean
exact?: boolean
exactQuery?: boolean

7
src/runtime/types/tooltip.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
export interface Tooltip {
text?: string
prevent?: boolean
shortcuts?: string[]
openDelay?: number
closeDelay?: number
}

View File

@@ -74,3 +74,4 @@ export function looseToNumber (val: any): any {
}
export * from './lodash'
export * from './link'

119
src/runtime/utils/link.ts Normal file
View File

@@ -0,0 +1,119 @@
import type { PropType } from 'vue'
import type { RouteLocationRaw } from '#vue-router'
export const nuxtLinkProps = {
to: {
type: [String, Object] as PropType<RouteLocationRaw>,
default: undefined
},
href: {
type: [String, Object] as PropType<RouteLocationRaw>,
default: undefined
},
// Attributes
target: {
type: String as PropType<'_blank' | '_parent' | '_self' | '_top' | (string & {}) | null>,
default: undefined
},
rel: {
type: String as PropType<string | null>,
default: undefined
},
noRel: {
type: Boolean,
default: undefined
},
// Prefetching
prefetch: {
type: Boolean,
default: undefined
},
noPrefetch: {
type: Boolean,
default: undefined
},
// Styling
activeClass: {
type: String,
default: undefined
},
exactActiveClass: {
type: String,
default: undefined
},
prefetchedClass: {
type: String,
default: undefined
},
// Vue Router's `<RouterLink>` additional props
replace: {
type: Boolean,
default: undefined
},
ariaCurrentValue: {
type: String,
default: undefined
},
// Edge cases handling
external: {
type: Boolean,
default: undefined
}
} as const
const uLinkProps = {
as: {
type: String,
default: 'button'
},
type: {
type: String,
default: 'button'
},
disabled: {
type: Boolean,
default: null
},
active: {
type: Boolean,
default: undefined
},
exact: {
type: Boolean,
default: false
},
exactQuery: {
type: Boolean,
default: false
},
exactHash: {
type: Boolean,
default: false
},
inactiveClass: {
type: String,
default: undefined
}
} as const
export const getNuxtLinkProps = (props) => {
const keys = Object.keys(nuxtLinkProps)
return keys.reduce((acc, key) => {
if (props[key] !== undefined) {
acc[key] = props[key]
}
return acc
}, {})
}
export const getULinkProps = (props) => {
const keys = [...Object.keys(nuxtLinkProps), ...Object.keys(uLinkProps)]
return keys.reduce((acc, key) => {
if (props[key] !== undefined) {
acc[key] = props[key]
}
return acc
}, {})
}

View File

@@ -1,11 +1,8 @@
// @vitest-environment nuxt
import { describe, it, expect } from 'vitest'
import Button from '../../src/runtime/components/elements/Button.vue'
import { UButton } from '#components'
import type { TypeOf } from 'zod'
import ComponentRender from '../component-render'
type ButtonOptions = TypeOf<typeof Button.props>
describe('Button', () => {
it.each([
[ 'basic case', { } ],
@@ -14,12 +11,12 @@ describe('Button', () => {
[ 'rounded full', { props: { ui: { rounded: 'rounded-full' } } } ],
[ '<UButton icon="i-heroicons-pencil-square" size="sm" color="primary" square variant="solid" />' ]
// @ts-ignore
])('renders %s correctly', async (nameOrHtml: string, options: ButtonOptions) => {
])('renders %s correctly', async (nameOrHtml: string, options: TypeOf<typeof Button.props>) => {
if (options !== undefined) {
options.slots = options.slots || { default: () => 'label' }
options.slots.default = options.slots.default || (() => 'label')
}
const html = await ComponentRender(nameOrHtml, options, Button)
const html = await ComponentRender(nameOrHtml, options, UButton)
expect(html).toMatchSnapshot()
})
})