mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-16 04:58:12 +01:00
Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f99b9e283a | ||
|
|
246449b328 | ||
|
|
ea740bf10a | ||
|
|
2ded24bec9 | ||
|
|
180a1df374 | ||
|
|
b9edf31aed | ||
|
|
526f84692e | ||
|
|
d66f4c5e46 | ||
|
|
ec3fd88472 | ||
|
|
0b097352b4 | ||
|
|
85b10ba4ee | ||
|
|
4ac0e0c481 | ||
|
|
617567d6e5 | ||
|
|
e59fe42cc9 | ||
|
|
1743ea91d5 | ||
|
|
fa59ea9d83 | ||
|
|
efb9e8a263 | ||
|
|
0931372157 | ||
|
|
7f00ec6c3d | ||
|
|
2bdeb04f56 | ||
|
|
c834f401cd | ||
|
|
421f3bd2be | ||
|
|
e63d5b74fc | ||
|
|
9ffcef9cd0 | ||
|
|
1aa8376d4f | ||
|
|
94f24da723 | ||
|
|
44457a0530 | ||
|
|
3fa10aa4eb | ||
|
|
ead46f6798 | ||
|
|
1bfcf31964 | ||
|
|
b795f0f65b | ||
|
|
d93e995298 | ||
|
|
00641b1439 | ||
|
|
07197c531e | ||
|
|
4ac9a613e3 | ||
|
|
5336436da0 | ||
|
|
7588eca148 | ||
|
|
c0a4b28f54 | ||
|
|
157e34f2c0 | ||
|
|
6675bcf396 | ||
|
|
3d43fc7a32 | ||
|
|
b3b349aab7 | ||
|
|
ab67659412 | ||
|
|
c090a9e9cd | ||
|
|
5e3357b696 | ||
|
|
11941bd581 | ||
|
|
036658b6de | ||
|
|
86dd6092f3 | ||
|
|
cba95de72b | ||
|
|
4d8dbfb912 | ||
|
|
703fdef9bd | ||
|
|
c78d5ab0d3 | ||
|
|
10f3c0c271 | ||
|
|
370c31296a | ||
|
|
f5b57bb3c8 | ||
|
|
8286d15d1e | ||
|
|
f528a5ebc0 | ||
|
|
5fea0eb8d5 | ||
|
|
5ede417bd8 | ||
|
|
066b239299 | ||
|
|
97da6c6343 | ||
|
|
6d9e4d424b | ||
|
|
52192a4ac0 | ||
|
|
a6c53e4d20 | ||
|
|
e8b46540d8 | ||
|
|
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 |
12
.eslintrc.cjs
Normal file
12
.eslintrc.cjs
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@nuxt/eslint-config'],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 0,
|
||||
'vue/max-attributes-per-line': ['error', {
|
||||
singleline: {
|
||||
max: 5
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'@nuxtjs/eslint-config-typescript'
|
||||
],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 0
|
||||
}
|
||||
}
|
||||
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug report to help us improve the module.
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- **IMPORTANT!**
|
||||
Before reporting a bug, please make sure that you have read through our documentation and you think your problem is indeed an issue related to our module. -->
|
||||
|
||||
### Version
|
||||
@nuxthq/ui: <!-- ex: v2.0.0 -->
|
||||
nuxt: <!-- ex: v3.5.0 -->
|
||||
|
||||
### Reproduction Link
|
||||
|
||||
<!--
|
||||
A minimal test case based on one of:
|
||||
- a GitHub repository that can reproduce the bug
|
||||
- https://stackblitz.com/edit/nuxtlabs-ui
|
||||
-->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
|
||||
### What is Expected?
|
||||
|
||||
|
||||
### What is actually happening?
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Nuxt Community Discord
|
||||
url: https://discord.nuxtjs.org/
|
||||
about: Consider asking questions about the module here.
|
||||
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea or enhancement for the module.
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Is your feature request related to a problem? Please describe.
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
### Describe the solution you'd like
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
### Describe alternatives you've considered
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
### Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about the module.
|
||||
title: ''
|
||||
labels: 'question'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- **IMPORTANT!**
|
||||
Please make sure to look for an answer to your question in our documentation and the documentation before asking a question here.
|
||||
|
||||
If you have a general question regarding the module use Discord `modules` channel. Thanks!
|
||||
|
||||
Nuxt Discord: https://discord.nuxtjs.org/
|
||||
-->
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -2,6 +2,61 @@
|
||||
|
||||
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.1.0](https://github.com/nuxtlabs/ui/compare/v2.0.4...v2.1.0) (2023-05-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **app.config:** trailing space ([703fdef](https://github.com/nuxtlabs/ui/commit/703fdef9bd4c0e26b0e38a13c30aff5b1d9d19aa))
|
||||
* **ButtonGroup/AvatarGroup:** allow `v-for` ([#173](https://github.com/nuxtlabs/ui/issues/173)) ([3fa10aa](https://github.com/nuxtlabs/ui/commit/3fa10aa4ebf9ff7d443f8f2564dcaf9b63ce1fa8))
|
||||
* **DocsPageHeader:** github component link ([#182](https://github.com/nuxtlabs/ui/issues/182)) ([7f00ec6](https://github.com/nuxtlabs/ui/commit/7f00ec6c3d059e5e78172a8e4bab905a7f02fa63))
|
||||
* **Input:** expose ref ([2ded24b](https://github.com/nuxtlabs/ui/commit/2ded24bec90a5ea6665ab6895ced15d9dd49e8ef))
|
||||
* **module:** add `.mjs` extension to tailwind `content` when builded ([246449b](https://github.com/nuxtlabs/ui/commit/246449b32850db805c1133151b8449687e7c14be)), closes [#172](https://github.com/nuxtlabs/ui/issues/172)
|
||||
* **Textarea:** expose ref ([ea740bf](https://github.com/nuxtlabs/ui/commit/ea740bf10a6090545ed58ff26322ee3a679b5452))
|
||||
|
||||
### [2.0.4](https://github.com/nuxtlabs/ui/compare/v2.0.3...v2.0.4) (2023-05-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **SelectMenu:** add missing `inline-flex` on wrapper ([e8b4654](https://github.com/nuxtlabs/ui/commit/e8b46540d8767c7a63c0ff8e28263615626916e7))
|
||||
|
||||
### [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)
|
||||
|
||||
|
||||
|
||||
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 NuxtLabs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
57
README.md
57
README.md
@@ -1,6 +1,22 @@
|
||||
# @nuxthq/ui
|
||||
# NuxtLabs UI
|
||||
|
||||
Components library as a Nuxt module using [TailwindCSS](https://tailwindcss.com) and [HeadlessUI](https://headlessui.com).
|
||||
[![npm version][npm-version-src]][npm-version-href]
|
||||
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
||||
[![License][license-src]][license-href]
|
||||
[![Nuxt][nuxt-src]][nuxt-href]
|
||||
|
||||
This module has been developed by [NuxtLabs](https://nuxtlabs.com/) for [Volta](https://volta.net) and [Nuxt Studio](https://nuxt.studio/). It provides everything related to UI when building a Nuxt application, including components, icons, colors, dark mode and also keyboard shortcuts.
|
||||
|
||||
[](https://ui.nuxtlabs.com)
|
||||
|
||||
## Features
|
||||
|
||||
- Built with [Headless UI](https://headlessui.dev/) and [Tailwind CSS](https://tailwindcss.com/)
|
||||
- HMR support through Nuxt App Config
|
||||
- Dark mode support
|
||||
- Keyboard shortcuts
|
||||
- Bundled icons
|
||||
- Fully typed
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -8,13 +24,11 @@ Components library as a Nuxt module using [TailwindCSS](https://tailwindcss.com)
|
||||
yarn add --dev @nuxthq/ui
|
||||
```
|
||||
|
||||
Then, register the module in your `nuxt.config.js`:
|
||||
Then, register the module in your `nuxt.config.ts`:
|
||||
|
||||
```js
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
buildModules: [
|
||||
modules: [
|
||||
'@nuxthq/ui'
|
||||
]
|
||||
})
|
||||
@@ -29,3 +43,34 @@ If you want latest updates, please use `@nuxthq/ui-edge` in your `package.json`:
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit http://ui.nuxtlabs.com to view the documentation.
|
||||
|
||||
## 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)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [MIT license](https://github.com/nuxtlabs/ui/blob/dev/LICENSE.md).
|
||||
|
||||
<!-- Badges -->
|
||||
[npm-version-src]: https://img.shields.io/npm/v/@nuxthq/ui/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
[npm-version-href]: https://npmjs.com/package/@nuxthq/ui
|
||||
|
||||
[npm-downloads-src]: https://img.shields.io/npm/dm/@nuxthq/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
[npm-downloads-href]: https://npmjs.com/package/@nuxthq/ui
|
||||
|
||||
[license-src]: https://img.shields.io/github/license/nuxtlabs/ui.svg?style=flat&colorA=18181B&colorB=28CF8D
|
||||
[license-href]: https://github.com/nuxtlabs/ui/blob/main/LICENSE
|
||||
|
||||
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
|
||||
[nuxt-href]: https://nuxt.com
|
||||
|
||||
37
docs/app.vue
37
docs/app.vue
@@ -3,35 +3,54 @@
|
||||
<Header />
|
||||
|
||||
<UContainer>
|
||||
<NuxtPage />
|
||||
<div class="relative grid lg:grid-cols-10 lg:gap-8">
|
||||
<DocsAside class="lg:col-span-2" />
|
||||
|
||||
<div class="relative pt-8 pb-16" :class="[toc ? 'lg:col-span-6' : 'lg:col-span-8']">
|
||||
<DocsPageHeader />
|
||||
|
||||
<NuxtPage />
|
||||
|
||||
<DocsPageFooter class="mt-12" />
|
||||
|
||||
<hr class="border-gray-200 dark:border-gray-800 my-6">
|
||||
|
||||
<DocsPrevNext />
|
||||
|
||||
<DocsFooter class="mt-16" />
|
||||
</div>
|
||||
|
||||
<DocsToc v-if="toc" class="lg:col-span-2 order-first lg:order-last" />
|
||||
</div>
|
||||
</UContainer>
|
||||
|
||||
<DocsSearch />
|
||||
<ClientOnly>
|
||||
<DocsSearch />
|
||||
</ClientOnly>
|
||||
|
||||
<UNotifications />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const colorScheme = usePreferredColorScheme()
|
||||
const { toc } = useContent()
|
||||
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({
|
||||
titleTemplate: title => title && title !== 'nuxthq/ui' ? `${title} - nuxthq/ui` : 'nuxthq/ui',
|
||||
titleTemplate: title => title && title.includes('NuxtLabs UI') ? title : `${title} - NuxtLabs UI`,
|
||||
meta: [
|
||||
{ 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: 'icon', type: 'image/svg+xml', href }
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' }
|
||||
],
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
@@ -40,4 +59,10 @@ useHead({
|
||||
class: 'antialiased font-sans text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-900'
|
||||
}
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
ogImage: '/social-preview.jpg',
|
||||
twitterImage: '/social-preview.jpg',
|
||||
twitterCard: 'summary_large_image'
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<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">
|
||||
<NuxtLink to="/" class="flex items-end gap-1.5 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
|
||||
NuxtLabs<span class="text-primary-500 dark:text-primary-400">UI</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,12 @@
|
||||
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"
|
||||
|
||||
9
docs/components/LogoLabs.vue
Normal file
9
docs/components/LogoLabs.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg width="312" height="78" viewBox="0 0 312 78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M65.6381 78H109.132C110.513 78.0002 111.87 77.6398 113.067 76.9552C114.263 76.2705 115.257 75.2857 115.947 74.0998C116.637 72.9139 117.001 71.5688 117 70.1996C116.999 68.8305 116.635 67.4856 115.944 66.3003L86.7343 16.1575C86.0439 14.9719 85.0508 13.9873 83.8547 13.3028C82.6586 12.6183 81.3017 12.2579 79.9205 12.2579C78.5393 12.2579 77.1825 12.6183 75.9864 13.3028C74.7903 13.9873 73.7971 14.9719 73.1067 16.1575L65.6381 28.9873L51.0356 3.89908C50.3446 2.71356 49.351 1.72914 48.1546 1.04472C46.9581 0.360308 45.6011 0 44.2196 0C42.8382 0 41.4811 0.360308 40.2847 1.04472C39.0883 1.72914 38.0947 2.71356 37.4037 3.89908L1.05644 66.3003C0.364965 67.4856 0.000601816 68.8305 7.44871e-07 70.1996C-0.000600326 71.5688 0.362582 72.9139 1.05302 74.0998C1.74346 75.2857 2.7368 76.2705 3.93315 76.9552C5.12949 77.6398 6.48665 78.0002 7.86812 78H35.1699C45.9872 78 53.9645 73.2907 59.4537 64.1032L72.7803 41.2289L79.9184 28.9873L101.341 65.7584H72.7803L65.6381 78ZM34.7248 65.7458L15.6717 65.7416L44.2324 16.7162L58.483 41.2289L48.9416 57.6127C45.2963 63.5739 41.155 65.7458 34.7248 65.7458Z" fill="currentColor" />
|
||||
<path d="M175.417 77.3598V66.9562H149.03V21.3406H136.5V77.3598H175.417Z" fill="currentColor" />
|
||||
<path d="M198.81 78C203.706 78 208.103 76.0793 210.178 73.1183V77.3598H221.795V37.026H210.178V41.0274C207.854 38.2264 203.706 36.3858 198.644 36.3858C186.446 36.3858 179.061 44.6286 179.061 57.1929C179.061 69.7572 186.446 78 198.81 78ZM200.635 68.3967C194.495 68.3967 190.429 63.9152 190.429 57.1929C190.429 50.3906 194.495 45.909 200.635 45.909C206.859 45.909 210.925 50.3906 210.925 57.1929C210.925 63.9152 206.859 68.3967 200.635 68.3967Z" fill="currentColor" />
|
||||
<path d="M254.606 78C266.97 78 274.604 69.7572 274.604 57.1929C274.604 44.6286 266.97 36.3858 254.772 36.3858C249.544 36.3858 245.478 38.3064 243.155 41.2674V19.5H231.621V77.3598H243.155V72.9583C245.478 76.0793 249.793 78 254.606 78ZM252.78 68.3967C246.557 68.3967 242.491 63.9152 242.491 57.1929C242.491 50.3906 246.557 45.909 252.78 45.909C258.838 45.909 262.987 50.3906 262.987 57.1929C262.987 63.9152 258.838 68.3967 252.78 68.3967Z" fill="currentColor" />
|
||||
<path d="M295.736 78C305.528 78 312 72.9583 312 65.2757C312 47.0294 289.098 56.3926 289.098 48.1498C289.098 45.5889 291.339 44.2285 294.575 44.2285C297.728 44.2285 300.964 46.0691 301.462 49.5103H311.502C311.087 41.5876 304.283 36.3858 294.243 36.3858C285.696 36.3858 279.307 41.4275 279.307 48.5499C279.307 65.5157 301.794 57.433 301.794 65.6758C301.794 67.9166 299.304 69.5971 295.736 69.5971C291.422 69.5971 288.517 67.3564 288.102 63.7551H278.145C278.56 72.4781 285.53 78 295.736 78Z" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -2,7 +2,7 @@
|
||||
<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="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"
|
||||
: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" />
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<kbd class="inline-flex items-center justify-center font-sans font-semibold px-1 h-5 min-w-[20px] text-[11px] rounded !my-0 align-text-top ring-1 ring-gray-300 dark:ring-gray-700">
|
||||
<ClientOnly>
|
||||
{{ shortcut }}
|
||||
</ClientOnly>
|
||||
</kbd>
|
||||
<UKbd class="!my-0 align-text-top">
|
||||
{{ shortcut }}
|
||||
</UKbd>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
16
docs/components/content/VoltaEmbed.vue
Normal file
16
docs/components/content/VoltaEmbed.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<iframe :src="src" class="w-full min-h-[calc(100vh/1.5)] border border-gray-200 dark:border-gray-800 rounded-md" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
token: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const src = computed(() => `https://volta.net/embed/${props.token}?gray=${appConfig.ui.gray}&primary=${appConfig.ui.primary}`)
|
||||
</script>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
const groups = computed(() => {
|
||||
return [{
|
||||
key: 'users',
|
||||
label: q => q && `Users matching “${q}”...`,
|
||||
search: async (q) => {
|
||||
if (!q) {
|
||||
return []
|
||||
}
|
||||
|
||||
const users = await $fetch(`https://jsonplaceholder.typicode.com/users`, { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
}
|
||||
}].filter(Boolean)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette :groups="groups" :autoselect="false" />
|
||||
</template>
|
||||
@@ -20,7 +20,8 @@ const selected = ref([people[3]])
|
||||
v-model="selected"
|
||||
multiple
|
||||
nullable
|
||||
:autoselect="false"
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
:fuse="{ resultLimit: 6 }"
|
||||
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -10,10 +10,10 @@ const users = [
|
||||
]
|
||||
|
||||
const actions = [
|
||||
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file') },
|
||||
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder') },
|
||||
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag') },
|
||||
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label') }
|
||||
{ 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
|
||||
@@ -42,5 +42,5 @@ function onSelect (option) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette ref="commandPaletteRef" :groups="groups" @update:model-value="onSelect" />
|
||||
<UCommandPalette ref="commandPaletteRef" :groups="groups" :autoselect="false" @update:model-value="onSelect" />
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
@@ -19,9 +19,9 @@ const selected = ref([])
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="open">
|
||||
<UModal v-model="isOpen">
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function openContextMenu () {
|
||||
const top = unref(y)
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
@@ -20,12 +21,12 @@ function openContextMenu () {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full" @contextmenu.prevent="openContextMenu">
|
||||
<Placeholder class="h-20 w-full flex items-center justify-center">
|
||||
<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" width-class="w-48">
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement">
|
||||
<div class="p-4">
|
||||
Menu
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,12 @@ const items = [
|
||||
}
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid'
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E']
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid'
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D']
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
@@ -19,7 +21,8 @@ const items = [
|
||||
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||
}], [{
|
||||
label: 'Delete',
|
||||
icon: 'i-heroicons-trash-20-solid'
|
||||
icon: 'i-heroicons-trash-20-solid',
|
||||
shortcuts: ['⌘', 'D']
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
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>
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="open">
|
||||
<UModal v-model="isOpen">
|
||||
<div class="p-4">
|
||||
<Placeholder class="h-48" />
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="open">
|
||||
<UModal v-model="isOpen">
|
||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<Placeholder class="h-8" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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()
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<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>
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="open">
|
||||
<USlideover v-model="isOpen">
|
||||
<div class="p-4 h-full">
|
||||
<Placeholder class="w-full h-full" />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<UTooltip text="Tooltip">
|
||||
<UButton color="gray" label="Button" />
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
@@ -50,7 +50,7 @@ export default defineComponent({
|
||||
<div class="group relative" :class="`language-${language}`">
|
||||
<UButton
|
||||
:icon="icon"
|
||||
variant="link"
|
||||
variant="solid"
|
||||
class="absolute right-3 top-3 opacity-0 group-hover:opacity-100 transition-opacity z-[1]"
|
||||
size="xs"
|
||||
tabindex="-1"
|
||||
|
||||
@@ -24,8 +24,14 @@ 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 h-14 text-lg bg-white dark:bg-gray-900',
|
||||
icon: 'pointer-events-none absolute left-3 h-6 w-6 text-primary-500 dark:text-primary-400'
|
||||
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',
|
||||
@@ -60,6 +66,7 @@ const ui = {
|
||||
:ui="ui"
|
||||
:close="close"
|
||||
:empty="empty"
|
||||
:autoselect="false"
|
||||
command-attribute="title"
|
||||
:fuse="{
|
||||
fuseOptions: { keys: ['title', 'category'] },
|
||||
|
||||
@@ -53,5 +53,12 @@ const ui = {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette ref="commandPaletteRef" :groups="groups" icon="" :ui="ui" placeholder="Search for apps and commands" />
|
||||
<UCommandPalette
|
||||
ref="commandPaletteRef"
|
||||
:groups="groups"
|
||||
icon=""
|
||||
:ui="ui"
|
||||
:autoselect="false"
|
||||
placeholder="Search for apps and commands"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<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]"
|
||||
>
|
||||
<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">
|
||||
<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
|
||||
@@ -14,9 +12,9 @@
|
||||
>
|
||||
Search
|
||||
|
||||
<div class="hidden lg:flex items-center gap-1 ml-auto -my-1">
|
||||
<Shortcut value="meta" />
|
||||
<Shortcut value="K" />
|
||||
<div class="hidden lg:flex items-center gap-0.5 ml-auto -my-1">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</UButton>
|
||||
</div>
|
||||
@@ -30,4 +28,5 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
const { isSearchModalOpen } = useDocs()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
class="mt-1"
|
||||
:ui="{
|
||||
wrapper: 'border-l border-gray-200 dark:border-gray-800 space-y-2',
|
||||
spacing: 'pl-4',
|
||||
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'
|
||||
|
||||
10
docs/components/docs/DocsFooter.vue
Normal file
10
docs/components/docs/DocsFooter.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<footer class="flex items-center justify-end gap-1.5">
|
||||
<div class="flex items-baseline gap-1.5 text-sm text-center text-gray-500 dark:text-gray-400">
|
||||
Made by
|
||||
<NuxtLink to="https://nuxtlabs.com" aria-label="NuxtLabs">
|
||||
<LogoLabs class="text-white w-14 h-auto" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
17
docs/components/docs/DocsPageFooter.vue
Normal file
17
docs/components/docs/DocsPageFooter.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between gap-1.5">
|
||||
<UButton
|
||||
v-if="page"
|
||||
:to="`https://github.com/nuxtlabs/ui/edit/dev/docs/content/${page._file}`"
|
||||
label="Edit this page on GitHub"
|
||||
color="primary"
|
||||
variant="link"
|
||||
:padded="false"
|
||||
icon="i-heroicons-pencil-square"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { page } = useContent()
|
||||
</script>
|
||||
@@ -22,7 +22,7 @@
|
||||
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`"
|
||||
:to="`https://github.com/nuxtlabs/ui/blob/dev/src/runtime/components/${page._dir}/${page.title.replace(' ', '')}.vue`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
<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" />
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<DocsPrevNextCard v-if="prev" :title="prev.navigation?.title || prev.title" :description="prev.navigation?.description || prev.description" :to="prev._path" icon="i-heroicons-arrow-left-20-solid" />
|
||||
<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" />
|
||||
<DocsPrevNextCard
|
||||
v-if="next"
|
||||
:title="next.navigation?.title || next.title"
|
||||
:description="next.navigation?.description || next.description"
|
||||
:to="next._path"
|
||||
icon="i-heroicons-arrow-right-20-solid"
|
||||
class="text-right"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
36
docs/components/docs/DocsPrevNextCard.vue
Normal file
36
docs/components/docs/DocsPrevNextCard.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<NuxtLink :to="to" class="block px-5 py-8 border not-prose rounded-lg border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/25 group">
|
||||
<div v-if="icon" class="inline-flex items-center rounded-full p-1.5 bg-gray-50 dark:bg-gray-800 group-hover:bg-primary-50 dark:group-hover:bg-primary-400/10 ring-1 ring-gray-300 dark:ring-gray-700 mb-4 group-hover:ring-primary-500/50 dark:group-hover:ring-primary-400/50">
|
||||
<UIcon :name="icon" class="w-5 h-5 text-gray-900 dark:text-gray-100 group-hover:text-primary-500 dark:group-hover:text-primary-400" />
|
||||
</div>
|
||||
|
||||
<p class="text-gray-900 dark:text-gray-50 font-medium text-[15px] mb-1">
|
||||
{{ title }}
|
||||
</p>
|
||||
|
||||
<p class="text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
{{ description }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<UModal
|
||||
v-model="isSearchModalOpen"
|
||||
:ui="{
|
||||
spacing: 'sm:p-4',
|
||||
padding: 'sm:p-4',
|
||||
rounded: 'sm:rounded-lg',
|
||||
width: 'sm:max-w-3xl',
|
||||
height: 'h-screen sm:h-[28rem]'
|
||||
@@ -13,7 +13,7 @@
|
||||
:groups="groups"
|
||||
command-attribute="title"
|
||||
:fuse="{
|
||||
fuseOptions: { ignoreLocation: true, includeMatches: true, minMatchCharLength: 2, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
|
||||
fuseOptions: { ignoreLocation: true, includeMatches: true, threshold: 0, keys: ['title', 'description', 'children.children.value', 'children.children.children.value'] },
|
||||
resultLimit: 10
|
||||
}"
|
||||
@update:model-value="onSelect"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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="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>
|
||||
|
||||
36
docs/content/1.getting-started/1.index.md
Normal file
36
docs/content/1.getting-started/1.index.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: 'Fully styled and customizable components for Nuxt.'
|
||||
head:
|
||||
title: 'NuxtLabs UI: Fully styled and customizable components for Nuxt'
|
||||
description: 'It provides everything related to UI when building your Nuxt app. This includes components, icons, colors, dark mode but also keyboard shortcuts. Built with Headless UI and Tailwind CSS, published under MIT License.'
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
- Built with [Headless UI](https://headlessui.dev/) and [Tailwind CSS](https://tailwindcss.com/)
|
||||
- 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.
|
||||
::
|
||||
@@ -1,12 +1,7 @@
|
||||
---
|
||||
title: 'nuxthq/ui'
|
||||
description: 'Components library as a Nuxt3 module using TailwindCSS based on TailwindUI.'
|
||||
navigation:
|
||||
title: Installation
|
||||
description: 'Learn how to install and configure the module in your Nuxt app.'
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install `@nuxthq/ui` dependency to your project:
|
||||
|
||||
::code-group
|
||||
@@ -33,10 +28,16 @@ export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
::alert
|
||||
That's it! You can now use all the components and composables in your Nuxt app ✨
|
||||
|
||||
::alert{icon="i-heroicons-exclamation-triangle"}
|
||||
As this module installs [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/) and [@nuxtjs/color-mode](https://color-mode.nuxtjs.org/) for you, you should remove them from your `modules` and `dependencies` if you've previously installed them manually.
|
||||
::
|
||||
|
||||
## Playground
|
||||
|
||||
:u-button{icon="i-simple-icons-stackblitz" label="Play on StackBlitz" size="lg" to="https://stackblitz.com/edit/nuxtlabs-ui?file=app.config.ts,app.vue" target="_blank"}
|
||||
|
||||
## Options
|
||||
|
||||
| Key | Default | Description |
|
||||
@@ -45,7 +46,7 @@ That's it! You can now use all the components and composables in your Nuxt app
|
||||
| `global` | `false` | Expose components globally. |
|
||||
| `icons` | `['heroicons']` | Icon collections to load. |
|
||||
|
||||
## Edge channel
|
||||
## Edge
|
||||
|
||||
To use the latest updates pushed on the [`dev`](https://github.com/nuxtlabs/ui/tree/dev) branch, you can use `@nuxthq/ui-edge`.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
## Overview
|
||||
@@ -1,7 +0,0 @@
|
||||
## Overview
|
||||
|
||||
## Composables
|
||||
|
||||
## `defineShortcuts`
|
||||
|
||||
## `useShortcuts`
|
||||
189
docs/content/1.getting-started/3.theming.md
Normal file
189
docs/content/1.getting-started/3.theming.md
Normal file
@@ -0,0 +1,189 @@
|
||||
---
|
||||
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: 'green',
|
||||
gray: 'cool'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
As this module uses Tailwind CSS under the hood, you can use any of the [Tailwind CSS colors](https://tailwindcss.com/docs/customizing-colors#color-palette-reference) or your own custom colors. By default, the `primary` color is `green` and the `gray` color is `cool`.
|
||||
|
||||
To provide dynamic colors that can be changed at runtime, this module uses CSS variables. As Tailwind CSS 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 Tailwind CSS colors.
|
||||
|
||||
## Dark mode
|
||||
|
||||
All the components are styled with dark mode in mind.
|
||||
|
||||
Thanks to [Tailwind CSS 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.
|
||||
|
||||
## Components
|
||||
|
||||
Components are styled with Tailwind CSS 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 either `@iconify/json` (full icon collections, 50MB) or the individual icon packages you want to use in your app.
|
||||
|
||||
::code-group
|
||||
|
||||
```bash [yarn]
|
||||
yarn add -D @iconify-json/{collection_name}
|
||||
```
|
||||
|
||||
```bash [npm]
|
||||
npm install -D @iconify-json/{collection_name}
|
||||
```
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm i -D @iconify-json/{collection_name}
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
When using `@iconify/json`, you can specifiy `icons: 'all'` in your `nuxt.config.ts` to use any icon in your app.
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
ui: {
|
||||
icons: 'all'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -1,152 +0,0 @@
|
||||
---
|
||||
navigation: false
|
||||
---
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Classes to invert dark mode like `u-text-gray-900` have been removed.
|
||||
|
||||
- Components now have a `ui` prop to override the entire preset instead of individual props
|
||||
- Components prop `popperOptions` has been renamed to `popper`
|
||||
- `Alert`, `AlertDialog`, `Tabs` and `Pills` components have been removed
|
||||
|
||||
### `Avatar`
|
||||
|
||||
- `wrapperClass`, `backgroundClass`, `placeholderClass` and `roundedClass` props have been removed in favor of `ui`
|
||||
- `rounded` prop is now a class defaulting to `rounded-full` instead of a boolean prop, can be overriden through `ui.avatar.rounded`
|
||||
- `chip` prop is now `chipVariant`
|
||||
- `ui.avatar.size` and `ui.avatar.chip.size` `xxs` and `xxxs` have been renamed respectively to `2xs` and `3xs`
|
||||
|
||||
### `AvatarGroup`
|
||||
|
||||
- `ringClass` and `marginClass` props have been removed in favor of `ui`
|
||||
- `group` prop has been removed in favor of slots
|
||||
|
||||
### `Badge`
|
||||
|
||||
- `baseClass` prop has been removed in favor of `ui`
|
||||
- `rounded` prop is now a class defaulting to `rounded-md` instead of a boolean prop, can be overriden through `ui.badge.rounded`
|
||||
- `color` prop has been added to change the color scheme of the badge
|
||||
- `variant` prop is now the variant instead of the color
|
||||
- `font-medium` has been moved from `ui.badge.base` to `ui.badge.font`
|
||||
|
||||
### `Button`
|
||||
|
||||
- `customClass` prop have been removed
|
||||
- `baseClass`, `iconBaseClass` and `roundedClass` props have been removed in favor of `ui`
|
||||
- `leadingIconClass` and `trailingIconClass` props have been removed
|
||||
- `rounded` prop is now a class defaulting to `rounded-md` instead of a boolean prop, can be overriden through `ui.button.rounded`
|
||||
- `color` prop has been added to change the color scheme of the badge
|
||||
- `variant` prop is now the variant instead of the color
|
||||
- `labelCompact` and `compact` props have been removed entirely alongside preset `ui.button.compact` and `ui.button.icon.leading.compactSpacing` and `ui.button.icon.trailing.compactSpacing`
|
||||
- `padded` prop has been added to remove padding
|
||||
- `ui.button.size.xxs` has been renamed to `ui.button.size.2xs`
|
||||
- `ui.button.size.2xl` has been introduced
|
||||
- `ui.button.gap` has been introduced to replace margins defined in `ui.button.icon.leading.spacing` and `ui.button.icon.trailing.spacing`
|
||||
- `ui.button.icon.leading.spacing` and `ui.button.icon.trailing.spacing` that added negative margin to icons have been removed to keep consitency when surcharging a button through default slot (code has only been commented for now)
|
||||
- `font-medium` has been moved from `ui.button.base` to `ui.button.font`
|
||||
|
||||
### `ButtonGroup`
|
||||
|
||||
- New component
|
||||
|
||||
### `Dropdown`
|
||||
|
||||
- `wrapperClass`, `containerClass`, `widthClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass`, `divideClass`, `baseClass`, `transitionClass`, `groupClass`, `itemBaseClass`, `itemActiveClass`, `itemInactiveClass`, `itemDisabledClass`, `itemIconClass`, `itemAvatarClass` and `itemShortcutsClass` props have been removed in favor of `ui`
|
||||
- preset has been updated to improve dark mode
|
||||
|
||||
### `Card`
|
||||
|
||||
- `baseClass`, `backgroundClass`, `borderColorClass`, `shadowClass`, `ringClass`, `roundedClass`, `bodyClass`, `bodyBackgroundClass`, `headerClass`, `headerBackgroundClass`, `footerClass`, `footerBackgroundClass` props have been removed in favor of `ui`
|
||||
- `rounded` prop is now a class defaulting to `rounded-lg` instead of a boolean prop, can be overriden through `ui.avatar.rounded`
|
||||
- `padded` prop has been removed, use `ui.rounded = 'sm:rounded-lg'` instead when false
|
||||
- `ui.card.border` has been removed in favor of `ui.card.divide`
|
||||
- `ui.card.header` & `ui.card.footer` are now `{ spacing: '', background: '' }`
|
||||
|
||||
### `Container`
|
||||
|
||||
- `constrainedClass` prop has been removed in favor of `ui`
|
||||
- `ui.container.base` and `ui.container.spacing` have been added
|
||||
- `padded` prop has been removed, use `ui.spacing = 'sm:px-6 lg:px-8'` instead when false
|
||||
- `constrained` prop has been removed, use `ui.constrained = ''` instead when false
|
||||
|
||||
### `Input`
|
||||
|
||||
- `wrapperClass`, `baseClass`, `iconBaseClass` and `customClass` props have been removed in favor of `ui`
|
||||
|
||||
### `FormGroup`
|
||||
|
||||
- Renamed to `InputGroup`
|
||||
- `wrapperClass`, `containerClass`, `labelClass`, `labelWrapperClass`, `descriptionClass`, `requiredClass` and `hintClass` props have been removed in favor of `ui`
|
||||
|
||||
### `Textarea`
|
||||
|
||||
- `wrapperClass`, `baseClass` and `customClass` props have been removed in favor of `ui`
|
||||
- `resize` is now false by default
|
||||
|
||||
### `Select`
|
||||
|
||||
- `wrapperClass`, `baseClass`, `iconBaseClass` and `customClass` props have been removed in favor of `ui`
|
||||
|
||||
### `SelectCustom`
|
||||
|
||||
- Renamed to `SelectMenu`
|
||||
- `placeholder` prop is now `null` by default
|
||||
- `nullable` prop has been removed
|
||||
- `textAttribute` has been renamed to `optionAttribute` and now defaults to `label`
|
||||
- `wrapperClass`, `baseClass`, `iconBaseClass`, `customClass`, `listBaseClass`, `listContainerClass`, `listWidthClass`, `listInputClass`, `listTransitionClass`, `listOptionBaseClass`, `listOptionContainerClass`, `listOptionActiveClass`, `listOptionInactiveClass`, `listOptionSelectedClass`, `listOptionUnselectedClass`, `listOptionDisabledClass`, `listOptionEmptyClass`, `listOptionIcon`, `listOptionIconBaseClass`, `listOptionIconActiveClass`, `listOptionIconInactiveClass` and `listOptionIconSizeClass` props have been removed in favor of `ui`
|
||||
- `ui.selectCustom.list` has been moved to the root of `ui.selectMenu`, the component now uses `ui.select` to render the default slot
|
||||
|
||||
### `Radio`
|
||||
|
||||
- `wrapperClass`, `baseClass`, `labelClass`, `requiredClass`, `helpClass` and `customClass` props have been removed in favor of `ui`
|
||||
|
||||
### `Checkbox`
|
||||
|
||||
- `wrapperClass`, `baseClass`, `labelClass`, `requiredClass`, `helpClass` and `customClass` props have been removed in favor of `ui`
|
||||
|
||||
### `Toggle`
|
||||
|
||||
- `baseClass`, `activeClass`, `inactiveClass`, `containerBaseClass`, `containerActiveClass`, `containerInactiveClass`, `iconBaseClass`, `iconActiveClass`, `iconInactiveClass`, `iconOnClass` and `iconOffClass` props have been removed in favor of `ui`
|
||||
|
||||
### `CommandPalette`
|
||||
|
||||
- `inputCloseIcon` and `emptyIcon` props have been removed in favor of `ui`
|
||||
- `inputIcon` prop has been renamed to `icon`
|
||||
- `inputPlaceholder` prop has been renamed to `placeholder`
|
||||
- `options` prop has been renamed to `fuse` to follow the `popper` and `ui` props convention
|
||||
|
||||
### `Modal`
|
||||
|
||||
- `wrapperClass`, `innerClass`, `containerClass`, `baseClass`, `backgroundClass`, `overlayBackgroundClass`, `overlayTransitionClass`, `shadowClass`, `ringClass`, `roundedClass`, `widthClass` and `transitionClass` props have been removed in favor of `ui`
|
||||
- `innerStyle` prop has been removed
|
||||
- `#header` and `#footer` slots have been removed
|
||||
|
||||
### `Slideover`
|
||||
|
||||
- `wrapperClass`, `baseClass`, `backgroundClass`, `overlayBackgroundClass`, `overlayTransitionClass`, `widthClass`, `headerClass` and `transitionClass` props have been removed in favor of `ui`
|
||||
- `#header` slot has been removed
|
||||
|
||||
### `Popover`
|
||||
|
||||
- `wrapperClass`, `containerClass`, `widthClass`, `baseClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass` and `transitionClass` props have been removed in favor of `ui`
|
||||
|
||||
### `Tooltip`
|
||||
|
||||
- `wrapperClass`, `containerClass`, `baseClass`, `widthClass`, `backgroundClass`, `shadowClass`, `ringClass`, `roundedClass`, `shortcutsClass` and `transitionClass` props have been removed in favor of `ui`
|
||||
|
||||
### `ContextMenu`
|
||||
|
||||
- `wrapperClass`, `containerClass`, `widthClass`, `backgroundClass`, `shadowClass`, `roundedClass`, `ringClass`, `baseClass` and `transitionClass`
|
||||
|
||||
### `Notification`
|
||||
|
||||
- `backgroundClass`, `shadowClass`, `ringClass`, `roundedClass`, `transitionClass`, `customClass` and `iconBaseClass` props have been removed in favor of `ui`
|
||||
- `type` prop has been removed
|
||||
- `ui.notification.type` and `ui.notification.icon.color` have been removed
|
||||
- `ui.notification.close.icon.name` has been moved to `ui.notification.default.closeIcon`
|
||||
|
||||
### `useToast`
|
||||
|
||||
- `addNotification` and `removeNotification` have been renamed to `add` and `remove`
|
||||
- `success`, `info`, `warning` and `error` methods have been removed as `type` disappeared from `Notification`
|
||||
123
docs/content/1.getting-started/4.shortcuts.md
Normal file
123
docs/content/1.getting-started/4.shortcuts.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
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>
|
||||
```
|
||||
|
||||
### `usingInput`
|
||||
|
||||
Prop: `usingInput?: string | boolean`
|
||||
|
||||
By default, `usingInput` is `false`, meaning it will only trigger when no input is focused. When set to `true`, the shortcut will also trigger when any input is focused.
|
||||
|
||||
For a more advanced behavior, `usingInput` can be set to the name of an input, so it only triggers when focusing this specific input.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<UInput v-model="query" name="queryInput" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const query = ref('')
|
||||
|
||||
defineShortcuts({
|
||||
enter: {
|
||||
usingInput: 'queryInput',
|
||||
handler: () => {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
_`enter` shortcut will only trigger when `queryInput` is focused._
|
||||
|
||||
### `whenever`
|
||||
|
||||
Prop: `whenever?: WatchSource<Boolean>[]`
|
||||
|
||||
`whenever` allows to add constraints on the shortcut triggering behavior. This array can contain `Ref<Boolean>`, `ComputedRef<Boolean>` or `() => Boolean`.
|
||||
|
||||
```ts
|
||||
defineShortcuts({
|
||||
meta_k: {
|
||||
usingInput: true,
|
||||
handler: () => { isOpen.value = !isOpen.value }
|
||||
},
|
||||
escape: {
|
||||
usingInput: true,
|
||||
whenever: [isOpen],
|
||||
handler: () => { isOpen.value = false }
|
||||
}
|
||||
})
|
||||
```
|
||||
_`escape` shortcut will only trigger when `isOpen` is `true`._
|
||||
|
||||
### Simple shortcut
|
||||
|
||||
In case the shortcut does not need any config, it can be written as a function.
|
||||
|
||||
```ts
|
||||
defineShortcuts({
|
||||
'?': () => openHelpModal()
|
||||
})
|
||||
```
|
||||
|
||||
## `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>
|
||||
```
|
||||
_`metaSymbol` will display either `⌘` on MacOS or `Ctrl` on any other OS_
|
||||
7
docs/content/1.getting-started/5.roadmap.md
Normal file
7
docs/content/1.getting-started/5.roadmap.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Roadmap
|
||||
description: Discover our Volta board for @nuxthq/ui development status.
|
||||
toc: false
|
||||
---
|
||||
|
||||
:volta-embed{token="eyJ2aWV3IjoiYm9hcmQiLCJib2FyZFN0YXR1c2VzIjpbInRyaWFnZSIsImJhY2tsb2ciLCJ0b2RvIiwiaW5fcHJvZ3Jlc3MiLCJpbl9yZXZpZXciLCJkb25lIiwicmVsZWFzZWQiXSwiYm9hcmRMaW5rZWRQcnMiOnRydWUsImxpc3RHcm91cCI6InN0YXRlIiwibGlzdE9yZGVyIjoiY3JlYXRlZF9hdCIsInRpbWVsaW5lWm9vbSI6Im1vbnRoIiwidGltZWxpbmVPcmRlciI6InN0YXRlIiwidGltZWxpbmVEaXNwbGF5IjoiYWxsX21pbGVzdG9uZXMiLCJmaWx0ZXJzIjp7fSwib3duZXIiOiJudXh0bGFicyIsIm5hbWUiOiJ1aSJ9"}
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display an image that represents a resource or a group of resources.
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -19,7 +20,7 @@ Use the `size` prop to change the size of the Avatar.
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'md'
|
||||
size: 'sm'
|
||||
baseProps:
|
||||
src: 'https://avatars.githubusercontent.com/u/739984?v=4'
|
||||
alt: 'Avatar'
|
||||
@@ -66,7 +67,7 @@ To stack avatars as a group, use the `AvatarGroup` component.
|
||||
::component-card{slug="AvatarGroup"}
|
||||
---
|
||||
props:
|
||||
size: 'md'
|
||||
size: 'sm'
|
||||
max: 2
|
||||
ui:
|
||||
size:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a short text to represent a status or a category.
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -44,7 +45,7 @@ Use the `size` prop to change the size of the Badge.
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'md'
|
||||
size: 'sm'
|
||||
---
|
||||
|
||||
Badge
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Create a button with icon or link capabilities.
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -37,7 +38,7 @@ props:
|
||||
Button
|
||||
::
|
||||
|
||||
Besides all the colors from the `ui.colors` object, you can also use the `white` and `gray` and `black` colors with their pre-defined variants.
|
||||
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
|
||||
|
||||
@@ -125,6 +126,7 @@ props:
|
||||
trailing: false
|
||||
excludedProps:
|
||||
- icon
|
||||
- label
|
||||
---
|
||||
::
|
||||
|
||||
@@ -161,6 +163,8 @@ Button
|
||||
|
||||
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:
|
||||
@@ -255,7 +259,7 @@ props:
|
||||
size: 'sm'
|
||||
ui:
|
||||
size:
|
||||
xxs: ''
|
||||
2xs: ''
|
||||
xs: ''
|
||||
sm: ''
|
||||
md: ''
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a list of actions in a dropdown menu.
|
||||
headlessui:
|
||||
label: 'Menu'
|
||||
to: 'https://headlessui.com/vue/menu'
|
||||
@@ -22,10 +23,12 @@ const items = [
|
||||
}
|
||||
}], [{
|
||||
label: 'Edit',
|
||||
icon: 'i-heroicons-pencil-square-20-solid'
|
||||
icon: 'i-heroicons-pencil-square-20-solid',
|
||||
shortcuts: ['E']
|
||||
}, {
|
||||
label: 'Duplicate',
|
||||
icon: 'i-heroicons-document-duplicate-20-solid'
|
||||
icon: 'i-heroicons-document-duplicate-20-solid',
|
||||
shortcuts: ['D']
|
||||
}], [{
|
||||
label: 'Archive',
|
||||
icon: 'i-heroicons-archive-box-20-solid'
|
||||
@@ -34,7 +37,8 @@ const items = [
|
||||
icon: 'i-heroicons-arrow-right-circle-20-solid'
|
||||
}], [{
|
||||
label: 'Delete',
|
||||
icon: 'i-heroicons-trash-20-solid'
|
||||
icon: 'i-heroicons-trash-20-solid',
|
||||
shortcuts: ['⌘', 'D']
|
||||
}]
|
||||
]
|
||||
</script>
|
||||
|
||||
21
docs/content/2.elements/5.icon.md
Normal file
21
docs/content/2.elements/5.icon.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
github: true
|
||||
description: Display an icon from Iconify library.
|
||||
---
|
||||
|
||||
## 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
|
||||
71
docs/content/2.elements/6.kbd.md
Normal file
71
docs/content/2.elements/6.kbd.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
github: true
|
||||
title: 'Keyboard Key'
|
||||
description: Display a keyboard key in a text block.
|
||||
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
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display an input field.
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -24,6 +25,33 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### 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}`.
|
||||
@@ -34,15 +62,14 @@ Use the `leading` and `trailing` props to set the icon position or the `leadingI
|
||||
---
|
||||
baseProps:
|
||||
name: 'input'
|
||||
placeholder: 'Search...'
|
||||
props:
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
appearance: 'white'
|
||||
size: 'sm'
|
||||
trailing: false
|
||||
placeholder: 'Search...'
|
||||
excludedProps:
|
||||
- icon
|
||||
- placeholder
|
||||
---
|
||||
::
|
||||
|
||||
@@ -67,11 +94,13 @@ excludedProps:
|
||||
|
||||
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'
|
||||
placeholder: 'Searching...'
|
||||
props:
|
||||
loading: true
|
||||
icon: 'i-heroicons-magnifying-glass-20-solid'
|
||||
@@ -82,6 +111,30 @@ excludedProps:
|
||||
|
||||
### Group
|
||||
|
||||
You can use the `InputGroup` component to add a label and additional informations to a form element.
|
||||
|
||||
::component-card{slug="InputGroup"}
|
||||
---
|
||||
baseProps:
|
||||
name: 'group'
|
||||
props:
|
||||
label: 'Email'
|
||||
help: "We'll only use this for spam."
|
||||
hint: 'Required'
|
||||
required: true
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{name="group" placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
::alert{icon="i-heroicons-light-bulb"}
|
||||
This also works with `Textarea`, `Select` and `SelectMenu` components.
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a textarea field.
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -11,6 +12,61 @@ baseProps:
|
||||
---
|
||||
::
|
||||
|
||||
### 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
|
||||
|
||||
@@ -1,18 +1,119 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a select field.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The Select component is a wrapper around the native `<select>` HTML element. For more advanced use cases like searching or multiple selection, consider using the [SelectMenu](/forms/select-menu) component.
|
||||
|
||||
::component-card
|
||||
---
|
||||
baseProps:
|
||||
name: 'select'
|
||||
modelValue: 'Canada'
|
||||
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
|
||||
---
|
||||
::
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a select menu with advanced features.
|
||||
headlessui:
|
||||
label: 'Listbox'
|
||||
to: 'https://headlessui.com/vue/listbox'
|
||||
@@ -7,6 +8,10 @@ headlessui:
|
||||
|
||||
## 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"}
|
||||
@@ -16,7 +21,7 @@ headlessui:
|
||||
<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()
|
||||
const selected = ref(people[0])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -25,7 +30,7 @@ const selected = ref()
|
||||
```
|
||||
::
|
||||
|
||||
You can use multiple values but you have to override the `#label` slot and handle the display yourself.
|
||||
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
|
||||
@@ -76,7 +81,7 @@ const selected = ref(people[3])
|
||||
```
|
||||
::
|
||||
|
||||
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 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
|
||||
@@ -131,6 +136,10 @@ const selected = ref(people[0])
|
||||
|
||||
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:
|
||||
@@ -148,7 +157,7 @@ excludedProps:
|
||||
|
||||
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).
|
||||
This will use Headless UI [Combobox](https://headlessui.com/vue/combobox) component instead of [Listbox](https://headlessui.com/vue/listbox).
|
||||
|
||||
::component-card
|
||||
---
|
||||
@@ -161,21 +170,6 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` prop to disable the SelectMenu.
|
||||
|
||||
::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:
|
||||
disabled: true
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
|
||||
@@ -1,10 +1,70 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a checkbox field.
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,10 +1,70 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a radio field.
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a toggle field.
|
||||
headlessui:
|
||||
label: 'Switch'
|
||||
to: 'https://headlessui.com/vue/switch'
|
||||
@@ -10,6 +11,21 @@ headlessui:
|
||||
::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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a vertical navigation.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Add a customizable command palette to your app.
|
||||
headlessui:
|
||||
label: 'Combobox'
|
||||
to: 'https://headlessui.com/vue/combobox'
|
||||
@@ -42,7 +43,7 @@ const selected = ref([people[3]])
|
||||
multiple
|
||||
nullable
|
||||
:groups="[{ key: 'people', commands: people }]"
|
||||
:fuse="{ resultLimit: 6 }"
|
||||
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
@@ -57,7 +58,7 @@ You can put a `CommandPalette` anywhere you want but it's most commonly used ins
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
|
||||
const people = [
|
||||
{ id: 1, label: 'Wade Cooper' },
|
||||
@@ -77,9 +78,9 @@ const selected = ref([])
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="open">
|
||||
<UModal v-model="isOpen">
|
||||
<UCommandPalette
|
||||
v-model="selected"
|
||||
multiple
|
||||
@@ -118,10 +119,10 @@ const users = [
|
||||
]
|
||||
|
||||
const actions = [
|
||||
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => alert('New file') },
|
||||
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder') },
|
||||
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag') },
|
||||
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label') }
|
||||
{ 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
|
||||
@@ -159,6 +160,8 @@ function onSelect (option) {
|
||||
|
||||
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
|
||||
@@ -230,6 +233,72 @@ excludedProps:
|
||||
---
|
||||
::
|
||||
|
||||
## 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"}.
|
||||
::
|
||||
|
||||
## Async search
|
||||
|
||||
You can also pass an `async` function to the `search` property of a group to perform an async search. The function will receive the query as its first argument and should return an array of commands.
|
||||
|
||||
::component-example
|
||||
---
|
||||
padding: false
|
||||
---
|
||||
|
||||
#default
|
||||
:command-palette-example-async{class="h-[274px]"}
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const groups = computed(() => {
|
||||
return [{
|
||||
key: 'users',
|
||||
label: q => q && `Users matching “${q}”...`,
|
||||
search: async (q) => {
|
||||
if (!q) {
|
||||
return []
|
||||
}
|
||||
|
||||
const users = await $fetch(`https://jsonplaceholder.typicode.com/users`, { params: { q } })
|
||||
|
||||
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
|
||||
}
|
||||
}].filter(Boolean)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCommandPalette :groups="groups" />
|
||||
</template>
|
||||
```
|
||||
::
|
||||
|
||||
## Themes
|
||||
|
||||
Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.
|
||||
@@ -245,7 +314,7 @@ padding: false
|
||||
: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"}
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeAlgolia.vue#L23"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
@@ -260,7 +329,7 @@ padding: false
|
||||
: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"}
|
||||
::alert{icon="i-simple-icons-github" to="https://github.com/nuxtlabs/ui/blob/dev/docs/components/content/themes/CommandPaletteThemeRaycast.vue#L30"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a modal within your application.
|
||||
headlessui:
|
||||
label: 'Dialog'
|
||||
to: 'https://headlessui.com/vue/dialog'
|
||||
@@ -14,14 +15,14 @@ headlessui:
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="open">
|
||||
<UModal v-model="isOpen">
|
||||
<!-- Content -->
|
||||
</UModal>
|
||||
</div>
|
||||
@@ -38,14 +39,14 @@ You can put a [Card](/layout/card) component inside your Modal to handle forms a
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<UModal v-model="open">
|
||||
<UModal v-model="isOpen">
|
||||
<UCard :ui="{ divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<!-- Content -->
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a dialog that slides in from the edge of the screen.
|
||||
headlessui:
|
||||
label: 'Dialog'
|
||||
to: 'https://headlessui.com/vue/dialog'
|
||||
@@ -13,14 +14,14 @@ headlessui:
|
||||
#code
|
||||
```vue
|
||||
<script setup>
|
||||
const open = ref(false)
|
||||
const isOpen = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UButton label="Open" @click="open = true" />
|
||||
<UButton label="Open" @click="isOpen = true" />
|
||||
|
||||
<USlideover v-model="open">
|
||||
<USlideover v-model="isOpen">
|
||||
<!-- Content -->
|
||||
</USlideover>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a non-modal dialog that floats around a trigger element.
|
||||
headlessui:
|
||||
label: 'Popover'
|
||||
to: 'https://headlessui.com/vue/popover'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display content that appears on hover next to an element.
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -10,8 +11,8 @@ github: true
|
||||
#code
|
||||
```vue
|
||||
<template>
|
||||
<UTooltip text="Tooltip">
|
||||
<UButton color="gray" label="Button" />
|
||||
<UTooltip text="Tooltip example" :shortcuts="['⌘', 'O']">
|
||||
<UButton color="gray" label="Hover me" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a menu that appears on right click.
|
||||
---
|
||||
|
||||
## Usage
|
||||
@@ -11,12 +12,13 @@ github: true
|
||||
```vue
|
||||
<script setup>
|
||||
const { x, y } = useMouse()
|
||||
const { y: windowY } = useWindowScroll()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const virtualElement = ref({ getBoundingClientRect: () => ({}) })
|
||||
|
||||
function openContextMenu () {
|
||||
const top = unref(y)
|
||||
function onContextMenu () {
|
||||
const top = unref(y) - unref(windowY)
|
||||
const left = unref(x)
|
||||
|
||||
virtualElement.value.getBoundingClientRect = () => ({
|
||||
@@ -31,8 +33,8 @@ function openContextMenu () {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @contextmenu.prevent="openContextMenu">
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement" width-class="w-48">
|
||||
<div @contextmenu.prevent="onContextMenu">
|
||||
<UContextMenu v-model="isOpen" :virtual-element="virtualElement">
|
||||
<!-- Content -->
|
||||
</UContextMenu>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a toast notification in your app.
|
||||
---
|
||||
|
||||
## 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>
|
||||
@@ -37,6 +36,19 @@ const toast = useToast()
|
||||
```
|
||||
::
|
||||
|
||||
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`:
|
||||
|
||||
```ts [app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
notifications: {
|
||||
// Show toasts at the top right of the screen
|
||||
position: 'top-0 right-0'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Description
|
||||
|
||||
You can add a `description` in addition of the `title`.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a card for content with a header, body and footer.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
github: true
|
||||
description: A container lets you center and constrain the width of your content.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
34
docs/content/6.layout/3.skeleton.md
Normal file
34
docs/content/6.layout/3.skeleton.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
github: true
|
||||
description: Display a placeholder while content is loading.
|
||||
---
|
||||
|
||||
## 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
|
||||
@@ -1,19 +1,5 @@
|
||||
<template>
|
||||
<div class="relative grid lg:grid-cols-10 lg:gap-8">
|
||||
<DocsAside class="lg:col-span-2" />
|
||||
|
||||
<div class="relative lg:col-span-6 pt-8 pb-16">
|
||||
<DocsPageHeader />
|
||||
|
||||
<div class="prose prose-primary dark:prose-invert max-w-none">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<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 class="prose prose-primary dark:prose-invert max-w-none">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -6,6 +6,8 @@ export default defineNuxtConfig({
|
||||
ui,
|
||||
'@vueuse/nuxt',
|
||||
'@nuxt/content',
|
||||
'@nuxt/devtools',
|
||||
'@nuxthq/studio',
|
||||
'@nuxtjs/plausible',
|
||||
'nuxt-lodash',
|
||||
'nuxt-component-meta'
|
||||
@@ -34,5 +36,8 @@ export default defineNuxtConfig({
|
||||
'/api/_content/**': { isr: true, static: true },
|
||||
'/api/component-meta/**': { isr: true, static: true }
|
||||
}
|
||||
},
|
||||
routeRules: {
|
||||
// '/getting-started': { swr: 100000 }
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
await navigateTo('/getting-started/installation')
|
||||
await navigateTo('/getting-started')
|
||||
</script>
|
||||
|
||||
BIN
docs/public/favicon.ico
Normal file
BIN
docs/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +1,15 @@
|
||||
<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"/>
|
||||
<style>
|
||||
path {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<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" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
docs/public/social-preview.jpg
Normal file
BIN
docs/public/social-preview.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
@@ -2,7 +2,6 @@ import type { Config } from 'tailwindcss'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
|
||||
export default <Partial<Config>> {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'docs/content/**/*.md'
|
||||
],
|
||||
|
||||
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxthq/ui",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"repository": "https://github.com/nuxtlabs/ui",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
@@ -15,14 +15,14 @@
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18 <19"
|
||||
"node": ">=16.14.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "nuxt-module-build",
|
||||
"prepack": "yarn build",
|
||||
"dev": "nuxi dev docs",
|
||||
"build:docs": "nuxi build docs",
|
||||
"lint": "eslint --ext .ts,.js,.vue .",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "nuxi typecheck",
|
||||
"prepare": "nuxi prepare docs",
|
||||
"release": "yarn lint && standard-version && git push --follow-tags"
|
||||
@@ -33,10 +33,10 @@
|
||||
"@iconify-json/heroicons": "^1.1.10",
|
||||
"@nuxt/kit": "^3.4.3",
|
||||
"@nuxtjs/color-mode": "^3.2.0",
|
||||
"@nuxtjs/tailwindcss": "^6.6.7",
|
||||
"@nuxtjs/tailwindcss": "^6.7.0",
|
||||
"@popperjs/core": "^2.11.7",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.0.0-insiders.615a228",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"@vueuse/integrations": "^10.1.2",
|
||||
@@ -47,23 +47,22 @@
|
||||
"tailwindcss": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/simple-icons": "^1.1.51",
|
||||
"@iconify-json/simple-icons": "^1.1.53",
|
||||
"@nuxt/content": "^2.6.0",
|
||||
"@nuxt/devtools": "^0.4.6",
|
||||
"@nuxt/eslint-config": "^0.1.1",
|
||||
"@nuxt/module-builder": "^0.3.1",
|
||||
"@nuxtjs/eslint-config-typescript": "^12.0.0",
|
||||
"@nuxtjs/plausible": "^0.2.0",
|
||||
"@nuxthq/studio": "^0.12.1",
|
||||
"@nuxtjs/plausible": "^0.2.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^18.16.3",
|
||||
"@types/node": "^20.1.7",
|
||||
"@vueuse/nuxt": "^10.1.2",
|
||||
"eslint": "^8.39.0",
|
||||
"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.2.1",
|
||||
"vue-tsc": "^1.6.3"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.0"
|
||||
"vue-tsc": "1.6.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin } from '@nuxt/kit'
|
||||
import { defineNuxtModule, installModule, addComponentsDir, addImportsDir, createResolver, addPlugin, resolvePath } from '@nuxt/kit'
|
||||
import colors from 'tailwindcss/colors.js'
|
||||
import { iconsPlugin, getIconCollections } from '@egoist/tailwindcss-icons'
|
||||
import { name, version } from '../package.json'
|
||||
import { colorsAsRegex, excludeColors } from './runtime/utils/colors'
|
||||
import preset from './runtime/app.config'
|
||||
import type { DeepPartial } from './runtime/types'
|
||||
|
||||
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
|
||||
@@ -23,7 +24,7 @@ declare module 'nuxt/schema' {
|
||||
primary?: string
|
||||
gray?: string
|
||||
colors?: string[]
|
||||
} & DeepPartial<typeof preset.ui>
|
||||
} & DeepPartial<typeof appConfig.ui>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ export interface ModuleOptions {
|
||||
*/
|
||||
global?: boolean
|
||||
|
||||
icons: string[]
|
||||
icons: string[] | string
|
||||
}
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
@@ -64,8 +65,9 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
|
||||
nuxt.options.css.push(resolve(runtimeDir, 'ui.css'))
|
||||
|
||||
const appConfigFile = await resolvePath(resolve(runtimeDir, 'app.config'))
|
||||
nuxt.hook('app:resolve', (app) => {
|
||||
app.configs.push(resolve(runtimeDir, 'app.config.ts'))
|
||||
app.configs.push(appConfigFile)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
@@ -113,13 +115,13 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
|
||||
nuxt.options.appConfig.ui = {
|
||||
...nuxt.options.appConfig.ui,
|
||||
primary: 'sky',
|
||||
primary: 'green',
|
||||
gray: 'cool',
|
||||
colors: variantColors
|
||||
}
|
||||
|
||||
tailwindConfig.safelist = tailwindConfig.safelist || []
|
||||
tailwindConfig.safelist.push(...[{
|
||||
tailwindConfig.safelist.push(...['bg-gray-400', {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-(50|400|500)`)
|
||||
}, {
|
||||
pattern: new RegExp(`bg-(${safeColorsAsRegex})-500`),
|
||||
@@ -175,7 +177,7 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
require('@tailwindcss/typography')
|
||||
],
|
||||
content: [
|
||||
resolve(runtimeDir, 'components/**/*.{vue,js,ts}'),
|
||||
resolve(runtimeDir, 'components/**/*.{vue,mjs,ts}'),
|
||||
resolve(runtimeDir, '*.{mjs,js,ts}')
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const avatar = {
|
||||
'3xl': 'h-20 w-20 text-3xl'
|
||||
},
|
||||
chip: {
|
||||
base: 'absolute block rounded-full ring-2 ring-white dark:ring-gray-900',
|
||||
base: 'absolute block rounded-full ring-1 ring-white dark:ring-gray-900',
|
||||
position: {
|
||||
'top-right': 'top-0 right-0',
|
||||
'bottom-right': 'bottom-0 right-0',
|
||||
@@ -40,7 +40,7 @@ const avatar = {
|
||||
}
|
||||
},
|
||||
default: {
|
||||
size: 'md',
|
||||
size: 'sm',
|
||||
chipVariant: 'solid',
|
||||
chipPosition: 'top-right'
|
||||
}
|
||||
@@ -57,22 +57,23 @@ const badge = {
|
||||
rounded: 'rounded-md',
|
||||
font: 'font-medium',
|
||||
size: {
|
||||
sm: 'text-xs px-1.5 py-0.5',
|
||||
md: 'text-xs px-2 py-1',
|
||||
lg: 'text-xs px-2.5 py-1.5'
|
||||
xs: 'text-xs px-1.5 py-0.5',
|
||||
sm: 'text-xs px-2 py-1',
|
||||
md: 'text-sm px-2 py-1',
|
||||
lg: 'text-sm px-2.5 py-1.5'
|
||||
},
|
||||
variant: {
|
||||
solid: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400 ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 ring-opacity-10 dark:ring-opacity-20'
|
||||
},
|
||||
default: {
|
||||
size: 'md',
|
||||
size: 'sm',
|
||||
variant: 'solid',
|
||||
color: 'primary'
|
||||
}
|
||||
}
|
||||
|
||||
const button = {
|
||||
base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75',
|
||||
base: 'focus:outline-none focus-visible:outline-0 disabled:cursor-not-allowed disabled:opacity-75 flex-shrink-0',
|
||||
font: 'font-medium',
|
||||
rounded: 'rounded-md',
|
||||
size: {
|
||||
@@ -91,7 +92,7 @@ const button = {
|
||||
lg: 'gap-x-2',
|
||||
xl: 'gap-x-2'
|
||||
},
|
||||
spacing: {
|
||||
padding: {
|
||||
'2xs': 'px-2 py-1',
|
||||
xs: 'px-2.5 py-1.5',
|
||||
sm: 'px-3 py-1.5',
|
||||
@@ -114,8 +115,6 @@ const button = {
|
||||
},
|
||||
gray: {
|
||||
solid: 'shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 text-gray-700 dark:text-gray-200 bg-gray-50 hover:bg-gray-100 disabled:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700/50 dark:disabled:bg-gray-800 focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
|
||||
// TODO: For Volta
|
||||
// 'outline-ghost': 'text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:ring-1 ring-inset ring-gray-300 dark:ring-gray-700',
|
||||
ghost: 'text-gray-700 dark:text-gray-200 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800 focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
|
||||
link: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 underline-offset-4 hover:underline focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400'
|
||||
},
|
||||
@@ -166,9 +165,12 @@ const dropdown = {
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
|
||||
base: 'focus:outline-none',
|
||||
divide: 'divide-y divide-gray-200 dark:divide-gray-700',
|
||||
spacing: 'p-1',
|
||||
padding: 'p-1',
|
||||
item: {
|
||||
base: 'group flex items-center gap-2 px-2 py-1.5 text-sm w-full rounded-md',
|
||||
base: 'group flex items-center gap-2 w-full',
|
||||
rounded: 'rounded-md',
|
||||
padding: 'px-2 py-1.5',
|
||||
size: 'text-sm',
|
||||
active: 'bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-white',
|
||||
inactive: 'text-gray-700 dark:text-gray-200',
|
||||
disabled: 'cursor-not-allowed opacity-50',
|
||||
@@ -181,7 +183,7 @@ const dropdown = {
|
||||
base: 'flex-shrink-0',
|
||||
size: '3xs'
|
||||
},
|
||||
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold text-gray-500 dark:text-gray-400 ml-auto'
|
||||
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5 ml-auto'
|
||||
},
|
||||
transition: {
|
||||
enterActiveClass: 'transition duration-100 ease-out',
|
||||
@@ -197,6 +199,23 @@ const dropdown = {
|
||||
}
|
||||
}
|
||||
|
||||
const kbd = {
|
||||
base: 'inline-flex items-center justify-center text-gray-900 dark:text-white',
|
||||
padding: 'px-1',
|
||||
size: {
|
||||
xs: 'h-4 min-w-[16px] text-[10px]',
|
||||
sm: 'h-5 min-w-[20px] text-[11px]',
|
||||
md: 'h-6 min-w-[24px] text-[12px]'
|
||||
},
|
||||
rounded: 'rounded',
|
||||
font: 'font-medium font-sans',
|
||||
background: 'bg-gray-100 dark:bg-gray-800',
|
||||
ring: 'ring-1 ring-gray-300 dark:ring-gray-700 ring-inset',
|
||||
default: {
|
||||
size: 'sm'
|
||||
}
|
||||
}
|
||||
|
||||
// Forms
|
||||
|
||||
const input = {
|
||||
@@ -219,7 +238,7 @@ const input = {
|
||||
lg: 'gap-x-2',
|
||||
xl: 'gap-x-2'
|
||||
},
|
||||
spacing: {
|
||||
padding: {
|
||||
'2xs': 'px-2 py-1',
|
||||
xs: 'px-2.5 py-1.5',
|
||||
sm: 'px-3 py-1.5',
|
||||
@@ -228,7 +247,7 @@ const input = {
|
||||
xl: 'px-4 py-3'
|
||||
},
|
||||
leading: {
|
||||
spacing: {
|
||||
padding: {
|
||||
'2xs': 'pl-[26px]',
|
||||
xs: 'pl-8',
|
||||
sm: 'pl-9',
|
||||
@@ -238,7 +257,7 @@ const input = {
|
||||
}
|
||||
},
|
||||
trailing: {
|
||||
spacing: {
|
||||
padding: {
|
||||
'2xs': 'pr-[26px]',
|
||||
xs: 'pr-8',
|
||||
sm: 'pr-9',
|
||||
@@ -250,7 +269,7 @@ const input = {
|
||||
appearance: {
|
||||
white: 'border-0 bg-white dark:bg-gray-900 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
|
||||
gray: 'border-0 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 placeholder:text-gray-400 dark:placeholder:text-gray-500',
|
||||
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none'
|
||||
none: 'border-0 bg-transparent focus:ring-0 focus:shadow-none placeholder:text-gray-400 dark:placeholder:text-gray-500'
|
||||
},
|
||||
icon: {
|
||||
base: 'text-gray-400 dark:text-gray-500',
|
||||
@@ -264,7 +283,7 @@ const input = {
|
||||
},
|
||||
leading: {
|
||||
wrapper: 'absolute inset-y-0 left-0 flex items-center pointer-events-none',
|
||||
spacing: {
|
||||
padding: {
|
||||
'2xs': 'pl-2',
|
||||
xs: 'pl-2.5',
|
||||
sm: 'pl-3',
|
||||
@@ -275,7 +294,7 @@ const input = {
|
||||
},
|
||||
trailing: {
|
||||
wrapper: 'absolute inset-y-0 right-0 flex items-center pointer-events-none',
|
||||
spacing: {
|
||||
padding: {
|
||||
'2xs': 'pr-2',
|
||||
xs: 'pr-2.5',
|
||||
sm: 'pr-3',
|
||||
@@ -297,22 +316,31 @@ const inputGroup = {
|
||||
label: 'block text-sm font-medium text-gray-700 dark:text-gray-200',
|
||||
labelWrapper: 'flex content-center justify-between',
|
||||
container: 'mt-1 relative',
|
||||
required: 'text-red-400',
|
||||
required: 'text-red-500 dark:text-red-400 ml-0.5',
|
||||
description: 'text-sm leading-5 text-gray-500 dark:text-gray-400',
|
||||
hint: 'text-sm leading-5 text-gray-500 dark:text-gray-400',
|
||||
help: 'mt-2 text-sm text-gray-500 dark:text-gray-400'
|
||||
}
|
||||
|
||||
const textarea = {
|
||||
...input
|
||||
...input,
|
||||
default: {
|
||||
size: 'sm',
|
||||
appearance: 'white'
|
||||
}
|
||||
}
|
||||
|
||||
const select = {
|
||||
...input
|
||||
...input,
|
||||
default: {
|
||||
size: 'sm',
|
||||
appearance: 'white',
|
||||
trailingIcon: 'i-heroicons-chevron-down-20-solid'
|
||||
}
|
||||
}
|
||||
|
||||
const selectMenu = {
|
||||
wrapper: 'relative',
|
||||
wrapper: 'relative inline-flex',
|
||||
container: 'z-20',
|
||||
width: 'w-full',
|
||||
height: 'max-h-60',
|
||||
@@ -320,14 +348,19 @@ const selectMenu = {
|
||||
background: 'bg-white dark:bg-gray-800',
|
||||
shadow: 'shadow-lg',
|
||||
rounded: 'rounded-md',
|
||||
spacing: 'p-1',
|
||||
padding: 'p-1',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
|
||||
input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 u-text-gray-700 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 focus:border-inherit sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500',
|
||||
input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 focus:border-inherit sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500',
|
||||
option: {
|
||||
base: 'cursor-default select-none relative px-2 py-1.5 rounded-md text-sm text-gray-900 dark:text-white flex items-center justify-between gap-1',
|
||||
container: 'flex items-center gap-2',
|
||||
base: 'cursor-default select-none relative flex items-center justify-between gap-1',
|
||||
rounded: 'rounded-md',
|
||||
padding: 'px-2 py-1.5',
|
||||
size: 'text-sm',
|
||||
color: 'text-gray-900 dark:text-white',
|
||||
container: 'flex items-center gap-2 min-w-0',
|
||||
active: 'bg-gray-100 dark:bg-gray-900',
|
||||
inactive: '',
|
||||
selected: 'pr-7',
|
||||
disabled: 'cursor-not-allowed opacity-50',
|
||||
empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
|
||||
icon: {
|
||||
@@ -335,16 +368,17 @@ const selectMenu = {
|
||||
active: 'text-gray-900 dark:text-white',
|
||||
inactive: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
selectedIcon: {
|
||||
wrapper: 'absolute inset-y-0 right-0 flex items-center',
|
||||
padding: 'pr-2',
|
||||
base: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '3xs'
|
||||
},
|
||||
chip: {
|
||||
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
||||
},
|
||||
selected: {
|
||||
wrapper: 'absolute inset-y-0 right-0 flex items-center pr-2',
|
||||
icon: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
|
||||
}
|
||||
},
|
||||
transition: {
|
||||
@@ -362,9 +396,9 @@ const selectMenu = {
|
||||
|
||||
const radio = {
|
||||
wrapper: 'relative flex items-start',
|
||||
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus:ring-2 focus:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus:ring-primary-500 dark:focus:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
base: 'h-4 w-4 text-primary-500 dark:text-primary-400 focus-visible:ring-2 focus-visible:ring-offset-2 bg-white dark:bg-gray-900 dark:checked:bg-current dark:checked:border-transparent focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900 border-gray-300 dark:border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed focus:ring-0 focus:ring-transparent',
|
||||
label: 'font-medium text-gray-700 dark:text-gray-200',
|
||||
required: 'text-red-400',
|
||||
required: 'text-red-500 dark:text-red-400',
|
||||
help: 'text-gray-500 dark:text-gray-400'
|
||||
}
|
||||
|
||||
@@ -374,12 +408,12 @@ const checkbox = {
|
||||
}
|
||||
|
||||
const toggle = {
|
||||
base: 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:focus:ring-primary-400 focus:ring-offset-white dark:focus:ring-offset-gray-900',
|
||||
base: 'relative inline-flex flex-shrink-0 h-5 w-9 border-2 border-transparent rounded-full cursor-pointer disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-gray-900',
|
||||
active: 'bg-primary-500 dark:bg-primary-400',
|
||||
inactive: 'bg-gray-200 dark:bg-gray-700',
|
||||
container: {
|
||||
base: 'pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
|
||||
active: 'translate-x-5',
|
||||
base: 'pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
|
||||
active: 'translate-x-4',
|
||||
inactive: 'translate-x-0'
|
||||
},
|
||||
icon: {
|
||||
@@ -396,45 +430,52 @@ const toggle = {
|
||||
const card = {
|
||||
base: 'overflow-hidden',
|
||||
background: 'bg-white dark:bg-gray-900',
|
||||
divide: 'divide-y divide-gray-200 dark:divide-gray-700',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
|
||||
divide: 'divide-y divide-gray-200 dark:divide-gray-800',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
|
||||
rounded: 'rounded-lg',
|
||||
shadow: 'shadow',
|
||||
body: {
|
||||
base: '',
|
||||
background: '',
|
||||
spacing: 'px-4 py-5 sm:p-6'
|
||||
padding: 'px-4 py-5 sm:p-6'
|
||||
},
|
||||
header: {
|
||||
base: '',
|
||||
background: '',
|
||||
spacing: 'px-4 py-5 sm:px-6'
|
||||
padding: 'px-4 py-5 sm:px-6'
|
||||
},
|
||||
footer: {
|
||||
base: '',
|
||||
background: '',
|
||||
spacing: 'px-4 py-4 sm:px-6'
|
||||
padding: 'px-4 py-4 sm:px-6'
|
||||
}
|
||||
}
|
||||
|
||||
const container = {
|
||||
base: 'mx-auto',
|
||||
spacing: 'px-4 sm:px-6 lg:px-8',
|
||||
padding: 'px-4 sm:px-6 lg:px-8',
|
||||
constrained: 'max-w-7xl'
|
||||
}
|
||||
|
||||
const skeleton = {
|
||||
base: 'animate-pulse',
|
||||
background: 'bg-gray-100 dark:bg-gray-800',
|
||||
rounded: 'rounded-md'
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
const verticalNavigation = {
|
||||
wrapper: 'relative z-0',
|
||||
base: 'group flex items-center gap-2 text-sm font-medium rounded-md w-full relative focus:outline-none after:absolute after:inset-px after:z-[-1] after:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
|
||||
spacing: 'px-3 py-1.5',
|
||||
active: 'u-text-gray-900 after:bg-gray-100 dark:after:bg-gray-800',
|
||||
inactive: 'u-text-gray-500 hover:u-text-gray-900 hover:after:bg-gray-50 dark:hover:after:bg-gray-800/50 focus-visible:after:bg-gray-50 dark:focus-visible:after:bg-gray-800/50',
|
||||
wrapper: 'relative',
|
||||
base: 'group flex items-center gap-2 text-sm font-medium rounded-md w-full relative focus:outline-none focus-visible:outline-none dark:focus-visible:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400 focus-visible:before:ring-inset focus-visible:before:ring-1 focus-visible:before:ring-primary-500 dark:focus-visible:before:ring-primary-400 before:absolute before:inset-px before:rounded-md disabled:cursor-not-allowed disabled:opacity-75',
|
||||
padding: 'px-3 py-1.5',
|
||||
active: 'text-gray-900 dark:text-white before:bg-gray-100 dark:before:bg-gray-800',
|
||||
inactive: 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:before:bg-gray-50 dark:hover:before:bg-gray-800/50',
|
||||
label: 'truncate relative',
|
||||
icon: {
|
||||
base: 'flex-shrink-0 w-4 h-4',
|
||||
active: 'u-text-gray-700',
|
||||
inactive: 'u-text-gray-400 group-hover:u-text-gray-700'
|
||||
active: 'text-gray-700 dark:text-gray-200',
|
||||
inactive: 'text-gray-400 dark:text-gray-500 group-hover:text-gray-700 dark:group-hover:text-gray-200'
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
@@ -443,7 +484,7 @@ const verticalNavigation = {
|
||||
badge: {
|
||||
base: 'ml-auto inline-block py-0.5 px-2 text-xs rounded-md -mr-1 -my-0.5',
|
||||
active: 'bg-white dark:bg-gray-900',
|
||||
inactive: 'u-bg-gray-100 u-text-gray-600 group-hover:bg-white dark:group-hover:bg-gray-900'
|
||||
inactive: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 group-hover:bg-white dark:group-hover:bg-gray-900'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,9 +493,15 @@ const commandPalette = {
|
||||
container: 'relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2',
|
||||
input: {
|
||||
wrapper: 'relative flex items-center',
|
||||
base: 'w-full h-12 px-4 placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0 sm:text-sm',
|
||||
spacing: 'pl-10',
|
||||
icon: 'pointer-events-none absolute left-4 h-4 w-4 text-gray-400 dark:text-gray-500',
|
||||
base: 'w-full placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0',
|
||||
padding: 'px-4',
|
||||
height: 'h-12',
|
||||
size: 'sm:text-sm',
|
||||
icon: {
|
||||
base: 'pointer-events-none absolute left-4 text-gray-400 dark:text-gray-500',
|
||||
size: 'h-4 w-4',
|
||||
padding: 'pl-10'
|
||||
},
|
||||
close: 'absolute right-4'
|
||||
},
|
||||
empty: {
|
||||
@@ -480,6 +527,9 @@ const commandPalette = {
|
||||
active: 'text-gray-900 dark:text-white',
|
||||
inactive: 'text-gray-400 dark:text-gray-500'
|
||||
},
|
||||
selectedIcon: {
|
||||
base: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
|
||||
},
|
||||
avatar: {
|
||||
base: 'flex-shrink-0',
|
||||
size: '3xs'
|
||||
@@ -488,10 +538,7 @@ const commandPalette = {
|
||||
base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
|
||||
},
|
||||
disabled: 'opacity-50',
|
||||
selected: {
|
||||
icon: 'h-4 w-4 text-gray-900 dark:text-white flex-shrink-0'
|
||||
},
|
||||
shortcuts: 'hidden md:inline-flex flex-shrink-0 text-xs font-semibold text-gray-500 dark:text-gray-400'
|
||||
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5'
|
||||
},
|
||||
active: 'flex-shrink-0 text-gray-500 dark:text-gray-400',
|
||||
inactive: 'flex-shrink-0 text-gray-500 dark:text-gray-400'
|
||||
@@ -514,11 +561,11 @@ const modal = {
|
||||
wrapper: 'relative z-50',
|
||||
inner: 'fixed inset-0 overflow-y-auto',
|
||||
container: 'flex min-h-full items-end sm:items-center justify-center text-center',
|
||||
spacing: 'p-4 sm:p-0',
|
||||
padding: 'p-4 sm:p-0',
|
||||
base: 'relative text-left overflow-hidden sm:my-8 w-full flex flex-col',
|
||||
overlay: {
|
||||
base: 'fixed inset-0 transition-opacity',
|
||||
background: 'bg-gray-500/75 dark:bg-gray-600/75',
|
||||
background: 'bg-gray-200/75 dark:bg-gray-800/75',
|
||||
transition: {
|
||||
enter: 'ease-out duration-300',
|
||||
enterFrom: 'opacity-0',
|
||||
@@ -548,7 +595,7 @@ const slideover = {
|
||||
wrapper: 'fixed inset-0 flex z-50',
|
||||
overlay: {
|
||||
base: 'fixed inset-0 transition-opacity',
|
||||
background: 'bg-gray-500/75 dark:bg-gray-600/75',
|
||||
background: 'bg-gray-200/75 dark:bg-gray-800/75',
|
||||
transition: {
|
||||
enter: 'ease-in-out duration-500',
|
||||
enterFrom: 'opacity-0',
|
||||
@@ -562,11 +609,12 @@ const slideover = {
|
||||
background: 'bg-white dark:bg-gray-900',
|
||||
ring: '',
|
||||
rounded: '',
|
||||
padding: '',
|
||||
shadow: 'shadow-xl',
|
||||
width: 'w-screen max-w-md',
|
||||
transition: {
|
||||
enter: 'transform transition ease-in-out duration-500 sm:duration-700',
|
||||
leave: 'transform transition ease-in-out duration-500 sm:duration-700'
|
||||
enter: 'transform transition ease-in-out duration-300',
|
||||
leave: 'transform transition ease-in-out duration-200'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,7 +627,7 @@ const tooltip = {
|
||||
rounded: 'rounded',
|
||||
ring: 'ring-1 ring-gray-200 dark:ring-gray-800',
|
||||
base: 'invisible lg:visible h-6 px-2 py-1 text-xs font-normal truncate',
|
||||
shortcuts: 'hidden md:inline-flex items-center justify-end flex-shrink-0 gap-0.5 ml-1',
|
||||
shortcuts: 'hidden md:inline-flex flex-shrink-0 gap-0.5',
|
||||
transition: {
|
||||
enterActiveClass: 'transition ease-out duration-200',
|
||||
enterFromClass: 'opacity-0 translate-y-1',
|
||||
@@ -673,7 +721,9 @@ const notification = {
|
||||
}
|
||||
|
||||
const notifications = {
|
||||
wrapper: 'fixed bottom-0 right-0 flex flex-col justify-end w-full z-[55] sm:w-96',
|
||||
wrapper: 'fixed flex flex-col justify-end z-[55]',
|
||||
position: 'bottom-0 right-0',
|
||||
width: 'w-full sm:w-96',
|
||||
container: 'px-4 sm:px-6 py-6 space-y-3 overflow-y-auto'
|
||||
}
|
||||
|
||||
@@ -685,6 +735,7 @@ export default {
|
||||
button,
|
||||
buttonGroup,
|
||||
dropdown,
|
||||
kbd,
|
||||
input,
|
||||
inputGroup,
|
||||
textarea,
|
||||
@@ -695,6 +746,7 @@ export default {
|
||||
toggle,
|
||||
card,
|
||||
container,
|
||||
skeleton,
|
||||
verticalNavigation,
|
||||
commandPalette,
|
||||
modal,
|
||||
|
||||
@@ -45,7 +45,7 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null,
|
||||
validator (value: string) {
|
||||
return appConfig.ui.colors.includes(value)
|
||||
return ['gray', ...appConfig.ui.colors].includes(value)
|
||||
}
|
||||
},
|
||||
chipVariant: {
|
||||
|
||||
@@ -36,10 +36,15 @@ export default defineComponent({
|
||||
|
||||
const children = computed(() => {
|
||||
let children = slots.default?.()
|
||||
// @ts-ignore-next
|
||||
if (children.length && children[0].type.name === 'ContentSlot') {
|
||||
if (children.length) {
|
||||
if (typeof children[0].type === 'symbol') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].children
|
||||
// @ts-ignore-next
|
||||
children = children[0].ctx.slots.default?.()
|
||||
} else if (children[0].type.name === 'ContentSlot') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].ctx.slots.default?.()
|
||||
}
|
||||
}
|
||||
return children
|
||||
})
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
<template>
|
||||
<component
|
||||
:is="buttonIs"
|
||||
ref="button"
|
||||
:class="buttonClass"
|
||||
:aria-label="ariaLabel"
|
||||
v-bind="buttonProps"
|
||||
>
|
||||
<Icon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
|
||||
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="leadingIconClass" aria-hidden="true" />
|
||||
<slot>
|
||||
<span v-if="label" :class="[truncate ? 'text-left break-all line-clamp-1' : '']">
|
||||
{{ label }}
|
||||
</span>
|
||||
</slot>
|
||||
<Icon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
||||
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="trailingIconClass" aria-hidden="true" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, defineComponent, useSlots } from 'vue'
|
||||
import { computed, defineComponent, useSlots } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
|
||||
import { defu } from 'defu'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { classNames } from '../../utils'
|
||||
import { NuxtLink } from '#components'
|
||||
import { useAppConfig } from '#imports'
|
||||
@@ -33,7 +32,7 @@ import appConfig from '#build/app.config'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Icon
|
||||
UIcon
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
@@ -78,7 +77,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: () => appConfig.ui.button.default.variant,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.button.variant).includes(value)
|
||||
return [
|
||||
...Object.keys(appConfig.ui.button.variant),
|
||||
...Object.values(appConfig.ui.button.color).flatMap(value => Object.keys(value))
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
@@ -138,8 +140,6 @@ export default defineComponent({
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const button = ref(null)
|
||||
|
||||
const buttonIs = computed(() => {
|
||||
if (props.to) {
|
||||
return NuxtLink
|
||||
@@ -175,7 +175,7 @@ export default defineComponent({
|
||||
ui.value.rounded,
|
||||
ui.value.size[props.size],
|
||||
ui.value.gap[props.size],
|
||||
props.padded && ui.value[isSquare.value ? 'square' : 'spacing'][props.size],
|
||||
props.padded && ui.value[isSquare.value ? 'square' : 'padding'][props.size],
|
||||
variant?.replaceAll('{color}', props.color),
|
||||
props.block ? 'w-full flex justify-center items-center' : 'inline-flex items-center'
|
||||
)
|
||||
@@ -214,7 +214,6 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
button,
|
||||
buttonIs,
|
||||
buttonProps,
|
||||
isLeading,
|
||||
|
||||
@@ -30,10 +30,15 @@ export default defineComponent({
|
||||
|
||||
const children = computed(() => {
|
||||
let children = slots.default?.()
|
||||
// @ts-ignore-next
|
||||
if (children.length && children[0].type.name === 'ContentSlot') {
|
||||
if (children.length) {
|
||||
if (typeof children[0].type === 'symbol') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].children
|
||||
// @ts-ignore-next
|
||||
children = children[0].ctx.slots.default?.()
|
||||
} else if (children[0].type.name === 'ContentSlot') {
|
||||
// @ts-ignore-next
|
||||
children = children[0].ctx.slots.default?.()
|
||||
}
|
||||
}
|
||||
return children
|
||||
})
|
||||
|
||||
@@ -18,22 +18,22 @@
|
||||
<div v-if="open && items.length" ref="container" :class="[ui.container, ui.width]" @mouseover="onMouseOver">
|
||||
<transition appear v-bind="ui.transition">
|
||||
<MenuItems :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background]" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="ui.spacing">
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="ui.padding">
|
||||
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled: itemDisabled }" :disabled="item.disabled">
|
||||
<Component
|
||||
v-bind="omit(item, ['click'])"
|
||||
:is="(item.to && NuxtLink) || (item.click && 'button') || 'div'"
|
||||
:class="resolveItemClass({ active, disabled: itemDisabled })"
|
||||
:class="[ui.item.base, ui.item.padding, ui.item.size, ui.item.rounded, active ? ui.item.active : ui.item.inactive, itemDisabled && ui.item.disabled]"
|
||||
@click="item.click"
|
||||
>
|
||||
<slot :name="item.slot || 'item'" :item="item">
|
||||
<Icon v-if="item.icon" :name="item.icon" :class="[ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive, item.iconClass]" />
|
||||
<Avatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
|
||||
<UIcon v-if="item.icon" :name="item.icon" :class="[ui.item.icon.base, active ? ui.item.icon.active : ui.item.icon.inactive, item.iconClass]" />
|
||||
<UAvatar v-else-if="item.avatar" v-bind="{ size: ui.item.avatar.size, ...item.avatar }" :class="ui.item.avatar.base" />
|
||||
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<span v-if="item.shortcuts?.length" :class="ui.item.shortcuts">
|
||||
<kbd v-for="shortcut of item.shortcuts" :key="shortcut" class="font-sans">{{ shortcut }}</kbd>
|
||||
<UKbd v-for="shortcut of item.shortcuts" :key="shortcut">{{ shortcut }}</UKbd>
|
||||
</span>
|
||||
</slot>
|
||||
</Component>
|
||||
@@ -51,9 +51,10 @@ import type { PropType } from 'vue'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import { defineComponent, ref, computed, onMounted } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Avatar from '../elements/Avatar.vue'
|
||||
import { classNames, omit } from '../../utils'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import UKbd from '../elements/Kbd.vue'
|
||||
import { omit } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||
import type { PopperOptions } from '../../types'
|
||||
@@ -72,8 +73,9 @@ export default defineComponent({
|
||||
MenuButton,
|
||||
MenuItems,
|
||||
MenuItem,
|
||||
Icon,
|
||||
Avatar
|
||||
UIcon,
|
||||
UAvatar,
|
||||
UKbd
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
@@ -129,14 +131,6 @@ export default defineComponent({
|
||||
|
||||
const [trigger, container] = usePopper(popper.value)
|
||||
|
||||
function resolveItemClass ({ active, disabled }: { active: boolean, disabled: boolean }) {
|
||||
return classNames(
|
||||
ui.value.item.base,
|
||||
active ? ui.value.item.active : ui.value.item.inactive,
|
||||
disabled && ui.value.item.disabled
|
||||
)
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/menu/menu.ts#L131
|
||||
const menuApi = ref<any>(null)
|
||||
|
||||
@@ -200,7 +194,6 @@ export default defineComponent({
|
||||
ui,
|
||||
trigger,
|
||||
container,
|
||||
resolveItemClass,
|
||||
onMouseOver,
|
||||
onMouseLeave,
|
||||
omit,
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
<span :class="name" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
48
src/runtime/components/elements/Kbd.vue
Normal file
48
src/runtime/components/elements/Kbd.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<kbd :class="[ui.base, ui.size[size], ui.padding, ui.rounded, ui.font, ui.background, ui.ring]">
|
||||
<slot>{{ value }}</slot>
|
||||
</kbd>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.kbd.default.size,
|
||||
validator (value: string) {
|
||||
return Object.keys(appConfig.ui.kbd.size).includes(value)
|
||||
}
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.kbd>>,
|
||||
default: () => appConfig.ui.kbd
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.kbd>>(() => defu({}, props.ui, appConfig.ui.kbd))
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<button v-if="!$attrs.to" v-bind="$attrs" :class="inactiveClass">
|
||||
<slot />
|
||||
</button>
|
||||
<NuxtLink
|
||||
v-else
|
||||
v-slot="{ href, navigate, exact, isActive, isExactActive }"
|
||||
custom
|
||||
>
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
:class="inputClass"
|
||||
@input="onInput(($event.target as any).value)"
|
||||
@input="onInput"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
>
|
||||
<slot />
|
||||
<div v-if="isLeading && leadingIconName" :class="leadingIconClass">
|
||||
<Icon :name="leadingIconName" :class="iconClass" />
|
||||
<UIcon :name="leadingIconName" :class="iconClass" />
|
||||
</div>
|
||||
<div v-if="isTrailing && trailingIconName" :class="trailingIconClass">
|
||||
<Icon :name="trailingIconName" :class="iconClass" />
|
||||
<UIcon :name="trailingIconName" :class="iconClass" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -31,7 +31,7 @@
|
||||
import { ref, computed, onMounted, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { classNames } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
@@ -42,7 +42,7 @@ import appConfig from '#build/app.config'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Icon
|
||||
UIcon
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
@@ -147,8 +147,8 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const onInput = (value: string) => {
|
||||
emit('update:modelValue', value)
|
||||
const onInput = (event: InputEvent) => {
|
||||
emit('update:modelValue', (event.target as any).value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -161,10 +161,10 @@ export default defineComponent({
|
||||
return classNames(
|
||||
ui.value.base,
|
||||
ui.value.size[props.size],
|
||||
ui.value.spacing[props.size],
|
||||
ui.value.padding[props.size],
|
||||
ui.value.appearance[props.appearance],
|
||||
isLeading.value && ui.value.leading.spacing[props.size],
|
||||
isTrailing.value && ui.value.trailing.spacing[props.size],
|
||||
isLeading.value && ui.value.leading.padding[props.size],
|
||||
isTrailing.value && ui.value.trailing.padding[props.size],
|
||||
ui.value.custom
|
||||
)
|
||||
})
|
||||
@@ -204,20 +204,21 @@ export default defineComponent({
|
||||
const leadingIconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.icon.leading.wrapper,
|
||||
ui.value.icon.leading.spacing[props.size]
|
||||
ui.value.icon.leading.padding[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
const trailingIconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.icon.trailing.wrapper,
|
||||
ui.value.icon.trailing.spacing[props.size]
|
||||
ui.value.icon.trailing.padding[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
input,
|
||||
isLeading,
|
||||
isTrailing,
|
||||
inputClass,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
:class="selectClass"
|
||||
@input="onInput(($event.target as any).value)"
|
||||
@input="onInput"
|
||||
>
|
||||
<template v-for="(option, index) in normalizedOptionsWithPlaceholder">
|
||||
<optgroup
|
||||
@@ -17,7 +17,7 @@
|
||||
:label="option[textAttribute]"
|
||||
>
|
||||
<option
|
||||
v-for="(childOption, index2) in (option.children as any[])"
|
||||
v-for="(childOption, index2) in option.children"
|
||||
:key="`${childOption[valueAttribute]}-${index}-${index2}`"
|
||||
:value="childOption[valueAttribute]"
|
||||
:selected="childOption[valueAttribute] === normalizedValue"
|
||||
@@ -37,21 +37,21 @@
|
||||
</select>
|
||||
|
||||
<div v-if="icon" :class="leadingIconClass">
|
||||
<Icon :name="icon" :class="iconClass" />
|
||||
<UIcon :name="icon" :class="iconClass" />
|
||||
</div>
|
||||
|
||||
<span :class="trailingIconClass">
|
||||
<Icon name="i-heroicons-chevron-down-20-solid" :class="iconClass" aria-hidden="true" />
|
||||
<span v-if="trailingIcon" :class="trailingIconClass">
|
||||
<UIcon :name="trailingIcon" :class="iconClass" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { PropType, ComputedRef } from 'vue'
|
||||
import { get } from 'lodash-es'
|
||||
import { defu } from 'defu'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { classNames } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
@@ -62,7 +62,7 @@ import appConfig from '#build/app.config'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Icon
|
||||
UIcon
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
@@ -89,6 +89,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
trailingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.trailingIcon
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
@@ -127,8 +131,8 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defu({}, props.ui, appConfig.ui.select))
|
||||
|
||||
const onInput = (value: string) => {
|
||||
emit('update:modelValue', value)
|
||||
const onInput = (event: InputEvent) => {
|
||||
emit('update:modelValue', (event.target as any).value)
|
||||
}
|
||||
|
||||
const guessOptionValue = (option: any) => {
|
||||
@@ -158,7 +162,7 @@ export default defineComponent({
|
||||
return props.options.map(option => normalizeOption(option))
|
||||
})
|
||||
|
||||
const normalizedOptionsWithPlaceholder = computed(() => {
|
||||
const normalizedOptionsWithPlaceholder: ComputedRef<{ disabled?: boolean, children: { disabled?: boolean }[] }[]> = computed(() => {
|
||||
if (!props.placeholder) {
|
||||
return normalizedOptions.value
|
||||
}
|
||||
@@ -187,10 +191,10 @@ export default defineComponent({
|
||||
return classNames(
|
||||
ui.value.base,
|
||||
ui.value.size[props.size],
|
||||
ui.value.spacing[props.size],
|
||||
ui.value.padding[props.size],
|
||||
ui.value.appearance[props.appearance],
|
||||
!!props.icon && ui.value.leading.spacing[props.size],
|
||||
ui.value.trailing.spacing[props.size],
|
||||
!!props.icon && ui.value.leading.padding[props.size],
|
||||
ui.value.trailing.padding[props.size],
|
||||
ui.value.custom
|
||||
)
|
||||
})
|
||||
@@ -205,14 +209,14 @@ export default defineComponent({
|
||||
const leadingIconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.icon.leading.wrapper,
|
||||
ui.value.icon.leading.spacing[props.size]
|
||||
ui.value.icon.leading.padding[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
const trailingIconClass = computed(() => {
|
||||
return classNames(
|
||||
ui.value.icon.trailing.wrapper,
|
||||
ui.value.icon.trailing.spacing[props.size]
|
||||
ui.value.icon.trailing.padding[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -11,8 +11,14 @@
|
||||
:class="ui.wrapper"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<!-- TODO: check that `name` fixes required -->
|
||||
<!-- <input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1" aria-hidden="true"> -->
|
||||
<input
|
||||
v-if="required"
|
||||
:value="modelValue"
|
||||
:required="required"
|
||||
class="absolute inset-0 w-px opacity-0 cursor-default"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<component
|
||||
:is="searchable ? 'ComboboxButton' : 'ListboxButton'"
|
||||
@@ -24,16 +30,16 @@
|
||||
<slot :open="open" :disabled="disabled">
|
||||
<button :class="selectMenuClass" :disabled="disabled" type="button">
|
||||
<span v-if="icon" :class="leadingIconClass">
|
||||
<Icon :name="icon" :class="iconClass" />
|
||||
<UIcon :name="icon" :class="iconClass" />
|
||||
</span>
|
||||
|
||||
<slot name="label">
|
||||
<span v-if="modelValue" class="block truncate">{{ typeof modelValue === 'string' ? modelValue : (modelValue as any)[optionAttribute] }}</span>
|
||||
<span v-if="modelValue" class="block truncate">{{ typeof modelValue === 'string' ? modelValue : modelValue[optionAttribute] }}</span>
|
||||
<span v-else class="block truncate text-gray-400 dark:text-gray-500">{{ placeholder || ' ' }}</span>
|
||||
</slot>
|
||||
|
||||
<span :class="trailingIconClass">
|
||||
<Icon name="i-heroicons-chevron-down-20-solid" :class="iconClass" aria-hidden="true" />
|
||||
<span v-if="trailingIcon" :class="trailingIconClass">
|
||||
<UIcon :name="trailingIcon" :class="iconClass" aria-hidden="true" />
|
||||
</span>
|
||||
</button>
|
||||
</slot>
|
||||
@@ -41,7 +47,7 @@
|
||||
|
||||
<div v-if="open" ref="container" :class="[ui.container, ui.width]">
|
||||
<transition v-bind="ui.transition">
|
||||
<component :is="searchable ? 'ComboboxOptions' : 'ListboxOptions'" static :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.spacing, ui.height]">
|
||||
<component :is="searchable ? 'ComboboxOptions' : 'ListboxOptions'" static :class="[ui.base, ui.divide, ui.ring, ui.rounded, ui.shadow, ui.background, ui.padding, ui.height]">
|
||||
<ComboboxInput
|
||||
v-if="searchable"
|
||||
ref="searchInput"
|
||||
@@ -62,11 +68,11 @@
|
||||
:value="option"
|
||||
:disabled="option.disabled"
|
||||
>
|
||||
<li :class="resolveOptionClass({ active, disabled: optionDisabled })">
|
||||
<li :class="[ui.option.base, ui.option.rounded, ui.option.padding, ui.option.size, ui.option.color, active ? ui.option.active : ui.option.inactive, selected && ui.option.selected, optionDisabled && ui.option.disabled]">
|
||||
<div :class="ui.option.container">
|
||||
<slot name="option" :option="option" :active="active" :selected="selected">
|
||||
<Icon v-if="option.icon" :name="option.icon" :class="[ui.option.icon.base, active ? ui.option.icon.active : ui.option.icon.inactive, option.iconClass]" aria-hidden="true" />
|
||||
<Avatar
|
||||
<UIcon v-if="option.icon" :name="option.icon" :class="[ui.option.icon.base, active ? ui.option.icon.active : ui.option.icon.inactive, option.iconClass]" aria-hidden="true" />
|
||||
<UAvatar
|
||||
v-else-if="option.avatar"
|
||||
v-bind="{ size: ui.option.avatar.size, ...option.avatar }"
|
||||
:class="ui.option.avatar.base"
|
||||
@@ -78,14 +84,14 @@
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<span v-if="selected" :class="ui.option.selected.wrapper">
|
||||
<Icon :name="selectedIcon" :class="ui.option.selected.icon" aria-hidden="true" />
|
||||
<span v-if="selected" :class="[ui.option.selectedIcon.wrapper, ui.option.selectedIcon.padding]">
|
||||
<UIcon :name="selectedIcon" :class="ui.option.selectedIcon.base" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</component>
|
||||
|
||||
<component :is="searchable ? 'ComboboxOption' : 'ListboxOption'" v-if="creatable && queryOption && !filteredOptions.length" v-slot="{ active, selected }" :value="queryOption" as="template">
|
||||
<li :class="resolveOptionClass({ active })">
|
||||
<li :class="[ui.option.base, ui.option.rounded, ui.option.padding, ui.option.size, ui.option.color, active ? ui.option.active : ui.option.inactive]">
|
||||
<div :class="ui.option.container">
|
||||
<slot name="option-create" :option="queryOption" :active="active" :selected="selected">
|
||||
<span class="block truncate">Create "{{ queryOption[optionAttribute] }}"</span>
|
||||
@@ -95,7 +101,7 @@
|
||||
</component>
|
||||
<p v-else-if="searchable && query && !filteredOptions.length" :class="ui.option.empty">
|
||||
<slot name="option-empty" :query="query">
|
||||
No results found for "{{ query }}".
|
||||
No results for "{{ query }}".
|
||||
</slot>
|
||||
</p>
|
||||
</component>
|
||||
@@ -109,8 +115,8 @@ import { ref, computed, watch, defineComponent } from 'vue'
|
||||
import type { PropType, ComponentPublicInstance } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Combobox, ComboboxButton, ComboboxOptions, ComboboxOption, ComboboxInput, Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/vue'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Avatar from '../elements/Avatar.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UAvatar from '../elements/Avatar.vue'
|
||||
import { classNames } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from '../../types'
|
||||
@@ -132,8 +138,8 @@ export default defineComponent({
|
||||
ListboxButton,
|
||||
ListboxOptions,
|
||||
ListboxOption,
|
||||
Icon,
|
||||
Avatar
|
||||
UIcon,
|
||||
UAvatar
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
@@ -160,6 +166,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
trailingIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.select.default.trailingIcon
|
||||
},
|
||||
selectedIcon: {
|
||||
type: String,
|
||||
default: () => appConfig.ui.selectMenu.default.selectedIcon
|
||||
@@ -240,10 +250,10 @@ export default defineComponent({
|
||||
'text-left cursor-default',
|
||||
uiSelect.value.size[props.size],
|
||||
uiSelect.value.gap[props.size],
|
||||
uiSelect.value.spacing[props.size],
|
||||
uiSelect.value.padding[props.size],
|
||||
uiSelect.value.appearance[props.appearance],
|
||||
!!props.icon && uiSelect.value.leading.spacing[props.size],
|
||||
uiSelect.value.trailing.spacing[props.size],
|
||||
!!props.icon && uiSelect.value.leading.padding[props.size],
|
||||
uiSelect.value.trailing.padding[props.size],
|
||||
uiSelect.value.custom,
|
||||
'inline-flex items-center'
|
||||
)
|
||||
@@ -259,14 +269,14 @@ export default defineComponent({
|
||||
const leadingIconClass = computed(() => {
|
||||
return classNames(
|
||||
uiSelect.value.icon.leading.wrapper,
|
||||
uiSelect.value.icon.leading.spacing[props.size]
|
||||
uiSelect.value.icon.leading.padding[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
const trailingIconClass = computed(() => {
|
||||
return classNames(
|
||||
uiSelect.value.icon.trailing.wrapper,
|
||||
uiSelect.value.icon.trailing.spacing[props.size]
|
||||
uiSelect.value.icon.trailing.padding[props.size]
|
||||
)
|
||||
})
|
||||
|
||||
@@ -292,14 +302,6 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
function resolveOptionClass ({ active, disabled }: { active: boolean, disabled?: boolean }) {
|
||||
return classNames(
|
||||
ui.value.option.base,
|
||||
active ? ui.value.option.active : ui.value.option.inactive,
|
||||
disabled && ui.value.option.disabled
|
||||
)
|
||||
}
|
||||
|
||||
function onUpdate (event: any) {
|
||||
if (query.value && searchInput.value?.$el) {
|
||||
query.value = ''
|
||||
@@ -321,7 +323,6 @@ export default defineComponent({
|
||||
filteredOptions,
|
||||
queryOption,
|
||||
query,
|
||||
resolveOptionClass,
|
||||
onUpdate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
:placeholder="placeholder"
|
||||
:autocomplete="autocomplete"
|
||||
:class="textareaClass"
|
||||
@input="onInput(($event.target as any).value)"
|
||||
@input="onInput"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
/>
|
||||
@@ -128,10 +128,10 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const onInput = (value: string) => {
|
||||
const onInput = (event: InputEvent) => {
|
||||
autoResize()
|
||||
|
||||
emit('update:modelValue', value)
|
||||
emit('update:modelValue', (event.target as any).value)
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
@@ -149,7 +149,7 @@ export default defineComponent({
|
||||
return classNames(
|
||||
ui.value.base,
|
||||
ui.value.size[props.size],
|
||||
ui.value.spacing[props.size],
|
||||
ui.value.padding[props.size],
|
||||
ui.value.appearance[props.appearance],
|
||||
!props.resize && 'resize-none',
|
||||
ui.value.custom
|
||||
@@ -159,6 +159,7 @@ export default defineComponent({
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
textarea,
|
||||
textareaClass,
|
||||
onInput
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
>
|
||||
<span :class="[active ? ui.container.active : ui.container.inactive, ui.container.base]">
|
||||
<span v-if="iconOn" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
|
||||
<Icon :name="iconOn" :class="ui.icon.on" />
|
||||
<UIcon :name="iconOn" :class="ui.icon.on" />
|
||||
</span>
|
||||
<span v-if="iconOff" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
|
||||
<Icon :name="iconOff" :class="ui.icon.off" />
|
||||
<span v-if="iconOff" :class="[active ? ui.icon.inactive : ui.icon.active, ui.icon.base]" aria-hidden="true">
|
||||
<UIcon :name="iconOff" :class="ui.icon.off" />
|
||||
</span>
|
||||
</span>
|
||||
</Switch>
|
||||
@@ -19,7 +19,7 @@ import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { Switch } from '@headlessui/vue'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
@@ -31,7 +31,7 @@ export default defineComponent({
|
||||
components: {
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Switch,
|
||||
Icon
|
||||
UIcon
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<component
|
||||
:is="$attrs.onSubmit ? 'form': 'div'"
|
||||
:is="$attrs.onSubmit ? 'form': as"
|
||||
:class="[ui.base, ui.rounded, ui.divide, ui.ring, ui.shadow, ui.background]"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<div v-if="$slots.header" :class="[ui.header.base, ui.header.spacing, ui.header.background]">
|
||||
<div v-if="$slots.header" :class="[ui.header.base, ui.header.padding, ui.header.background]">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div :class="[ui.body.base, ui.body.spacing, ui.body.background]">
|
||||
<div :class="[ui.body.base, ui.body.padding, ui.body.background]">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="$slots.footer" :class="[ui.footer.base, ui.footer.spacing, ui.footer.background]">
|
||||
<div v-if="$slots.footer" :class="[ui.footer.base, ui.footer.padding, ui.footer.background]">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</component>
|
||||
@@ -30,6 +30,10 @@ import appConfig from '#build/app.config'
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: 'div'
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.card>>,
|
||||
default: () => appConfig.ui.card
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div :class="[ui.base, ui.spacing, ui.constrained]">
|
||||
<component :is="as" :class="[ui.base, ui.padding, ui.constrained]">
|
||||
<slot />
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -17,6 +17,10 @@ import appConfig from '#build/app.config'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
as: {
|
||||
type: String,
|
||||
default: 'div'
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.container>>,
|
||||
default: () => appConfig.ui.container
|
||||
|
||||
35
src/runtime/components/layout/Skeleton.vue
Normal file
35
src/runtime/components/layout/Skeleton.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div :class="[ui.base, ui.background, ui.rounded]" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.skeleton>>,
|
||||
default: () => appConfig.ui.skeleton
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.skeleton>>(() => defu({}, props.ui, appConfig.ui.skeleton))
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<Combobox
|
||||
ref="comboboxRef"
|
||||
:by="by"
|
||||
:model-value="modelValue"
|
||||
:multiple="multiple"
|
||||
@@ -8,18 +7,18 @@
|
||||
@update:model-value="onSelect"
|
||||
>
|
||||
<div :class="ui.wrapper">
|
||||
<div v-if="searchable" :class="ui.input.wrapper">
|
||||
<Icon v-if="icon" :name="icon" :class="ui.input.icon" aria-hidden="true" />
|
||||
<div v-show="searchable" :class="ui.input.wrapper">
|
||||
<UIcon v-if="icon" :name="icon" :class="[ui.input.icon.base, ui.input.icon.size]" aria-hidden="true" />
|
||||
<ComboboxInput
|
||||
ref="comboboxInput"
|
||||
:value="query"
|
||||
:class="[ui.input.base, icon && ui.input.spacing]"
|
||||
:class="[ui.input.base, ui.input.size, ui.input.height, ui.input.padding, icon && ui.input.icon.padding]"
|
||||
:placeholder="placeholder"
|
||||
autocomplete="off"
|
||||
@change="query = $event.target.value"
|
||||
/>
|
||||
|
||||
<Button
|
||||
<UButton
|
||||
v-if="close"
|
||||
v-bind="close"
|
||||
:class="ui.input.close"
|
||||
@@ -53,7 +52,7 @@
|
||||
</ComboboxOptions>
|
||||
|
||||
<div v-else-if="empty" :class="ui.empty.wrapper">
|
||||
<Icon v-if="empty.icon" :name="empty.icon" :class="ui.empty.icon" aria-hidden="true" />
|
||||
<UIcon v-if="empty.icon" :name="empty.icon" :class="ui.empty.icon" aria-hidden="true" />
|
||||
<p :class="query ? ui.empty.queryLabel : ui.empty.label">
|
||||
{{ query ? empty.queryLabel : empty.label }}
|
||||
</p>
|
||||
@@ -72,8 +71,8 @@ import { groupBy, map } from 'lodash-es'
|
||||
import { defu } from 'defu'
|
||||
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||
import type { Group, Command } from '../../types/command-palette'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Button from '../elements/Button.vue'
|
||||
import UIcon from '../elements/Icon.vue'
|
||||
import UButton from '../elements/Button.vue'
|
||||
import type { Button as ButtonType } from '../../types/button'
|
||||
import CommandPaletteGroup from './CommandPaletteGroup.vue'
|
||||
import { useAppConfig } from '#imports'
|
||||
@@ -88,9 +87,8 @@ export default defineComponent({
|
||||
Combobox,
|
||||
ComboboxInput,
|
||||
ComboboxOptions,
|
||||
Icon,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Button,
|
||||
UIcon,
|
||||
UButton,
|
||||
CommandPaletteGroup
|
||||
},
|
||||
props: {
|
||||
@@ -292,6 +290,7 @@ export default defineComponent({
|
||||
ui,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
groups,
|
||||
comboboxInput,
|
||||
query,
|
||||
onSelect,
|
||||
onClear
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user