mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-16 13:08:06 +01:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac5224cbae | ||
|
|
79ec3fd031 | ||
|
|
91b27c8581 | ||
|
|
66c78c899c | ||
|
|
05e90aa1d1 | ||
|
|
d28bb0efa8 | ||
|
|
d67c7482ac | ||
|
|
b8db18513d | ||
|
|
a3b33ac917 | ||
|
|
0f25f8563e | ||
|
|
81126b299a | ||
|
|
4ce8348a43 | ||
|
|
0776455a71 | ||
|
|
1a937919a2 | ||
|
|
b9fe74bca5 | ||
|
|
e116f931b2 | ||
|
|
393b992aeb | ||
|
|
c187d367ff | ||
|
|
d43fb835d8 | ||
|
|
033fcfacd8 | ||
|
|
e0977b2933 | ||
|
|
4405d3239f | ||
|
|
a3a7201396 | ||
|
|
29029ca8ae | ||
|
|
2862741e5f | ||
|
|
e4fd20888b | ||
|
|
5c759c326d | ||
|
|
4c9c8d343a | ||
|
|
28b736a703 | ||
|
|
02d72df527 | ||
|
|
a44bfc8511 | ||
|
|
b0df864379 | ||
|
|
969b02d936 | ||
|
|
d3e19dc65a | ||
|
|
cefa597664 | ||
|
|
bad8a69a36 |
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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
|
||||
|
||||
|
||||
2
.github/workflows/ci-dev.yml
vendored
2
.github/workflows/ci-dev.yml
vendored
@@ -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 }}
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -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 }}
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -75,7 +75,7 @@ onMounted(() => {
|
||||
/>
|
||||
</UAvatarGroup>
|
||||
|
||||
<UButton label="Button" icon="i-heroicons-pencil-square" />
|
||||
<UButton label="Button" loading />
|
||||
|
||||
<UBadge label="Badge" />
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -42,6 +42,12 @@ props:
|
||||
variant: 'soft'
|
||||
size: 'sm'
|
||||
options:
|
||||
- name: color
|
||||
restriction: included
|
||||
values:
|
||||
- gray
|
||||
- white
|
||||
- black
|
||||
- name: variant
|
||||
restriction: included
|
||||
values:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
34
package.json
34
package.json
@@ -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
1555
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 || ' ' }}</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
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<UKbd v-for="shortcut of shortcuts" :key="shortcut" size="xs">
|
||||
{{ shortcut }}
|
||||
</Ukbd>
|
||||
</UKbd>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
4
src/runtime/types/chip.d.ts
vendored
4
src/runtime/types/chip.d.ts
vendored
@@ -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
|
||||
}
|
||||
|
||||
1
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -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'
|
||||
|
||||
3
src/runtime/types/link.d.ts
vendored
3
src/runtime/types/link.d.ts
vendored
@@ -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
7
src/runtime/types/tooltip.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface Tooltip {
|
||||
text?: string
|
||||
prevent?: boolean
|
||||
shortcuts?: string[]
|
||||
openDelay?: number
|
||||
closeDelay?: number
|
||||
}
|
||||
@@ -74,3 +74,4 @@ export function looseToNumber (val: any): any {
|
||||
}
|
||||
|
||||
export * from './lodash'
|
||||
export * from './link'
|
||||
|
||||
119
src/runtime/utils/link.ts
Normal file
119
src/runtime/utils/link.ts
Normal 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
|
||||
}, {})
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user