mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-20 15:01:46 +01:00
Compare commits
248 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f022665351 | ||
|
|
f29c325dc7 | ||
|
|
876f9578c2 | ||
|
|
f69f584188 | ||
|
|
377b4189ca | ||
|
|
f76a9f0ab0 | ||
|
|
37b2271bf0 | ||
|
|
fa49d52f17 | ||
|
|
fd4b608150 | ||
|
|
fef93f3198 | ||
|
|
0826ef8d59 | ||
|
|
0e3066d865 | ||
|
|
fb9d6cb544 | ||
|
|
531a89cdb8 | ||
|
|
6970c2d665 | ||
|
|
9719ea3858 | ||
|
|
a4af6b3805 | ||
|
|
3493c138d9 | ||
|
|
d08e64d53f | ||
|
|
6aecb082d2 | ||
|
|
63e27f8b4b | ||
|
|
7970aefcb0 | ||
|
|
a893d7fa2e | ||
|
|
8ace629ff8 | ||
|
|
0d35b82ecb | ||
|
|
5f37077835 | ||
|
|
948f4b89b1 | ||
|
|
e6d0dd5898 | ||
|
|
4702a4f103 | ||
|
|
efa9674815 | ||
|
|
97d40395d3 | ||
|
|
a2fb22d835 | ||
|
|
d14a1a82c2 | ||
|
|
a566627a23 | ||
|
|
ee3352278c | ||
|
|
b1d9e01818 | ||
|
|
ca171f3095 | ||
|
|
c0e493d96a | ||
|
|
d0d3235860 | ||
|
|
18915975be | ||
|
|
f5d068be9d | ||
|
|
6018f009a8 | ||
|
|
2b78b5d7dc | ||
|
|
87f3f0b4c0 | ||
|
|
41bf56f2ae | ||
|
|
b7795f4ef6 | ||
|
|
57f8145a8d | ||
|
|
70fbcb6b24 | ||
|
|
bea47b5906 | ||
|
|
fc1b3b2f17 | ||
|
|
5bf5a314c4 | ||
|
|
3558eb1a4f | ||
|
|
1c4d46e056 | ||
|
|
1b0ed9e732 | ||
|
|
b72037a777 | ||
|
|
a7644860b8 | ||
|
|
c90cd9c4f3 | ||
|
|
7805168685 | ||
|
|
27717a55b3 | ||
|
|
d651a22dce | ||
|
|
c3ecbf4b20 | ||
|
|
d8b10f3eef | ||
|
|
1071b80b39 | ||
|
|
c5e9a1ef46 | ||
|
|
afe69a570d | ||
|
|
e6ed834cea | ||
|
|
30c5412a6b | ||
|
|
01f56d9553 | ||
|
|
91f273c117 | ||
|
|
cda8ce32a3 | ||
|
|
2bc0eb05d1 | ||
|
|
cfc4bdfbfe | ||
|
|
370d05921d | ||
|
|
b6455a151d | ||
|
|
8c0e0ec823 | ||
|
|
4f56921096 | ||
|
|
6a5ee32e05 | ||
|
|
4ea07e1077 | ||
|
|
5fd65d0917 | ||
|
|
2ec0cee1d9 | ||
|
|
758e6f1400 | ||
|
|
275fa1831d | ||
|
|
8b5e08f6f2 | ||
|
|
1635f57de6 | ||
|
|
b3e0122001 | ||
|
|
4f9d20e603 | ||
|
|
f7add47cf2 | ||
|
|
99c1c683eb | ||
|
|
ff9f6c251d | ||
|
|
e0c703ca6c | ||
|
|
2210faa160 | ||
|
|
19589b5e05 | ||
|
|
7051fa39a7 | ||
|
|
911278e95e | ||
|
|
c015148f29 | ||
|
|
cb1fd55801 | ||
|
|
16fd1c0ca3 | ||
|
|
84ac92ed7a | ||
|
|
0ade69de26 | ||
|
|
d9193abf55 | ||
|
|
44a78d7f67 | ||
|
|
3ce600f89a | ||
|
|
662dd9ee65 | ||
|
|
3be3751bfc | ||
|
|
5042b5806b | ||
|
|
2535301bcd | ||
|
|
629bb72424 | ||
|
|
543b4e025f | ||
|
|
7f18c6bdc7 | ||
|
|
0ea924ca0d | ||
|
|
22f3d42470 | ||
|
|
1ad96065fd | ||
|
|
952786ed79 | ||
|
|
9cf92e34ab | ||
|
|
6321d3ed8d | ||
|
|
bc0c168c0b | ||
|
|
9975305f2a | ||
|
|
edc1bd677b | ||
|
|
32d1f21299 | ||
|
|
f901e417e1 | ||
|
|
23921e1ae2 | ||
|
|
8a5f52dbc8 | ||
|
|
04d5ce1c51 | ||
|
|
3a300f72c1 | ||
|
|
927debfb6b | ||
|
|
44176ee897 | ||
|
|
7f9d69183c | ||
|
|
23e5f4c501 | ||
|
|
24998e3ac5 | ||
|
|
bebf18a89a | ||
|
|
1b56b50d4d | ||
|
|
3337559b89 | ||
|
|
d33a23ebc0 | ||
|
|
28586c35a5 | ||
|
|
e521e1ac24 | ||
|
|
8ea3223071 | ||
|
|
4cbe983f61 | ||
|
|
e42969f003 | ||
|
|
c93e37a0eb | ||
|
|
9e20f96b65 | ||
|
|
9f6f132a76 | ||
|
|
5517cc2846 | ||
|
|
8c24b5dc97 | ||
|
|
ec8bd5cdc4 | ||
|
|
6fab68baa8 | ||
|
|
fc951e2980 | ||
|
|
08ff6e6c09 | ||
|
|
9dcdaf474e | ||
|
|
e9f0224b91 | ||
|
|
929192fd46 | ||
|
|
d9ff11d1a2 | ||
|
|
80edb1bd4b | ||
|
|
fc658842bb | ||
|
|
32922def7d | ||
|
|
a4bea1c508 | ||
|
|
f87252f05d | ||
|
|
ec9f67094a | ||
|
|
72dc0d0d0c | ||
|
|
02b0a29d86 | ||
|
|
cd2f1122be | ||
|
|
e3943123ce | ||
|
|
d57647a77a | ||
|
|
aa97f26180 | ||
|
|
0b17fb02e4 | ||
|
|
5039265097 | ||
|
|
c89c65bd44 | ||
|
|
4515a1239b | ||
|
|
de9596bfc4 | ||
|
|
7da11ccec0 | ||
|
|
d29377f614 | ||
|
|
f0e482cf01 | ||
|
|
056ab30474 | ||
|
|
95c14a4360 | ||
|
|
b25bb75783 | ||
|
|
98ae6c14aa | ||
|
|
8ee2a3cf5b | ||
|
|
91f93c450e | ||
|
|
237a673af3 | ||
|
|
591e60dfc7 | ||
|
|
b9f0b3cb10 | ||
|
|
3ed64250cd | ||
|
|
f1b59fc59e | ||
|
|
005c18e4c0 | ||
|
|
44b7199c4f | ||
|
|
4774adc0ae | ||
|
|
03b0e07a53 | ||
|
|
2560088b2b | ||
|
|
4ce991652b | ||
|
|
d96b4ea119 | ||
|
|
b20c8c82ab | ||
|
|
e5bf052c1f | ||
|
|
cf1b2cdd13 | ||
|
|
8776929f63 | ||
|
|
62361bfa8f | ||
|
|
bf25ad15af | ||
|
|
03b18b1ff8 | ||
|
|
fdf149f0d0 | ||
|
|
e4c3a74e0e | ||
|
|
38f248c24d | ||
|
|
74de5b106b | ||
|
|
70f8cd4889 | ||
|
|
6a2159da94 | ||
|
|
ae6b3c0ba6 | ||
|
|
70495e0a22 | ||
|
|
be99df3b30 | ||
|
|
6870eff661 | ||
|
|
062e468518 | ||
|
|
3fc2a043fa | ||
|
|
e99ec9b82b | ||
|
|
4556f24eb1 | ||
|
|
f8fb66f177 | ||
|
|
5d760d9d8d | ||
|
|
cb5c9b3923 | ||
|
|
8e9418f94f | ||
|
|
4712726fbd | ||
|
|
0cb38e2214 | ||
|
|
3e9c502fbe | ||
|
|
541ed304a0 | ||
|
|
72919425b6 | ||
|
|
8a66f5e9bf | ||
|
|
a0d56d0f66 | ||
|
|
c6706c76b2 | ||
|
|
1f7f28835d | ||
|
|
593573a286 | ||
|
|
3a6ecd6988 | ||
|
|
f9dbe28938 | ||
|
|
2ae17ed919 | ||
|
|
abb93b5fd3 | ||
|
|
f75fc4f864 | ||
|
|
5722a3ae62 | ||
|
|
aa242aa87d | ||
|
|
945fec62c2 | ||
|
|
9302b8d635 | ||
|
|
b43394ddc3 | ||
|
|
be94fea84a | ||
|
|
c08501db3f | ||
|
|
db2b73e741 | ||
|
|
6b813bd3b3 | ||
|
|
7b3263a621 | ||
|
|
002a8f6803 | ||
|
|
5a596eb2b0 | ||
|
|
2f87cf86a1 | ||
|
|
e4f148efa9 | ||
|
|
024a5914b9 | ||
|
|
ce28b04187 | ||
|
|
1ff9fd4f69 | ||
|
|
487f253e14 | ||
|
|
f54d4d788d |
3
.github/workflows/ci-dev.yml
vendored
3
.github/workflows/ci-dev.yml
vendored
@@ -41,6 +41,9 @@ jobs:
|
|||||||
- name: Lint
|
- name: Lint
|
||||||
run: yarn lint
|
run: yarn lint
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: yarn typecheck
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -41,6 +41,9 @@ jobs:
|
|||||||
- name: Lint
|
- name: Lint
|
||||||
run: yarn lint
|
run: yarn lint
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: yarn typecheck
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
||||||
|
|||||||
262
CHANGELOG.md
262
CHANGELOG.md
@@ -2,6 +2,268 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [1.2.2](https://github.com/nuxtlabs/ui/compare/v1.2.1...v1.2.2) (2023-03-20)
|
||||||
|
|
||||||
|
### [1.2.1](https://github.com/nuxtlabs/ui/compare/v1.2.0...v1.2.1) (2023-03-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **defineShortcuts:** shift + alphabetic character handling ([#140](https://github.com/nuxtlabs/ui/issues/140)) ([377b418](https://github.com/nuxtlabs/ui/commit/377b4189ca85603db0b7f040949260ba7494c46f))
|
||||||
|
|
||||||
|
## [1.2.0](https://github.com/nuxtlabs/ui/compare/v1.1.4...v1.2.0) (2023-03-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **defineShortcuts:** add missing import ([37b2271](https://github.com/nuxtlabs/ui/commit/37b2271bf04adfe6bee4d984fa12452b2168318c))
|
||||||
|
* **Tooltip:** `shortcutsClass` prop type ([fa49d52](https://github.com/nuxtlabs/ui/commit/fa49d52f17752eaa06f997a9b6e8df8adcab983f))
|
||||||
|
|
||||||
|
### [1.1.4](https://github.com/nuxtlabs/ui/compare/v1.1.3...v1.1.4) (2023-03-02)
|
||||||
|
|
||||||
|
### [1.1.3](https://github.com/nuxtlabs/ui/compare/v1.1.2...v1.1.3) (2023-03-02)
|
||||||
|
|
||||||
|
### [1.1.2](https://github.com/nuxtlabs/ui/compare/v1.1.1...v1.1.2) (2023-02-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Tooltip:** truncate ([d08e64d](https://github.com/nuxtlabs/ui/commit/d08e64d53fa439f34d51909bcb6812f1bcd95d83))
|
||||||
|
* **VerticalNavigation:** links `to` type ([7970aef](https://github.com/nuxtlabs/ui/commit/7970aefcb032ce01fcb11e9285fa61ce87f59519))
|
||||||
|
|
||||||
|
### [1.1.1](https://github.com/nuxtlabs/ui/compare/v1.1.0...v1.1.1) (2023-02-20)
|
||||||
|
|
||||||
|
## [1.1.0](https://github.com/nuxtlabs/ui/compare/v1.0.0...v1.1.0) (2023-02-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **CommandPalette:** handle async search for specific groups ([efa9674](https://github.com/nuxtlabs/ui/commit/efa9674815ab4de756079690da0a381c3703d564))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CommandPalette:** types ([4702a4f](https://github.com/nuxtlabs/ui/commit/4702a4f10379201c167cc52099519778756a5780))
|
||||||
|
|
||||||
|
## [1.0.0](https://github.com/nuxtlabs/ui/compare/v0.2.1...v1.0.0) (2023-02-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* migrate to `@egoist/tailwindcss-icons` ([ee33522](https://github.com/nuxtlabs/ui/commit/ee3352278cf03fdd12f2a4663b403052de3f089a))
|
||||||
|
|
||||||
|
### [0.2.1](https://github.com/nuxtlabs/ui/compare/v0.2.0...v0.2.1) (2023-02-16)
|
||||||
|
|
||||||
|
## [0.2.0](https://github.com/nuxtlabs/ui/compare/v0.1.39...v0.2.0) (2023-02-16)
|
||||||
|
|
||||||
|
### [0.1.39](https://github.com/nuxtlabs/ui/compare/v0.1.38...v0.1.39) (2023-02-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* use `nuxt-icon` ([f5d068b](https://github.com/nuxtlabs/ui/commit/f5d068be9d5778b3d4fcdc11d06d9d765e62075d))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **SelectCustom:** handle search on string arrays ([6018f00](https://github.com/nuxtlabs/ui/commit/6018f009a86cca196d15e4e72dd5eb41aaeb4bad))
|
||||||
|
|
||||||
|
### [0.1.38](https://github.com/nuxtlabs/ui/compare/v0.1.37...v0.1.38) (2023-02-03)
|
||||||
|
|
||||||
|
### [0.1.37](https://github.com/nuxtlabs/ui/compare/v0.1.36...v0.1.37) (2023-02-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CommandPalette:** improve accessibility ([#129](https://github.com/nuxtlabs/ui/issues/129)) ([bea47b5](https://github.com/nuxtlabs/ui/commit/bea47b5906d1bc665717830d6dc2f3ff2a0374f3))
|
||||||
|
|
||||||
|
### [0.1.36](https://github.com/nuxtlabs/ui/compare/v0.1.35...v0.1.36) (2023-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CommandPalette:** put back cursor on top only when query changes ([5bf5a31](https://github.com/nuxtlabs/ui/commit/5bf5a314c414b96c656190719bd56acca10676f5))
|
||||||
|
|
||||||
|
### [0.1.35](https://github.com/nuxtlabs/ui/compare/v0.1.34...v0.1.35) (2023-02-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **AvatarGroup:** preset size prop ([c90cd9c](https://github.com/nuxtlabs/ui/commit/c90cd9c4f37bc3ce5f6e13f3279dc2c574c76524))
|
||||||
|
* **Dropdown:** lint ([1c4d46e](https://github.com/nuxtlabs/ui/commit/1c4d46e056adf84d69462a12af8ac29f93cbf87a))
|
||||||
|
* **Dropdown:** prevent panel display when no items ([a764486](https://github.com/nuxtlabs/ui/commit/a7644860b8c22a0163e01ca2c0eab2c48b09745a))
|
||||||
|
|
||||||
|
### [0.1.34](https://github.com/nuxtlabs/ui/compare/v0.1.33...v0.1.34) (2023-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CommandPalette:** typecheck ([27717a5](https://github.com/nuxtlabs/ui/commit/27717a55b3e5120f32fba2bcea30f5a91262f1c5))
|
||||||
|
|
||||||
|
### [0.1.33](https://github.com/nuxtlabs/ui/compare/v0.1.32...v0.1.33) (2023-01-27)
|
||||||
|
|
||||||
|
### [0.1.32](https://github.com/nuxtlabs/ui/compare/v0.1.31...v0.1.32) (2023-01-23)
|
||||||
|
|
||||||
|
### [0.1.31](https://github.com/nuxtlabs/ui/compare/v0.1.30...v0.1.31) (2023-01-17)
|
||||||
|
|
||||||
|
### [0.1.30](https://github.com/nuxtlabs/ui/compare/v0.1.29...v0.1.30) (2023-01-17)
|
||||||
|
|
||||||
|
### [0.1.29](https://github.com/nuxtlabs/ui/compare/v0.1.28...v0.1.29) (2023-01-17)
|
||||||
|
|
||||||
|
### [0.1.28](https://github.com/nuxtlabs/ui/compare/v0.1.27...v0.1.28) (2023-01-13)
|
||||||
|
|
||||||
|
### [0.1.27](https://github.com/nuxtlabs/ui/compare/v0.1.26...v0.1.27) (2023-01-12)
|
||||||
|
|
||||||
|
### [0.1.26](https://github.com/nuxtlabs/ui/compare/v0.1.25...v0.1.26) (2023-01-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CommandPalette:** select first item on search changes ([#126](https://github.com/nuxtlabs/ui/issues/126)) ([4f56921](https://github.com/nuxtlabs/ui/commit/4f56921096f5885cdab8b7cb5c5aa01304188e11))
|
||||||
|
|
||||||
|
### [0.1.25](https://github.com/nuxtlabs/ui/compare/v0.1.24...v0.1.25) (2023-01-09)
|
||||||
|
|
||||||
|
### [0.1.24](https://github.com/nuxtlabs/ui/compare/v0.1.23...v0.1.24) (2023-01-04)
|
||||||
|
|
||||||
|
### [0.1.23](https://github.com/nuxtlabs/ui/compare/v0.1.22...v0.1.23) (2022-12-20)
|
||||||
|
|
||||||
|
### [0.1.22](https://github.com/nuxtlabs/ui/compare/v0.1.21...v0.1.22) (2022-12-19)
|
||||||
|
|
||||||
|
### [0.1.21](https://github.com/nuxtlabs/ui/compare/v0.1.20...v0.1.21) (2022-12-19)
|
||||||
|
|
||||||
|
### [0.1.20](https://github.com/nuxtlabs/ui/compare/v0.1.19...v0.1.20) (2022-12-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* avoid referring to complex types in props ([#123](https://github.com/nuxtlabs/ui/issues/123)) ([ff9f6c2](https://github.com/nuxtlabs/ui/commit/ff9f6c251df59641862d82587e5d963c8e6ea298))
|
||||||
|
|
||||||
|
### [0.1.19](https://github.com/nuxtlabs/ui/compare/v0.1.18...v0.1.19) (2022-12-16)
|
||||||
|
|
||||||
|
### [0.1.18](https://github.com/nuxtlabs/ui/compare/v0.1.17...v0.1.18) (2022-12-15)
|
||||||
|
|
||||||
|
### [0.1.17](https://github.com/nuxtlabs/ui/compare/v0.1.16...v0.1.17) (2022-12-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* remove stop propagation on mode hover ([16fd1c0](https://github.com/nuxtlabs/ui/commit/16fd1c0ca38f1438e791c0d44399f590d9f20d02))
|
||||||
|
|
||||||
|
### [0.1.16](https://github.com/nuxtlabs/ui/compare/v0.1.15...v0.1.16) (2022-12-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Popover:** preset from tooltip ([0ade69d](https://github.com/nuxtlabs/ui/commit/0ade69de2689b094b11a2dead8f71e3d2dccd552))
|
||||||
|
|
||||||
|
### [0.1.15](https://github.com/nuxtlabs/ui/compare/v0.1.14...v0.1.15) (2022-12-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Dropdown:** better handle item click to preventDefault ([#119](https://github.com/nuxtlabs/ui/issues/119)) ([44a78d7](https://github.com/nuxtlabs/ui/commit/44a78d7f679812c59d4410d0bbc01f09abaa78dd))
|
||||||
|
|
||||||
|
### [0.1.14](https://github.com/nuxtlabs/ui/compare/v0.1.13...v0.1.14) (2022-12-02)
|
||||||
|
|
||||||
|
### [0.1.13](https://github.com/nuxtlabs/ui/compare/v0.1.12...v0.1.13) (2022-12-01)
|
||||||
|
|
||||||
|
### [0.1.12](https://github.com/nuxtlabs/ui/compare/v0.1.11...v0.1.12) (2022-11-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Checkbox:** types ([629bb72](https://github.com/nuxtlabs/ui/commit/629bb724249cfe1fdd999cf52f8e72ca444bd94d))
|
||||||
|
|
||||||
|
### [0.1.11](https://github.com/nuxtlabs/ui/compare/v0.1.10...v0.1.11) (2022-11-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Checkbox:** revert type fix as it breaks checkboxes ([7f18c6b](https://github.com/nuxtlabs/ui/commit/7f18c6bdc7c0054b2e5d4f9bf4e362847a3ba3a3))
|
||||||
|
|
||||||
|
### [0.1.10](https://github.com/nuxtlabs/ui/compare/v0.1.9...v0.1.10) (2022-11-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* default popper options ([1ad9606](https://github.com/nuxtlabs/ui/commit/1ad96065fd706d828b906db3a5d578226ff08c36))
|
||||||
|
* default props for command palette ([#116](https://github.com/nuxtlabs/ui/issues/116)) ([952786e](https://github.com/nuxtlabs/ui/commit/952786ed79cd9cdf523f6eac5958f68790bacbea))
|
||||||
|
|
||||||
|
### [0.1.9](https://github.com/nuxtlabs/ui/compare/v0.1.8...v0.1.9) (2022-11-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Icon:** couldn't load anymore ([6321d3e](https://github.com/nuxtlabs/ui/commit/6321d3ed8d5c9478cb1dafc1da94b21d0c7edb88))
|
||||||
|
* **Icon:** eslint ignore ([bc0c168](https://github.com/nuxtlabs/ui/commit/bc0c168c0b0feae96d6a1848c3a356d846e2cbb5))
|
||||||
|
|
||||||
|
### [0.1.8](https://github.com/nuxtlabs/ui/compare/v0.1.7...v0.1.8) (2022-11-16)
|
||||||
|
|
||||||
|
### [0.1.7](https://github.com/nuxtlabs/ui/compare/v0.1.6...v0.1.7) (2022-11-16)
|
||||||
|
|
||||||
|
### [0.1.6](https://github.com/nuxtlabs/ui/compare/v0.1.5...v0.1.6) (2022-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **SelectCustom:** add `w-full` on `ComboboxButton` ([3a300f7](https://github.com/nuxtlabs/ui/commit/3a300f72c1eca756ffd8f07ab871bf9c7bd7868d))
|
||||||
|
|
||||||
|
### [0.1.5](https://github.com/nuxtlabs/ui/compare/v0.1.4...v0.1.5) (2022-11-08)
|
||||||
|
|
||||||
|
### [0.1.4](https://github.com/nuxtlabs/ui/compare/v0.1.3...v0.1.4) (2022-11-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **button:** support `RouteLocationRaw` type for `to` ([#112](https://github.com/nuxtlabs/ui/issues/112)) ([1b56b50](https://github.com/nuxtlabs/ui/commit/1b56b50d4d54a5cb9e5febacdf42867988ae6c5d))
|
||||||
|
|
||||||
|
### [0.1.3](https://github.com/nuxtlabs/ui/compare/v0.1.2...v0.1.3) (2022-11-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **docs:** component input string field ([e521e1a](https://github.com/nuxtlabs/ui/commit/e521e1ac2421dc331652f1ea4ac2b0b2959dc069))
|
||||||
|
* **types:** add missing field in command palette type ([#111](https://github.com/nuxtlabs/ui/issues/111)) ([28586c3](https://github.com/nuxtlabs/ui/commit/28586c35a558d9e925094f86e07acdb928d54ad7))
|
||||||
|
|
||||||
|
### [0.1.2](https://github.com/nuxtlabs/ui/compare/v0.1.1...v0.1.2) (2022-10-27)
|
||||||
|
|
||||||
|
### [0.1.1](https://github.com/nuxtlabs/ui/compare/v0.1.0...v0.1.1) (2022-10-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **CommandPalette:** lint ([6fab68b](https://github.com/nuxtlabs/ui/commit/6fab68baa836c97680446e8cfdee7c5a64008539))
|
||||||
|
* **Dropdown:** close on click item with `to` ([#103](https://github.com/nuxtlabs/ui/issues/103)) ([5517cc2](https://github.com/nuxtlabs/ui/commit/5517cc28467f957956a20126c1ce48e64677cb82))
|
||||||
|
* **Popover:** avoid crash on mount if ref not loaded ([#105](https://github.com/nuxtlabs/ui/issues/105)) ([e9f0224](https://github.com/nuxtlabs/ui/commit/e9f0224b919445260d3b19511420a3fd448e4ea7))
|
||||||
|
* **SelectCustom:** types and lint ([ec8bd5c](https://github.com/nuxtlabs/ui/commit/ec8bd5cdc49feb924dfdff352d9f3d788653c583))
|
||||||
|
|
||||||
|
## [0.1.0](https://github.com/nuxtlabs/ui/compare/v0.0.3...v0.1.0) (2022-10-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* `to` prop type ([be94fea](https://github.com/nuxtlabs/ui/commit/be94fea84acc69c0114099b5251ff34e3a239726))
|
||||||
|
* **CommandPalette:** command icons opacity in dark mode ([abb93b5](https://github.com/nuxtlabs/ui/commit/abb93b5fd3ddda8c91db3370c8e3109cff4a091d))
|
||||||
|
* **CommandPalette:** fix groups computed ([9302b8d](https://github.com/nuxtlabs/ui/commit/9302b8d635c3ffb489142601a17a9878072c89fe))
|
||||||
|
* **CommandPalette:** group items spacing ([32922de](https://github.com/nuxtlabs/ui/commit/32922def7deec5bee920a1fb1400449461315d0d))
|
||||||
|
* **CommandPalette:** hack for reactivity ([b43394d](https://github.com/nuxtlabs/ui/commit/b43394ddc3ee795b56679f7076e0c80a1c496b2e))
|
||||||
|
* **CommandPalette:** icon color on hover ([e4f148e](https://github.com/nuxtlabs/ui/commit/e4f148efa97adf52b1b5544ff6c349a4ac82a956))
|
||||||
|
* **CommandPalette:** icon inactive opacity on dark mode ([5722a3a](https://github.com/nuxtlabs/ui/commit/5722a3ae62706229179b75d9291babd1c2039244))
|
||||||
|
* **CommandPalette:** prevent empty active slot ([056ab30](https://github.com/nuxtlabs/ui/commit/056ab304745c3ba8dedbf9d6491839b9d620df88))
|
||||||
|
* **CommandPalette:** prevent shortcuts to disappear on hover ([f87252f](https://github.com/nuxtlabs/ui/commit/f87252f05debda7c98f5ab8a9453e57efafaad0f))
|
||||||
|
* **CommandPalette:** reactivity issue + improve results ([ec9f670](https://github.com/nuxtlabs/ui/commit/ec9f67094a51e3afde92f7924b8ee5d4e9053158)), closes [#95](https://github.com/nuxtlabs/ui/issues/95) [#96](https://github.com/nuxtlabs/ui/issues/96)
|
||||||
|
* **CommandPalette:** truncate suffix ([aa242aa](https://github.com/nuxtlabs/ui/commit/aa242aa87d5ae834d838518efd530003fdde5e24))
|
||||||
|
* default object options ([95c14a4](https://github.com/nuxtlabs/ui/commit/95c14a43600016bf405b557752fad289fb31154a))
|
||||||
|
* **Dropdown:** increase timeout for hover mode ([7291942](https://github.com/nuxtlabs/ui/commit/72919425b6e84581ba3b854aec3348977b335a3f))
|
||||||
|
* error in Popover and Dropdown ([541ed30](https://github.com/nuxtlabs/ui/commit/541ed304a0a4fa2646115547e03e44cf9e17c65e))
|
||||||
|
* **icon:** hydratation warning when loading same icon twice ([#99](https://github.com/nuxtlabs/ui/issues/99)) ([d57647a](https://github.com/nuxtlabs/ui/commit/d57647a77a145ff6e81d3a71550e98e3eaf3a842))
|
||||||
|
* load icons on mount rather than within setup ([#82](https://github.com/nuxtlabs/ui/issues/82)) ([62361bf](https://github.com/nuxtlabs/ui/commit/62361bfa8f77c2f3452af108f08434ba4c6ec4c5))
|
||||||
|
* **Modal:** use object for `innerStyle` ([72dc0d0](https://github.com/nuxtlabs/ui/commit/72dc0d0d0c270b2dfbf2ba8a8eb03a04eb5dea9a))
|
||||||
|
* **Notification:** improve placement with description ([945fec6](https://github.com/nuxtlabs/ui/commit/945fec62c2efa6baf7b32c8a85ba658dfd9311c9))
|
||||||
|
* **Notification:** prevent error without timeout ([8a66f5e](https://github.com/nuxtlabs/ui/commit/8a66f5e9bf65ab04b8878f0d597e439b45b46bb3))
|
||||||
|
* **Popover:** `inline-flex` on trigger button ([593573a](https://github.com/nuxtlabs/ui/commit/593573a286459b48fde8f49df2c2f1fc1dc98da6))
|
||||||
|
* **SelectCustom:** avoid submitting to form when closing ([#83](https://github.com/nuxtlabs/ui/issues/83)) ([cf1b2cd](https://github.com/nuxtlabs/ui/commit/cf1b2cdd133233481da6e1ec47b49b7f012aa204))
|
||||||
|
|
||||||
### [0.0.3](https://github.com/nuxtlabs/ui/compare/v0.0.2...v0.0.3) (2022-07-18)
|
### [0.0.3](https://github.com/nuxtlabs/ui/compare/v0.0.2...v0.0.3) (2022-07-18)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
12
build.config.ts
Normal file
12
build.config.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import { join, resolve } from 'node:path'
|
||||||
|
import { defineBuildConfig } from 'unbuild'
|
||||||
|
|
||||||
|
export default defineBuildConfig({
|
||||||
|
hooks: {
|
||||||
|
'rollup:done': async (ctx) => {
|
||||||
|
// copy env.d.ts to dist
|
||||||
|
await fs.copyFile(resolve('src/env.d.ts'), join(ctx.options.outDir, 'env.d.ts'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
25
docs/app.vue
25
docs/app.vue
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<nav class="u-bg-white border-b u-border-gray-200 fixed top-0 inset-x-0 z-20">
|
<nav class="u-bg-white border-b u-border-gray-200 fixed top-0 inset-x-0 z-10">
|
||||||
<UContainer padded>
|
<UContainer padded>
|
||||||
<div class="flex items-center justify-between h-16">
|
<div class="flex items-center justify-between h-16">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -9,9 +9,11 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center">
|
||||||
<UButton variant="transparent" :icon="colorMode.value === 'dark' ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark" />
|
<ColorScheme placeholder="" tag="span">
|
||||||
<UButton to="https://github.com/nuxtlabs/ui" target="_blank" variant="transparent" icon="fa-brands:github" />
|
<UButton variant="transparent" :icon="colorMode.value === 'dark' ? 'i-heroicons-moon' : 'i-heroicons-sun'" @click="toggleDark" />
|
||||||
|
</ColorScheme>
|
||||||
|
<UButton to="https://github.com/nuxtlabs/ui" target="_blank" variant="transparent" icon="i-mdi-github" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UContainer>
|
</UContainer>
|
||||||
@@ -19,7 +21,7 @@
|
|||||||
|
|
||||||
<UContainer class="mt-16">
|
<UContainer class="mt-16">
|
||||||
<div class="lg:grid lg:grid-cols-10 lg:gap-10 lg:relative">
|
<div class="lg:grid lg:grid-cols-10 lg:gap-10 lg:relative">
|
||||||
<aside class="lg:flex lg:flex-col lg:relative pb-8 lg:pb-0 lg:sticky lg:top-0 px-4 sm:px-6 lg:px-0 lg:pt-16 lg:-mt-16 lg:self-start lg:col-span-2 lg:overflow-hidden lg:sticky lg:h-screen">
|
<aside class="lg:flex lg:flex-col pb-8 lg:pb-0 lg:sticky lg:top-0 px-4 sm:px-6 lg:px-0 lg:pt-16 lg:-mt-16 lg:self-start lg:col-span-2 lg:overflow-hidden lg:h-screen">
|
||||||
<nav class="overflow-y-auto h-auto pt-8 lg:py-12">
|
<nav class="overflow-y-auto h-auto pt-8 lg:py-12">
|
||||||
<ul class="space-y-6">
|
<ul class="space-y-6">
|
||||||
<li v-for="section of sections" :key="section">
|
<li v-for="section of sections" :key="section">
|
||||||
@@ -57,7 +59,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
useMeta({
|
useHead({
|
||||||
|
title: '@nuxthq/ui',
|
||||||
|
meta: [
|
||||||
|
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
|
||||||
|
],
|
||||||
|
link: [
|
||||||
|
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }
|
||||||
|
],
|
||||||
bodyAttrs: {
|
bodyAttrs: {
|
||||||
class: 'antialiased font-sans text-gray-700 bg-gray-50 dark:bg-gray-900 dark:text-gray-200 bg-white dark:bg-black'
|
class: 'antialiased font-sans text-gray-700 bg-gray-50 dark:bg-gray-900 dark:text-gray-200 bg-white dark:bg-black'
|
||||||
}
|
}
|
||||||
@@ -70,12 +79,12 @@ const toggleDark = () => {
|
|||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
{ label: 'Getting Started', links: [{ label: 'Usage', to: '/' }, { label: 'Examples', to: '/examples' }, { label: 'Migration', to: '/migration' }, { label: 'Dark mode', to: '/dark' }] },
|
{ label: 'Getting Started', links: [{ label: 'Usage', to: '/' }, { label: 'Examples', to: '/examples' }, { label: 'Migration', to: '/migration' }, { label: 'Dark mode', to: '/dark' }] },
|
||||||
{ label: 'Elements', links: [{ label: 'Avatar', to: '/components/Avatar' }, { label: 'AvatarGroup', to: '/components/AvatarGroup' }, { label: 'Badge', to: '/components/Badge' }, { label: 'Button', to: '/components/Button' }, { label: 'Dropdown', to: '/components/Dropdown' }, { label: 'Icon', to: '/components/Icon' }] },
|
{ label: 'Elements', links: [{ label: 'Avatar', to: '/components/Avatar' }, { label: 'AvatarGroup', to: '/components/AvatarGroup' }, { label: 'Badge', to: '/components/Badge' }, { label: 'Button', to: '/components/Button' }, { label: 'Dropdown', to: '/components/Dropdown' }] },
|
||||||
{ label: 'Feedback', links: [{ label: 'Alert', to: '/components/Alert' }, { label: 'AlertDialog', to: '/components/AlertDialog' }] },
|
{ label: 'Feedback', links: [{ label: 'Alert', to: '/components/Alert' }, { label: 'AlertDialog', to: '/components/AlertDialog' }] },
|
||||||
{ label: 'Forms', links: [{ label: 'Checkbox', to: '/components/Checkbox' }, { label: 'Input', to: '/components/Input' }, { label: 'FormGroup', to: '/components/FormGroup' }, { label: 'Radio', to: '/components/Radio' }, { label: 'Select', to: '/components/Select' }, { label: 'SelectCustom', to: '/components/SelectCustom' }, { label: 'Textarea', to: '/components/Textarea' }, { label: 'Toggle', to: '/components/Toggle' }] },
|
{ label: 'Forms', links: [{ label: 'Checkbox', to: '/components/Checkbox' }, { label: 'Input', to: '/components/Input' }, { label: 'FormGroup', to: '/components/FormGroup' }, { label: 'Radio', to: '/components/Radio' }, { label: 'Select', to: '/components/Select' }, { label: 'SelectCustom', to: '/components/SelectCustom' }, { label: 'Textarea', to: '/components/Textarea' }, { label: 'Toggle', to: '/components/Toggle' }] },
|
||||||
{ label: 'Layout', links: [{ label: 'Card', to: '/components/Card' }, { label: 'Container', to: '/components/Container' }] },
|
{ label: 'Layout', links: [{ label: 'Card', to: '/components/Card' }, { label: 'Container', to: '/components/Container' }] },
|
||||||
{ label: 'Navigation', links: [{ label: 'Pills', to: '/components/Pills' }, { label: 'Tabs', to: '/components/Tabs' }, { label: 'VerticalNavigation', to: '/components/VerticalNavigation' }, { label: 'CommandPalette', to: '/components/CommandPalette' }] },
|
{ label: 'Navigation', links: [{ label: 'Pills', to: '/components/Pills' }, { label: 'Tabs', to: '/components/Tabs' }, { label: 'VerticalNavigation', to: '/components/VerticalNavigation' }, { label: 'CommandPalette', to: '/components/CommandPalette' }] },
|
||||||
{ label: 'Overlays', links: [{ label: 'Modal', to: '/components/Modal' }, { label: 'Notification', to: '/components/Notification' }, { label: 'Popover', to: '/components/Popover' }, { label: 'Slideover', to: '/components/Slideover' }, { label: 'Tooltip', to: '/components/Tooltip' }] }
|
{ label: 'Overlays', links: [{ label: 'ContextMenu', to: '/components/ContextMenu' }, { label: 'Modal', to: '/components/Modal' }, { label: 'Notification', to: '/components/Notification' }, { label: 'Popover', to: '/components/Popover' }, { label: 'Slideover', to: '/components/Slideover' }, { label: 'Tooltip', to: '/components/Tooltip' }] }
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { defineNuxtConfig } from 'nuxt'
|
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||||
import module from '../src/module'
|
import nuxtUI from '../src/module'
|
||||||
|
|
||||||
// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
|
// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
meta: {
|
app: {
|
||||||
title: '@nuxthq/ui',
|
head: {
|
||||||
meta: [
|
htmlAttrs: {
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
|
lang: 'en'
|
||||||
],
|
}
|
||||||
link: [
|
}
|
||||||
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
buildModules: [
|
modules: [
|
||||||
module
|
// @ts-ignore
|
||||||
|
nuxtUI
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
global: true
|
global: true
|
||||||
@@ -25,11 +24,12 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
preset: {
|
preset: {
|
||||||
},
|
},
|
||||||
|
icons: ['heroicons', 'mdi'],
|
||||||
tailwindcss: {
|
tailwindcss: {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['"Inter var", sans-serif']
|
sans: ['Inter var', ...defaultTheme.fontFamily.sans]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
<!-- eslint-disable vue/no-template-shadow -->
|
||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||||
<template>
|
<template>
|
||||||
<UCard v-if="component" class="relative flex flex-col lg:h-[calc(100vh-10rem)]" body-class="px-4 py-5 sm:p-6 relative" footer-class="flex flex-col flex-1 overflow-hidden">
|
<UCard v-if="component" class="relative flex flex-col" body-class="px-4 py-5 sm:p-6 relative" footer-class="flex flex-col flex-1 overflow-hidden">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<component :is="`U${defaultProps[params.component].component.name}`" v-if="defaultProps[params.component] && defaultProps[params.component].component" v-bind="defaultProps[params.component].component.props" />
|
<component :is="`U${defaultProps[params.component].component.name}`" v-if="defaultProps[params.component] && defaultProps[params.component].component" v-bind="defaultProps[params.component].component.props" />
|
||||||
|
|
||||||
@@ -26,7 +29,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="props.length" #footer>
|
<template v-if="props.length" #footer>
|
||||||
<div class="flex-1 px-4 py-5 sm:p-6 space-y-3 lg:overflow-y-auto">
|
<div class="border-b u-border-gray-200">
|
||||||
|
<pre class="text-sm leading-6 u-text-gray-900 flex-1 relative flex ligatures-none overflow-x-hidden px-4 sm:px-6 py-5 sm:py-6">
|
||||||
|
<code class="flex-none min-w-full whitespace-pre-wrap break-all">{{ code }}</code>
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
class="absolute top-0 right-0"
|
||||||
|
:icon="copied ? 'i-heroicons-clipboard-document-check' : 'i-heroicons-clipboard-document'"
|
||||||
|
variant="transparent"
|
||||||
|
size="sm"
|
||||||
|
:custom-class="copied ? '!text-green-500' : ''"
|
||||||
|
@click="onCopy"
|
||||||
|
/>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 px-4 py-5 sm:p-6 space-y-3">
|
||||||
<UFormGroup
|
<UFormGroup
|
||||||
v-for="prop of props"
|
v-for="prop of props"
|
||||||
:key="prop.key"
|
:key="prop.key"
|
||||||
@@ -38,7 +56,6 @@
|
|||||||
v-if="prop.type === 'Boolean'"
|
v-if="prop.type === 'Boolean'"
|
||||||
v-model="prop.value"
|
v-model="prop.value"
|
||||||
:name="prop.key"
|
:name="prop.key"
|
||||||
:label="prop.key"
|
|
||||||
/>
|
/>
|
||||||
<USelect
|
<USelect
|
||||||
v-else-if="prop.values"
|
v-else-if="prop.values"
|
||||||
@@ -49,7 +66,7 @@
|
|||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
<UInput
|
<UInput
|
||||||
v-else-if="prop.type === 'String' && typeof prop.value === 'string'"
|
v-else-if="prop.type === 'String'"
|
||||||
v-model="prop.value"
|
v-model="prop.value"
|
||||||
:name="prop.key"
|
:name="prop.key"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -73,21 +90,6 @@
|
|||||||
/>
|
/>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t u-border-gray-200">
|
|
||||||
<pre class="text-sm leading-6 u-text-gray-900 flex-1 relative flex ligatures-none lg:overflow-y-auto overflow-x-hidden px-4 sm:px-6 py-5 sm:py-6">
|
|
||||||
<code class="flex-none min-w-full whitespace-pre-wrap break-all">{{ code }}</code>
|
|
||||||
|
|
||||||
<UButton
|
|
||||||
class="absolute top-0 right-0"
|
|
||||||
:icon="copied ? 'heroicons-outline:clipboard-check' : 'heroicons-outline:clipboard-copy'"
|
|
||||||
variant="transparent"
|
|
||||||
size="sm"
|
|
||||||
:custom-class="copied ? '!text-green-500' : ''"
|
|
||||||
@click="onCopy"
|
|
||||||
/>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</UCard>
|
</UCard>
|
||||||
</template>
|
</template>
|
||||||
@@ -118,6 +120,31 @@ const toggle = ref(false)
|
|||||||
const modal = ref(false)
|
const modal = ref(false)
|
||||||
const slideover = ref(false)
|
const slideover = ref(false)
|
||||||
|
|
||||||
|
const x = ref(0)
|
||||||
|
const y = ref(0)
|
||||||
|
const isContextMenuOpen = ref(false)
|
||||||
|
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('mousemove', ({ clientX, clientY }) => {
|
||||||
|
x.value = clientX
|
||||||
|
y.value = clientY
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function openContextMenu () {
|
||||||
|
const top = unref(y)
|
||||||
|
const left = unref(x)
|
||||||
|
|
||||||
|
virtualElement.value.getBoundingClientRect = () => ({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
top,
|
||||||
|
left
|
||||||
|
})
|
||||||
|
isContextMenuOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
Button: {
|
Button: {
|
||||||
label: 'Button text'
|
label: 'Button text'
|
||||||
@@ -129,7 +156,9 @@ const defaultProps = {
|
|||||||
title: 'A new software update is available. See what’s new in version 2.0.4.'
|
title: 'A new software update is available. See what’s new in version 2.0.4.'
|
||||||
},
|
},
|
||||||
AlertDialog: {
|
AlertDialog: {
|
||||||
title: 'Are you sure you want to close this modal?',
|
icon: 'i-heroicons-exclamation-triangle',
|
||||||
|
title: 'Deactivate account',
|
||||||
|
description: 'Are you sure you want to deactivate your account? All of your data will be permanently removed from our servers forever. This action cannot be undone.',
|
||||||
modelValue: alertDialog,
|
modelValue: alertDialog,
|
||||||
'onUpdate:modelValue': (v) => { alertDialog.value = v },
|
'onUpdate:modelValue': (v) => { alertDialog.value = v },
|
||||||
component: {
|
component: {
|
||||||
@@ -151,21 +180,21 @@ const defaultProps = {
|
|||||||
items: [
|
items: [
|
||||||
[{
|
[{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: 'heroicons-solid:pencil'
|
icon: 'i-heroicons-pencil-square-20-solid'
|
||||||
}, {
|
}, {
|
||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
icon: 'heroicons-solid:duplicate'
|
icon: 'i-heroicons-document-duplicate-20-solid'
|
||||||
}],
|
}],
|
||||||
[{
|
[{
|
||||||
label: 'Archive',
|
label: 'Archive',
|
||||||
icon: 'heroicons-solid:archive'
|
icon: 'i-heroicons-archive-box-20-solid'
|
||||||
}, {
|
}, {
|
||||||
label: 'Move',
|
label: 'Move',
|
||||||
icon: 'heroicons-solid:external-link'
|
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||||
}],
|
}],
|
||||||
[{
|
[{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
icon: 'heroicons-solid:trash'
|
icon: 'i-heroicons-trash-20-solid'
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -173,22 +202,22 @@ const defaultProps = {
|
|||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
label: 'Home',
|
label: 'Home',
|
||||||
icon: 'heroicons-outline:home',
|
icon: 'i-heroicons-home',
|
||||||
to: '/'
|
to: '/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Examples',
|
label: 'Examples',
|
||||||
icon: 'heroicons-outline:book-open',
|
icon: 'i-heroicons-book-open',
|
||||||
to: '/examples'
|
to: '/examples'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Migration',
|
label: 'Migration',
|
||||||
icon: 'heroicons-outline:refresh',
|
icon: 'i-heroicons-arrow-path',
|
||||||
to: '/migration'
|
to: '/migration'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'External link',
|
label: 'External link',
|
||||||
icon: 'heroicons-outline:external-link',
|
icon: 'i-heroicons-link',
|
||||||
to: 'https://google.fr',
|
to: 'https://google.fr',
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}
|
}
|
||||||
@@ -204,7 +233,7 @@ const defaultProps = {
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
Icon: {
|
Icon: {
|
||||||
name: 'heroicons-outline:bell'
|
name: 'i-heroicons-bell'
|
||||||
},
|
},
|
||||||
Input: {
|
Input: {
|
||||||
name: 'input',
|
name: 'input',
|
||||||
@@ -256,6 +285,31 @@ const defaultProps = {
|
|||||||
title: 'Notification title',
|
title: 'Notification title',
|
||||||
callback: 'console.log(\'Timer expired\')'
|
callback: 'console.log(\'Timer expired\')'
|
||||||
},
|
},
|
||||||
|
ContextMenu: {
|
||||||
|
modelValue: isContextMenuOpen,
|
||||||
|
'onUpdate:modelValue': (v) => { isContextMenuOpen.value = v },
|
||||||
|
virtualElement,
|
||||||
|
component: {
|
||||||
|
name: 'Card',
|
||||||
|
props: {
|
||||||
|
variant: 'secondary',
|
||||||
|
label: 'Open context menu',
|
||||||
|
onClick: () => { isContextMenuOpen.value = false },
|
||||||
|
onContextmenu: (e) => {
|
||||||
|
e?.preventDefault()
|
||||||
|
openContextMenu()
|
||||||
|
},
|
||||||
|
class: 'relative w-[300px] h-[100px]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
default: {
|
||||||
|
tag: 'div',
|
||||||
|
html: 'Context menu content',
|
||||||
|
class: 'rounded border u-border-gray-200 p-2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Modal: {
|
Modal: {
|
||||||
modelValue: modal,
|
modelValue: modal,
|
||||||
'onUpdate:modelValue': (v) => { modal.value = v },
|
'onUpdate:modelValue': (v) => { modal.value = v },
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
Button:
|
Button:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UButton variant="primary" icon="heroicons-outline:mail">
|
<UButton variant="primary" icon="i-heroicons-envelope">
|
||||||
Button text
|
Button text
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
<UModal v-model="isModalOpen" @submit.prevent="onSubmit">
|
<UModal v-model="isModalOpen" @submit.prevent="onSubmit">
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<UIcon name="heroicons-outline:exclamation" class="h-6 w-6 text-red-600" aria-hidden="true" />
|
<UIcon name="i-heroicons-exclamation" class="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<h3 class="text-lg leading-6 font-medium u-text-gray-900">
|
<h3 class="text-lg leading-6 font-medium u-text-gray-900">
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UDropdown v-slot="{ open }" :items="dropdownItems" placement="bottom-start">
|
<UDropdown v-slot="{ open }" :items="dropdownItems" placement="bottom-start">
|
||||||
<UButton variant="white" :icon="open ? 'heroicons-solid:chevron-up' : 'heroicons-solid:chevron-down'" trailing icon-class="transition">
|
<UButton variant="white" :icon="open ? 'i-heroicons-chevron-up-20-solid' : 'i-heroicons-chevron-down-20-solid'" trailing icon-class="transition">
|
||||||
Open menu!
|
Open menu!
|
||||||
</UButton>
|
</UButton>
|
||||||
</UDropdown>
|
</UDropdown>
|
||||||
@@ -83,15 +84,10 @@
|
|||||||
Dropdown with avatar:
|
Dropdown with avatar:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UDropdown :items="customDropdownItems" placement="bottom-end" @hover="onHover">
|
<UDropdown :items="customDropdownItems" placement="bottom-end">
|
||||||
<button class="flex">
|
<button class="flex">
|
||||||
<UAvatar src="https://picsum.photos/200/300" />
|
<UAvatar src="https://picsum.photos/200/300" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<template #item-with-avatar="{ item }">
|
|
||||||
<UAvatar v-if="item.avatar" :src="item.avatar" size="xxs" />
|
|
||||||
{{ item.label }}
|
|
||||||
</template>
|
|
||||||
</UDropdown>
|
</UDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -100,54 +96,16 @@
|
|||||||
Popover:
|
Popover:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UPopover mode="hover" wrapper-class="inline-block relative">
|
<UPopover wrapper-class="inline-block relative">
|
||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
<UButton variant="secondary" :icon="open ? 'heroicons-solid:chevron-up' : 'heroicons-solid:chevron-down'" trailing icon-class="transition">
|
<UButton variant="secondary" :icon="open ? 'i-heroicons-chevron-up-20-solid-20' : 'i-heroicons-chevron-down-20-solid'" trailing icon-class="transition">
|
||||||
Open popover!
|
Open popover!
|
||||||
</UButton>
|
</UButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #panel>
|
<template #panel>
|
||||||
<div
|
<div class="p-2">
|
||||||
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
|
Panel
|
||||||
>
|
|
||||||
<div class="relative grid gap-8 bg-white p-7">
|
|
||||||
<a
|
|
||||||
v-for="item in solutions"
|
|
||||||
:key="item.name"
|
|
||||||
:href="item.href"
|
|
||||||
class="flex items-center p-2 -m-3 transition duration-150 ease-in-out rounded-lg hover:bg-gray-50 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-center flex-shrink-0 w-10 h-10 text-white sm:h-12 sm:w-12"
|
|
||||||
>
|
|
||||||
<div v-html="item.icon" />
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-medium u-text-gray-900">
|
|
||||||
{{ item.name }}
|
|
||||||
</p>
|
|
||||||
<p class="text-sm u-text-gray-500">
|
|
||||||
{{ item.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="p-4 bg-gray-50">
|
|
||||||
<a
|
|
||||||
href="##"
|
|
||||||
class="flow-root px-2 py-2 transition duration-150 ease-in-out rounded-md hover:bg-gray-100 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50"
|
|
||||||
>
|
|
||||||
<span class="flex items-center">
|
|
||||||
<span class="text-sm font-medium u-text-gray-900">
|
|
||||||
Documentation
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="block text-sm u-text-gray-500">
|
|
||||||
Start integrating products and tools
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UPopover>
|
</UPopover>
|
||||||
@@ -158,8 +116,8 @@
|
|||||||
Tooltip:
|
Tooltip:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UTooltip text="Hello tooltip!">
|
<UTooltip text="Hello tooltip!" :shortcuts="['⌘', 'G']">
|
||||||
<UIcon name="heroicons-outline:information-circle" class="w-6 h-6 text-black cursor-pointer" />
|
<UIcon name="i-heroicons-information-circle" class="w-6 h-6 u-text-gray-900 cursor-pointer" />
|
||||||
</UTooltip>
|
</UTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -167,7 +125,31 @@
|
|||||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||||
Notifications:
|
Notifications:
|
||||||
</div>
|
</div>
|
||||||
<UButton icon="heroicons-outline:bell" variant="red" label="Trigger an error" @click="onNotificationClick" />
|
<UButton icon="i-heroicons-bell" variant="red" label="Trigger an error" @click="onNotificationClick" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||||
|
Copy text to clipboard:
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<UInput v-model="textToCopy" name="textToCopy" />
|
||||||
|
<UButton icon="i-heroicons-document-duplicate-solid" variant="primary" label="Copy text" @click="onCopyTextClick" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||||
|
Context menu:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UCard class="relative" body-class="h-64" @click="isContextMenuOpen = false" @contextmenu.prevent="openContextMenu">
|
||||||
|
<UContextMenu v-model="isContextMenuOpen" :virtual-element="virtualElement" width-class="w-48">
|
||||||
|
<div class="p-2">
|
||||||
|
Menu
|
||||||
|
</div>
|
||||||
|
</UContextMenu>
|
||||||
|
</UCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -176,7 +158,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UCard body-class="">
|
<UCard body-class="">
|
||||||
<UCommandPalette v-model="form.persons" multiple :groups="[{ key: 'persons', commands: people }]" />
|
<UCommandPalette
|
||||||
|
:placeholder="false"
|
||||||
|
:options="{
|
||||||
|
fuseOptions: {
|
||||||
|
includeMatches: true
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
:groups="commandPaletteGroups"
|
||||||
|
command-attribute="name"
|
||||||
|
/>
|
||||||
</UCard>
|
</UCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -188,7 +179,7 @@
|
|||||||
<UCard body-class="flex">
|
<UCard body-class="flex">
|
||||||
<div class="flex-1 px-4 py-5 sm:p-6 space-y-3">
|
<div class="flex-1 px-4 py-5 sm:p-6 space-y-3">
|
||||||
<UFormGroup label="Email" name="email" required>
|
<UFormGroup label="Email" name="email" required>
|
||||||
<UInput v-model="form.email" type="email" name="email" required icon="heroicons-outline:mail" />
|
<UInput v-model="form.email" type="email" name="email" required icon="i-heroicons-mail" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
||||||
<UFormGroup label="Description" name="description">
|
<UFormGroup label="Description" name="description">
|
||||||
@@ -203,16 +194,16 @@
|
|||||||
placeholder="Select a person"
|
placeholder="Select a person"
|
||||||
text-attribute="name"
|
text-attribute="name"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
icon="heroicons-outline:user"
|
icon="i-heroicons-user"
|
||||||
/>
|
/>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
||||||
<UFormGroup label="People" name="people" required>
|
<UFormGroup label="People" name="people" required>
|
||||||
<USelectCustom v-model="form.person" name="people" :options="people" text-attribute="label" searchable />
|
<USelectCustom v-model="form.person" name="people" :options="people" text-attribute="name" searchable />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
||||||
<UFormGroup label="Toggle" name="toggle">
|
<UFormGroup label="Toggle" name="toggle">
|
||||||
<UToggle v-model="form.toggle" name="toggle" icon-off="heroicons-solid:x" icon-on="heroicons-solid:check" />
|
<UToggle v-model="form.toggle" name="toggle" icon-off="i-heroicons-x-mark-20-solid" icon-on="i-heroicons-check-20-solid" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
||||||
<UFormGroup label="Notifications" label-class="text-base font-medium u-text-gray-900" description="How do you prefer to receive notifications?">
|
<UFormGroup label="Notifications" label-class="text-base font-medium u-text-gray-900" description="How do you prefer to receive notifications?">
|
||||||
@@ -249,14 +240,16 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const isModalOpen = ref(false)
|
const isModalOpen = ref(false)
|
||||||
|
const textToCopy = ref('Copied text')
|
||||||
|
|
||||||
|
const people = ref([
|
||||||
|
{ id: 1, name: 'Durward Reynolds', disabled: false },
|
||||||
|
{ id: 2, name: 'Kenton Towne', disabled: false },
|
||||||
|
{ id: 3, name: 'Therese Wunsch', disabled: false },
|
||||||
|
{ id: 4, name: 'Benedict Kessler', disabled: true },
|
||||||
|
{ id: 5, name: 'Katelyn Rohan', disabled: false }
|
||||||
|
])
|
||||||
|
|
||||||
const people = [
|
|
||||||
{ id: 1, label: 'Durward Reynolds', disabled: false },
|
|
||||||
{ id: 2, label: 'Kenton Towne', disabled: false },
|
|
||||||
{ id: 3, label: 'Therese Wunsch', disabled: false },
|
|
||||||
{ id: 4, label: 'Benedict Kessler', disabled: true },
|
|
||||||
{ id: 5, label: 'Katelyn Rohan', disabled: false }
|
|
||||||
]
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -265,11 +258,49 @@ const form = reactive({
|
|||||||
notifications: [],
|
notifications: [],
|
||||||
terms: false,
|
terms: false,
|
||||||
personId: null,
|
personId: null,
|
||||||
person: ref(people[0]),
|
person: ref(people.value[0]),
|
||||||
persons: ref([people[0]])
|
persons: ref([people.value[0]])
|
||||||
})
|
})
|
||||||
|
|
||||||
const { $toast } = useNuxtApp()
|
const toast = useToast()
|
||||||
|
const clipboard = useCopyToClipboard()
|
||||||
|
|
||||||
|
const x = ref(0)
|
||||||
|
const y = ref(0)
|
||||||
|
const isContextMenuOpen = ref(false)
|
||||||
|
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||||
|
|
||||||
|
const commandPaletteGroups = computed(() => ([{
|
||||||
|
key: 'people',
|
||||||
|
commands: people.value
|
||||||
|
}, {
|
||||||
|
key: 'search',
|
||||||
|
label: q => q && `Search results for "${q}"...`,
|
||||||
|
search: async (q) => {
|
||||||
|
if (!q) { return [] }
|
||||||
|
return await $fetch(`https://jsonplaceholder.typicode.com/users?q=${q}`)
|
||||||
|
}
|
||||||
|
}]))
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('mousemove', ({ clientX, clientY }) => {
|
||||||
|
x.value = clientX
|
||||||
|
y.value = clientY
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function openContextMenu () {
|
||||||
|
const top = unref(y)
|
||||||
|
const left = unref(x)
|
||||||
|
|
||||||
|
virtualElement.value.getBoundingClientRect = () => ({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
top,
|
||||||
|
left
|
||||||
|
})
|
||||||
|
isContextMenuOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
function toggleModalIsOpen () {
|
function toggleModalIsOpen () {
|
||||||
isModalOpen.value = !isModalOpen.value
|
isModalOpen.value = !isModalOpen.value
|
||||||
@@ -285,139 +316,52 @@ function onSubmit () {
|
|||||||
console.warn('submit')
|
console.warn('submit')
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHover (item) {
|
|
||||||
console.log(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropdownItems = [
|
const dropdownItems = [
|
||||||
[{
|
[{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: 'heroicons-solid:pencil',
|
icon: 'i-heroicons-pencil-square-20-solid',
|
||||||
click: () => onClick()
|
click: (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Duplicate',
|
label: 'Duplicate',
|
||||||
icon: 'heroicons-solid:duplicate'
|
icon: 'i-heroicons-document-duplicate-20-solid'
|
||||||
}],
|
}],
|
||||||
[{
|
[{
|
||||||
label: 'Archive',
|
label: 'Archive',
|
||||||
icon: 'heroicons-solid:archive'
|
icon: 'i-heroicons-archive-box-20-solid'
|
||||||
}, {
|
}, {
|
||||||
label: 'Move',
|
label: 'Move',
|
||||||
icon: 'heroicons-solid:external-link',
|
icon: 'i-heroicons-arrow-right-circle-20-solid',
|
||||||
to: 'https://www.google.fr',
|
to: 'https://www.google.fr',
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}],
|
}],
|
||||||
[{
|
[{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
icon: 'heroicons-solid:trash'
|
icon: 'i-heroicons-trash-20-solid'
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
|
|
||||||
const customDropdownItems = [
|
const customDropdownItems = [
|
||||||
[{
|
[{
|
||||||
label: 'benjamincanac',
|
label: 'benjamincanac',
|
||||||
avatar: 'https://picsum.photos/200/300',
|
avatar: { src: 'https://picsum.photos/200/300' },
|
||||||
href: 'https://google.fr',
|
href: 'https://google.fr',
|
||||||
target: '_blank',
|
target: '_blank'
|
||||||
slot: 'item-with-avatar'
|
|
||||||
}],
|
}],
|
||||||
[{
|
[{
|
||||||
label: 'About',
|
label: 'About',
|
||||||
icon: 'heroicons-solid:plus',
|
icon: 'i-heroicons-plus-20-solid',
|
||||||
to: '/about'
|
to: '/about'
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
|
|
||||||
const solutions = [
|
|
||||||
{
|
|
||||||
name: 'Insights',
|
|
||||||
description: 'Measure actions your users take',
|
|
||||||
href: '##',
|
|
||||||
icon: `
|
|
||||||
<svg
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 48 48"
|
|
||||||
fill="none"
|
|
||||||
aria-hidden='true'
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
|
|
||||||
<path
|
|
||||||
d="M24 11L35.2583 17.5V30.5L24 37L12.7417 30.5V17.5L24 11Z"
|
|
||||||
stroke="#FB923C"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M16.7417 19.8094V28.1906L24 32.3812L31.2584 28.1906V19.8094L24 15.6188L16.7417 19.8094Z"
|
|
||||||
stroke="#FDBA74"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M20.7417 22.1196V25.882L24 27.7632L27.2584 25.882V22.1196L24 20.2384L20.7417 22.1196Z"
|
|
||||||
stroke="#FDBA74"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Automations',
|
|
||||||
description: 'Create your own targeted content',
|
|
||||||
href: '##',
|
|
||||||
icon: `
|
|
||||||
<svg
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 48 48"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
|
|
||||||
<path
|
|
||||||
d="M28.0413 20L23.9998 13L19.9585 20M32.0828 27.0001L36.1242 34H28.0415M19.9585 34H11.8755L15.9171 27"
|
|
||||||
stroke="#FB923C"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M18.804 30H29.1963L24.0001 21L18.804 30Z"
|
|
||||||
stroke="#FDBA74"
|
|
||||||
stroke-width="2"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Reports',
|
|
||||||
description: 'Keep track of your growth',
|
|
||||||
href: '##',
|
|
||||||
icon: `
|
|
||||||
<svg
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 48 48"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect width="48" height="48" rx="8" fill="#FFEDD5" />
|
|
||||||
<rect x="13" y="32" width="2" height="4" fill="#FDBA74" />
|
|
||||||
<rect x="17" y="28" width="2" height="8" fill="#FDBA74" />
|
|
||||||
<rect x="21" y="24" width="2" height="12" fill="#FDBA74" />
|
|
||||||
<rect x="25" y="20" width="2" height="16" fill="#FDBA74" />
|
|
||||||
<rect x="29" y="16" width="2" height="20" fill="#FB923C" />
|
|
||||||
<rect x="33" y="12" width="2" height="24" fill="#FB923C" />
|
|
||||||
</svg>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const onNotificationClick = () => {
|
const onNotificationClick = () => {
|
||||||
$toast.error({ title: 'Error', description: 'This is an error message' })
|
toast.error({ title: 'Error', description: 'This is an error message' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCopyTextClick = () => {
|
||||||
|
clipboard.copy(textToCopy.value, { title: 'Text copied successfully!' })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||||
|
|||||||
@@ -105,13 +105,6 @@ const components = [
|
|||||||
preset: true,
|
preset: true,
|
||||||
typescript: true
|
typescript: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Icon',
|
|
||||||
to: '/components/Icon',
|
|
||||||
nuxt3: true,
|
|
||||||
capi: true,
|
|
||||||
typescript: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Link',
|
label: 'Link',
|
||||||
to: '/components/Link',
|
to: '/components/Link',
|
||||||
|
|||||||
58
package.json
58
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nuxthq/ui",
|
"name": "@nuxthq/ui",
|
||||||
"version": "0.0.3",
|
"version": "1.2.2",
|
||||||
"repository": "https://github.com/nuxtlabs/ui",
|
"repository": "https://github.com/nuxtlabs/ui",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -18,42 +18,42 @@
|
|||||||
"build": "nuxt-module-build",
|
"build": "nuxt-module-build",
|
||||||
"prepack": "yarn build",
|
"prepack": "yarn build",
|
||||||
"dev": "nuxi dev docs",
|
"dev": "nuxi dev docs",
|
||||||
"build:docs": "nuxi build docs",
|
"build:docs": "nuxi generate docs",
|
||||||
"postbuild:docs": "mv ./docs/.vercel_build_output .vercel_build_output",
|
|
||||||
"lint": "eslint --ext .ts,.js,.vue .",
|
"lint": "eslint --ext .ts,.js,.vue .",
|
||||||
|
"typecheck": "nuxi typecheck",
|
||||||
"prepare": "nuxi prepare docs",
|
"prepare": "nuxi prepare docs",
|
||||||
"release": "yarn lint && standard-version && git push --follow-tags"
|
"release": "yarn lint && standard-version && git push --follow-tags"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.6.7",
|
"@egoist/tailwindcss-icons": "^1.0.7",
|
||||||
"@iconify/vue": "^3.2.1",
|
"@headlessui/vue": "1.7.10",
|
||||||
"@nuxt/kit": "npm:@nuxt/kit-edge@latest",
|
"@iconify-json/heroicons": "^1.1.10",
|
||||||
"@nuxtjs/color-mode": "^3.1.4",
|
"@nuxt/kit": "^3.3.1",
|
||||||
"@nuxtjs/tailwindcss": "^5.3.0",
|
"@nuxtjs/color-mode": "^3.2.0",
|
||||||
"@popperjs/core": "^2.11.5",
|
"@nuxtjs/tailwindcss": "^6.6.0",
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.0",
|
"@popperjs/core": "^2.11.6",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||||
"@tailwindcss/line-clamp": "^0.4.0",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
"@vueuse/core": "^8.9.4",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@vueuse/integrations": "^8.9.4",
|
"@vueuse/core": "^9.13.0",
|
||||||
"defu": "^6.0.0",
|
"@vueuse/integrations": "^9.13.0",
|
||||||
|
"@vueuse/math": "^9.13.0",
|
||||||
|
"defu": "^6.1.2",
|
||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"tailwindcss": "^3.1.6"
|
"tailwindcss": "^3.2.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/module-builder": "^0.1.7",
|
"@iconify-json/mdi": "^1.1.50",
|
||||||
"@nuxtjs/eslint-config-typescript": "^10.0.0",
|
"@nuxt/module-builder": "^0.2.1",
|
||||||
"@types/lodash-es": "^4.17.6",
|
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
||||||
"eslint": "^8.20.0",
|
"@types/lodash-es": "^4.17.7",
|
||||||
"nuxt": "npm:nuxt3@latest",
|
"@types/node": "^18.15.3",
|
||||||
"standard-version": "^9.5.0"
|
"eslint": "^8.36.0",
|
||||||
},
|
"nuxt": "^3.3.1",
|
||||||
"build": {
|
"standard-version": "^9.5.0",
|
||||||
"externals": [
|
"unbuild": "^1.1.2",
|
||||||
"tailwindcss",
|
"vue-tsc": "^1.2.0"
|
||||||
"tailwindcss/colors.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/env.d.ts
vendored
Normal file
6
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type { DefaultPreset } from './runtime/presets/default'
|
||||||
|
|
||||||
|
declare module '#build/ui' {
|
||||||
|
declare const preset: DefaultPreset
|
||||||
|
export default preset
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { defineNuxtModule, installModule, addComponentsDir, addTemplate, addPlugin, createResolver } from '@nuxt/kit'
|
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, addTemplate, createResolver } from '@nuxt/kit'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import colors from 'tailwindcss/colors.js'
|
import colors from 'tailwindcss/colors.js'
|
||||||
import type { Config } from 'tailwindcss'
|
import type { Config } from 'tailwindcss'
|
||||||
|
import { iconsPlugin, getIconCollections } from '@egoist/tailwindcss-icons'
|
||||||
import { name, version } from '../package.json'
|
import { name, version } from '../package.json'
|
||||||
import { colorsAsRegex, excludeColors } from './runtime/utils/colors'
|
import { colorsAsRegex, excludeColors } from './runtime/utils/colors'
|
||||||
import defaultPreset from './runtime/presets/default'
|
import defaultPreset from './runtime/presets/default'
|
||||||
@@ -39,6 +40,8 @@ export interface ModuleOptions {
|
|||||||
|
|
||||||
colors?: ColorsOptions
|
colors?: ColorsOptions
|
||||||
|
|
||||||
|
icons: string[]
|
||||||
|
|
||||||
tailwindcss?: Partial<Config>
|
tailwindcss?: Partial<Config>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ const defaults = {
|
|||||||
primary: 'indigo',
|
primary: 'indigo',
|
||||||
gray: 'gray'
|
gray: 'gray'
|
||||||
},
|
},
|
||||||
|
icons: ['heroicons'],
|
||||||
tailwindcss: {
|
tailwindcss: {
|
||||||
theme: {}
|
theme: {}
|
||||||
}
|
}
|
||||||
@@ -60,8 +64,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
version,
|
version,
|
||||||
configKey: 'ui',
|
configKey: 'ui',
|
||||||
compatibility: {
|
compatibility: {
|
||||||
nuxt: '^3.0.0',
|
nuxt: '^3.0.0-rc.8'
|
||||||
bridge: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaults,
|
defaults,
|
||||||
@@ -73,7 +76,7 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
// Transpile runtime
|
// Transpile runtime
|
||||||
const runtimeDir = resolve('./runtime')
|
const runtimeDir = resolve('./runtime')
|
||||||
nuxt.options.build.transpile.push(runtimeDir)
|
nuxt.options.build.transpile.push(runtimeDir)
|
||||||
nuxt.options.build.transpile.push('@popperjs/core', '@headlessui/vue', '@iconify/vue')
|
nuxt.options.build.transpile.push('@popperjs/core', '@headlessui/vue')
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
nuxt.hook('tailwindcss:config', function (tailwindConfig: TailwindConfig) {
|
nuxt.hook('tailwindcss:config', function (tailwindConfig: TailwindConfig) {
|
||||||
@@ -109,12 +112,20 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
variants: ['focus']
|
variants: ['focus']
|
||||||
}])
|
}])
|
||||||
|
|
||||||
|
tailwindConfig.plugins = tailwindConfig.plugins || []
|
||||||
|
tailwindConfig.plugins.push(iconsPlugin({ collections: getIconCollections(options.icons as any[]) }))
|
||||||
|
|
||||||
const ui: object = defu(preset, defaultPreset(variantColors))
|
const ui: object = defu(preset, defaultPreset(variantColors))
|
||||||
|
|
||||||
addTemplate({
|
addTemplate({
|
||||||
filename: 'ui.mjs',
|
filename: 'ui.mjs',
|
||||||
getContents: () => `export default ${JSON.stringify(ui)}`
|
getContents: () => `export default ${JSON.stringify(ui)}`
|
||||||
})
|
})
|
||||||
|
addTemplate({
|
||||||
|
filename: 'ui.d.ts',
|
||||||
|
write: true,
|
||||||
|
getContents: () => 'declare const d: any; export default d;'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||||
@@ -144,9 +155,6 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
cssPath: resolve(runtimeDir, 'tailwind.css')
|
cssPath: resolve(runtimeDir, 'tailwind.css')
|
||||||
})
|
})
|
||||||
|
|
||||||
addPlugin(resolve(runtimeDir, 'plugins', 'toast.client'))
|
|
||||||
addPlugin(resolve(runtimeDir, 'plugins', 'clipboard.client'))
|
|
||||||
|
|
||||||
addComponentsDir({
|
addComponentsDir({
|
||||||
path: resolve(runtimeDir, 'components', 'elements'),
|
path: resolve(runtimeDir, 'components', 'elements'),
|
||||||
prefix,
|
prefix,
|
||||||
@@ -178,9 +186,6 @@ export default defineNuxtModule<ModuleOptions>({
|
|||||||
watch: false
|
watch: false
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add composables
|
addImportsDir(resolve(runtimeDir, 'composables'))
|
||||||
nuxt.hook('autoImports:dirs', (dirs) => {
|
|
||||||
dirs.push(resolve(runtimeDir, 'composables'))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<div class="flex flex-row-reverse">
|
||||||
|
<Avatar
|
||||||
|
v-if="remainingGroupSize > 0"
|
||||||
|
:size="size"
|
||||||
|
:text="`+${remainingGroupSize}`"
|
||||||
|
:class="avatarClass"
|
||||||
|
/>
|
||||||
<Avatar
|
<Avatar
|
||||||
v-for="(avatar, index) of displayedGroup"
|
v-for="(avatar, index) of displayedGroup"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -7,19 +13,13 @@
|
|||||||
:size="size"
|
:size="size"
|
||||||
:class="avatarClass"
|
:class="avatarClass"
|
||||||
/>
|
/>
|
||||||
<Avatar
|
|
||||||
v-if="remainingGroupSize > 0"
|
|
||||||
:size="size"
|
|
||||||
:text="`+${remainingGroupSize}`"
|
|
||||||
:class="avatarClass"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { classNames } from '../../utils/'
|
import { classNames } from '../../utils'
|
||||||
import Avatar from './Avatar'
|
import Avatar from './Avatar.vue'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -31,7 +31,7 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'md',
|
default: 'md',
|
||||||
validator (value: string) {
|
validator (value: string) {
|
||||||
return ['xxxs', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
|
return Object.keys($ui.avatar.size).includes(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
max: {
|
max: {
|
||||||
@@ -55,9 +55,9 @@ const avatars = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const displayedGroup = computed(() => {
|
const displayedGroup = computed(() => {
|
||||||
if (!props.max) { return avatars.value }
|
if (!props.max) { return [...avatars.value].reverse() }
|
||||||
|
|
||||||
return avatars.value.slice(0, props.max)
|
return avatars.value.slice(0, props.max).reverse()
|
||||||
})
|
})
|
||||||
|
|
||||||
const remainingGroupSize = computed(() => {
|
const remainingGroupSize = computed(() => {
|
||||||
|
|||||||
@@ -6,16 +6,23 @@
|
|||||||
:aria-label="ariaLabel"
|
:aria-label="ariaLabel"
|
||||||
v-bind="buttonProps"
|
v-bind="buttonProps"
|
||||||
>
|
>
|
||||||
<Icon v-if="isLeading" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
|
<Icon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
|
||||||
<slot><span :class="truncate ? 'text-left break-all line-clamp-1' : ''">{{ label }}</span></slot>
|
<slot>
|
||||||
<Icon v-if="isTrailing" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
<span :class="[truncate ? 'text-left break-all line-clamp-1' : '', compact ? 'hidden sm:block' : '']">
|
||||||
|
<span :class="[labelCompact && 'hidden sm:block']">{{ label }}</span>
|
||||||
|
<span v-if="labelCompact" class="sm:hidden">{{ labelCompact }}</span>
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
<Icon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, useSlots } from 'vue'
|
import { ref, computed, useSlots } from 'vue'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
|
||||||
import NuxtLink from '#app/components/nuxt-link'
|
import NuxtLink from '#app/components/nuxt-link'
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon.vue'
|
||||||
import { classNames } from '../../utils'
|
import { classNames } from '../../utils'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
@@ -32,6 +39,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
labelCompact: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -79,7 +90,7 @@ const props = defineProps({
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
type: [String, Object],
|
type: [String, Object] as PropType<string | RouteLocationNormalized | RouteLocationRaw>,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
target: {
|
target: {
|
||||||
@@ -125,6 +136,10 @@ const props = defineProps({
|
|||||||
truncate: {
|
truncate: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
compact: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -162,7 +177,7 @@ const buttonClass = computed(() => {
|
|||||||
return classNames(
|
return classNames(
|
||||||
props.baseClass,
|
props.baseClass,
|
||||||
$ui.button.size[props.size],
|
$ui.button.size[props.size],
|
||||||
$ui.button[isSquare.value ? 'square' : 'spacing'][props.size],
|
$ui.button[isSquare.value ? 'square' : (props.compact ? 'compact' : 'spacing')][props.size],
|
||||||
$ui.button.variant[props.variant],
|
$ui.button.variant[props.variant],
|
||||||
props.block ? 'w-full flex justify-center items-center' : 'inline-flex items-center',
|
props.block ? 'w-full flex justify-center items-center' : 'inline-flex items-center',
|
||||||
props.rounded ? 'rounded-full' : props.roundedClass,
|
props.rounded ? 'rounded-full' : props.roundedClass,
|
||||||
@@ -190,7 +205,7 @@ const leadingIconClass = computed(() => {
|
|||||||
return classNames(
|
return classNames(
|
||||||
props.iconBaseClass,
|
props.iconBaseClass,
|
||||||
$ui.button.icon.size[props.size],
|
$ui.button.icon.size[props.size],
|
||||||
(!!slots.default || !!props.label?.length) && $ui.button.icon.leading.spacing[props.size],
|
(!!slots.default || !!props.label?.length) && $ui.button.icon.leading[props.compact ? 'compactSpacing' : 'spacing'][props.size],
|
||||||
props.leadingIconClass,
|
props.leadingIconClass,
|
||||||
props.loading && 'animate-spin'
|
props.loading && 'animate-spin'
|
||||||
)
|
)
|
||||||
@@ -200,7 +215,7 @@ const trailingIconClass = computed(() => {
|
|||||||
return classNames(
|
return classNames(
|
||||||
props.iconBaseClass,
|
props.iconBaseClass,
|
||||||
$ui.button.icon.size[props.size],
|
$ui.button.icon.size[props.size],
|
||||||
(!!slots.default || !!props.label?.length) && $ui.button.icon.trailing.spacing[props.size],
|
(!!slots.default || !!props.label?.length) && $ui.button.icon.trailing[props.compact ? 'compactSpacing' : 'spacing'][props.size],
|
||||||
props.trailingIconClass,
|
props.trailingIconClass,
|
||||||
props.loading && !isLeading.value && 'animate-spin'
|
props.loading && !isLeading.value && 'animate-spin'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,30 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<Menu v-slot="{ open }" as="div" :class="wrapperClass" @mouseleave="onMouseLeave">
|
<Menu v-slot="{ open }" as="div" :class="wrapperClass" @mouseleave="onMouseLeave">
|
||||||
<MenuButton ref="trigger" as="div" class="inline-flex" @mouseover="onMouseOver">
|
<MenuButton
|
||||||
|
ref="trigger"
|
||||||
|
as="div"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="inline-flex w-full"
|
||||||
|
role="button"
|
||||||
|
@mouseover="onMouseOver"
|
||||||
|
>
|
||||||
<slot :open="open">
|
<slot :open="open">
|
||||||
<button>Open</button>
|
<button :disabled="disabled">
|
||||||
|
Open
|
||||||
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<div v-if="open" ref="container" :class="containerClass" @mouseover="onMouseOver">
|
<div v-if="open && items.length" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
|
||||||
<transition
|
<transition appear v-bind="transitionClass">
|
||||||
appear
|
<MenuItems :class="[baseClass, divideClass, ringClass, roundedClass, shadowClass, backgroundClass]" static>
|
||||||
enter-active-class="transition duration-100 ease-out"
|
<div v-for="(subItems, index) of items" :key="index" :class="groupClass">
|
||||||
enter-from-class="transform scale-95 opacity-0"
|
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
|
||||||
enter-to-class="transform scale-100 opacity-100"
|
<Component
|
||||||
leave-active-class="transition duration-75 ease-out"
|
v-bind="omit(item, ['click'])"
|
||||||
leave-from-class="transform scale-100 opacity-100"
|
:is="(item.to && NuxtLink) || (item.click && 'button') || 'div'"
|
||||||
leave-to-class="transform scale-95 opacity-0"
|
:class="resolveItemClass({ active, disabled: itemDisabled })"
|
||||||
>
|
@click="item.click"
|
||||||
<MenuItems :class="baseClass" static>
|
>
|
||||||
<div v-for="(subItems, index) of items" :key="index" class="py-1">
|
|
||||||
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }" :disabled="item.disabled" as="div">
|
|
||||||
<Component v-bind="item" :is="(item.to && NuxtLink) || (item.click && 'button') || 'div'" :class="resolveItemClass({ active, disabled })" @click="onItemClick(item)" @mouseover="$emit('hover', item)">
|
|
||||||
<slot :name="item.slot" :item="item">
|
<slot :name="item.slot" :item="item">
|
||||||
<Icon v-if="item.icon" :name="item.icon" :class="itemIconClass" />
|
<Icon v-if="item.icon" :name="item.icon" :class="[itemIconClass, item.iconClass]" />
|
||||||
<Avatar v-if="item.avatar" :src="item.avatar" :alt="item.label" :class="itemAvatarClass" size="xs" />
|
<Avatar v-if="item.avatar" v-bind="{ size: 'xxs', ...item.avatar }" :class="itemAvatarClass" />
|
||||||
|
|
||||||
<span class="truncate">{{ item.label }}</span>
|
<span class="truncate">{{ item.label }}</span>
|
||||||
|
|
||||||
|
<span v-if="item.shortcuts?.length" :class="itemShortcutsClass">
|
||||||
|
<kbd v-for="shortcut of item.shortcuts" :key="shortcut" class="font-sans">{{ shortcut }}</kbd>
|
||||||
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
</Component>
|
</Component>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -42,34 +52,35 @@ import {
|
|||||||
MenuItems,
|
MenuItems,
|
||||||
MenuItem
|
MenuItem
|
||||||
} from '@headlessui/vue'
|
} from '@headlessui/vue'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
import type { Ref } from 'vue'
|
import type { RouteLocationNormalized } from 'vue-router'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { defu } from 'defu'
|
||||||
import NuxtLink from '#app/components/nuxt-link'
|
import NuxtLink from '#app/components/nuxt-link'
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon.vue'
|
||||||
import Avatar from '../elements/Avatar'
|
import Avatar from '../elements/Avatar.vue'
|
||||||
import { classNames, usePopper } from '../../utils'
|
import { classNames, omit } from '../../utils'
|
||||||
|
import { usePopper } from '../../composables/usePopper'
|
||||||
|
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||||
|
import type { PopperOptions } from '../../types'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: {
|
items: {
|
||||||
type: Array,
|
type: Array as PropType<{
|
||||||
|
to?: RouteLocationNormalized
|
||||||
|
exact?: boolean
|
||||||
|
label: string
|
||||||
|
disabled?: boolean
|
||||||
|
slot?: string
|
||||||
|
icon?: string
|
||||||
|
iconClass?: string
|
||||||
|
avatar?: Partial<AvatarType>
|
||||||
|
click?: Function
|
||||||
|
shortcuts?: string[]
|
||||||
|
}[][]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
placement: {
|
|
||||||
type: String,
|
|
||||||
default: 'bottom-end',
|
|
||||||
validator: (value: string) => {
|
|
||||||
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
strategy: {
|
|
||||||
type: String,
|
|
||||||
default: 'fixed',
|
|
||||||
validator: (value: string) => {
|
|
||||||
return ['absolute', 'fixed'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'click',
|
default: 'click',
|
||||||
@@ -77,6 +88,10 @@ const props = defineProps({
|
|||||||
return ['click', 'hover'].includes(value)
|
return ['click', 'hover'].includes(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.dropdown.wrapper
|
default: () => $ui.dropdown.wrapper
|
||||||
@@ -85,10 +100,42 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.dropdown.container
|
default: () => $ui.dropdown.container
|
||||||
},
|
},
|
||||||
|
widthClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.width
|
||||||
|
},
|
||||||
|
backgroundClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.background
|
||||||
|
},
|
||||||
|
shadowClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.shadow
|
||||||
|
},
|
||||||
|
roundedClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.rounded
|
||||||
|
},
|
||||||
|
ringClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.ring
|
||||||
|
},
|
||||||
|
divideClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.divide
|
||||||
|
},
|
||||||
baseClass: {
|
baseClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.dropdown.base
|
default: () => $ui.dropdown.base
|
||||||
},
|
},
|
||||||
|
transitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.dropdown.transition
|
||||||
|
},
|
||||||
|
groupClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.group
|
||||||
|
},
|
||||||
itemBaseClass: {
|
itemBaseClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.dropdown.item.base
|
default: () => $ui.dropdown.item.base
|
||||||
@@ -112,34 +159,28 @@ const props = defineProps({
|
|||||||
itemAvatarClass: {
|
itemAvatarClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.dropdown.item.avatar
|
default: () => $ui.dropdown.item.avatar
|
||||||
|
},
|
||||||
|
itemShortcutsClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.dropdown.item.shortcuts
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
type: Object as PropType<PopperOptions>,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
openDelay: {
|
||||||
|
type: Number,
|
||||||
|
default: 50
|
||||||
|
},
|
||||||
|
closeDelay: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['hover'])
|
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.dropdown.popperOptions))
|
||||||
|
|
||||||
const [trigger, container] = usePopper({
|
const [trigger, container] = usePopper(popperOptions.value)
|
||||||
placement: props.placement,
|
|
||||||
strategy: props.strategy,
|
|
||||||
modifiers: [{
|
|
||||||
name: 'offset',
|
|
||||||
options: {
|
|
||||||
offset: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'computeStyles',
|
|
||||||
options: {
|
|
||||||
gpuAcceleration: false,
|
|
||||||
adaptive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preventOverflow',
|
|
||||||
options: {
|
|
||||||
padding: 8
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
function resolveItemClass ({ active, disabled }: { active: boolean, disabled: boolean }) {
|
function resolveItemClass ({ active, disabled }: { active: boolean, disabled: boolean }) {
|
||||||
return classNames(
|
return classNames(
|
||||||
@@ -149,31 +190,22 @@ function resolveItemClass ({ active, disabled }: { active: boolean, disabled: bo
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onItemClick (item: any) {
|
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/menu/menu.ts#L131
|
||||||
if (item.disabled) {
|
const menuApi = ref<any>(null)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.click) {
|
|
||||||
item.click()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const menuApi: Ref<any> = ref(null)
|
|
||||||
let openTimeout: NodeJS.Timeout | null = null
|
let openTimeout: NodeJS.Timeout | null = null
|
||||||
let closeTimeout: NodeJS.Timeout | null = null
|
let closeTimeout: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// @ts-expect-error internals
|
||||||
const menuProvides = trigger.value?.$.provides
|
const menuProvides = trigger.value?.$.provides
|
||||||
|
if (!menuProvides) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const menuProvidesSymbols = Object.getOwnPropertySymbols(menuProvides)
|
const menuProvidesSymbols = Object.getOwnPropertySymbols(menuProvides)
|
||||||
menuApi.value = menuProvidesSymbols.length && menuProvides[menuProvidesSymbols[0]]
|
menuApi.value = menuProvidesSymbols.length && menuProvides[menuProvidesSymbols[0]]
|
||||||
// stop trigger click propagation on hover
|
}, 200)
|
||||||
menuApi.value?.buttonRef.addEventListener('click', (e: Event) => {
|
|
||||||
if (props.mode === 'hover') {
|
|
||||||
e.stopPropagation()
|
|
||||||
}
|
|
||||||
}, true)
|
|
||||||
}, 0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function onMouseOver () {
|
function onMouseOver () {
|
||||||
@@ -193,7 +225,7 @@ function onMouseOver () {
|
|||||||
openTimeout = openTimeout || setTimeout(() => {
|
openTimeout = openTimeout || setTimeout(() => {
|
||||||
menuApi.value.openMenu && menuApi.value.openMenu()
|
menuApi.value.openMenu && menuApi.value.openMenu()
|
||||||
openTimeout = null
|
openTimeout = null
|
||||||
}, 50)
|
}, props.openDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseLeave () {
|
function onMouseLeave () {
|
||||||
@@ -213,7 +245,7 @@ function onMouseLeave () {
|
|||||||
closeTimeout = closeTimeout || setTimeout(() => {
|
closeTimeout = closeTimeout || setTimeout(() => {
|
||||||
menuApi.value.closeMenu && menuApi.value.closeMenu()
|
menuApi.value.closeMenu && menuApi.value.closeMenu()
|
||||||
closeTimeout = null
|
closeTimeout = null
|
||||||
}, 0)
|
}, props.closeDelay)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<span v-if="isFetching" />
|
<span :class="name" />
|
||||||
<Iconify v-else-if="icon" :icon="icon" />
|
|
||||||
<Component :is="component" v-else-if="component" />
|
|
||||||
<span v-else>{{ name }}</span>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IconifyIcon } from '@iconify/vue'
|
defineProps({
|
||||||
import { computed, ref, watch } from 'vue'
|
|
||||||
import { Icon as Iconify } from '@iconify/vue/dist/offline'
|
|
||||||
import { loadIcon } from '@iconify/vue'
|
|
||||||
import { useNuxtApp, useState } from '#imports'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const nuxtApp = useNuxtApp()
|
|
||||||
const state = useState('u-icons', () => ({}))
|
|
||||||
const isFetching = ref(false)
|
|
||||||
const icon = computed<IconifyIcon | null>(() => state.value?.[props.name])
|
|
||||||
const component = computed(() => nuxtApp.vueApp.component(props.name))
|
|
||||||
const loadIconComponent = async () => {
|
|
||||||
if (component.value) { return }
|
|
||||||
if (!state.value?.[props.name]) {
|
|
||||||
isFetching.value = true
|
|
||||||
state.value[props.name] = await loadIcon(props.name).catch(() => null)
|
|
||||||
isFetching.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watch(() => props.name, loadIconComponent)
|
|
||||||
!component.value && (await loadIconComponent())
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<router-link
|
<router-link
|
||||||
v-else
|
v-else
|
||||||
v-slot="{ href, navigate, isActive, isExactActive }"
|
v-slot="{ href, navigate, isActive, isExactActive }"
|
||||||
v-bind="$props"
|
v-bind="$props as RouterLinkProps"
|
||||||
custom
|
custom
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -24,12 +24,15 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
import type { RouteLocationNormalized, RouterLinkProps } from 'vue-router'
|
||||||
import { RouterLink } from 'vue-router'
|
import { RouterLink } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
// @ts-expect-error internal props
|
||||||
...RouterLink.props,
|
...RouterLink.props,
|
||||||
to: {
|
to: {
|
||||||
type: [String, Object],
|
type: [String, Object] as PropType<string | RouteLocationNormalized>,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
inactiveClass: {
|
inactiveClass: {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="rounded-md p-4" :class="variantClass">
|
<div class="rounded-md p-4" :class="variantClass">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="inline-flex flex-shrink-0">
|
||||||
<Icon :name="iconName" :class="iconClass" class="h-5 w-5" />
|
<Icon v-if="iconName" :name="iconName" :class="iconClass" class="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 flex-1 md:flex md:justify-between">
|
<div class="ml-3 flex-1 md:flex md:justify-between">
|
||||||
<p v-if="title" class="text-sm leading-5" :class="titleClass">
|
<p v-if="title" class="text-sm leading-5" :class="titleClass">
|
||||||
@@ -25,7 +25,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Icon from '../elements/Icon'
|
import type { PropType } from 'vue'
|
||||||
|
import type { RouteLocationNormalized } from 'vue-router'
|
||||||
|
import Icon from '../elements/Icon.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
variant: {
|
variant: {
|
||||||
@@ -36,7 +38,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
type: [String, Object],
|
type: [String, Object] as PropType<string | RouteLocationNormalized>,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
click: {
|
click: {
|
||||||
@@ -55,10 +57,10 @@ const props = defineProps({
|
|||||||
|
|
||||||
const iconName = computed(() => {
|
const iconName = computed(() => {
|
||||||
return ({
|
return ({
|
||||||
info: 'heroicons-solid:information-circle',
|
info: 'i-heroicons-information-circle',
|
||||||
warning: 'heroicons-solid:exclamation',
|
warning: 'i-heroicons-exclamation',
|
||||||
error: 'heroicons-solid:x-circle',
|
error: 'i-heroicons-x-circle',
|
||||||
success: 'heroicons-solid:check-circle'
|
success: 'i-heroicons-check-circle'
|
||||||
})[props.variant]
|
})[props.variant]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { DialogTitle, DialogDescription } from '@headlessui/vue'
|
import { DialogTitle, DialogDescription } from '@headlessui/vue'
|
||||||
import Modal from '../overlays/Modal'
|
import Icon from '../elements/Icon.vue'
|
||||||
import Button from '../elements/Button'
|
import Modal from '../overlays/Modal.vue'
|
||||||
import Icon from '../elements/Icon'
|
import Button from '../elements/Button.vue'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -55,7 +55,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
titleClass: {
|
titleClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.alertDialog.icon.title
|
default: () => $ui.alertDialog.title
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const props = defineProps({
|
|||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [String, Number, Boolean, Array],
|
type: [Boolean, Array],
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
|
|||||||
@@ -13,24 +13,23 @@
|
|||||||
:autocomplete="autocomplete"
|
:autocomplete="autocomplete"
|
||||||
:spellcheck="spellcheck"
|
:spellcheck="spellcheck"
|
||||||
:class="inputClass"
|
:class="inputClass"
|
||||||
@input="onInput($event.target.value)"
|
@input="onInput(($event.target as any).value)"
|
||||||
@focus="$emit('focus', $event)"
|
@focus="$emit('focus', $event)"
|
||||||
@blur="$emit('blur', $event)"
|
@blur="$emit('blur', $event)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<div v-if="isLeading" :class="iconLeadingWrapperClass">
|
<div v-if="isLeading" :class="iconLeadingWrapperClass">
|
||||||
<Icon :name="iconName" :class="iconClass" />
|
<Icon v-if="iconName" :name="iconName" :class="iconClass" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isTrailing" :class="iconTrailingWrapperClass">
|
<div v-if="isTrailing" :class="iconTrailingWrapperClass">
|
||||||
<Icon :name="iconName" :class="iconClass" />
|
<Icon v-if="iconName" :name="iconName" :class="iconClass" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from 'vue'
|
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon.vue'
|
||||||
import { classNames } from '../../utils'
|
import { classNames } from '../../utils'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
@@ -72,7 +71,7 @@ const props = defineProps({
|
|||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
spellcheck: {
|
spellcheck: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
@@ -129,11 +128,11 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
||||||
|
|
||||||
const input: Ref<HTMLInputElement> = ref(null)
|
const input = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
const autoFocus = () => {
|
const autoFocus = () => {
|
||||||
if (props.autofocus) {
|
if (props.autofocus) {
|
||||||
input.value.focus()
|
input.value?.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
:required="required"
|
:required="required"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:class="selectClass"
|
:class="selectClass"
|
||||||
@input="onInput($event.target.value)"
|
@input="onInput(($event.target as any).value)"
|
||||||
>
|
>
|
||||||
<template v-for="(option, index) in normalizedOptionsWithPlaceholder">
|
<template v-for="(option, index) in normalizedOptionsWithPlaceholder">
|
||||||
<optgroup
|
<optgroup
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
import Icon from '../elements/Icon'
|
|
||||||
import { classNames } from '../../utils'
|
import { classNames } from '../../utils'
|
||||||
|
import Icon from '../elements/Icon.vue'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -1,36 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<Combobox
|
<Combobox
|
||||||
v-slot="{ open }"
|
v-slot="{ open }"
|
||||||
|
:by="by"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
:nullable="nullable"
|
:nullable="nullable"
|
||||||
|
:disabled="disabled"
|
||||||
as="div"
|
as="div"
|
||||||
:class="wrapperClass"
|
:class="wrapperClass"
|
||||||
@update:model-value="$emit('update:modelValue', $event)"
|
@update:model-value="onUpdate"
|
||||||
>
|
>
|
||||||
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1">
|
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1" aria-hidden="true">
|
||||||
|
|
||||||
<ComboboxButton ref="trigger" as="div">
|
<ComboboxButton ref="trigger" v-slot="{ disabled: buttonDisabled }" as="div" role="button" class="inline-flex w-full">
|
||||||
<slot :open="open">
|
<slot :open="open" :disabled="buttonDisabled">
|
||||||
<button :class="selectCustomClass">
|
<button :class="selectCustomClass" :disabled="disabled" type="button">
|
||||||
<slot name="label">
|
<slot name="label">
|
||||||
<span v-if="modelValue" class="block truncate">{{ modelValue[textAttribute] }}</span>
|
<span v-if="modelValue" class="block truncate">{{ (modelValue as any)[textAttribute] }}</span>
|
||||||
<span v-else class="block truncate u-text-gray-400">{{ placeholder }}</span>
|
<span v-else class="block truncate u-text-gray-400">{{ placeholder }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
<slot name="icon">
|
<slot name="icon">
|
||||||
<span :class="iconWrapperClass">
|
<span :class="iconWrapperClass">
|
||||||
<Icon :name="icon" :class="iconClass" aria-hidden="true" />
|
<Icon v-if="icon" :name="icon" :class="iconClass" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
</button>
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
</ComboboxButton>
|
</ComboboxButton>
|
||||||
|
|
||||||
<div v-if="open" ref="container" :class="listContainerClass">
|
<div v-if="open" ref="container" :class="[listContainerClass, listWidthClass]">
|
||||||
<transition appear leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
|
<transition appear v-bind="listTransitionClass">
|
||||||
<ComboboxOptions static :class="listBaseClass">
|
<ComboboxOptions static :class="listBaseClass">
|
||||||
<ComboboxInput
|
<ComboboxInput
|
||||||
v-if="searchable"
|
v-if="searchable"
|
||||||
|
ref="searchInput"
|
||||||
:display-value="() => query"
|
:display-value="() => query"
|
||||||
name="q"
|
name="q"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
@@ -41,13 +44,13 @@
|
|||||||
/>
|
/>
|
||||||
<ComboboxOption
|
<ComboboxOption
|
||||||
v-for="(option, index) in filteredOptions"
|
v-for="(option, index) in filteredOptions"
|
||||||
v-slot="{ active, selected, disabled }"
|
v-slot="{ active, selected, disabled: optionDisabled }"
|
||||||
:key="index"
|
:key="index"
|
||||||
as="template"
|
as="template"
|
||||||
:value="option"
|
:value="option"
|
||||||
:disabled="option.disabled"
|
:disabled="option.disabled"
|
||||||
>
|
>
|
||||||
<li :class="resolveOptionClass({ active, selected, disabled })">
|
<li :class="resolveOptionClass({ active, selected, disabled: optionDisabled })">
|
||||||
<div :class="listOptionContainerClass">
|
<div :class="listOptionContainerClass">
|
||||||
<slot name="option" :option="option" :active="active" :selected="selected">
|
<slot name="option" :option="option" :active="active" :selected="selected">
|
||||||
<span class="block truncate">{{ option[textAttribute] }}</span>
|
<span class="block truncate">{{ option[textAttribute] }}</span>
|
||||||
@@ -55,7 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span v-if="selected" :class="resolveOptionIconClass({ active })">
|
<span v-if="selected" :class="resolveOptionIconClass({ active })">
|
||||||
<Icon :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
|
<Icon v-if="listOptionIcon" :name="listOptionIcon" :class="listOptionIconSizeClass" aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ComboboxOption>
|
</ComboboxOption>
|
||||||
@@ -81,7 +84,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import type { PropType, ComponentPublicInstance } from 'vue'
|
||||||
|
import { defu } from 'defu'
|
||||||
import {
|
import {
|
||||||
Combobox,
|
Combobox,
|
||||||
ComboboxButton,
|
ComboboxButton,
|
||||||
@@ -89,8 +94,10 @@ import {
|
|||||||
ComboboxOption,
|
ComboboxOption,
|
||||||
ComboboxInput
|
ComboboxInput
|
||||||
} from '@headlessui/vue'
|
} from '@headlessui/vue'
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon.vue'
|
||||||
import { classNames, usePopper } from '../../utils'
|
import { classNames } from '../../utils'
|
||||||
|
import { usePopper } from '../../composables/usePopper'
|
||||||
|
import type { PopperOptions } from '../../types'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -98,28 +105,22 @@ const props = defineProps({
|
|||||||
type: [String, Number, Object, Array],
|
type: [String, Number, Object, Array],
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
by: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Array,
|
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[] | string[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
placement: {
|
|
||||||
type: String,
|
|
||||||
default: 'bottom-end',
|
|
||||||
validator: (value: string) => {
|
|
||||||
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
strategy: {
|
|
||||||
type: String,
|
|
||||||
default: 'absolute',
|
|
||||||
validator: (value: string) => {
|
|
||||||
return ['absolute', 'fixed'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: {
|
required: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
multiple: {
|
multiple: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -157,7 +158,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => 'heroicons-solid:selector'
|
default: () => $ui.selectCustom.icon.name
|
||||||
},
|
},
|
||||||
iconBaseClass: {
|
iconBaseClass: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -182,10 +183,18 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.selectCustom.list.container
|
default: () => $ui.selectCustom.list.container
|
||||||
},
|
},
|
||||||
|
listWidthClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.selectCustom.list.width
|
||||||
|
},
|
||||||
listInputClass: {
|
listInputClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.selectCustom.list.input
|
default: () => $ui.selectCustom.list.input
|
||||||
},
|
},
|
||||||
|
listTransitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.selectCustom.list.transition
|
||||||
|
},
|
||||||
listOptionBaseClass: {
|
listOptionBaseClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.selectCustom.list.option.base
|
default: () => $ui.selectCustom.list.option.base
|
||||||
@@ -220,7 +229,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
listOptionIcon: {
|
listOptionIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => 'heroicons-solid:check'
|
default: () => $ui.selectCustom.list.option.icon.name
|
||||||
},
|
},
|
||||||
listOptionIconBaseClass: {
|
listOptionIconBaseClass: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -245,36 +254,21 @@ const props = defineProps({
|
|||||||
searchAttributes: {
|
searchAttributes: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
type: Object as PropType<PopperOptions>,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue', 'open', 'close'])
|
||||||
|
|
||||||
const [trigger, container] = usePopper({
|
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.selectCustom.popperOptions))
|
||||||
placement: props.placement,
|
|
||||||
strategy: props.strategy,
|
const [trigger, container] = usePopper(popperOptions.value)
|
||||||
modifiers: [{
|
|
||||||
name: 'offset',
|
|
||||||
options: {
|
|
||||||
offset: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'computeStyles',
|
|
||||||
options: {
|
|
||||||
gpuAcceleration: false,
|
|
||||||
adaptive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preventOverflow',
|
|
||||||
options: {
|
|
||||||
padding: 8
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
const query = ref('')
|
const query = ref('')
|
||||||
|
const searchInput = ref<ComponentPublicInstance<HTMLElement>>()
|
||||||
|
|
||||||
const selectCustomClass = computed(() => {
|
const selectCustomClass = computed(() => {
|
||||||
return classNames(
|
return classNames(
|
||||||
@@ -298,11 +292,11 @@ const iconClass = computed(() => {
|
|||||||
const filteredOptions = computed(() =>
|
const filteredOptions = computed(() =>
|
||||||
query.value === ''
|
query.value === ''
|
||||||
? props.options
|
? props.options
|
||||||
: props.options.filter((option: any) => {
|
: (props.options as any[]).filter((option: any) => {
|
||||||
return (props.searchAttributes?.length ? props.searchAttributes : [props.textAttribute]).some((searchAttribute: any) => {
|
return (props.searchAttributes?.length ? props.searchAttributes : [props.textAttribute]).some((searchAttribute: any) => {
|
||||||
return option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1
|
return typeof option === 'string' ? option.search(new RegExp(query.value, 'i')) !== -1 : (option[searchAttribute] && option[searchAttribute].search(new RegExp(query.value, 'i')) !== -1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const queryOption = computed(() => {
|
const queryOption = computed(() => {
|
||||||
@@ -313,6 +307,14 @@ const iconWrapperClass = classNames(
|
|||||||
$ui.selectCustom.icon.trailing.wrapper
|
$ui.selectCustom.icon.trailing.wrapper
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(container, (value) => {
|
||||||
|
if (value) {
|
||||||
|
emit('open')
|
||||||
|
} else {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function resolveOptionClass ({ active, selected, disabled }: { active: boolean, selected: boolean, disabled?: boolean }) {
|
function resolveOptionClass ({ active, selected, disabled }: { active: boolean, selected: boolean, disabled?: boolean }) {
|
||||||
return classNames(
|
return classNames(
|
||||||
props.listOptionBaseClass,
|
props.listOptionBaseClass,
|
||||||
@@ -328,6 +330,15 @@ function resolveOptionIconClass ({ active }: { active: boolean }) {
|
|||||||
active ? props.listOptionIconActiveClass : props.listOptionIconInactiveClass
|
active ? props.listOptionIconActiveClass : props.listOptionIconInactiveClass
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUpdate (event: any) {
|
||||||
|
if (query.value && searchInput.value?.$el) {
|
||||||
|
query.value = ''
|
||||||
|
// explicitly set input text because `ComboboxInput` `displayValue` is not reactive
|
||||||
|
searchInput.value.$el.value = ''
|
||||||
|
}
|
||||||
|
emit('update:modelValue', event)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:autocomplete="autocomplete"
|
:autocomplete="autocomplete"
|
||||||
:class="textareaClass"
|
:class="textareaClass"
|
||||||
@input="onInput($event.target.value)"
|
@input="onInput(($event.target as any).value)"
|
||||||
@focus="$emit('focus', $event)"
|
@focus="$emit('focus', $event)"
|
||||||
@blur="$emit('blur', $event)"
|
@blur="$emit('blur', $event)"
|
||||||
/>
|
/>
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from 'vue'
|
|
||||||
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
||||||
import { classNames } from '../../utils'
|
import { classNames } from '../../utils'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
@@ -95,11 +94,11 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
|
||||||
|
|
||||||
const textarea: Ref<HTMLTextAreaElement> = ref(null)
|
const textarea = ref<HTMLTextAreaElement | null>(null)
|
||||||
|
|
||||||
const autoFocus = () => {
|
const autoFocus = () => {
|
||||||
if (props.autofocus) {
|
if (props.autofocus) {
|
||||||
textarea.value.focus()
|
textarea.value?.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Switch } from '@headlessui/vue'
|
import { Switch } from '@headlessui/vue'
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon.vue'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { classNames } from '../../utils/'
|
import { classNames } from '../../utils'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { classNames } from '../../utils/'
|
import { classNames } from '../../utils'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -1,32 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<Combobox
|
<Combobox
|
||||||
|
ref="comboboxRef"
|
||||||
|
:by="by"
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
:nullable="nullable"
|
:nullable="nullable"
|
||||||
@update:model-value="onSelect"
|
@update:model-value="onSelect"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800">
|
<div :class="$ui.commandPalette.wrapper">
|
||||||
<div class="relative flex items-center">
|
<div v-show="searchable" class="relative flex items-center">
|
||||||
<UIcon :name="inputIcon" class="pointer-events-none absolute top-3.5 left-5 h-5 w-5 u-text-gray-400" aria-hidden="true" />
|
<Icon v-if="inputIcon" :name="inputIcon" :class="$ui.commandPalette.input.icon.base" aria-hidden="true" />
|
||||||
<ComboboxInput
|
<ComboboxInput
|
||||||
ref="comboboxInput"
|
ref="comboboxInput"
|
||||||
:value="query"
|
:value="query"
|
||||||
class="w-full h-12 pr-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 pl-[3.25rem] u-text-gray-900 focus:ring-0 sm:text-sm"
|
:class="$ui.commandPalette.input.base"
|
||||||
:placeholder="inputPlaceholder"
|
:placeholder="inputPlaceholder"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@change="query = $event.target.value"
|
@change="query = $event.target.value"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UButton v-if="closeIcon" :icon="closeIcon" variant="transparent" class="absolute right-3" @click="onClear" />
|
<Button
|
||||||
|
v-if="inputCloseIcon"
|
||||||
|
:icon="inputCloseIcon"
|
||||||
|
:class="$ui.commandPalette.input.close.base"
|
||||||
|
:size="$ui.commandPalette.input.close.size"
|
||||||
|
:variant="$ui.commandPalette.input.close.variant"
|
||||||
|
aria-label="Close"
|
||||||
|
@click="onClear"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ComboboxOptions v-if="groups.length" static hold class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2">
|
<ComboboxOptions
|
||||||
<CommandPaletteGroup v-for="group of groups" :key="group.key" :group="group" />
|
v-if="groups.length"
|
||||||
|
static
|
||||||
|
hold
|
||||||
|
as="div"
|
||||||
|
aria-label="Commands"
|
||||||
|
class="relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2"
|
||||||
|
>
|
||||||
|
<CommandPaletteGroup
|
||||||
|
v-for="group of groups"
|
||||||
|
:key="group.key"
|
||||||
|
:query="query"
|
||||||
|
:group="group"
|
||||||
|
:group-attribute="groupAttribute"
|
||||||
|
:command-attribute="commandAttribute"
|
||||||
|
>
|
||||||
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
||||||
|
<slot :name="name" v-bind="slotData" />
|
||||||
|
</template>
|
||||||
|
</CommandPaletteGroup>
|
||||||
</ComboboxOptions>
|
</ComboboxOptions>
|
||||||
|
|
||||||
<div v-else class="flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14">
|
<div v-else-if="placeholder" class="flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14">
|
||||||
<UIcon :name="emptyIcon" class="w-6 h-6 mx-auto u-text-gray-400" aria-hidden="true" />
|
<Icon v-if="emptyIcon" :name="emptyIcon" class="w-6 h-6 mx-auto u-text-gray-400 mb-4" aria-hidden="true" />
|
||||||
<p class="mt-4 text-sm u-text-gray-900">
|
<p class="text-sm text-center u-text-gray-900">
|
||||||
{{ query ? "We couldn't find any items with that term. Please try again." : "We couldn't find any items." }}
|
{{ query ? "We couldn't find any items with that term. Please try again." : "We couldn't find any items." }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,20 +63,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
import { Combobox, ComboboxInput, ComboboxOptions } from '@headlessui/vue'
|
import { Combobox, ComboboxInput, ComboboxOptions } from '@headlessui/vue'
|
||||||
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
|
import type { ComputedRef, PropType, ComponentPublicInstance } from 'vue'
|
||||||
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { useFuse } from '@vueuse/integrations/useFuse'
|
import { useFuse } from '@vueuse/integrations/useFuse'
|
||||||
|
import { groupBy, map } from 'lodash-es'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||||
import type { Group, Command } from '../../types/command-palette'
|
import type { Group, Command } from '../../types/command-palette'
|
||||||
|
import Icon from '../elements/Icon.vue'
|
||||||
|
import Button from '../elements/Button.vue'
|
||||||
import CommandPaletteGroup from './CommandPaletteGroup.vue'
|
import CommandPaletteGroup from './CommandPaletteGroup.vue'
|
||||||
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [String, Number, Object, Array],
|
type: [String, Number, Object, Array],
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
by: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
multiple: {
|
multiple: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -57,17 +94,21 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
searchable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
groups: {
|
groups: {
|
||||||
type: Array as PropType<Group[]>,
|
type: Array as PropType<Group[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
closeIcon: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
inputIcon: {
|
inputIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'heroicons-outline:search'
|
default: () => $ui.commandPalette.input.icon.name
|
||||||
|
},
|
||||||
|
inputCloseIcon: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.commandPalette.input.close.icon.name
|
||||||
},
|
},
|
||||||
inputPlaceholder: {
|
inputPlaceholder: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -75,11 +116,35 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
emptyIcon: {
|
emptyIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'heroicons-outline:search'
|
default: () => $ui.commandPalette.empty.icon.name
|
||||||
|
},
|
||||||
|
groupAttribute: {
|
||||||
|
type: String,
|
||||||
|
default: 'label'
|
||||||
|
},
|
||||||
|
commandAttribute: {
|
||||||
|
type: String,
|
||||||
|
default: 'label'
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Object as PropType<Partial<UseFuseOptions<Command>>>,
|
type: Object as PropType<Partial<UseFuseOptions<Command>>>,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
autoselect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
autoclear: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
debounce: {
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -87,47 +152,94 @@ const emit = defineEmits(['update:modelValue', 'close'])
|
|||||||
|
|
||||||
const query = ref('')
|
const query = ref('')
|
||||||
const comboboxInput = ref<ComponentPublicInstance<HTMLInputElement>>()
|
const comboboxInput = ref<ComponentPublicInstance<HTMLInputElement>>()
|
||||||
|
const comboboxApi = ref(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
activateFirstOption()
|
if (props.autoselect) {
|
||||||
|
activateFirstOption()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// @ts-expect-error internals
|
||||||
|
const popoverProvides = comboboxInput.value?.$.provides
|
||||||
|
if (!popoverProvides) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
|
||||||
|
comboboxApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
|
||||||
|
}, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
const options: ComputedRef<Partial<UseFuseOptions<Command>>> = computed(() => defu({}, props.options, {
|
const options: ComputedRef<Partial<UseFuseOptions<Command>>> = computed(() => defu({}, props.options, {
|
||||||
fuseOptions: {
|
fuseOptions: {
|
||||||
keys: ['label']
|
keys: [props.commandAttribute]
|
||||||
},
|
},
|
||||||
resultLimit: 12,
|
resultLimit: 12,
|
||||||
matchAllWhenSearchEmpty: true
|
matchAllWhenSearchEmpty: true
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const fuse = props.groups.reduce((acc, group) => {
|
const commands = computed(() => props.groups.filter(group => !group.search).reduce((acc, group) => {
|
||||||
const fuse = useFuse(query, group.commands, defu({}, group.options || {}, options.value))
|
return acc.concat(group.commands.map(command => ({ ...command, group: group.key })))
|
||||||
acc[group.key] = fuse
|
}, [] as Command[]))
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
const groups = computed(() => props.groups.map((group) => {
|
const searchResults = ref<{ [key: string]: any }>({})
|
||||||
return {
|
|
||||||
...group,
|
const { results } = useFuse(query, commands, options)
|
||||||
commands: fuse[group.key].results.value.map(result => result.item).slice(0, group.options?.resultLimit || options.value.resultLimit)
|
|
||||||
}
|
const groups = computed(() => ([
|
||||||
}).filter(group => group.commands.length))
|
...map(groupBy(results.value, command => command.item.group), (results, key) => {
|
||||||
|
const commands = results.map((result) => {
|
||||||
|
const { item, ...data } = result
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...props.groups.find(group => group.key === key),
|
||||||
|
commands: commands.slice(0, options.value.resultLimit)
|
||||||
|
} as Group
|
||||||
|
}),
|
||||||
|
...props.groups.filter(group => !!group.search).map(group => ({ ...group, commands: (searchResults.value[group.key] || []).slice(0, options.value.resultLimit) })).filter(group => group.commands.length)
|
||||||
|
]))
|
||||||
|
|
||||||
|
const debouncedSearch = useDebounceFn(async () => {
|
||||||
|
const searchableGroups = props.groups.filter(group => !!group.search)
|
||||||
|
|
||||||
|
await Promise.all(searchableGroups.map(async (group) => {
|
||||||
|
searchResults.value[group.key] = await group.search(query.value)
|
||||||
|
}))
|
||||||
|
}, props.debounce)
|
||||||
|
|
||||||
|
watch(query, () => {
|
||||||
|
debouncedSearch()
|
||||||
|
|
||||||
|
// Select first item on search changes
|
||||||
|
setTimeout(() => {
|
||||||
|
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L804
|
||||||
|
comboboxInput.value?.$el.dispatchEvent(new KeyboardEvent('keydown', { key: 'PageUp' }))
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
|
|
||||||
function activateFirstOption () {
|
function activateFirstOption () {
|
||||||
// hack combobox by using keyboard event
|
// hack combobox by using keyboard event
|
||||||
// https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L692
|
// https://github.com/tailwindlabs/headlessui/blob/6fa6074cd5d3a96f78a2d965392aa44101f5eede/packages/%40headlessui-vue/src/components/combobox/combobox.ts#L769
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
comboboxInput.value?.$el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
|
comboboxInput.value?.$el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }))
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelect (option: Command | Command[]) {
|
function onSelect (option: Command | Command[]) {
|
||||||
emit('update:modelValue', option)
|
emit('update:modelValue', option, { query: query.value })
|
||||||
|
|
||||||
// Clear input after selection
|
// Clear input after selection
|
||||||
if (!props.multiple) {
|
if (props.autoclear) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
query.value = ''
|
query.value = ''
|
||||||
}, 0)
|
}, 0)
|
||||||
@@ -141,6 +253,15 @@ function onClear () {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
query,
|
||||||
|
updateQuery: (q: string) => {
|
||||||
|
query.value = q
|
||||||
|
},
|
||||||
|
comboboxApi,
|
||||||
|
results
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<li class="p-2">
|
<div class="p-2" role="option">
|
||||||
<h2 v-if="group.label" class="px-3 my-2 text-xs font-semibold u-text-gray-900">
|
<h2 v-if="label" class="px-3 my-2 text-xs font-semibold u-text-gray-900">
|
||||||
{{ group.label }}
|
{{ label }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<ul class="text-sm u-text-gray-700">
|
<div class="text-sm u-text-gray-700" role="listbox" :aria-label="group[groupAttribute]">
|
||||||
<ComboboxOption
|
<ComboboxOption
|
||||||
v-for="(command, index) of group.commands"
|
v-for="(command, index) of group.commands"
|
||||||
:key="`${group.key}-${index}`"
|
:key="`${group.key}-${index}`"
|
||||||
@@ -13,49 +13,88 @@
|
|||||||
:disabled="command.disabled"
|
:disabled="command.disabled"
|
||||||
as="template"
|
as="template"
|
||||||
>
|
>
|
||||||
<li :class="['flex justify-between select-none items-center rounded-md px-3 py-2 u-text-gray-400 gap-3 relative', active && 'bg-gray-100 dark:bg-gray-800 u-text-gray-900', command.disabled ? 'cursor-not-allowed' : 'cursor-pointer']">
|
<div :class="['flex justify-between select-none items-center rounded-md px-3 py-2 gap-3 relative', active && 'bg-gray-100 dark:bg-gray-800 u-text-gray-900', command.disabled ? 'cursor-not-allowed' : 'cursor-pointer']">
|
||||||
<div class="flex items-center flex-1 gap-3 min-w-0">
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<UIcon v-if="command.icon" :name="command.icon" :class="['h-4 w-4', command.iconClass]" class="flex-shrink-0" aria-hidden="true" />
|
<slot :name="`${group.key}-icon`" :group="group" :command="command">
|
||||||
<UAvatar
|
<Icon v-if="command.icon" :name="command.icon" :class="['h-4 w-4 flex-shrink-0', active ? 'text-opacity-100 dark:text-opacity-100' : 'text-opacity-40 dark:text-opacity-40', command.iconClass || 'text-gray-900 dark:text-gray-50']" aria-hidden="true" />
|
||||||
v-else-if="command.avatar"
|
<Avatar
|
||||||
:src="command.avatar"
|
v-else-if="command.avatar"
|
||||||
:alt="command.label"
|
v-bind="{ size: 'xxxs', ...command.avatar }"
|
||||||
:rounded="false"
|
class="flex-shrink-0"
|
||||||
size="xxxs"
|
aria-hidden="true"
|
||||||
class="flex-shrink-0"
|
/>
|
||||||
/>
|
<span v-else-if="command.chip" class="flex-shrink-0 w-2 h-2 mx-1 rounded-full" :style="{ background: `#${command.chip}` }" />
|
||||||
<span v-else-if="command.chip" class="flex-shrink-0 w-2 h-2 rounded-full" :style="{ background: `#${command.chip}` }" />
|
</slot>
|
||||||
|
|
||||||
<div class="flex items-center flex-1 min-w-0 u-text-gray-400 gap-1.5" :class="{ 'opacity-50': command.disabled }">
|
<div class="flex items-center gap-1.5 min-w-0" :class="{ 'opacity-50': command.disabled }">
|
||||||
<span v-if="command.prefix">{{ command.prefix }}</span>
|
<slot :name="`${group.key}-command`" :group="group" :command="command">
|
||||||
<span class="u-text-gray-700 truncate">{{ command.label }}</span>
|
<span v-if="command.prefix" class="u-text-gray-400">{{ command.prefix }}</span>
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<span v-if="command.matches?.length" class="truncate" :class="{ 'flex-none': command.suffix }" v-html="highlight(command.matches[0])" />
|
||||||
|
<span v-else class="truncate" :class="{ 'flex-none': command.suffix }">{{ command[commandAttribute] }}</span>
|
||||||
|
<span v-if="command.suffix" class="u-text-gray-400 truncate">{{ command.suffix }}</span>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span v-if="selected" class="absolute inset-y-0 right-0 flex items-center pr-2 u-text-gray-900">
|
<Icon v-if="selected" :name="$ui.commandPalette.option.selected.icon.name" class="h-5 w-5 u-text-gray-900 flex-shrink-0" aria-hidden="true" />
|
||||||
<UIcon name="heroicons-outline:check" class="h-5 w-5" aria-hidden="true" />
|
<slot v-else-if="active && (group.active || $slots[`${group.key}-active`])" :name="`${group.key}-active`" :group="group" :command="command">
|
||||||
</span>
|
<span v-if="group.active" class="flex-shrink-0 u-text-gray-500">{{ group.active }}</span>
|
||||||
<span v-else-if="active && command.suffix" class="flex-none u-text-gray-500">{{ command.suffix }}</span>
|
</slot>
|
||||||
<span v-else-if="command.shortcuts?.length" class="flex-none text-xs font-semibold u-text-gray-500">
|
<slot v-else :name="`${group.key}-inactive`" :group="group" :command="command">
|
||||||
<kbd v-for="shortcut of command.shortcuts" :key="shortcut" class="font-sans">{{ shortcut }}</kbd>
|
<span v-if="command.shortcuts?.length" :class="$ui.commandPalette.option.shortcuts">
|
||||||
</span>
|
<kbd v-for="shortcut of command.shortcuts" :key="shortcut" class="font-sans">{{ shortcut }}</kbd>
|
||||||
</li>
|
</span>
|
||||||
|
<span v-else-if="!command.disabled && group.inactive" class="flex-shrink-0 u-text-gray-500">{{ group.inactive }}</span>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
</ComboboxOption>
|
</ComboboxOption>
|
||||||
</ul>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { ComboboxOption } from '@headlessui/vue'
|
import { ComboboxOption } from '@headlessui/vue'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
import Icon from '../elements/Icon.vue'
|
||||||
|
import Avatar from '../elements/Avatar.vue'
|
||||||
import type { Group } from '../../types/command-palette'
|
import type { Group } from '../../types/command-palette'
|
||||||
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
group: {
|
group: {
|
||||||
type: Object as PropType<Group>,
|
type: Object as PropType<Group>,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
groupAttribute: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
commandAttribute: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const label = computed(() => {
|
||||||
|
const label = props.group[props.groupAttribute]
|
||||||
|
|
||||||
|
return typeof label === 'function' ? label(props.query) : label
|
||||||
|
})
|
||||||
|
|
||||||
|
function highlight ({ indices, value }: { indices: number[][], value:string }, i = 1): string {
|
||||||
|
const pair = indices[indices.length - i]
|
||||||
|
if (!pair) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${highlight({ indices, value: value.substring(0, pair[0]) }, i + 1)}<mark>${value.substring(pair[0], pair[1] + 1)}</mark>${value.substring(pair[1] + 1)}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Link from '../elements/Link'
|
import type { PropType } from 'vue'
|
||||||
|
import type { RouteLocationNormalized } from 'vue-router'
|
||||||
|
import Link from '../elements/Link.vue'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
links: {
|
links: {
|
||||||
type: Array,
|
type: Array as PropType<{ to: RouteLocationNormalized, exact: boolean, label: string }[]>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Link from '../elements/Link'
|
import type { PropType } from 'vue'
|
||||||
|
import type { RouteLocationNormalized } from 'vue-router'
|
||||||
|
import Link from '../elements/Link.vue'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
links: {
|
links: {
|
||||||
type: Array,
|
type: Array as PropType<{ to: RouteLocationNormalized, exact: boolean, label: string }[]>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
v-slot="{ isActive }"
|
v-slot="{ isActive }"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-bind="link"
|
v-bind="link"
|
||||||
:class="[baseClass, spacingClass]"
|
:class="[baseClass, spacingClass].join(' ')"
|
||||||
:active-class="activeClass"
|
:active-class="activeClass"
|
||||||
:inactive-class="inactiveClass"
|
:inactive-class="inactiveClass"
|
||||||
@click="link.click && link.click()"
|
@click="link.click && link.click()"
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
<slot name="avatar" :link="link">
|
<slot name="avatar" :link="link">
|
||||||
<Avatar
|
<Avatar
|
||||||
v-if="link.avatar"
|
v-if="link.avatar"
|
||||||
v-bind="link.avatar"
|
v-bind="{ size: 'xs', ...link.avatar }"
|
||||||
:class="[avatarBaseClass, link.label && avatarSpacingClass]"
|
:class="[avatarBaseClass, link.label && avatarSpacingClass]"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
<slot name="icon" :link="link">
|
<slot name="icon" :link="link" :is-active="isActive">
|
||||||
<Icon
|
<Icon
|
||||||
v-if="link.icon"
|
v-if="link.icon"
|
||||||
:name="link.icon"
|
:name="link.icon"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<slot :link="link">
|
<slot :link="link">
|
||||||
<span v-if="link.label" class="truncate">{{ link.label }}</span>
|
<span v-if="link.label" class="truncate">{{ link.label }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
<slot name="badge" :link="link">
|
<slot name="badge" :link="link" :is-active="isActive">
|
||||||
<span v-if="link.badge" :class="[badgeBaseClass, isActive ? badgeActiveClass : badgeInactiveClass]">
|
<span v-if="link.badge" :class="[badgeBaseClass, isActive ? badgeActiveClass : badgeInactiveClass]">
|
||||||
{{ link.badge }}
|
{{ link.badge }}
|
||||||
</span>
|
</span>
|
||||||
@@ -38,14 +38,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Icon from '../elements/Icon'
|
import type { PropType } from 'vue'
|
||||||
import Link from '../elements/Link'
|
import type { RouteLocationNormalized } from 'vue-router'
|
||||||
import Avatar from '../elements/Avatar'
|
import Icon from '../elements/Icon.vue'
|
||||||
|
import Link from '../elements/Link.vue'
|
||||||
|
import Avatar from '../elements/Avatar.vue'
|
||||||
|
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
links: {
|
links: {
|
||||||
type: Array,
|
type: Array as PropType<{
|
||||||
|
to?: RouteLocationNormalized | string
|
||||||
|
exact?: boolean
|
||||||
|
label: string
|
||||||
|
icon?: string
|
||||||
|
iconClass?: string
|
||||||
|
avatar?: Partial<AvatarType>
|
||||||
|
click?: Function
|
||||||
|
badge?: string
|
||||||
|
}[]>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
|
|||||||
91
src/runtime/components/overlays/ContextMenu.vue
Normal file
91
src/runtime/components/overlays/ContextMenu.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="isOpen" ref="container" :class="[containerClass, widthClass]">
|
||||||
|
<transition appear v-bind="transitionClass">
|
||||||
|
<div :class="[baseClass, ringClass, roundedClass, shadowClass, backgroundClass]">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PropType, Ref } from 'vue'
|
||||||
|
import { computed, toRef } from 'vue'
|
||||||
|
import { defu } from 'defu'
|
||||||
|
import type { VirtualElement } from '@popperjs/core'
|
||||||
|
import { usePopper } from '../../composables/usePopper'
|
||||||
|
import type { PopperOptions } from '../../types'
|
||||||
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
virtualElement: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
wrapperClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.wrapper
|
||||||
|
},
|
||||||
|
containerClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.container
|
||||||
|
},
|
||||||
|
widthClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.width
|
||||||
|
},
|
||||||
|
backgroundClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.background
|
||||||
|
},
|
||||||
|
shadowClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.shadow
|
||||||
|
},
|
||||||
|
roundedClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.rounded
|
||||||
|
},
|
||||||
|
ringClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.ring
|
||||||
|
},
|
||||||
|
baseClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.contextMenu.base
|
||||||
|
},
|
||||||
|
transitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.contextMenu.transition
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
type: Object as PropType<PopperOptions>,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'close'])
|
||||||
|
|
||||||
|
const isOpen = computed({
|
||||||
|
get () {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set (value) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const virtualElement = toRef(props, 'virtualElement') as Ref<VirtualElement>
|
||||||
|
|
||||||
|
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.contextMenu.popperOptions))
|
||||||
|
|
||||||
|
const [, container] = usePopper(popperOptions.value, virtualElement)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default { name: 'UContextMenu' }
|
||||||
|
</script>
|
||||||
@@ -1,52 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<TransitionRoot :appear="appear" :show="isOpen" as="template">
|
<TransitionRoot :appear="appear" :show="isOpen" as="template">
|
||||||
<Dialog class="relative z-50" @close="close">
|
<Dialog :class="wrapperClass" @close="close">
|
||||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
<TransitionChild
|
||||||
<TransitionChild
|
v-if="overlay"
|
||||||
as="template"
|
as="template"
|
||||||
:appear="appear"
|
:appear="appear"
|
||||||
enter="ease-out duration-300"
|
v-bind="overlayTransition"
|
||||||
enter-from="opacity-0"
|
>
|
||||||
enter-to="opacity-100"
|
<div class="fixed inset-0 transition-opacity" :class="overlayBackgroundClass" />
|
||||||
leave="ease-in duration-200"
|
</TransitionChild>
|
||||||
leave-from="opacity-100"
|
|
||||||
leave-to="opacity-0"
|
|
||||||
>
|
|
||||||
<div class="fixed inset-0 transition-opacity" :class="overlayClass" />
|
|
||||||
</TransitionChild>
|
|
||||||
|
|
||||||
<div class="fixed inset-0 overflow-y-auto">
|
<div :class="innerClass" :style="innerStyle">
|
||||||
<div class="flex min-h-full items-end sm:items-center justify-center p-4 text-center">
|
<div :class="containerClass">
|
||||||
<TransitionChild
|
<TransitionChild
|
||||||
as="template"
|
as="template"
|
||||||
:appear="appear"
|
:appear="appear"
|
||||||
enter="ease-out duration-300"
|
v-bind="modalTransition"
|
||||||
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
>
|
||||||
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
<DialogPanel :class="modalClass">
|
||||||
leave="ease-in duration-200"
|
<Card
|
||||||
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
base-class=""
|
||||||
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
background-class=""
|
||||||
>
|
shadow-class=""
|
||||||
<DialogPanel :class="modalClass">
|
ring-class=""
|
||||||
<Card
|
rounded-class=""
|
||||||
base-class
|
v-bind="$attrs"
|
||||||
background-class
|
>
|
||||||
shadow-class
|
<template v-if="$slots.header" #header>
|
||||||
ring-class
|
<slot name="header" />
|
||||||
rounded-class
|
</template>
|
||||||
v-bind="$attrs"
|
<slot />
|
||||||
>
|
<template v-if="$slots.footer" #footer>
|
||||||
<template v-if="$slots.header" #header>
|
<slot name="footer" />
|
||||||
<slot name="header" />
|
</template>
|
||||||
</template>
|
</Card>
|
||||||
<slot />
|
</DialogPanel>
|
||||||
<template v-if="$slots.footer" #footer>
|
</TransitionChild>
|
||||||
<slot name="footer" />
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
</DialogPanel>
|
|
||||||
</TransitionChild>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -56,8 +45,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||||
import { classNames } from '../../utils/'
|
import { classNames } from '../../utils'
|
||||||
import Card from '../layout/Card'
|
import Card from '../layout/Card.vue'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -69,6 +58,22 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
wrapperClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.modal.wrapper
|
||||||
|
},
|
||||||
|
innerClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.modal.inner
|
||||||
|
},
|
||||||
|
innerStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
containerClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.modal.container
|
||||||
|
},
|
||||||
baseClass: {
|
baseClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.modal.base
|
default: () => $ui.modal.base
|
||||||
@@ -77,9 +82,17 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.modal.background
|
default: () => $ui.modal.background
|
||||||
},
|
},
|
||||||
overlayClass: {
|
overlay: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
overlayBackgroundClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.modal.overlay
|
default: () => $ui.modal.overlay.background
|
||||||
|
},
|
||||||
|
overlayTransitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.modal.overlay.transition
|
||||||
},
|
},
|
||||||
shadowClass: {
|
shadowClass: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -96,6 +109,14 @@ const props = defineProps({
|
|||||||
widthClass: {
|
widthClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.modal.width
|
default: () => $ui.modal.width
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
transitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.modal.transition
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -121,6 +142,22 @@ const modalClass = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const overlayTransition = computed(() => {
|
||||||
|
if (!props.transition) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.overlayTransitionClass
|
||||||
|
})
|
||||||
|
|
||||||
|
const modalTransition = computed(() => {
|
||||||
|
if (!props.transition) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.transitionClass
|
||||||
|
})
|
||||||
|
|
||||||
function close (value: boolean) {
|
function close (value: boolean) {
|
||||||
isOpen.value = value
|
isOpen.value = value
|
||||||
emit('close')
|
emit('close')
|
||||||
|
|||||||
@@ -1,48 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition
|
<transition appear v-bind="transitionClass">
|
||||||
appear
|
|
||||||
enter-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
|
|
||||||
enter-active-class="transition duration-300 ease-out transform"
|
|
||||||
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
|
|
||||||
leave-class="opacity-100"
|
|
||||||
leave-active-class="transition duration-100 ease-in"
|
|
||||||
leave-to-class="opacity-0"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="z-50 w-full bg-white rounded-lg shadow-lg pointer-events-auto dark:bg-gray-800"
|
:class="['z-50 w-full pointer-events-auto', backgroundClass, roundedClass, shadowClass]"
|
||||||
@mouseover="onMouseover"
|
@mouseover="onMouseover"
|
||||||
@mouseleave="onMouseleave"
|
@mouseleave="onMouseleave"
|
||||||
>
|
>
|
||||||
<div class="relative overflow-hidden rounded-lg ring-1 u-ring-gray-200">
|
<div :class="['relative overflow-hidden', roundedClass, ringClass]">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex">
|
<div class="flex gap-3" :class="{ 'items-start': description, 'items-center': !description }">
|
||||||
<div class="flex-shrink-0">
|
<div v-if="iconName" class="flex-shrink-0">
|
||||||
<Icon :name="iconName" class="w-6 h-6" :class="iconClass" />
|
<Icon :name="iconName" :class="iconClass" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 w-0 flex-1 pt-0.5">
|
<div class="w-0 flex-1">
|
||||||
<p class="text-sm font-medium leading-5 u-text-gray-900">
|
<p class="text-sm font-medium u-text-gray-900">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="description" class="mt-1 text-sm leading-5 u-text-gray-500">
|
<p v-if="description" class="mt-1 text-sm leading-5 u-text-gray-500">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</p>
|
</p>
|
||||||
<Button
|
|
||||||
v-if="undo"
|
<div v-if="description && actions.length" class="mt-3 flex items-center gap-6">
|
||||||
variant="white"
|
<button v-for="(action, index) of actions" :key="index" type="button" class="text-sm font-medium focus:outline-none text-primary-500 dark:text-primary-400 hover:text-primary-400 dark:hover:text-primary-500" @click.stop="onAction(action)">
|
||||||
size="xs"
|
{{ action.label }}
|
||||||
class="mt-2"
|
</button>
|
||||||
@click.stop="onUndo"
|
</div>
|
||||||
>
|
|
||||||
Undo
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0 ml-4">
|
<div class="flex-shrink-0 flex items-center gap-3">
|
||||||
|
<div v-if="!description && actions.length" class="flex items-center gap-2">
|
||||||
|
<button v-for="(action, index) of actions" :key="index" type="button" class="text-sm font-medium focus:outline-none text-primary-500 dark:text-primary-400 hover:text-primary-400 dark:hover:text-primary-500" @click.stop="onAction(action)">
|
||||||
|
{{ action.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="transition duration-150 ease-in-out u-text-gray-400 focus:outline-none hover:u-text-gray-500 focus:u-text-gray-500"
|
class="inline-flex transition duration-150 ease-in-out u-text-gray-400 focus:outline-none hover:u-text-gray-500 focus:u-text-gray-500"
|
||||||
@click.stop="onClose"
|
@click.stop="onClose"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Close</span>
|
<span class="sr-only">Close</span>
|
||||||
<Icon name="heroicons-solid:x" class="w-5 h-5" />
|
<Icon :name="$ui.notification.close.icon.name" class="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,10 +52,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, watchEffect } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, watchEffect } from 'vue'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
import Icon from '../elements/Icon'
|
import Icon from '../elements/Icon.vue'
|
||||||
import Button from '../elements/Button'
|
|
||||||
import { useTimer } from '../../composables/useTimer'
|
import { useTimer } from '../../composables/useTimer'
|
||||||
|
import { classNames } from '../../utils'
|
||||||
|
import type { ToastNotificationAction } from '../../types'
|
||||||
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
@@ -69,10 +66,9 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
default: null,
|
||||||
default: 'info',
|
|
||||||
validator (value: string) {
|
validator (value: string) {
|
||||||
return ['info', 'success', 'error', 'warning'].includes(value)
|
return Object.keys($ui.notification.type).includes(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
@@ -83,17 +79,48 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
backgroundClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.notification.background
|
||||||
|
},
|
||||||
|
shadowClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.notification.shadow
|
||||||
|
},
|
||||||
|
ringClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.notification.ring
|
||||||
|
},
|
||||||
|
roundedClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.notification.rounded
|
||||||
|
},
|
||||||
|
transitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.notification.transition
|
||||||
|
},
|
||||||
|
customClass: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
iconBaseClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.notification.icon.base
|
||||||
|
},
|
||||||
timeout: {
|
timeout: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 5000
|
default: 5000
|
||||||
},
|
},
|
||||||
undo: {
|
actions: {
|
||||||
type: Function,
|
type: Array as PropType<{
|
||||||
default: null
|
label: string,
|
||||||
|
click: Function
|
||||||
|
}[]>,
|
||||||
|
default: () => []
|
||||||
},
|
},
|
||||||
callback: {
|
callback: {
|
||||||
type: Function,
|
type: Function,
|
||||||
@@ -107,21 +134,14 @@ let timer: any = null
|
|||||||
const remaining = ref(props.timeout)
|
const remaining = ref(props.timeout)
|
||||||
|
|
||||||
const iconName = computed(() => {
|
const iconName = computed(() => {
|
||||||
return props.icon || ({
|
return props.icon || $ui.notification.type[props.type]
|
||||||
warning: 'heroicons-outline:exclamation-circle',
|
|
||||||
info: 'heroicons-outline:information-circle',
|
|
||||||
success: 'heroicons-outline:check-circle',
|
|
||||||
error: 'heroicons-outline:x-circle'
|
|
||||||
})[props.type]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const iconClass = computed(() => {
|
const iconClass = computed(() => {
|
||||||
return ({
|
return classNames(
|
||||||
warning: 'text-orange-400',
|
props.iconBaseClass,
|
||||||
info: 'text-blue-400',
|
$ui.notification.icon.color[props.type] || 'u-text-gray-400'
|
||||||
success: 'text-green-400',
|
)
|
||||||
error: 'text-red-400'
|
|
||||||
})[props.type] || 'u-text-gray-400'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const progressBarStyle = computed(() => {
|
const progressBarStyle = computed(() => {
|
||||||
@@ -153,13 +173,13 @@ function onClose () {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUndo () {
|
function onAction (action: ToastNotificationAction) {
|
||||||
if (timer) {
|
if (timer) {
|
||||||
timer.stop()
|
timer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.undo) {
|
if (action.click) {
|
||||||
props.undo()
|
action.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('close')
|
emit('close')
|
||||||
@@ -180,7 +200,9 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
timer.stop()
|
if (timer) {
|
||||||
|
timer.stop()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,14 @@
|
|||||||
<div class="fixed bottom-0 right-0 flex flex-col justify-end w-full z-[55] sm:w-96">
|
<div class="fixed bottom-0 right-0 flex flex-col justify-end w-full z-[55] sm:w-96">
|
||||||
<div v-if="notifications.length" class="px-4 py-6 space-y-3 overflow-y-auto sm:px-6">
|
<div v-if="notifications.length" class="px-4 py-6 space-y-3 overflow-y-auto sm:px-6">
|
||||||
<div
|
<div
|
||||||
v-for="(notification, index) of notifications"
|
v-for="notification of notifications"
|
||||||
v-show="index === notifications.length - 1"
|
|
||||||
:key="notification.id"
|
:key="notification.id"
|
||||||
>
|
>
|
||||||
<Notification
|
<Notification
|
||||||
v-bind="notification"
|
v-bind="notification"
|
||||||
:class="notification.click && 'cursor-pointer'"
|
:class="notification.click && 'cursor-pointer'"
|
||||||
@click="notification.click && notification.click(notification)"
|
@click="notification.click && notification.click(notification)"
|
||||||
@close="$toast.removeNotification(notification.id)"
|
@close="toast.removeNotification(notification.id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,11 +17,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Notification from './Notification'
|
import type { ToastNotification } from '../../types'
|
||||||
import { useNuxtApp, useState } from '#imports'
|
import { useToast } from '../../composables/useToast'
|
||||||
|
import Notification from './Notification.vue'
|
||||||
|
import { useState } from '#imports'
|
||||||
|
|
||||||
const { $toast } = useNuxtApp()
|
const toast = useToast()
|
||||||
const notifications = useState('notifications', () => [])
|
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<Popover v-slot="{ open, close }" :class="wrapperClass" @mouseleave="onMouseLeave">
|
<Popover v-slot="{ open, close }" :class="wrapperClass" @mouseleave="onMouseLeave">
|
||||||
<PopoverButton ref="trigger" as="div" @mouseover="onMouseOver">
|
<PopoverButton
|
||||||
|
ref="trigger"
|
||||||
|
as="div"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="inline-flex w-full"
|
||||||
|
role="button"
|
||||||
|
@mouseover="onMouseOver"
|
||||||
|
>
|
||||||
<slot :open="open" :close="close">
|
<slot :open="open" :close="close">
|
||||||
<button>Open</button>
|
<button :disabled="disabled">
|
||||||
|
Open
|
||||||
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
|
|
||||||
<div v-if="open" ref="container" :class="containerClass" @mouseover="onMouseOver">
|
<div v-if="open" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
|
||||||
<transition
|
<transition appear v-bind="transitionClass">
|
||||||
appear
|
<PopoverPanel :class="[baseClass, ringClass, roundedClass, shadowClass, backgroundClass]" static>
|
||||||
enter-active-class="transition ease-out duration-200"
|
|
||||||
enter-from-class="opacity-0 translate-y-1"
|
|
||||||
enter-to-class="opacity-100 translate-y-0"
|
|
||||||
leave-active-class="transition ease-in duration-150"
|
|
||||||
leave-from-class="opacity-100 translate-y-0"
|
|
||||||
leave-to-class="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<PopoverPanel :class="panelClass" static>
|
|
||||||
<slot name="panel" :open="open" :close="close" />
|
<slot name="panel" :open="open" :close="close" />
|
||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -25,21 +26,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ref } from 'vue'
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import { ref, onMounted } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
import { defu } from 'defu'
|
||||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
||||||
|
import { usePopper } from '../../composables/usePopper'
|
||||||
import { usePopper } from '../../utils'
|
import type { PopperOptions } from '../../types'
|
||||||
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
placement: {
|
|
||||||
type: String,
|
|
||||||
default: 'bottom'
|
|
||||||
},
|
|
||||||
strategy: {
|
|
||||||
type: String,
|
|
||||||
default: 'fixed'
|
|
||||||
},
|
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'click',
|
default: 'click',
|
||||||
@@ -47,67 +42,87 @@ const props = defineProps({
|
|||||||
return ['click', 'hover'].includes(value)
|
return ['click', 'hover'].includes(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'relative'
|
default: () => $ui.popover.wrapper
|
||||||
},
|
},
|
||||||
containerClass: {
|
containerClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'z-10 py-2'
|
default: () => $ui.popover.container
|
||||||
},
|
},
|
||||||
panelClass: {
|
widthClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: () => $ui.popover.width
|
||||||
|
},
|
||||||
|
baseClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.popover.base
|
||||||
|
},
|
||||||
|
backgroundClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.popover.background
|
||||||
|
},
|
||||||
|
shadowClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.popover.shadow
|
||||||
|
},
|
||||||
|
roundedClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.popover.rounded
|
||||||
|
},
|
||||||
|
ringClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.popover.ring
|
||||||
|
},
|
||||||
|
transitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.popover.transition
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
type: Object as PropType<PopperOptions>,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
openDelay: {
|
||||||
|
type: Number,
|
||||||
|
default: 50
|
||||||
|
},
|
||||||
|
closeDelay: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const [trigger, container] = usePopper({
|
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.popover.popperOptions))
|
||||||
placement: props.placement,
|
|
||||||
strategy: props.strategy,
|
|
||||||
modifiers: [{
|
|
||||||
name: 'offset',
|
|
||||||
options: {
|
|
||||||
offset: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'computeStyles',
|
|
||||||
options: {
|
|
||||||
gpuAcceleration: false,
|
|
||||||
adaptive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'preventOverflow',
|
|
||||||
options: {
|
|
||||||
padding: 8
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
const popoverApi: Ref<any> = ref(null)
|
const [trigger, container] = usePopper(popperOptions.value)
|
||||||
|
|
||||||
|
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/popover/popover.ts#L151
|
||||||
|
const popoverApi = ref<any>(null)
|
||||||
|
|
||||||
let openTimeout: NodeJS.Timeout | null = null
|
let openTimeout: NodeJS.Timeout | null = null
|
||||||
let closeTimeout: NodeJS.Timeout | null = null
|
let closeTimeout: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// @ts-expect-error internals
|
||||||
const popoverProvides = trigger.value?.$.provides
|
const popoverProvides = trigger.value?.$.provides
|
||||||
|
if (!popoverProvides) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
|
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
|
||||||
popoverApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
|
popoverApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
|
||||||
// stop trigger click propagation on hover
|
}, 200)
|
||||||
popoverApi.value.button.addEventListener('click', (e: Event) => {
|
|
||||||
if (props.mode === 'hover') {
|
|
||||||
e.stopPropagation()
|
|
||||||
}
|
|
||||||
}, true)
|
|
||||||
}, 0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function onMouseOver () {
|
function onMouseOver () {
|
||||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancel programmed closing
|
// cancel programmed closing
|
||||||
if (closeTimeout) {
|
if (closeTimeout) {
|
||||||
clearTimeout(closeTimeout)
|
clearTimeout(closeTimeout)
|
||||||
@@ -120,13 +135,14 @@ function onMouseOver () {
|
|||||||
openTimeout = openTimeout || setTimeout(() => {
|
openTimeout = openTimeout || setTimeout(() => {
|
||||||
popoverApi.value.togglePopover && popoverApi.value.togglePopover()
|
popoverApi.value.togglePopover && popoverApi.value.togglePopover()
|
||||||
openTimeout = null
|
openTimeout = null
|
||||||
}, 50)
|
}, props.openDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseLeave () {
|
function onMouseLeave () {
|
||||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancel programmed opening
|
// cancel programmed opening
|
||||||
if (openTimeout) {
|
if (openTimeout) {
|
||||||
clearTimeout(openTimeout)
|
clearTimeout(openTimeout)
|
||||||
@@ -139,7 +155,7 @@ function onMouseLeave () {
|
|||||||
closeTimeout = closeTimeout || setTimeout(() => {
|
closeTimeout = closeTimeout || setTimeout(() => {
|
||||||
popoverApi.value.closePopover && popoverApi.value.closePopover()
|
popoverApi.value.closePopover && popoverApi.value.closePopover()
|
||||||
closeTimeout = null
|
closeTimeout = null
|
||||||
}, 0)
|
}, props.closeDelay)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<TransitionRoot as="template" :show="isOpen">
|
<TransitionRoot as="template" :appear="appear" :show="isOpen">
|
||||||
<Dialog
|
<Dialog :class="[wrapperClass, { 'justify-end': side === 'right' }]" @close="close">
|
||||||
class="fixed inset-0 flex z-40"
|
|
||||||
:class="{
|
|
||||||
'justify-end': side === 'right'
|
|
||||||
}"
|
|
||||||
@close="isOpen = false"
|
|
||||||
>
|
|
||||||
<TransitionChild
|
<TransitionChild
|
||||||
|
v-if="overlay"
|
||||||
as="template"
|
as="template"
|
||||||
enter="transition-opacity ease-linear duration-300"
|
:appear="appear"
|
||||||
enter-from="opacity-0"
|
v-bind="overlayTransition"
|
||||||
enter-to="opacity-100"
|
|
||||||
leave="transition-opacity ease-linear duration-300"
|
|
||||||
leave-from="opacity-100"
|
|
||||||
leave-to="opacity-0"
|
|
||||||
>
|
>
|
||||||
<div class="fixed inset-0 transition-opacity" :class="overlayClass" />
|
<div class="fixed inset-0 transition-opacity" :class="overlayBackgroundClass" />
|
||||||
</TransitionChild>
|
</TransitionChild>
|
||||||
|
|
||||||
<TransitionChild
|
<TransitionChild
|
||||||
as="template"
|
as="template"
|
||||||
enter="transition ease-in-out duration-300 transform"
|
:appear="appear"
|
||||||
:enter-from="side === 'left' ? '-translate-x-full' : 'translate-x-full'"
|
v-bind="slideoverTransition"
|
||||||
enter-to="translate-x-0"
|
|
||||||
leave="transition ease-in-out duration-300 transform"
|
|
||||||
leave-from="translate-x-0"
|
|
||||||
:leave-to="side === 'left' ? '-translate-x-full' : 'translate-x-full'"
|
|
||||||
>
|
>
|
||||||
<DialogPanel :class="slideoverClass">
|
<DialogPanel :class="slideoverClass">
|
||||||
<div v-if="$slots.header" :class="headerClass">
|
<div v-if="$slots.header" :class="headerClass">
|
||||||
@@ -43,7 +30,7 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { WritableComputedRef, PropType } from 'vue'
|
import type { WritableComputedRef, PropType } from 'vue'
|
||||||
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
|
||||||
import { classNames } from '../../utils/'
|
import { classNames } from '../../utils'
|
||||||
import $ui from '#build/ui'
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -51,11 +38,19 @@ const props = defineProps({
|
|||||||
type: Boolean as PropType<boolean>,
|
type: Boolean as PropType<boolean>,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
appear: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
side: {
|
side: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'left',
|
default: 'left',
|
||||||
validator: (value: string) => ['left', 'right'].includes(value)
|
validator: (value: string) => ['left', 'right'].includes(value)
|
||||||
},
|
},
|
||||||
|
wrapperClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.slideover.wrapper
|
||||||
|
},
|
||||||
baseClass: {
|
baseClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.slideover.base
|
default: () => $ui.slideover.base
|
||||||
@@ -64,9 +59,17 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.slideover.background
|
default: () => $ui.slideover.background
|
||||||
},
|
},
|
||||||
overlayClass: {
|
overlay: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
overlayBackgroundClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.slideover.overlay
|
default: () => $ui.slideover.overlay.background
|
||||||
|
},
|
||||||
|
overlayTransitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.slideover.overlay.transition
|
||||||
},
|
},
|
||||||
widthClass: {
|
widthClass: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -75,9 +78,18 @@ const props = defineProps({
|
|||||||
headerClass: {
|
headerClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => $ui.slideover.header
|
default: () => $ui.slideover.header
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
transitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.slideover.transition
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'close'])
|
||||||
|
|
||||||
const isOpen: WritableComputedRef<boolean> = computed({
|
const isOpen: WritableComputedRef<boolean> = computed({
|
||||||
get () {
|
get () {
|
||||||
@@ -95,6 +107,33 @@ const slideoverClass = computed(() => {
|
|||||||
props.backgroundClass
|
props.backgroundClass
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const overlayTransition = computed(() => {
|
||||||
|
if (!props.transition) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.overlayTransitionClass
|
||||||
|
})
|
||||||
|
|
||||||
|
const slideoverTransition = computed(() => {
|
||||||
|
if (!props.transition) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enterFrom: props.side === 'left' ? '-translate-x-full' : 'translate-x-full',
|
||||||
|
enterTo: 'translate-x-0',
|
||||||
|
leaveFrom: 'translate-x-0',
|
||||||
|
leaveTo: props.side === 'left' ? '-translate-x-full' : 'translate-x-full',
|
||||||
|
...props.transitionClass
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function close (value: boolean) {
|
||||||
|
isOpen.value = value
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="trigger" :class="wrapperClass" @mouseover="open = true" @mouseleave="open = false">
|
<div ref="trigger" :class="wrapperClass" @mouseover="onMouseOver" @mouseleave="onMouseLeave">
|
||||||
<slot :open="open">
|
<slot :open="open">
|
||||||
Hover me
|
Hover me
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<div v-if="open" ref="container" :class="containerClass">
|
<div v-if="open && !prevent" ref="container" :class="[containerClass, widthClass]">
|
||||||
<transition
|
<transition appear v-bind="transitionClass">
|
||||||
appear
|
<div :class="[baseClass, backgroundClass, roundedClass, shadowClass, ringClass]">
|
||||||
enter-active-class="transition ease-out duration-200"
|
|
||||||
enter-from-class="opacity-0 translate-y-1"
|
|
||||||
enter-to-class="opacity-100 translate-y-0"
|
|
||||||
leave-active-class="transition ease-in duration-150"
|
|
||||||
leave-from-class="opacity-100 translate-y-0"
|
|
||||||
leave-to-class="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<div :class="tooltipClass">
|
|
||||||
<slot name="text">
|
<slot name="text">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
|
<span v-if="shortcuts?.length" :class="shortcutsClass">
|
||||||
|
<span class="mr-1 u-text-gray-700">·</span>
|
||||||
|
<kbd v-for="shortcut of shortcuts" :key="shortcut" class="flex items-center justify-center font-sans px-1 h-4 min-w-[16px] text-[10px] u-bg-gray-100 rounded u-text-gray-900">
|
||||||
|
{{ shortcut }}
|
||||||
|
</kbd>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,66 +24,122 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import { usePopper } from '../../utils'
|
import { computed, ref } from 'vue'
|
||||||
|
import { defu } from 'defu'
|
||||||
|
import { usePopper } from '../../composables/usePopper'
|
||||||
|
import type { PopperOptions } from '../../types'
|
||||||
|
import $ui from '#build/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
text: {
|
text: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
placement: {
|
prevent: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
default: 'bottom',
|
default: false
|
||||||
validator: (value: string) => {
|
|
||||||
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
strategy: {
|
shortcuts: {
|
||||||
type: String,
|
type: Array as PropType<string[]>,
|
||||||
default: 'fixed',
|
default: () => []
|
||||||
validator: (value: string) => {
|
|
||||||
return ['absolute', 'fixed'].includes(value)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
wrapperClass: {
|
wrapperClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'relative inline-flex'
|
default: () => $ui.tooltip.wrapper
|
||||||
},
|
},
|
||||||
containerClass: {
|
containerClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'z-10 py-2'
|
default: () => $ui.tooltip.container
|
||||||
},
|
},
|
||||||
tooltipClass: {
|
widthClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'flex items-center justify-center invisible w-auto h-6 max-w-xs px-2 space-x-1 truncate rounded shadow lg:visible u-bg-gray-800 truncate u-text-gray-50 text-xs'
|
default: () => $ui.tooltip.width
|
||||||
|
},
|
||||||
|
backgroundClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.tooltip.background
|
||||||
|
},
|
||||||
|
shadowClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.tooltip.shadow
|
||||||
|
},
|
||||||
|
ringClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.tooltip.ring
|
||||||
|
},
|
||||||
|
roundedClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.tooltip.rounded
|
||||||
|
},
|
||||||
|
baseClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.tooltip.base
|
||||||
|
},
|
||||||
|
transitionClass: {
|
||||||
|
type: Object,
|
||||||
|
default: () => $ui.tooltip.transition
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
type: Object as PropType<PopperOptions>,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
shortcutsClass: {
|
||||||
|
type: String,
|
||||||
|
default: () => $ui.tooltip.shortcuts
|
||||||
|
},
|
||||||
|
openDelay: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
closeDelay: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions, $ui.tooltip.popperOptions))
|
||||||
|
|
||||||
|
const [trigger, container] = usePopper(popperOptions.value)
|
||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
const [trigger, container] = usePopper({
|
|
||||||
placement: props.placement,
|
let openTimeout: NodeJS.Timeout | null = null
|
||||||
strategy: props.strategy,
|
let closeTimeout: NodeJS.Timeout | null = null
|
||||||
modifiers: [{
|
|
||||||
name: 'offset',
|
// Methods
|
||||||
options: {
|
|
||||||
offset: 0
|
function onMouseOver () {
|
||||||
}
|
// cancel programmed closing
|
||||||
},
|
if (closeTimeout) {
|
||||||
{
|
clearTimeout(closeTimeout)
|
||||||
name: 'computeStyles',
|
closeTimeout = null
|
||||||
options: {
|
}
|
||||||
gpuAcceleration: false,
|
// dropdown already open
|
||||||
adaptive: false
|
if (open.value) {
|
||||||
}
|
return
|
||||||
},
|
}
|
||||||
{
|
openTimeout = openTimeout || setTimeout(() => {
|
||||||
name: 'preventOverflow',
|
open.value = true
|
||||||
options: {
|
openTimeout = null
|
||||||
padding: 8
|
}, props.openDelay)
|
||||||
}
|
}
|
||||||
}]
|
|
||||||
})
|
function onMouseLeave () {
|
||||||
|
// cancel programmed opening
|
||||||
|
if (openTimeout) {
|
||||||
|
clearTimeout(openTimeout)
|
||||||
|
openTimeout = null
|
||||||
|
}
|
||||||
|
// dropdown already closed
|
||||||
|
if (!open.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
closeTimeout = closeTimeout || setTimeout(() => {
|
||||||
|
open.value = false
|
||||||
|
closeTimeout = null
|
||||||
|
}, props.closeDelay)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
109
src/runtime/composables/defineShortcuts.ts
Normal file
109
src/runtime/composables/defineShortcuts.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import type { Ref, ComputedRef } from 'vue'
|
||||||
|
import { logicAnd, logicNot } from '@vueuse/math'
|
||||||
|
import { computed, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import { useShortcuts } from './useShortcuts'
|
||||||
|
|
||||||
|
export interface ShortcutConfig {
|
||||||
|
handler: Function
|
||||||
|
usingInput?: string | boolean
|
||||||
|
whenever?: Ref<Boolean>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShortcutsConfig {
|
||||||
|
[key: string]: ShortcutConfig | Function
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Shortcut {
|
||||||
|
handler: Function
|
||||||
|
condition: ComputedRef<Boolean>
|
||||||
|
// KeyboardEvent attributes
|
||||||
|
key: string
|
||||||
|
ctrlKey: boolean
|
||||||
|
metaKey: boolean
|
||||||
|
shiftKey: boolean
|
||||||
|
altKey: boolean
|
||||||
|
// code?: string
|
||||||
|
// keyCode?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defineShortcuts = (config: ShortcutsConfig) => {
|
||||||
|
const { macOS, usingInput } = useShortcuts()
|
||||||
|
|
||||||
|
let shortcuts: Shortcut[] = []
|
||||||
|
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
const alphabeticalKey = /^[a-z]{1}$/i.test(e.key)
|
||||||
|
|
||||||
|
for (const shortcut of shortcuts) {
|
||||||
|
if (e.key.toLowerCase() !== shortcut.key) { continue }
|
||||||
|
if (e.metaKey !== shortcut.metaKey) { continue }
|
||||||
|
if (e.ctrlKey !== shortcut.ctrlKey) { continue }
|
||||||
|
// shift modifier is only checked in combination with alphabetical keys
|
||||||
|
// (shift with non-alphabetical keys would change the key)
|
||||||
|
if (alphabeticalKey && e.shiftKey !== shortcut.shiftKey) { continue }
|
||||||
|
// alt modifier changes the combined key anyways
|
||||||
|
// if (e.altKey !== shortcut.altKey) { continue }
|
||||||
|
|
||||||
|
if (shortcut.condition.value) {
|
||||||
|
e.preventDefault()
|
||||||
|
shortcut.handler()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Map config to full detailled shortcuts
|
||||||
|
shortcuts = Object.entries(config).map(([key, shortcutConfig]) => {
|
||||||
|
if (!shortcutConfig) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse key and modifiers
|
||||||
|
const keySplit = key.toLowerCase().split('_').map(k => k)
|
||||||
|
let shortcut: Partial<Shortcut> = {
|
||||||
|
key: keySplit.filter(k => !['meta', 'ctrl', 'shift', 'alt'].includes(k)).join('_'),
|
||||||
|
metaKey: keySplit.includes('meta'),
|
||||||
|
ctrlKey: keySplit.includes('ctrl'),
|
||||||
|
shiftKey: keySplit.includes('shift'),
|
||||||
|
altKey: keySplit.includes('alt')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Meta to Ctrl for non-MacOS
|
||||||
|
if (!macOS.value && shortcut.metaKey && !shortcut.ctrlKey) {
|
||||||
|
shortcut.metaKey = false
|
||||||
|
shortcut.ctrlKey = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve handler function
|
||||||
|
if (typeof shortcutConfig === 'function') {
|
||||||
|
shortcut.handler = shortcutConfig
|
||||||
|
} else if (typeof shortcutConfig === 'object') {
|
||||||
|
shortcut = { ...shortcut, handler: shortcutConfig.handler }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shortcut.handler) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.trace('[Shortcut] Invalid value')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create shortcut computed
|
||||||
|
const conditions = []
|
||||||
|
if (!(shortcutConfig as ShortcutConfig).usingInput) {
|
||||||
|
conditions.push(logicNot(usingInput))
|
||||||
|
} else if (typeof (shortcutConfig as ShortcutConfig).usingInput === 'string') {
|
||||||
|
conditions.push(computed(() => usingInput.value === (shortcutConfig as ShortcutConfig).usingInput))
|
||||||
|
}
|
||||||
|
shortcut.condition = logicAnd(...conditions, ...((shortcutConfig as ShortcutConfig).whenever || []))
|
||||||
|
|
||||||
|
return shortcut as Shortcut
|
||||||
|
}).filter(Boolean) as Shortcut[]
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyDown)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.removeEventListener('keydown', onKeyDown)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
import { defineNuxtPlugin } from '#app'
|
import { useToast } from './useToast'
|
||||||
import { ClipboardPlugin } from '../types'
|
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export function useCopyToClipboard () {
|
||||||
const { copy: copyToClipboard, isSupported } = useClipboard()
|
const { copy: copyToClipboard, isSupported } = useClipboard()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
function copy (text: string, success: { title?: string, description?: string } = {}, failure: { title?: string, description?: string } = {}) {
|
function copy (text: string, success: { title?: string, description?: string } = {}, failure: { title?: string, description?: string } = {}) {
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
@@ -15,9 +15,9 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nuxtApp.$toast.success(success)
|
toast.success(success)
|
||||||
}, function (e) {
|
}, function (e) {
|
||||||
nuxtApp.$toast.error({
|
toast.error({
|
||||||
...failure,
|
...failure,
|
||||||
description: failure.description || e.message
|
description: failure.description || e.message
|
||||||
})
|
})
|
||||||
@@ -25,16 +25,6 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provide: {
|
copy
|
||||||
clipboard: {
|
|
||||||
copy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
declare module '#app' {
|
|
||||||
interface NuxtApp {
|
|
||||||
$clipboard: ClipboardPlugin
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
82
src/runtime/composables/usePopper.ts
Normal file
82
src/runtime/composables/usePopper.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { ref, onMounted, watchEffect } from 'vue'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { popperGenerator, defaultModifiers, VirtualElement } from '@popperjs/core/lib/popper-lite'
|
||||||
|
import type { Instance } from '@popperjs/core'
|
||||||
|
import { omitBy, isUndefined } from 'lodash-es'
|
||||||
|
import flip from '@popperjs/core/lib/modifiers/flip'
|
||||||
|
import offset from '@popperjs/core/lib/modifiers/offset'
|
||||||
|
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow'
|
||||||
|
import computeStyles from '@popperjs/core/lib/modifiers/computeStyles'
|
||||||
|
import eventListeners from '@popperjs/core/lib/modifiers/eventListeners'
|
||||||
|
import { MaybeElement, unrefElement } from '@vueuse/core'
|
||||||
|
import type { PopperOptions } from '../types'
|
||||||
|
|
||||||
|
export const createPopper = popperGenerator({
|
||||||
|
defaultModifiers: [...defaultModifiers, offset, flip, preventOverflow, computeStyles, eventListeners]
|
||||||
|
})
|
||||||
|
|
||||||
|
export function usePopper ({
|
||||||
|
locked = false,
|
||||||
|
overflowPadding = 8,
|
||||||
|
offsetDistance = 8,
|
||||||
|
offsetSkid = 0,
|
||||||
|
gpuAcceleration = true,
|
||||||
|
adaptive = true,
|
||||||
|
scroll = true,
|
||||||
|
resize = true,
|
||||||
|
placement,
|
||||||
|
strategy
|
||||||
|
}: PopperOptions, virtualReference?: Ref<Element | VirtualElement>) {
|
||||||
|
const reference = ref<MaybeElement>(null)
|
||||||
|
const popper = ref<MaybeElement>(null)
|
||||||
|
const instance = ref<Instance | null>(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
watchEffect((onInvalidate) => {
|
||||||
|
if (!popper.value) { return }
|
||||||
|
if (!reference.value && !virtualReference?.value) { return }
|
||||||
|
|
||||||
|
const popperEl = unrefElement(popper)
|
||||||
|
const referenceEl = virtualReference?.value || unrefElement(reference)
|
||||||
|
|
||||||
|
// if (!(referenceEl instanceof HTMLElement)) { return }
|
||||||
|
if (!(popperEl instanceof HTMLElement)) { return }
|
||||||
|
if (!referenceEl) { return }
|
||||||
|
|
||||||
|
instance.value = createPopper(referenceEl, popperEl, omitBy({
|
||||||
|
placement,
|
||||||
|
strategy,
|
||||||
|
modifiers: [{
|
||||||
|
name: 'flip',
|
||||||
|
enabled: !locked
|
||||||
|
}, {
|
||||||
|
name: 'preventOverflow',
|
||||||
|
options: {
|
||||||
|
padding: overflowPadding
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'offset',
|
||||||
|
options: {
|
||||||
|
offset: [offsetSkid, offsetDistance]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'computeStyles',
|
||||||
|
options: {
|
||||||
|
adaptive,
|
||||||
|
gpuAcceleration
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: 'eventListeners',
|
||||||
|
options: {
|
||||||
|
scroll,
|
||||||
|
resize
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}, isUndefined))
|
||||||
|
|
||||||
|
onInvalidate(instance.value.destroy)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return [reference, popper, instance] as const
|
||||||
|
}
|
||||||
32
src/runtime/composables/useShortcuts.ts
Normal file
32
src/runtime/composables/useShortcuts.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { createSharedComposable, useActiveElement } from '@vueuse/core'
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
|
export const _useShortcuts = () => {
|
||||||
|
const macOS = computed(() => process.client && navigator && navigator.userAgent && navigator.userAgent.match(/Macintosh;/))
|
||||||
|
|
||||||
|
const metaSymbol = ref(' ')
|
||||||
|
|
||||||
|
const activeElement = useActiveElement()
|
||||||
|
const usingInput = computed(() => {
|
||||||
|
const usingInput = !!(activeElement.value?.tagName === 'INPUT' || activeElement.value?.tagName === 'TEXTAREA' || activeElement.value?.contentEditable === 'true')
|
||||||
|
|
||||||
|
if (usingInput) {
|
||||||
|
return ((activeElement.value as any)?.name as string) || true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
metaSymbol.value = macOS.value ? '⌘' : 'Ctrl'
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
macOS,
|
||||||
|
metaSymbol,
|
||||||
|
activeElement,
|
||||||
|
usingInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useShortcuts = createSharedComposable(_useShortcuts)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Ref, ref, computed } from 'vue-demi'
|
import { ref, computed } from 'vue-demi'
|
||||||
import { useTimestamp } from '@vueuse/core'
|
import { useTimestamp } from '@vueuse/core'
|
||||||
|
|
||||||
export function useTimer (cb: (...args: unknown[]) => any, interval: number) {
|
export function useTimer (cb: (...args: unknown[]) => any, interval: number) {
|
||||||
let timer: number | null = null
|
let timer: number | null = null
|
||||||
const timestamp = useTimestamp({ controls: true })
|
const timestamp = useTimestamp({ controls: true })
|
||||||
const startTime: Ref<number | null> = ref(null)
|
const startTime = ref<number | null>(null)
|
||||||
|
|
||||||
const remaining = computed(() => {
|
const remaining = computed(() => {
|
||||||
if (!startTime.value) {
|
if (!startTime.value) {
|
||||||
@@ -46,7 +46,7 @@ export function useTimer (cb: (...args: unknown[]) => any, interval: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resume () {
|
function resume () {
|
||||||
startTime.value += (Date.now() - timestamp.timestamp.value)
|
startTime.value = (startTime.value || 0) + (Date.now() - timestamp.timestamp.value)
|
||||||
timestamp.resume()
|
timestamp.resume()
|
||||||
set()
|
set()
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/runtime/composables/useToast.ts
Normal file
41
src/runtime/composables/useToast.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type { ToastNotification } from '../types'
|
||||||
|
import { useState } from '#imports'
|
||||||
|
|
||||||
|
export function useToast () {
|
||||||
|
const notifications = useState<ToastNotification[]>('notifications', () => [])
|
||||||
|
|
||||||
|
function addNotification (notification: Partial<ToastNotification>) {
|
||||||
|
const body = {
|
||||||
|
id: new Date().getTime().toString(),
|
||||||
|
...notification
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = notifications.value.findIndex((n: ToastNotification) => n.id === body.id)
|
||||||
|
if (index === -1) {
|
||||||
|
notifications.value.push(body as ToastNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeNotification (id: string) {
|
||||||
|
notifications.value = notifications.value.filter((n: ToastNotification) => n.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = (notification: Partial<ToastNotification> = {}) => addNotification({ type: 'success', ...notification })
|
||||||
|
|
||||||
|
const info = (notification: Partial<ToastNotification> = {}) => addNotification({ type: 'info', ...notification })
|
||||||
|
|
||||||
|
const warning = (notification: Partial<ToastNotification> = {}) => addNotification({ type: 'warning', ...notification })
|
||||||
|
|
||||||
|
const error = (notification: Partial<ToastNotification>) => addNotification({ type: 'error', title: 'An error occurred!', ...notification })
|
||||||
|
|
||||||
|
return {
|
||||||
|
addNotification,
|
||||||
|
removeNotification,
|
||||||
|
success,
|
||||||
|
info,
|
||||||
|
warning,
|
||||||
|
error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { Ref } from 'vue'
|
|
||||||
import { defineNuxtPlugin, useState } from '#app'
|
|
||||||
import { ToastNotification, ToastPlugin } from '../types'
|
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
|
||||||
const notifications: Ref<ToastNotification[]> = useState('notifications', () => [])
|
|
||||||
|
|
||||||
function addNotification (notification: Partial<ToastNotification>) {
|
|
||||||
const body = {
|
|
||||||
id: new Date().getTime().toString(),
|
|
||||||
...notification
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = notifications.value.findIndex((n: ToastNotification) => n.id === body.id)
|
|
||||||
if (index === -1) {
|
|
||||||
notifications.value.push(body as ToastNotification)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNotification (id: string) {
|
|
||||||
notifications.value = notifications.value.filter((n: ToastNotification) => n.id !== id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
provide: {
|
|
||||||
toast: {
|
|
||||||
addNotification,
|
|
||||||
removeNotification,
|
|
||||||
success ({ title, description, timeout }: { title?: string, description?: string, timeout?: number } = {}) {
|
|
||||||
addNotification({
|
|
||||||
type: 'success',
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
timeout
|
|
||||||
})
|
|
||||||
},
|
|
||||||
info ({ title, description, timeout }: { title?: string, description?: string, timeout?: number } = {}) {
|
|
||||||
addNotification({
|
|
||||||
type: 'info',
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
timeout
|
|
||||||
})
|
|
||||||
},
|
|
||||||
warning ({ title, description, timeout }: { title?: string, description?: string, timeout?: number } = {}) {
|
|
||||||
addNotification({
|
|
||||||
type: 'warning',
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
timeout
|
|
||||||
})
|
|
||||||
},
|
|
||||||
error ({ title = 'An error occurred!', description, timeout }: { title?: string, description?: string, timeout?: number } = {}) {
|
|
||||||
addNotification({
|
|
||||||
type: 'error',
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
timeout
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
declare module '#app' {
|
|
||||||
interface NuxtApp {
|
|
||||||
$toast: ToastPlugin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export default (variantColors: string[]) => {
|
export default function defaultPreset (variantColors: string[]) {
|
||||||
const button = {
|
const button = {
|
||||||
base: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 focus:ring-offset-white dark:focus:ring-offset-black',
|
base: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 focus:ring-offset-white dark:focus:ring-offset-black',
|
||||||
rounded: 'rounded-md',
|
rounded: 'rounded-md',
|
||||||
@@ -26,6 +26,14 @@ export default (variantColors: string[]) => {
|
|||||||
lg: 'p-2',
|
lg: 'p-2',
|
||||||
xl: 'p-3'
|
xl: 'p-3'
|
||||||
},
|
},
|
||||||
|
compact: {
|
||||||
|
xxs: 'p-1 sm:px-2',
|
||||||
|
xs: 'p-1.5 sm:px-2.5',
|
||||||
|
sm: 'p-2 sm:px-3',
|
||||||
|
md: 'p-2 sm:px-4',
|
||||||
|
lg: 'p-2 sm:px-4',
|
||||||
|
xl: 'p-3 sm:px-6'
|
||||||
|
},
|
||||||
variant: {
|
variant: {
|
||||||
...variantColors.reduce((acc: any, color: string) => {
|
...variantColors.reduce((acc: any, color: string) => {
|
||||||
acc[color] = `shadow-sm border border-transparent text-white bg-${color}-600 hover:bg-${color}-700 disabled:bg-${color}-600 focus:ring-2 focus:ring-offset-2 focus:ring-${color}-500`
|
acc[color] = `shadow-sm border border-transparent text-white bg-${color}-600 hover:bg-${color}-700 disabled:bg-${color}-600 focus:ring-2 focus:ring-offset-2 focus:ring-${color}-500`
|
||||||
@@ -40,7 +48,7 @@ export default (variantColors: string[]) => {
|
|||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
base: 'flex-shrink-0',
|
base: 'flex-shrink-0',
|
||||||
loading: 'heroicons-outline:refresh',
|
loading: 'i-heroicons-arrow-path',
|
||||||
size: {
|
size: {
|
||||||
xxs: 'h-3.5 w-3.5',
|
xxs: 'h-3.5 w-3.5',
|
||||||
xs: 'h-4 w-4',
|
xs: 'h-4 w-4',
|
||||||
@@ -57,6 +65,14 @@ export default (variantColors: string[]) => {
|
|||||||
md: '-ml-1 mr-2',
|
md: '-ml-1 mr-2',
|
||||||
lg: '-ml-1 mr-3',
|
lg: '-ml-1 mr-3',
|
||||||
xl: '-ml-1 mr-3'
|
xl: '-ml-1 mr-3'
|
||||||
|
},
|
||||||
|
compactSpacing: {
|
||||||
|
xxs: 'sm:-ml-0.5 sm:mr-1',
|
||||||
|
xs: 'sm:-ml-0.5 sm:mr-1.5',
|
||||||
|
sm: 'sm:-ml-0.5 sm:mr-2',
|
||||||
|
md: 'sm:-ml-1 sm:mr-2',
|
||||||
|
lg: 'sm:-ml-1 sm:mr-3',
|
||||||
|
xl: 'sm:-ml-1 sm:mr-3'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trailing: {
|
trailing: {
|
||||||
@@ -67,6 +83,14 @@ export default (variantColors: string[]) => {
|
|||||||
md: 'ml-2 -mr-1',
|
md: 'ml-2 -mr-1',
|
||||||
lg: 'ml-3 -mr-1',
|
lg: 'ml-3 -mr-1',
|
||||||
xl: 'ml-3 -mr-1'
|
xl: 'ml-3 -mr-1'
|
||||||
|
},
|
||||||
|
compactSpacing: {
|
||||||
|
xxs: 'sm:ml-1 sm:-mr-0.5',
|
||||||
|
xs: 'sm:ml-1.5 sm:-mr-0.5',
|
||||||
|
sm: 'sm:ml-2 sm:-mr-0.5',
|
||||||
|
md: 'sm:ml-2 sm:-mr-1',
|
||||||
|
lg: 'sm:ml-3 sm:-mr-1',
|
||||||
|
xl: 'sm:ml-3 sm:-mr-1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +168,7 @@ export default (variantColors: string[]) => {
|
|||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
base: 'u-text-gray-400',
|
base: 'u-text-gray-400',
|
||||||
loading: 'heroicons-outline:refresh',
|
loading: 'i-heroicons-arrow-path',
|
||||||
size: {
|
size: {
|
||||||
xxs: 'h-3 w-3',
|
xxs: 'h-3 w-3',
|
||||||
xs: 'h-4 w-4',
|
xs: 'h-4 w-4',
|
||||||
@@ -190,8 +214,13 @@ export default (variantColors: string[]) => {
|
|||||||
...select,
|
...select,
|
||||||
wrapper: 'relative',
|
wrapper: 'relative',
|
||||||
base: `${select.base} text-left cursor-default`,
|
base: `${select.base} text-left cursor-default`,
|
||||||
|
icon: {
|
||||||
|
name: 'i-heroicons-chevron-up-down-20-solid',
|
||||||
|
...select.icon
|
||||||
|
},
|
||||||
list: {
|
list: {
|
||||||
container: 'z-10 w-full py-1',
|
container: 'z-20',
|
||||||
|
width: 'w-full',
|
||||||
base: 'u-bg-white shadow-lg rounded-md ring-1 u-ring-gray-200 focus:outline-none overflow-y-auto py-1 max-h-60',
|
base: 'u-bg-white shadow-lg rounded-md ring-1 u-ring-gray-200 focus:outline-none overflow-y-auto py-1 max-h-60',
|
||||||
input: 'relative block w-full focus:ring-transparent text-sm px-4 py-2 u-text-gray-700 border-l-0 u-bg-white border-t-0 border-r-0 u-border-gray-200 focus:u-border-gray-200',
|
input: 'relative block w-full focus:ring-transparent text-sm px-4 py-2 u-text-gray-700 border-l-0 u-bg-white border-t-0 border-r-0 u-border-gray-200 focus:u-border-gray-200',
|
||||||
option: {
|
option: {
|
||||||
@@ -204,18 +233,27 @@ export default (variantColors: string[]) => {
|
|||||||
disabled: 'cursor-not-allowed opacity-50',
|
disabled: 'cursor-not-allowed opacity-50',
|
||||||
empty: 'text-sm u-text-gray-400 px-4 py-2',
|
empty: 'text-sm u-text-gray-400 px-4 py-2',
|
||||||
icon: {
|
icon: {
|
||||||
|
name: 'i-heroicons-check-20-solid',
|
||||||
base: 'absolute inset-y-0 right-0 flex items-center pr-4',
|
base: 'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||||
active: 'text-white',
|
active: 'text-white',
|
||||||
inactive: 'text-primary-600',
|
inactive: 'text-primary-600',
|
||||||
size: 'h-5 w-5'
|
size: 'h-5 w-5'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
leaveActiveClass: 'transition ease-in duration-100',
|
||||||
|
leaveFromClass: 'opacity-100',
|
||||||
|
leaveToClass: 'opacity-0'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
placement: 'bottom-end'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const radio = {
|
const radio = {
|
||||||
wrapper: 'relative flex items-start',
|
wrapper: 'relative flex items-start',
|
||||||
base: 'h-4 w-4 text-primary-600 focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 focus:ring-offset-white dark:focus:ring-offset-black u-border-gray-300 dark:checked:border-primary-600 disabled:opacity-50 disabled:cursor-not-allowed',
|
base: 'h-4 w-4 text-primary-600 focus:ring-2 focus:ring-offset-2 u-bg-white dark:checked:bg-current dark:checked:border-transparent focus:ring-primary-500 focus:ring-offset-white dark:focus:ring-offset-black u-border-gray-300 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||||
label: 'font-medium u-text-gray-700',
|
label: 'font-medium u-text-gray-700',
|
||||||
required: 'text-red-400',
|
required: 'text-red-400',
|
||||||
help: 'u-text-gray-500'
|
help: 'u-text-gray-500'
|
||||||
@@ -239,14 +277,35 @@ export default (variantColors: string[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const modal = {
|
const modal = {
|
||||||
|
wrapper: 'relative z-50',
|
||||||
|
inner: 'fixed inset-0 overflow-y-auto',
|
||||||
|
container: 'flex min-h-full items-end sm:items-center justify-center p-4 sm:p-0 text-center',
|
||||||
base: 'relative inline-block align-bottom text-left overflow-hidden transform transition-all sm:my-8 sm:align-middle w-full',
|
base: 'relative inline-block align-bottom text-left overflow-hidden transform transition-all sm:my-8 sm:align-middle w-full',
|
||||||
background: 'u-bg-white',
|
background: 'u-bg-white',
|
||||||
overlay: 'bg-gray-500/75 dark:bg-gray-600/75',
|
overlay: {
|
||||||
|
background: 'bg-gray-500/75 dark:bg-gray-600/75',
|
||||||
|
transition: {
|
||||||
|
enter: 'ease-out duration-300',
|
||||||
|
enterFrom: 'opacity-0',
|
||||||
|
enterTo: 'opacity-100',
|
||||||
|
leave: 'ease-in duration-200',
|
||||||
|
leaveFrom: 'opacity-100',
|
||||||
|
leaveTo: 'opacity-0'
|
||||||
|
}
|
||||||
|
},
|
||||||
border: '',
|
border: '',
|
||||||
ring: '',
|
ring: '',
|
||||||
rounded: 'rounded-lg',
|
rounded: 'rounded-lg',
|
||||||
shadow: 'shadow-xl',
|
shadow: 'shadow-xl',
|
||||||
width: 'sm:max-w-lg'
|
width: 'sm:max-w-lg',
|
||||||
|
transition: {
|
||||||
|
enter: 'ease-out duration-300',
|
||||||
|
enterFrom: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95',
|
||||||
|
enterTo: 'opacity-100 translate-y-0 sm:scale-100',
|
||||||
|
leave: 'ease-in duration-200',
|
||||||
|
leaveFrom: 'opacity-100 translate-y-0 sm:scale-100',
|
||||||
|
leaveTo: 'opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = {
|
const container = {
|
||||||
@@ -296,24 +355,44 @@ export default (variantColors: string[]) => {
|
|||||||
|
|
||||||
const alertDialog = {
|
const alertDialog = {
|
||||||
icon: {
|
icon: {
|
||||||
wrapper: 'h-12 w-12 sm:h-10 sm:w-10 bg-primary-100',
|
wrapper: 'mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-primary-100 sm:mx-0 sm:h-10 sm:w-10',
|
||||||
base: 'text-primary-600'
|
base: 'h-6 w-6 text-primary-600'
|
||||||
},
|
},
|
||||||
title: 'text-lg leading-6 font-medium text-gray-900',
|
title: 'text-lg leading-6 font-medium u-text-gray-900',
|
||||||
description: 'text-sm text-gray-500'
|
description: 'text-sm u-text-gray-500'
|
||||||
}
|
}
|
||||||
|
|
||||||
const dropdown = {
|
const dropdown = {
|
||||||
wrapper: 'relative inline-flex text-left',
|
wrapper: 'relative inline-flex text-left',
|
||||||
container: 'w-48 z-20 py-2',
|
container: 'z-20',
|
||||||
base: 'u-bg-white divide-y u-divide-gray-100 rounded-md ring-1 u-ring-gray-200 shadow-lg',
|
width: 'w-48',
|
||||||
|
background: 'u-bg-white',
|
||||||
|
shadow: 'shadow-lg',
|
||||||
|
rounded: 'rounded-md',
|
||||||
|
ring: 'ring-1 u-ring-gray-200',
|
||||||
|
base: 'focus:outline-none',
|
||||||
|
divide: 'divide-y u-divide-gray-100',
|
||||||
|
group: 'py-1',
|
||||||
item: {
|
item: {
|
||||||
base: 'group flex items-center gap-3 px-4 py-2 text-sm w-full',
|
base: 'group flex items-center gap-3 px-4 py-2 text-sm w-full',
|
||||||
active: 'u-bg-gray-100 u-text-gray-900',
|
active: 'u-bg-gray-100 u-text-gray-900',
|
||||||
inactive: 'u-text-gray-700',
|
inactive: 'u-text-gray-700',
|
||||||
disabled: 'cursor-not-allowed opacity-50',
|
disabled: 'cursor-not-allowed opacity-50',
|
||||||
icon: 'h-5 w-5 u-text-gray-400 group-hover:u-text-gray-500 flex-shrink-0',
|
icon: 'h-5 w-5 u-text-gray-400 group-hover:u-text-gray-500 flex-shrink-0',
|
||||||
avatar: '-m-0.5 group-hover:u-bg-gray-200 flex-shrink-0'
|
avatar: '-m-0.5 group-hover:u-bg-gray-200 flex-shrink-0',
|
||||||
|
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold u-text-gray-500 ml-auto'
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
enterActiveClass: 'transition duration-100 ease-out',
|
||||||
|
enterFromClass: 'transform scale-95 opacity-0',
|
||||||
|
enterToClass: 'transform scale-100 opacity-100',
|
||||||
|
leaveActiveClass: 'transition duration-75 ease-out',
|
||||||
|
leaveFromClass: 'transform scale-100 opacity-100',
|
||||||
|
leaveToClass: 'transform scale-95 opacity-0'
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
placement: 'bottom-end',
|
||||||
|
strategy: 'fixed'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,15 +456,165 @@ export default (variantColors: string[]) => {
|
|||||||
|
|
||||||
const avatarGroup = {
|
const avatarGroup = {
|
||||||
ring: 'ring-2 u-ring-white',
|
ring: 'ring-2 u-ring-white',
|
||||||
margin: '-ml-1.5 first:ml-0'
|
margin: '-mr-1.5 first:mr-0'
|
||||||
}
|
}
|
||||||
|
|
||||||
const slideover = {
|
const slideover = {
|
||||||
overlay: 'bg-gray-500/75 dark:bg-gray-600/75',
|
wrapper: 'fixed inset-0 flex z-40',
|
||||||
|
overlay: {
|
||||||
|
background: 'bg-gray-500/75 dark:bg-gray-600/75',
|
||||||
|
transition: {
|
||||||
|
enter: 'ease-in-out duration-500',
|
||||||
|
enterFrom: 'opacity-0',
|
||||||
|
enterTo: 'opacity-100',
|
||||||
|
leave: 'ease-in-out duration-500',
|
||||||
|
leaveFrom: 'opacity-100',
|
||||||
|
leaveTo: 'opacity-0'
|
||||||
|
}
|
||||||
|
},
|
||||||
base: 'relative flex-1 flex flex-col w-full focus:outline-none',
|
base: 'relative flex-1 flex flex-col w-full focus:outline-none',
|
||||||
background: 'u-bg-white',
|
background: 'u-bg-white',
|
||||||
width: 'max-w-md',
|
width: 'max-w-md',
|
||||||
header: 'flex items-center justify-between flex-shrink-0 px-4 sm:px-6 h-16 border-b u-border-gray-200'
|
header: 'flex items-center justify-between flex-shrink-0 px-4 sm:px-6 h-16 border-b u-border-gray-200',
|
||||||
|
transition: {
|
||||||
|
enter: 'transform transition ease-in-out duration-500 sm:duration-700',
|
||||||
|
leave: 'transform transition ease-in-out duration-500 sm:duration-700'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notification = {
|
||||||
|
background: 'u-bg-white',
|
||||||
|
shadow: 'shadow-lg',
|
||||||
|
rounded: 'rounded-lg',
|
||||||
|
ring: 'ring-1 u-ring-gray-200',
|
||||||
|
type: {
|
||||||
|
info: 'i-heroicons-information-circle',
|
||||||
|
success: 'i-heroicons-check-circle',
|
||||||
|
warning: 'i-heroicons-exclamation-circle',
|
||||||
|
error: 'i-heroicons-x-circle'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
base: 'w-6 h-6',
|
||||||
|
color: {
|
||||||
|
warning: 'text-orange-400',
|
||||||
|
info: 'text-blue-400',
|
||||||
|
success: 'text-green-400',
|
||||||
|
error: 'text-red-400'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
icon: {
|
||||||
|
name: 'i-heroicons-x-mark-20-solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
enterActiveClass: 'transform ease-out duration-300 transition',
|
||||||
|
enterFromClass: 'translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2',
|
||||||
|
enterToClass: 'translate-y-0 opacity-100 sm:translate-x-0',
|
||||||
|
leaveActiveClass: 'transition ease-in duration-100',
|
||||||
|
leaveFromClass: 'opacity-100',
|
||||||
|
leaveToClass: 'opacity-0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltip = {
|
||||||
|
wrapper: 'relative inline-flex',
|
||||||
|
container: 'z-20',
|
||||||
|
width: 'max-w-xs',
|
||||||
|
background: 'u-bg-white',
|
||||||
|
shadow: 'shadow',
|
||||||
|
rounded: 'rounded',
|
||||||
|
ring: 'ring-1 u-ring-gray-200',
|
||||||
|
base: 'invisible lg:visible h-6 px-2 py-1 text-xs font-normal truncate',
|
||||||
|
shortcuts: 'hidden md:inline-flex items-center justify-end flex-shrink-0 gap-0.5 ml-1',
|
||||||
|
transition: {
|
||||||
|
enterActiveClass: 'transition ease-out duration-200',
|
||||||
|
enterFromClass: 'opacity-0 translate-y-1',
|
||||||
|
enterToClass: 'opacity-100 translate-y-0',
|
||||||
|
leaveActiveClass: 'transition ease-in duration-150',
|
||||||
|
leaveFromClass: 'opacity-100 translate-y-0',
|
||||||
|
leaveToClass: 'opacity-0 translate-y-1'
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
strategy: 'fixed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const popover = {
|
||||||
|
wrapper: 'relative',
|
||||||
|
container: 'z-20',
|
||||||
|
width: '',
|
||||||
|
background: 'u-bg-white',
|
||||||
|
shadow: 'shadow-lg',
|
||||||
|
rounded: 'rounded-md',
|
||||||
|
ring: 'ring-1 u-ring-gray-200',
|
||||||
|
base: 'overflow-hidden focus:outline-none',
|
||||||
|
transition: {
|
||||||
|
enterActiveClass: 'transition ease-out duration-200',
|
||||||
|
enterFromClass: 'opacity-0 translate-y-1',
|
||||||
|
enterToClass: 'opacity-100 translate-y-0',
|
||||||
|
leaveActiveClass: 'transition ease-in duration-150',
|
||||||
|
leaveFromClass: 'opacity-100 translate-y-0',
|
||||||
|
leaveToClass: 'opacity-0 translate-y-1'
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
strategy: 'fixed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextMenu = {
|
||||||
|
wrapper: 'relative',
|
||||||
|
container: 'z-20',
|
||||||
|
width: '',
|
||||||
|
background: 'u-bg-white',
|
||||||
|
shadow: 'shadow-lg',
|
||||||
|
rounded: 'rounded-md',
|
||||||
|
ring: 'ring-1 u-ring-gray-200',
|
||||||
|
base: 'overflow-hidden focus:outline-none',
|
||||||
|
transition: {
|
||||||
|
enterActiveClass: 'transition ease-out duration-200',
|
||||||
|
enterFromClass: 'opacity-0 translate-y-1',
|
||||||
|
enterToClass: 'opacity-100 translate-y-0',
|
||||||
|
leaveActiveClass: 'transition ease-in duration-150',
|
||||||
|
leaveFromClass: 'opacity-100 translate-y-0',
|
||||||
|
leaveToClass: 'opacity-0 translate-y-1'
|
||||||
|
},
|
||||||
|
popperOptions: {
|
||||||
|
placement: 'bottom-start',
|
||||||
|
scroll: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandPalette = {
|
||||||
|
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800',
|
||||||
|
input: {
|
||||||
|
base: 'w-full h-12 pr-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 pl-[3.25rem] u-text-gray-900 focus:ring-0 sm:text-sm',
|
||||||
|
icon: {
|
||||||
|
base: 'pointer-events-none absolute top-3.5 left-5 h-5 w-5 u-text-gray-400',
|
||||||
|
name: 'i-heroicons-magnifying-glass'
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
base: 'absolute right-2',
|
||||||
|
variant: 'transparent',
|
||||||
|
size: 'md',
|
||||||
|
icon: {
|
||||||
|
name: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
icon: {
|
||||||
|
name: 'i-heroicons-magnifying-glass'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
selected: {
|
||||||
|
icon: {
|
||||||
|
name: 'i-heroicons-check-20-solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold u-text-gray-500'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -409,6 +638,13 @@ export default (variantColors: string[]) => {
|
|||||||
pills,
|
pills,
|
||||||
avatar,
|
avatar,
|
||||||
avatarGroup,
|
avatarGroup,
|
||||||
slideover
|
slideover,
|
||||||
|
notification,
|
||||||
|
tooltip,
|
||||||
|
popover,
|
||||||
|
contextMenu,
|
||||||
|
commandPalette
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DefaultPreset = ReturnType<typeof defaultPreset>
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a:focus {
|
||||||
|
@apply outline-primary-500;
|
||||||
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.u-bg-white { @apply bg-white dark:bg-black; }
|
.u-bg-white { @apply bg-white dark:bg-black; }
|
||||||
.u-bg-gray-50 { @apply bg-gray-50 dark:bg-gray-900; }
|
.u-bg-gray-50 { @apply bg-gray-50 dark:bg-gray-900; }
|
||||||
|
|||||||
10
src/runtime/types/avatar.d.ts
vendored
Normal file
10
src/runtime/types/avatar.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface Avatar {
|
||||||
|
src: string
|
||||||
|
alt: string
|
||||||
|
text: string
|
||||||
|
size: string
|
||||||
|
rounded: boolean
|
||||||
|
chip: boolean
|
||||||
|
chipVariant: string
|
||||||
|
chipPosition: string
|
||||||
|
}
|
||||||
15
src/runtime/types/command-palette.d.ts
vendored
15
src/runtime/types/command-palette.d.ts
vendored
@@ -1,20 +1,27 @@
|
|||||||
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||||
|
import type { FuseSortFunctionMatch, FuseSortFunctionMatchList } from 'fuse.js'
|
||||||
|
import type { Avatar } from './avatar'
|
||||||
|
|
||||||
export interface Command {
|
export interface Command {
|
||||||
|
id: string | number
|
||||||
prefix?: string
|
prefix?: string
|
||||||
suffix?: string
|
suffix?: string
|
||||||
label: string
|
|
||||||
icon?: string
|
icon?: string
|
||||||
iconClass?: string
|
iconClass?: string
|
||||||
avatar?: string
|
avatar?: Partial<Avatar>
|
||||||
chip?: string
|
chip?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
shortcuts?: string[]
|
shortcuts?: string[]
|
||||||
|
group?: string
|
||||||
|
score?: number
|
||||||
|
matches?: (FuseSortFunctionMatch | FuseSortFunctionMatchList)[]
|
||||||
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Group {
|
export interface Group {
|
||||||
key: string
|
key: string
|
||||||
label: string
|
active?: string
|
||||||
|
inactive?: string
|
||||||
commands: Command[]
|
commands: Command[]
|
||||||
options?: Partial<UseFuseOptions<Command>>
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/runtime/types/index.d.ts
vendored
3
src/runtime/types/index.d.ts
vendored
@@ -1,2 +1,5 @@
|
|||||||
|
export * from './avatar'
|
||||||
export * from './clipboard'
|
export * from './clipboard'
|
||||||
|
export * from './command-palette'
|
||||||
|
export * from './popper'
|
||||||
export * from './toast'
|
export * from './toast'
|
||||||
|
|||||||
14
src/runtime/types/popper.d.ts
vendored
Normal file
14
src/runtime/types/popper.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { Placement, PositioningStrategy } from '@popperjs/core'
|
||||||
|
|
||||||
|
export interface PopperOptions {
|
||||||
|
locked?: boolean
|
||||||
|
overflowPadding?: number
|
||||||
|
offsetDistance?: number
|
||||||
|
offsetSkid?: number
|
||||||
|
placement?: Placement
|
||||||
|
strategy?: PositioningStrategy
|
||||||
|
gpuAcceleration?: boolean
|
||||||
|
adaptive?: boolean
|
||||||
|
scroll?: boolean
|
||||||
|
resize?: boolean
|
||||||
|
}
|
||||||
15
src/runtime/types/toast.d.ts
vendored
15
src/runtime/types/toast.d.ts
vendored
@@ -1,3 +1,8 @@
|
|||||||
|
export interface ToastNotificationAction {
|
||||||
|
label: string,
|
||||||
|
click: Function
|
||||||
|
}
|
||||||
|
|
||||||
export interface ToastNotification {
|
export interface ToastNotification {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
@@ -5,13 +10,7 @@ export interface ToastNotification {
|
|||||||
type: string
|
type: string
|
||||||
icon?: string
|
icon?: string
|
||||||
timeout: number
|
timeout: number
|
||||||
undo?: Function
|
actions?: ToastNotificationAction[]
|
||||||
|
click?: Function
|
||||||
callback?: Function
|
callback?: Function
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToastPlugin {
|
|
||||||
addNotification: (notification: Partial<Notification>) => Notification
|
|
||||||
removeNotification: (id: string) => void
|
|
||||||
success: (options: { title?: string, description?: string }) => void
|
|
||||||
error: (options: { title?: string, description?: string }) => void
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
export * from './popper'
|
|
||||||
|
|
||||||
export function classNames (...classes: any[string]) {
|
export function classNames (...classes: any[string]) {
|
||||||
return classes.filter(Boolean).join(' ')
|
return classes.filter(Boolean).join(' ')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { ref, onMounted, watchEffect } from 'vue'
|
|
||||||
import { createPopper } from '@popperjs/core'
|
|
||||||
|
|
||||||
export function usePopper (options: object) {
|
|
||||||
const reference = ref(null)
|
|
||||||
const popper = ref(null)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
watchEffect((onInvalidate) => {
|
|
||||||
if (!popper.value) { return }
|
|
||||||
if (!reference.value) { return }
|
|
||||||
|
|
||||||
const popperEl = popper.value.$el || popper.value
|
|
||||||
const referenceEl = reference.value.$el || reference.value
|
|
||||||
|
|
||||||
if (!(referenceEl instanceof HTMLElement)) { return }
|
|
||||||
if (!(popperEl instanceof HTMLElement)) { return }
|
|
||||||
|
|
||||||
const { destroy } = createPopper(referenceEl, popperEl, options)
|
|
||||||
|
|
||||||
onInvalidate(destroy)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return [reference, popper]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user