mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
130 Commits
issue-1987
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b830f63c89 | ||
|
|
71dac5e5b0 | ||
|
|
7b81bfa1ae | ||
|
|
bf1c9e7c94 | ||
|
|
23d9b51a58 | ||
|
|
2e6ba71e89 | ||
|
|
ea4007c62d | ||
|
|
69d6997210 | ||
|
|
6565472570 | ||
|
|
ee408e522e | ||
|
|
461e6173a9 | ||
|
|
b824f0682e | ||
|
|
7ce6af4870 | ||
|
|
b4cc9a5ab4 | ||
|
|
06eceff68b | ||
|
|
40f3e3b486 | ||
|
|
a5458765dc | ||
|
|
ac574b239b | ||
|
|
feb716c941 | ||
|
|
15da5cf71e | ||
|
|
125a28190b | ||
|
|
569fa7619b | ||
|
|
0ff2448655 | ||
|
|
a6c3daa363 | ||
|
|
e16eeee8c1 | ||
|
|
53ac62eae5 | ||
|
|
9c36d37b84 | ||
|
|
0462edb84e | ||
|
|
91e77bb09c | ||
|
|
84e35d1a79 | ||
|
|
28f29e98b8 | ||
|
|
81d7ca0cd1 | ||
|
|
89d3766835 | ||
|
|
9104213d35 | ||
|
|
d699558e38 | ||
|
|
7cbc3913d9 | ||
|
|
d2ceeadae7 | ||
|
|
c7f64b64c7 | ||
|
|
72ab47e77d | ||
|
|
5b187d6fbd | ||
|
|
f9e61fc422 | ||
|
|
1291e95e1c | ||
|
|
f943203770 | ||
|
|
f2d387622a | ||
|
|
b02dc4d5b7 | ||
|
|
6dddadc370 | ||
|
|
d89ecce472 | ||
|
|
efb74668bd | ||
|
|
e065734d58 | ||
|
|
0c5bea5f11 | ||
|
|
f6d4dd3b88 | ||
|
|
d9d4f1915a | ||
|
|
c70d29702e | ||
|
|
a0d8935f64 | ||
|
|
04aefcf81f | ||
|
|
e68b9795be | ||
|
|
b8c8718560 | ||
|
|
2a33a8171d | ||
|
|
23cfc046e7 | ||
|
|
e68cb53ab6 | ||
|
|
109b857472 | ||
|
|
ea15e21cdc | ||
|
|
b7153cd879 | ||
|
|
5047d448ed | ||
|
|
a0fee0fa73 | ||
|
|
b762d29220 | ||
|
|
98c19be71a | ||
|
|
8cf9f27d53 | ||
|
|
c0455c831f | ||
|
|
0360ea7a3c | ||
|
|
711539f3ce | ||
|
|
80d6d89467 | ||
|
|
d573fb636f | ||
|
|
1d08d319a7 | ||
|
|
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 |
15
.github/ISSUE_TEMPLATE/bug-v3.yml
vendored
15
.github/ISSUE_TEMPLATE/bug-v3.yml
vendored
@@ -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,11 +29,20 @@ 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.5
|
||||
placeholder: v3.0.0-alpha.x
|
||||
validations:
|
||||
required: true
|
||||
- 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?
|
||||
options:
|
||||
- v2.x
|
||||
- v3-alpha
|
||||
- v3.0.0-alpha.x
|
||||
validations:
|
||||
required: true
|
||||
- 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?
|
||||
options:
|
||||
- v2.x
|
||||
- v3-alpha
|
||||
- v3.0.0-alpha.x
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
17
.github/workflows/ci-dev.yml
vendored
17
.github/workflows/ci-dev.yml
vendored
@@ -37,16 +37,6 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Filter changes
|
||||
uses: dorny/paths-filter@v3
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
src:
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
@@ -65,8 +55,5 @@ jobs:
|
||||
- name: Test
|
||||
run: pnpm run test run
|
||||
|
||||
- name: Release Edge
|
||||
if: github.event_name == 'push' && steps.changes.outputs.src == 'true'
|
||||
run: ./scripts/release-edge.sh
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}
|
||||
- name: Publish
|
||||
run: pnpx pkg-pr-new publish --compact --no-template --pnpm
|
||||
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,5 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
## [2.21.1](https://github.com/nuxt/ui/compare/v2.21.0...v2.21.1) (2025-03-08)
|
||||
|
||||
### Features
|
||||
|
||||
* **Form:** add standard schema support ([#2880](https://github.com/nuxt/ui/issues/2880)) ([9c36d37](https://github.com/nuxt/ui/commit/9c36d37b847468d1cbd76eea38ac00cbc22549ca))
|
||||
* **module:** add `colorMode` option ([d2ceead](https://github.com/nuxt/ui/commit/d2ceeadae796254128697d94a3e317234bc2ecda)), closes [#3143](https://github.com/nuxt/ui/issues/3143)
|
||||
* **SelectMenu:** add inputTargetForm prop to handle input validation ([#3107](https://github.com/nuxt/ui/issues/3107)) ([feb716c](https://github.com/nuxt/ui/commit/feb716c941f1e7315009b53861a4dc0c2f233052))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Alert/Notification:** allow description ui override ([125a281](https://github.com/nuxt/ui/commit/125a28190b1a83e2456457e7a4ec618384b2446c)), closes [#2554](https://github.com/nuxt/ui/issues/2554)
|
||||
* **Table:** revert [#2600](https://github.com/nuxt/ui/issues/2600) to fix excessive column data slot re-renders ([#3375](https://github.com/nuxt/ui/issues/3375)) ([23d9b51](https://github.com/nuxt/ui/commit/23d9b51a5861f5d1f32f68a3141a600655a0598a))
|
||||
|
||||
## [2.21.0](https://github.com/nuxt/ui/compare/v2.20.0...v2.21.0) (2025-01-14)
|
||||
|
||||
### Features
|
||||
|
||||
* **module:** handle `tailwindMerge` config from `app.config` ([#2902](https://github.com/nuxt/ui/issues/2902)) ([ea15e21](https://github.com/nuxt/ui/commit/ea15e21cdcba00e21302415829113e8c6def8a6e))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Table:** `v-model` causing first column missing ([#2890](https://github.com/nuxt/ui/issues/2890)) ([d573fb6](https://github.com/nuxt/ui/commit/d573fb636f7f749ce95b93c5fb1ae2a053eeeeb0))
|
||||
* **Table:** remove `[@select](https://github.com/select)` event on checkbox ([#3042](https://github.com/nuxt/ui/issues/3042)) ([d9d4f19](https://github.com/nuxt/ui/commit/d9d4f1915aac586ae1abf3ebe67ca9aff65b9be0))
|
||||
* **tailwind:** use mjs template ([#2945](https://github.com/nuxt/ui/issues/2945)) ([8cf9f27](https://github.com/nuxt/ui/commit/8cf9f27d537bad5ffe4e136f52ff71548a451c5f))
|
||||
|
||||
## [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
|
||||
|
||||
14
README.md
14
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://ui.nuxt.com)
|
||||
[](https://ui.nuxt.com)
|
||||
|
||||
# Nuxt UI
|
||||
|
||||
@@ -20,7 +20,7 @@ Its goal is to provide everything related to UI when building a Nuxt app. This i
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- 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)
|
||||
|
||||
@@ -30,16 +30,6 @@ Read more on [ui.nuxt.com](https://ui.nuxt.com)
|
||||
npx nuxi@latest module add ui
|
||||
```
|
||||
|
||||
If you want latest updates, please use `@nuxt/ui-edge` in your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit https://ui.nuxt.com to explore the documentation.
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const id = 'nuxt-ui-banner-2'
|
||||
const to = 'https://ui3.nuxt.dev'
|
||||
const id = 'nuxt-ui-banner-3'
|
||||
const to = '/pro/pricing'
|
||||
|
||||
const hideBanner = () => {
|
||||
localStorage.setItem(id, 'true')
|
||||
@@ -28,9 +28,8 @@ if (import.meta.server) {
|
||||
<NuxtLink
|
||||
v-if="to"
|
||||
:to="to"
|
||||
target="_blank"
|
||||
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"
|
||||
>
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
<span class="font-semibold">Nuxt UI v3-alpha</span> has been released!
|
||||
<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>!
|
||||
</p>
|
||||
|
||||
<UButton
|
||||
to="https://ui3.nuxt.dev"
|
||||
<!-- <UButton
|
||||
:to="to"
|
||||
target="_blank"
|
||||
label="Try it out"
|
||||
label="Buy now"
|
||||
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
|
||||
|
||||
@@ -30,7 +30,7 @@ const { $ui } = useNuxtApp()
|
||||
const links = [{
|
||||
icon: 'i-simple-icons-figma',
|
||||
label: 'Figma Kit',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
||||
to: 'https://www.figma.com/community/file/1436401057300493073',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
|
||||
@@ -10,12 +10,34 @@
|
||||
}"
|
||||
>
|
||||
<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" />
|
||||
<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>
|
||||
|
||||
<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 #right>
|
||||
@@ -25,10 +47,10 @@
|
||||
<UContentSearchButton :label="null" />
|
||||
</UTooltip>
|
||||
|
||||
<UColorModeButton />
|
||||
<UColorModeButton class="hidden lg:inline-flex" />
|
||||
|
||||
<UButton
|
||||
to="https://github.com/nuxt/ui"
|
||||
to="https://github.com/nuxt/ui/tree/dev"
|
||||
target="_blank"
|
||||
icon="i-simple-icons-github"
|
||||
aria-label="GitHub"
|
||||
|
||||
@@ -45,7 +45,7 @@ const ui = {
|
||||
inactive: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
avatar: {
|
||||
size: '2xs' as const
|
||||
size: '2xs'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
docs/components/content/examples/InputExampleMaxLength.vue
Normal file
15
docs/components/content/examples/InputExampleMaxLength.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<UInput
|
||||
v-model="name"
|
||||
:maxlength="maxLength"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ name.length }}/{{ maxLength }}</span>
|
||||
</template>
|
||||
</UInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const name = ref('')
|
||||
const maxLength = 10
|
||||
</script>
|
||||
@@ -1,6 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
// Columns
|
||||
const columns = [{
|
||||
key: 'select',
|
||||
class: 'w-2'
|
||||
}, {
|
||||
key: 'id',
|
||||
label: '#',
|
||||
sortable: true
|
||||
@@ -20,6 +23,7 @@ const columns = [{
|
||||
|
||||
const selectedColumns = ref(columns)
|
||||
const columnsTable = computed(() => columns.filter(column => selectedColumns.value.includes(column)))
|
||||
const excludeSelectColumn = computed(() => columns.filter(v => v.key !== 'select'))
|
||||
|
||||
// Selected Rows
|
||||
const selectedRows = ref([])
|
||||
@@ -153,7 +157,7 @@ const { data: todos, status } = await useLazyAsyncData<{
|
||||
</UButton>
|
||||
</UDropdown>
|
||||
|
||||
<USelectMenu v-model="selectedColumns" :options="columns" multiple>
|
||||
<USelectMenu v-model="selectedColumns" :options="excludeSelectColumn" multiple>
|
||||
<UButton
|
||||
icon="i-heroicons-view-columns"
|
||||
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
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
- [Figma Kit](https://www.figma.com/community/file/1288455405058138934)
|
||||
- [Figma Kit](https://www.figma.com/community/file/1436401057300493073)
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -243,19 +243,21 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
## Edge
|
||||
## Continuous Releases
|
||||
|
||||
To use the latest updates pushed on the [`dev`](https://github.com/nuxt/ui/tree/dev) branch, you can use `@nuxt/ui-edge`.
|
||||
Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases.
|
||||
|
||||
Update your `package.json` to the following:
|
||||
Preview releases are automatically generated for every commit to the `dev` branch and pull requests targeting the `dev` branch. To use it into your project, replace the version in your `package.json` with the commit hash or pull request number.
|
||||
|
||||
```diff [package.json]
|
||||
{
|
||||
"devDependencies": {
|
||||
- "@nuxt/ui": "^2.11.0"
|
||||
+ "@nuxt/ui": "npm:@nuxt/ui-edge@latest"
|
||||
"dependencies": {
|
||||
- "@nuxt/ui": "^2.21.0",
|
||||
+ "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@bf1c9e7",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run `pnpm install`, `yarn install` or `npm install`.
|
||||
::note
|
||||
**pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes.
|
||||
::
|
||||
|
||||
@@ -221,6 +221,52 @@ export default defineAppConfig({
|
||||
})
|
||||
```
|
||||
|
||||
### Extend Tailwind Merge
|
||||
|
||||
Tailwind Merge is a library that allows you to efficiently merge Tailwind CSS classes. It is used by this module to merge the classes from the `ui` prop, the `class` attribute, and the default classes.
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb" to="https://github.com/dcastil/tailwind-merge" target="_blank"}
|
||||
Learn more about Tailwind Merge.
|
||||
::
|
||||
|
||||
By default, Tailwind Merge doesn't handle custom Tailwind CSS configuration like custom colors, spacing, or other utilities you may have defined. You'll need to extend it to handle your custom configuration.
|
||||
|
||||
You can extend Tailwind Merge by using the `tailwindMerge` option in your `app.config.ts`:
|
||||
|
||||
::code-group
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
tailwindMerge: {
|
||||
extend: {
|
||||
theme: {
|
||||
spacing: ['sm', 'md', 'lg', 'xl', '2xl']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```ts [tailwind.config.ts]
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
export default <Partial<Config>>{
|
||||
theme: {
|
||||
extend: {
|
||||
spacing: {
|
||||
sm: '0.5rem',
|
||||
md: '1rem',
|
||||
lg: '1.5rem',
|
||||
xl: '2rem',
|
||||
'2xl': '2.5rem'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
::
|
||||
|
||||
## Dark mode
|
||||
|
||||
All the components are styled with dark mode in mind.
|
||||
@@ -343,6 +389,12 @@ export default defineAppConfig({
|
||||
loadingIcon: 'i-octicon-sync-24'
|
||||
}
|
||||
},
|
||||
inputMenu: {
|
||||
default: {
|
||||
selectedIcon: 'i-octicon-check-24',
|
||||
trailingIcon: 'i-octicon-chevron-down-24'
|
||||
}
|
||||
},
|
||||
select: {
|
||||
default: {
|
||||
loadingIcon: 'i-octicon-sync-24',
|
||||
@@ -378,6 +430,9 @@ export default defineAppConfig({
|
||||
sortButton: {
|
||||
icon: 'i-octicon-arrow-switch-24'
|
||||
},
|
||||
expandButton: {
|
||||
icon: 'i-octicon-chevron-down-24'
|
||||
},
|
||||
loadingState: {
|
||||
icon: 'i-octicon-sync-24'
|
||||
},
|
||||
@@ -411,6 +466,21 @@ export default defineAppConfig({
|
||||
default: {
|
||||
divider: 'i-octicon-chevron-right-24'
|
||||
}
|
||||
},
|
||||
carousel: {
|
||||
default: {
|
||||
prevButton: {
|
||||
icon: 'i-octicon-chevron-left-24'
|
||||
},
|
||||
nextButton: {
|
||||
icon: 'i-octicon-chevron-right-24'
|
||||
}
|
||||
}
|
||||
},
|
||||
toggle: {
|
||||
default: {
|
||||
loadingIcon: 'i-octicon-sync-24'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -49,18 +49,22 @@ defineShortcuts({
|
||||
Shortcuts keys are written as the literal keyboard key value. Combinations are made with `_` separator. Chained shortcuts are made with `-` separator.
|
||||
|
||||
Modifiers are also available:
|
||||
- `meta`: acts as `Command` for MacOS and `Control` for others
|
||||
- `ctrl`: acts as `Control`
|
||||
- `shift`: acts as `Shift` and is only necessary for alphabetic keys
|
||||
| Modifier | Description |
|
||||
|----------|-------------|
|
||||
| `meta` | Acts as `Command (⌘)` on macOS and `Control (Ctrl)` on Windows/Linux. |
|
||||
| `ctrl` | Represents the `Control (Ctrl)` key across all operating systems. |
|
||||
| `shift` | Represents the `Shift` key, only needed for alphabetic keys (e.g., `shift_e`). |
|
||||
|
||||
Examples of keys:
|
||||
- `escape`: will trigger by hitting `Esc`
|
||||
- `meta_k`: will trigger by hitting `⌘` and `K` at the same time on MacOS, and `Ctrl` and `K` on Windows and Linux
|
||||
- `ctrl_k`: will trigger by hitting `Ctrl` and `K` at the same time on MacOS, Windows and Linux
|
||||
- `shift_e`: will trigger by hitting `Shift` and `E` at the same time on MacOS, Windows and Linux
|
||||
- `?`: will trigger by hitting `?` on some keyboard layouts, or for example `Shift` and `/`, which results in `?` on US Mac keyboards
|
||||
- `g-d`: will trigger by hitting `g` then `d` with a maximum delay of 800ms by default
|
||||
- `arrowleft`: will trigger by hitting `←` (also: `arrowright`, `arrowup`, `arrowdown`)
|
||||
| Shortcut Key | Action |
|
||||
|---------------|--------|
|
||||
| `escape` | Triggers when `Esc` is pressed |
|
||||
| `meta_k` | `⌘ + K` on Mac, `Ctrl + K` on Windows/Linux |
|
||||
| `ctrl_k` | Triggers `Ctrl + K` on all OS |
|
||||
| `shift_e` | Triggers `Shift + E` on all OS |
|
||||
| `?` | Triggers `?` (Shift + `/` on US Mac keyboards) |
|
||||
| `g-d` | Triggers when `g` then `d` are pressed within 800ms |
|
||||
| `arrowleft` | Triggers when `←` is pressed (also: `arrowright`, `arrowup`, `arrowdown`) |
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
For a complete list of available shortcut keys, refer to the [`KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) API docs. Note the `KeyboardEvent.key` has to be written in lowercase.
|
||||
|
||||
@@ -141,6 +141,74 @@ 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
|
||||
|
||||
@@ -142,7 +142,7 @@ props:
|
||||
|
||||
### 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`.
|
||||
|
||||
@@ -173,6 +173,13 @@ baseProps:
|
||||
---
|
||||
::
|
||||
|
||||
### Limit
|
||||
|
||||
Use the `maxlength` prop to limit the length of the Input.
|
||||
|
||||
:component-example{component="input-example-max-length"}
|
||||
|
||||
|
||||
## Slots
|
||||
|
||||
### `leading`
|
||||
|
||||
@@ -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.
|
||||
- `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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
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
|
||||
---
|
||||
@@ -149,6 +149,7 @@ baseProps:
|
||||
description: 'This is a notification.'
|
||||
props:
|
||||
timeout: 60000
|
||||
pauseTimeoutOnHover: true
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ extraClass: 'overflow-hidden'
|
||||
padding: false
|
||||
component: 'table-example-columns-selectable'
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -393,7 +452,6 @@ Controls whether multiple rows can be expanded simultaneously in the table.
|
||||
<!-- Or simply -->
|
||||
<UTable />
|
||||
</template>
|
||||
|
||||
```
|
||||
|
||||
#### 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`
|
||||
|
||||
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',
|
||||
'@vueuse/nuxt',
|
||||
'nuxt-component-meta',
|
||||
'nuxt-cloudflare-analytics',
|
||||
'modules/content-examples-code'
|
||||
'nuxt-cloudflare-analytics'
|
||||
],
|
||||
|
||||
site: {
|
||||
|
||||
@@ -3,28 +3,29 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iconify-json/heroicons": "^1.2.1",
|
||||
"@iconify-json/simple-icons": "^1.2.11",
|
||||
"@iconify-json/vscode-icons": "^1.2.2",
|
||||
"@iconify-json/heroicons": "^1.2.2",
|
||||
"@iconify-json/lucide": "^1.2.28",
|
||||
"@iconify-json/simple-icons": "^1.2.27",
|
||||
"@iconify-json/vscode-icons": "^1.2.16",
|
||||
"@nuxt/content": "^2.13.4",
|
||||
"@nuxt/fonts": "^0.10.2",
|
||||
"@nuxt/image": "^1.8.1",
|
||||
"@nuxt/fonts": "^0.10.3",
|
||||
"@nuxt/image": "^1.9.0",
|
||||
"@nuxt/ui": "latest",
|
||||
"@nuxt/ui-pro": "^1.5.0",
|
||||
"@nuxtjs/plausible": "^1.0.3",
|
||||
"@octokit/rest": "^21.0.2",
|
||||
"@vueuse/nuxt": "^11.2.0",
|
||||
"@nuxt/ui-pro": "^1.7.0",
|
||||
"@nuxtjs/plausible": "^1.2.0",
|
||||
"@octokit/rest": "^21.1.1",
|
||||
"@vueuse/nuxt": "^12.8.2",
|
||||
"date-fns": "^4.1.0",
|
||||
"joi": "^17.13.3",
|
||||
"nuxt": "^3.14.159",
|
||||
"nuxt": "^3.16.0",
|
||||
"nuxt-cloudflare-analytics": "^1.0.8",
|
||||
"nuxt-component-meta": "^0.9.0",
|
||||
"nuxt-og-image": "^3.0.8",
|
||||
"prettier": "^3.3.3",
|
||||
"nuxt-component-meta": "^0.10.0",
|
||||
"nuxt-og-image": "^4.2.0",
|
||||
"prettier": "^3.5.3",
|
||||
"ufo": "^1.5.4",
|
||||
"v-calendar": "^3.1.2",
|
||||
"valibot": "^0.42.1",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "^3.23.8"
|
||||
"yup": "^1.6.1",
|
||||
"zod": "^3.24.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ const communityLinks = computed(() => [{
|
||||
const resourcesLinks = [{
|
||||
icon: 'i-simple-icons-figma',
|
||||
label: 'Figma Kit',
|
||||
to: 'https://www.figma.com/community/file/1288455405058138934',
|
||||
to: 'https://www.figma.com/community/file/1436401057300493073',
|
||||
target: '_blank'
|
||||
}, {
|
||||
label: 'Playground',
|
||||
|
||||
@@ -423,6 +423,7 @@ const { data: module } = await useFetch<{
|
||||
username: string
|
||||
}[]
|
||||
}>('https://api.nuxt.com/modules/ui', {
|
||||
key: 'stats',
|
||||
transform: ({ stats, contributors }) => ({ stats, contributors })
|
||||
})
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ if (!page.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||
}
|
||||
|
||||
const { data: releases } = await useFetch('/api/releases.json')
|
||||
const { data: pulls } = await useLazyFetch('/api/pulls.json', { default: () => [] })
|
||||
const { data: releases } = await useFetch('/api/releases.json', { key: 'releases-list' })
|
||||
const { data: pulls } = await useLazyFetch('/api/pulls.json', { default: () => [], key: 'pulls-list' })
|
||||
|
||||
const dates = computed(() => {
|
||||
const first = releases.value[releases.value.length - 1]
|
||||
|
||||
62
package.json
62
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@nuxt/ui",
|
||||
"description": "A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.",
|
||||
"version": "2.19.2",
|
||||
"packageManager": "pnpm@9.12.3",
|
||||
"version": "2.21.1",
|
||||
"packageManager": "pnpm@10.6.1",
|
||||
"repository": "nuxt/ui",
|
||||
"homepage": "https://ui.nuxt.com",
|
||||
"type": "module",
|
||||
@@ -32,54 +32,58 @@
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/tailwindcss": "^0.2.1",
|
||||
"@headlessui/tailwindcss": "^0.2.2",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@iconify-json/heroicons": "^1.2.1",
|
||||
"@nuxt/icon": "^1.6.1",
|
||||
"@nuxt/kit": "^3.14.159",
|
||||
"@iconify-json/heroicons": "^1.2.2",
|
||||
"@nuxt/icon": "^1.10.3",
|
||||
"@nuxt/kit": "^3.16.0",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||
"@nuxtjs/tailwindcss": "^6.13.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@vueuse/core": "^11.2.0",
|
||||
"@vueuse/integrations": "^11.2.0",
|
||||
"@vueuse/math": "^11.2.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vueuse/core": "^12.8.2",
|
||||
"@vueuse/integrations": "^12.8.2",
|
||||
"@vueuse/math": "^12.8.2",
|
||||
"defu": "^6.1.4",
|
||||
"fuse.js": "^7.0.0",
|
||||
"ohash": "^1.1.4",
|
||||
"pathe": "^1.1.2",
|
||||
"fuse.js": "^7.1.0",
|
||||
"ohash": "^2.0.11",
|
||||
"pathe": "^2.0.3",
|
||||
"scule": "^1.3.0",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss": "^3.4.14"
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss": "^3.4.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^0.6.1",
|
||||
"@nuxt/eslint-config": "^1.1.0",
|
||||
"@nuxt/module-builder": "^0.8.4",
|
||||
"@nuxt/test-utils": "^3.14.4",
|
||||
"@release-it/conventional-changelog": "^9.0.2",
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"@nuxt/test-utils": "^3.17.1",
|
||||
"@release-it/conventional-changelog": "^10.0.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "^9.14.0",
|
||||
"happy-dom": "^14.12.3",
|
||||
"eslint": "^9.22.0",
|
||||
"happy-dom": "^17.1.8",
|
||||
"joi": "^17.13.3",
|
||||
"nuxt": "^3.14.159",
|
||||
"release-it": "^17.10.0",
|
||||
"nuxt": "^3.16.0",
|
||||
"release-it": "^18.1.2",
|
||||
"superstruct": "^2.0.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"typescript": "^5.6.3",
|
||||
"valibot": "^0.42.1",
|
||||
"valibot30": "npm:valibot@0.30.0",
|
||||
"valibot31": "npm:valibot@0.31.0",
|
||||
"vitest": "^2.1.4",
|
||||
"vitest": "^3.0.8",
|
||||
"vitest-environment-nuxt": "^1.0.1",
|
||||
"vue-tsc": "^2.1.10",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "^3.23.8"
|
||||
"yup": "^1.6.1",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@nuxt/ui": "workspace:*",
|
||||
"@nuxt/content": "2.13.2",
|
||||
"@nuxtjs/mdc": "0.9.0"
|
||||
"@nuxtjs/mdc": "0.9.0",
|
||||
"chokidar": "3.6.0",
|
||||
"vue-tsc": "2.1.10",
|
||||
"typescript": "5.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/ui": "latest",
|
||||
"nuxt": "^3.14.159"
|
||||
"nuxt": "^3.16.0"
|
||||
}
|
||||
}
|
||||
|
||||
8667
pnpm-lock.yaml
generated
8667
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@
|
||||
"enabled": true
|
||||
},
|
||||
"ignoreDeps": [
|
||||
"happy-dom",
|
||||
"valibot30",
|
||||
"valibot31"
|
||||
],
|
||||
@@ -24,5 +23,6 @@
|
||||
}, {
|
||||
"matchDepTypes": ["resolutions"],
|
||||
"enabled": false
|
||||
}]
|
||||
}],
|
||||
"postUpdateOptions": ["pnpmDedupe"]
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { execSync } from 'node:child_process'
|
||||
|
||||
async function loadPackage(dir: string) {
|
||||
const pkgPath = resolve(dir, 'package.json')
|
||||
|
||||
const data = JSON.parse(await fsp.readFile(pkgPath, 'utf-8').catch(() => '{}'))
|
||||
|
||||
const save = () => fsp.writeFile(pkgPath, JSON.stringify(data, null, 2) + '\n')
|
||||
|
||||
return {
|
||||
dir,
|
||||
data,
|
||||
save
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const pkg = await loadPackage(process.cwd())
|
||||
|
||||
const commit = execSync('git rev-parse --short HEAD').toString('utf-8').trim()
|
||||
|
||||
const date = Math.round(Date.now() / (1000 * 60))
|
||||
|
||||
pkg.data.name = `${pkg.data.name}-edge`
|
||||
|
||||
pkg.data.version = `${pkg.data.version}-${date}.${commit}`
|
||||
|
||||
pkg.save()
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Restore all git changes
|
||||
git restore -s@ -SW -- .
|
||||
|
||||
# Bump versions to edge
|
||||
pnpm jiti ./scripts/bump-edge
|
||||
|
||||
# Update token
|
||||
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
|
||||
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
|
||||
echo "registry=https://registry.npmjs.org/" >> ~/.npmrc
|
||||
echo "always-auth=true" >> ~/.npmrc
|
||||
npm whoami
|
||||
fi
|
||||
|
||||
# Release package
|
||||
echo "Publishing @nuxt/ui"
|
||||
npm publish -q --access public
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
|
||||
import type { ConfigExtension, DefaultClassGroupIds, DefaultThemeGroupIds } from 'tailwind-merge'
|
||||
import { name, version } from '../package.json'
|
||||
import createTemplates from './templates'
|
||||
import type * as config from './runtime/ui.config'
|
||||
@@ -20,6 +21,7 @@ type UI = {
|
||||
gray?: string
|
||||
colors?: string[]
|
||||
strategy?: Strategy
|
||||
tailwindMerge?: ConfigExtension<DefaultClassGroupIds, DefaultThemeGroupIds>
|
||||
[key: string]: any
|
||||
} & DeepPartial<typeof config, string | number | boolean>
|
||||
|
||||
@@ -41,6 +43,11 @@ export interface ModuleOptions {
|
||||
*/
|
||||
global?: boolean
|
||||
|
||||
/**
|
||||
* @default true
|
||||
*/
|
||||
colorMode?: boolean
|
||||
|
||||
safelistColors?: string[]
|
||||
/**
|
||||
* Disables the global css styles added by the module.
|
||||
@@ -59,6 +66,7 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
},
|
||||
defaults: {
|
||||
prefix: 'U',
|
||||
colorMode: true,
|
||||
safelistColors: ['primary'],
|
||||
disableGlobalStyles: false
|
||||
},
|
||||
@@ -81,7 +89,9 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
// Modules
|
||||
|
||||
await installModule('@nuxt/icon')
|
||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||
if (options.colorMode) {
|
||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||
}
|
||||
await installTailwind(options, nuxt, resolve)
|
||||
|
||||
// Plugins
|
||||
|
||||
@@ -8,28 +8,27 @@
|
||||
</slot>
|
||||
<thead :class="ui.thead">
|
||||
<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">
|
||||
<span class="sr-only">Expand</span>
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-for="(column, index) in columns"
|
||||
:key="index"
|
||||
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)"
|
||||
>
|
||||
<slot :name="`${column.key}-header`" :column="column" :sort="sort" :on-sort="onSort">
|
||||
<slot v-if="!singleSelect && modelValue && column.key === '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
|
||||
v-if="column.sortable"
|
||||
v-bind="{ ...(ui.default.sortButton || {}), ...sortButton }"
|
||||
@@ -77,16 +76,7 @@
|
||||
|
||||
<template v-else>
|
||||
<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)">
|
||||
<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>
|
||||
<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="expand"
|
||||
:class="[ui.td.base, ui.td.padding, ui.td.color, ui.td.font, ui.td.size]"
|
||||
@@ -102,8 +92,24 @@
|
||||
@click.capture.stop="toggleOpened(row)"
|
||||
/>
|
||||
</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]">
|
||||
<slot :name="`${column.key}-data`" :column="column" :row="row" :index="index" :get-row-data="(defaultValue) => getRowData(row, column.key, defaultValue)">
|
||||
<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 v-if="modelValue && column.key === '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)"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<slot
|
||||
v-else
|
||||
:name="`${column.key}-data`"
|
||||
:column="column"
|
||||
:row="row"
|
||||
:index="index"
|
||||
:get-row-data="(defaultValue) => getRowData(row, column.key, defaultValue)"
|
||||
>
|
||||
{{ getRowData(row, column.key) }}
|
||||
</slot>
|
||||
</td>
|
||||
@@ -130,12 +136,13 @@ import type { PropType, AriaAttributes } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import { defu } from 'defu'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isEqual } from 'ohash/utils'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import UProgress from '../elements/Progress.vue'
|
||||
import UCheckbox from '../forms/Checkbox.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, get } from '../../utils'
|
||||
import { get, mergeConfig } from '../../utils'
|
||||
import type { TableRow, TableColumn, Strategy, Button, ProgressColor, ProgressAnimation, DeepPartial, Expanded } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
@@ -144,7 +151,7 @@ import { table } from '#ui/ui.config'
|
||||
const config = mergeConfig<typeof table>(appConfig.ui.strategy, appConfig.ui.table, table)
|
||||
|
||||
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') {
|
||||
@@ -159,6 +166,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({
|
||||
components: {
|
||||
UIcon,
|
||||
@@ -221,7 +236,7 @@ export default defineComponent({
|
||||
default: false
|
||||
},
|
||||
loadingState: {
|
||||
type: Object as PropType<{ icon: string, label: string }>,
|
||||
type: Object as PropType<{ icon: string, label: string } | null>,
|
||||
default: () => config.default.loadingState
|
||||
},
|
||||
emptyState: {
|
||||
@@ -247,13 +262,40 @@ export default defineComponent({
|
||||
multipleExpand: {
|
||||
type: Boolean,
|
||||
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 }) {
|
||||
const { ui, attrs } = useUI('table', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
const columns = computed(() => props.columns ?? Object.keys(props.rows[0] ?? {}).map(key => ({ key, label: upperFirst(key), sortable: false, class: undefined, sort: defaultSort }) as TableColumn))
|
||||
const columns = computed(() => {
|
||||
const defaultColumns = props.columns ?? (
|
||||
Object.keys(props.rows[0]).map(key => ({
|
||||
key,
|
||||
label: upperFirst(key),
|
||||
sortable: false,
|
||||
class: undefined,
|
||||
sort: defaultSort
|
||||
}))
|
||||
) as TableColumn[]
|
||||
|
||||
const hasColumnSelect = defaultColumns.find(v => v.key === 'select')
|
||||
|
||||
if (hasColumnSelect || !props.modelValue) {
|
||||
return defaultColumns
|
||||
}
|
||||
|
||||
return [{
|
||||
key: 'select',
|
||||
sortable: false,
|
||||
class: undefined,
|
||||
sort: defaultSort
|
||||
}, ...defaultColumns]
|
||||
})
|
||||
|
||||
const sort = useVModel(props, 'sort', emit, { passive: true, defaultValue: defu({}, props.sort, { column: null, direction: 'asc' }) })
|
||||
const expand = useVModel(props, 'expand', emit, {
|
||||
@@ -292,8 +334,6 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
const getStringifiedSet = (arr: TableRow[]) => new Set(arr.map(item => JSON.stringify(item)))
|
||||
|
||||
const totalRows = computed(() => props.rows.length)
|
||||
|
||||
const countCheckedRow = computed(() => {
|
||||
@@ -328,10 +368,6 @@ export default defineComponent({
|
||||
return props.by(a, z)
|
||||
}
|
||||
|
||||
function accessor<T extends Record<string, any>>(key: string) {
|
||||
return (obj: T) => get(obj, key)
|
||||
}
|
||||
|
||||
function isSelected(row: TableRow) {
|
||||
if (!props.modelValue) {
|
||||
return false
|
||||
@@ -355,6 +391,11 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function onSelect(row: TableRow) {
|
||||
const selection = window.getSelection()
|
||||
if (selection && selection.toString().length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!$attrs.onSelect) {
|
||||
return
|
||||
}
|
||||
@@ -393,14 +434,14 @@ export default defineComponent({
|
||||
} else {
|
||||
selected.value = []
|
||||
}
|
||||
emit('select:all', checked)
|
||||
}
|
||||
|
||||
function onChangeCheckbox(checked: boolean, row: TableRow) {
|
||||
if (checked) {
|
||||
selected.value.push(row)
|
||||
selected.value = props.singleSelect ? [row] : [...selected.value, row]
|
||||
} else {
|
||||
const index = selected.value.findIndex(item => compare(item, row))
|
||||
selected.value.splice(index, 1)
|
||||
selected.value = selected.value.filter(value => !compare(toRaw(value), toRaw(row)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ export default defineComponent({
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: ['open'],
|
||||
emits: ['open', 'close'],
|
||||
setup(props, { emit }) {
|
||||
const { ui, attrs } = useUI('accordion', toRef(props, 'ui'), config, toRef(props, 'class'))
|
||||
|
||||
@@ -142,6 +142,8 @@ export default defineComponent({
|
||||
|
||||
if (!isOpenBefore && isOpenAfter) {
|
||||
emit('open', index)
|
||||
} else if (isOpenBefore && !isOpenAfter) {
|
||||
emit('close', index)
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{{ title }}
|
||||
</slot>
|
||||
</p>
|
||||
<div v-if="description || $slots.description" :class="twMerge(ui.description, !title && !$slots.title && 'mt-0 leading-5')">
|
||||
<div v-if="description || $slots.description" :class="twMerge(ui.description, !title && !$slots.title && ui.descriptionOnly)">
|
||||
<slot name="description" :description="description">
|
||||
{{ description }}
|
||||
</slot>
|
||||
@@ -42,13 +42,13 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import type { Avatar, Button, AlertColor, AlertVariant, AlertAction, Strategy, DeepPartial } from '../../types/index'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
import { alert } from '#ui/ui.config'
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, toRef, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { AvatarSize, AvatarChipColor, AvatarChipPosition, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { h, cloneVNode, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getSlotsChildren } from '../../utils'
|
||||
import { getSlotsChildren, mergeConfig, twMerge } from '../../utils'
|
||||
import type { AvatarSize, DeepPartial, Strategy } from '../../types/index'
|
||||
import UAvatar from './Avatar.vue'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { BadgeColor, BadgeSize, BadgeVariant, DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
@@ -19,6 +32,9 @@ 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: {
|
||||
@@ -49,6 +65,26 @@ 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: () => ''
|
||||
@@ -63,6 +99,14 @@ 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]
|
||||
|
||||
@@ -71,13 +115,42 @@ 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,
|
||||
badgeClass
|
||||
isLeading,
|
||||
isTrailing,
|
||||
badgeClass,
|
||||
leadingIconName,
|
||||
trailingIconName,
|
||||
leadingIconClass,
|
||||
trailingIconClass
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, nuxtLinkProps, getNuxtLinkProps } from '../../utils'
|
||||
import { getNuxtLinkProps, mergeConfig, nuxtLinkProps, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { ButtonColor, ButtonSize, ButtonVariant, DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { h, computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getSlotsChildren } from '../../utils'
|
||||
import { getSlotsChildren, mergeConfig, twMerge } from '../../utils'
|
||||
import { useProvideButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { ButtonSize, DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -58,9 +58,8 @@
|
||||
<script lang="ts">
|
||||
import { ref, toRef, computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { useScroll, useResizeObserver, useElementSize } from '@vueuse/core'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import type { Strategy, Button, DeepPartial } from '../../types/index'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
@@ -106,7 +105,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
|
||||
}
|
||||
},
|
||||
|
||||
@@ -60,13 +60,13 @@ import { defineComponent, ref, computed, watch, toRef, onMounted, resolveCompone
|
||||
import type { PropType } from 'vue'
|
||||
import { Menu as HMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem, provideUseId } from '@headlessui/vue'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UKbd from '../elements/Kbd.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { mergeConfig, getNuxtLinkProps } from '../../utils'
|
||||
import { getNuxtLinkProps, mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, DropdownItem, PopperOptions, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<script lang="ts">
|
||||
import { toRef, defineComponent, computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, KbdSize, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { isEqual } from 'ohash'
|
||||
import { defineComponent } from 'vue'
|
||||
import { isEqual, diff } from 'ohash/utils'
|
||||
import { type PropType, defineComponent } from 'vue'
|
||||
import { nuxtLinkProps } from '../../utils'
|
||||
|
||||
export default defineComponent({
|
||||
@@ -61,7 +61,7 @@ export default defineComponent({
|
||||
default: false
|
||||
},
|
||||
exactQuery: {
|
||||
type: Boolean,
|
||||
type: [Boolean, String] as PropType<boolean | 'partial'>,
|
||||
default: false
|
||||
},
|
||||
exactHash: {
|
||||
@@ -74,9 +74,25 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
function isPartiallyEqual(item1: any, item2: any) {
|
||||
const diffedKeys = diff(item1, item2).reduce((filtered, q) => {
|
||||
if (q.type === 'added') {
|
||||
filtered.add(q.key)
|
||||
}
|
||||
return filtered
|
||||
}, new Set<string>())
|
||||
|
||||
const item1Filtered = Object.fromEntries(Object.entries(item1).filter(([key]) => !diffedKeys.has(key)))
|
||||
const item2Filtered = Object.fromEntries(Object.entries(item2).filter(([key]) => !diffedKeys.has(key)))
|
||||
|
||||
return isEqual(item1Filtered, item2Filtered)
|
||||
}
|
||||
|
||||
function resolveLinkClass(route, $route, { isActive, isExactActive }: { isActive: boolean, isExactActive: boolean }) {
|
||||
if (props.exactQuery && !isEqual(route.query, $route.query)) {
|
||||
return props.inactiveClass
|
||||
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.exactHash && route.hash !== $route.hash) {
|
||||
return props.inactiveClass
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ComputedRef, VNode, SlotsType, PropType } from 'vue'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getSlotsChildren } from '../../utils'
|
||||
import { getSlotsChildren, mergeConfig } from '../../utils'
|
||||
import type { DeepPartial, Strategy, MeterSize } from '../../types/index'
|
||||
import type Meter from './Meter.vue'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } fro
|
||||
import type { BaseSchema as ValibotSchema30, BaseSchemaAsync as ValibotSchemaAsync30 } from 'valibot30'
|
||||
import type { GenericSchema as ValibotSchema31, GenericSchemaAsync as ValibotSchemaAsync31, SafeParser as ValibotSafeParser31, SafeParserAsync as ValibotSafeParserAsync31 } from 'valibot31'
|
||||
import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchemaAsync, SafeParser as ValibotSafeParser, SafeParserAsync as ValibotSafeParserAsync } from 'valibot'
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||
import type { Struct } from 'superstruct'
|
||||
import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form, ValidateReturnSchema } from '../../types/form'
|
||||
import { useId } from '#imports'
|
||||
@@ -33,6 +34,7 @@ type Schema = PropType<ZodSchema>
|
||||
| PropType<ValibotSafeParser31<any, any> | ValibotSafeParserAsync31<any, any>>
|
||||
| PropType<ValibotSchema | ValibotSchemaAsync>
|
||||
| PropType<ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>> | PropType<Struct<any, any>>
|
||||
| PropType<StandardSchemaV1>
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -60,6 +62,8 @@ 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)) {
|
||||
@@ -87,7 +91,7 @@ export default defineComponent({
|
||||
if (errors) {
|
||||
errs = errs.concat(errors)
|
||||
} else {
|
||||
Object.assign(props.state, result)
|
||||
parsedValue.value = result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +134,7 @@ export default defineComponent({
|
||||
if (props.validateOn?.includes('submit')) {
|
||||
await validate()
|
||||
}
|
||||
event.data = props.state
|
||||
event.data = props.schema ? parsedValue.value : props.state
|
||||
emit('submit', event)
|
||||
} catch (error) {
|
||||
if (!(error instanceof FormException)) {
|
||||
@@ -218,6 +222,35 @@ function isZodSchema(schema: any): schema is ZodSchema {
|
||||
return schema.parse !== undefined
|
||||
}
|
||||
|
||||
export function isStandardSchema(schema: any): schema is StandardSchemaV1 {
|
||||
return '~standard' in schema
|
||||
}
|
||||
|
||||
export async function validateStandardSchema(
|
||||
state: any,
|
||||
schema: StandardSchemaV1
|
||||
): Promise<ValidateReturnSchema<typeof state>> {
|
||||
const result = await schema['~standard'].validate(state)
|
||||
|
||||
if (!result.issues || result.issues.length === 0) {
|
||||
const output = ('value' in result ? result.value : null)
|
||||
return {
|
||||
errors: null,
|
||||
result: output
|
||||
}
|
||||
}
|
||||
|
||||
const errors = result.issues.map(issue => ({
|
||||
path: issue.path?.map(item => typeof item === 'object' ? item.key : item).join('.') || '',
|
||||
message: issue.message
|
||||
}))
|
||||
|
||||
return {
|
||||
errors,
|
||||
result: null
|
||||
}
|
||||
}
|
||||
|
||||
async function validateValibotSchema(
|
||||
state: any,
|
||||
schema: ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31<any, any> | ValibotSafeParserAsync31<any, any> | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser<any, any> | ValibotSafeParserAsync<any, any>
|
||||
@@ -252,10 +285,10 @@ async function validateJoiSchema(
|
||||
schema: JoiSchema
|
||||
): Promise<ValidateReturnSchema<typeof state>> {
|
||||
try {
|
||||
await schema.validateAsync(state, { abortEarly: false })
|
||||
const result = await schema.validateAsync(state, { abortEarly: false })
|
||||
return {
|
||||
errors: null,
|
||||
result: state
|
||||
result
|
||||
}
|
||||
} catch (error) {
|
||||
if (isJoiError(error)) {
|
||||
@@ -321,7 +354,7 @@ async function validateYupSchema(
|
||||
schema: YupObjectSchema<any>
|
||||
): Promise<ValidateReturnSchema<typeof state>> {
|
||||
try {
|
||||
const result = schema.validateSync(state, { abortEarly: false })
|
||||
const result = await schema.validate(state, { abortEarly: false })
|
||||
return {
|
||||
errors: null,
|
||||
result
|
||||
@@ -344,7 +377,9 @@ async function validateYupSchema(
|
||||
}
|
||||
|
||||
function parseSchema(state: any, schema: Schema): Promise<ValidateReturnSchema<typeof state>> {
|
||||
if (isZodSchema(schema)) {
|
||||
if (isStandardSchema(schema)) {
|
||||
return validateStandardSchema(state, schema)
|
||||
} else if (isZodSchema(schema)) {
|
||||
return validateZodSchema(state, schema)
|
||||
} else if (isJoiSchema(schema)) {
|
||||
return validateJoiSchema(state, schema)
|
||||
|
||||
@@ -33,12 +33,12 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, onMounted, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { defu } from 'defu'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig, looseToNumber } from '../../utils'
|
||||
import { looseToNumber, mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { InputSize, InputColor, InputVariant, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
v-slot="{ active, selected, disabled: optionDisabled }"
|
||||
:key="index"
|
||||
as="template"
|
||||
:value="valueAttribute ? option[valueAttribute] : option"
|
||||
:value="valueAttribute ? accessor(option, valueAttribute) : option"
|
||||
: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]">
|
||||
@@ -103,13 +103,14 @@ import {
|
||||
} from '@headlessui/vue'
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { isEqual } from 'ohash/utils'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { get, mergeConfig } from '../../utils'
|
||||
import { get, mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { InputSize, InputColor, InputVariant, PopperOptions, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
@@ -292,6 +293,24 @@ 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() {
|
||||
@@ -304,12 +323,30 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const label = computed(() => {
|
||||
if (!props.modelValue) {
|
||||
return
|
||||
if (!props.modelValue) return null
|
||||
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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,
|
||||
accessor,
|
||||
onUpdate,
|
||||
onQueryChange
|
||||
onQueryChange,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
by
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -34,7 +34,7 @@ import { computed, defineComponent, provide, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig, get } from '../../utils'
|
||||
import { get, mergeConfig } from '../../utils'
|
||||
import type { DeepPartial, Strategy } from '../../types/index'
|
||||
import URadio from './Radio.vue'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { RangeSize, RangeColor, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -55,11 +55,11 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType, ComputedRef } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig, get } from '../../utils'
|
||||
import { get, mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { SelectSize, SelectColor, SelectVariant, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
:value="modelValue"
|
||||
:required="required"
|
||||
:class="uiMenu.required"
|
||||
:form="inputTargetForm"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -71,7 +72,7 @@
|
||||
v-slot="{ active, selected: optionSelected, disabled: optionDisabled }"
|
||||
:key="index"
|
||||
as="template"
|
||||
:value="valueAttribute ? option[valueAttribute] : option"
|
||||
:value="valueAttribute ? accessor(option, valueAttribute) : option"
|
||||
: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]">
|
||||
@@ -139,13 +140,14 @@ import {
|
||||
} from '@headlessui/vue'
|
||||
import { computedAsync, useDebounceFn } from '@vueuse/core'
|
||||
import { defu } from 'defu'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { isEqual } from 'ohash/utils'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { get, mergeConfig } from '../../utils'
|
||||
import { get, mergeConfig, twMerge } from '../../utils'
|
||||
import { useInjectButtonGroup } from '../../composables/useButtonGroup'
|
||||
import type { SelectSize, SelectColor, SelectVariant, PopperOptions, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
@@ -313,6 +315,10 @@ export default defineComponent({
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
inputTargetForm: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
popper: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({})
|
||||
@@ -347,6 +353,24 @@ 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)
|
||||
|
||||
@@ -364,39 +388,49 @@ 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])
|
||||
}
|
||||
return isEqual(value1, value2)
|
||||
}
|
||||
|
||||
function getValue(value: any) {
|
||||
if (props.valueAttribute) {
|
||||
return accessor(value, props.valueAttribute)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
if (props.multiple) {
|
||||
if (!Array.isArray(props.modelValue) || !props.modelValue.length) {
|
||||
const modelValue = props.modelValue
|
||||
if (!Array.isArray(modelValue) || !modelValue.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (props.valueAttribute) {
|
||||
return options.value.filter(option => (props.modelValue as any[]).includes(option[props.valueAttribute]))
|
||||
}
|
||||
return options.value.filter(option => (props.modelValue as any[]).includes(option))
|
||||
return options.value.filter((option) => {
|
||||
const optionValue = getValue(option)
|
||||
return modelValue.some(value => compareValues(value, optionValue))
|
||||
})
|
||||
}
|
||||
|
||||
if (props.valueAttribute) {
|
||||
return options.value.find(option => option[props.valueAttribute] === props.modelValue)
|
||||
}
|
||||
return options.value.find(option => option === props.modelValue)
|
||||
return options.value.find((option) => {
|
||||
const optionValue = getValue(option)
|
||||
return compareValues(optionValue, toRaw(props.modelValue))
|
||||
}) ?? props.modelValue
|
||||
})
|
||||
|
||||
const label = computed(() => {
|
||||
if (props.multiple) {
|
||||
if (Array.isArray(props.modelValue) && props.modelValue.length) {
|
||||
return `${selected.value.length} selected`
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
if (!props.modelValue) return null
|
||||
|
||||
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 null
|
||||
return accessor(props.modelValue as Record<string, any>, props.optionAttribute)
|
||||
})
|
||||
|
||||
const selectClass = computed(() => {
|
||||
@@ -597,7 +631,9 @@ export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
query,
|
||||
onUpdate,
|
||||
onQueryChange
|
||||
onQueryChange,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
by
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, watch, onMounted, nextTick, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { defu } from 'defu'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig, looseToNumber } from '../../utils'
|
||||
import { looseToNumber, mergeConfig, twMerge } from '../../utils'
|
||||
import type { TextareaSize, TextareaColor, TextareaVariant, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { Switch as HSwitch, provideUseId } from '@headlessui/vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useFormGroup } from '../../composables/useFormGroup'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { ToggleSize, ToggleColor, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
<script lang="ts">
|
||||
import { toRef, computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Avatar, DeepPartial, DividerSize, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRef } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getULinkProps } from '../../utils'
|
||||
import { getULinkProps, mergeConfig, twMerge } from '../../utils'
|
||||
import type { BreadcrumbLink, DeepPartial, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
<script lang="ts">
|
||||
import { toRef, defineComponent, computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UBadge from '../elements/Badge.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getULinkProps } from '../../utils'
|
||||
import { getULinkProps, mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, HorizontalNavigationLink, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -55,14 +55,14 @@
|
||||
<script lang="ts">
|
||||
import { toRef, defineComponent, computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UBadge from '../elements/Badge.vue'
|
||||
import ULink from '../elements/Link.vue'
|
||||
import UDivider from '../layout/Divider.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { mergeConfig, getULinkProps } from '../../utils'
|
||||
import { getULinkProps, mergeConfig, twMerge } from '../../utils'
|
||||
import type { VerticalNavigationLink, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -18,10 +18,10 @@ import type { PropType, Ref } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import type { VirtualElement } from '@popperjs/core'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, PopperOptions, Strategy } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{{ title }}
|
||||
</slot>
|
||||
</p>
|
||||
<div v-if="(description || $slots.description)" :class="twMerge(ui.description, !title && !$slots.title && 'mt-0 leading-5')">
|
||||
<div v-if="(description || $slots.description)" :class="twMerge(ui.description, !title && !$slots.title && ui.descriptionOnly)">
|
||||
<slot name="description" :description="description">
|
||||
{{ description }}
|
||||
</slot>
|
||||
@@ -45,13 +45,13 @@
|
||||
<script lang="ts">
|
||||
import { ref, computed, toRef, onMounted, onUnmounted, watch, watchEffect, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useTimer } from '../../composables/useTimer'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { Avatar, Button, NotificationColor, NotificationAction, Strategy, DeepPartial } from '../../types/index'
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
@@ -117,6 +117,10 @@ export default defineComponent({
|
||||
ui: {
|
||||
type: Object as PropType<DeepPartial<typeof config> & { strategy?: Strategy }>,
|
||||
default: () => ({})
|
||||
},
|
||||
pauseTimeoutOnHover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
@@ -157,13 +161,13 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
function onMouseover() {
|
||||
if (timer) {
|
||||
if (props.pauseTimeoutOnHover && timer) {
|
||||
timer.pause()
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseleave() {
|
||||
if (timer) {
|
||||
if (props.pauseTimeoutOnHover && timer) {
|
||||
timer.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div :class="wrapperClass" role="region" v-bind="attrs">
|
||||
<div v-if="notifications.length" :class="ui.container">
|
||||
<div v-if="notifications.length" :class="wrapperClass" role="region" v-bind="attrs">
|
||||
<div :class="ui.container">
|
||||
<div v-for="notification of notifications" :key="notification.id">
|
||||
<UNotification
|
||||
v-bind="notification"
|
||||
@@ -22,10 +22,10 @@
|
||||
<script lang="ts">
|
||||
import { computed, toRef, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { twMerge, twJoin } from 'tailwind-merge'
|
||||
import { twJoin } from 'tailwind-merge'
|
||||
import { useUI } from '../../composables/useUI'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
import { mergeConfig } from '../../utils'
|
||||
import { mergeConfig, twMerge } from '../../utils'
|
||||
import type { DeepPartial, Notification, Strategy } from '../../types/index'
|
||||
import UNotification from './Notification.vue'
|
||||
import { useState } from '#imports'
|
||||
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ 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'
|
||||
|
||||
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
|
||||
active?: boolean
|
||||
exact?: boolean
|
||||
exactQuery?: boolean
|
||||
exactQuery?: boolean | 'partial'
|
||||
exactHash?: boolean
|
||||
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> = {
|
||||
[P in keyof T]?: T[P] extends object
|
||||
? DeepPartial<T[P], O>
|
||||
: T[P];
|
||||
: T[P] extends string ? string : T[P];
|
||||
} & {
|
||||
[key: string]: O | TightMap<O>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ButtonColor, ButtonSize, ButtonVariant, CheckboxColor, ProgressAnimation, ProgressColor } from '../../types'
|
||||
|
||||
export default {
|
||||
wrapper: 'relative overflow-x-auto',
|
||||
base: 'min-w-full table-fixed',
|
||||
@@ -51,23 +53,23 @@ export default {
|
||||
icon: 'i-heroicons-arrows-up-down-20-solid',
|
||||
trailing: true,
|
||||
square: true,
|
||||
color: 'gray' as const,
|
||||
variant: 'ghost' as const,
|
||||
color: 'gray' as ButtonColor,
|
||||
variant: 'ghost' as ButtonVariant,
|
||||
class: '-m-1.5'
|
||||
},
|
||||
expandButton: {
|
||||
icon: 'i-heroicons-chevron-down',
|
||||
color: 'gray' as const,
|
||||
variant: 'ghost' as const,
|
||||
size: 'xs' as const,
|
||||
color: 'gray' as ButtonColor,
|
||||
variant: 'ghost' as ButtonVariant,
|
||||
size: 'xs' as ButtonSize,
|
||||
class: '-my-1.5 align-middle'
|
||||
},
|
||||
checkbox: {
|
||||
color: 'primary' as const
|
||||
color: 'primary' as CheckboxColor
|
||||
},
|
||||
progress: {
|
||||
color: 'primary' as const,
|
||||
animation: 'carousel' as const
|
||||
color: 'primary' as ProgressColor,
|
||||
animation: 'carousel' as ProgressAnimation
|
||||
},
|
||||
loadingState: {
|
||||
icon: 'i-heroicons-arrow-path-20-solid',
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
openIcon: 'i-heroicons-chevron-down-20-solid',
|
||||
closeIcon: '',
|
||||
class: 'mb-1.5 w-full',
|
||||
variant: 'soft' as const,
|
||||
variant: 'soft',
|
||||
truncate: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import type { AvatarSize, ButtonColor, ButtonSize, ButtonVariant } from '../../types'
|
||||
|
||||
export default {
|
||||
wrapper: 'w-full relative overflow-hidden',
|
||||
inner: 'w-0 flex-1',
|
||||
title: 'text-sm font-medium',
|
||||
description: 'mt-1 text-sm leading-4 opacity-90',
|
||||
descriptionOnly: 'mt-0 leading-5',
|
||||
actions: 'flex items-center gap-2 mt-3 flex-shrink-0',
|
||||
shadow: '',
|
||||
rounded: 'rounded-lg',
|
||||
@@ -13,7 +16,7 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0 self-center',
|
||||
size: 'md' as const
|
||||
size: 'md' as AvatarSize
|
||||
},
|
||||
color: {
|
||||
white: {
|
||||
@@ -32,9 +35,9 @@ export default {
|
||||
icon: null,
|
||||
closeButton: null,
|
||||
actionButton: {
|
||||
size: 'xs' as const,
|
||||
color: 'primary' as const,
|
||||
variant: 'link' as const
|
||||
size: 'xs' as ButtonSize,
|
||||
color: 'primary' as ButtonColor,
|
||||
variant: 'link' as ButtonVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ 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'
|
||||
@@ -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',
|
||||
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',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ButtonColor } from '../../types'
|
||||
|
||||
export default {
|
||||
wrapper: 'relative',
|
||||
container: 'relative w-full flex overflow-x-auto snap-x snap-mandatory scroll-smooth',
|
||||
@@ -13,12 +15,12 @@ export default {
|
||||
},
|
||||
default: {
|
||||
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',
|
||||
icon: 'i-heroicons-chevron-left-20-solid'
|
||||
},
|
||||
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',
|
||||
icon: 'i-heroicons-chevron-right-20-solid'
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AvatarSize } from '../../types'
|
||||
import { arrow } from '../popper'
|
||||
|
||||
export default {
|
||||
@@ -28,7 +29,7 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '2xs' as const
|
||||
size: '2xs' as AvatarSize
|
||||
},
|
||||
label: 'truncate',
|
||||
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'
|
||||
|
||||
export default {
|
||||
@@ -36,7 +37,7 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '2xs' as const
|
||||
size: '2xs' as AvatarSize
|
||||
},
|
||||
chip: {
|
||||
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { AvatarSize } from '../../types'
|
||||
|
||||
export default {
|
||||
wrapper: {
|
||||
base: 'flex items-center align-center text-center',
|
||||
@@ -42,11 +44,11 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '2xs' as const
|
||||
size: '2xs' as AvatarSize
|
||||
},
|
||||
label: 'text-sm',
|
||||
default: {
|
||||
size: '2xs' as const,
|
||||
type: 'solid' as const
|
||||
size: '2xs',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
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',
|
||||
@@ -46,7 +48,7 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '2xs' as const
|
||||
size: '2xs' as AvatarSize
|
||||
},
|
||||
chip: {
|
||||
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 {
|
||||
wrapper: 'relative w-full flex items-center justify-between',
|
||||
container: 'flex items-center min-w-0',
|
||||
@@ -15,12 +17,12 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '2xs' as const
|
||||
size: '2xs' as AvatarSize
|
||||
},
|
||||
badge: {
|
||||
base: 'flex-shrink-0 ms-auto relative rounded',
|
||||
color: 'gray' as const,
|
||||
variant: 'solid' as const,
|
||||
size: 'xs' as const
|
||||
color: 'gray' as BadgeColor,
|
||||
variant: 'solid' as BadgeVariant,
|
||||
size: 'xs' as BadgeSize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ButtonColor } from '../../types'
|
||||
|
||||
export default {
|
||||
wrapper: 'flex items-center -space-x-px',
|
||||
base: '',
|
||||
@@ -5,28 +7,28 @@ export default {
|
||||
default: {
|
||||
size: 'sm',
|
||||
activeButton: {
|
||||
color: 'primary' as const
|
||||
color: 'primary' as ButtonColor
|
||||
},
|
||||
inactiveButton: {
|
||||
color: 'white' as const
|
||||
color: 'white' as ButtonColor
|
||||
},
|
||||
firstButton: {
|
||||
color: 'white' as const,
|
||||
color: 'white' as ButtonColor,
|
||||
class: 'rtl:[&_span:first-child]:rotate-180',
|
||||
icon: 'i-heroicons-chevron-double-left-20-solid'
|
||||
},
|
||||
lastButton: {
|
||||
color: 'white' as const,
|
||||
color: 'white' as ButtonColor,
|
||||
class: 'rtl:[&_span:last-child]:rotate-180',
|
||||
icon: 'i-heroicons-chevron-double-right-20-solid'
|
||||
},
|
||||
prevButton: {
|
||||
color: 'white' as const,
|
||||
color: 'white' as ButtonColor,
|
||||
class: 'rtl:[&_span:first-child]:rotate-180',
|
||||
icon: 'i-heroicons-chevron-left-20-solid'
|
||||
},
|
||||
nextButton: {
|
||||
color: 'white' as const,
|
||||
color: 'white' as ButtonColor,
|
||||
class: 'rtl:[&_span:last-child]:rotate-180',
|
||||
icon: 'i-heroicons-chevron-right-20-solid'
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
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',
|
||||
@@ -17,13 +19,13 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '2xs' as const
|
||||
size: '2xs' as AvatarSize
|
||||
},
|
||||
badge: {
|
||||
base: 'flex-shrink-0 ms-auto relative rounded',
|
||||
color: 'gray' as const,
|
||||
variant: 'solid' as const,
|
||||
size: 'xs' as const
|
||||
color: 'gray' as BadgeColor,
|
||||
variant: 'solid' as BadgeVariant,
|
||||
size: 'xs' as BadgeSize
|
||||
},
|
||||
divider: {
|
||||
wrapper: {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { AvatarSize, ButtonColor, ButtonSize, ButtonVariant } from '../../types'
|
||||
|
||||
export default {
|
||||
wrapper: 'w-full pointer-events-auto',
|
||||
container: 'relative overflow-hidden',
|
||||
inner: 'w-0 flex-1',
|
||||
title: 'text-sm font-medium text-gray-900 dark:text-white',
|
||||
description: 'mt-1 text-sm leading-4 text-gray-500 dark:text-gray-400',
|
||||
descriptionOnly: 'mt-0 leading-5',
|
||||
actions: 'flex items-center gap-2 mt-3 flex-shrink-0',
|
||||
background: 'bg-white dark:bg-gray-900',
|
||||
shadow: 'shadow-lg',
|
||||
@@ -17,7 +20,7 @@ export default {
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0 self-center',
|
||||
size: 'md' as const
|
||||
size: 'md' as AvatarSize
|
||||
},
|
||||
progress: {
|
||||
base: 'absolute bottom-0 end-0 start-0 h-1',
|
||||
@@ -38,13 +41,13 @@ export default {
|
||||
timeout: 5000,
|
||||
closeButton: {
|
||||
icon: 'i-heroicons-x-mark-20-solid',
|
||||
color: 'gray' as const,
|
||||
variant: 'link' as const,
|
||||
color: 'gray' as ButtonColor,
|
||||
variant: 'link' as ButtonVariant,
|
||||
padded: false
|
||||
},
|
||||
actionButton: {
|
||||
size: 'xs' as const,
|
||||
color: 'white' as const
|
||||
size: 'xs' as ButtonSize,
|
||||
color: 'white' as ButtonColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { defu, createDefu } from 'defu'
|
||||
import { extendTailwindMerge } from 'tailwind-merge'
|
||||
import type { Strategy } from '../types/index'
|
||||
// @ts-ignore
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
const customTwMerge = extendTailwindMerge<string, string>({
|
||||
export const twMerge = extendTailwindMerge<string, string>(defu({
|
||||
extend: {
|
||||
classGroups: {
|
||||
icons: [(classPart: string) => classPart.startsWith('i-')]
|
||||
}
|
||||
}
|
||||
})
|
||||
}, appConfig.ui?.tailwindMerge))
|
||||
|
||||
const defuTwMerge = createDefu((obj, key, value, namespace) => {
|
||||
if (namespace === 'default' || namespace.startsWith('default.')) {
|
||||
@@ -28,7 +30,7 @@ const defuTwMerge = createDefu((obj, key, value, namespace) => {
|
||||
}
|
||||
if (typeof obj[key] === 'string' && typeof value === 'string' && obj[key] && value) {
|
||||
// @ts-ignore
|
||||
obj[key] = customTwMerge(obj[key], value)
|
||||
obj[key] = twMerge(obj[key], value)
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -30,21 +30,26 @@ export default function installTailwind(
|
||||
|
||||
// 2. add config template
|
||||
const configTemplate = addTemplate({
|
||||
filename: 'nuxtui-tailwind.config.cjs',
|
||||
filename: 'nuxtui-tailwind.config.mjs',
|
||||
write: true,
|
||||
getContents: ({ nuxt }) => `
|
||||
const { defaultExtractor: createDefaultExtractor } = require('tailwindcss/lib/lib/defaultExtractor.js')
|
||||
const { customSafelistExtractor, generateSafelist } = require(${JSON.stringify(resolve(runtimeDir, 'utils', 'colors'))})
|
||||
import { defaultExtractor as createDefaultExtractor } from "tailwindcss/lib/lib/defaultExtractor.js";
|
||||
import { customSafelistExtractor, generateSafelist } from ${JSON.stringify(resolve(runtimeDir, 'utils', 'colors'))};
|
||||
import formsPlugin from "@tailwindcss/forms";
|
||||
import aspectRatio from "@tailwindcss/aspect-ratio";
|
||||
import typography from "@tailwindcss/typography";
|
||||
import containerQueries from "@tailwindcss/container-queries";
|
||||
import headlessUi from "@headlessui/tailwindcss";
|
||||
|
||||
const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } })
|
||||
const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } });
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
plugins: [
|
||||
require('@tailwindcss/forms')({ strategy: 'class' }),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/container-queries'),
|
||||
require('@headlessui/tailwindcss')
|
||||
formsPlugin({ strategy: 'class' }),
|
||||
aspectRatio,
|
||||
typography,
|
||||
containerQueries,
|
||||
headlessUi
|
||||
],
|
||||
content: {
|
||||
files: [
|
||||
|
||||
Reference in New Issue
Block a user