mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-20 15:01:46 +01:00
Compare commits
56 Commits
issue-1987
...
v2.20.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b654c93e93 | ||
|
|
b7e04db645 | ||
|
|
e6034a2765 | ||
|
|
a8c38224c6 | ||
|
|
a9ef6406ea | ||
|
|
96e846ddee | ||
|
|
16dbc1b536 | ||
|
|
c6b2ae45e5 | ||
|
|
547c657ee7 | ||
|
|
b16b434041 | ||
|
|
fb12323304 | ||
|
|
0a404615ff | ||
|
|
cbf0f22efd | ||
|
|
4cde571e38 | ||
|
|
023497d144 | ||
|
|
56d4ca3b74 | ||
|
|
11b8c3d9db | ||
|
|
419a24f703 | ||
|
|
854bb81295 | ||
|
|
bf8e3954a4 | ||
|
|
637ec4d27b | ||
|
|
f3632ddee5 | ||
|
|
dbd2aed20b | ||
|
|
51c8b8e3e5 | ||
|
|
588a908358 | ||
|
|
d692a81b1e | ||
|
|
ec98d415b4 | ||
|
|
c80d2e6c12 | ||
|
|
ce61a2b6db | ||
|
|
eee5bb9939 | ||
|
|
d3804157ec | ||
|
|
03e24f4583 | ||
|
|
d0e626c551 | ||
|
|
670d8bfbac | ||
|
|
64b703df8d | ||
|
|
976b03f241 | ||
|
|
35e3b8c720 | ||
|
|
07ef771b17 | ||
|
|
5c75b5c490 | ||
|
|
53df9d9a8c | ||
|
|
0d1a76e3c6 | ||
|
|
b2ed4662af | ||
|
|
423c48879d | ||
|
|
acecff40ec | ||
|
|
1fd5fac295 | ||
|
|
b23f2decfc | ||
|
|
7154254ac2 | ||
|
|
49f85d55c5 | ||
|
|
97037864b3 | ||
|
|
0abccabc26 | ||
|
|
ac323c4ccc | ||
|
|
d4e408cfd8 | ||
|
|
f3bf69c233 | ||
|
|
d6daf466ac | ||
|
|
6e66990372 | ||
|
|
56e28d80db |
11
.github/ISSUE_TEMPLATE/bug-v3.yml
vendored
11
.github/ISSUE_TEMPLATE/bug-v3.yml
vendored
@@ -29,11 +29,20 @@ body:
|
|||||||
- Build Modules: `-`
|
- Build Modules: `-`
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: package
|
||||||
|
attributes:
|
||||||
|
label: Is this bug related to Nuxt or Vue?
|
||||||
|
options:
|
||||||
|
- Nuxt
|
||||||
|
- Vue
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
placeholder: v3.0.0-alpha.5
|
placeholder: v3.0.0-alpha.x
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -12,7 +12,7 @@ body:
|
|||||||
label: For what version of Nuxt UI are you suggesting this?
|
label: For what version of Nuxt UI are you suggesting this?
|
||||||
options:
|
options:
|
||||||
- v2.x
|
- v2.x
|
||||||
- v3-alpha
|
- v3.0.0-alpha.x
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/question.yml
vendored
2
.github/ISSUE_TEMPLATE/question.yml
vendored
@@ -12,7 +12,7 @@ body:
|
|||||||
label: For what version of Nuxt UI are you asking this question?
|
label: For what version of Nuxt UI are you asking this question?
|
||||||
options:
|
options:
|
||||||
- v2.x
|
- v2.x
|
||||||
- v3-alpha
|
- v3.0.0-alpha.x
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,5 +1,38 @@
|
|||||||
# Changelog
|
# 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)
|
## [2.19.2](https://github.com/nuxt/ui/compare/v2.19.1...v2.19.2) (2024-11-05)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[](https://ui.nuxt.com)
|
[](https://ui.nuxt.com)
|
||||||
|
|
||||||
# Nuxt UI
|
# Nuxt UI
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ Its goal is to provide everything related to UI when building a Nuxt app. This i
|
|||||||
- Keyboard shortcuts
|
- Keyboard shortcuts
|
||||||
- Bundled icons
|
- Bundled icons
|
||||||
- Fully typed
|
- Fully typed
|
||||||
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
- [Figma Kit](https://www.figma.com/community/file/1436401057300493073)
|
||||||
|
|
||||||
Read more on [ui.nuxt.com](https://ui.nuxt.com)
|
Read more on [ui.nuxt.com](https://ui.nuxt.com)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<NuxtLoadingIndicator />
|
<NuxtLoadingIndicator />
|
||||||
|
|
||||||
<Banner v-if="!$route.path.startsWith('/examples')" />
|
<!-- <Banner v-if="!$route.path.startsWith('/examples')" /> -->
|
||||||
|
|
||||||
<Header v-if="!$route.path.startsWith('/examples')" :links="links" />
|
<Header v-if="!$route.path.startsWith('/examples')" :links="links" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const id = 'nuxt-ui-banner-2'
|
const id = 'nuxt-ui-banner-3'
|
||||||
const to = 'https://ui3.nuxt.dev'
|
const to = '/pro/pricing'
|
||||||
|
|
||||||
const hideBanner = () => {
|
const hideBanner = () => {
|
||||||
localStorage.setItem(id, 'true')
|
localStorage.setItem(id, 'true')
|
||||||
@@ -28,9 +28,8 @@ if (import.meta.server) {
|
|||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="to"
|
v-if="to"
|
||||||
:to="to"
|
:to="to"
|
||||||
target="_blank"
|
|
||||||
class="focus:outline-none"
|
class="focus:outline-none"
|
||||||
aria-label="Nuxt UI Pro pricing"
|
aria-label="20% off on all Nuxt UI Pro products for Black Friday week"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<span class="absolute inset-0 " aria-hidden="true" />
|
<span class="absolute inset-0 " aria-hidden="true" />
|
||||||
@@ -40,19 +39,19 @@ if (import.meta.server) {
|
|||||||
<div class="lg:flex-1 hidden lg:flex items-center" />
|
<div class="lg:flex-1 hidden lg:flex items-center" />
|
||||||
|
|
||||||
<p class="text-sm font-medium text-white dark:text-gray-900 truncate">
|
<p class="text-sm font-medium text-white dark:text-gray-900 truncate">
|
||||||
<UIcon name="i-heroicons-rocket-launch" class="w-5 h-5 align-top flex-shrink-0 pointer-events-none mr-2" />
|
<UIcon name="i-ri-discount-percent-fill" class="size-5 align-top flex-shrink-0 pointer-events-none mr-2" />
|
||||||
<span class="font-semibold">Nuxt UI v3-alpha</span> has been released!
|
<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>!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<UButton
|
<!-- <UButton
|
||||||
to="https://ui3.nuxt.dev"
|
:to="to"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
label="Try it out"
|
label="Buy now"
|
||||||
color="black"
|
color="black"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
size="2xs"
|
size="2xs"
|
||||||
trailing-icon="i-heroicons-arrow-right-20-solid"
|
trailing-icon="i-heroicons-arrow-right-20-solid"
|
||||||
/>
|
/> -->
|
||||||
|
|
||||||
<div class="flex items-center justify-end lg:flex-1">
|
<div class="flex items-center justify-end lg:flex-1">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const { $ui } = useNuxtApp()
|
|||||||
const links = [{
|
const links = [{
|
||||||
icon: 'i-simple-icons-figma',
|
icon: 'i-simple-icons-figma',
|
||||||
label: 'Figma Kit',
|
label: 'Figma Kit',
|
||||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
to: 'https://www.figma.com/community/file/1436401057300493073',
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}, {
|
}, {
|
||||||
label: 'Playground',
|
label: 'Playground',
|
||||||
|
|||||||
@@ -10,12 +10,34 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<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">
|
<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">
|
||||||
<LogoPro v-if="$route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" />
|
<LogoPro v-if="$route.path.startsWith('/pro')" class="w-auto h-6 shrink-0" />
|
||||||
<Logo v-else class="w-auto h-6 shrink-0" />
|
<Logo v-else class="w-auto h-6 shrink-0" />
|
||||||
|
|
||||||
<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>
|
</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>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #right>
|
<template #right>
|
||||||
@@ -25,10 +47,10 @@
|
|||||||
<UContentSearchButton :label="null" />
|
<UContentSearchButton :label="null" />
|
||||||
</UTooltip>
|
</UTooltip>
|
||||||
|
|
||||||
<UColorModeButton />
|
<UColorModeButton class="hidden lg:inline-flex" />
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
to="https://github.com/nuxt/ui"
|
to="https://github.com/nuxt/ui/tree/dev"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
icon="i-simple-icons-github"
|
icon="i-simple-icons-github"
|
||||||
aria-label="GitHub"
|
aria-label="GitHub"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const ui = {
|
|||||||
inactive: 'text-gray-400 dark:text-gray-500'
|
inactive: 'text-gray-400 dark:text-gray-500'
|
||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
size: '2xs' as const
|
size: '2xs'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// Columns
|
// Columns
|
||||||
const columns = [{
|
const columns = [{
|
||||||
|
key: 'select',
|
||||||
|
class: 'w-2'
|
||||||
|
}, {
|
||||||
key: 'id',
|
key: 'id',
|
||||||
label: '#',
|
label: '#',
|
||||||
sortable: true
|
sortable: true
|
||||||
@@ -20,6 +23,7 @@ const columns = [{
|
|||||||
|
|
||||||
const selectedColumns = ref(columns)
|
const selectedColumns = ref(columns)
|
||||||
const columnsTable = computed(() => columns.filter(column => selectedColumns.value.includes(column)))
|
const columnsTable = computed(() => columns.filter(column => selectedColumns.value.includes(column)))
|
||||||
|
const excludeSelectColumn = computed(() => columns.filter(v => v.key !== 'select'))
|
||||||
|
|
||||||
// Selected Rows
|
// Selected Rows
|
||||||
const selectedRows = ref([])
|
const selectedRows = ref([])
|
||||||
@@ -153,7 +157,7 @@ const { data: todos, status } = await useLazyAsyncData<{
|
|||||||
</UButton>
|
</UButton>
|
||||||
</UDropdown>
|
</UDropdown>
|
||||||
|
|
||||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple>
|
<USelectMenu v-model="selectedColumns" :options="excludeSelectColumn" multiple>
|
||||||
<UButton
|
<UButton
|
||||||
icon="i-heroicons-view-columns"
|
icon="i-heroicons-view-columns"
|
||||||
color="gray"
|
color="gray"
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const people = [{
|
||||||
|
id: 1,
|
||||||
|
name: 'Lindsay Walton',
|
||||||
|
title: 'Front-end Developer',
|
||||||
|
email: 'lindsay.walton@example.com',
|
||||||
|
role: 'Member'
|
||||||
|
}, {
|
||||||
|
id: 2,
|
||||||
|
name: 'Courtney Henry',
|
||||||
|
title: 'Designer',
|
||||||
|
email: 'courtney.henry@example.com',
|
||||||
|
role: 'Admin'
|
||||||
|
}, {
|
||||||
|
id: 3,
|
||||||
|
name: 'Tom Cook',
|
||||||
|
title: 'Director of Product',
|
||||||
|
email: 'tom.cook@example.com',
|
||||||
|
role: 'Member'
|
||||||
|
}, {
|
||||||
|
id: 4,
|
||||||
|
name: 'Whitney Francis',
|
||||||
|
title: 'Copywriter',
|
||||||
|
email: 'whitney.francis@example.com',
|
||||||
|
role: 'Admin'
|
||||||
|
}, {
|
||||||
|
id: 5,
|
||||||
|
name: 'Leonard Krasner',
|
||||||
|
title: 'Senior Designer',
|
||||||
|
email: 'leonard.krasner@example.com',
|
||||||
|
role: 'Owner'
|
||||||
|
}, {
|
||||||
|
id: 6,
|
||||||
|
name: 'Floyd Miles',
|
||||||
|
title: 'Principal Designer',
|
||||||
|
email: 'floyd.miles@example.com',
|
||||||
|
role: 'Member'
|
||||||
|
}]
|
||||||
|
|
||||||
|
const selected = ref([people[1]])
|
||||||
|
|
||||||
|
const columns = [{
|
||||||
|
key: 'id',
|
||||||
|
label: 'ID'
|
||||||
|
}, {
|
||||||
|
key: 'name',
|
||||||
|
label: 'User name'
|
||||||
|
}, {
|
||||||
|
key: 'title',
|
||||||
|
label: 'Job position'
|
||||||
|
}, {
|
||||||
|
key: 'email',
|
||||||
|
label: 'Email'
|
||||||
|
}, {
|
||||||
|
key: 'role'
|
||||||
|
}, {
|
||||||
|
key: 'select',
|
||||||
|
class: 'w-2'
|
||||||
|
}]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UTable v-model="selected" :rows="people" :columns="columns" />
|
||||||
|
</template>
|
||||||
@@ -16,7 +16,7 @@ Its goal is to provide everything related to UI when building a Nuxt app. This i
|
|||||||
- Keyboard shortcuts
|
- Keyboard shortcuts
|
||||||
- Bundled icons
|
- Bundled icons
|
||||||
- Fully typed
|
- Fully typed
|
||||||
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
- [Figma Kit](https://www.figma.com/community/file/1436401057300493073)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,74 @@ Badge
|
|||||||
You can customize the whole [preset](#preset) by using the `ui` prop.
|
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
|
## Props
|
||||||
|
|
||||||
:component-props
|
:component-props
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ props:
|
|||||||
|
|
||||||
### Loading
|
### Loading
|
||||||
|
|
||||||
Use the `loading` prop to show a loading icon and disable the Input.
|
Use the `loading` prop to show a loading icon in 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`.
|
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`.
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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.
|
- `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` 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.
|
- `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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -137,9 +137,9 @@ excludedProps:
|
|||||||
|
|
||||||
### Timeout
|
### 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.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
::component-card
|
::component-card
|
||||||
---
|
---
|
||||||
@@ -149,6 +149,7 @@ baseProps:
|
|||||||
description: 'This is a notification.'
|
description: 'This is a notification.'
|
||||||
props:
|
props:
|
||||||
timeout: 60000
|
timeout: 60000
|
||||||
|
pauseTimeoutOnHover: true
|
||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ extraClass: 'overflow-hidden'
|
|||||||
padding: false
|
padding: false
|
||||||
component: 'table-example-columns-selectable'
|
component: 'table-example-columns-selectable'
|
||||||
componentProps:
|
componentProps:
|
||||||
class: 'flex-1 flex-col overflow-hidden'
|
class: 'flex-1 flex-col overflow-hidden min-h-[230px]'
|
||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
@@ -285,9 +285,68 @@ componentProps:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
||||||
|
#### Event Selectable
|
||||||
|
The `UTable` component provides two key events for handling row selection:
|
||||||
|
|
||||||
|
##### ***@select:all***
|
||||||
|
The `@select:all` event is emitted when the header checkbox in a selectable table is toggled. This event returns a boolean value indicating whether all rows are selected (true) or deselected (false).
|
||||||
|
|
||||||
|
##### ***@update:modelValue***
|
||||||
|
The `@update:modelValue` event is emitted whenever the selection state changes, including both individual row selection and bulk selection. This event returns an array containing the currently selected rows.
|
||||||
|
|
||||||
|
Here's how to implement both events:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const selected = ref([])
|
||||||
|
|
||||||
|
const onHandleSelectAll = (isSelected: boolean) => {
|
||||||
|
console.log('All rows selected:', isSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onUpdateSelection = (selectedRows: any[]) => {
|
||||||
|
console.log('Currently selected rows:', selectedRows)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UTable
|
||||||
|
v-model="selected"
|
||||||
|
:rows="people"
|
||||||
|
@select:all="onHandleSelectAll"
|
||||||
|
@update:modelValue="onUpdateSelection"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Single Select Mode
|
||||||
|
Control how the select function allows only one row to be selected at a time.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- Allow only one row to be selectable at a time -->
|
||||||
|
<UTable :single-select="true" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Checkbox Placement
|
||||||
|
You can customize the checkbox column position by using the `select` key in the `columns` configuration.
|
||||||
|
|
||||||
|
::component-example{class="grid"}
|
||||||
|
---
|
||||||
|
extraClass: 'overflow-hidden'
|
||||||
|
padding: false
|
||||||
|
component: 'table-example-dynamically-render-selectable'
|
||||||
|
componentProps:
|
||||||
|
class: 'flex-1'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
### Contextmenu
|
### Contextmenu
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
You can use this to open a [ContextMenu](/components/context-menu) for that row.
|
You can use this to open a [ContextMenu](/components/context-menu) for that row.
|
||||||
|
|
||||||
@@ -393,7 +452,6 @@ Controls whether multiple rows can be expanded simultaneously in the table.
|
|||||||
<!-- Or simply -->
|
<!-- Or simply -->
|
||||||
<UTable />
|
<UTable />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Disable Row Expansion
|
#### Disable Row Expansion
|
||||||
@@ -534,6 +592,82 @@ componentProps:
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### `select-header`
|
||||||
|
This slot allows you to customize the checkbox appearance in the table header for selecting all rows at once while using feature [Selectable](#selectable).
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<UTable v-model="selectable">
|
||||||
|
<template #select-header="{ checked, change, indeterminate }">
|
||||||
|
<!-- Place your custom component here -->
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Props
|
||||||
|
|
||||||
|
| Prop | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `checked` | `Boolean` | Indicates if all rows are selected |
|
||||||
|
| `change` | `Function` | Function to handle selection state changes. Must receive a boolean value (true/false) |
|
||||||
|
| `indeterminate` | `Boolean` | Indicates partial selection (when some rows are selected) |
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<UTable>
|
||||||
|
<!-- Header checkbox customization -->
|
||||||
|
<template #select-header="{ indeterminate, checked, change }">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:indeterminate="indeterminate"
|
||||||
|
:checked="checked"
|
||||||
|
@change="e => change(e.target.checked)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `select-data`
|
||||||
|
This slot allows you to customize the checkbox appearance for each row in the table while using feature [Selectable](#selectable).
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<UTable v-model="selectable">
|
||||||
|
<template #select-data="{ checked, change }">
|
||||||
|
<!-- Place your custom component here -->
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Props
|
||||||
|
|
||||||
|
| Prop | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `checked` | `Boolean` | Indicates if the current row is selected |
|
||||||
|
| `change` | `Function` | Function to handle selection state changes. Must receive a boolean value (true/false) |
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<UTable>
|
||||||
|
<!-- Row checkbox customization -->
|
||||||
|
<template #select-data="{ checked, change }">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="checked"
|
||||||
|
@change="e => change(e.target.checked)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
### `expand-action`
|
### `expand-action`
|
||||||
|
|
||||||
The `#expand-action` slot allows you to customize the expansion control interface for expandable table rows. This feature provides a flexible way to implement custom expand/collapse functionality while maintaining access to essential row data and state.
|
The `#expand-action` slot allows you to customize the expansion control interface for expandable table rows. This feature provides a flexible way to implement custom expand/collapse functionality while maintaining access to essential row data and state.
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ export default defineNuxtConfig({
|
|||||||
'@nuxtjs/plausible',
|
'@nuxtjs/plausible',
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'nuxt-component-meta',
|
'nuxt-component-meta',
|
||||||
'nuxt-cloudflare-analytics',
|
'nuxt-cloudflare-analytics'
|
||||||
'modules/content-examples-code'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
site: {
|
site: {
|
||||||
|
|||||||
@@ -4,27 +4,27 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/heroicons": "^1.2.1",
|
"@iconify-json/heroicons": "^1.2.1",
|
||||||
"@iconify-json/simple-icons": "^1.2.11",
|
"@iconify-json/simple-icons": "^1.2.14",
|
||||||
"@iconify-json/vscode-icons": "^1.2.2",
|
"@iconify-json/vscode-icons": "^1.2.3",
|
||||||
"@nuxt/content": "^2.13.4",
|
"@nuxt/content": "^2.13.4",
|
||||||
"@nuxt/fonts": "^0.10.2",
|
"@nuxt/fonts": "^0.10.3",
|
||||||
"@nuxt/image": "^1.8.1",
|
"@nuxt/image": "^1.8.1",
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"@nuxt/ui-pro": "^1.5.0",
|
"@nuxt/ui-pro": "^1.5.0",
|
||||||
"@nuxtjs/plausible": "^1.0.3",
|
"@nuxtjs/plausible": "^1.2.0",
|
||||||
"@octokit/rest": "^21.0.2",
|
"@octokit/rest": "^21.0.2",
|
||||||
"@vueuse/nuxt": "^11.2.0",
|
"@vueuse/nuxt": "^12.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"nuxt": "^3.14.159",
|
"nuxt": "^3.14.1592",
|
||||||
"nuxt-cloudflare-analytics": "^1.0.8",
|
"nuxt-cloudflare-analytics": "^1.0.8",
|
||||||
"nuxt-component-meta": "^0.9.0",
|
"nuxt-component-meta": "^0.9.0",
|
||||||
"nuxt-og-image": "^3.0.8",
|
"nuxt-og-image": "^3.1.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.4.2",
|
||||||
"ufo": "^1.5.4",
|
"ufo": "^1.5.4",
|
||||||
"v-calendar": "^3.1.2",
|
"v-calendar": "^3.1.2",
|
||||||
"valibot": "^0.42.1",
|
"valibot": "^0.42.1",
|
||||||
"yup": "^1.4.0",
|
"yup": "^1.5.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const communityLinks = computed(() => [{
|
|||||||
const resourcesLinks = [{
|
const resourcesLinks = [{
|
||||||
icon: 'i-simple-icons-figma',
|
icon: 'i-simple-icons-figma',
|
||||||
label: 'Figma Kit',
|
label: 'Figma Kit',
|
||||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
to: 'https://www.figma.com/community/file/1436401057300493073',
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}, {
|
}, {
|
||||||
label: 'Playground',
|
label: 'Playground',
|
||||||
|
|||||||
37
package.json
37
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxt/ui",
|
"name": "@nuxt/ui",
|
||||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||||
"version": "2.19.2",
|
"version": "2.20.0",
|
||||||
"packageManager": "pnpm@9.12.3",
|
"packageManager": "pnpm@9.15.0",
|
||||||
"repository": "nuxt/ui",
|
"repository": "nuxt/ui",
|
||||||
"homepage": "https://ui.nuxt.com",
|
"homepage": "https://ui.nuxt.com",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
"@headlessui/tailwindcss": "^0.2.1",
|
"@headlessui/tailwindcss": "^0.2.1",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@iconify-json/heroicons": "^1.2.1",
|
"@iconify-json/heroicons": "^1.2.1",
|
||||||
"@nuxt/icon": "^1.6.1",
|
"@nuxt/icon": "^1.9.1",
|
||||||
"@nuxt/kit": "^3.14.159",
|
"@nuxt/kit": "^3.14.1592",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
@@ -44,42 +44,45 @@
|
|||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@vueuse/core": "^11.2.0",
|
"@vueuse/core": "^12.0.0",
|
||||||
"@vueuse/integrations": "^11.2.0",
|
"@vueuse/integrations": "^12.0.0",
|
||||||
"@vueuse/math": "^11.2.0",
|
"@vueuse/math": "^12.0.0",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss": "^3.4.14"
|
"tailwindcss": "^3.4.16"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint-config": "^0.6.1",
|
"@nuxt/eslint-config": "^0.7.2",
|
||||||
"@nuxt/module-builder": "^0.8.4",
|
"@nuxt/module-builder": "^0.8.4",
|
||||||
"@nuxt/test-utils": "^3.14.4",
|
"@nuxt/test-utils": "^3.15.1",
|
||||||
"@release-it/conventional-changelog": "^9.0.2",
|
"@release-it/conventional-changelog": "^9.0.3",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.16.0",
|
||||||
"happy-dom": "^14.12.3",
|
"happy-dom": "^14.12.3",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"nuxt": "^3.14.159",
|
"nuxt": "^3.14.1592",
|
||||||
"release-it": "^17.10.0",
|
"release-it": "^17.10.0",
|
||||||
"superstruct": "^2.0.2",
|
"superstruct": "^2.0.2",
|
||||||
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"valibot": "^0.42.1",
|
"valibot": "^0.42.1",
|
||||||
"valibot30": "npm:valibot@0.30.0",
|
"valibot30": "npm:valibot@0.30.0",
|
||||||
"valibot31": "npm:valibot@0.31.0",
|
"valibot31": "npm:valibot@0.31.0",
|
||||||
"vitest": "^2.1.4",
|
"vitest": "^2.1.8",
|
||||||
"vitest-environment-nuxt": "^1.0.1",
|
"vitest-environment-nuxt": "^1.0.1",
|
||||||
"vue-tsc": "^2.1.10",
|
"vue-tsc": "^2.1.10",
|
||||||
"yup": "^1.4.0",
|
"yup": "^1.5.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@nuxt/ui": "workspace:*",
|
"@nuxt/ui": "workspace:*",
|
||||||
"@nuxt/content": "2.13.2",
|
"@nuxt/content": "2.13.2",
|
||||||
"@nuxtjs/mdc": "0.9.0"
|
"@nuxtjs/mdc": "0.9.0",
|
||||||
|
"chokidar": "3.6.0",
|
||||||
|
"typescript": "5.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "latest",
|
"@nuxt/ui": "latest",
|
||||||
"nuxt": "^3.14.159"
|
"nuxt": "^3.14.1592"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4037
pnpm-lock.yaml
generated
4037
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -8,28 +8,27 @@
|
|||||||
</slot>
|
</slot>
|
||||||
<thead :class="ui.thead">
|
<thead :class="ui.thead">
|
||||||
<tr :class="ui.tr.base">
|
<tr :class="ui.tr.base">
|
||||||
<th v-if="modelValue" scope="col" :class="ui.checkbox.padding">
|
|
||||||
<UCheckbox
|
|
||||||
:model-value="isAllRowChecked"
|
|
||||||
:indeterminate="indeterminate"
|
|
||||||
v-bind="ui.default.checkbox"
|
|
||||||
aria-label="Select all"
|
|
||||||
@change="onChange"
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th v-if="expand" scope="col" :class="ui.tr.base">
|
<th v-if="expand" scope="col" :class="ui.tr.base">
|
||||||
<span class="sr-only">Expand</span>
|
<span class="sr-only">Expand</span>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th
|
<th
|
||||||
v-for="(column, index) in columns"
|
v-for="(column, index) in columns"
|
||||||
:key="index"
|
:key="index"
|
||||||
scope="col"
|
scope="col"
|
||||||
:class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.class]"
|
:class="[ui.th.base, ui.th.padding, ui.th.color, ui.th.font, ui.th.size, column.key === 'select' && ui.checkbox.padding, column.class]"
|
||||||
:aria-sort="getAriaSort(column)"
|
:aria-sort="getAriaSort(column)"
|
||||||
>
|
>
|
||||||
<slot :name="`${column.key}-header`" :column="column" :sort="sort" :on-sort="onSort">
|
<slot v-if="!singleSelect && modelValue && (column.key === 'select' || shouldRenderColumnInFirstPlace(index, 'select'))" name="select-header" :indeterminate="indeterminate" :checked="isAllRowChecked" :change="onChange">
|
||||||
|
<UCheckbox
|
||||||
|
:model-value="isAllRowChecked"
|
||||||
|
:indeterminate="indeterminate"
|
||||||
|
v-bind="ui.default.checkbox"
|
||||||
|
aria-label="Select all"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<slot v-else :name="`${column.key}-header`" :column="column" :sort="sort" :on-sort="onSort">
|
||||||
<UButton
|
<UButton
|
||||||
v-if="column.sortable"
|
v-if="column.sortable"
|
||||||
v-bind="{ ...(ui.default.sortButton || {}), ...sortButton }"
|
v-bind="{ ...(ui.default.sortButton || {}), ...sortButton }"
|
||||||
@@ -77,16 +76,7 @@
|
|||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-for="(row, index) in rows" :key="index">
|
<template v-for="(row, index) in rows" :key="index">
|
||||||
<tr :class="[ui.tr.base, isSelected(row) && ui.tr.selected, isExpanded(row) && ui.tr.expanded, ($attrs.onSelect || $attrs.onContextmenu) && ui.tr.active, row?.class]" @click="() => onSelect(row)" @contextmenu="(event) => onContextmenu(event, row)">
|
<tr :class="[ui.tr.base, isSelected(row) && ui.tr.selected, isExpanded(row) && ui.tr.expanded, $attrs.onSelect && ui.tr.active, row?.class]" @click="() => onSelect(row)" @contextmenu="(event) => onContextmenu(event, row)">
|
||||||
<td v-if="modelValue" :class="ui.checkbox.padding">
|
|
||||||
<UCheckbox
|
|
||||||
:model-value="isSelected(row)"
|
|
||||||
v-bind="ui.default.checkbox"
|
|
||||||
aria-label="Select row"
|
|
||||||
@change="onChangeCheckbox($event, row)"
|
|
||||||
@click.capture.stop="() => onSelect(row)"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td
|
<td
|
||||||
v-if="expand"
|
v-if="expand"
|
||||||
:class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size]"
|
:class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size]"
|
||||||
@@ -102,8 +92,26 @@
|
|||||||
@click.capture.stop="toggleOpened(row)"
|
@click.capture.stop="toggleOpened(row)"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, column?.rowClass, row[column.key]?.class]">
|
<td v-for="(column, subIndex) in columns" :key="subIndex" :class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size, column?.rowClass, row[column.key]?.class, column.key === 'select' && ui.checkbox.padding]">
|
||||||
<slot :name="`${column.key}-data`" :column="column" :row="row" :index="index" :get-row-data="(defaultValue) => getRowData(row, column.key, defaultValue)">
|
<slot v-if="modelValue && (column.key === 'select' || shouldRenderColumnInFirstPlace(subIndex, 'select')) " name="select-data" :checked="isSelected(row)" :change="(ev: boolean) => onChangeCheckbox(ev, row)">
|
||||||
|
<UCheckbox
|
||||||
|
:model-value="isSelected(row)"
|
||||||
|
v-bind="ui.default.checkbox"
|
||||||
|
aria-label="Select row"
|
||||||
|
@change="onChangeCheckbox($event, row)"
|
||||||
|
@click.capture.stop="() => onSelect(row)"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<slot
|
||||||
|
v-else
|
||||||
|
:key="retriggerSlot"
|
||||||
|
:name="`${column.key}-data`"
|
||||||
|
:column="column"
|
||||||
|
:row="row"
|
||||||
|
:index="index"
|
||||||
|
:get-row-data="(defaultValue) => getRowData(row, column.key, defaultValue)"
|
||||||
|
>
|
||||||
{{ getRowData(row, column.key) }}
|
{{ getRowData(row, column.key) }}
|
||||||
</slot>
|
</slot>
|
||||||
</td>
|
</td>
|
||||||
@@ -125,11 +133,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, toRaw, toRef } from 'vue'
|
import { computed, defineComponent, ref, toRaw, toRef, watch } from 'vue'
|
||||||
import type { PropType, AriaAttributes } from 'vue'
|
import type { PropType, AriaAttributes } from 'vue'
|
||||||
import { upperFirst } from 'scule'
|
import { upperFirst } from 'scule'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { isEqual } from 'ohash'
|
||||||
import UIcon from '../elements/Icon.vue'
|
import UIcon from '../elements/Icon.vue'
|
||||||
import UButton from '../elements/Button.vue'
|
import UButton from '../elements/Button.vue'
|
||||||
import UProgress from '../elements/Progress.vue'
|
import UProgress from '../elements/Progress.vue'
|
||||||
@@ -144,7 +153,7 @@ import { table } from '#ui/ui.config'
|
|||||||
const config = mergeConfig<typeof table>(appConfig.ui.strategy, appConfig.ui.table, table)
|
const config = mergeConfig<typeof table>(appConfig.ui.strategy, appConfig.ui.table, table)
|
||||||
|
|
||||||
function defaultComparator<T>(a: T, z: T): boolean {
|
function defaultComparator<T>(a: T, z: T): boolean {
|
||||||
return JSON.stringify(a) === JSON.stringify(z)
|
return isEqual(a, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultSort(a: any, b: any, direction: 'asc' | 'desc') {
|
function defaultSort(a: any, b: any, direction: 'asc' | 'desc') {
|
||||||
@@ -159,6 +168,14 @@ function defaultSort(a: any, b: any, direction: 'asc' | 'desc') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStringifiedSet(arr: TableRow[]) {
|
||||||
|
return new Set(arr.map(item => JSON.stringify(item)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function accessor<T extends Record<string, any>>(key: string) {
|
||||||
|
return (obj: T) => get(obj, key)
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
UIcon,
|
UIcon,
|
||||||
@@ -221,7 +238,7 @@ export default defineComponent({
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
loadingState: {
|
loadingState: {
|
||||||
type: Object as PropType<{ icon: string, label: string }>,
|
type: Object as PropType<{ icon: string, label: string } | null>,
|
||||||
default: () => config.default.loadingState
|
default: () => config.default.loadingState
|
||||||
},
|
},
|
||||||
emptyState: {
|
emptyState: {
|
||||||
@@ -247,9 +264,13 @@ export default defineComponent({
|
|||||||
multipleExpand: {
|
multipleExpand: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
singleSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'update:sort', 'update:expand'],
|
emits: ['update:modelValue', 'update:sort', 'update:expand', 'select:all'],
|
||||||
setup(props, { emit, attrs: $attrs }) {
|
setup(props, { emit, attrs: $attrs }) {
|
||||||
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
|
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||||
|
|
||||||
@@ -264,6 +285,8 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const retriggerSlot = ref(null)
|
||||||
|
|
||||||
const savedSort = { column: sort.value.column, direction: null }
|
const savedSort = { column: sort.value.column, direction: null }
|
||||||
|
|
||||||
const rows = computed(() => {
|
const rows = computed(() => {
|
||||||
@@ -292,8 +315,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const getStringifiedSet = (arr: TableRow[]) => new Set(arr.map(item => JSON.stringify(item)))
|
|
||||||
|
|
||||||
const totalRows = computed(() => props.rows.length)
|
const totalRows = computed(() => props.rows.length)
|
||||||
|
|
||||||
const countCheckedRow = computed(() => {
|
const countCheckedRow = computed(() => {
|
||||||
@@ -328,10 +349,6 @@ export default defineComponent({
|
|||||||
return props.by(a, z)
|
return props.by(a, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
function accessor<T extends Record<string, any>>(key: string) {
|
|
||||||
return (obj: T) => get(obj, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSelected(row: TableRow) {
|
function isSelected(row: TableRow) {
|
||||||
if (!props.modelValue) {
|
if (!props.modelValue) {
|
||||||
return false
|
return false
|
||||||
@@ -355,6 +372,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onSelect(row: TableRow) {
|
function onSelect(row: TableRow) {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (selection && selection.toString().length > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!$attrs.onSelect) {
|
if (!$attrs.onSelect) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -393,11 +415,12 @@ export default defineComponent({
|
|||||||
} else {
|
} else {
|
||||||
selected.value = []
|
selected.value = []
|
||||||
}
|
}
|
||||||
|
emit('select:all', checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeCheckbox(checked: boolean, row: TableRow) {
|
function onChangeCheckbox(checked: boolean, row: TableRow) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
selected.value.push(row)
|
selected.value = props.singleSelect ? [row] : [...selected.value, row]
|
||||||
} else {
|
} else {
|
||||||
const index = selected.value.findIndex(item => compare(item, row))
|
const index = selected.value.findIndex(item => compare(item, row))
|
||||||
selected.value.splice(index, 1)
|
selected.value.splice(index, 1)
|
||||||
@@ -412,6 +435,13 @@ export default defineComponent({
|
|||||||
return expand.value?.openedRows ? expand.value.openedRows.some(openedRow => compare(openedRow, row)) : false
|
return expand.value?.openedRows ? expand.value.openedRows.some(openedRow => compare(openedRow, row)) : false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldRenderColumnInFirstPlace(index: number, key: string) {
|
||||||
|
if (!props.columns) {
|
||||||
|
return index === 0
|
||||||
|
}
|
||||||
|
return index === 0 && !props.columns.find(col => col.key === key)
|
||||||
|
}
|
||||||
|
|
||||||
function toggleOpened(row: TableRow) {
|
function toggleOpened(row: TableRow) {
|
||||||
expand.value = {
|
expand.value = {
|
||||||
openedRows: isExpanded(row) ? expand.value.openedRows.filter(v => !compare(v, row)) : props.multipleExpand ? [...expand.value.openedRows, row] : [row],
|
openedRows: isExpanded(row) ? expand.value.openedRows.filter(v => !compare(v, row)) : props.multipleExpand ? [...expand.value.openedRows, row] : [row],
|
||||||
@@ -439,6 +469,12 @@ export default defineComponent({
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(rows, () => {
|
||||||
|
retriggerSlot.value = new Date()
|
||||||
|
}, {
|
||||||
|
deep: true
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// eslint-disable-next-line vue/no-dupe-keys
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
ui,
|
ui,
|
||||||
@@ -465,7 +501,9 @@ export default defineComponent({
|
|||||||
getRowData,
|
getRowData,
|
||||||
toggleOpened,
|
toggleOpened,
|
||||||
getAriaSort,
|
getAriaSort,
|
||||||
isExpanded
|
isExpanded,
|
||||||
|
shouldRenderColumnInFirstPlace,
|
||||||
|
retriggerSlot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export default defineComponent({
|
|||||||
default: () => ({})
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['open'],
|
emits: ['open', 'close'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class'))
|
const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||||
|
|
||||||
@@ -142,6 +142,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (!isOpenBefore && isOpenAfter) {
|
if (!isOpenBefore && isOpenAfter) {
|
||||||
emit('open', index)
|
emit('open', index)
|
||||||
|
} else if (isOpenBefore && !isOpenAfter) {
|
||||||
|
emit('close', index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<span :class="badgeClass" v-bind="attrs">
|
<span :class="badgeClass" v-bind="attrs">
|
||||||
<slot>{{ label }}</slot>
|
<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>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -8,6 +20,7 @@
|
|||||||
import { computed, toRef, defineComponent } from 'vue'
|
import { computed, toRef, defineComponent } from 'vue'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import { twMerge, twJoin } from 'tailwind-merge'
|
import { twMerge, twJoin } from 'tailwind-merge'
|
||||||
|
import UIcon from '../elements/Icon.vue'
|
||||||
import { useUI } from '../../composables/useUI'
|
import { useUI } from '../../composables/useUI'
|
||||||
import { mergeConfig } from '../../utils'
|
import { mergeConfig } from '../../utils'
|
||||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||||
@@ -19,6 +32,9 @@ import { badge } from '#ui/ui.config'
|
|||||||
const config = mergeConfig<typeof badge>(appConfig.ui.strategy, appConfig.ui.badge, badge)
|
const config = mergeConfig<typeof badge>(appConfig.ui.strategy, appConfig.ui.badge, badge)
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
UIcon
|
||||||
|
},
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: {
|
props: {
|
||||||
size: {
|
size: {
|
||||||
@@ -49,6 +65,26 @@ export default defineComponent({
|
|||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
default: null
|
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: {
|
class: {
|
||||||
type: [String, Object, Array] as PropType<any>,
|
type: [String, Object, Array] as PropType<any>,
|
||||||
default: () => ''
|
default: () => ''
|
||||||
@@ -63,6 +99,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
const { size, rounded } = useInjectButtonGroup({ ui, props })
|
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 badgeClass = computed(() => {
|
||||||
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
|
||||||
|
|
||||||
@@ -71,13 +115,42 @@ export default defineComponent({
|
|||||||
ui.value.font,
|
ui.value.font,
|
||||||
rounded.value,
|
rounded.value,
|
||||||
ui.value.size[size.value],
|
ui.value.size[size.value],
|
||||||
|
ui.value.gap[size.value],
|
||||||
variant?.replaceAll('{color}', props.color)
|
variant?.replaceAll('{color}', props.color)
|
||||||
), props.class)
|
), 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 {
|
return {
|
||||||
attrs,
|
attrs,
|
||||||
badgeClass
|
isLeading,
|
||||||
|
isTrailing,
|
||||||
|
badgeClass,
|
||||||
|
leadingIconName,
|
||||||
|
trailingIconName,
|
||||||
|
leadingIconClass,
|
||||||
|
trailingIconClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default defineComponent({
|
|||||||
default: () => ''
|
default: () => ''
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
type: Object as PropType<DeepPartial<typeof config & { strategy?: Strategy }>>,
|
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
||||||
default: undefined
|
default: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { isEqual } from 'ohash'
|
import { isEqual, diff } from 'ohash'
|
||||||
import { defineComponent } from 'vue'
|
import { type PropType, defineComponent } from 'vue'
|
||||||
import { nuxtLinkProps } from '../../utils'
|
import { nuxtLinkProps } from '../../utils'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -61,7 +61,7 @@ export default defineComponent({
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
exactQuery: {
|
exactQuery: {
|
||||||
type: Boolean,
|
type: [Boolean, String] as PropType<boolean | 'partial'>,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
exactHash: {
|
exactHash: {
|
||||||
@@ -74,9 +74,21 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
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 }) {
|
function resolveLinkClass(route, $route, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) {
|
||||||
if (props.exactQuery && !isEqual(route.query, $route.query)) {
|
if (props.exactQuery === 'partial') {
|
||||||
return props.inactiveClass
|
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.exactHash && route.hash !== $route.hash) {
|
if (props.exactHash && route.hash !== $route.hash) {
|
||||||
return props.inactiveClass
|
return props.inactiveClass
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ export default defineComponent({
|
|||||||
const formId = useId()
|
const formId = useId()
|
||||||
const bus = useEventBus<FormEvent>(`form-${formId}`)
|
const bus = useEventBus<FormEvent>(`form-${formId}`)
|
||||||
|
|
||||||
|
const parsedValue = ref(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
bus.on(async (event) => {
|
bus.on(async (event) => {
|
||||||
if (event.type !== 'submit' && props.validateOn?.includes(event.type)) {
|
if (event.type !== 'submit' && props.validateOn?.includes(event.type)) {
|
||||||
@@ -87,7 +89,7 @@ export default defineComponent({
|
|||||||
if (errors) {
|
if (errors) {
|
||||||
errs = errs.concat(errors)
|
errs = errs.concat(errors)
|
||||||
} else {
|
} else {
|
||||||
Object.assign(props.state, result)
|
parsedValue.value = result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +132,7 @@ export default defineComponent({
|
|||||||
if (props.validateOn?.includes('submit')) {
|
if (props.validateOn?.includes('submit')) {
|
||||||
await validate()
|
await validate()
|
||||||
}
|
}
|
||||||
event.data = props.state
|
event.data = props.schema ? parsedValue.value : props.state
|
||||||
emit('submit', event)
|
emit('submit', event)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!(error instanceof FormException)) {
|
if (!(error instanceof FormException)) {
|
||||||
@@ -252,10 +254,10 @@ async function validateJoiSchema(
|
|||||||
schema: JoiSchema
|
schema: JoiSchema
|
||||||
): Promise<ValidateReturnSchema<typeof state>> {
|
): Promise<ValidateReturnSchema<typeof state>> {
|
||||||
try {
|
try {
|
||||||
await schema.validateAsync(state, { abortEarly: false })
|
const result = await schema.validateAsync(state, { abortEarly: false })
|
||||||
return {
|
return {
|
||||||
errors: null,
|
errors: null,
|
||||||
result: state
|
result
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isJoiError(error)) {
|
if (isJoiError(error)) {
|
||||||
@@ -321,7 +323,7 @@ async function validateYupSchema(
|
|||||||
schema: YupObjectSchema<any>
|
schema: YupObjectSchema<any>
|
||||||
): Promise<ValidateReturnSchema<typeof state>> {
|
): Promise<ValidateReturnSchema<typeof state>> {
|
||||||
try {
|
try {
|
||||||
const result = schema.validateSync(state, { abortEarly: false })
|
const result = await schema.validate(state, { abortEarly: false })
|
||||||
return {
|
return {
|
||||||
errors: null,
|
errors: null,
|
||||||
result
|
result
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
v-slot="{ active, selected, disabled: optionDisabled }"
|
v-slot="{ active, selected, disabled: optionDisabled }"
|
||||||
:key="index"
|
:key="index"
|
||||||
as="template"
|
as="template"
|
||||||
:value="valueAttribute ? option[valueAttribute] : option"
|
:value="valueAttribute ? accessor(option, valueAttribute) : option"
|
||||||
:disabled="option.disabled"
|
:disabled="option.disabled"
|
||||||
>
|
>
|
||||||
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, selected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
|
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, selected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
|
||||||
@@ -104,6 +104,7 @@ import {
|
|||||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { twMerge, twJoin } from 'tailwind-merge'
|
import { twMerge, twJoin } from 'tailwind-merge'
|
||||||
|
import { isEqual } from 'ohash'
|
||||||
import UIcon from '../elements/Icon.vue'
|
import UIcon from '../elements/Icon.vue'
|
||||||
import UAvatar from '../elements/Avatar.vue'
|
import UAvatar from '../elements/Avatar.vue'
|
||||||
import { useUI } from '../../composables/useUI'
|
import { useUI } from '../../composables/useUI'
|
||||||
@@ -292,6 +293,24 @@ export default defineComponent({
|
|||||||
|
|
||||||
const size = computed(() => sizeButtonGroup.value ?? sizeFormGroup.value)
|
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 internalQuery = ref('')
|
||||||
const query = computed({
|
const query = computed({
|
||||||
get() {
|
get() {
|
||||||
@@ -304,12 +323,30 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const label = computed(() => {
|
const label = computed(() => {
|
||||||
if (!props.modelValue) {
|
if (!props.modelValue) return null
|
||||||
return
|
|
||||||
|
function getValue(value: any) {
|
||||||
|
if (props.valueAttribute) {
|
||||||
|
return accessor(value, props.valueAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareValues(value1: any, value2: any) {
|
||||||
|
if (by.value && typeof by.value !== 'function' && typeof value1 === 'object' && typeof value2 === 'object') {
|
||||||
|
return isEqual(value1[props.by], value2[props.by])
|
||||||
|
}
|
||||||
|
return isEqual(value1, value2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.valueAttribute) {
|
if (props.valueAttribute) {
|
||||||
const option = options.value.find(option => option[props.valueAttribute] === props.modelValue)
|
const option = options.value.find((option) => {
|
||||||
|
const optionValue = getValue(option)
|
||||||
|
|
||||||
|
return compareValues(optionValue, props.modelValue)
|
||||||
|
})
|
||||||
|
|
||||||
return option ? accessor(option, props.optionAttribute) : null
|
return option ? accessor(option, props.optionAttribute) : null
|
||||||
} else {
|
} else {
|
||||||
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
||||||
@@ -486,7 +523,9 @@ export default defineComponent({
|
|||||||
query,
|
query,
|
||||||
accessor,
|
accessor,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onQueryChange
|
onQueryChange,
|
||||||
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
|
by
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
v-slot="{ active, selected: optionSelected, disabled: optionDisabled }"
|
v-slot="{ active, selected: optionSelected, disabled: optionDisabled }"
|
||||||
:key="index"
|
:key="index"
|
||||||
as="template"
|
as="template"
|
||||||
:value="valueAttribute ? option[valueAttribute] : option"
|
:value="valueAttribute ? accessor(option, valueAttribute) : option"
|
||||||
:disabled="option.disabled"
|
:disabled="option.disabled"
|
||||||
>
|
>
|
||||||
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, optionSelected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
|
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive, optionSelected && uiMenu.option.selected, optionDisabled && uiMenu.option.disabled]">
|
||||||
@@ -140,6 +140,7 @@ import {
|
|||||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import { twMerge, twJoin } from 'tailwind-merge'
|
import { twMerge, twJoin } from 'tailwind-merge'
|
||||||
|
import { isEqual } from 'ohash'
|
||||||
import UIcon from '../elements/Icon.vue'
|
import UIcon from '../elements/Icon.vue'
|
||||||
import UAvatar from '../elements/Avatar.vue'
|
import UAvatar from '../elements/Avatar.vue'
|
||||||
import { useUI } from '../../composables/useUI'
|
import { useUI } from '../../composables/useUI'
|
||||||
@@ -347,6 +348,24 @@ export default defineComponent({
|
|||||||
|
|
||||||
const [trigger, container] = usePopper(popper.value)
|
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 { size: sizeButtonGroup, rounded } = useInjectButtonGroup({ ui, props })
|
||||||
const { emitFormBlur, emitFormChange, inputId, color, size: sizeFormGroup, name } = useFormGroup(props, config)
|
const { emitFormBlur, emitFormChange, inputId, color, size: sizeFormGroup, name } = useFormGroup(props, config)
|
||||||
|
|
||||||
@@ -364,39 +383,49 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const selected = computed(() => {
|
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])
|
||||||
|
}
|
||||||
|
return isEqual(value1, value2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(value: any) {
|
||||||
|
if (props.valueAttribute) {
|
||||||
|
return accessor(value, props.valueAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
if (props.multiple) {
|
if (props.multiple) {
|
||||||
if (!Array.isArray(props.modelValue) || !props.modelValue.length) {
|
const modelValue = props.modelValue
|
||||||
|
if (!Array.isArray(modelValue) || !modelValue.length) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.valueAttribute) {
|
return options.value.filter((option) => {
|
||||||
return options.value.filter(option => (props.modelValue as any[]).includes(option[props.valueAttribute]))
|
const optionValue = getValue(option)
|
||||||
}
|
return modelValue.some(value => compareValues(value, optionValue))
|
||||||
return options.value.filter(option => (props.modelValue as any[]).includes(option))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.valueAttribute) {
|
return options.value.find((option) => {
|
||||||
return options.value.find(option => option[props.valueAttribute] === props.modelValue)
|
const optionValue = getValue(option)
|
||||||
}
|
return compareValues(optionValue, toRaw(props.modelValue))
|
||||||
return options.value.find(option => option === props.modelValue)
|
}) ?? props.modelValue
|
||||||
})
|
})
|
||||||
|
|
||||||
const label = computed(() => {
|
const label = computed(() => {
|
||||||
if (props.multiple) {
|
if (!props.modelValue) return null
|
||||||
if (Array.isArray(props.modelValue) && props.modelValue.length) {
|
|
||||||
return `${selected.value.length} selected`
|
if (Array.isArray(props.modelValue) && props.modelValue.length) {
|
||||||
} else {
|
return `${props.modelValue.length} selected`
|
||||||
return null
|
} else if (['string', 'number'].includes(typeof props.modelValue)) {
|
||||||
}
|
return props.valueAttribute ? accessor(selected.value, props.optionAttribute) : props.modelValue
|
||||||
} else if (props.modelValue !== undefined && props.modelValue !== null) {
|
|
||||||
if (props.valueAttribute) {
|
|
||||||
return accessor(selected.value, props.optionAttribute) ?? null
|
|
||||||
} else {
|
|
||||||
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectClass = computed(() => {
|
const selectClass = computed(() => {
|
||||||
@@ -597,7 +626,9 @@ export default defineComponent({
|
|||||||
// eslint-disable-next-line vue/no-dupe-keys
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
query,
|
query,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onQueryChange
|
onQueryChange,
|
||||||
|
// eslint-disable-next-line vue/no-dupe-keys
|
||||||
|
by
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ export default defineComponent({
|
|||||||
ui: {
|
ui: {
|
||||||
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
pauseTimeoutOnHover: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['close'],
|
emits: ['close'],
|
||||||
@@ -157,13 +161,13 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function onMouseover() {
|
function onMouseover() {
|
||||||
if (timer) {
|
if (props.pauseTimeoutOnHover && timer) {
|
||||||
timer.pause()
|
timer.pause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseleave() {
|
function onMouseleave() {
|
||||||
if (timer) {
|
if (props.pauseTimeoutOnHover && timer) {
|
||||||
timer.resume()
|
timer.resume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div :class="wrapperClass" role="region" v-bind="attrs">
|
<div v-if="notifications.length" :class="wrapperClass" role="region" v-bind="attrs">
|
||||||
<div v-if="notifications.length" :class="ui.container">
|
<div :class="ui.container">
|
||||||
<div v-for="notification of notifications" :key="notification.id">
|
<div v-for="notification of notifications" :key="notification.id">
|
||||||
<UNotification
|
<UNotification
|
||||||
v-bind="notification"
|
v-bind="notification"
|
||||||
|
|||||||
3
src/runtime/types/checkbox.d.ts
vendored
Normal file
3
src/runtime/types/checkbox.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import type colors from '#ui-colors'
|
||||||
|
|
||||||
|
export type CheckboxColor = typeof colors[number]
|
||||||
2
src/runtime/types/divider.d.ts
vendored
2
src/runtime/types/divider.d.ts
vendored
@@ -1,3 +1,3 @@
|
|||||||
import type { divider } from '#ui/ui.config'
|
import type { divider } from '../ui.config'
|
||||||
|
|
||||||
export type DividerSize = keyof typeof divider.border.size.horizontal | keyof typeof divider.border.size.vertical
|
export type DividerSize = keyof typeof divider.border.size.horizontal | keyof typeof divider.border.size.vertical
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export * from './avatar'
|
|||||||
export * from './badge'
|
export * from './badge'
|
||||||
export * from './breadcrumb'
|
export * from './breadcrumb'
|
||||||
export * from './button'
|
export * from './button'
|
||||||
|
export * from './checkbox'
|
||||||
export * from './chip'
|
export * from './chip'
|
||||||
export * from './clipboard'
|
export * from './clipboard'
|
||||||
export * from './command-palette'
|
export * from './command-palette'
|
||||||
|
|||||||
2
src/runtime/types/link.d.ts
vendored
2
src/runtime/types/link.d.ts
vendored
@@ -6,7 +6,7 @@ export interface Link extends NuxtLinkProps {
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
active?: boolean
|
active?: boolean
|
||||||
exact?: boolean
|
exact?: boolean
|
||||||
exactQuery?: boolean
|
exactQuery?: boolean | 'partial'
|
||||||
exactHash?: boolean
|
exactHash?: boolean
|
||||||
inactiveClass?: string
|
inactiveClass?: string
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/runtime/types/utils.d.ts
vendored
2
src/runtime/types/utils.d.ts
vendored
@@ -7,7 +7,7 @@ export interface TightMap<O = any> {
|
|||||||
export type DeepPartial<T, O = any> = {
|
export type DeepPartial<T, O = any> = {
|
||||||
[P in keyof T]?: T[P] extends object
|
[P in keyof T]?: T[P] extends object
|
||||||
? DeepPartial<T[P], O>
|
? DeepPartial<T[P], O>
|
||||||
: T[P];
|
: T[P] extends string ? string : T[P];
|
||||||
} & {
|
} & {
|
||||||
[key: string]: O | TightMap<O>
|
[key: string]: O | TightMap<O>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ButtonColor, ButtonSize, ButtonVariant, CheckboxColor, ProgressAnimation, ProgressColor } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'relative overflow-x-auto',
|
wrapper: 'relative overflow-x-auto',
|
||||||
base: 'min-w-full table-fixed',
|
base: 'min-w-full table-fixed',
|
||||||
@@ -51,23 +53,23 @@ export default {
|
|||||||
icon: 'i-heroicons-arrows-up-down-20-solid',
|
icon: 'i-heroicons-arrows-up-down-20-solid',
|
||||||
trailing: true,
|
trailing: true,
|
||||||
square: true,
|
square: true,
|
||||||
color: 'gray' as const,
|
color: 'gray' as ButtonColor,
|
||||||
variant: 'ghost' as const,
|
variant: 'ghost' as ButtonVariant,
|
||||||
class: '-m-1.5'
|
class: '-m-1.5'
|
||||||
},
|
},
|
||||||
expandButton: {
|
expandButton: {
|
||||||
icon: 'i-heroicons-chevron-down',
|
icon: 'i-heroicons-chevron-down',
|
||||||
color: 'gray' as const,
|
color: 'gray' as ButtonColor,
|
||||||
variant: 'ghost' as const,
|
variant: 'ghost' as ButtonVariant,
|
||||||
size: 'xs' as const,
|
size: 'xs' as ButtonSize,
|
||||||
class: '-my-1.5 align-middle'
|
class: '-my-1.5 align-middle'
|
||||||
},
|
},
|
||||||
checkbox: {
|
checkbox: {
|
||||||
color: 'primary' as const
|
color: 'primary' as CheckboxColor
|
||||||
},
|
},
|
||||||
progress: {
|
progress: {
|
||||||
color: 'primary' as const,
|
color: 'primary' as ProgressColor,
|
||||||
animation: 'carousel' as const
|
animation: 'carousel' as ProgressAnimation
|
||||||
},
|
},
|
||||||
loadingState: {
|
loadingState: {
|
||||||
icon: 'i-heroicons-arrow-path-20-solid',
|
icon: 'i-heroicons-arrow-path-20-solid',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default {
|
|||||||
openIcon: 'i-heroicons-chevron-down-20-solid',
|
openIcon: 'i-heroicons-chevron-down-20-solid',
|
||||||
closeIcon: '',
|
closeIcon: '',
|
||||||
class: 'mb-1.5 w-full',
|
class: 'mb-1.5 w-full',
|
||||||
variant: 'soft' as const,
|
variant: 'soft',
|
||||||
truncate: true
|
truncate: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AvatarSize, ButtonColor, ButtonSize, ButtonVariant } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'w-full relative overflow-hidden',
|
wrapper: 'w-full relative overflow-hidden',
|
||||||
inner: 'w-0 flex-1',
|
inner: 'w-0 flex-1',
|
||||||
@@ -13,7 +15,7 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0 self-center',
|
base: 'flex-shrink-0 self-center',
|
||||||
size: 'md' as const
|
size: 'md' as AvatarSize
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
white: {
|
white: {
|
||||||
@@ -32,9 +34,9 @@ export default {
|
|||||||
icon: null,
|
icon: null,
|
||||||
closeButton: null,
|
closeButton: null,
|
||||||
actionButton: {
|
actionButton: {
|
||||||
size: 'xs' as const,
|
size: 'xs' as ButtonSize,
|
||||||
color: 'primary' as const,
|
color: 'primary' as ButtonColor,
|
||||||
variant: 'link' as const
|
variant: 'link' as ButtonVariant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ export default {
|
|||||||
md: 'text-sm px-2 py-1',
|
md: 'text-sm px-2 py-1',
|
||||||
lg: 'text-sm px-2.5 py-1.5'
|
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: {
|
color: {
|
||||||
white: {
|
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'
|
solid: 'ring-1 ring-inset ring-gray-300 dark:ring-gray-700 text-gray-900 dark:text-white bg-white dark:bg-gray-900'
|
||||||
@@ -25,6 +31,15 @@ export default {
|
|||||||
soft: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400',
|
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'
|
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: {
|
default: {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
variant: 'solid',
|
variant: 'solid',
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ButtonColor } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'relative',
|
wrapper: 'relative',
|
||||||
container: 'relative w-full flex overflow-x-auto snap-x snap-mandatory scroll-smooth',
|
container: 'relative w-full flex overflow-x-auto snap-x snap-mandatory scroll-smooth',
|
||||||
@@ -13,12 +15,12 @@ export default {
|
|||||||
},
|
},
|
||||||
default: {
|
default: {
|
||||||
prevButton: {
|
prevButton: {
|
||||||
color: 'black' as const,
|
color: 'black' as ButtonColor,
|
||||||
class: 'rtl:[&_span:first-child]:rotate-180 absolute start-4 top-1/2 transform -translate-y-1/2 rounded-full',
|
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'
|
icon: 'i-heroicons-chevron-left-20-solid'
|
||||||
},
|
},
|
||||||
nextButton: {
|
nextButton: {
|
||||||
color: 'black' as const,
|
color: 'black' as ButtonColor,
|
||||||
class: 'rtl:[&_span:last-child]:rotate-180 absolute end-4 top-1/2 transform -translate-y-1/2 rounded-full',
|
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'
|
icon: 'i-heroicons-chevron-right-20-solid'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { AvatarSize } from '../../types'
|
||||||
import { arrow } from '../popper'
|
import { arrow } from '../popper'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -28,7 +29,7 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0',
|
base: 'flex-shrink-0',
|
||||||
size: '2xs' as const
|
size: '2xs' as AvatarSize
|
||||||
},
|
},
|
||||||
label: 'truncate',
|
label: 'truncate',
|
||||||
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5 ms-auto'
|
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5 ms-auto'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { AvatarSize } from '../../types'
|
||||||
import { arrow } from '../popper'
|
import { arrow } from '../popper'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -36,7 +37,7 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0',
|
base: 'flex-shrink-0',
|
||||||
size: '2xs' as const
|
size: '2xs' as AvatarSize
|
||||||
},
|
},
|
||||||
chip: {
|
chip: {
|
||||||
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AvatarSize } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: {
|
wrapper: {
|
||||||
base: 'flex items-center align-center text-center',
|
base: 'flex items-center align-center text-center',
|
||||||
@@ -42,11 +44,11 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0',
|
base: 'flex-shrink-0',
|
||||||
size: '2xs' as const
|
size: '2xs' as AvatarSize
|
||||||
},
|
},
|
||||||
label: 'text-sm',
|
label: 'text-sm',
|
||||||
default: {
|
default: {
|
||||||
size: '2xs' as const,
|
size: '2xs',
|
||||||
type: 'solid' as const
|
type: 'solid'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AvatarSize } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800',
|
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',
|
container: 'relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2',
|
||||||
@@ -46,7 +48,7 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0',
|
base: 'flex-shrink-0',
|
||||||
size: '2xs' as const
|
size: '2xs' as AvatarSize
|
||||||
},
|
},
|
||||||
chip: {
|
chip: {
|
||||||
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AvatarSize, BadgeColor, BadgeSize, BadgeVariant } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'relative w-full flex items-center justify-between',
|
wrapper: 'relative w-full flex items-center justify-between',
|
||||||
container: 'flex items-center min-w-0',
|
container: 'flex items-center min-w-0',
|
||||||
@@ -15,12 +17,12 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0',
|
base: 'flex-shrink-0',
|
||||||
size: '2xs' as const
|
size: '2xs' as AvatarSize
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
base: 'flex-shrink-0 ms-auto relative rounded',
|
base: 'flex-shrink-0 ms-auto relative rounded',
|
||||||
color: 'gray' as const,
|
color: 'gray' as BadgeColor,
|
||||||
variant: 'solid' as const,
|
variant: 'solid' as BadgeVariant,
|
||||||
size: 'xs' as const
|
size: 'xs' as BadgeSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ButtonColor } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'flex items-center -space-x-px',
|
wrapper: 'flex items-center -space-x-px',
|
||||||
base: '',
|
base: '',
|
||||||
@@ -5,28 +7,28 @@ export default {
|
|||||||
default: {
|
default: {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
activeButton: {
|
activeButton: {
|
||||||
color: 'primary' as const
|
color: 'primary' as ButtonColor
|
||||||
},
|
},
|
||||||
inactiveButton: {
|
inactiveButton: {
|
||||||
color: 'white' as const
|
color: 'white' as ButtonColor
|
||||||
},
|
},
|
||||||
firstButton: {
|
firstButton: {
|
||||||
color: 'white' as const,
|
color: 'white' as ButtonColor,
|
||||||
class: 'rtl:[&_span:first-child]:rotate-180',
|
class: 'rtl:[&_span:first-child]:rotate-180',
|
||||||
icon: 'i-heroicons-chevron-double-left-20-solid'
|
icon: 'i-heroicons-chevron-double-left-20-solid'
|
||||||
},
|
},
|
||||||
lastButton: {
|
lastButton: {
|
||||||
color: 'white' as const,
|
color: 'white' as ButtonColor,
|
||||||
class: 'rtl:[&_span:last-child]:rotate-180',
|
class: 'rtl:[&_span:last-child]:rotate-180',
|
||||||
icon: 'i-heroicons-chevron-double-right-20-solid'
|
icon: 'i-heroicons-chevron-double-right-20-solid'
|
||||||
},
|
},
|
||||||
prevButton: {
|
prevButton: {
|
||||||
color: 'white' as const,
|
color: 'white' as ButtonColor,
|
||||||
class: 'rtl:[&_span:first-child]:rotate-180',
|
class: 'rtl:[&_span:first-child]:rotate-180',
|
||||||
icon: 'i-heroicons-chevron-left-20-solid'
|
icon: 'i-heroicons-chevron-left-20-solid'
|
||||||
},
|
},
|
||||||
nextButton: {
|
nextButton: {
|
||||||
color: 'white' as const,
|
color: 'white' as ButtonColor,
|
||||||
class: 'rtl:[&_span:last-child]:rotate-180',
|
class: 'rtl:[&_span:last-child]:rotate-180',
|
||||||
icon: 'i-heroicons-chevron-right-20-solid'
|
icon: 'i-heroicons-chevron-right-20-solid'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AvatarSize, BadgeColor, BadgeSize, BadgeVariant } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'relative',
|
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',
|
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',
|
||||||
@@ -17,13 +19,13 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0',
|
base: 'flex-shrink-0',
|
||||||
size: '2xs' as const
|
size: '2xs' as AvatarSize
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
base: 'flex-shrink-0 ms-auto relative rounded',
|
base: 'flex-shrink-0 ms-auto relative rounded',
|
||||||
color: 'gray' as const,
|
color: 'gray' as BadgeColor,
|
||||||
variant: 'solid' as const,
|
variant: 'solid' as BadgeVariant,
|
||||||
size: 'xs' as const
|
size: 'xs' as BadgeSize
|
||||||
},
|
},
|
||||||
divider: {
|
divider: {
|
||||||
wrapper: {
|
wrapper: {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { AvatarSize, ButtonColor, ButtonSize, ButtonVariant } from '../../types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
wrapper: 'w-full pointer-events-auto',
|
wrapper: 'w-full pointer-events-auto',
|
||||||
container: 'relative overflow-hidden',
|
container: 'relative overflow-hidden',
|
||||||
@@ -17,7 +19,7 @@ export default {
|
|||||||
},
|
},
|
||||||
avatar: {
|
avatar: {
|
||||||
base: 'flex-shrink-0 self-center',
|
base: 'flex-shrink-0 self-center',
|
||||||
size: 'md' as const
|
size: 'md' as AvatarSize
|
||||||
},
|
},
|
||||||
progress: {
|
progress: {
|
||||||
base: 'absolute bottom-0 end-0 start-0 h-1',
|
base: 'absolute bottom-0 end-0 start-0 h-1',
|
||||||
@@ -38,13 +40,13 @@ export default {
|
|||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
closeButton: {
|
closeButton: {
|
||||||
icon: 'i-heroicons-x-mark-20-solid',
|
icon: 'i-heroicons-x-mark-20-solid',
|
||||||
color: 'gray' as const,
|
color: 'gray' as ButtonColor,
|
||||||
variant: 'link' as const,
|
variant: 'link' as ButtonVariant,
|
||||||
padded: false
|
padded: false
|
||||||
},
|
},
|
||||||
actionButton: {
|
actionButton: {
|
||||||
size: 'xs' as const,
|
size: 'xs' as ButtonSize,
|
||||||
color: 'white' as const
|
color: 'white' as ButtonColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user