Compare commits

...

33 Commits

Author SHA1 Message Date
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 1119 additions and 789 deletions

View File

@@ -4,7 +4,7 @@
### 🔗 Linked issue ### 🔗 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 ### ❓ Type of change
@@ -21,7 +21,6 @@
<!-- Describe your changes in detail --> <!-- Describe your changes in detail -->
<!-- Why is this change required? What problem does it solve? --> <!-- 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 ### 📝 Checklist

View File

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

View File

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

View File

@@ -1,5 +1,24 @@
# Changelog # Changelog
## [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) ## [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"> <script setup lang="ts">
import type { NavItem } from '@nuxt/content/dist/runtime/types' import type { NavItem } from '@nuxt/content/dist/runtime/types'
import type { Link } from '#ui-pro/types' import type { HeaderLink } from '#ui-pro/types'
defineProps<{ defineProps<{
links: Link[] links: HeaderLink[]
}>() }>()
const route = useRoute() 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 // eslint-disable-next-line vue/no-dupe-keys
const code = computed(() => { const code = computed(() => {
let code = `\`\`\`html let code = `\`\`\`html
<template>
<${name}` <${name}`
for (const [key, value] of Object.entries(fullProps.value)) { for (const [key, value] of Object.entries(fullProps.value)) {
if (value === 'undefined' || value === null) { if (value === 'undefined' || value === null) {
@@ -246,7 +247,7 @@ const code = computed(() => {
} else { } else {
code += ' />' code += ' />'
} }
code += ` code += `\n</template>
\`\`\` \`\`\`
` `
return code return code

View File

@@ -17,7 +17,7 @@ const form = reactive({ email: 'mail@example.com', password: 'password' })
<UButton label="Login" color="gray" block /> <UButton label="Login" color="gray" block />
</div> </div>
<UDivider label="OR" color="gray" orientation="vertical" /> <UDivider label="OR" orientation="vertical" />
<div class="space-y-4 flex flex-col justify-center"> <div class="space-y-4 flex flex-col justify-center">
<UButton color="black" label="Login with GitHub" icon="i-simple-icons-github" block /> <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 /> <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 GitHub" icon="i-simple-icons-github" block />
<UButton color="black" label="Login with Google" icon="i-simple-icons-google" 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' name: 'Tom Cook'
}] }]
const selected = ref(people[0].name) const selected = ref(people[0].id)
</script> </script>
<template> <template>
<UInputMenu <UInputMenu
v-model="selected" v-model="selected"
:options="people" :options="people"
value-attribute="name" value-attribute="id"
option-attribute="name" option-attribute="name"
/> />
</template> </template>

View File

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

View File

@@ -75,7 +75,7 @@ onMounted(() => {
/> />
</UAvatarGroup> </UAvatarGroup>
<UButton label="Button" icon="i-heroicons-pencil-square" /> <UButton label="Button" loading />
<UBadge label="Badge" /> <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. 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: This module is pre-configured and will automatically load the following plugins:

View File

@@ -42,6 +42,12 @@ props:
variant: 'soft' variant: 'soft'
size: 'sm' size: 'sm'
options: options:
- name: color
restriction: included
values:
- gray
- white
- black
- name: variant - name: variant
restriction: included restriction: included
values: 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 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. 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": { "devDependencies": {
"@iconify-json/heroicons": "^1.1.19", "@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/content": "^2.10.0",
"@nuxt/devtools": "^1.0.6", "@nuxt/devtools": "^1.0.8",
"@nuxt/eslint-config": "^0.2.0", "@nuxt/eslint-config": "^0.2.0",
"@nuxt/image": "^1.1.0", "@nuxt/image": "^1.3.0",
"@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.6.1-28413612.408e456", "@nuxt/ui-pro": "npm:@nuxt/ui-pro-edge@0.7.0-28425529.a466815",
"@nuxthq/studio": "^1.0.6", "@nuxthq/studio": "^1.0.8",
"@nuxtjs/fontaine": "^0.4.1", "@nuxtjs/fontaine": "^0.4.1",
"@nuxtjs/google-fonts": "^3.1.3", "@nuxtjs/google-fonts": "^3.1.3",
"@nuxtjs/plausible": "^0.2.4", "@nuxtjs/plausible": "^0.2.4",
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.0.2",
"@vueuse/nuxt": "^10.7.1", "@vueuse/nuxt": "^10.7.2",
"date-fns": "^3.1.0", "date-fns": "^3.2.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"joi": "^17.11.0", "joi": "^17.11.1",
"nuxt": "^3.9.1", "nuxt": "^3.9.3",
"nuxt-cloudflare-analytics": "^1.0.8", "nuxt-cloudflare-analytics": "^1.0.8",
"nuxt-component-meta": "^0.6.0", "nuxt-component-meta": "^0.6.2",
"nuxt-og-image": "^2.2.4", "nuxt-og-image": "^2.2.4",
"prettier": "^3.1.1", "prettier": "^3.2.4",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"ufo": "^1.3.2", "ufo": "^1.3.2",
"v-calendar": "^3.1.2", "v-calendar": "^3.1.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nuxt/ui", "name": "@nuxt/ui",
"version": "2.12.0", "version": "2.12.2",
"repository": "nuxt/ui", "repository": "nuxt/ui",
"homepage": "https://ui.nuxt.com", "homepage": "https://ui.nuxt.com",
"license": "MIT", "license": "MIT",
@@ -34,52 +34,52 @@
"dependencies": { "dependencies": {
"@egoist/tailwindcss-icons": "^1.7.2", "@egoist/tailwindcss-icons": "^1.7.2",
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.0",
"@headlessui/vue": "^1.7.16", "@headlessui/vue": "1.7.16",
"@iconify-json/heroicons": "^1.1.19", "@iconify-json/heroicons": "^1.1.19",
"@nuxt/kit": "^3.9.1", "@nuxt/kit": "^3.9.3",
"@nuxtjs/color-mode": "^3.3.2", "@nuxtjs/color-mode": "^3.3.2",
"@nuxtjs/tailwindcss": "^6.10.3", "@nuxtjs/tailwindcss": "^6.10.4",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@vueuse/core": "^10.7.1", "@vueuse/core": "^10.7.2",
"@vueuse/integrations": "^10.7.1", "@vueuse/integrations": "^10.7.2",
"@vueuse/math": "^10.7.1", "@vueuse/math": "^10.7.2",
"defu": "^6.1.4", "defu": "^6.1.4",
"fuse.js": "^6.6.2", "fuse.js": "^6.6.2",
"nuxt-icon": "^0.6.8", "nuxt-icon": "^0.6.8",
"ohash": "^1.1.3", "ohash": "^1.1.3",
"pathe": "^1.1.1", "pathe": "^1.1.2",
"scule": "^1.1.1", "scule": "^1.2.0",
"tailwind-merge": "^2.2.0", "tailwind-merge": "^2.2.0",
"tailwindcss": "^3.4.1" "tailwindcss": "^3.4.1"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint-config": "^0.2.0", "@nuxt/eslint-config": "^0.2.0",
"@nuxt/module-builder": "^0.5.5", "@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", "@release-it/conventional-changelog": "^8.0.1",
"@vue/test-utils": "^2.4.3", "@vue/test-utils": "^2.4.3",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"happy-dom": "^12.10.3", "happy-dom": "^12.10.3",
"joi": "^17.11.0", "joi": "^17.11.1",
"nuxt": "^3.9.1", "nuxt": "^3.9.3",
"release-it": "^17.0.1", "release-it": "^17.0.1",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"unbuild": "^2.0.0", "unbuild": "^2.0.0",
"valibot": "^0.25.0", "valibot": "^0.25.0",
"vitest": "^1.1.3", "vitest": "^1.2.1",
"vitest-environment-nuxt": "^1.0.0", "vitest-environment-nuxt": "^1.0.0",
"vue-tsc": "^1.8.27", "vue-tsc": "^1.8.27",
"yup": "^1.3.3", "yup": "^1.3.3",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"resolutions": { "resolutions": {
"@nuxt/kit": "3.9.1", "@nuxt/kit": "3.9.3",
"@nuxt/schema": "3.9.1", "@nuxt/schema": "3.9.3",
"vue": "3.3.13", "tailwindcss": "3.4.1",
"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> <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"> <slot name="leading" :disabled="disabled" :loading="loading">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" /> <UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
</slot> </slot>
@@ -23,7 +23,7 @@ import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue' import UIcon from '../elements/Icon.vue'
import ULink from '../elements/Link.vue' import ULink from '../elements/Link.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { mergeConfig } from '../../utils' import { mergeConfig, nuxtLinkProps, getNuxtLinkProps } from '../../utils'
import { useInjectButtonGroup } from '../../composables/useButtonGroup' import { useInjectButtonGroup } from '../../composables/useButtonGroup'
import type { ButtonColor, ButtonSize, ButtonVariant, Strategy } from '../../types' import type { ButtonColor, ButtonSize, ButtonVariant, Strategy } from '../../types'
// @ts-expect-error // @ts-expect-error
@@ -39,6 +39,7 @@ export default defineComponent({
}, },
inheritAttrs: false, inheritAttrs: false,
props: { props: {
...nuxtLinkProps,
type: { type: {
type: String, type: String,
default: 'button' default: 'button'
@@ -190,6 +191,8 @@ export default defineComponent({
) )
}) })
const linkProps = computed(() => getNuxtLinkProps(props))
return { return {
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys
ui, ui,
@@ -201,7 +204,8 @@ export default defineComponent({
leadingIconName, leadingIconName,
trailingIconName, trailingIconName,
leadingIconClass, 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> <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"> <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"> <HMenuItem v-slot="{ active, disabled: itemDisabled, close }" :disabled="item.disabled">
<component <component
:is="!!href ? 'a' : 'button'" :is="!!href ? 'a' : 'button'"
@@ -65,7 +65,7 @@ import UAvatar from '../elements/Avatar.vue'
import UKbd from '../elements/Kbd.vue' import UKbd from '../elements/Kbd.vue'
import { useUI } from '../../composables/useUI' import { useUI } from '../../composables/useUI'
import { usePopper } from '../../composables/usePopper' import { usePopper } from '../../composables/usePopper'
import { mergeConfig, omit } from '../../utils' import { mergeConfig, getNuxtLinkProps } from '../../utils'
import type { DropdownItem, PopperOptions, Strategy } from '../../types' import type { DropdownItem, PopperOptions, Strategy } from '../../types'
// @ts-expect-error // @ts-expect-error
import appConfig from '#build/app.config' import appConfig from '#build/app.config'
@@ -263,7 +263,7 @@ export default defineComponent({
onMouseOver, onMouseOver,
onMouseLeave, onMouseLeave,
onClick, onClick,
omit, getNuxtLinkProps,
twMerge, twMerge,
twJoin, twJoin,
NuxtLink NuxtLink

View File

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

View File

@@ -19,7 +19,7 @@
:class="inputClass" :class="inputClass"
autocomplete="off" autocomplete="off"
v-bind="attrs" v-bind="attrs"
:display-value="() => query ? query : ['string', 'number'].includes(typeof modelValue) ? modelValue : modelValue[optionAttribute]" :display-value="() => query ? query : label"
@change="onChange" @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 inputClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant] 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, popper,
trigger, trigger,
container, container,
label,
isLeading, isLeading,
isTrailing, isTrailing,
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys

View File

@@ -36,8 +36,7 @@
</span> </span>
<slot name="label"> <slot name="label">
<span v-if="multiple && Array.isArray(modelValue) && modelValue.length" :class="uiMenu.label">{{ modelValue.length }} selected</span> <span v-if="label" :class="uiMenu.label">{{ label }}</span>
<span v-else-if="!multiple && modelValue" :class="uiMenu.label">{{ ['string', 'number'].includes(typeof modelValue) ? modelValue : modelValue[optionAttribute] }}</span>
<span v-else :class="uiMenu.label">{{ placeholder || '&nbsp;' }}</span> <span v-else :class="uiMenu.label">{{ placeholder || '&nbsp;' }}</span>
</slot> </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 selectClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant] 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, popper,
trigger, trigger,
container, container,
label,
isLeading, isLeading,
isTrailing, isTrailing,
// eslint-disable-next-line vue/no-dupe-keys // eslint-disable-next-line vue/no-dupe-keys

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
import type { NuxtLinkProps } from '#app' import type { NuxtLinkProps } from '#app'
export interface Link extends NuxtLinkProps { export interface Link extends NuxtLinkProps {
as?: string
type?: string
disabled?: boolean
active?: boolean active?: boolean
exact?: boolean exact?: boolean
exactQuery?: 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 './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 { describe, it, expect } from 'vitest'
import Button from '../../src/runtime/components/elements/Button.vue' import { UButton } from '#components'
import type { TypeOf } from 'zod' import type { TypeOf } from 'zod'
import ComponentRender from '../component-render' import ComponentRender from '../component-render'
type ButtonOptions = TypeOf<typeof Button.props>
describe('Button', () => { describe('Button', () => {
it.each([ it.each([
[ 'basic case', { } ], [ 'basic case', { } ],
@@ -14,12 +11,12 @@ describe('Button', () => {
[ 'rounded full', { props: { ui: { rounded: 'rounded-full' } } } ], [ 'rounded full', { props: { ui: { rounded: 'rounded-full' } } } ],
[ '<UButton icon="i-heroicons-pencil-square" size="sm" color="primary" square variant="solid" />' ] [ '<UButton icon="i-heroicons-pencil-square" size="sm" color="primary" square variant="solid" />' ]
// @ts-ignore // @ts-ignore
])('renders %s correctly', async (nameOrHtml: string, options: ButtonOptions) => { ])('renders %s correctly', async (nameOrHtml: string, options: TypeOf<typeof Button.props>) => {
if (options !== undefined) { if (options !== undefined) {
options.slots = options.slots || { default: () => 'label' } options.slots = options.slots || { default: () => 'label' }
options.slots.default = 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() expect(html).toMatchSnapshot()
}) })
}) })