Compare commits

..

5 Commits

Author SHA1 Message Date
rdjanuar
21939ed333 docs(SelectMenu): add section customization on clearable prop 2024-11-12 19:04:42 +07:00
rdjanuar
5a414eb55a feat(SelectMenu): add clearble 2024-11-12 18:47:39 +07:00
kyyy
3a5960fb58 Merge branch 'dev' into issue-1057 2024-11-12 17:17:46 +07:00
rdjanuar
a78203ce49 up 2024-11-08 14:30:53 +07:00
rdjanuar
592da565fe feat(SelectMenu): add clearble 2024-11-08 14:02:54 +07:00
50 changed files with 2701 additions and 2430 deletions

View File

@@ -5,8 +5,8 @@ body:
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> As Nuxt UI v3 is currently in alpha, we recommend thorough testing before using it in production environments. We're actively working on stabilization and welcome feedback from early adopters to improve the library.
> [!IMPORTANT]
> As Nuxt UI v3 is currently in alpha, we recommend thorough testing before using it in production environments. We're actively working on stabilization and welcome feedback from early adopters to improve the library.
- type: markdown
attributes:
value: |
@@ -29,20 +29,11 @@ body:
- Build Modules: `-`
validations:
required: true
- type: dropdown
id: package
attributes:
label: Is this bug related to Nuxt or Vue?
options:
- Nuxt
- Vue
validations:
required: true
- type: input
id: version
attributes:
label: Version
placeholder: v3.0.0-alpha.x
placeholder: v3.0.0-alpha.5
validations:
required: true
- type: textarea

View File

@@ -12,7 +12,7 @@ body:
label: For what version of Nuxt UI are you suggesting this?
options:
- v2.x
- v3.0.0-alpha.x
- v3-alpha
validations:
required: true
- type: textarea

View File

@@ -12,7 +12,7 @@ body:
label: For what version of Nuxt UI are you asking this question?
options:
- v2.x
- v3.0.0-alpha.x
- v3-alpha
validations:
required: true
- type: textarea

View File

@@ -1,38 +1,5 @@
# Changelog
## [2.20.0](https://github.com/nuxt/ui/compare/v2.19.2...v2.20.0) (2024-12-09)
### ⚠ BREAKING CHANGES
* **Form:** resolve async validation in yup & issue directly mutate state (#2701)
### Features
* **Accordion:** add `close` event ([#2750](https://github.com/nuxt/ui/issues/2750)) ([419a24f](https://github.com/nuxt/ui/commit/419a24f7034cefda2c6669f3c26742552e500f63))
* **Badge:** handle `icon` prop ([#2594](https://github.com/nuxt/ui/issues/2594)) ([0d1a76e](https://github.com/nuxt/ui/commit/0d1a76e3c69e08534abb295b96548e67cfbea00c))
* **InputMenu/SelectMenu:** add support for `dot notation` in `by` prop ([#2607](https://github.com/nuxt/ui/issues/2607)) ([53df9d9](https://github.com/nuxt/ui/commit/53df9d9a8cd6850803bdafc7ef6efe4e7404d334))
* **Link:** allow partial query match for `activeClass` ([#2663](https://github.com/nuxt/ui/issues/2663)) ([03e24f4](https://github.com/nuxt/ui/commit/03e24f45836bdddd94b30cbaecc2288a78b56b0b))
* **Notification:** add `pauseTimeoutOnHover` prop ([#2661](https://github.com/nuxt/ui/issues/2661)) ([11b8c3d](https://github.com/nuxt/ui/commit/11b8c3d9db1ec62b1c3557703c7ab5c99cb42df5))
* **Table:** add contextmenu handling to table rows ([#2283](https://github.com/nuxt/ui/issues/2283)) ([c9e6256](https://github.com/nuxt/ui/commit/c9e6256e7f2c06da8bfda13700f56f6994e76eab))
* **Table:** add custom `[@select](https://github.com/select):all` event ([#2581](https://github.com/nuxt/ui/issues/2581)) ([ac323c4](https://github.com/nuxt/ui/commit/ac323c4cccd930f2cd8c1f54b325bd509acd40bf))
* **Table:** allow dynamically render `checkbox` ([#2549](https://github.com/nuxt/ui/issues/2549)) ([d6daf46](https://github.com/nuxt/ui/commit/d6daf466ace42b828151c45b18cd47179e85d66d))
### Bug Fixes
* **AvatarGroup/ButtonGroup/MeterGroup:** allow deeply partial `ui` config ([#2542](https://github.com/nuxt/ui/issues/2542)) ([bf58086](https://github.com/nuxt/ui/commit/bf580863af11d6a1a4c6c6774b44ec37b082e933))
* **Carousel:** wrong `ui` type with `strategy` ([07ef771](https://github.com/nuxt/ui/commit/07ef771b17c72e275508a273371454a5e8a62257))
* **components:** replace `as const` with correct type in config ([#2652](https://github.com/nuxt/ui/issues/2652)) ([51c8b8e](https://github.com/nuxt/ui/commit/51c8b8e3e59d7eceff72625650a199fcf7c6feca))
* **date-picker:** undefined `dayIndex` ([#2545](https://github.com/nuxt/ui/issues/2545)) ([ce955d2](https://github.com/nuxt/ui/commit/ce955d24f1dfd222e87ce88428c0612c3f13cd50))
* **Form:** resolve async validation in yup & issue directly mutate state ([#2701](https://github.com/nuxt/ui/issues/2701)) ([f3632dd](https://github.com/nuxt/ui/commit/f3632ddee511f0fccb24d4fc37403421e84ffdae))
* **Form:** use parsed value from `joi` instead of original state ([#2587](https://github.com/nuxt/ui/issues/2587)) ([acecff4](https://github.com/nuxt/ui/commit/acecff40ec0156e45b4934c5d10c4dfa7c135f8e))
* **InputMenu/SelectMenu:** use `by` prop to compare objects & support dot notation in `value-attribute` ([#2566](https://github.com/nuxt/ui/issues/2566)) ([7154254](https://github.com/nuxt/ui/commit/7154254ac22830f651ec200f7f3af2f5577f2de0))
* **Link:** `exactQuery` prop type ([#2781](https://github.com/nuxt/ui/issues/2781)) ([4cde571](https://github.com/nuxt/ui/commit/4cde571e387775a9b12759f6f8c99117c84cbcff))
* **Notification:** element renders even when no `notification` is present ([#2561](https://github.com/nuxt/ui/issues/2561)) ([d4e408c](https://github.com/nuxt/ui/commit/d4e408cfd8e2ef26021519f2f30f57e9120e1939))
* **Table:** data outdated when rows change ([#2600](https://github.com/nuxt/ui/issues/2600)) ([b23f2de](https://github.com/nuxt/ui/commit/b23f2decfc9607555a315d0d087d0a042f03a938))
* **Table:** missing type on props `loadingState` ([#2551](https://github.com/nuxt/ui/issues/2551)) ([6e66990](https://github.com/nuxt/ui/commit/6e66990372ef6bd7c109a64c753d9b50e96a450b))
* **Table:** prevent `onClick` while blocking element ([#2592](https://github.com/nuxt/ui/issues/2592)) ([9703786](https://github.com/nuxt/ui/commit/97037864b39749db228fa5f51981f19e4a9c29dd))
* **types:** improve `DeepPartial` type for App Config ([#2621](https://github.com/nuxt/ui/issues/2621)) ([976b03f](https://github.com/nuxt/ui/commit/976b03f241ef9626a6338685e43c844a8b3953fd))
## [2.19.2](https://github.com/nuxt/ui/compare/v2.19.1...v2.19.2) (2024-11-05)
### Bug Fixes

View File

@@ -1,4 +1,4 @@
[![nuxt-ui.png](https://volta.s3.fr-par.scw.cloud/nuxt_ui_social_card_531d133fa2.png)](https://ui.nuxt.com)
[![nuxt-ui.png](https://repository-images.githubusercontent.com/428329515/43fec891-9030-4601-8233-5d45ba5c6013)](https://ui.nuxt.com)
# Nuxt UI

View File

@@ -3,7 +3,7 @@
<div>
<NuxtLoadingIndicator />
<!-- <Banner v-if="!$route.path.startsWith('/examples')" /> -->
<Banner v-if="!$route.path.startsWith('/examples')" />
<Header v-if="!$route.path.startsWith('/examples')" :links="links" />

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
const id = 'nuxt-ui-banner-3'
const to = '/pro/pricing'
const id = 'nuxt-ui-banner-2'
const to = 'https://ui3.nuxt.dev'
const hideBanner = () => {
localStorage.setItem(id, 'true')
@@ -28,8 +28,9 @@ if (import.meta.server) {
<NuxtLink
v-if="to"
:to="to"
target="_blank"
class="focus:outline-none"
aria-label="20% off on all Nuxt UI Pro products for Black Friday week"
aria-label="Nuxt UI Pro pricing"
tabindex="-1"
>
<span class="absolute inset-0 " aria-hidden="true" />
@@ -39,19 +40,19 @@ if (import.meta.server) {
<div class="lg:flex-1 hidden lg:flex items-center" />
<p class="text-sm font-medium text-white dark:text-gray-900 truncate">
<UIcon name="i-ri-discount-percent-fill" class="size-5 align-top flex-shrink-0 pointer-events-none mr-2" />
<span class="font-bold">Black Friday Week</span>: <UBadge label="20% off" color="white" class="ring-0 font-semibold" /> on all Nuxt UI Pro products from <span class="font-semibold">Nov 25</span> to <span class="font-semibold">Dec 2</span>!
<UIcon name="i-heroicons-rocket-launch" class="w-5 h-5 align-top flex-shrink-0 pointer-events-none mr-2" />
<span class="font-semibold">Nuxt UI v3-alpha</span> has been released!
</p>
<!-- <UButton
:to="to"
<UButton
to="https://ui3.nuxt.dev"
target="_blank"
label="Buy now"
label="Try it out"
color="black"
variant="solid"
size="2xs"
trailing-icon="i-heroicons-arrow-right-20-solid"
/> -->
/>
<div class="flex items-center justify-end lg:flex-1">
<button

View File

@@ -10,34 +10,12 @@
}"
>
<template #left>
<NuxtLink to="/" class="flex items-end gap-2 text-xl text-gray-900 dark:text-white min-w-0 shrink-0" aria-label="Nuxt UI">
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white min-w-0" aria-label="Nuxt UI">
<LogoPro v-if="$route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" />
<Logo v-else class="w-auto h-6 shrink-0" />
</NuxtLink>
<UDropdown
:items="[[{ label: $route.path.startsWith('/pro') ? `v${pkg.version.split('-')[0]}` : `v${config.version}`, class: 'text-primary-500 dark:text-primary-400' }, { label: 'v3.0.0-alpha.x', to: 'https://ui3.nuxt.dev' }]]"
:popper="{ strategy: 'absolute', offsetDistance: 11, placement: 'bottom-start' }"
:ui="{
background: 'dark:bg-gray-900',
ring: 'dark:ring-gray-800',
width: 'w-auto',
item: {
padding: 'p-1',
size: 'text-xs',
active: 'dark:bg-gray-800/50'
}
}"
>
<UButton
:label="$route.path.startsWith('/pro') ? `v${pkg.version.split('-')[0]}` : `v${config.version}`"
trailing-icon="i-lucide-chevron-down"
variant="outline"
size="2xs"
truncate
class="-mb-[6px] font-semibold rounded-full truncate ring-primary-500/25 dark:ring-primary-400/25 bg-primary-500/10 dark:bg-primary-400/10 hover:bg-primary-500/15 dark:hover:bg-primary-400/15 transition-colors"
/>
</UDropdown>
<UBadge :label="$route.path.startsWith('/pro') ? `v${pkg.version.split('-')[0]}` : `v${config.version}`" variant="subtle" size="xs" class="-mb-[2px] rounded font-semibold truncate hidden sm:inline-flex" />
</NuxtLink>
</template>
<template #right>
@@ -47,10 +25,10 @@
<UContentSearchButton :label="null" />
</UTooltip>
<UColorModeButton class="hidden lg:inline-flex" />
<UColorModeButton />
<UButton
to="https://github.com/nuxt/ui/tree/dev"
to="https://github.com/nuxt/ui"
target="_blank"
icon="i-simple-icons-github"
aria-label="GitHub"

View File

@@ -45,7 +45,7 @@ const ui = {
inactive: 'text-gray-400 dark:text-gray-500'
},
avatar: {
size: '2xs'
size: '2xs' as const
}
}
}

View File

@@ -0,0 +1,102 @@
<script setup lang="ts">
const options = ref([
{ id: 1, name: 'bug', color: 'd73a4a' },
{ id: 2, name: 'documentation', color: '0075ca' },
{ id: 3, name: 'duplicate', color: 'cfd3d7' },
{ id: 4, name: 'enhancement', color: 'a2eeef' },
{ id: 5, name: 'good first issue', color: '7057ff' },
{ id: 6, name: 'help wanted', color: '008672' },
{ id: 7, name: 'invalid', color: 'e4e669' },
{ id: 8, name: 'question', color: 'd876e3' },
{ id: 9, name: 'wontfix', color: 'ffffff' }
])
const selected = ref([])
const labels = computed({
get: () => selected.value,
set: async (labels) => {
const promises = labels.map(async (label) => {
if (label.id) {
return label
}
// In a real app, you would make an API call to create the label
const response = {
id: options.value.length + 1,
name: label.name,
color: generateColorFromString(label.name)
}
options.value.push(response)
return response
})
selected.value = await Promise.all(promises)
}
})
function hashCode(str) {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash)
}
return hash
}
function intToRGB(i) {
const c = (i & 0x00FFFFFF)
.toString(16)
.toUpperCase()
return '00000'.substring(0, 6 - c.length) + c
}
function generateColorFromString(str) {
return intToRGB(hashCode(str))
}
</script>
<template>
<USelectMenu
v-model="labels"
by="id"
name="labels"
:options="options"
option-attribute="name"
clearable
multiple
searchable
creatable
>
<template #label>
<template v-if="labels.length">
<span class="flex items-center -space-x-1">
<span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
</span>
<span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
</template>
<template v-else>
<span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
</template>
</template>
<template #option="{ option }">
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
:style="{ background: `#${option.color}` }"
/>
<span class="truncate">{{ option.name }}</span>
</template>
<template #option-create="{ option }">
<span class="flex-shrink-0">New label:</span>
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
:style="{ background: `#${generateColorFromString(option.name)}` }"
/>
<span class="block truncate">{{ option.name }}</span>
</template>
</USelectMenu>
</template>

View File

@@ -0,0 +1,105 @@
<script setup lang="ts">
const options = ref([
{ id: 1, name: 'bug', color: 'd73a4a' },
{ id: 2, name: 'documentation', color: '0075ca' },
{ id: 3, name: 'duplicate', color: 'cfd3d7' },
{ id: 4, name: 'enhancement', color: 'a2eeef' },
{ id: 5, name: 'good first issue', color: '7057ff' },
{ id: 6, name: 'help wanted', color: '008672' },
{ id: 7, name: 'invalid', color: 'e4e669' },
{ id: 8, name: 'question', color: 'd876e3' },
{ id: 9, name: 'wontfix', color: 'ffffff' }
])
const selected = ref([])
const labels = computed({
get: () => selected.value,
set: async (labels) => {
const promises = labels.map(async (label) => {
if (label.id) {
return label
}
// In a real app, you would make an API call to create the label
const response = {
id: options.value.length + 1,
name: label.name,
color: generateColorFromString(label.name)
}
options.value.push(response)
return response
})
selected.value = await Promise.all(promises)
}
})
function hashCode(str) {
let hash = 0
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash)
}
return hash
}
function intToRGB(i) {
const c = (i & 0x00FFFFFF)
.toString(16)
.toUpperCase()
return '00000'.substring(0, 6 - c.length) + c
}
function generateColorFromString(str) {
return intToRGB(hashCode(str))
}
</script>
<template>
<USelectMenu
v-model="labels"
by="id"
name="labels"
:options="options"
option-attribute="name"
clearable
multiple
searchable
creatable
>
<template #label>
<template v-if="labels.length">
<span class="flex items-center -space-x-1">
<span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
</span>
<span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
</template>
<template v-else>
<span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
</template>
</template>
<template #option="{ option }">
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
:style="{ background: `#${option.color}` }"
/>
<span class="truncate">{{ option.name }}</span>
</template>
<template #option-create="{ option }">
<span class="flex-shrink-0">New label:</span>
<span
class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
:style="{ background: `#${generateColorFromString(option.name)}` }"
/>
<span class="block truncate">{{ option.name }}</span>
</template>
<template #clearable="{ onClear }">
<UButton icon="i-heroicons-trash-20-solid" size="xs" class="text-gray-400 dark:text-gray-500" variant="ghost" @click.capture.stop="onClear" />
</template>
</USelectMenu>
</template>

View File

@@ -141,74 +141,6 @@ Badge
You can customize the whole [preset](#preset) by using the `ui` prop.
::
### Icon
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
::component-card
---
props:
icon: 'i-heroicons-rocket-launch'
size: 'sm'
color: 'primary'
variant: 'solid'
label: Badge
trailing: false
options:
- name: variant
restriction: only
values:
- solid
excludedProps:
- icon
- label
---
::
## Slots
### `leading`
Use the `#leading` slot to set the content of the leading icon.
::component-card
---
slots:
leading: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
baseProps:
color: 'gray'
props:
label: Badge
color: 'gray'
excludedProps:
- color
---
#leading
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs"}
::
### `trailing`
Use the `#trailing` slot to set the content of the trailing icon.
::component-card
---
slots:
trailing: <UIcon name="i-heroicons-rocket-launch" class="w-4 h-4" />
props:
label: Badge
color: 'gray'
excludedProps:
- color
---
#trailing
:u-icon{name="i-heroicons-rocket-launch" class="w-4 h-4"}
::
## Props
:component-props

View File

@@ -142,7 +142,7 @@ props:
### Loading
Use the `loading` prop to show a loading icon in the Input.
Use the `loading` prop to show a loading icon and disable the Input.
Use the `loading-icon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.

View File

@@ -14,7 +14,6 @@ The Link component is a wrapper around [`<NuxtLink>`](https://nuxt.com/docs/api/
- `inactive-class` prop to set a class when the link is inactive, `active-class` is used when active.
- `exact` prop to style with `active-class` when the link is active and the route is exactly the same as the current route.
- `exact-query` and `exact-hash` props to style with `active-class` when the link is active and the query or hash is exactly the same as the current query or hash.
- use `exact-query="partial"` to style with `active-class` when the link is active and the query partially match the current query.
The incentive behind this is to provide the same API as NuxtLink back in Nuxt 2 / Vue 2. You can read more about it in the Vue Router [migration from Vue 2](https://router.vuejs.org/guide/migration/#removal-of-the-exact-prop-in-router-link) guide.

View File

@@ -137,9 +137,9 @@ excludedProps:
### Timeout
Use the `timeout` prop to configure how long the Notification will remain. The default value is `5000`, set it to `0` to disable the timeout. The `pauseTimeoutOnHover` prop (`true` by default) controls whether hovering the notification should pause the timeout.
Use the `timeout` prop to configure how long the Notification will remain. The default value is `5000`, set it to `0` to disable the timeout.
You will see a progress bar at the bottom of the Notification which will indicate the remaining time. When hovering the Notification, the progress bar will be paused if `pauseTimeoutOnHover` is enabled; otherwise, it won't stop.
You will see a progress bar at the bottom of the Notification which will indicate the remaining time. When hovering the Notification, the progress bar will be paused.
::component-card
---
@@ -149,7 +149,6 @@ baseProps:
description: 'This is a notification.'
props:
timeout: 60000
pauseTimeoutOnHover: true
---
::

View File

@@ -156,7 +156,38 @@ Use the `searchableLazy` prop to control the immediacy of data requests.
---
component: 'select-menu-example-search-async'
componentProps:
class: 'w-full lg:w-48'
class: 'w-full lg:w-48'
---
::
## Clearable
The `clearable` prop allows users to easily remove their selected option(s) with a clear button.
::component-example
---
component: 'select-menu-example-clearable'
componentProps:
class: 'w-full lg:w-52'
---
::
### Customization
#### Slot Props
The slot provides four key props:
| Prop | Type | Description |
|------|------|-------------|
| `selected` | `Object` | The currently selected value/item in the component |
| `disabled` | `Boolean` | Whether the component is in a disabled state |
| `loading` | `Boolean` | Whether the component is in a loading state |
| `onClear` | `Function` | Callback function to clear the selected value when the clear button is clicked |
::component-example
---
component: 'select-menu-example-clearable-customization'
componentProps:
class: 'w-full lg:w-52'
---
::

View File

@@ -62,7 +62,7 @@ extraClass: 'overflow-hidden'
padding: false
component: 'table-example-columns-selectable'
componentProps:
class: 'flex-1 flex-col overflow-hidden min-h-[230px]'
class: 'flex-1 flex-col overflow-hidden'
---
::
@@ -346,7 +346,7 @@ componentProps:
### Contextmenu
Use the `contextmenu` listener on your Table to make the rows right-clickable. The function will receive the original event as the first argument and the row as the second argument.
Use the `contextmenu` listener on your Table to make the rows righ-clickable. The function will receive the original event as the first argument and the row as the second argument.
You can use this to open a [ContextMenu](/components/context-menu) for that row.

View File

@@ -27,7 +27,8 @@ export default defineNuxtConfig({
'@nuxtjs/plausible',
'@vueuse/nuxt',
'nuxt-component-meta',
'nuxt-cloudflare-analytics'
'nuxt-cloudflare-analytics',
'modules/content-examples-code'
],
site: {

View File

@@ -4,27 +4,27 @@
"type": "module",
"dependencies": {
"@iconify-json/heroicons": "^1.2.1",
"@iconify-json/simple-icons": "^1.2.14",
"@iconify-json/vscode-icons": "^1.2.3",
"@iconify-json/simple-icons": "^1.2.11",
"@iconify-json/vscode-icons": "^1.2.2",
"@nuxt/content": "^2.13.4",
"@nuxt/fonts": "^0.10.3",
"@nuxt/fonts": "^0.10.2",
"@nuxt/image": "^1.8.1",
"@nuxt/ui": "latest",
"@nuxt/ui-pro": "^1.5.0",
"@nuxtjs/plausible": "^1.2.0",
"@nuxtjs/plausible": "^1.0.3",
"@octokit/rest": "^21.0.2",
"@vueuse/nuxt": "^12.0.0",
"@vueuse/nuxt": "^11.2.0",
"date-fns": "^4.1.0",
"joi": "^17.13.3",
"nuxt": "^3.14.1592",
"nuxt": "^3.14.159",
"nuxt-cloudflare-analytics": "^1.0.8",
"nuxt-component-meta": "^0.9.0",
"nuxt-og-image": "^3.1.1",
"prettier": "^3.4.2",
"nuxt-og-image": "^3.0.8",
"prettier": "^3.3.3",
"ufo": "^1.5.4",
"v-calendar": "^3.1.2",
"valibot": "^0.42.1",
"yup": "^1.5.0",
"yup": "^1.4.0",
"zod": "^3.23.8"
}
}

View File

@@ -1,8 +1,8 @@
{
"name": "@nuxt/ui",
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
"version": "2.20.0",
"packageManager": "pnpm@9.15.0",
"version": "2.19.2",
"packageManager": "pnpm@9.12.3",
"repository": "nuxt/ui",
"homepage": "https://ui.nuxt.com",
"type": "module",
@@ -35,8 +35,8 @@
"@headlessui/tailwindcss": "^0.2.1",
"@headlessui/vue": "^1.7.23",
"@iconify-json/heroicons": "^1.2.1",
"@nuxt/icon": "^1.9.1",
"@nuxt/kit": "^3.14.1592",
"@nuxt/icon": "^1.7.2",
"@nuxt/kit": "^3.14.159",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/tailwindcss": "^6.12.2",
"@popperjs/core": "^2.11.8",
@@ -44,45 +44,44 @@
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@vueuse/core": "^12.0.0",
"@vueuse/integrations": "^12.0.0",
"@vueuse/math": "^12.0.0",
"@vueuse/core": "^11.2.0",
"@vueuse/integrations": "^11.2.0",
"@vueuse/math": "^11.2.0",
"defu": "^6.1.4",
"fuse.js": "^7.0.0",
"ohash": "^1.1.4",
"pathe": "^1.1.2",
"scule": "^1.3.0",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^3.4.16"
"tailwind-merge": "^2.5.4",
"tailwindcss": "^3.4.14"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.7.2",
"@nuxt/eslint-config": "^0.6.1",
"@nuxt/module-builder": "^0.8.4",
"@nuxt/test-utils": "^3.15.1",
"@release-it/conventional-changelog": "^9.0.3",
"@nuxt/test-utils": "^3.14.4",
"@release-it/conventional-changelog": "^9.0.2",
"@vue/test-utils": "^2.4.6",
"eslint": "^9.16.0",
"eslint": "^9.14.0",
"happy-dom": "^14.12.3",
"joi": "^17.13.3",
"nuxt": "^3.14.1592",
"nuxt": "^3.14.159",
"release-it": "^17.10.0",
"superstruct": "^2.0.2",
"typescript": "^5.6.3",
"unbuild": "^2.0.0",
"valibot": "^0.42.1",
"valibot30": "npm:valibot@0.30.0",
"valibot31": "npm:valibot@0.31.0",
"vitest": "^2.1.8",
"vitest": "^2.1.4",
"vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^2.1.10",
"yup": "^1.5.0",
"yup": "^1.4.0",
"zod": "^3.23.8"
},
"resolutions": {
"@nuxt/ui": "workspace:*",
"@nuxt/content": "2.13.2",
"@nuxtjs/mdc": "0.9.0",
"chokidar": "3.6.0",
"typescript": "5.6.3"
"nuxt": "3.13.2",
"@nuxt/kit": "3.13.2"
}
}

View File

@@ -9,6 +9,6 @@
},
"dependencies": {
"@nuxt/ui": "latest",
"nuxt": "^3.14.1592"
"nuxt": "^3.14.159"
}
}

4248
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -126,7 +126,7 @@ export default defineComponent({
default: () => ({})
}
},
emits: ['open', 'close'],
emits: ['open'],
setup(props, { emit }) {
const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class'))
@@ -142,8 +142,6 @@ export default defineComponent({
if (!isOpenBefore && isOpenAfter) {
emit('open', index)
} else if (isOpenBefore && !isOpenAfter) {
emit('close', index)
}
}
}, { immediate: true })

View File

@@ -1,18 +1,6 @@
<template>
<span :class="badgeClass" v-bind="attrs">
<slot name="leading">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
</slot>
<slot>
<span v-if="label">
{{ label }}
</span>
</slot>
<slot name="trailing">
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
</slot>
<slot>{{ label }}</slot>
</span>
</template>
@@ -20,7 +8,6 @@
import { computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import { useUI } from '../../composables/useUI'
import { mergeConfig } from '../../utils'
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
@@ -32,9 +19,6 @@ import { badge } from '#ui/ui.config'
const config = mergeConfig<typeof badge>(appConfig.ui.strategy, appConfig.ui.badge, badge)
export default defineComponent({
components: {
UIcon
},
inheritAttrs: false,
props: {
size: {
@@ -65,26 +49,6 @@ export default defineComponent({
type: [String, Number],
default: null
},
icon: {
type: String,
default: null
},
leadingIcon: {
type: String,
default: null
},
trailingIcon: {
type: String,
default: null
},
trailing: {
type: Boolean,
default: false
},
leading: {
type: Boolean,
default: false
},
class: {
type: [String, Object, Array] as PropType<any>,
default: () => ''
@@ -99,14 +63,6 @@ export default defineComponent({
const { size, rounded } = useInjectButtonGroup({ ui, props })
const isLeading = computed(() => {
return (props.icon && props.leading) || (props.icon && !props.trailing) || !props.trailing || props.leadingIcon
})
const isTrailing = computed(() => {
return (props.icon && props.trailing) || props.trailing || props.trailingIcon
})
const badgeClass = computed(() => {
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
@@ -115,42 +71,13 @@ export default defineComponent({
ui.value.font,
rounded.value,
ui.value.size[size.value],
ui.value.gap[size.value],
variant?.replaceAll('{color}', props.color)
), props.class)
})
const leadingIconName = computed(() => {
return props.leadingIcon || props.icon
})
const trailingIconName = computed(() => {
return props.trailingIcon || props.icon
})
const leadingIconClass = computed(() => {
return twJoin(
ui.value.icon.base,
ui.value.icon.size[size.value]
)
})
const trailingIconClass = computed(() => {
return twJoin(
ui.value.icon.base,
ui.value.icon.size[size.value]
)
})
return {
attrs,
isLeading,
isTrailing,
badgeClass,
leadingIconName,
trailingIconName,
leadingIconClass,
trailingIconClass
badgeClass
}
}
})

View File

@@ -106,7 +106,7 @@ export default defineComponent({
default: () => ''
},
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
type: Object as PropType<DeepPartial<typeof config & { strategy?: Strategy }>>,
default: undefined
}
},

View File

@@ -32,8 +32,8 @@
</template>
<script lang="ts">
import { isEqual, diff } from 'ohash'
import { type PropType, defineComponent } from 'vue'
import { isEqual } from 'ohash'
import { defineComponent } from 'vue'
import { nuxtLinkProps } from '../../utils'
export default defineComponent({
@@ -61,7 +61,7 @@ export default defineComponent({
default: false
},
exactQuery: {
type: [Boolean, String] as PropType<boolean | 'partial'>,
type: Boolean,
default: false
},
exactHash: {
@@ -74,21 +74,9 @@ export default defineComponent({
}
},
setup(props) {
function isPartiallyEqual(item1, item2) {
const diffedKeys = diff(item1, item2).reduce((filtered, q) => {
if (q.type === 'added') {
filtered.push(q.key)
}
return filtered
}, [])
return isEqual(item1, item2, { excludeKeys: key => diffedKeys.includes(key) })
}
function resolveLinkClass(route, $route, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) {
if (props.exactQuery === 'partial') {
if (!isPartiallyEqual(route.query, $route.query)) return props.inactiveClass
} else if (props.exactQuery === true) {
if (!isEqual(route.query, $route.query)) return props.inactiveClass
if (props.exactQuery && !isEqual(route.query, $route.query)) {
return props.inactiveClass
}
if (props.exactHash && route.hash !== $route.hash) {
return props.inactiveClass

View File

@@ -60,8 +60,6 @@ export default defineComponent({
const formId = useId()
const bus = useEventBus<FormEvent>(`form-${formId}`)
const parsedValue = ref(null)
onMounted(() => {
bus.on(async (event) => {
if (event.type !== 'submit' && props.validateOn?.includes(event.type)) {
@@ -89,7 +87,7 @@ export default defineComponent({
if (errors) {
errs = errs.concat(errors)
} else {
parsedValue.value = result
Object.assign(props.state, result)
}
}
@@ -132,7 +130,7 @@ export default defineComponent({
if (props.validateOn?.includes('submit')) {
await validate()
}
event.data = props.schema ? parsedValue.value : props.state
event.data = props.state
emit('submit', event)
} catch (error) {
if (!(error instanceof FormException)) {
@@ -323,7 +321,7 @@ async function validateYupSchema(
schema: YupObjectSchema<any>
): Promise<ValidateReturnSchema<typeof state>> {
try {
const result = await schema.validate(state, { abortEarly: false })
const result = schema.validateSync(state, { abortEarly: false })
return {
errors: null,
result

View File

@@ -293,24 +293,6 @@ export default defineComponent({
const size = computed(() => sizeButtonGroup.value ?? sizeFormGroup.value)
const by = computed(() => {
if (!props.by) return undefined
if (typeof props.by === 'function') {
return props.by
}
const key = props.by
const hasDot = key.indexOf('.')
if (hasDot > 0) {
return (a: any, z: any) => {
return accessor(a, key) === accessor(z, key)
}
}
return key
})
const internalQuery = ref('')
const query = computed({
get() {
@@ -323,7 +305,9 @@ export default defineComponent({
})
const label = computed(() => {
if (!props.modelValue) return null
if (!props.modelValue) {
return
}
function getValue(value: any) {
if (props.valueAttribute) {
@@ -334,7 +318,7 @@ export default defineComponent({
}
function compareValues(value1: any, value2: any) {
if (by.value && typeof by.value !== 'function' && typeof value1 === 'object' && typeof value2 === 'object') {
if (props.by && typeof value1 === 'object' && typeof value2 === 'object') {
return isEqual(value1[props.by], value2[props.by])
}
return isEqual(value1, value2)
@@ -523,9 +507,7 @@ export default defineComponent({
query,
accessor,
onUpdate,
onQueryChange,
// eslint-disable-next-line vue/no-dupe-keys
by
onQueryChange
}
}
})

View File

@@ -39,6 +39,18 @@
<span v-if="label" :class="uiMenu.label">{{ label }}</span>
<span v-else :class="uiMenu.label">{{ placeholder || '&nbsp;' }}</span>
</slot>
<span v-if="canClearValue" :class="clearableWrapperClass">
<slot name="clearable" :selected="selected" :disabled="disabled" :loading="loading" @clear="onClear">
<UButton
:icon="clearableIcon"
size="xs"
class="p-0"
:class="clearableButtonClass"
variant="ghost"
@click.capture.stop="onClear"
/>
</slot>
</span>
<span v-if="(isTrailing && trailingIconName) || $slots.trailing" :class="trailingWrapperIconClass">
<slot name="trailing" :selected="selected" :disabled="disabled" :loading="loading">
@@ -149,6 +161,7 @@ import { useFormGroup } from '../../composables/useFormGroup'
import { get, mergeConfig } from '../../utils'
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
import type { SelectSize, SelectColor, SelectVariant, PopperOptions, Strategy, DeepPartial } from '../../types/index'
import type { Button } from '../../types/button'
// @ts-expect-error
import appConfig from '#build/app.config'
import { select, selectMenu } from '#ui/ui.config'
@@ -333,9 +346,18 @@ export default defineComponent({
uiMenu: {
type: Object as PropType<DeepPartial<typeof configMenu> & { strategy?: Strategy }>,
default: () => ({})
},
clearable: {
type: Boolean,
default: false
},
clearableIcon: {
type: String,
default: () => config.default.clerableIcon
}
},
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change'],
emits: ['update:modelValue', 'update:query', 'open', 'close', 'change', 'clear'],
setup(props, { emit, slots }) {
if (import.meta.dev && props.multiple && !Array.isArray(props.modelValue)) {
console.warn(`[@nuxt/ui] The USelectMenu components needs to have a modelValue of type Array when using the multiple prop. Got '${typeof props.modelValue}' instead.`, props.modelValue)
@@ -348,24 +370,6 @@ export default defineComponent({
const [trigger, container] = usePopper(popper.value)
const by = computed(() => {
if (!props.by) return undefined
if (typeof props.by === 'function') {
return props.by
}
const key = props.by
const hasDot = key.indexOf('.')
if (hasDot > 0) {
return (a: any, z: any) => {
return accessor(a, key) === accessor(z, key)
}
}
return key
})
const { size: sizeButtonGroup, rounded } = useInjectButtonGroup({ ui, props })
const { emitFormBlur, emitFormChange, inputId, color, size: sizeFormGroup, name } = useFormGroup(props, config)
@@ -384,8 +388,8 @@ export default defineComponent({
const selected = computed(() => {
function compareValues(value1: any, value2: any) {
if (by.value && typeof by.value !== 'function' && typeof value1 === 'object' && typeof value2 === 'object') {
return isEqual(value1[by.value], value2[by.value])
if (props.by && typeof value1 === 'object' && typeof value2 === 'object') {
return isEqual(value1[props.by], value2[props.by])
}
return isEqual(value1, value2)
}
@@ -417,12 +421,16 @@ export default defineComponent({
})
const label = computed(() => {
if (!props.modelValue) return null
if (!selected.value) return null
if (props.valueAttribute) {
return accessor(selected.value as Record<string, any>, props.optionAttribute)
}
if (Array.isArray(props.modelValue) && props.modelValue.length) {
return `${props.modelValue.length} selected`
} else if (['string', 'number'].includes(typeof props.modelValue)) {
return props.valueAttribute ? accessor(selected.value, props.optionAttribute) : props.modelValue
return props.modelValue
}
return accessor(props.modelValue as Record<string, any>, props.optionAttribute)
@@ -460,6 +468,23 @@ export default defineComponent({
return props.leadingIcon || props.icon
})
const canClearValue = computed(() => props.clearable && (Array.isArray(selected.value) ? selected.value.length > 0 : !!selected.value))
const clearableWrapperClass = computed(() => {
return twJoin(
ui.value.icon.clearable.wrapper,
ui.value.icon.clearable.padding[size.value]
)
})
const clearableButtonClass = computed(() => {
return twJoin(
ui.value.icon.base,
color.value && appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
props.loading && ui.value.icon.loading
)
})
const trailingIconName = computed(() => {
if (props.loading && !isLeading.value) {
return props.loadingIcon
@@ -488,7 +513,6 @@ export default defineComponent({
const trailingWrapperIconClass = computed(() => {
return twJoin(
ui.value.icon.trailing.wrapper,
ui.value.icon.trailing.pointer,
ui.value.icon.trailing.padding[size.value]
)
})
@@ -563,7 +587,7 @@ export default defineComponent({
return ['string', 'number'].includes(typeof props.modelValue) ? query.value : { [props.optionAttribute]: query.value }
})
function clearOnClose() {
function handleClearSearchOnClose() {
if (props.clearSearchOnClose) {
query.value = ''
}
@@ -573,7 +597,7 @@ export default defineComponent({
if (value) {
emit('open')
} else {
clearOnClose()
handleClearSearchOnClose()
emit('close')
emitFormBlur()
}
@@ -593,6 +617,28 @@ export default defineComponent({
query.value = event.target.value
}
function onClear() {
if (canClearValue.value) {
emit('update:modelValue', props.multiple ? [] : null)
emit('clear')
emitFormChange()
}
}
function trailingSlotProps() {
const slotProps: Record<string, any> = {
selected: selected.value,
loading: props.loading,
disabled: props.disabled
}
if (props.clearable) {
slotProps.onClear = onClear
}
return slotProps
}
provideUseId(() => useId())
return {
@@ -612,6 +658,7 @@ export default defineComponent({
label,
accessor,
isLeading,
onClear,
isTrailing,
// eslint-disable-next-line vue/no-dupe-keys
selectClass,
@@ -627,8 +674,10 @@ export default defineComponent({
query,
onUpdate,
onQueryChange,
// eslint-disable-next-line vue/no-dupe-keys
by
trailingSlotProps,
canClearValue,
clearableWrapperClass,
clearableButtonClass
}
}
})

View File

@@ -117,10 +117,6 @@ export default defineComponent({
ui: {
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
default: () => ({})
},
pauseTimeoutOnHover: {
type: Boolean,
default: true
}
},
emits: ['close'],
@@ -161,13 +157,13 @@ export default defineComponent({
})
function onMouseover() {
if (props.pauseTimeoutOnHover && timer) {
if (timer) {
timer.pause()
}
}
function onMouseleave() {
if (props.pauseTimeoutOnHover && timer) {
if (timer) {
timer.resume()
}
}

View File

@@ -1,3 +0,0 @@
import type colors from '#ui-colors'
export type CheckboxColor = typeof colors[number]

View File

@@ -1,3 +1,3 @@
import type { divider } from '../ui.config'
import type { divider } from '#ui/ui.config'
export type DividerSize = keyof typeof divider.border.size.horizontal | keyof typeof divider.border.size.vertical

View File

@@ -4,7 +4,6 @@ export * from './avatar'
export * from './badge'
export * from './breadcrumb'
export * from './button'
export * from './checkbox'
export * from './chip'
export * from './clipboard'
export * from './command-palette'

View File

@@ -6,7 +6,7 @@ export interface Link extends NuxtLinkProps {
disabled?: boolean
active?: boolean
exact?: boolean
exactQuery?: boolean | 'partial'
exactQuery?: boolean
exactHash?: boolean
inactiveClass?: string
}

View File

@@ -7,7 +7,7 @@ export interface TightMap<O = any> {
export type DeepPartial<T, O = any> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P], O>
: T[P] extends string ? string : T[P];
: T[P];
} & {
[key: string]: O | TightMap<O>
}

View File

@@ -1,5 +1,3 @@
import type { ButtonColor, ButtonSize, ButtonVariant, CheckboxColor, ProgressAnimation, ProgressColor } from '../../types'
export default {
wrapper: 'relative overflow-x-auto',
base: 'min-w-full table-fixed',
@@ -53,23 +51,23 @@ export default {
icon: 'i-heroicons-arrows-up-down-20-solid',
trailing: true,
square: true,
color: 'gray' as ButtonColor,
variant: 'ghost' as ButtonVariant,
color: 'gray' as const,
variant: 'ghost' as const,
class: '-m-1.5'
},
expandButton: {
icon: 'i-heroicons-chevron-down',
color: 'gray' as ButtonColor,
variant: 'ghost' as ButtonVariant,
size: 'xs' as ButtonSize,
color: 'gray' as const,
variant: 'ghost' as const,
size: 'xs' as const,
class: '-my-1.5 align-middle'
},
checkbox: {
color: 'primary' as CheckboxColor
color: 'primary' as const
},
progress: {
color: 'primary' as ProgressColor,
animation: 'carousel' as ProgressAnimation
color: 'primary' as const,
animation: 'carousel' as const
},
loadingState: {
icon: 'i-heroicons-arrow-path-20-solid',

View File

@@ -16,7 +16,7 @@ export default {
openIcon: 'i-heroicons-chevron-down-20-solid',
closeIcon: '',
class: 'mb-1.5 w-full',
variant: 'soft',
variant: 'soft' as const,
truncate: true
}
}

View File

@@ -1,5 +1,3 @@
import type { AvatarSize, ButtonColor, ButtonSize, ButtonVariant } from '../../types'
export default {
wrapper: 'w-full relative overflow-hidden',
inner: 'w-0 flex-1',
@@ -15,7 +13,7 @@ export default {
},
avatar: {
base: 'flex-shrink-0 self-center',
size: 'md' as AvatarSize
size: 'md' as const
},
color: {
white: {
@@ -34,9 +32,9 @@ export default {
icon: null,
closeButton: null,
actionButton: {
size: 'xs' as ButtonSize,
color: 'primary' as ButtonColor,
variant: 'link' as ButtonVariant
size: 'xs' as const,
color: 'primary' as const,
variant: 'link' as const
}
}
}

View File

@@ -8,12 +8,6 @@ export default {
md: 'text-sm px-2 py-1',
lg: 'text-sm px-2.5 py-1.5'
},
gap: {
xs: 'gap-0.5',
sm: 'gap-1',
md: 'gap-1',
lg: 'gap-1.5'
},
color: {
white: {
solid: 'ring-1 ring-inset ring-gray-300 dark:ring-gray-700 text-gray-900 dark:text-white bg-white dark:bg-gray-900'
@@ -31,15 +25,6 @@ export default {
soft: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400',
subtle: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400 ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 ring-opacity-25 dark:ring-opacity-25'
},
icon: {
base: 'flex-shrink-0',
size: {
xs: 'h-4 w-4',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-5 w-5'
}
},
default: {
size: 'sm',
variant: 'solid',

View File

@@ -1,5 +1,3 @@
import type { ButtonColor } from '../../types'
export default {
wrapper: 'relative',
container: 'relative w-full flex overflow-x-auto snap-x snap-mandatory scroll-smooth',
@@ -15,12 +13,12 @@ export default {
},
default: {
prevButton: {
color: 'black' as ButtonColor,
color: 'black' as const,
class: 'rtl:[&_span:first-child]:rotate-180 absolute start-4 top-1/2 transform -translate-y-1/2 rounded-full',
icon: 'i-heroicons-chevron-left-20-solid'
},
nextButton: {
color: 'black' as ButtonColor,
color: 'black' as const,
class: 'rtl:[&_span:last-child]:rotate-180 absolute end-4 top-1/2 transform -translate-y-1/2 rounded-full',
icon: 'i-heroicons-chevron-right-20-solid'
}

View File

@@ -1,4 +1,3 @@
import type { AvatarSize } from '../../types'
import { arrow } from '../popper'
export default {
@@ -29,7 +28,7 @@ export default {
},
avatar: {
base: 'flex-shrink-0',
size: '2xs' as AvatarSize
size: '2xs' as const
},
label: 'truncate',
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5 ms-auto'

View File

@@ -98,6 +98,18 @@ export default {
'lg': 'px-3.5',
'xl': 'px-3.5'
}
},
clearable: {
wrapper: 'absolute inset-y-0 end-6 flex items-center',
pointer: 'pointer-events-auto',
padding: {
'2xs': 'px-2',
'xs': 'px-2.5',
'sm': 'px-2.5',
'md': 'px-3',
'lg': 'px-3.5',
'xl': 'px-3.5'
}
}
},
default: {

View File

@@ -1,4 +1,3 @@
import type { AvatarSize } from '../../types'
import { arrow } from '../popper'
export default {
@@ -37,7 +36,7 @@ export default {
},
avatar: {
base: 'flex-shrink-0',
size: '2xs' as AvatarSize
size: '2xs' as const
},
chip: {
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'

View File

@@ -9,6 +9,7 @@ export default {
color: 'white',
variant: 'outline',
loadingIcon: 'i-heroicons-arrow-path-20-solid',
trailingIcon: 'i-heroicons-chevron-down-20-solid'
trailingIcon: 'i-heroicons-chevron-down-20-solid',
clerableIcon: 'i-heroicons-x-mark-20-solid'
}
}

View File

@@ -1,5 +1,3 @@
import type { AvatarSize } from '../../types'
export default {
wrapper: {
base: 'flex items-center align-center text-center',
@@ -44,11 +42,11 @@ export default {
},
avatar: {
base: 'flex-shrink-0',
size: '2xs' as AvatarSize
size: '2xs' as const
},
label: 'text-sm',
default: {
size: '2xs',
type: 'solid'
size: '2xs' as const,
type: 'solid' as const
}
}

View File

@@ -1,5 +1,3 @@
import type { AvatarSize } from '../../types'
export default {
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800',
container: 'relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2',
@@ -48,7 +46,7 @@ export default {
},
avatar: {
base: 'flex-shrink-0',
size: '2xs' as AvatarSize
size: '2xs' as const
},
chip: {
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'

View File

@@ -1,5 +1,3 @@
import type { AvatarSize, BadgeColor, BadgeSize, BadgeVariant } from '../../types'
export default {
wrapper: 'relative w-full flex items-center justify-between',
container: 'flex items-center min-w-0',
@@ -17,12 +15,12 @@ export default {
},
avatar: {
base: 'flex-shrink-0',
size: '2xs' as AvatarSize
size: '2xs' as const
},
badge: {
base: 'flex-shrink-0 ms-auto relative rounded',
color: 'gray' as BadgeColor,
variant: 'solid' as BadgeVariant,
size: 'xs' as BadgeSize
color: 'gray' as const,
variant: 'solid' as const,
size: 'xs' as const
}
}

View File

@@ -1,5 +1,3 @@
import type { ButtonColor } from '../../types'
export default {
wrapper: 'flex items-center -space-x-px',
base: '',
@@ -7,28 +5,28 @@ export default {
default: {
size: 'sm',
activeButton: {
color: 'primary' as ButtonColor
color: 'primary' as const
},
inactiveButton: {
color: 'white' as ButtonColor
color: 'white' as const
},
firstButton: {
color: 'white' as ButtonColor,
color: 'white' as const,
class: 'rtl:[&_span:first-child]:rotate-180',
icon: 'i-heroicons-chevron-double-left-20-solid'
},
lastButton: {
color: 'white' as ButtonColor,
color: 'white' as const,
class: 'rtl:[&_span:last-child]:rotate-180',
icon: 'i-heroicons-chevron-double-right-20-solid'
},
prevButton: {
color: 'white' as ButtonColor,
color: 'white' as const,
class: 'rtl:[&_span:first-child]:rotate-180',
icon: 'i-heroicons-chevron-left-20-solid'
},
nextButton: {
color: 'white' as ButtonColor,
color: 'white' as const,
class: 'rtl:[&_span:last-child]:rotate-180',
icon: 'i-heroicons-chevron-right-20-solid'
}

View File

@@ -1,5 +1,3 @@
import type { AvatarSize, BadgeColor, BadgeSize, BadgeVariant } from '../../types'
export default {
wrapper: 'relative',
base: 'group relative flex items-center gap-1.5 focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-1 focus-visible:before:ring-primary-500 dark:focus-visible:before:ring-primary-400 before:absolute before:inset-px before:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
@@ -19,13 +17,13 @@ export default {
},
avatar: {
base: 'flex-shrink-0',
size: '2xs' as AvatarSize
size: '2xs' as const
},
badge: {
base: 'flex-shrink-0 ms-auto relative rounded',
color: 'gray' as BadgeColor,
variant: 'solid' as BadgeVariant,
size: 'xs' as BadgeSize
color: 'gray' as const,
variant: 'solid' as const,
size: 'xs' as const
},
divider: {
wrapper: {

View File

@@ -1,5 +1,3 @@
import type { AvatarSize, ButtonColor, ButtonSize, ButtonVariant } from '../../types'
export default {
wrapper: 'w-full pointer-events-auto',
container: 'relative overflow-hidden',
@@ -19,7 +17,7 @@ export default {
},
avatar: {
base: 'flex-shrink-0 self-center',
size: 'md' as AvatarSize
size: 'md' as const
},
progress: {
base: 'absolute bottom-0 end-0 start-0 h-1',
@@ -40,13 +38,13 @@ export default {
timeout: 5000,
closeButton: {
icon: 'i-heroicons-x-mark-20-solid',
color: 'gray' as ButtonColor,
variant: 'link' as ButtonVariant,
color: 'gray' as const,
variant: 'link' as const,
padded: false
},
actionButton: {
size: 'xs' as ButtonSize,
color: 'white' as ButtonColor
size: 'xs' as const,
color: 'white' as const
}
}
}