mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
195 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01e0279a9 | ||
|
|
ccbf3e78b1 | ||
|
|
dcc0a6b3a9 | ||
|
|
bb4cd0b1b9 | ||
|
|
ae02f23a8c | ||
|
|
8caa78819a | ||
|
|
6fd5a70ac9 | ||
|
|
cfcd2f1371 | ||
|
|
c47f5e6a68 | ||
|
|
37f1a1b5ad | ||
|
|
0c2a5d98cf | ||
|
|
aabf1dd9eb | ||
|
|
c7c78cb47b | ||
|
|
3335a6a32c | ||
|
|
15f9db9420 | ||
|
|
aacb7e9841 | ||
|
|
3ded73194d | ||
|
|
82adedf764 | ||
|
|
192bf4c375 | ||
|
|
59fc14e93f | ||
|
|
0d83366427 | ||
|
|
cc65afafbd | ||
|
|
c7e0cb40e7 | ||
|
|
24434dc561 | ||
|
|
6c35ee9270 | ||
|
|
950b341696 | ||
|
|
24e7109959 | ||
|
|
be96824323 | ||
|
|
d5471f4d37 | ||
|
|
00b444b3eb | ||
|
|
76a0d61a0f | ||
|
|
6d79548ee8 | ||
|
|
f48ead6faf | ||
|
|
fd4c80acd4 | ||
|
|
3df917ae70 | ||
|
|
19a34c44da | ||
|
|
365c843fc0 | ||
|
|
cd430a4cad | ||
|
|
32ada0b28b | ||
|
|
034a95d3c9 | ||
|
|
530d8a8c27 | ||
|
|
939efba47c | ||
|
|
c43c212ae1 | ||
|
|
0404c871fb | ||
|
|
ebf5fd6aeb | ||
|
|
d5d250b8cf | ||
|
|
410d2351d6 | ||
|
|
949a476125 | ||
|
|
4586eed90c | ||
|
|
b21c55f5c4 | ||
|
|
fc11612a49 | ||
|
|
6de57aa1a0 | ||
|
|
6355b16156 | ||
|
|
26fc923ea4 | ||
|
|
28ee9179f5 | ||
|
|
4665563e6f | ||
|
|
f221b890a9 | ||
|
|
d1d8ab3c64 | ||
|
|
5b8ab168ba | ||
|
|
767a2bf3fc | ||
|
|
0c69385771 | ||
|
|
97b1a85ea1 | ||
|
|
9ce43ac68b | ||
|
|
fa05653f23 | ||
|
|
626409e101 | ||
|
|
f5c0030a19 | ||
|
|
11e00a10e4 | ||
|
|
b55a7c58f6 | ||
|
|
8c8bc0b751 | ||
|
|
a076cae4bf | ||
|
|
5facfee76c | ||
|
|
3c5256c462 | ||
|
|
a38ef00fb8 | ||
|
|
6da0db0113 | ||
|
|
56230ea915 | ||
|
|
126b5fcfd4 | ||
|
|
d3536d8768 | ||
|
|
59f62d322f | ||
|
|
b85a8e7203 | ||
|
|
8830d848fd | ||
|
|
cfce1524b2 | ||
|
|
f845e89a76 | ||
|
|
d9ca5d188a | ||
|
|
2429bcf5a7 | ||
|
|
f45f4a3e56 | ||
|
|
09e957e702 | ||
|
|
1ecd7cefde | ||
|
|
aafdfdb59c | ||
|
|
453ff6ca20 | ||
|
|
55832b6b99 | ||
|
|
6b93bbe5cd | ||
|
|
1402553145 | ||
|
|
5d84dfd05b | ||
|
|
7dc59a05ec | ||
|
|
4bd994985d | ||
|
|
c83d3b7147 | ||
|
|
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 |
6
.github/workflows/ci-dev.yml
vendored
6
.github/workflows/ci-dev.yml
vendored
@@ -15,10 +15,10 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [16]
|
||||
node: [18]
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -15,10 +15,10 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest] # macos-latest, windows-latest
|
||||
node: [16]
|
||||
node: [18]
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
|
||||
|
||||
230
CHANGELOG.md
230
CHANGELOG.md
@@ -2,6 +2,236 @@
|
||||
|
||||
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.
|
||||
|
||||
### [2.0.3](https://github.com/nuxtlabs/ui/compare/v2.0.2...v2.0.3) (2023-05-15)
|
||||
|
||||
### [2.0.2](https://github.com/nuxtlabs/ui/compare/v2.0.1...v2.0.2) (2023-05-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **LinkCustom:** handle `button` when no `to` prop ([c7c78cb](https://github.com/nuxtlabs/ui/commit/c7c78cb47b00963c8a9ea0c0599fbc7e128cff66))
|
||||
|
||||
### [2.0.1](https://github.com/nuxtlabs/ui/compare/v2.0.0...v2.0.1) (2023-05-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **app.config:** remove old `u-` classes ([939efba](https://github.com/nuxtlabs/ui/commit/939efba47ceb660e5448a3ea42f2acd71b9837ee))
|
||||
* **Avatar:** `gray` missing for `chipColor` ([fd4c80a](https://github.com/nuxtlabs/ui/commit/fd4c80acd4c70c7d378ebf780cd843115d8f434d))
|
||||
* **Avatar:** shrink chip ring ([ebf5fd6](https://github.com/nuxtlabs/ui/commit/ebf5fd6aeb2a5363e80457cf8245fbab5fbc17ca))
|
||||
* **Button:** `variant` validator takes color into account ([d1d8ab3](https://github.com/nuxtlabs/ui/commit/d1d8ab3c647d50f37832d1ae531550944d5aa8e3))
|
||||
* **colors:** missing `useNuxtApp` import ([76a0d61](https://github.com/nuxtlabs/ui/commit/76a0d61a0f7b3936b0eceff16e17bc6540fb946c))
|
||||
* **CommandPalette:** expose input ref to template ([192bf4c](https://github.com/nuxtlabs/ui/commit/192bf4c375293b16d952b94cc098a0260f47996a))
|
||||
* **CommandPalette:** put back searchable on `v-show` to input ref always exists ([aacb7e9](https://github.com/nuxtlabs/ui/commit/aacb7e98412d2973c6fc61d9cb3b6da9bd433eb0))
|
||||
* **CommandPalette:** wrong type usage ([4665563](https://github.com/nuxtlabs/ui/commit/4665563e6f9c4054cb1c859991369fe2cc844047))
|
||||
* **docs:** sticky search button `z-index` ([f48ead6](https://github.com/nuxtlabs/ui/commit/f48ead6faf6fd14deeff84ca7b25d6bb7fae6f12))
|
||||
* **Icon:** missing import ([cd430a4](https://github.com/nuxtlabs/ui/commit/cd430a4cad5143c5bd45c003086091f769e4f015))
|
||||
* **module:** remove `.ts` ext from app.config ([a076cae](https://github.com/nuxtlabs/ui/commit/a076cae4bfa387e1fd9800741b10702896c21ad2))
|
||||
* **Notifications:** missing `computed` from vue ([9ce43ac](https://github.com/nuxtlabs/ui/commit/9ce43ac68bcef3fb7fff8a9e317ad6d4a5ac2cb9))
|
||||
* prefix imported components ([0c69385](https://github.com/nuxtlabs/ui/commit/0c69385771ff1815cdcbff812962056da381a541))
|
||||
* put back app.config for hmr ([626409e](https://github.com/nuxtlabs/ui/commit/626409e1014ddcacaf6ee155830bd9862b335058))
|
||||
* remove augmentation of app ([#152](https://github.com/nuxtlabs/ui/issues/152)) ([f5c0030](https://github.com/nuxtlabs/ui/commit/f5c0030a198579e5929fd517b80e2e20c9bac769))
|
||||
* revert back to runtime app for hmr ([#153](https://github.com/nuxtlabs/ui/issues/153)) ([97b1a85](https://github.com/nuxtlabs/ui/commit/97b1a85ea12499289866a6baf15661c1f15279ce))
|
||||
* **Select:** move types from template ([fa05653](https://github.com/nuxtlabs/ui/commit/fa05653f23c4e9b1732eb4b9cd5e034f9bdca272))
|
||||
* **Toggle:** wrong `icon-off` positioning ([d5471f4](https://github.com/nuxtlabs/ui/commit/d5471f4d371b72df0ca5fac36e698066aca3864e))
|
||||
* update to fix type issues ([#151](https://github.com/nuxtlabs/ui/issues/151)) ([11e00a1](https://github.com/nuxtlabs/ui/commit/11e00a10e4781881e293e5fcd382331008c15346))
|
||||
* **VerticalNavigation:** improve focus ([034a95d](https://github.com/nuxtlabs/ui/commit/034a95d3c92eee9a54bd266e02d7446f7792d051))
|
||||
* **VerticalNavigation:** improve stacking context ([28ee917](https://github.com/nuxtlabs/ui/commit/28ee9179f5fbc006a47719ee632adf54f0e0ec4d))
|
||||
|
||||
## [2.0.0](https://github.com/nuxtlabs/ui/compare/v1.2.11...v2.0.0) (2023-05-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* rewrite to use app config and rework docs ([#143](https://github.com/nuxtlabs/ui/issues/143)) ([6da0db0](https://github.com/nuxtlabs/ui/commit/6da0db0113733df1a03220cb528bea862b553f37))
|
||||
|
||||
### [1.2.11](https://github.com/nuxtlabs/ui/compare/v1.2.10...v1.2.11) (2023-05-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **defineShortcuts:** use `useEventListener` ([#150](https://github.com/nuxtlabs/ui/issues/150)) ([59f62d3](https://github.com/nuxtlabs/ui/commit/59f62d322f07919d16a8d35340c3aa038cd09520))
|
||||
|
||||
### [1.2.10](https://github.com/nuxtlabs/ui/compare/v1.2.9...v1.2.10) (2023-04-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **CommandPalette:** typecheck ([cfce152](https://github.com/nuxtlabs/ui/commit/cfce1524b209212d9ce635b61376ff0d6bc3601b))
|
||||
|
||||
### [1.2.9](https://github.com/nuxtlabs/ui/compare/v1.2.8...v1.2.9) (2023-04-07)
|
||||
|
||||
### [1.2.8](https://github.com/nuxtlabs/ui/compare/v1.2.7...v1.2.8) (2023-04-04)
|
||||
|
||||
### [1.2.7](https://github.com/nuxtlabs/ui/compare/v1.2.6...v1.2.7) (2023-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **useTimer:** remaining after pause ([aafdfdb](https://github.com/nuxtlabs/ui/commit/aafdfdb59c365c542f93703dd52b4306ac935040))
|
||||
|
||||
### [1.2.6](https://github.com/nuxtlabs/ui/compare/v1.2.5...v1.2.6) (2023-04-04)
|
||||
|
||||
### [1.2.5](https://github.com/nuxtlabs/ui/compare/v1.2.4...v1.2.5) (2023-04-04)
|
||||
|
||||
### [1.2.4](https://github.com/nuxtlabs/ui/compare/v1.2.3...v1.2.4) (2023-04-04)
|
||||
|
||||
### [1.2.3](https://github.com/nuxtlabs/ui/compare/v1.2.2...v1.2.3) (2023-03-22)
|
||||
|
||||
### [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)
|
||||
|
||||
|
||||
|
||||
42
README.md
42
README.md
@@ -1,6 +1,6 @@
|
||||
# @nuxthq/ui
|
||||
|
||||
Components library as a Nuxt module using [TailwindCSS](https://tailwindcss.com).
|
||||
Components library as a Nuxt module using [TailwindCSS](https://tailwindcss.com) and [HeadlessUI](https://headlessui.com).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -29,43 +29,3 @@ If you want latest updates, please use `@nuxthq/ui-edge` in your `package.json`:
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `primary`
|
||||
|
||||
Define the primary variant. Defaults to `indigo`. You can specify your own object of colors like here:
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
buildModules: [
|
||||
'@nuxthq/ui'
|
||||
],
|
||||
ui: {
|
||||
primary: 'blue'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
- `prefix`
|
||||
|
||||
Define the prefix of the imported components. Defaults to `u`.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
buildModules: [
|
||||
'@nuxthq/ui'
|
||||
],
|
||||
ui: {
|
||||
prefix: 'tw'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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'))
|
||||
}
|
||||
}
|
||||
})
|
||||
108
docs/app.vue
108
docs/app.vue
@@ -1,95 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<nav class="u-bg-white border-b u-border-gray-200 fixed top-0 inset-x-0 z-10">
|
||||
<UContainer padded>
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
<NuxtLink to="/" class="block font-bold text-lg u-text-gray-900">
|
||||
@nuxthq/ui
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<Header />
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<ColorScheme placeholder="" tag="span">
|
||||
<UButton variant="transparent" :icon="colorMode.value === 'dark' ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark" />
|
||||
</ColorScheme>
|
||||
<UButton to="https://github.com/nuxtlabs/ui" target="_blank" variant="transparent" icon="fa-brands:github" />
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</nav>
|
||||
<UContainer>
|
||||
<div class="relative grid lg:grid-cols-10 lg:gap-8">
|
||||
<DocsAside class="lg:col-span-2" />
|
||||
|
||||
<UContainer class="mt-16">
|
||||
<div class="lg:grid lg:grid-cols-10 lg:gap-10 lg:relative">
|
||||
<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">
|
||||
<ul class="space-y-6">
|
||||
<li v-for="section of sections" :key="section">
|
||||
<h5 class="mb-3 uppercase tracking-wide font-semibold text-xs u-text-gray-900">
|
||||
{{ section.label }}
|
||||
</h5>
|
||||
<ul class="space-y-1.5">
|
||||
<li v-for="(link, index) of section.links" :key="index">
|
||||
<ULink
|
||||
:to="link.to"
|
||||
class="relative block text-sm rounded-md"
|
||||
active-class="text-primary-600"
|
||||
inactive-class="u-text-gray-500 hover:u-text-gray-700"
|
||||
exact
|
||||
>
|
||||
{{ link.label }}
|
||||
</ULink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<div class="relative lg:col-span-6 pt-8 pb-16">
|
||||
<DocsPageHeader />
|
||||
|
||||
<div class="space-y-6 sm:px-6 lg:px-0 lg:col-span-8 lg:py-12">
|
||||
<NuxtPage />
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-12">
|
||||
|
||||
<DocsPrevNext />
|
||||
</div>
|
||||
|
||||
<DocsToc class="lg:col-span-2 order-first lg:order-last" />
|
||||
</div>
|
||||
</UContainer>
|
||||
|
||||
<ClientOnly>
|
||||
<UNotifications />
|
||||
</ClientOnly>
|
||||
<DocsSearch />
|
||||
|
||||
<UNotifications />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const colorScheme = usePreferredColorScheme()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
// Computed
|
||||
|
||||
const href = computed(() => colorScheme.value === 'dark' ? '/icon-dark.svg' : '/icon-light.svg')
|
||||
const color = computed(() => colorMode.value === 'dark' ? '#18181b' : 'white')
|
||||
|
||||
// Head
|
||||
|
||||
useHead({
|
||||
title: '@nuxthq/ui',
|
||||
titleTemplate: title => title && title !== 'nuxthq/ui' ? `${title} - nuxthq/ui` : 'nuxthq/ui',
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' }
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' },
|
||||
{ key: 'theme-color', name: 'theme-color', content: color }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }
|
||||
{ rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' },
|
||||
{ rel: 'icon', type: 'image/svg+xml', href }
|
||||
],
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
},
|
||||
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-500 dark:text-gray-400 bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
|
||||
const colorMode = useColorMode()
|
||||
const toggleDark = () => {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
|
||||
const sections = [
|
||||
{ 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: '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: '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: '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' }] }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html.dark {
|
||||
@apply bg-black;
|
||||
}
|
||||
</style>
|
||||
|
||||
48
docs/app/router.options.ts
Normal file
48
docs/app/router.options.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { RouterConfig } from '@nuxt/schema'
|
||||
|
||||
function findHashPosition (hash): { el: any, behavior: ScrollBehavior, top: number } {
|
||||
const el = document.querySelector(hash)
|
||||
// vue-router does not incorporate scroll-margin-top on its own.
|
||||
if (el) {
|
||||
const top = parseFloat(getComputedStyle(el).scrollMarginTop)
|
||||
|
||||
return {
|
||||
el: hash,
|
||||
behavior: 'smooth',
|
||||
top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://router.vuejs.org/api/#routeroptions
|
||||
export default <RouterConfig>{
|
||||
scrollBehavior (to, from, savedPosition) {
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
// If history back
|
||||
if (savedPosition) {
|
||||
// Handle Suspense resolution
|
||||
return new Promise((resolve) => {
|
||||
nuxtApp.hooks.hookOnce('page:finish', () => {
|
||||
setTimeout(() => resolve(savedPosition), 50)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Scroll to heading on click
|
||||
if (to.hash) {
|
||||
return new Promise((resolve) => {
|
||||
if (to.path === from.path) {
|
||||
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
|
||||
} else {
|
||||
nuxtApp.hooks.hookOnce('page:finish', () => {
|
||||
setTimeout(() => resolve(findHashPosition(to.hash)), 50)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Scroll to top of window
|
||||
return { top: 0 }
|
||||
}
|
||||
}
|
||||
117
docs/components/Header.vue
Normal file
117
docs/components/Header.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<header class="sticky top-0 z-50 w-full backdrop-blur flex-none border-b border-gray-900/10 dark:border-gray-50/[0.06] bg-white/75 dark:bg-gray-900/75">
|
||||
<UContainer>
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
nuxthq/ui
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center -mr-1.5">
|
||||
<div class="mr-1.5 hidden lg:block">
|
||||
<ThemeSelect />
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
@click="openDocsSearch"
|
||||
/>
|
||||
|
||||
<ClientOnly>
|
||||
<UButton
|
||||
:icon="isDark ? 'i-heroicons-moon' : 'i-heroicons-sun'"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
aria-label="Theme"
|
||||
@click="isDark = !isDark"
|
||||
/>
|
||||
|
||||
<template #fallback>
|
||||
<div class="w-8 h-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<UButton
|
||||
to="https://github.com/nuxtlabs/ui"
|
||||
target="_blank"
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-simple-icons-github"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="lg:hidden"
|
||||
icon="i-heroicons-bars-3-20-solid"
|
||||
@click="isDialogOpen = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
|
||||
<TransitionRoot :show="isDialogOpen" as="template">
|
||||
<Dialog as="div" @close="isDialogOpen = false">
|
||||
<DialogPanel class="fixed inset-0 z-50 overflow-y-auto bg-white dark:bg-gray-900 lg:hidden">
|
||||
<div class="px-4 sm:px-6 sticky top-0 border-b border-gray-900/10 dark:border-gray-50/[0.06] bg-white/75 dark:bg-gray-900/75 backdrop-blur z-10">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/" class="flex items-end gap-2 font-bold text-xl text-gray-900 dark:text-white">
|
||||
<Logo class="w-8 h-8 text-primary-500 dark:text-primary-400" />
|
||||
|
||||
nuxthq/ui
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="flex -mr-1.5">
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
@click="isDialogOpen = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 py-4 sm:py-6">
|
||||
<ThemeSelect class="mb-4 sm:mb-6 w-full" />
|
||||
|
||||
<DocsAsideLinks @click="isDialogOpen = false" />
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Dialog, DialogPanel, TransitionRoot } from '@headlessui/vue'
|
||||
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDialogOpen = ref(false)
|
||||
|
||||
const isDark = computed({
|
||||
get () {
|
||||
return colorMode.value === 'dark'
|
||||
},
|
||||
set () {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
|
||||
function openDocsSearch () {
|
||||
isDialogOpen.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
isSearchModalOpen.value = true
|
||||
}, 100)
|
||||
}
|
||||
</script>
|
||||
5
docs/components/Logo.vue
Normal file
5
docs/components/Logo.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M504.908 750H839.476C850.103 750.001 860.542 747.229 869.745 741.963C878.948 736.696 886.589 729.121 891.9 719.999C897.211 710.876 900.005 700.529 900 689.997C899.995 679.465 897.193 669.12 891.873 660.002L667.187 274.289C661.876 265.169 654.237 257.595 645.036 252.329C635.835 247.064 625.398 244.291 614.773 244.291C604.149 244.291 593.711 247.064 584.511 252.329C575.31 257.595 567.67 265.169 562.36 274.289L504.908 372.979L392.581 179.993C387.266 170.874 379.623 163.301 370.42 158.036C361.216 152.772 350.777 150 340.151 150C329.525 150 319.086 152.772 309.883 158.036C300.679 163.301 293.036 170.874 287.721 179.993L8.12649 660.002C2.80743 669.12 0.00462935 679.465 5.72978e-06 689.997C-0.00461789 700.529 2.78909 710.876 8.10015 719.999C13.4112 729.121 21.0523 736.696 30.255 741.963C39.4576 747.229 49.8973 750.001 60.524 750H270.538C353.748 750 415.112 713.775 457.336 643.101L559.849 467.145L614.757 372.979L779.547 655.834H559.849L504.908 750ZM267.114 655.737L120.551 655.704L340.249 278.586L449.87 467.145L376.474 593.175C348.433 639.03 316.577 655.737 267.114 655.737Z" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
87
docs/components/ThemeSelect.vue
Normal file
87
docs/components/ThemeSelect.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="flex items-center shadow-sm">
|
||||
<USelectMenu
|
||||
v-model="primary"
|
||||
name="primary"
|
||||
class="w-full [&>div>button]:!rounded-r-none"
|
||||
appearance="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-start' }"
|
||||
:options="primaryOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${primary.hex}`}" />
|
||||
|
||||
{{ primary.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
<USelectMenu
|
||||
v-model="gray"
|
||||
name="gray"
|
||||
class="w-full [&>div>button]:!rounded-l-none [&>div>button]:-ml-px"
|
||||
appearance="gray"
|
||||
:ui="{ width: 'w-[194px]' }"
|
||||
:popper="{ placement: 'bottom-end' }"
|
||||
:options="grayOptions"
|
||||
>
|
||||
<template #label>
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${gray.hex}`}" />
|
||||
|
||||
{{ gray.text }}
|
||||
</template>
|
||||
|
||||
<template #option="{ option }">
|
||||
<span class="flex-shrink-0 h-3 w-3 rounded-full" :style="{ backgroundColor: `${option.hex}`}" />
|
||||
|
||||
{{ option.text }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const primaryCookie = useCookie('primary', { path: '/', default: () => appConfig.ui.primary })
|
||||
const grayCookie = useCookie('gray', { path: '/', default: () => appConfig.ui.gray })
|
||||
|
||||
watch(primaryCookie, (primary) => {
|
||||
appConfig.ui.primary = primary
|
||||
}, { immediate: true })
|
||||
|
||||
watch(grayCookie, (gray) => {
|
||||
appConfig.ui.gray = gray
|
||||
}, { immediate: true })
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryOptions = computed(() => useWithout(appConfig.ui.colors, 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get () {
|
||||
return primaryOptions.value.find(option => option.value === primaryCookie.value)
|
||||
},
|
||||
set (option) {
|
||||
primaryCookie.value = option.value
|
||||
}
|
||||
})
|
||||
|
||||
const grayOptions = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const gray = computed({
|
||||
get () {
|
||||
return grayOptions.value.find(option => option.value === grayCookie.value)
|
||||
},
|
||||
set (option) {
|
||||
grayCookie.value = option.value
|
||||
}
|
||||
})
|
||||
</script>
|
||||
33
docs/components/content/Alert.vue
Normal file
33
docs/components/content/Alert.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<component
|
||||
:is="to ? NuxtLink : 'div'"
|
||||
:to="to"
|
||||
class="block pl-4 pr-6 py-3 rounded-md !border !border-gray-200 dark:!border-gray-700 bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200 text-sm leading-6 my-5 last:mb-0 font-normal group relative prose-code:bg-gray-200 dark:prose-code:bg-gray-800"
|
||||
:class="[to ? 'hover:!border-primary-500 dark:hover:!border-primary-400 hover:text-primary-500 dark:hover:text-primary-400 border-dashed' : '']"
|
||||
>
|
||||
<UIcon v-if="!!to" name="i-heroicons-link-20-solid" class="w-3 h-3 absolute right-2 top-2 text-gray-400 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
|
||||
<UIcon v-if="icon" :name="icon" class="w-4 h-4 mr-2 inline-flex items-center align-text-top" :class="color" />
|
||||
|
||||
<ContentSlot :use="$slots.default" unwrap="p" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const NuxtLink = resolveComponent('NuxtLink')
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'text-primary-500 dark:text-primary-400'
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
18
docs/components/content/BadgeShortcut.vue
Normal file
18
docs/components/content/BadgeShortcut.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<UKbd class="!my-0 align-text-top">
|
||||
{{ shortcut }}
|
||||
</UKbd>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const { metaSymbol } = useShortcuts()
|
||||
|
||||
const shortcut = computed(() => props.value === 'meta' ? metaSymbol.value : props.value)
|
||||
</script>
|
||||
53
docs/components/content/CodeGroup.vue
Normal file
53
docs/components/content/CodeGroup.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div :selected-index="selectedIndex" @change="changeTab">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 border-b-0 rounded-t-md overflow-hidden -mb-px">
|
||||
<div
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
as="template"
|
||||
@click="selectedIndex = index"
|
||||
>
|
||||
<button
|
||||
class="px-4 py-2 focus:outline-none text-sm border-r border-r-gray-200 dark:border-r-gray-700 transition-colors"
|
||||
tabindex="-1"
|
||||
:class="[selectedIndex === index ? 'font-medium text-primary-500 dark:text-primary-400 bg-gray-50 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800']"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="[&>div>pre]:!rounded-t-none">
|
||||
<component :is="selectedTab.component" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const slots = useSlots()
|
||||
|
||||
const selectedIndex = ref(0)
|
||||
|
||||
// Computed
|
||||
|
||||
const tabs = computed(() => slots.default?.().map((slot, index) => {
|
||||
return {
|
||||
label: slot.props?.filename || slot.props?.label || `${index}`,
|
||||
component: slot
|
||||
}
|
||||
}) || [])
|
||||
|
||||
const selectedTab = computed(() => tabs.value.find((_, index) => index === selectedIndex.value))
|
||||
|
||||
// Methods
|
||||
|
||||
function changeTab (index) {
|
||||
selectedIndex.value = index
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false
|
||||
}
|
||||
</script>
|
||||
184
docs/components/content/ComponentCard.vue
Normal file
184
docs/components/content/ComponentCard.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="propsToSelect.length" class="relative flex border border-gray-200 dark:border-gray-700 rounded-t-md overflow-hidden not-prose">
|
||||
<div v-for="prop in propsToSelect" :key="prop.name" class="flex flex-col gap-0.5 justify-between py-1.5 font-medium bg-gray-50 dark:bg-gray-800 border-r border-r-gray-200 dark:border-r-gray-700">
|
||||
<label :for="prop.name" class="block text-xs px-3 font-medium text-gray-400 dark:text-gray-500 -my-px">{{ prop.label }}</label>
|
||||
<UCheckbox
|
||||
v-if="prop.type === 'boolean'"
|
||||
v-model="componentProps[prop.name]"
|
||||
:name="prop.name"
|
||||
appearance="none"
|
||||
class="justify-center"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-else-if="prop.type === 'string' && prop.options.length"
|
||||
v-model="componentProps[prop.name]"
|
||||
:options="prop.options"
|
||||
:name="prop.name"
|
||||
:label="componentProps[prop.name]"
|
||||
appearance="none"
|
||||
class="inline-flex"
|
||||
:ui="{ width: 'w-32 !-mt-px', rounded: 'rounded-b-md' }"
|
||||
:ui-select="{ custom: '!py-0' }"
|
||||
:popper="{ strategy: 'fixed', placement: 'bottom-start' }"
|
||||
/>
|
||||
<UInput
|
||||
v-else
|
||||
:model-value="componentProps[prop.name]"
|
||||
:type="prop.type === 'number' ? 'number' : 'text'"
|
||||
:name="prop.name"
|
||||
appearance="none"
|
||||
autocomplete="off"
|
||||
:ui="{ custom: '!py-0' }"
|
||||
@update:model-value="val => componentProps[prop.name] = prop.type === 'number' ? Number(val) : val"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex border border-b-0 border-gray-200 dark:border-gray-700 relative not-prose" :class="[{ 'p-4': padding }, propsToSelect.length ? 'border-t-0' : 'rounded-t-md', backgroundClass]">
|
||||
<component :is="name" v-model="vModel" v-bind="fullProps">
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
</component>
|
||||
</div>
|
||||
|
||||
<ContentRenderer :value="ast" class="[&>div>pre]:!rounded-t-none" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
padding: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
props: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
code: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
baseProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
ui: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
excludedProps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
|
||||
const baseProps = reactive({ ...props.baseProps })
|
||||
const componentProps = reactive({ ...props.props })
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
|
||||
// Computed
|
||||
|
||||
const ui = computed(() => ({ ...appConfig.ui[camelName], ...props.ui }))
|
||||
|
||||
const fullProps = computed(() => ({ ...props.baseProps, ...componentProps }))
|
||||
const vModel = computed({
|
||||
get: () => baseProps.modelValue,
|
||||
set: (value) => {
|
||||
baseProps.modelValue = value
|
||||
}
|
||||
})
|
||||
|
||||
const propsToSelect = computed(() => Object.keys(componentProps).map((key) => {
|
||||
if (props.excludedProps.includes(key)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
|
||||
const dottedKey = useKebabCase(key).replaceAll('-', '.')
|
||||
const keys = useGet(ui.value, dottedKey, {})
|
||||
let options = typeof keys === 'object' && Object.keys(keys)
|
||||
if (key.toLowerCase().endsWith('color')) {
|
||||
options = appConfig.ui.colors
|
||||
}
|
||||
|
||||
return {
|
||||
type: prop?.type || 'string',
|
||||
name: key,
|
||||
label: key === 'modelValue' ? 'value' : useCamelCase(key),
|
||||
options
|
||||
}
|
||||
}).filter(Boolean))
|
||||
|
||||
const code = computed(() => {
|
||||
let code = `\`\`\`html
|
||||
<${name}`
|
||||
for (const [key, value] of Object.entries(componentProps)) {
|
||||
if (value === 'undefined' || value === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
const prop = meta?.meta?.props?.find((prop: any) => prop.name === key)
|
||||
|
||||
code += ` ${(prop?.type === 'boolean' && value !== true) || typeof value === 'object' ? ':' : ''}${key === 'modelValue' ? 'value' : useKebabCase(key)}${prop?.type === 'boolean' && !!value && key !== 'modelValue' ? '' : `="${typeof value === 'object' ? renderObject(value) : value}"`}`
|
||||
}
|
||||
if (props.code) {
|
||||
const lineBreaks = (props.code.match(/\n/g) || []).length
|
||||
if (lineBreaks > 1) {
|
||||
code += `>
|
||||
${props.code}</${name}>`
|
||||
} else {
|
||||
code += `>${props.code}</${name}>`
|
||||
}
|
||||
} else {
|
||||
code += ' />'
|
||||
}
|
||||
code += `
|
||||
\`\`\`
|
||||
`
|
||||
return code
|
||||
})
|
||||
|
||||
function renderObject (obj: any) {
|
||||
if (Array.isArray(obj)) {
|
||||
return `[${obj.map(renderObject).join(', ')}]`
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
return `{ ${Object.entries(obj).map(([key, value]) => `${key}: ${renderObject(value)}`).join(', ')} }`
|
||||
}
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
return `'${obj}'`
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-ast-${JSON.stringify(componentProps)}`, () => transformContent('content:_markdown.md', code.value, {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
dark: 'material-palenight'
|
||||
}
|
||||
}
|
||||
}), { watch: [code] })
|
||||
</script>
|
||||
22
docs/components/content/ComponentExample.vue
Normal file
22
docs/components/content/ComponentExample.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="[&>div>pre]:!rounded-t-none">
|
||||
<div class="flex border border-gray-200 dark:border-gray-700 relative not-prose rounded-t-md" :class="[{ 'p-4': padding, 'rounded-b-md': !$slots.code, 'border-b-0': !!$slots.code }, backgroundClass]">
|
||||
<ContentSlot v-if="$slots.default" :use="$slots.default" />
|
||||
</div>
|
||||
|
||||
<ContentSlot v-if="$slots.code" :use="$slots.code" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
padding: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
backgroundClass: {
|
||||
type: String,
|
||||
default: 'bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
36
docs/components/content/ComponentPreset.vue
Normal file
36
docs/components/content/ComponentPreset.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<ContentRenderer :value="ast" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// @ts-expect-error
|
||||
import { transformContent } from '@nuxt/content/transformers'
|
||||
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const route = useRoute()
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
|
||||
const preset = appConfig.ui[camelName]
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-preset`, () => transformContent('content:_markdown.md', `
|
||||
\`\`\`json
|
||||
${JSON.stringify(preset, null, 2)}
|
||||
\`\`\`\
|
||||
`, {
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
dark: 'material-palenight'
|
||||
}
|
||||
}
|
||||
}))
|
||||
</script>
|
||||
55
docs/components/content/ComponentProps.vue
Normal file
55
docs/components/content/ComponentProps.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
<table class="table-fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-[25%]">
|
||||
Prop
|
||||
</th>
|
||||
<th class="w-[50%]">
|
||||
Default
|
||||
</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="prop in metaProps" :key="prop.name">
|
||||
<td class="relative flex-shrink-0">
|
||||
<code>{{ prop.name }}</code><span v-if="prop.required" class="font-bold text-red-500 dark:text-red-400 absolute top-0 ml-1">*</span>
|
||||
</td>
|
||||
<td>
|
||||
<code v-if="prop.default">{{ prop.default }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<a v-if="prop.name === 'ui'" href="#preset">
|
||||
<code>{{ prop.type }}</code>
|
||||
</a>
|
||||
<code v-else class="break-all">
|
||||
{{ prop.type }}
|
||||
</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
|
||||
const metaProps = computed(() => useSortBy(meta?.meta?.props || [], [
|
||||
prop => ['string', 'number', 'boolean', 'any'].indexOf(prop.type)
|
||||
]))
|
||||
</script>
|
||||
34
docs/components/content/ComponentSlots.vue
Normal file
34
docs/components/content/ComponentSlots.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slot</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="slot in (meta.meta.slots as any[])" :key="slot.name">
|
||||
<td class="whitespace-nowrap">
|
||||
<code>{{ slot.name }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
slug: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const slug = props.slug || route.params.slug[1]
|
||||
const camelName = useCamelCase(slug)
|
||||
const name = `U${useUpperFirst(camelName)}`
|
||||
|
||||
const meta = await fetchComponentMeta(name)
|
||||
</script>
|
||||
21
docs/components/content/Placeholder.vue
Normal file
21
docs/components/content/Placeholder.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded border border-dashed border-gray-400 dark:border-gray-500 opacity-75">
|
||||
<svg class="absolute inset-0 h-full w-full stroke-gray-900/10 dark:stroke-white/10" fill="none">
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e"
|
||||
x="0"
|
||||
y="0"
|
||||
width="10"
|
||||
height="10"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M-3 13 15-5M-5 5l18-18M-1 21 17 3" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect stroke="none" fill="url(#pattern-5c1e4f0e-62d5-498b-8ff0-cf77bb448c8e)" width="100%" height="100%" />
|
||||
</svg>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
13
docs/components/content/examples/CardExample.vue
Normal file
13
docs/components/content/examples/CardExample.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-32" />
|
||||
|
||||
<template #footer>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb' },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox' },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz' },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham' },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const selected = ref([people[3]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
nullable
|
||||
:autoselect="false"
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const users = [
|
||||
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } },
|
||||
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' } },
|
||||
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' } }
|
||||
]
|
||||
|
||||
const actions = [
|
||||
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file'), shortcuts: ['⌘', 'N'] },
|
||||
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder'), shortcuts: ['⌘', 'F'] },
|
||||
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag'), shortcuts: ['⌘', 'H'] },
|
||||
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label'), shortcuts: ['⌘', 'L'] }
|
||||
]
|
||||
|
||||
const groups = computed(() => commandPaletteRef.value?.query
|
||||
? [{
|
||||
key: 'users',
|
||||
commands: users
|
||||
}]
|
||||
: [{
|
||||
key: 'recent',
|
||||
label: 'Recent searches',
|
||||
commands: users.slice(0, 1)
|
||||
}, {
|
||||
key: 'actions',
|
||||
commands: actions
|
||||
}])
|
||||
|
||||
function onSelect (option) {
|
||||
if (option.click) {
|
||||
option.click()
|
||||
} else if (option.to) {
|
||||
router.push(option.to)
|
||||
} else if (option.href) {
|
||||
window.open(option.href, '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette ref="commandPaletteRef" :groups="groups" :autoselect="false" @update:model-value="onSelect" />
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb' },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox' },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz' },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham' },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
nullable
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
/>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
5
docs/components/content/examples/ContainerExample.vue
Normal file
5
docs/components/content/examples/ContainerExample.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UContainer>
|
||||
<Placeholder class="h-32" />
|
||||
</UContainer>
|
||||
</template>
|
||||
35
docs/components/content/examples/ContextMenuExample.vue
Normal file
35
docs/components/content/examples/ContextMenuExample.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="onContextMenu">
|
||||
<Placeholder class="h-96 select-none w-full flex items-center justify-center">
|
||||
Right click here
|
||||
</Placeholder>
|
||||
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
34
docs/components/content/examples/DropdownExample.vue
Normal file
34
docs/components/content/examples/DropdownExample.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E']
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D']
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
}, {
|
||||
label: 'Move',
|
||||
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||
}], [{
|
||||
label: 'Delete',
|
||||
icon: 'i-heroicons-trash-20-solid',
|
||||
shortcuts: ['⌘', 'D']
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ placement: 'bottom-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
10
docs/components/content/examples/KbdExample.vue
Normal file
10
docs/components/content/examples/KbdExample.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script setup>
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
15
docs/components/content/examples/ModalExampleBasic.vue
Normal file
15
docs/components/content/examples/ModalExampleBasic.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-48" />
|
||||
</div>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
23
docs/components/content/examples/ModalExampleCard.vue
Normal file
23
docs/components/content/examples/ModalExampleCard.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
|
||||
<Placeholder class="h-32" />
|
||||
|
||||
<template #footer>
|
||||
<Placeholder class="h-8" />
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
const actions = ref([{
|
||||
label: 'Action 1',
|
||||
click: () => alert('Action 1 clicked!')
|
||||
}, {
|
||||
label: 'Action 2',
|
||||
click: () => alert('Action 2 clicked!')
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'With actions', actions })" />
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Hello world!' })" />
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
function onCallback () {
|
||||
alert('Notification expired!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Expires soon...', timeout: 1000, callback: onCallback })" />
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
function onClick () {
|
||||
alert('Clicked!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: onClick })" />
|
||||
</template>
|
||||
11
docs/components/content/examples/PopoverExample.vue
Normal file
11
docs/components/content/examples/PopoverExample.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-20 w-48" />
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" />
|
||||
</template>
|
||||
15
docs/components/content/examples/SelectMenuExampleButton.vue
Normal file
15
docs/components/content/examples/SelectMenuExampleButton.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[3])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton>
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple>
|
||||
<template #label>
|
||||
<span v-if="selected.length" class="font-medium truncate">{{ selected.join(', ') }}</span>
|
||||
<span v-else class="block truncate text-gray-400 dark:text-gray-500">Select people</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 'benjamincanac',
|
||||
label: 'benjamincanac',
|
||||
href: 'https://github.com/benjamincanac',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'Atinux',
|
||||
label: 'Atinux',
|
||||
href: 'https://github.com/Atinux',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'smarroufin',
|
||||
label: 'smarroufin',
|
||||
href: 'https://github.com/smarroufin',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'nobody',
|
||||
label: 'Nobody',
|
||||
icon: 'i-heroicons-user-circle'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people">
|
||||
<template #label>
|
||||
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" />
|
||||
|
||||
{{ selected.label }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
9
docs/components/content/examples/SkeletonExample.vue
Normal file
9
docs/components/content/examples/SkeletonExample.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="flex items-center space-x-4">
|
||||
<USkeleton class="h-12 w-12" :ui="{ rounded: 'rounded-full' }" />
|
||||
<div class="space-y-2">
|
||||
<USkeleton class="h-4 w-[250px]" />
|
||||
<USkeleton class="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
15
docs/components/content/examples/SlideoverExample.vue
Normal file
15
docs/components/content/examples/SlideoverExample.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<div class="p-4 h-full">
|
||||
<Placeholder class="w-full h-full" />
|
||||
</div>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
5
docs/components/content/examples/TooltipExample.vue
Normal file
5
docs/components/content/examples/TooltipExample.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-home',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
icon: 'i-heroicons-chart-bar',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
icon: 'i-heroicons-command-line',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links" />
|
||||
</template>
|
||||
71
docs/components/content/prose/ProseCode.vue
Normal file
71
docs/components/content/prose/ProseCode.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '#imports'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
highlights: {
|
||||
type: Array as () => number[],
|
||||
default: () => []
|
||||
},
|
||||
meta: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
const clipboard = useCopyToClipboard({ timeout: 2000 })
|
||||
const icon = ref('i-heroicons-clipboard-document')
|
||||
|
||||
function copy () {
|
||||
clipboard.copy(props.code, { title: 'Copied to clipboard!' })
|
||||
|
||||
icon.value = 'i-heroicons-clipboard-document-check'
|
||||
|
||||
setTimeout(() => {
|
||||
icon.value = 'i-heroicons-clipboard-document'
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
return {
|
||||
icon,
|
||||
copy
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="group relative" :class="`language-${language}`">
|
||||
<UButton
|
||||
:icon="icon"
|
||||
variant="link"
|
||||
class="absolute right-3 top-3 opacity-0 group-hover:opacity-100 transition-opacity z-[1]"
|
||||
size="xs"
|
||||
tabindex="-1"
|
||||
@click="copy"
|
||||
/>
|
||||
|
||||
<span v-if="filename" class="text-gray-400 dark:text-gray-500 absolute right-3 bottom-3 text-sm group-hover:opacity-0 transition-opacity">{{ filename }}</span>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
pre code .line {
|
||||
display: block;
|
||||
min-height: 1rem;
|
||||
}
|
||||
</style>
|
||||
15
docs/components/content/prose/ProseH2.vue
Normal file
15
docs/components/content/prose/ProseH2.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ id: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2 :id="id" class="scroll-mt-[161px] lg:scroll-mt-[112px]">
|
||||
<NuxtLink :href="`#${id}`" class="group">
|
||||
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
|
||||
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</h2>
|
||||
</template>
|
||||
15
docs/components/content/prose/ProseH3.vue
Normal file
15
docs/components/content/prose/ProseH3.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ id: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3 :id="id" class="scroll-mt-[145px] lg:scroll-mt-[96px]">
|
||||
<NuxtLink :href="`#${id}`" class="group">
|
||||
<div class="-ml-6 pr-2 py-2 inline-flex opacity-0 group-hover:opacity-100 transition-opacity absolute">
|
||||
<UIcon name="i-heroicons-hashtag-20-solid" class="w-4 h-4 text-primary-500 dark:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</h3>
|
||||
</template>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script setup>
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const { navigation } = useContent()
|
||||
|
||||
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
|
||||
|
||||
const groups = computed(() => navigation.value.map(item => ({
|
||||
key: item._path,
|
||||
label: item.title,
|
||||
commands: files.value.filter(file => file._path.startsWith(item._path)).map(file => ({
|
||||
id: file._id,
|
||||
icon: 'i-heroicons-document',
|
||||
title: file.navigation?.title || file.title,
|
||||
category: item.title,
|
||||
to: file._path
|
||||
}))
|
||||
})))
|
||||
|
||||
const close = computed(() => commandPaletteRef.value?.query ? ({ icon: 'i-heroicons-x-mark', color: 'black', variant: 'ghost', size: 'lg', padded: false }) : null)
|
||||
const empty = computed(() => commandPaletteRef.value?.query ? ({ icon: 'i-heroicons-magnifying-glass', queryLabel: 'No results' }) : ({ icon: '', label: 'No recent searches' }))
|
||||
|
||||
const ui = {
|
||||
wrapper: 'flex flex-col flex-1 min-h-0 bg-gray-50 dark:bg-gray-800',
|
||||
input: {
|
||||
wrapper: 'relative flex items-center mx-3 py-3',
|
||||
base: 'w-full rounded border-2 border-primary-500 placeholder-gray-400 dark:placeholder-gray-500 focus:border-primary-500 focus:outline-none focus:ring-0 bg-white dark:bg-gray-900',
|
||||
padding: 'px-4',
|
||||
height: 'h-14',
|
||||
size: 'text-lg',
|
||||
icon: {
|
||||
base: 'pointer-events-none absolute left-3 text-primary-500 dark:text-primary-400',
|
||||
size: 'h-6 w-6'
|
||||
}
|
||||
},
|
||||
group: {
|
||||
wrapper: 'p-3 relative',
|
||||
label: '-mx-3 px-3 -mt-4 mb-2 py-1 text-sm font-semibold text-primary-500 dark:text-primary-400 font-semibold sticky top-0 bg-gray-50 dark:bg-gray-800 z-10',
|
||||
container: 'space-y-1',
|
||||
command: {
|
||||
base: 'flex justify-between select-none items-center rounded px-2 py-4 gap-2 relative font-medium text-sm group shadow',
|
||||
active: 'bg-primary-500 dark:bg-primary-400 text-white',
|
||||
inactive: 'bg-white dark:bg-gray-900',
|
||||
label: 'flex flex-col min-w-0',
|
||||
suffix: 'text-xs',
|
||||
icon: {
|
||||
base: 'flex-shrink-0 w-6 h-6',
|
||||
active: 'text-white',
|
||||
inactive: 'text-gray-400 dark:text-gray-500'
|
||||
}
|
||||
}
|
||||
},
|
||||
empty: {
|
||||
wrapper: 'flex flex-col items-center justify-center flex-1 py-9',
|
||||
label: 'text-sm text-center text-gray-500 dark:text-gray-400',
|
||||
queryLabel: 'text-lg text-center text-gray-900 dark:text-white',
|
||||
icon: 'w-12 h-12 mx-auto text-gray-400 dark:text-gray-500 mb-4'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
:ui="ui"
|
||||
:close="close"
|
||||
:empty="empty"
|
||||
:autoselect="false"
|
||||
command-attribute="title"
|
||||
:fuse="{
|
||||
fuseOptions: { keys: ['title', 'category'] },
|
||||
}"
|
||||
placeholder="Search docs"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script setup>
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const suggestions = [
|
||||
{ id: 'linear', label: 'Linear', icon: 'i-simple-icons-linear' },
|
||||
{ id: 'figma', label: 'Figma', icon: 'i-simple-icons-figma' },
|
||||
{ id: 'slack', label: 'Slack', icon: 'i-simple-icons-slack' },
|
||||
{ id: 'youtube', label: 'YouTube', icon: 'i-simple-icons-youtube' },
|
||||
{ id: 'github', label: 'GitHub', icon: 'i-simple-icons-github' }
|
||||
]
|
||||
|
||||
const commands = [
|
||||
{ id: 'clipboard-history', label: 'Clipboard History', icon: 'i-heroicons-clipboard', click: () => alert('New file') },
|
||||
{ id: 'import-extension', label: 'Import Extension', icon: 'i-heroicons-wrench-screwdriver', click: () => alert('New folder') },
|
||||
{ id: 'manage-extensions', label: 'Manage Extensions', icon: 'i-heroicons-wrench-screwdriver', click: () => alert('Add hashtag') }
|
||||
]
|
||||
|
||||
const groups = [{
|
||||
key: 'suggestions',
|
||||
label: 'Suggestions',
|
||||
inactive: 'Application',
|
||||
commands: suggestions
|
||||
}, {
|
||||
key: 'commands',
|
||||
label: 'Commands',
|
||||
inactive: 'Command',
|
||||
commands
|
||||
}]
|
||||
|
||||
const ui = {
|
||||
wrapper: 'flex flex-col flex-1 min-h-0 divide-y divide-gray-200 dark:divide-gray-700 bg-gray-50 dark:bg-gray-800',
|
||||
container: 'relative flex-1 overflow-y-auto divide-y divide-gray-200 dark:divide-gray-700 scroll-py-2',
|
||||
input: {
|
||||
base: 'w-full h-14 px-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0'
|
||||
},
|
||||
group: {
|
||||
label: 'px-2 my-2 text-xs font-semibold text-gray-500 dark:text-gray-400',
|
||||
command: {
|
||||
base: 'flex justify-between select-none cursor-default items-center rounded-md px-2 py-2 gap-2 relative',
|
||||
active: 'bg-gray-200 dark:bg-gray-700/50 text-gray-900 dark:text-white',
|
||||
container: 'flex items-center gap-3 min-w-0',
|
||||
icon: {
|
||||
base: 'flex-shrink-0 w-5 h-5',
|
||||
active: 'text-gray-900 dark:text-white',
|
||||
inactive: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
avatar: {
|
||||
size: '2xs'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
icon=""
|
||||
:ui="ui"
|
||||
:autoselect="false"
|
||||
placeholder="Search for apps and commands"
|
||||
/>
|
||||
</template>
|
||||
32
docs/components/docs/DocsAside.vue
Normal file
32
docs/components/docs/DocsAside.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<aside class="hidden pb-8 overflow-y-auto lg:block lg:self-start lg:top-16 lg:max-h-[calc(100vh-64px)] lg:sticky lg:pr-8 lg:pl-[2px]">
|
||||
<div class="relative">
|
||||
<div class="sticky top-0 pointer-events-none z-[1]">
|
||||
<div class="h-8 bg-white dark:bg-gray-900" />
|
||||
<div class="bg-white dark:bg-gray-900 relative pointer-events-auto">
|
||||
<UButton
|
||||
icon="i-heroicons-magnifying-glass-20-solid"
|
||||
class="w-full"
|
||||
color="gray"
|
||||
@click="isSearchModalOpen = true"
|
||||
>
|
||||
Search
|
||||
|
||||
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="h-8 bg-gradient-to-b from-white dark:from-gray-900" />
|
||||
</div>
|
||||
|
||||
<DocsAsideLinks />
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
31
docs/components/docs/DocsAsideLinks.vue
Normal file
31
docs/components/docs/DocsAsideLinks.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<div v-for="(group, index) in navigation" :key="index" class="space-y-3">
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-gray-200">
|
||||
<span class="truncate">{{ group.title }}</span>
|
||||
</div>
|
||||
|
||||
<UVerticalNavigation
|
||||
:links="mapContentLinks(group.children)"
|
||||
class="mt-1"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
padding: 'pl-4',
|
||||
base: 'group text-sm block border-l -ml-px lg:leading-6',
|
||||
active: 'text-primary-500 dark:text-primary-400 border-current font-semibold',
|
||||
inactive: 'border-transparent hover:border-gray-400 dark:hover:border-gray-500 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NavItem } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
const { navigation } = useContent() as { navigation: NavItem[] }
|
||||
|
||||
function mapContentLinks (links: NavItem[]) {
|
||||
return links?.map(link => ({ label: link.title, icon: link.icon, to: link._path })) || []
|
||||
}
|
||||
</script>
|
||||
37
docs/components/docs/DocsPageHeader.vue
Normal file
37
docs/components/docs/DocsPageHeader.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<header v-if="page" class="relative border-b border-gray-200 dark:border-gray-800 pb-8 mb-12">
|
||||
<p class="mb-4 text-sm leading-6 font-semibold text-primary-500 dark:text-primary-400 capitalize">
|
||||
{{ useLowerCase(page._dir) }}
|
||||
</p>
|
||||
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h1 class="text-3xl sm:text-4xl font-extrabold text-gray-900 tracking-tight dark:text-white">
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
|
||||
<div class="flex items-center gap-2 mt-4 lg:mt-0">
|
||||
<UButton
|
||||
v-if="page.headlessui"
|
||||
:label="page.headlessui.label"
|
||||
:to="page.headlessui.to"
|
||||
icon="i-simple-icons-headlessui"
|
||||
color="white"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
v-if="page.github"
|
||||
label="GitHub"
|
||||
icon="i-simple-icons-github"
|
||||
color="white"
|
||||
:to="`https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/${page._dir}/U${page.title}.vue`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="page.description" class="mt-4 text-lg">
|
||||
{{ page.description }}
|
||||
</p>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { page } = useContent()
|
||||
</script>
|
||||
11
docs/components/docs/DocsPrevNext.vue
Normal file
11
docs/components/docs/DocsPrevNext.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between">
|
||||
<UButton v-if="prev" :label="prev.navigation?.title || prev.title" :to="prev._path" icon="i-heroicons-arrow-small-left-20-solid" color="white" />
|
||||
<span v-else> </span>
|
||||
<UButton v-if="next" :label="next.navigation?.title || next.title" :to="next._path" trailing-icon="i-heroicons-arrow-small-right-20-solid" color="white" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { prev, next } = useContent()
|
||||
</script>
|
||||
172
docs/components/docs/DocsSearch.vue
Normal file
172
docs/components/docs/DocsSearch.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<UModal
|
||||
v-model="isSearchModalOpen"
|
||||
:ui="{
|
||||
padding: 'sm:p-4',
|
||||
rounded: 'sm:rounded-lg',
|
||||
width: 'sm:max-w-3xl',
|
||||
height: 'h-screen sm:h-[28rem]'
|
||||
}"
|
||||
>
|
||||
<UCommandPalette
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
command-attribute="title"
|
||||
:fuse="{
|
||||
fuseOptions: { ignoreLocation: true, includeMatches: true, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
|
||||
resultLimit: 10
|
||||
}"
|
||||
@update:model-value="onSelect"
|
||||
@close="isSearchModalOpen = false"
|
||||
/>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Command } from '../../../src/runtime/types'
|
||||
|
||||
const { navigation } = useContent()
|
||||
const router = useRouter()
|
||||
const { usingInput } = useShortcuts()
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
|
||||
const commandPaletteRef = ref<HTMLElement & { query: Ref<string>, results: { item: Command }[] }>()
|
||||
|
||||
const { data: files } = await useLazyAsyncData('search', () => queryContent().where({ _type: 'markdown' }).find(), { default: () => [] })
|
||||
|
||||
// Computed
|
||||
|
||||
const defaultGroups = computed(() => navigation.value.map(item => ({
|
||||
key: item._path,
|
||||
label: item.title,
|
||||
commands: files.value.filter(file => file._path.startsWith(item._path)).map(file => ({
|
||||
id: file._id,
|
||||
title: file.navigation?.title || file.title,
|
||||
to: file._path,
|
||||
suffix: file.description,
|
||||
icon: file.icon
|
||||
}))
|
||||
})))
|
||||
|
||||
const queryGroups = computed(() => navigation.value.map(item => ({
|
||||
key: item._path,
|
||||
label: item.title,
|
||||
commands: files.value.filter(file => file._path.startsWith(item._path)).flatMap((file) => {
|
||||
return [{
|
||||
id: file._id,
|
||||
title: file.navigation?.title || file.title,
|
||||
to: file._path,
|
||||
description: file.description,
|
||||
icon: file.icon
|
||||
},
|
||||
// @ts-ignore
|
||||
...Object.entries(groupByHeading(file.body.children)).map(([hash, { title, children }]) => ({
|
||||
id: `${file._path}${hash}`,
|
||||
title,
|
||||
prefix: `${file.navigation?.title || file.title} ->`,
|
||||
prefixClass: 'text-gray-700 dark:text-gray-200',
|
||||
to: `${file._path}${hash}`,
|
||||
children: concatChildren(children),
|
||||
icon: file.icon
|
||||
}))]
|
||||
})
|
||||
})))
|
||||
|
||||
const groups = computed(() => commandPaletteRef.value?.query ? queryGroups.value : defaultGroups.value)
|
||||
|
||||
// avoid conflicts between multiple meta_k shortcuts
|
||||
const canToggleModal = computed(() => isSearchModalOpen.value || !usingInput.value)
|
||||
|
||||
// Methods
|
||||
|
||||
function remapChildren (children: any[]) {
|
||||
return children?.map((grandChild) => {
|
||||
if (['code-inline', 'em', 'a', 'strong'].includes(grandChild.tag)) {
|
||||
return { type: 'text', value: grandChild.children.find(child => child.type === 'text')?.value || '' }
|
||||
}
|
||||
|
||||
return grandChild
|
||||
})
|
||||
}
|
||||
|
||||
function concatChildren (children: any[]) {
|
||||
return children.map((child) => {
|
||||
if (['alert'].includes(child.tag)) {
|
||||
child.children = concatChildren(child.children)
|
||||
}
|
||||
if (child.tag === 'p') {
|
||||
child.children = remapChildren(child.children)
|
||||
|
||||
child.children = child.children?.reduce((acc, grandChild) => {
|
||||
if (grandChild.type === 'text') {
|
||||
if (acc.length && acc[acc.length - 1].type === 'text') {
|
||||
acc[acc.length - 1].value += grandChild.value
|
||||
} else {
|
||||
acc.push(grandChild)
|
||||
}
|
||||
} else {
|
||||
acc.push(grandChild)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
if (['style'].includes(child.tag)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return child
|
||||
})
|
||||
}
|
||||
|
||||
function groupByHeading (children: any[]) {
|
||||
const groups = {} // grouped by path
|
||||
let hash = '' // file.page with potential `#anchor` concat
|
||||
let title: string | null
|
||||
for (const node of children) {
|
||||
// if heading found, udpate current path
|
||||
if (['h2', 'h3'].includes(node.tag)) {
|
||||
// find heading text value
|
||||
title = node.children?.find(child => child.type === 'text')?.value
|
||||
if (title) {
|
||||
hash = `#${node.props.id}`
|
||||
}
|
||||
}
|
||||
// push to existing/new group based on path
|
||||
if (groups[hash]) {
|
||||
groups[hash].children.push(node)
|
||||
} else {
|
||||
groups[hash] = { children: [node], title }
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
function onSelect (option) {
|
||||
isSearchModalOpen.value = false
|
||||
|
||||
if (option.click) {
|
||||
option.click()
|
||||
} else if (option.to) {
|
||||
router.push(option.to)
|
||||
} else if (option.href) {
|
||||
window.open(option.href, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
// Shortcuts
|
||||
|
||||
defineShortcuts({
|
||||
meta_k: {
|
||||
usingInput: true,
|
||||
whenever: [canToggleModal],
|
||||
handler: () => {
|
||||
isSearchModalOpen.value = !isSearchModalOpen.value
|
||||
}
|
||||
},
|
||||
escape: {
|
||||
usingInput: true,
|
||||
whenever: [isSearchModalOpen],
|
||||
handler: () => { isSearchModalOpen.value = false }
|
||||
}
|
||||
})
|
||||
</script>
|
||||
19
docs/components/docs/DocsToc.vue
Normal file
19
docs/components/docs/DocsToc.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div v-if="toc" class="sticky top-16 bg-white/75 dark:bg-gray-900/75 backdrop-blur group lg:self-start -mx-4 sm:-mx-6 lg:mx-0 px-4 sm:px-6 lg:pl-8 lg:pr-0">
|
||||
<div class="py-3 lg:py-8 border-b border-dashed border-gray-200 dark:border-gray-800 lg:border-0">
|
||||
<button class="flex items-center gap-2" tabindex="-1" @click="isTocOpen = !isTocOpen">
|
||||
<span class="text-sm text-slate-900 font-semibold text-sm leading-6 dark:text-slate-100 truncate">Table of Contents</span>
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="lg:hidden w-4 h-4 transition-transform duration-100 transform text-gray-400 dark:text-gray-500" :class="[isTocOpen ? 'rotate-90' : 'rotate-0']" />
|
||||
</button>
|
||||
|
||||
<DocsTocLinks class="mt-2 lg:mt-4" :links="toc.links" :class="[isTocOpen ? 'lg:block' : 'hidden lg:block']" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { toc } = useContent()
|
||||
|
||||
const isTocOpen = ref(false)
|
||||
</script>
|
||||
49
docs/components/docs/DocsTocLinks.vue
Normal file
49
docs/components/docs/DocsTocLinks.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="link in links" :key="link.text" :class="{ 'ml-3': link.depth === 3 }">
|
||||
<a
|
||||
:href="`#${link.id}`"
|
||||
class="block py-1 font-medium text-sm"
|
||||
:class="[activeHeadings.includes(link.id) ? 'text-primary-500 dark:text-primary-400' : 'hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300']"
|
||||
@click.prevent="scrollToHeading(link.id)"
|
||||
>
|
||||
{{ link.text }}
|
||||
</a>
|
||||
|
||||
<DocsTocLinks v-if="link.children" :links="link.children" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TocLink } from '@nuxt/content/dist/runtime/types'
|
||||
|
||||
defineProps({
|
||||
links: {
|
||||
type: Array as PropType<TocLink[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['move'])
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { activeHeadings, updateHeadings } = useScrollspy()
|
||||
|
||||
watch(() => route.path, () => {
|
||||
setTimeout(() => {
|
||||
if (process.client) {
|
||||
updateHeadings([
|
||||
...document.querySelectorAll('h2'),
|
||||
...document.querySelectorAll('h3')
|
||||
])
|
||||
}
|
||||
}, 300)
|
||||
}, { immediate: true })
|
||||
|
||||
const scrollToHeading = (id: string) => {
|
||||
router.push(`#${id}`)
|
||||
emit('move', id)
|
||||
}
|
||||
</script>
|
||||
19
docs/composables/useComponentMeta.ts
Normal file
19
docs/composables/useComponentMeta.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
const useComponentsMetaState = () => useState('components-meta', () => ({}))
|
||||
|
||||
export async function fetchComponentMeta (name: string) {
|
||||
const state = useComponentsMetaState()
|
||||
|
||||
if (state.value[name]?.then) {
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
if (state.value[name]) { return state.value[name] }
|
||||
|
||||
// Store promise to avoid multiple calls
|
||||
state.value[name] = $fetch(`/api/component-meta/${name}`).then((meta) => {
|
||||
state.value[name] = meta
|
||||
})
|
||||
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
11
docs/composables/useDocs.ts
Normal file
11
docs/composables/useDocs.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
|
||||
const _useDocs = () => {
|
||||
const isSearchModalOpen = ref(false)
|
||||
|
||||
return {
|
||||
isSearchModalOpen
|
||||
}
|
||||
}
|
||||
|
||||
export const useDocs = createSharedComposable(_useDocs)
|
||||
37
docs/composables/useScrollspy.ts
Normal file
37
docs/composables/useScrollspy.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Scrollspy allows you to watch visible headings in a specific page.
|
||||
* Useful for table of contents live style updates.
|
||||
*/
|
||||
export const useScrollspy = () => {
|
||||
const observer = ref() as Ref<IntersectionObserver>
|
||||
const visibleHeadings = ref([]) as Ref<string[]>
|
||||
const activeHeadings = ref([]) as Ref<string[]>
|
||||
|
||||
const observerCallback = (entries: IntersectionObserverEntry[]) =>
|
||||
entries.forEach((entry) => {
|
||||
const id = entry.target.id
|
||||
|
||||
if (entry.isIntersecting) { visibleHeadings.value.push(id) } else { visibleHeadings.value = visibleHeadings.value.filter(t => t !== id) }
|
||||
})
|
||||
|
||||
const updateHeadings = (headings: Element[]) =>
|
||||
headings.forEach((heading) => {
|
||||
observer.value.observe(heading)
|
||||
})
|
||||
|
||||
watch(visibleHeadings, (val, oldVal) => {
|
||||
if (val.length === 0) { activeHeadings.value = oldVal } else { activeHeadings.value = val }
|
||||
})
|
||||
|
||||
// Create intersection observer
|
||||
onBeforeMount(() => (observer.value = new IntersectionObserver(observerCallback)))
|
||||
|
||||
// Destroy it
|
||||
onBeforeUnmount(() => observer.value?.disconnect())
|
||||
|
||||
return {
|
||||
visibleHeadings,
|
||||
activeHeadings,
|
||||
updateHeadings
|
||||
}
|
||||
}
|
||||
33
docs/content/1.getting-started/1.index.md
Normal file
33
docs/content/1.getting-started/1.index.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: 'Components library as a Nuxt module using TailwindCSS and HeadlessUI.'
|
||||
---
|
||||
|
||||
This module has been developed by the [NuxtLabs](https://nuxtlabs.com/) team for [Volta](https://volta.net) and [Nuxt Studio](https://nuxt.studio/), its goal is to provide everything related to UI when building a Nuxt app. This includes components, icons, colors, dark mode but also keyboard shortcuts.
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
[Volta](https://volta.net/) entire UI is built with this module alongside the 50+ keyboard shortcuts defined.
|
||||
::
|
||||
|
||||
## Features
|
||||
|
||||
- Fully styled and customizable components
|
||||
- HMR support through Nuxt App Config
|
||||
- Dark mode support
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
|
||||
## Credits
|
||||
|
||||
- [nuxt/nuxt](https://github.com/nuxt/nuxt)
|
||||
- [nuxt-modules/color-mode](https://github.com/nuxt-modules/color-mode)
|
||||
- [nuxt-modules/tailwindcss](https://github.com/nuxt-modules/tailwindcss)
|
||||
- [tailwindlabs/tailwindcss](https://github.com/tailwindlabs/tailwindcss)
|
||||
- [tailwindlabs/headlessui](https://github.com/tailwindlabs/headlessui)
|
||||
- [vueuse/vueuse](https://github.com/vueuse/vueuse)
|
||||
- [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons)
|
||||
|
||||
::alert{icon="i-heroicons-exclamation-triangle"}
|
||||
This documentation is still a work in progress and will be updated regularly.
|
||||
::
|
||||
59
docs/content/1.getting-started/2.installation.md
Normal file
59
docs/content/1.getting-started/2.installation.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
description: 'Learn how to install and configure the module in your Nuxt app.'
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
1. Install `@nuxthq/ui` dependency to your project:
|
||||
|
||||
::code-group
|
||||
|
||||
```bash [yarn]
|
||||
yarn add -D @nuxthq/ui
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install -D @nuxthq/ui
|
||||
```
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm i -D @nuxthq/ui
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
2. Add it to your `modules` section in your `nuxt.config`:
|
||||
|
||||
```ts [nuxt.config]
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxthq/ui']
|
||||
})
|
||||
```
|
||||
|
||||
::alert
|
||||
That's it! You can now use all the components and composables in your Nuxt app ✨
|
||||
::
|
||||
|
||||
## Options
|
||||
|
||||
| Key | Default | Description |
|
||||
| ------------------------ | ---------------------- | ------------------------------------------------ |
|
||||
| `prefix` | `u` | Define the prefix of the imported components. |
|
||||
| `global` | `false` | Expose components globally. |
|
||||
| `icons` | `['heroicons']` | Icon collections to load. |
|
||||
|
||||
## Edge channel
|
||||
|
||||
To use the latest updates pushed on the [`dev`](https://github.com/nuxtlabs/ui/tree/dev) branch, you can use `@nuxthq/ui-edge`.
|
||||
|
||||
Update your `package.json` to the following:
|
||||
|
||||
```json [package.json]
|
||||
{
|
||||
"devDependencies": {
|
||||
"@nuxthq/ui": "npm:@nuxthq/ui-edge@latest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run `npm install` or `yarn install`.
|
||||
167
docs/content/1.getting-started/3.theming.md
Normal file
167
docs/content/1.getting-started/3.theming.md
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
description: 'Learn how to customize the look and feel of the components.'
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This module relies on Nuxt [App Config](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file) file to customize the look and feel of the components at runtime with HMR (hot-module-replacement).
|
||||
|
||||
## Colors
|
||||
|
||||
Components are based on a `primary` and a `gray` color. You can change them in your `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
primary: 'sky',
|
||||
gray: 'cool'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
As this module uses TailwindCSS under the hood, you can use any of the [TailwindCSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `sky` and the `gray` color is `cool`.
|
||||
|
||||
To provide dynamic colors that can be changed at runtime, this module uses CSS variables. As TailwindCSS already has a `gray` color, the module automatically renames it to `cool` to avoid conflicts (`coolGray` was renamed to `gray` when Tailwind CSS v3.0 was released).
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Try to change the `primary` and `gray` colors in the navbar and see the colors change live.
|
||||
::
|
||||
|
||||
Components that have a `color` prop like [Avatar](/elements/avatar), [Badge](/elements/badge) and [Button](/elements/button) will use the `primary` color by default but will handle all the colors defined in your `tailwind.config.ts` or the default TailwindCSS colors.
|
||||
|
||||
## Components
|
||||
|
||||
Components are styled with TailwindCSS but classes are all defined in the default [app.config.ts](https://github.com/nuxtlabs/ui/blob/dev/src/runtime/app.config.ts) file. You can override them in your `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
container: {
|
||||
constrained: 'max-w-5xl'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Each component has a `ui` prop that allows you to customize everything specifically.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UContainer :ui="{ constrained: 'max-w-2xl' }">
|
||||
<slot />
|
||||
</UContainer>
|
||||
</template>
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
You can find the default classes for each component under the `Preset` section.
|
||||
::
|
||||
|
||||
Some component props like `size`, `color`, `variant`, etc. have a default value that you can override in your `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
button: {
|
||||
default: {
|
||||
size: 'md',
|
||||
color: 'gray',
|
||||
variant: 'ghost'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Icons
|
||||
|
||||
You can use any icon (100,000+) from [Iconify](https://iconify.design/).
|
||||
|
||||
Some components have an `icon` prop that allows you to add an icon to the component.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UButton icon="i-heroicons-magnifying-glass" />
|
||||
</template>
|
||||
```
|
||||
|
||||
You can also use the [Icon](/elements/icon) component to add an icon anywhere in your app by following this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UIcon name="i-heroicons-moon" />
|
||||
</template>
|
||||
```
|
||||
|
||||
By default, the module uses [Heroicons](https://heroicons.com/) but you can change it from the module options in your `nuxt.config.ts`.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
icons: ['mdi', 'simple-icons']
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Search the icon you want to use on https://icones.js.org built by [@antfu](https://github.com/antfu).
|
||||
::
|
||||
|
||||
Unlike the official [nuxt-icon](https://github.com/nuxt-modules/icon/) module, this module will not fetch any icon from the web and will only bundle the icons you use in your app thanks to [egoist/tailwindcss-icons](https://github.com/egoist/tailwindcss-icons).
|
||||
|
||||
However, you will need to install the icon packages you want to use.
|
||||
|
||||
```sh
|
||||
yarn add --dev @iconify/json-{collection_name}
|
||||
```
|
||||
|
||||
You can easily replace all the default icons of the components in your `app.config.ts`.
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
button: {
|
||||
default: {
|
||||
loadingIcon: 'i-octicon-sync-24'
|
||||
}
|
||||
},
|
||||
input: {
|
||||
default: {
|
||||
loadingIcon: 'i-octicon-sync-24'
|
||||
}
|
||||
},
|
||||
select: {
|
||||
default: {
|
||||
trailingIcon: 'i-octicon-chevron-down-24'
|
||||
}
|
||||
},
|
||||
selectMenu: {
|
||||
default: {
|
||||
selectedIcon: 'i-octicon-check-24'
|
||||
}
|
||||
},
|
||||
notification: {
|
||||
default: {
|
||||
close: {
|
||||
icon: 'i-octicon-x-24'
|
||||
}
|
||||
}
|
||||
},
|
||||
commandPalette: {
|
||||
default: {
|
||||
icon: 'i-octicon-search-24',
|
||||
selectedIcon: 'i-octicon-check-24',
|
||||
empty: {
|
||||
icon: 'i-octicon-search-24'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Dark mode
|
||||
|
||||
All the components are styled with dark mode in mind.
|
||||
|
||||
Thanks to [TailwindCSS dark mode](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually) `class` strategy and the [@nuxtjs/color-mode](https://github.com/nuxt-modules/color-mode) module, you literally have nothing to do.
|
||||
63
docs/content/1.getting-started/4.shortcuts.md
Normal file
63
docs/content/1.getting-started/4.shortcuts.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
description: 'Learn how to display and define keyboard shortcuts in your app.'
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Some components like [Dropdown](/elements/dropdown), [Command Palette](/navigation/command-palette) and [Tooltip](/overlays/tooltip) support the display of keyboard shortcuts.
|
||||
|
||||
```vue
|
||||
<UDropdown :items="[{ label: 'Edit', shortcuts: ['E'] }]" />
|
||||
```
|
||||
|
||||
Shortcuts are displayed and styled through the [Kbd](/elements/kbd) component.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<UKbd>⌘</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
You will have a preview of how shortcuts are rendered in each component page.
|
||||
::
|
||||
|
||||
## `defineShortcuts`
|
||||
|
||||
This module provides a `defineShortcuts` composable that allows you to define keyboard shortcuts in your app really easily.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UModal v-model="isOpen" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const isOpen = ref(false)
|
||||
|
||||
defineShortcuts({
|
||||
meta_k: {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
isOpen.value = !isOpen.value
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## `useShortcuts`
|
||||
|
||||
To display shortcuts in your app according to the user's OS, you can use the `useShortcuts` composable.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
</template>
|
||||
```
|
||||
100
docs/content/2.elements/1.avatar.md
Normal file
100
docs/content/2.elements/1.avatar.md
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
alt: 'Avatar'
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Avatar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
baseProps:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
alt: 'Avatar'
|
||||
---
|
||||
::
|
||||
|
||||
### Chip
|
||||
|
||||
Use the `chipColor`, `chipVariant` and `chipPosition` props to display a chip on the Avatar.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
chipColor: 'primary'
|
||||
chipVariant: 'solid'
|
||||
chipPosition: 'top-right'
|
||||
baseProps:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
alt: 'Avatar'
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
If there is an error loading the `src` of the avatar or `src` is null a background placeholder will be displayed, customizable in `ui.avatar.background`.
|
||||
|
||||
If there's an `alt` prop initials will be displayed on top of the background, customizable in `ui.avatar.placeholder`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
alt: 'Benjamin Canac'
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
|
||||
To stack avatars as a group, use the `AvatarGroup` component.
|
||||
|
||||
- To limit the amount of avatars to show, use the `max` prop. It'll truncate the avatars and show a "+X" label (where X is the remaining avatars)
|
||||
- To size all the avatars equally, pass the `size` prop
|
||||
- To adjust the spacing or the ring between avatars, customize with `ui.avatarGroup.margin` or `ui.avatarGroup.ring`
|
||||
|
||||
::component-card{slug="AvatarGroup"}
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
max: 2
|
||||
ui:
|
||||
size:
|
||||
'3xs': ''
|
||||
'2xs': ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
'2xl': ''
|
||||
'3xl': ''
|
||||
code: |
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" alt="benjamincanac" />
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Atinux" />
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/7547335?v=4" alt="smarroufin" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" alt="Avatar"}
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/904724?v=4" alt="Avatar"}
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/7547335?v=4" alt="Avatar"}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
59
docs/content/2.elements/2.badge.md
Normal file
59
docs/content/2.elements/2.badge.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the default slot to set the text of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
code: Badge
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
You can also use the `label` prop:
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: Badge
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Badge.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
|
||||
Badge
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
282
docs/content/2.elements/3.button.md
Normal file
282
docs/content/2.elements/3.button.md
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the default slot to set the text of the Button.
|
||||
|
||||
::component-card
|
||||
---
|
||||
code: Button
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
You can also use the `label` prop.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: Button
|
||||
---
|
||||
::
|
||||
|
||||
### Style
|
||||
|
||||
Use the `color` and `variant` props to change the visual style of the Button.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
Besides all the colors from the `ui.colors` object, you can also use the `white`, `gray` and `black` colors with their pre-defined variants.
|
||||
|
||||
#### White
|
||||
|
||||
::component-card
|
||||
---
|
||||
backgroundClass: 'bg-gray-50 dark:bg-gray-800'
|
||||
props:
|
||||
color: 'white'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
ghost: 1
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
#### Gray
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'gray'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
ghost: 1
|
||||
link: 1
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
#### Black
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
color: 'black'
|
||||
variant: 'solid'
|
||||
ui:
|
||||
variant:
|
||||
solid: 1
|
||||
link: 1
|
||||
excludedProps:
|
||||
- color
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Button.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
icon: 'i-heroicons-pencil-square'
|
||||
size: 'sm'
|
||||
color: 'primary'
|
||||
variant: 'solid'
|
||||
label: Button
|
||||
trailing: false
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
The `label` as prop or slot is optional so you can use the Button as an icon-only button.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
icon: 'i-heroicons-pencil-square'
|
||||
size: 'sm'
|
||||
color: 'primary'
|
||||
square: true
|
||||
variant: 'solid'
|
||||
excludedProps:
|
||||
- icon
|
||||
- square
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Button.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` prop to show a loading icon and disable the Button.
|
||||
|
||||
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.button.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
loading: true
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
### Block
|
||||
|
||||
Use the `block` prop to make the Button fill the width of its container.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
block: true
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
### Link
|
||||
|
||||
Use the `to` prop to make the Button a link.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
to: 'https://volta.net'
|
||||
target: '_blank'
|
||||
---
|
||||
|
||||
Button
|
||||
::
|
||||
|
||||
### Padded
|
||||
|
||||
Use the `padded` prop to remove the padding of the Button.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
padded: false
|
||||
baseProps:
|
||||
color: 'gray'
|
||||
variant: 'link'
|
||||
icon: 'i-heroicons-x-mark-20-solid'
|
||||
---
|
||||
::
|
||||
|
||||
### Square
|
||||
|
||||
Use the `square` prop to force the Button to have the same padding horizontally and vertically.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
square: true
|
||||
baseProps:
|
||||
label: 'Button'
|
||||
color: 'gray'
|
||||
variant: 'solid'
|
||||
---
|
||||
::
|
||||
|
||||
### Truncate
|
||||
|
||||
Use the `truncate` prop to truncate the label of the Button.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
truncate: true
|
||||
class: 'w-20'
|
||||
label: 'Button with a long text'
|
||||
excludedProps:
|
||||
- class
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
|
||||
To stack buttons as a group, use the `ButtonGroup` component.
|
||||
|
||||
- To size all the buttons equally, pass the `size` prop
|
||||
- To adjust the rounded or the shadow around buttons, customize with `ui.buttonGroup.rounded` or `ui.buttonGroup.shadow`
|
||||
|
||||
::component-card{slug="ButtonGroup"}
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
ui:
|
||||
size:
|
||||
xxs: ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
lg: ''
|
||||
xl: ''
|
||||
code: |
|
||||
<UButton label="Action" color="white" />
|
||||
<UButton icon="i-heroicons-chevron-down-20-solid" color="gray" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-button{label="Action" color="white"}
|
||||
:u-button{icon="i-heroicons-chevron-down-20-solid" color="gray"}
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
59
docs/content/2.elements/4.dropdown.md
Normal file
59
docs/content/2.elements/4.dropdown.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
github: true
|
||||
headlessui:
|
||||
label: 'Menu'
|
||||
to: 'https://headlessui.com/vue/menu'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:dropdown-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const items = [
|
||||
[{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E']
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D']
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
}, {
|
||||
label: 'Move',
|
||||
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||
}], [{
|
||||
label: 'Delete',
|
||||
icon: 'i-heroicons-trash-20-solid',
|
||||
shortcuts: ['⌘', 'D']
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown :items="items" :popper="{ placement: 'bottom-start' }">
|
||||
<UButton color="white" label="Options" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
20
docs/content/2.elements/5.icon.md
Normal file
20
docs/content/2.elements/5.icon.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
name: 'i-heroicons-light-bulb'
|
||||
---
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-exclamation-triangle"}
|
||||
When playing with the `name` prop above, you won't be able to use any icon you want as icons are bundled on build as explained in the [theming section](/getting-started/theming#icons).
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
70
docs/content/2.elements/6.kbd.md
Normal file
70
docs/content/2.elements/6.kbd.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
github: true
|
||||
title: 'Keyboard Key'
|
||||
navigation:
|
||||
title: 'Kbd'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the default slot to set the text of the Kbd.
|
||||
|
||||
::component-card
|
||||
---
|
||||
code: K
|
||||
---
|
||||
|
||||
K
|
||||
::
|
||||
|
||||
You can also use the `value` prop:
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
value: K
|
||||
---
|
||||
::
|
||||
|
||||
As explained in the [Shortcuts](/getting-started/shortcuts) page, you can use the `metaSymbol` property of the `useShortcuts` composable to display the meta key according to the user's OS.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:kbd-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Kbd.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'sm'
|
||||
code: 'U'
|
||||
---
|
||||
|
||||
U
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
119
docs/content/3.forms/1.input.md
Normal file
119
docs/content/3.forms/1.input.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
Use the `placeholder` prop to set a placeholder text.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
::
|
||||
|
||||
### Appearance
|
||||
|
||||
Use the `appearance` prop to change the style of the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `leading` and `trailing` props to set the icon position or the `leadingIcon` and `trailingIcon` props to set a different icon for each position.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
appearance: 'white'
|
||||
size: 'sm'
|
||||
trailing: false
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
appearance: 'white'
|
||||
disabled: true
|
||||
excludedProps:
|
||||
- placeholder
|
||||
---
|
||||
::
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` prop to show a loading icon and disable the Input.
|
||||
|
||||
Use the `loadingIcon` prop to set a different icon or change it globally in `ui.input.default.loadingIcon`. Defaults to `i-heroicons-arrow-path-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search'
|
||||
props:
|
||||
loading: true
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Group
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
75
docs/content/3.forms/2.textarea.md
Normal file
75
docs/content/3.forms/2.textarea.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Textarea.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
Use the `placeholder` prop to set a placeholder text.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
::
|
||||
|
||||
### Appearance
|
||||
|
||||
Use the `appearance` prop to change the style of the Textarea.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'textarea'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Textarea.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
123
docs/content/3.forms/3.select.md
Normal file
123
docs/content/3.forms/3.select.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
modelValue: 'United States'
|
||||
props:
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
excludedProps:
|
||||
- options
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
props:
|
||||
size: 'sm'
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
Use the `placeholder` prop to set a placeholder text.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
props:
|
||||
placeholder: 'Search...'
|
||||
---
|
||||
::
|
||||
|
||||
### Appearance
|
||||
|
||||
Use the `appearance` prop to change the style of the Select.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
appearance: 'white'
|
||||
size: 'sm'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Input.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
options:
|
||||
- 'United States'
|
||||
- 'Canada'
|
||||
- 'Mexico'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
appearance: 'white'
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
178
docs/content/3.forms/4.select-menu.md
Normal file
178
docs/content/3.forms/4.select-menu.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
github: true
|
||||
headlessui:
|
||||
label: 'Listbox'
|
||||
to: 'https://headlessui.com/vue/listbox'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The SelectMenu component renders by default a [Select](/forms/select) component and is based on the `ui.select` preset. You can use most of the Select props to configure the display if you don't want to override the default slot such as [size](/forms/select#size), [placeholder](/forms/select#placeholder), [appearance](/forms/select#appearance), [icon](/forms/select#icon), [disabled](/forms/select#disabled), etc.
|
||||
|
||||
Like the Select component, you can use the `options` prop to pass an array of strings or objects.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-basic{class="max-w-[12rem] w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can use the `multiple` prop to select multiple values but you have to override the `#label` slot and handle the display yourself.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-multiple{class="max-w-[12rem] w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people" multiple>
|
||||
<template #label>
|
||||
<span v-if="selected.length" class="font-medium truncate">{{ selected.join(', ') }}</span>
|
||||
<span v-else class="block truncate text-gray-400 dark:text-gray-500">Select people</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can also override the default slot entirely.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-button{class="max-w-[12rem] w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
|
||||
const selected = ref(people[3])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-slot="{ open }" v-model="selected" :options="people">
|
||||
<UButton>
|
||||
{{ selected }}
|
||||
|
||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform" :class="[open && 'transform rotate-90']" />
|
||||
</UButton>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `optionAttribute` prop that defaults to `label`.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:select-menu-example-objects{class="max-w-[12rem] w-full"}
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [{
|
||||
id: 'benjamincanac',
|
||||
label: 'benjamincanac',
|
||||
href: 'https://github.com/benjamincanac',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'Atinux',
|
||||
label: 'Atinux',
|
||||
href: 'https://github.com/Atinux',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'smarroufin',
|
||||
label: 'smarroufin',
|
||||
href: 'https://github.com/smarroufin',
|
||||
target: '_blank',
|
||||
avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
|
||||
},
|
||||
{
|
||||
id: 'nobody',
|
||||
label: 'Nobody',
|
||||
icon: 'i-heroicons-user-circle'
|
||||
}]
|
||||
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu v-model="selected" :options="people">
|
||||
<template #label>
|
||||
<UIcon v-if="selected.icon" :name="selected.icon" class="w-4 h-4" />
|
||||
<UAvatar v-else-if="selected.avatar" v-bind="selected.avatar" size="3xs" />
|
||||
|
||||
{{ selected.label }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `trailingIcon` prop to set a different icon or change it globally in `ui.select.default.trailingIcon`. Defaults to `i-heroicons-chevron-down-20-solid`.
|
||||
|
||||
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.selectMenu.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
class: 'max-w-[12rem] w-full'
|
||||
placeholder: 'Select a person'
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Search
|
||||
|
||||
Use the `searchable` prop to enable search.
|
||||
|
||||
This will use HeadlessUI [Combobox](https://headlessui.com/vue/combobox) component instead of [Listbox](https://headlessui.com/vue/listbox).
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
class: 'max-w-[12rem] w-full'
|
||||
placeholder: 'Select a person'
|
||||
options: ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']
|
||||
props:
|
||||
searchable: true
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
75
docs/content/3.forms/5.checkbox.md
Normal file
75
docs/content/3.forms/5.checkbox.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox'
|
||||
---
|
||||
::
|
||||
|
||||
### Label
|
||||
|
||||
Use the `label` prop to display a label on the right.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox1'
|
||||
props:
|
||||
label: 'Label'
|
||||
---
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to display a red star next to the label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox2'
|
||||
props:
|
||||
label: 'Label'
|
||||
required: true
|
||||
---
|
||||
::
|
||||
|
||||
### Help
|
||||
|
||||
Use the `help` prop to display some text under the Checkbox.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox3'
|
||||
props:
|
||||
label: 'Label'
|
||||
help: 'Please check this box'
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Checkbox.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'checkbox4'
|
||||
modelValue: true
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
75
docs/content/3.forms/6.radio.md
Normal file
75
docs/content/3.forms/6.radio.md
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio'
|
||||
---
|
||||
::
|
||||
|
||||
### Label
|
||||
|
||||
Use the `label` prop to display a label on the right.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio1'
|
||||
props:
|
||||
label: 'Label'
|
||||
---
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to display a red star next to the label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio2'
|
||||
props:
|
||||
label: 'Label'
|
||||
required: true
|
||||
---
|
||||
::
|
||||
|
||||
### Help
|
||||
|
||||
Use the `help` prop to display some text under the Radio.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio3'
|
||||
props:
|
||||
label: 'Label'
|
||||
help: 'Please choose one'
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the Radio.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'radio4'
|
||||
value: true
|
||||
props:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
34
docs/content/3.forms/7.toggle.md
Normal file
34
docs/content/3.forms/7.toggle.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
github: true
|
||||
headlessui:
|
||||
label: 'Switch'
|
||||
to: 'https://headlessui.com/vue/switch'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-card
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon-on` and `icon-off` props by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
iconOn: 'i-heroicons-check-20-solid'
|
||||
iconOff: 'i-heroicons-x-mark-20-solid'
|
||||
excludedProps:
|
||||
- iconOn
|
||||
- iconOff
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
46
docs/content/4.navigation/1.vertical-navigation.md
Normal file
46
docs/content/4.navigation/1.vertical-navigation.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:vertical-navigation-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const links = [{
|
||||
label: 'Profile',
|
||||
avatar: {
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
}
|
||||
}, {
|
||||
label: 'Installation',
|
||||
icon: 'i-heroicons-home',
|
||||
to: '/getting-started/installation'
|
||||
}, {
|
||||
label: 'Vertical Navigation',
|
||||
icon: 'i-heroicons-chart-bar',
|
||||
to: '/navigation/vertical-navigation'
|
||||
}, {
|
||||
label: 'Command Palette',
|
||||
icon: 'i-heroicons-command-line',
|
||||
to: '/navigation/command-palette'
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UVerticalNavigation :links="links" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
304
docs/content/4.navigation/2.command-palette.md
Normal file
304
docs/content/4.navigation/2.command-palette.md
Normal file
@@ -0,0 +1,304 @@
|
||||
---
|
||||
github: true
|
||||
headlessui:
|
||||
label: 'Combobox'
|
||||
to: 'https://headlessui.com/vue/combobox'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use a `v-model` to display a searchable and selectable list of commands.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-example-basic{class="h-[257px]"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb' },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox' },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz' },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham' },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const selected = ref([people[3]])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
nullable
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can put a `CommandPalette` anywhere you want but it's most commonly used inside of a modal.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:command-palette-example-modal
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
{ id: 2, label: 'Arlene Mccoy' },
|
||||
{ id: 3, label: 'Devon Webb' },
|
||||
{ id: 4, label: 'Tom Cook' },
|
||||
{ id: 5, label: 'Tanya Fox' },
|
||||
{ id: 6, label: 'Hellen Schmidt' },
|
||||
{ id: 7, label: 'Caroline Schultz' },
|
||||
{ id: 8, label: 'Mason Heaney' },
|
||||
{ id: 9, label: 'Claudie Smitham' },
|
||||
{ id: 10, label: 'Emil Schaefer' }
|
||||
]
|
||||
|
||||
const selected = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
nullable
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
/>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can pass multiple groups of commands to the component. Each group will be separated by a divider and will display a label.
|
||||
|
||||
Without a `v-model`, you can also listen on `@update:model-value` to navigate to a link or do something else when a command is clicked.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-example-groups{class="h-[274px]"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
|
||||
const commandPaletteRef = ref()
|
||||
|
||||
const users = [
|
||||
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } },
|
||||
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' } },
|
||||
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' } }
|
||||
]
|
||||
|
||||
const actions = [
|
||||
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file'), shortcuts: ['⌘', 'N'] },
|
||||
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder'), shortcuts: ['⌘', 'F'] },
|
||||
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag'), shortcuts: ['⌘', 'H'] },
|
||||
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label'), shortcuts: ['⌘', 'L'] }
|
||||
]
|
||||
|
||||
const groups = computed(() => commandPaletteRef.value?.query
|
||||
? [{
|
||||
key: 'users',
|
||||
commands: users
|
||||
}]
|
||||
: [{
|
||||
key: 'recent',
|
||||
label: 'Recent searches',
|
||||
commands: users.slice(0, 1)
|
||||
}, {
|
||||
key: 'actions',
|
||||
commands: actions
|
||||
}])
|
||||
|
||||
function onSelect (option) {
|
||||
if (option.click) {
|
||||
option.click()
|
||||
} else if (option.to) {
|
||||
router.push(option.to)
|
||||
} else if (option.href) {
|
||||
window.open(option.href, '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette ref="commandPaletteRef" :groups="groups" @update:model-value="onSelect" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
Use the `selectedIcon` prop to set a different icon or change it globally in `ui.commandPalette.default.selectedIcon`. Defaults to `i-heroicons-check-20-solid`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
props:
|
||||
icon: 'i-heroicons-command-line'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Placeholder
|
||||
|
||||
Use the `placeholder` prop to change the input placeholder
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
props:
|
||||
placeholder: 'Type a command...'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Close
|
||||
|
||||
Use the `close` prop to display a close button on the right side of the input.
|
||||
|
||||
You can pass all the props of the [Button](/elements/button) component to customize it through the `close` prop or globally through `ui.commandPalette.default.close`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
empty: null
|
||||
props:
|
||||
close:
|
||||
icon: 'i-heroicons-x-mark-20-solid'
|
||||
color: 'gray'
|
||||
variant: 'link'
|
||||
padded: false
|
||||
excludedProps:
|
||||
- close
|
||||
---
|
||||
::
|
||||
|
||||
### Empty
|
||||
|
||||
Use the `empty` prop to display a message when there are no results.
|
||||
|
||||
You can pass an `object` through the `empty` prop or globally through `ui.commandPalette.default.empty`. Here is the default:
|
||||
|
||||
::component-card
|
||||
---
|
||||
padding: false
|
||||
baseProps:
|
||||
placeholder: 'Type something to see the empty label change'
|
||||
props:
|
||||
empty:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
label: "We couldn't find any items."
|
||||
queryLabel: "We couldn't find any items with that term. Please try again."
|
||||
excludedProps:
|
||||
- empty
|
||||
---
|
||||
::
|
||||
|
||||
## Full-text search
|
||||
|
||||
The CommandPalette component takes care of the full-text search for you with [Fuse.js](https://fusejs.io). You can pass all the options of Fuse.js through the `fuse` prop.
|
||||
|
||||
When searching for a command, the component will look for a `label` property on the command by default. You can customize this behaviour by overriding the `commandAttribute` prop. This will also affect the display of the command.
|
||||
|
||||
You can also highlight the matches in the command by setting the `fuse.fuseOptions.includeMatches` to `true`. The CommandPalette component automatically takes care of the highlighting for you.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UCommandPalette
|
||||
command-attribute="title"
|
||||
:fuse="{
|
||||
fuseOptions: {
|
||||
ignoreLocation: true,
|
||||
includeMatches: true,
|
||||
threshold: 0,
|
||||
keys: ['title', 'description', 'children.children.value', 'children.children.children.value']
|
||||
},
|
||||
resultLimit: 10
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
Try it yourself in this documentation's search by pressing :badge-shortcut{value="meta"} :badge-shortcut{value="K" class="ml-1"}.
|
||||
::
|
||||
|
||||
## Themes
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
|
||||
|
||||
### Algolia
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-algolia{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/docs/rework/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
### Raycast
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-theme-raycast{class="max-h-[480px] rounded-md"}
|
||||
::
|
||||
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/docs/rework/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
72
docs/content/5.overlays/1.modal.md
Normal file
72
docs/content/5.overlays/1.modal.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
github: true
|
||||
headlessui:
|
||||
label: 'Dialog'
|
||||
to: 'https://headlessui.com/vue/dialog'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-basic
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<!-- Content -->
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
You can put a [Card](/layout/card) component inside your Modal to handle forms and take advantage of `header` and `footer` slots:
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:modal-example-card
|
||||
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="isOpen">
|
||||
<UCard :ui="{ divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
|
||||
<!-- Content -->
|
||||
|
||||
<template #footer>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
37
docs/content/5.overlays/2.slideover.md
Normal file
37
docs/content/5.overlays/2.slideover.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
github: true
|
||||
headlessui:
|
||||
label: 'Dialog'
|
||||
to: 'https://headlessui.com/vue/dialog'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:slideover-example
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="isOpen">
|
||||
<!-- Content -->
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
33
docs/content/5.overlays/3.popover.md
Normal file
33
docs/content/5.overlays/3.popover.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
github: true
|
||||
headlessui:
|
||||
label: 'Popover'
|
||||
to: 'https://headlessui.com/vue/popover'
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:popover-example
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UPopover>
|
||||
<UButton color="white" label="Open" trailing-icon="i-heroicons-chevron-down-20-solid" />
|
||||
|
||||
<template #panel>
|
||||
<!-- Content -->
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
26
docs/content/5.overlays/4.tooltip.md
Normal file
26
docs/content/5.overlays/4.tooltip.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:tooltip-example
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
50
docs/content/5.overlays/5.context-menu.md
Normal file
50
docs/content/5.overlays/5.context-menu.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:context-menu-example
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
|
||||
isOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="onContextMenu">
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement">
|
||||
<!-- Content -->
|
||||
</UContextMenu>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
249
docs/content/5.overlays/6.notification.md
Normal file
249
docs/content/5.overlays/6.notification.md
Normal file
@@ -0,0 +1,249 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
First of all, add the `Notifications` component to your app, preferably inside `app.vue`.
|
||||
|
||||
This component will render by default the notifications at the bottom right of the screen. You can configure its behaviour in the `app.config.ts` through `ui.notifications`.
|
||||
|
||||
```vue [app.vue]
|
||||
<template>
|
||||
<div>
|
||||
<UContainer>
|
||||
<NuxtPage />
|
||||
</UContainer>
|
||||
|
||||
<UNotifications />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Then, you can use the `useToast` composable to add notifications to your app:
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-basic
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Hello world!' })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Description
|
||||
|
||||
You can add a `description` in addition of the `title`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 2
|
||||
timeout: 0
|
||||
props:
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
---
|
||||
::
|
||||
|
||||
### Icon
|
||||
|
||||
Use any icon from [Iconify](https://icones.js.org) by setting the `icon` prop by using this pattern: `i-{collection_name}-{icon_name}`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 3
|
||||
timeout: 0
|
||||
title: 'Notification'
|
||||
props:
|
||||
icon: 'i-heroicons-check-circle'
|
||||
description: 'This is a notification.'
|
||||
excludedProps:
|
||||
- icon
|
||||
---
|
||||
::
|
||||
|
||||
### Avatar
|
||||
|
||||
Use the [avatar](/elements/avatar) prop as an `object` and configure it with any of its props.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 4
|
||||
timeout: 0
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
props:
|
||||
avatar:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
excludedProps:
|
||||
- avatar
|
||||
---
|
||||
::
|
||||
|
||||
### Timeout
|
||||
|
||||
Use the `timeout` prop to configure how long the Notification will remain. Set it to `0` to disable the timeout.
|
||||
|
||||
You will see a progress bar at the bottom of the Notification which will indicate the remaining time. When hovering the Notification, the progress bar will be paused.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 5
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
props:
|
||||
timeout: 10000
|
||||
---
|
||||
::
|
||||
|
||||
### Click
|
||||
|
||||
Use the `click` prop to execute a function when the Notification is clicked.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-click
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
function onClick () {
|
||||
alert('Clicked!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: onClick })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Callback
|
||||
|
||||
Use the `callback` prop to execute a function when the Notification expires.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-callback
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
|
||||
function onCallback () {
|
||||
alert('Notification expired!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Expires soon...', timeout: 1000, callback: onCallback })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
### Close
|
||||
|
||||
Use the `close` prop to hide or customize the close button on the Notification.
|
||||
|
||||
You can pass all the props of the [Button](/elements/button) component to customize it through the `close` prop or globally through `ui.notifications.default.close`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 6
|
||||
title: 'Notification'
|
||||
timeout: 0
|
||||
props:
|
||||
close:
|
||||
icon: 'i-heroicons-archive-box-x-mark'
|
||||
color: 'primary'
|
||||
variant: 'outline'
|
||||
padded: true
|
||||
size: '2xs'
|
||||
ui:
|
||||
rounded: 'rounded-full'
|
||||
excludedProps:
|
||||
- close
|
||||
---
|
||||
::
|
||||
|
||||
### Actions
|
||||
|
||||
Use the `actions` prop to add actions to the Notification.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:notification-example-actions
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const toast = useToast()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton label="Show toast" @click="toast.add({ title: 'Click me', click: () => alert('Clicked!') })" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
Like for `close`, you can pass all the props of the [Button](/elements/button) component inside the action or globally through `ui.notifications.default.action`.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 6
|
||||
title: 'Notification'
|
||||
timeout: 0
|
||||
props:
|
||||
actions:
|
||||
- variant: 'ghost'
|
||||
color: 'gray'
|
||||
label: Action 1
|
||||
- variant: 'solid'
|
||||
color: 'gray'
|
||||
label: Action 2
|
||||
excludedProps:
|
||||
- actions
|
||||
---
|
||||
::
|
||||
|
||||
Actions will render differently whether you have a `description` set.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
id: 6
|
||||
title: 'Notification'
|
||||
description: 'This is a notification.'
|
||||
timeout: 0
|
||||
props:
|
||||
actions:
|
||||
- variant: 'solid'
|
||||
color: 'primary'
|
||||
label: Action 1
|
||||
- variant: 'outline'
|
||||
color: 'primary'
|
||||
label: Action 2
|
||||
excludedProps:
|
||||
- actions
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
35
docs/content/6.layout/1.card.md
Normal file
35
docs/content/6.layout/1.card.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:card-example{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UCard>
|
||||
<template #header />
|
||||
|
||||
Body
|
||||
|
||||
<template #footer />
|
||||
</UCard>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
25
docs/content/6.layout/2.container.md
Normal file
25
docs/content/6.layout/2.container.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:container-example{class="w-full"}
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UContainer>Content</UContainer>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
33
docs/content/6.layout/3.skeleton.md
Normal file
33
docs/content/6.layout/3.skeleton.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
github: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use to show a placeholder while content is loading.
|
||||
|
||||
::component-example
|
||||
#default
|
||||
:skeleton-example
|
||||
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<div class="flex items-center space-x-4">
|
||||
<USkeleton class="h-12 w-12 rounded-full" />
|
||||
<div class="space-y-2">
|
||||
<USkeleton class="h-4 w-[250px]" />
|
||||
<USkeleton class="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
## Preset
|
||||
|
||||
:component-preset
|
||||
5
docs/layouts/default.vue
Normal file
5
docs/layouts/default.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="prose prose-primary dark:prose-invert max-w-none">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,29 +1,38 @@
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
import nuxtUI from '../src/module'
|
||||
import ui from '../src/module'
|
||||
|
||||
// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
// @ts-ignore
|
||||
modules: [
|
||||
nuxtUI
|
||||
ui,
|
||||
'@vueuse/nuxt',
|
||||
'@nuxt/content',
|
||||
'@nuxtjs/plausible',
|
||||
'nuxt-lodash',
|
||||
'nuxt-component-meta'
|
||||
],
|
||||
components: {
|
||||
global: true
|
||||
content: {
|
||||
documentDriven: true,
|
||||
highlight: {
|
||||
theme: {
|
||||
light: 'material-lighter',
|
||||
dark: 'material-palenight'
|
||||
},
|
||||
preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'yaml', 'bash', 'ini']
|
||||
}
|
||||
},
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'blue',
|
||||
gray: 'zinc'
|
||||
},
|
||||
preset: {
|
||||
},
|
||||
tailwindcss: {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter var', ...defaultTheme.fontFamily.sans]
|
||||
}
|
||||
}
|
||||
}
|
||||
global: true,
|
||||
icons: ['heroicons', 'simple-icons']
|
||||
},
|
||||
typescript: {
|
||||
strict: false,
|
||||
includeWorkspace: true
|
||||
},
|
||||
// @ts-ignore
|
||||
$production: {
|
||||
routeRules: {
|
||||
'/api/_content/**': { isr: true, static: true },
|
||||
'/api/component-meta/**': { isr: true, static: true }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||
<template>
|
||||
<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">
|
||||
<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="is" v-bind="{ ...boundProps, ...eventProps }">
|
||||
<template v-for="[key, slot] of Object.entries(defaultProps[params.component]?.slots || {}) || []" #[key]>
|
||||
<template v-if="Array.isArray(slot)">
|
||||
<div :key="key">
|
||||
<component
|
||||
:is="slot.component ? `U${slot.component.name}` : slot.tag"
|
||||
v-for="(slot, index) of slot"
|
||||
:key="index"
|
||||
:class="slot.class"
|
||||
v-bind="slot.component?.props || defaultProps[slot.component]"
|
||||
v-html="slot.html"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<component :is="`U${slot.component.name}`" v-if="slot.component" :key="`${key}-component`" v-bind="slot.component?.props || defaultProps[slot.component]" />
|
||||
<component :is="slot.tag" v-else :key="`${key}-tag`" :class="slot.class" v-html="slot.html" />
|
||||
</template>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
|
||||
<template v-if="props.length" #footer>
|
||||
<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 ? 'heroicons-outline:clipboard-check' : 'heroicons-outline:clipboard-copy'"
|
||||
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
|
||||
v-for="prop of props"
|
||||
:key="prop.key"
|
||||
class="capitalize"
|
||||
:name="prop.key"
|
||||
:label="prop.key"
|
||||
>
|
||||
<UToggle
|
||||
v-if="prop.type === 'Boolean'"
|
||||
v-model="prop.value"
|
||||
:name="prop.key"
|
||||
:label="prop.key"
|
||||
/>
|
||||
<USelect
|
||||
v-else-if="prop.values"
|
||||
v-model="prop.value"
|
||||
:name="prop.key"
|
||||
placeholder="Choose one..."
|
||||
:options="prop.values"
|
||||
size="sm"
|
||||
/>
|
||||
<UInput
|
||||
v-else-if="prop.type === 'String'"
|
||||
v-model="prop.value"
|
||||
:name="prop.key"
|
||||
size="sm"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<UInput
|
||||
v-else-if="prop.type === 'Number'"
|
||||
v-model="prop.value"
|
||||
type="number"
|
||||
:name="prop.key"
|
||||
size="sm"
|
||||
/>
|
||||
<UTextarea
|
||||
v-else
|
||||
:model-value="prop.value && JSON.stringify(prop.value)"
|
||||
:name="prop.key"
|
||||
size="sm"
|
||||
:rows="8"
|
||||
autoresize
|
||||
@update:model-value="value => prop.value = JSON.parse(value)"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const nuxtApp = useNuxtApp()
|
||||
const { params } = useRoute()
|
||||
|
||||
const is = `U${params.component}`
|
||||
|
||||
const component = nuxtApp.vueApp.component(is)
|
||||
|
||||
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 selectCustom = ref(people[0])
|
||||
const commandPalette = ref(people[0])
|
||||
const alertDialog = ref(false)
|
||||
const toggle = ref(false)
|
||||
const modal = ref(false)
|
||||
const slideover = ref(false)
|
||||
|
||||
const defaultProps = {
|
||||
Button: {
|
||||
label: 'Button text'
|
||||
},
|
||||
Badge: {
|
||||
label: 'Badge'
|
||||
},
|
||||
Alert: {
|
||||
title: 'A new software update is available. See what’s new in version 2.0.4.'
|
||||
},
|
||||
AlertDialog: {
|
||||
title: 'Are you sure you want to close this modal?',
|
||||
modelValue: alertDialog,
|
||||
'onUpdate:modelValue': (v) => { alertDialog.value = v },
|
||||
component: {
|
||||
name: 'Button',
|
||||
props: {
|
||||
variant: 'secondary',
|
||||
label: 'Open modal',
|
||||
onClick: () => { alertDialog.value = true }
|
||||
}
|
||||
}
|
||||
},
|
||||
Avatar: {
|
||||
src: 'https://picsum.photos/200/300'
|
||||
},
|
||||
AvatarGroup: {
|
||||
group: ['https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', 'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80', 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80', 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80']
|
||||
},
|
||||
Dropdown: {
|
||||
items: [
|
||||
[{
|
||||
label: 'Edit',
|
||||
icon: 'heroicons-solid:pencil'
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'heroicons-solid:duplicate'
|
||||
}],
|
||||
[{
|
||||
label: 'Archive',
|
||||
icon: 'heroicons-solid:archive'
|
||||
}, {
|
||||
label: 'Move',
|
||||
icon: 'heroicons-solid:external-link'
|
||||
}],
|
||||
[{
|
||||
label: 'Delete',
|
||||
icon: 'heroicons-solid:trash'
|
||||
}]
|
||||
]
|
||||
},
|
||||
VerticalNavigation: {
|
||||
links: [
|
||||
{
|
||||
label: 'Home',
|
||||
icon: 'heroicons-outline:home',
|
||||
to: '/'
|
||||
},
|
||||
{
|
||||
label: 'Examples',
|
||||
icon: 'heroicons-outline:book-open',
|
||||
to: '/examples'
|
||||
},
|
||||
{
|
||||
label: 'Migration',
|
||||
icon: 'heroicons-outline:refresh',
|
||||
to: '/migration'
|
||||
},
|
||||
{
|
||||
label: 'External link',
|
||||
icon: 'heroicons-outline:external-link',
|
||||
to: 'https://google.fr',
|
||||
target: '_blank'
|
||||
}
|
||||
]
|
||||
},
|
||||
CommandPalette: {
|
||||
modelValue: commandPalette,
|
||||
'onUpdate:modelValue': (v) => { commandPalette.value = v },
|
||||
groups: [{
|
||||
key: 'people',
|
||||
label: 'People',
|
||||
commands: people
|
||||
}]
|
||||
},
|
||||
Icon: {
|
||||
name: 'heroicons-outline:bell'
|
||||
},
|
||||
Input: {
|
||||
name: 'input',
|
||||
placeholder: 'Enter text'
|
||||
},
|
||||
FormGroup: {
|
||||
name: 'input',
|
||||
label: 'Input group',
|
||||
slots: {
|
||||
default: {
|
||||
component: {
|
||||
name: 'Input',
|
||||
props: {
|
||||
name: 'input',
|
||||
placeholder: 'Works with every form element'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Toggle: {
|
||||
modelValue: toggle,
|
||||
'onUpdate:modelValue': (v) => { toggle.value = v }
|
||||
},
|
||||
Checkbox: {
|
||||
name: 'checkbox'
|
||||
},
|
||||
Radio: {
|
||||
name: 'radio'
|
||||
},
|
||||
Select: {
|
||||
name: 'select',
|
||||
options: ['English', 'Spanish', 'French', 'German', 'Chinese']
|
||||
},
|
||||
SelectCustom: {
|
||||
modelValue: selectCustom,
|
||||
'onUpdate:modelValue': (v) => { selectCustom.value = v },
|
||||
textAttribute: 'label',
|
||||
options: people
|
||||
},
|
||||
Textarea: {
|
||||
name: 'textarea'
|
||||
},
|
||||
Tooltip: {
|
||||
text: 'Tooltip text'
|
||||
},
|
||||
Notification: {
|
||||
id: '1',
|
||||
title: 'Notification title',
|
||||
callback: 'console.log(\'Timer expired\')'
|
||||
},
|
||||
Modal: {
|
||||
modelValue: modal,
|
||||
'onUpdate:modelValue': (v) => { modal.value = v },
|
||||
component: {
|
||||
name: 'Button',
|
||||
props: {
|
||||
variant: 'secondary',
|
||||
label: 'Open modal',
|
||||
onClick: () => { modal.value = true }
|
||||
}
|
||||
},
|
||||
slots: {
|
||||
default: {
|
||||
tag: 'div',
|
||||
html: 'Modal content'
|
||||
},
|
||||
footer: {
|
||||
component: {
|
||||
name: 'Button',
|
||||
props: {
|
||||
label: 'Close',
|
||||
onClick: () => { modal.value = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Slideover: {
|
||||
modelValue: slideover,
|
||||
'onUpdate:modelValue': (v) => { slideover.value = v },
|
||||
component: {
|
||||
name: 'Button',
|
||||
props: {
|
||||
variant: 'secondary',
|
||||
label: 'Open slideover',
|
||||
onClick: () => { slideover.value = true }
|
||||
}
|
||||
},
|
||||
slots: {
|
||||
default: {
|
||||
tag: 'div',
|
||||
html: 'Slideover content'
|
||||
}
|
||||
}
|
||||
},
|
||||
Popover: {
|
||||
slots: {
|
||||
panel: {
|
||||
tag: 'div',
|
||||
class: 'u-bg-gray-100 rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 p-6',
|
||||
html: 'Popover content'
|
||||
}
|
||||
}
|
||||
},
|
||||
Tabs: {
|
||||
links: [{
|
||||
label: 'Usage',
|
||||
to: '/',
|
||||
exact: true
|
||||
}, {
|
||||
label: 'Examples',
|
||||
to: '/examples'
|
||||
}, {
|
||||
label: 'Migration',
|
||||
to: '/migration'
|
||||
}, {
|
||||
label: 'Tabs',
|
||||
to: '/components/Tabs'
|
||||
}]
|
||||
},
|
||||
Pills: {
|
||||
links: [{
|
||||
label: 'Usage',
|
||||
to: '/',
|
||||
exact: true
|
||||
}, {
|
||||
label: 'Examples',
|
||||
to: '/examples'
|
||||
}, {
|
||||
label: 'Migration',
|
||||
to: '/migration'
|
||||
}, {
|
||||
label: 'Pills',
|
||||
to: '/components/Pills'
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
const componentDefaultProps = defaultProps[params.component] || {}
|
||||
const { props: componentProps } = await component.__asyncLoader()
|
||||
|
||||
function lowercaseFirstLetter (string) {
|
||||
return string.charAt(0).toLowerCase() + string.slice(1)
|
||||
}
|
||||
|
||||
const refProps = Object.entries(componentProps).map(([key, prop]) => {
|
||||
const defaultValue = componentDefaultProps[key]
|
||||
const propDefault = (typeof prop.default === 'function' ? prop.default() : prop.default)
|
||||
let value = defaultValue !== undefined ? defaultValue : propDefault
|
||||
let type = prop.type
|
||||
if (Array.isArray(type)) {
|
||||
type = type[0].name
|
||||
} else {
|
||||
type = type.name
|
||||
}
|
||||
|
||||
let values
|
||||
if (prop.validator) {
|
||||
const arrayRegex = prop.validator.toString().match(/\[.*\]/g, '')
|
||||
if (arrayRegex) {
|
||||
values = JSON.parse(arrayRegex[0].replace(/'/g, '"')).filter(Boolean)
|
||||
} else {
|
||||
const $uiProp = $ui[lowercaseFirstLetter(params.component)][key]
|
||||
if ($uiProp) {
|
||||
values = Object.keys($uiProp).filter(Boolean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
if (type === 'String' && typeof value === 'string') {
|
||||
value = value.replace(/^'(.*)'$/, '$1')
|
||||
} else if (type === 'Array') {
|
||||
value = JSON.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
type,
|
||||
value,
|
||||
values,
|
||||
default: propDefault
|
||||
}
|
||||
})
|
||||
|
||||
const eventProps = Object.entries(componentDefaultProps)
|
||||
.filter(([key]) => !refProps.find(prop => prop.key === key))
|
||||
.filter(([key]) => !['slots'].includes(key))
|
||||
.reduce((acc, [key, value]) => {
|
||||
acc[key] = value
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const props = ref(refProps)
|
||||
const boundProps = computed(() => {
|
||||
const bound = {}
|
||||
for (const prop of props.value) {
|
||||
let value = prop.value
|
||||
if (value === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
if (prop.type === 'Array') {
|
||||
value = JSON.parse(value)
|
||||
} else if (prop.type === 'Number') {
|
||||
value = Number(value)
|
||||
} else if (prop.type === 'Function') {
|
||||
// eslint-disable-next-line no-new-func
|
||||
value = Function(value)
|
||||
}
|
||||
|
||||
bound[prop.key] = value
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return bound
|
||||
})
|
||||
|
||||
function toKebabCase (str) {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
||||
}
|
||||
|
||||
const { copy, copied } = useClipboard({ copiedDuring: 2000 })
|
||||
const onCopy = () => {
|
||||
copy(code.value)
|
||||
}
|
||||
|
||||
const code = computed(() => {
|
||||
let code = `<U${params.component}`
|
||||
for (const prop of props.value) {
|
||||
if (prop.value === null) {
|
||||
continue
|
||||
}
|
||||
if (prop.value === prop.default) {
|
||||
continue
|
||||
}
|
||||
|
||||
const key = toKebabCase(prop.key)
|
||||
code += `\n ${prop.type === 'Boolean' ? ':' : ''}${key === 'model-value' ? 'v-model' : key}="${prop.value}"`
|
||||
}
|
||||
code += '\n/>'
|
||||
return code
|
||||
})
|
||||
</script>
|
||||
@@ -1,65 +0,0 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
<div>
|
||||
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
|
||||
Dark mode
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p class="mt-1 text-lg u-text-gray-500">
|
||||
Dark mode implementation with <a href="https://color-mode.nuxtjs.org/" target="_blank" class="underline">Color Mode</a> module.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="font-bold text-2xl u-text-gray-900">
|
||||
Usage
|
||||
</h2>
|
||||
|
||||
<p>TailwindCSS takes advantage of the `dark` class on the html tag:</p>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm">
|
||||
{{ code4 }}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>The `@nuxtjs/color-mode` module is now installed by default, you can easily implement a toggle button:</p>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm">
|
||||
{{ code1 }}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<h2 class="font-bold text-2xl u-text-gray-900">
|
||||
Shortcuts
|
||||
</h2>
|
||||
|
||||
<p>A number of shortcuts are available to make your life with colors easier.</p>
|
||||
|
||||
<p>For each color utilities: `bg`, `text`, `border`, `ring` and `divide`, shortcuts for `white`, `black` and `gray` colors are generated (based on your prefix `u` by default) that handles the dark mode automatically:</p>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm">
|
||||
{{ code3 }}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<p>For example `u-bg-gray-100` is a shortcut for `bg-gray-100 dark:bg-gray-800`. Take a look at the <a href="https://github.com/nuxtlabs/ui/blob/dev/src/index.ts#L61" target="_blank" class="underline">shortcuts definitions</a>.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const code1 = `
|
||||
const colorMode = useColorMode()
|
||||
const toggleDark = () => {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}`
|
||||
const code3 = `
|
||||
<div class="u-bg-gray-100 border u-border-gray-200 u-text-gray-700"></div>
|
||||
`
|
||||
const code4 = `
|
||||
<div class="bg-white dark:bg-black"></div>
|
||||
`
|
||||
</script>
|
||||
@@ -1,473 +0,0 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
<div>
|
||||
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
|
||||
Examples
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p class="mt-1 text-lg u-text-gray-500">
|
||||
Examples of real-life usage of components.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Avatar:
|
||||
</div>
|
||||
|
||||
<UAvatar src="https://picsum.photos/200/300" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Button:
|
||||
</div>
|
||||
|
||||
<UButton variant="primary" icon="heroicons-outline:mail">
|
||||
Button text
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Modal:
|
||||
</div>
|
||||
|
||||
<UButton @click="toggleModalIsOpen()">
|
||||
Toggle modal!
|
||||
</UButton>
|
||||
|
||||
<UModal v-model="isModalOpen" @submit.prevent="onSubmit">
|
||||
<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">
|
||||
<UIcon name="heroicons-outline:exclamation" class="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||
</div>
|
||||
<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">
|
||||
Deactivate account
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm u-text-gray-500">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm" @click="open = false">
|
||||
Deactivate
|
||||
</button>
|
||||
<button ref="cancelButtonRef" type="button" class="mt-3 w-full inline-flex justify-center rounded-md border u-border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium u-text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm" @click="open = false">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</UModal>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Dropdown:
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Open menu!
|
||||
</UButton>
|
||||
</UDropdown>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Dropdown with avatar:
|
||||
</div>
|
||||
|
||||
<UDropdown :items="customDropdownItems" placement="bottom-end">
|
||||
<button class="flex">
|
||||
<UAvatar src="https://picsum.photos/200/300" />
|
||||
</button>
|
||||
</UDropdown>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Popover:
|
||||
</div>
|
||||
|
||||
<UPopover mode="hover" wrapper-class="inline-block relative">
|
||||
<template #default="{ open }">
|
||||
<UButton variant="secondary" :icon="open ? 'heroicons-solid:chevron-up' : 'heroicons-solid:chevron-down'" trailing icon-class="transition">
|
||||
Open popover!
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<div
|
||||
class="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5"
|
||||
>
|
||||
<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>
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Tooltip:
|
||||
</div>
|
||||
|
||||
<UTooltip text="Hello tooltip!" :shortcuts="['⌘', 'G']">
|
||||
<UIcon name="heroicons-outline:information-circle" class="w-6 h-6 u-text-gray-900 cursor-pointer" />
|
||||
</UTooltip>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Notifications:
|
||||
</div>
|
||||
<UButton icon="heroicons-outline:bell" variant="red" label="Trigger an error" @click="onNotificationClick" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Context menu:
|
||||
</div>
|
||||
|
||||
<UCard ref="contextMenuRef" class="relative" body-class="h-64" @click="isContextMenuOpen = false" @contextmenu.prevent="openContextMenu">
|
||||
<UContextMenu v-model="isContextMenuOpen" :virtual-element="virtualElement" width-class="w-48">
|
||||
<UCard @click.stop>
|
||||
Menu
|
||||
</UCard>
|
||||
</UContextMenu>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Command palette:
|
||||
</div>
|
||||
|
||||
<UCard body-class="">
|
||||
<UCommandPalette
|
||||
v-model="form.persons"
|
||||
multiple
|
||||
:placeholder="false"
|
||||
:options="{
|
||||
fuseOptions: {
|
||||
includeMatches: true
|
||||
}
|
||||
}"
|
||||
:groups="[{
|
||||
key: 'persons',
|
||||
commands: people
|
||||
}]"
|
||||
command-attribute="name"
|
||||
/>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Card:
|
||||
</div>
|
||||
|
||||
<UCard body-class="flex">
|
||||
<div class="flex-1 px-4 py-5 sm:p-6 space-y-3">
|
||||
<UFormGroup label="Email" name="email" required>
|
||||
<UInput v-model="form.email" type="email" name="email" required icon="heroicons-outline:mail" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Description" name="description">
|
||||
<UTextarea v-model="form.description" type="description" name="description" autoresize />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Person" name="person" required>
|
||||
<USelect
|
||||
v-model="form.personId"
|
||||
name="person"
|
||||
:options="people"
|
||||
placeholder="Select a person"
|
||||
text-attribute="name"
|
||||
value-attribute="id"
|
||||
icon="heroicons-outline:user"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="People" name="people" required>
|
||||
<USelectCustom v-model="form.person" name="people" :options="people" text-attribute="name" searchable />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Toggle" name="toggle">
|
||||
<UToggle v-model="form.toggle" name="toggle" icon-off="heroicons-solid:x" icon-on="heroicons-solid:check" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Notifications" label-class="text-base font-medium u-text-gray-900" description="How do you prefer to receive notifications?">
|
||||
<div class="space-y-4 mt-3">
|
||||
<URadio v-model="form.notification" value="email" label="Email" help="Email" />
|
||||
<URadio v-model="form.notification" value="phone" label="Phone (SMS)" help="Phone (SMS)" />
|
||||
<URadio v-model="form.notification" value="push" label="Push notification" help="Push notification" />
|
||||
</div>
|
||||
</UFormGroup>
|
||||
|
||||
<UCard body-class="px-4 py-5 space-y-5">
|
||||
<UCheckbox v-model="form.notifications" name="comments" value="comments" label="Comments" help="Get notified when someones posts a comment on a posting." />
|
||||
<UCheckbox v-model="form.notifications" name="candidates" value="candidates" label="Candidates" help="Get notified when a candidate applies for a job." />
|
||||
<UCheckbox v-model="form.notifications" name="offers" value="offers" label="Offers" help="Get notified when a candidate accepts or rejects an offer." />
|
||||
</UCard>
|
||||
|
||||
<div>
|
||||
<UCheckbox v-model="form.terms" label="I agree to the terms and conditions" name="terms" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<UButton type="submit" label="Submit" class="ml-auto" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3 px-4 py-5 sm:p-6 border-l u-border-gray-200 u-bg-gray-50">
|
||||
<pre class="whitespace-pre-wrap break-all">
|
||||
{{ form }}
|
||||
</pre>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const isModalOpen = ref(false)
|
||||
|
||||
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 form = reactive({
|
||||
email: '',
|
||||
description: '',
|
||||
toggle: false,
|
||||
notification: 'email',
|
||||
notifications: [],
|
||||
terms: false,
|
||||
personId: null,
|
||||
person: ref(people.value[0]),
|
||||
persons: ref([people.value[0]])
|
||||
})
|
||||
|
||||
const { $toast } = useNuxtApp()
|
||||
|
||||
const x = ref(0)
|
||||
const y = ref(0)
|
||||
const isContextMenuOpen = ref(false)
|
||||
const contextMenuRef = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousemove', ({ clientX, clientY }) => {
|
||||
x.value = clientX
|
||||
y.value = clientY
|
||||
})
|
||||
})
|
||||
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function openContextMenu () {
|
||||
const top = unref(y)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top,
|
||||
left
|
||||
})
|
||||
isContextMenuOpen.value = true
|
||||
}
|
||||
|
||||
function toggleModalIsOpen () {
|
||||
isModalOpen.value = !isModalOpen.value
|
||||
}
|
||||
|
||||
function onClick () {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('click')
|
||||
}
|
||||
|
||||
function onSubmit () {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('submit')
|
||||
}
|
||||
|
||||
const dropdownItems = [
|
||||
[{
|
||||
label: 'Edit',
|
||||
icon: 'heroicons-solid:pencil',
|
||||
click: (e) => {
|
||||
e.preventDefault()
|
||||
onClick()
|
||||
}
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'heroicons-solid:duplicate'
|
||||
}],
|
||||
[{
|
||||
label: 'Archive',
|
||||
icon: 'heroicons-solid:archive'
|
||||
}, {
|
||||
label: 'Move',
|
||||
icon: 'heroicons-solid:external-link',
|
||||
to: 'https://www.google.fr',
|
||||
target: '_blank'
|
||||
}],
|
||||
[{
|
||||
label: 'Delete',
|
||||
icon: 'heroicons-solid:trash'
|
||||
}]
|
||||
]
|
||||
|
||||
const customDropdownItems = [
|
||||
[{
|
||||
label: 'benjamincanac',
|
||||
avatar: { src: 'https://picsum.photos/200/300' },
|
||||
href: 'https://google.fr',
|
||||
target: '_blank'
|
||||
}],
|
||||
[{
|
||||
label: 'About',
|
||||
icon: 'heroicons-solid:plus',
|
||||
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 = () => {
|
||||
$toast.error({ title: 'Error', description: 'This is an error message' })
|
||||
}
|
||||
</script>
|
||||
@@ -1,117 +1,7 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
<div>
|
||||
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
|
||||
Documentation
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p class="mt-1 text-lg u-text-gray-500">
|
||||
Components library as a Nuxt3 module using <a href="https://tailwindcss.com" target="_blank" class="underline">TailwindCSS</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 class="font-bold text-2xl u-text-gray-900">
|
||||
Installation
|
||||
</h2>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm" v-html="code1" />
|
||||
</pre>
|
||||
|
||||
<p>Then, register the module in your `nuxt.config.js`:</p>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm" v-html="code2" />
|
||||
</pre>
|
||||
|
||||
<p>If you want latest updates, please use `@nuxthq/ui-edge` in your `package.json`:</p>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm" v-html="code3" />
|
||||
</pre>
|
||||
|
||||
<h2 class="font-bold text-2xl u-text-gray-900">
|
||||
Options
|
||||
</h2>
|
||||
|
||||
<p>- `preset`</p>
|
||||
|
||||
<p>Choose preset. Defaults to `tailwindui`. An object can also be used to override some parts of the default preset.</p>
|
||||
|
||||
<p>- `prefix`</p>
|
||||
|
||||
<p>Define the prefix of the imported components. Defaults to `u`.</p>
|
||||
|
||||
<p class="font-medium">
|
||||
Example:
|
||||
</p>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm" v-html="code4" />
|
||||
</pre>
|
||||
|
||||
<p>- `colors.primary`</p>
|
||||
|
||||
<p>Define the primary variant. Defaults to `indigo`. You can specify your own object of colors like here:</p>
|
||||
|
||||
<p class="font-medium">
|
||||
Example:
|
||||
</p>
|
||||
|
||||
<pre class="u-bg-gray-900 rounded-md u-text-white px-4">
|
||||
<code class="text-sm" v-html="code5" />
|
||||
</pre>
|
||||
|
||||
<p>- `colors.gray`</p>
|
||||
|
||||
<p>Define the gray variant. Defaults to `zinc`. You can like the `primary` color specify your own object. https://tailwindcss.com/docs/customizing-colors#default-color-palette</p>
|
||||
|
||||
<p>- `tailwindcss.theme`. Defaults to `{}`.</p>
|
||||
|
||||
<p>Define TailwindCSS theme: https://tailwindcss.com/docs/theme.</p>
|
||||
</div>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const code1 = `
|
||||
yarn add --dev @nuxthq/ui`
|
||||
|
||||
const code2 = `
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
|
||||
defineNuxtConfig({
|
||||
buildModules: [
|
||||
'@nuxthq/ui'
|
||||
]
|
||||
})`
|
||||
|
||||
const code3 = `
|
||||
{
|
||||
"devDependencies": {
|
||||
"@nuxthq/ui": "npm:@nuxthq/ui-edge@latest"
|
||||
}
|
||||
}`
|
||||
|
||||
const code4 = `
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
|
||||
defineNuxtConfig({
|
||||
ui: {
|
||||
prefix: 'tw'
|
||||
}
|
||||
})`
|
||||
|
||||
const code5 = `
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
|
||||
defineNuxtConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'blue'
|
||||
}
|
||||
}
|
||||
})`
|
||||
<script setup lang="ts">
|
||||
await navigateTo('/getting-started')
|
||||
</script>
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
<div>
|
||||
<h1 class="inline-block text-3xl font-extrabold u-text-gray-900 tracking-tight">
|
||||
Migration
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p class="mt-1 text-lg u-text-gray-500">
|
||||
Check out the components that have been migrated to Vue3 coming from `@nuxthq/volta-ui`.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<UCard body-class>
|
||||
<table class="min-w-full divide-y u-divide-gray-200">
|
||||
<thead class="u-bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
|
||||
Component
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
|
||||
Nuxt3 ready
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
|
||||
Composition API
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
|
||||
Preset system
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium u-text-gray-500 uppercase tracking-wider">
|
||||
Typescript
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(component, index) of components" :key="index" :class="index % 2 === 0 ? 'u-bg-white' : 'u-bg-gray-50'">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium u-text-gray-900">
|
||||
<NuxtLink :to="component.to" class="hover:underline">
|
||||
{{ component.label }}
|
||||
</NuxtLink>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
|
||||
<span v-if="component.nuxt3 || component.capi">✅</span>
|
||||
<span v-else>❌</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
|
||||
<span v-if="component.capi">✅</span>
|
||||
<span v-else>❌</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
|
||||
<span v-if="component.preset">✅</span>
|
||||
<span v-else>❌</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm u-text-gray-500">
|
||||
<span v-if="component.typescript">✅</span>
|
||||
<span v-else>❌</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const components = [
|
||||
{
|
||||
label: 'Avatar',
|
||||
to: '/components/Avatar',
|
||||
nuxt3: true,
|
||||
preset: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'AvatarGroup',
|
||||
to: '/components/AvatarGroup',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Badge',
|
||||
to: '/components/Badge',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Button',
|
||||
to: '/components/Button',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Dropdown',
|
||||
to: '/components/Dropdown',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Icon',
|
||||
to: '/components/Icon',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Link',
|
||||
to: '/components/Link',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Toggle',
|
||||
to: '/components/Toggle',
|
||||
nuxt3: true,
|
||||
preset: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Alert',
|
||||
to: '/components/Alert',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'AlertDialog',
|
||||
to: '/components/AlertDialog',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Input',
|
||||
to: '/components/Input',
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'FormGroup',
|
||||
to: '/components/FormGroup',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Checkbox',
|
||||
to: '/components/Checkbox',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Radio',
|
||||
to: '/components/Radio',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Select',
|
||||
to: '/components/Select',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'SelectCustom',
|
||||
to: '/components/SelectCustom',
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Textarea',
|
||||
to: '/components/Textarea',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Card',
|
||||
to: '/components/Card',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Container',
|
||||
to: '/components/Container',
|
||||
nuxt3: true,
|
||||
preset: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'CommandPalette',
|
||||
to: '/components/CommandPalette',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: false,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Tabs',
|
||||
to: '/components/Tabs',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Pills',
|
||||
to: '/components/Pills',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'VerticalNavigation',
|
||||
to: '/components/VerticalNavigation',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Modal',
|
||||
to: '/components/Modal',
|
||||
nuxt3: true,
|
||||
preset: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Notification',
|
||||
to: '/components/Notification',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Notifications',
|
||||
to: '/components/Notifications',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Popover',
|
||||
to: '/components/Popover',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Slideover',
|
||||
to: '/components/Slideover',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
preset: true,
|
||||
typescript: true
|
||||
},
|
||||
{
|
||||
label: 'Tooltip',
|
||||
to: '/components/Tooltip',
|
||||
nuxt3: true,
|
||||
capi: true,
|
||||
typescript: true
|
||||
}
|
||||
]
|
||||
</script>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
3
docs/public/icon-dark.svg
Normal file
3
docs/public/icon-dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M504.908 750H839.476C850.103 750.001 860.542 747.229 869.745 741.963C878.948 736.696 886.589 729.121 891.9 719.999C897.211 710.876 900.005 700.529 900 689.997C899.995 679.465 897.193 669.12 891.873 660.002L667.187 274.289C661.876 265.169 654.237 257.595 645.036 252.329C635.835 247.064 625.398 244.291 614.773 244.291C604.149 244.291 593.711 247.064 584.511 252.329C575.31 257.595 567.67 265.169 562.36 274.289L504.908 372.979L392.581 179.993C387.266 170.874 379.623 163.301 370.42 158.036C361.216 152.772 350.777 150 340.151 150C329.525 150 319.086 152.772 309.883 158.036C300.679 163.301 293.036 170.874 287.721 179.993L8.12649 660.002C2.80743 669.12 0.00462935 679.465 5.72978e-06 689.997C-0.00461789 700.529 2.78909 710.876 8.10015 719.999C13.4112 729.121 21.0523 736.696 30.255 741.963C39.4576 747.229 49.8973 750.001 60.524 750H270.538C353.748 750 415.112 713.775 457.336 643.101L559.849 467.145L614.757 372.979L779.547 655.834H559.849L504.908 750ZM267.114 655.737L120.551 655.704L340.249 278.586L449.87 467.145L376.474 593.175C348.433 639.03 316.577 655.737 267.114 655.737Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
docs/public/icon-light.svg
Normal file
3
docs/public/icon-light.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M504.908 750H839.476C850.103 750.001 860.542 747.229 869.745 741.963C878.948 736.696 886.589 729.121 891.9 719.999C897.211 710.876 900.005 700.529 900 689.997C899.995 679.465 897.193 669.12 891.873 660.002L667.187 274.289C661.876 265.169 654.237 257.595 645.036 252.329C635.835 247.064 625.398 244.291 614.773 244.291C604.149 244.291 593.711 247.064 584.511 252.329C575.31 257.595 567.67 265.169 562.36 274.289L504.908 372.979L392.581 179.993C387.266 170.874 379.623 163.301 370.42 158.036C361.216 152.772 350.777 150 340.151 150C329.525 150 319.086 152.772 309.883 158.036C300.679 163.301 293.036 170.874 287.721 179.993L8.12649 660.002C2.80743 669.12 0.00462935 679.465 5.72978e-06 689.997C-0.00461789 700.529 2.78909 710.876 8.10015 719.999C13.4112 729.121 21.0523 736.696 30.255 741.963C39.4576 747.229 49.8973 750.001 60.524 750H270.538C353.748 750 415.112 713.775 457.336 643.101L559.849 467.145L614.757 372.979L779.547 655.834H559.849L504.908 750ZM267.114 655.737L120.551 655.704L340.249 278.586L449.87 467.145L376.474 593.175C348.433 639.03 316.577 655.737 267.114 655.737Z" fill="#0C0C0D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
186
docs/tailwind.config.ts
Normal file
186
docs/tailwind.config.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import type { Config } from 'tailwindcss'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
|
||||
export default <Partial<Config>> {
|
||||
content: [
|
||||
'docs/content/**/*.md'
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter var', ...defaultTheme.fontFamily.sans]
|
||||
},
|
||||
maxWidth: {
|
||||
'8xl': '90rem'
|
||||
},
|
||||
typography: (theme) => {
|
||||
return {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
'h1, h2, h3, h4': {
|
||||
fontWeight: theme('fontWeight.bold'),
|
||||
'scroll-margin-top': 'var(--scroll-mt)'
|
||||
},
|
||||
'h1 a, h2 a, h3 a, h4 a': {
|
||||
borderBottom: 'none !important',
|
||||
color: 'inherit',
|
||||
fontWeight: 'inherit'
|
||||
},
|
||||
a: {
|
||||
fontWeight: theme('fontWeight.medium'),
|
||||
textDecoration: 'none',
|
||||
borderBottom: '1px solid transparent'
|
||||
},
|
||||
'a:hover': {
|
||||
borderColor: 'var(--tw-prose-links)'
|
||||
},
|
||||
'a:has(> code)': {
|
||||
borderColor: 'transparent !important'
|
||||
},
|
||||
'a code': {
|
||||
color: 'var(--tw-prose-code)',
|
||||
border: '1px dashed var(--tw-prose-pre-border)'
|
||||
},
|
||||
'a:hover code': {
|
||||
color: 'var(--tw-prose-links)',
|
||||
borderColor: 'var(--tw-prose-links)'
|
||||
},
|
||||
pre: {
|
||||
margin: '0',
|
||||
borderRadius: '0.375rem',
|
||||
border: '1px solid var(--tw-prose-pre-border)',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-words'
|
||||
},
|
||||
code: {
|
||||
backgroundColor: 'var(--tw-prose-pre-bg)',
|
||||
padding: '0.25rem 0.375rem',
|
||||
borderRadius: '0.375rem',
|
||||
border: '1px solid var(--tw-prose-pre-border)'
|
||||
},
|
||||
'code::before': {
|
||||
content: ''
|
||||
},
|
||||
'code::after': {
|
||||
content: ''
|
||||
},
|
||||
'blockquote p:first-of-type::before': {
|
||||
content: ''
|
||||
},
|
||||
'blockquote p:last-of-type::after': {
|
||||
content: ''
|
||||
},
|
||||
'input[type="checkbox"]': {
|
||||
color: 'rgb(var(--color-primary-500))',
|
||||
borderRadius: theme('borderRadius.DEFAULT'),
|
||||
borderColor: 'rgb(var(--color-gray-300))',
|
||||
height: theme('spacing.4'),
|
||||
width: theme('spacing.4'),
|
||||
marginTop: '-3.5px !important',
|
||||
marginBottom: '0 !important',
|
||||
'&:focus': {
|
||||
'--tw-ring-offset-width': 0
|
||||
}
|
||||
},
|
||||
'input[type="checkbox"]:checked': {
|
||||
borderColor: 'rgb(var(--color-primary-500))'
|
||||
},
|
||||
'input[type="checkbox"]:disabled': {
|
||||
opacity: 0.5,
|
||||
cursor: 'not-allowed'
|
||||
},
|
||||
'ul.contains-task-list': {
|
||||
marginLeft: '-1.625em'
|
||||
},
|
||||
'ul ul': {
|
||||
paddingLeft: theme('padding.6')
|
||||
},
|
||||
'ul ol': {
|
||||
paddingLeft: theme('padding.6')
|
||||
},
|
||||
'ul > li.task-list-item': {
|
||||
paddingLeft: '0 !important'
|
||||
},
|
||||
'ul > li.task-list-item input': {
|
||||
marginRight: '7px'
|
||||
},
|
||||
'ul > li.task-list-item > ul.contains-task-list': {
|
||||
marginLeft: 'initial'
|
||||
},
|
||||
'ul > li.task-list-item a': {
|
||||
marginBottom: 0
|
||||
},
|
||||
'ul > li.task-list-item::marker': {
|
||||
content: 'none'
|
||||
},
|
||||
'ul > li > p': {
|
||||
margin: 0
|
||||
},
|
||||
'ul > li > span.issue-badge, p > span.issue-badge': {
|
||||
verticalAlign: 'text-top',
|
||||
margin: '0 !important'
|
||||
},
|
||||
'ul > li > button': {
|
||||
verticalAlign: 'baseline !important'
|
||||
},
|
||||
table: {
|
||||
wordBreak: 'break-all'
|
||||
}
|
||||
}
|
||||
},
|
||||
primary: {
|
||||
css: {
|
||||
'--tw-prose-body': 'rgb(var(--color-gray-700))',
|
||||
'--tw-prose-headings': 'rgb(var(--color-gray-900))',
|
||||
'--tw-prose-lead': 'rgb(var(--color-gray-600))',
|
||||
'--tw-prose-links': 'rgb(var(--color-primary-500))',
|
||||
'--tw-prose-bold': 'rgb(var(--color-gray-900))',
|
||||
'--tw-prose-counters': 'rgb(var(--color-gray-500))',
|
||||
'--tw-prose-bullets': 'rgb(var(--color-gray-300))',
|
||||
'--tw-prose-hr': 'rgb(var(--color-gray-100))',
|
||||
'--tw-prose-quotes': 'rgb(var(--color-gray-900))',
|
||||
'--tw-prose-quote-borders': 'rgb(var(--color-gray-200))',
|
||||
'--tw-prose-captions': 'rgb(var(--color-gray-500))',
|
||||
'--tw-prose-code': 'rgb(var(--color-gray-900))',
|
||||
'--tw-prose-pre-code': 'rgb(var(--color-gray-900))',
|
||||
'--tw-prose-pre-bg': 'rgb(var(--color-gray-50))',
|
||||
'--tw-prose-pre-border': 'rgb(var(--color-gray-200))',
|
||||
'--tw-prose-th-borders': 'rgb(var(--color-gray-300))',
|
||||
'--tw-prose-td-borders': 'rgb(var(--color-gray-200))',
|
||||
'--tw-prose-invert-body': 'rgb(var(--color-gray-200))',
|
||||
'--tw-prose-invert-headings': theme('colors.white'),
|
||||
'--tw-prose-invert-lead': 'rgb(var(--color-gray-400))',
|
||||
'--tw-prose-invert-links': 'rgb(var(--color-primary-400))',
|
||||
'--tw-prose-invert-bold': theme('colors.white'),
|
||||
'--tw-prose-invert-counters': 'rgb(var(--color-gray-400))',
|
||||
'--tw-prose-invert-bullets': 'rgb(var(--color-gray-600))',
|
||||
'--tw-prose-invert-hr': 'rgb(var(--color-gray-800))',
|
||||
'--tw-prose-invert-quotes': 'rgb(var(--color-gray-100))',
|
||||
'--tw-prose-invert-quote-borders': 'rgb(var(--color-gray-700))',
|
||||
'--tw-prose-invert-captions': 'rgb(var(--color-gray-400))',
|
||||
'--tw-prose-invert-code': theme('colors.white'),
|
||||
'--tw-prose-invert-pre-code': theme('colors.white'),
|
||||
'--tw-prose-invert-pre-bg': 'rgb(var(--color-gray-800))',
|
||||
'--tw-prose-invert-pre-border': 'rgb(var(--color-gray-700))',
|
||||
'--tw-prose-invert-th-borders': 'rgb(var(--color-gray-700))',
|
||||
'--tw-prose-invert-td-borders': 'rgb(var(--color-gray-800))'
|
||||
}
|
||||
},
|
||||
invert: {
|
||||
css: {
|
||||
'--tw-prose-pre-border': 'var(--tw-prose-invert-pre-border)',
|
||||
'input[type="checkbox"]': {
|
||||
backgroundColor: 'rgb(var(--color-gray-800))',
|
||||
borderColor: 'rgb(var(--color-gray-700))'
|
||||
},
|
||||
'input[type="checkbox"]:checked': {
|
||||
backgroundColor: 'rgb(var(--color-primary-400))',
|
||||
borderColor: 'rgb(var(--color-primary-400))'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
docs/tsconfig.json
Normal file
3
docs/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
||||
55
package.json
55
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxthq/ui",
|
||||
"version": "0.1.15",
|
||||
"version": "2.0.3",
|
||||
"repository": "https://github.com/nuxtlabs/ui",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
@@ -14,48 +14,53 @@
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18 <19"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "nuxt-module-build",
|
||||
"prepack": "yarn build",
|
||||
"dev": "nuxi dev docs",
|
||||
"build:docs": "nuxi generate docs",
|
||||
"build:docs": "nuxi build docs",
|
||||
"lint": "eslint --ext .ts,.js,.vue .",
|
||||
"typecheck": "nuxi typecheck",
|
||||
"prepare": "nuxi prepare docs",
|
||||
"release": "yarn lint && standard-version && git push --follow-tags"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.4",
|
||||
"@iconify/vue": "^4.0.1",
|
||||
"@nuxt/kit": "^3.0.0",
|
||||
"@egoist/tailwindcss-icons": "^1.0.7",
|
||||
"@headlessui/vue": "1.7.10",
|
||||
"@iconify-json/heroicons": "^1.1.10",
|
||||
"@nuxt/kit": "^3.4.3",
|
||||
"@nuxtjs/color-mode": "^3.2.0",
|
||||
"@nuxtjs/tailwindcss": "^6.1.3",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@nuxtjs/tailwindcss": "^6.6.8",
|
||||
"@popperjs/core": "^2.11.7",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@vueuse/core": "^9.6.0",
|
||||
"@vueuse/integrations": "^9.6.0",
|
||||
"defu": "^6.1.1",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"@vueuse/integrations": "^10.1.2",
|
||||
"@vueuse/math": "^10.1.2",
|
||||
"defu": "^6.1.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"tailwindcss": "^3.2.4"
|
||||
"tailwindcss": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/module-builder": "^0.2.1",
|
||||
"@iconify-json/simple-icons": "^1.1.52",
|
||||
"@nuxt/content": "^2.6.0",
|
||||
"@nuxt/module-builder": "^0.3.1",
|
||||
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"eslint": "^8.28.0",
|
||||
"nuxt": "^3.0.0",
|
||||
"@nuxtjs/plausible": "^0.2.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^20.1.2",
|
||||
"@vueuse/nuxt": "^10.1.2",
|
||||
"eslint": "^8.40.0",
|
||||
"nuxt": "^3.4.3",
|
||||
"nuxt-component-meta": "^0.5.1",
|
||||
"nuxt-lodash": "^2.4.1",
|
||||
"standard-version": "^9.5.0",
|
||||
"unbuild": "^1.0.1",
|
||||
"vue-tsc": "^1.0.10"
|
||||
},
|
||||
"build": {
|
||||
"externals": [
|
||||
"tailwindcss",
|
||||
"tailwindcss/colors.js"
|
||||
]
|
||||
"unbuild": "^1.2.1",
|
||||
"vue-tsc": "1.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
6
src/env.d.ts
vendored
6
src/env.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
import type { DefaultPreset } from './runtime/presets/default'
|
||||
|
||||
declare module '#build/ui' {
|
||||
declare const preset: DefaultPreset
|
||||
export default preset
|
||||
}
|
||||
210
src/module.ts
210
src/module.ts
@@ -1,10 +1,11 @@
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, addTemplate, addPlugin, createResolver } from '@nuxt/kit'
|
||||
import { defu } from 'defu'
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin, resolvePath } from '@nuxt/kit'
|
||||
import colors from 'tailwindcss/colors.js'
|
||||
import type { Config } from 'tailwindcss'
|
||||
import { iconsPlugin, getIconCollections } from '@egoist/tailwindcss-icons'
|
||||
import { name, version } from '../package.json'
|
||||
import { colorsAsRegex, excludeColors } from './runtime/utils/colors'
|
||||
import defaultPreset from './runtime/presets/default'
|
||||
|
||||
import appConfig from './runtime/app.config'
|
||||
type DeepPartial<T> = Partial<{ [P in keyof T]: DeepPartial<T[P]> | { [key: string]: string } }>
|
||||
|
||||
// @ts-ignore
|
||||
delete colors.lightBlue
|
||||
@@ -17,41 +18,28 @@ delete colors.coolGray
|
||||
// @ts-ignore
|
||||
delete colors.blueGray
|
||||
|
||||
interface ColorsOptions {
|
||||
/**
|
||||
* @default 'indigo'
|
||||
*/
|
||||
primary?: string
|
||||
|
||||
/**
|
||||
* @default 'gray'
|
||||
*/
|
||||
gray?: string
|
||||
declare module 'nuxt/schema' {
|
||||
interface AppConfigInput {
|
||||
ui?: {
|
||||
primary?: string
|
||||
gray?: string
|
||||
colors?: string[]
|
||||
} & DeepPartial<typeof appConfig.ui>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ModuleOptions {
|
||||
preset?: object
|
||||
|
||||
/**
|
||||
* @default 'u'
|
||||
*/
|
||||
prefix?: string
|
||||
|
||||
colors?: ColorsOptions
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
global?: boolean
|
||||
|
||||
tailwindcss?: Partial<Config>
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
preset: {},
|
||||
prefix: 'u',
|
||||
colors: {
|
||||
primary: 'indigo',
|
||||
gray: 'gray'
|
||||
},
|
||||
tailwindcss: {
|
||||
theme: {}
|
||||
}
|
||||
icons: string[]
|
||||
}
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
@@ -63,16 +51,24 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
nuxt: '^3.0.0-rc.8'
|
||||
}
|
||||
},
|
||||
defaults,
|
||||
defaults: {
|
||||
prefix: 'u',
|
||||
icons: ['heroicons']
|
||||
},
|
||||
async setup (options, nuxt) {
|
||||
const { preset = {}, prefix, colors: { primary = 'indigo', gray = 'gray' } = {}, tailwindcss: { theme = {} } = {} } = options
|
||||
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Transpile runtime
|
||||
const runtimeDir = resolve('./runtime')
|
||||
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')
|
||||
|
||||
nuxt.options.css.push(resolve(runtimeDir, 'ui.css'))
|
||||
|
||||
const appConfigFile = await resolvePath(resolve(runtimeDir, 'app.config'))
|
||||
nuxt.hook('app:resolve', (app) => {
|
||||
app.configs.push(appConfigFile)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
nuxt.hook('tailwindcss:config', function (tailwindConfig: TailwindConfig) {
|
||||
@@ -81,104 +77,144 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
...tailwindConfig.theme.extend?.colors
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
tailwindConfig.theme.extend.colors = tailwindConfig.theme.extend.colors || {}
|
||||
// @ts-ignore
|
||||
globalColors.primary = tailwindConfig.theme.extend.colors.primary = globalColors[primary] || colors[primary]
|
||||
// @ts-ignore
|
||||
globalColors.gray = tailwindConfig.theme.extend.colors.gray = globalColors[gray] || colors[gray]
|
||||
globalColors.primary = tailwindConfig.theme.extend.colors.primary = {
|
||||
50: 'rgb(var(--color-primary-50) / <alpha-value>)',
|
||||
100: 'rgb(var(--color-primary-100) / <alpha-value>)',
|
||||
200: 'rgb(var(--color-primary-200) / <alpha-value>)',
|
||||
300: 'rgb(var(--color-primary-300) / <alpha-value>)',
|
||||
400: 'rgb(var(--color-primary-400) / <alpha-value>)',
|
||||
500: 'rgb(var(--color-primary-500) / <alpha-value>)',
|
||||
600: 'rgb(var(--color-primary-600) / <alpha-value>)',
|
||||
700: 'rgb(var(--color-primary-700) / <alpha-value>)',
|
||||
800: 'rgb(var(--color-primary-800) / <alpha-value>)',
|
||||
900: 'rgb(var(--color-primary-900) / <alpha-value>)',
|
||||
950: 'rgb(var(--color-primary-950) / <alpha-value>)'
|
||||
}
|
||||
|
||||
if (globalColors.gray) {
|
||||
globalColors.cool = tailwindConfig.theme.extend.colors.cool = colors.gray
|
||||
}
|
||||
|
||||
globalColors.gray = tailwindConfig.theme.extend.colors.gray = {
|
||||
50: 'rgb(var(--color-gray-50) / <alpha-value>)',
|
||||
100: 'rgb(var(--color-gray-100) / <alpha-value>)',
|
||||
200: 'rgb(var(--color-gray-200) / <alpha-value>)',
|
||||
300: 'rgb(var(--color-gray-300) / <alpha-value>)',
|
||||
400: 'rgb(var(--color-gray-400) / <alpha-value>)',
|
||||
500: 'rgb(var(--color-gray-500) / <alpha-value>)',
|
||||
600: 'rgb(var(--color-gray-600) / <alpha-value>)',
|
||||
700: 'rgb(var(--color-gray-700) / <alpha-value>)',
|
||||
800: 'rgb(var(--color-gray-800) / <alpha-value>)',
|
||||
900: 'rgb(var(--color-gray-900) / <alpha-value>)',
|
||||
950: 'rgb(var(--color-gray-950) / <alpha-value>)'
|
||||
}
|
||||
|
||||
const variantColors = excludeColors(globalColors)
|
||||
const safeColorsAsRegex = colorsAsRegex(variantColors)
|
||||
|
||||
nuxt.options.appConfig.ui = {
|
||||
...nuxt.options.appConfig.ui,
|
||||
primary: 'sky',
|
||||
gray: 'cool',
|
||||
colors: variantColors
|
||||
}
|
||||
|
||||
tailwindConfig.safelist = tailwindConfig.safelist || []
|
||||
tailwindConfig.safelist.push(...[{
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-400`)
|
||||
},
|
||||
{
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(100|600|700)`),
|
||||
variants: ['hover', 'disabled', 'dark']
|
||||
},
|
||||
{
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-(100|800)`),
|
||||
tailwindConfig.safelist.push(...['bg-gray-400', {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|400|500)`)
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-500`),
|
||||
variants: ['disabled']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(400|950)`),
|
||||
variants: ['dark']
|
||||
},
|
||||
{
|
||||
pattern: new RegExp(`ring-(${safeColorsAsRegex})-(500)`),
|
||||
variants: ['focus']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(500|900|950)`),
|
||||
variants: ['dark:hover']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark:disabled']
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|100|600)`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${safeColorsAsRegex})-500`),
|
||||
variants: ['focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`outline-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${safeColorsAsRegex})-500`),
|
||||
variants: ['focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`ring-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark', 'dark:focus-visible']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-400`),
|
||||
variants: ['dark']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-600`),
|
||||
variants: ['hover']
|
||||
}, {
|
||||
pattern: new RegExp(`text-(${safeColorsAsRegex})-500`),
|
||||
variants: ['dark:hover']
|
||||
}])
|
||||
|
||||
const ui: object = defu(preset, defaultPreset(variantColors))
|
||||
|
||||
addTemplate({
|
||||
filename: 'ui.mjs',
|
||||
getContents: () => `export default ${JSON.stringify(ui)}`
|
||||
})
|
||||
addTemplate({
|
||||
filename: 'ui.d.ts',
|
||||
write: true,
|
||||
getContents: () => 'declare const d: any; export default d;'
|
||||
})
|
||||
tailwindConfig.plugins = tailwindConfig.plugins || []
|
||||
tailwindConfig.plugins.push(iconsPlugin({ collections: getIconCollections(options.icons as any[]) }))
|
||||
})
|
||||
|
||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||
await installModule('@nuxtjs/tailwindcss', {
|
||||
viewer: false,
|
||||
exposeConfig: true,
|
||||
config: {
|
||||
darkMode: 'class',
|
||||
theme,
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/line-clamp'),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/typography')
|
||||
],
|
||||
content: [
|
||||
resolve(runtimeDir, 'components/**/*.{vue,js,ts}'),
|
||||
resolve(runtimeDir, 'presets/*.{mjs,js,ts}')
|
||||
],
|
||||
safelist: [
|
||||
'dark',
|
||||
{
|
||||
pattern: /rounded-(sm|md|lg|xl|2xl|3xl)/,
|
||||
variants: ['sm']
|
||||
}
|
||||
resolve(runtimeDir, '*.{mjs,js,ts}')
|
||||
]
|
||||
},
|
||||
cssPath: resolve(runtimeDir, 'tailwind.css')
|
||||
}
|
||||
})
|
||||
|
||||
addPlugin(resolve(runtimeDir, 'plugins', 'toast.client'))
|
||||
addPlugin(resolve(runtimeDir, 'plugins', 'clipboard.client'))
|
||||
addPlugin({
|
||||
src: resolve(runtimeDir, 'plugins', 'colors')
|
||||
})
|
||||
|
||||
addComponentsDir({
|
||||
path: resolve(runtimeDir, 'components', 'elements'),
|
||||
prefix,
|
||||
watch: false
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimeDir, 'components', 'feedback'),
|
||||
prefix,
|
||||
prefix: options.prefix,
|
||||
global: options.global,
|
||||
watch: false
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimeDir, 'components', 'forms'),
|
||||
prefix,
|
||||
prefix: options.prefix,
|
||||
global: options.global,
|
||||
watch: false
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimeDir, 'components', 'layout'),
|
||||
prefix,
|
||||
prefix: options.prefix,
|
||||
global: options.global,
|
||||
watch: false
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimeDir, 'components', 'navigation'),
|
||||
prefix,
|
||||
prefix: options.prefix,
|
||||
global: options.global,
|
||||
watch: false
|
||||
})
|
||||
addComponentsDir({
|
||||
path: resolve(runtimeDir, 'components', 'overlays'),
|
||||
prefix,
|
||||
prefix: options.prefix,
|
||||
global: options.global,
|
||||
watch: false
|
||||
})
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user